99 lines
3.0 KiB
Python
99 lines
3.0 KiB
Python
"""PiCopy – Konfiguration, Pfade, Konstanten, Logging."""
|
||
|
||
import os
|
||
import json
|
||
import logging
|
||
from pathlib import Path
|
||
|
||
RAW_BASE = 'https://git.leuschner.dev/Tobias/PiCopy/raw/branch/main'
|
||
VERSION_FILE = Path(__file__).parent.parent / 'version.txt'
|
||
|
||
|
||
def load_installed_version():
|
||
try:
|
||
return VERSION_FILE.read_text(encoding='utf-8').strip() or '1.0.4'
|
||
except Exception:
|
||
return 'X.X.X'
|
||
|
||
|
||
VERSION = load_installed_version()
|
||
|
||
BASE_DIR = Path('/opt/picopy')
|
||
CONFIG_FILE = BASE_DIR / 'config.json'
|
||
STATE_FILE = BASE_DIR / 'state.json'
|
||
LOG_DIR = BASE_DIR / 'logs'
|
||
LOG_FILE = LOG_DIR / 'picopy.log'
|
||
INTERNAL_DEST_DIR = BASE_DIR / 'internal'
|
||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||
HISTORY_FILE = BASE_DIR / 'history.json'
|
||
MAX_HISTORY = 100
|
||
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s %(levelname)s %(message)s',
|
||
handlers=[logging.FileHandler(LOG_FILE), logging.StreamHandler()]
|
||
)
|
||
log = logging.getLogger('picopy')
|
||
|
||
NM_AP_CON = 'PiCopy-AP'
|
||
NM_CLIENT_CON = 'PiCopy-WiFi'
|
||
WIFI_BOOT_WAIT = 25 # Sekunden warten beim Start bevor AP gestartet wird
|
||
|
||
DEFAULT_CONFIG = {
|
||
# USB
|
||
'source_ports': [], # [{port, label}, ...]
|
||
'source_port': None, 'source_label': '', # Migration legacy
|
||
'dest_port': None, 'dest_label': '',
|
||
'dest_type': 'usb', 'internal_dest_label': 'Interner Speicher',
|
||
'internal_share_enabled': False,
|
||
'folder_format': '%Y-%m-%d', 'add_time': True,
|
||
'subfolder': True, 'auto_copy': True,
|
||
'file_filter': '', 'exclude_system': True,
|
||
'duplicate_handling': 'skip',
|
||
'verify_checksum': False, 'delete_source': False,
|
||
# WiFi
|
||
'wifi_ssid': '', 'wifi_password': '',
|
||
'ap_ssid': 'PiCopy', 'ap_password': 'PiCopy,',
|
||
# WireGuard
|
||
'wireguard_auto': False,
|
||
}
|
||
|
||
|
||
def load_cfg():
|
||
cfg = DEFAULT_CONFIG.copy()
|
||
try:
|
||
if CONFIG_FILE.exists():
|
||
cfg.update(json.loads(CONFIG_FILE.read_text(encoding='utf-8')))
|
||
except (json.JSONDecodeError, ValueError) as e:
|
||
log.error(f'config.json korrupt ({e}), verwende Standardwerte')
|
||
try: CONFIG_FILE.rename(CONFIG_FILE.with_suffix('.corrupt'))
|
||
except Exception: pass
|
||
except Exception as e:
|
||
log.warning(f'config.json nicht lesbar: {e}')
|
||
return cfg
|
||
|
||
|
||
def save_cfg(cfg):
|
||
_atomic_write(CONFIG_FILE, json.dumps(cfg, indent=2))
|
||
|
||
|
||
def _atomic_write(path: Path, content: str) -> None:
|
||
"""Schreibt atomar: erst .tmp, dann os.replace() - sicher bei Stromausfall."""
|
||
tmp = path.with_suffix(path.suffix + '.tmp')
|
||
try:
|
||
tmp.write_text(content, encoding='utf-8')
|
||
with open(tmp, 'rb') as fh:
|
||
os.fsync(fh.fileno()) # Daten wirklich auf Datenträger schreiben
|
||
os.replace(str(tmp), str(path)) # Atomares Umbenennen (POSIX-Garantie)
|
||
except Exception:
|
||
try: tmp.unlink(missing_ok=True)
|
||
except Exception: pass
|
||
raise
|
||
|
||
|
||
def _fmt_bytes(b):
|
||
if b < 1024: return f'{b} B'
|
||
if b < 1024**2: return f'{b/1024:.1f} KB'
|
||
if b < 1024**3: return f'{b/1024**2:.1f} MB'
|
||
return f'{b/1024**3:.2f} GB'
|