ebuild_rasp2/ebuild/io/config_ini.py

334 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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")