334 lines
10 KiB
Python
334 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
||
__title__ = "config_ini"
|
||
__author__ = 'Mehmet Karatay & "Saraswati" (ChatGPT)'
|
||
__purpose__ = "INI tabanlı konfigürasyon ve değer depolama yardımcıları"
|
||
__version__ = "0.2.0"
|
||
__date__ = "2025-11-20"
|
||
|
||
"""
|
||
ebuild/io/config_ini.py
|
||
|
||
Revision : 2025-11-20
|
||
Authors : Mehmet Karatay & "Saraswati" (ChatGPT)
|
||
|
||
Amaç
|
||
-----
|
||
Eski EdmConfig yapısının modernize edilmiş, hatalardan arındırılmış ama
|
||
aynı API'yi koruyan sürümü.
|
||
|
||
Bu modül:
|
||
- INI dosyalarını ConfigParser ile yönetir
|
||
- Eksik section/item gördüğünde otomatik oluşturur
|
||
- Dosya değişimini (mtime) izleyerek reload imkanı verir
|
||
- Basit bir kilit (lock) mekanizması ile aynı dosyaya birden fazla
|
||
yazma girişimini sıraya sokar.
|
||
|
||
Kullanım Örnekleri
|
||
------------------
|
||
- EdmConfig:
|
||
cfg = EdmConfig("/home/karatay/ebuild/config.ini")
|
||
port = cfg.item("serial", "ttyUSB0", "/dev/ttyUSB0")
|
||
|
||
- ConfigDict:
|
||
section = cfg.get_section("serial")
|
||
tty0 = section.get_item("ttyUSB0")
|
||
|
||
- KilitliDosya:
|
||
log = KilitliDosya("/var/log/ebuild.log")
|
||
log.yaz("merhaba dünya\\n")
|
||
"""
|
||
|
||
import os
|
||
import time
|
||
from configparser import ConfigParser
|
||
import traceback as tb # eski davranışla uyum için bırakıldı
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# ConfigDict – tek bir section'ın sözlük hali
|
||
# ---------------------------------------------------------------------------
|
||
class ConfigDict:
|
||
"""
|
||
Bir INI dosyasındaki tek bir section'ı sözlük benzeri interface ile
|
||
kullanmaya yarar.
|
||
|
||
- Anahtar yoksa, otomatik olarak section içine eklenir ve boş string
|
||
ile başlatılır.
|
||
- Değerler her zaman string olarak saklanır.
|
||
"""
|
||
|
||
def __init__(self, cfgfile: str, section_name: str):
|
||
self.cfgfile = cfgfile
|
||
self.section_name = section_name
|
||
|
||
self.cfg = ConfigParser()
|
||
self.cfg.read(cfgfile)
|
||
|
||
if not self.cfg.has_section(section_name):
|
||
self.cfg.add_section(section_name)
|
||
self._write()
|
||
|
||
self.section = dict(self.cfg.items(section_name))
|
||
|
||
def _write(self) -> None:
|
||
"""INI dosyasını diske yazar."""
|
||
with open(self.cfgfile, "w") as f:
|
||
self.cfg.write(f)
|
||
|
||
def get_item(self, item_name: str) -> str:
|
||
"""
|
||
Section içindeki bir item’ı döndürür.
|
||
Yoksa item'i oluşturur, boş string ile başlatır.
|
||
"""
|
||
try:
|
||
return self.section[item_name]
|
||
except KeyError:
|
||
# Yoksa ekle
|
||
self.cfg.set(self.section_name, item_name, "")
|
||
self._write()
|
||
|
||
# Yeniden oku
|
||
self.cfg.read(self.cfgfile)
|
||
self.section = dict(self.cfg.items(self.section_name))
|
||
return self.section.get(item_name, "")
|
||
|
||
def set_item(self, item_name: str, value) -> None:
|
||
"""
|
||
Section içindeki bir item’ın değerini günceller (string'e çevirerek).
|
||
"""
|
||
value = str(value)
|
||
self.cfg.set(self.section_name, item_name, value)
|
||
self._write()
|
||
self.section[item_name] = value
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# EdmConfig – bir INI dosyasını yöneten üst sınıf
|
||
# ---------------------------------------------------------------------------
|
||
class EdmConfig:
|
||
"""
|
||
Tek bir INI dosyası için üst seviye sarmalayıcı.
|
||
|
||
Özellikler:
|
||
- Dosya yoksa otomatik oluşturur.
|
||
- item(section, name, default) ile değer okur; yoksa default yazar.
|
||
- reload() ile mtime kontrolü yaparak dosya değişimini algılar.
|
||
"""
|
||
|
||
def __init__(self, cfg_file_name: str):
|
||
self.fname = cfg_file_name
|
||
|
||
# Dosya yoksa oluştur
|
||
if not os.path.isfile(cfg_file_name):
|
||
with open(cfg_file_name, "w") as f:
|
||
f.write("")
|
||
|
||
self.cfg = ConfigParser()
|
||
self.cfg.read(cfg_file_name)
|
||
|
||
# İlk yükleme zamanı
|
||
try:
|
||
self.originalTime = os.path.getmtime(cfg_file_name)
|
||
except OSError:
|
||
self.originalTime = None
|
||
|
||
# ---------------------------
|
||
# Reload mekanizması
|
||
# ---------------------------
|
||
def get_loadtime(self) -> float:
|
||
"""Ini dosyasının son yüklenme zamanını (mtime) döndürür."""
|
||
return self.originalTime
|
||
|
||
def reload(self) -> bool:
|
||
"""
|
||
Dosya değişmişse yeniden okur, True döner.
|
||
Değişmemişse False döner.
|
||
"""
|
||
if self.fname is None:
|
||
return False
|
||
|
||
try:
|
||
current = os.path.getmtime(self.fname)
|
||
except FileNotFoundError:
|
||
return False
|
||
|
||
if self.originalTime is None or current > self.originalTime:
|
||
self.cfg.read(self.fname)
|
||
self.originalTime = current
|
||
return True
|
||
|
||
return False
|
||
|
||
# ---------------------------
|
||
# Section & item işlemleri
|
||
# ---------------------------
|
||
def add_section(self, section: str) -> None:
|
||
"""Yeni bir section ekler (yoksa)."""
|
||
if not self.cfg.has_section(section):
|
||
self.cfg.add_section(section)
|
||
self._write()
|
||
|
||
def add_item(self, section: str, item: str, value: str = "") -> None:
|
||
"""
|
||
İlgili section yoksa oluşturur, item yoksa ekler ve default
|
||
değerini yazar.
|
||
"""
|
||
if not self.cfg.has_section(section):
|
||
self.add_section(section)
|
||
|
||
if not self.cfg.has_option(section, item):
|
||
self.cfg.set(section, item, str(value))
|
||
self._write()
|
||
|
||
def set_item(self, section: str, name: str, value="") -> None:
|
||
"""
|
||
Belirli bir section/key için değeri ayarlar (string'e çevirir).
|
||
"""
|
||
self.add_section(section)
|
||
self.cfg.set(section, name, str(value))
|
||
self._write()
|
||
|
||
def item(self, section: str, name: str, default="") -> str:
|
||
"""
|
||
INI'den item okur; yoksa veya boşsa default değeri yazar ve onu döndürür.
|
||
|
||
eski davranışla uyumlu: her zaman string döner.
|
||
"""
|
||
try:
|
||
val = self.cfg.get(section, name).strip()
|
||
if val == "":
|
||
self.set_item(section, name, default)
|
||
return str(default)
|
||
return val
|
||
except Exception:
|
||
# Eski koddaki gibi stack trace istersen:
|
||
# print(tb.format_exc())
|
||
self.add_item(section, name, default)
|
||
return str(default)
|
||
|
||
def get_items(self, section: str) -> dict:
|
||
"""Verilen section'daki tüm key/value çiftlerini dict olarak döndürür."""
|
||
return dict(self.cfg.items(section))
|
||
|
||
def get_section(self, section_name: str) -> ConfigDict:
|
||
"""
|
||
Verilen section için ConfigDict nesnesi döndürür.
|
||
Section yoksa oluşturur.
|
||
"""
|
||
if not self.cfg.has_section(section_name):
|
||
self.cfg.add_section(section_name)
|
||
self._write()
|
||
return ConfigDict(self.fname, section_name)
|
||
|
||
def get_section_names(self):
|
||
"""INI içindeki tüm section isimlerini döndürür."""
|
||
return self.cfg.sections()
|
||
|
||
def get_key_names(self, section_name: str = ""):
|
||
"""
|
||
section_name verilirse o section altındaki key listesi,
|
||
verilmezse her section için key listelerini döndürür.
|
||
"""
|
||
if section_name:
|
||
if not self.cfg.has_section(section_name):
|
||
return []
|
||
return list(self.cfg[section_name].keys())
|
||
|
||
return {s: list(self.cfg[s].keys()) for s in self.cfg.sections()}
|
||
|
||
def get_section_values(self, section_name: str = ""):
|
||
"""
|
||
section_name verilirse o section'ın dict halini,
|
||
verilmezse tüm section'ların dict halini döndürür.
|
||
"""
|
||
if section_name:
|
||
if not self.cfg.has_section(section_name):
|
||
return {}
|
||
return dict(self.cfg.items(section_name))
|
||
return {s: dict(self.cfg.items(s)) for s in self.cfg.sections()}
|
||
|
||
def _write(self) -> None:
|
||
"""Ini dosyasını diske yazar."""
|
||
with open(self.fname, "w") as f:
|
||
self.cfg.write(f)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# KilitliDosya – çok basit file lock mekanizması
|
||
# ---------------------------------------------------------------------------
|
||
class KilitliDosya:
|
||
"""
|
||
Çok basit bir dosya kilit mekanizması.
|
||
|
||
Aynı dosyaya birden fazla sürecin/yazmanın çakışmasını azaltmak için
|
||
`fname.LCK` dosyasını lock olarak kullanır.
|
||
"""
|
||
|
||
def __init__(self, fname: str):
|
||
self.fname = fname
|
||
|
||
def _lockfile(self) -> str:
|
||
return f"{self.fname}.LCK"
|
||
|
||
def kontrol(self) -> bool:
|
||
"""Lock dosyası var mı? True/False."""
|
||
return os.path.exists(self._lockfile())
|
||
|
||
def kilitle(self) -> bool:
|
||
"""
|
||
Lock almaya çalışır. Lock yoksa oluşturur ve True döner.
|
||
Varsa False döner.
|
||
"""
|
||
if not self.kontrol():
|
||
with open(self._lockfile(), "w") as f:
|
||
f.write(" ")
|
||
return True
|
||
return False
|
||
|
||
def kilit_ac(self) -> None:
|
||
"""Lock dosyasını kaldırır (yoksa sessizce geçer)."""
|
||
try:
|
||
os.remove(self._lockfile())
|
||
except FileNotFoundError:
|
||
pass
|
||
|
||
def oku(self):
|
||
"""Ana dosyayı satır satır okur ve liste döndürür."""
|
||
with open(self.fname, "r") as f:
|
||
return f.readlines()
|
||
|
||
def yaz(self, text: str) -> bool:
|
||
"""
|
||
Dosyaya kilitleyerek ekleme yapar.
|
||
Lock alamazsa 10 deneme yapar, her seferinde 0.2s bekler.
|
||
"""
|
||
for _ in range(10):
|
||
if self.kilitle():
|
||
try:
|
||
with open(self.fname, "a") as f:
|
||
f.write(text)
|
||
finally:
|
||
self.kilit_ac()
|
||
return True
|
||
time.sleep(0.2)
|
||
return False
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Modül test / örnek kullanım
|
||
# ---------------------------------------------------------------------------
|
||
if __name__ == "__main__":
|
||
# Basit smoke-test
|
||
test_ini = "test_config.ini"
|
||
cfg = EdmConfig(test_ini)
|
||
|
||
# Birkaç değer dene
|
||
port = cfg.item("serial", "ttyUSB0", "/dev/ttyUSB0")
|
||
mode = cfg.item("general", "mode", "auto")
|
||
print("serial.ttyUSB0 =", port)
|
||
print("general.mode =", mode)
|
||
|
||
# KilitliDosya testi
|
||
log = KilitliDosya("test_log.txt")
|
||
log.yaz("Config_ini self test OK\n")
|