Als EXE lagen Journal, Berichte und Log bisher direkt neben der EXE. Damit laesst sich das Programm nicht in einen Ordner legen, in den Windows nicht schreiben laesst - und beim Sichern uebersieht man leicht die Haelfte. Jetzt: beim ersten Start ein Ordner "Daten" neben der EXE, alles darin. Geht das nicht, weicht es nach LOCALAPPDATA aus. Was schon neben der EXE lag, zieht mit um - sonst waere das Journal ploetzlich leer. Ist der gemerkte Ordner nicht erreichbar (Stick ab, NAS aus), wird NICHTS geaendert und der Merkzettel bleibt stehen: morgen steckt der Stick wieder dran und alles ist da, wo es war. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Wo das Programm seine Daten ablegt.
|
|
|
|
Als EXE legt sich das Programm beim ersten Start einen Ordner an - dort liegen
|
|
Journal, Berichte und Log beieinander. Zum Sichern reicht damit dieser eine
|
|
Ordner.
|
|
|
|
1. neben der EXE ein Unterordner "Daten"
|
|
2. geht das nicht (EXE liegt z. B. unter "Programme"), dann der Ordner
|
|
"Beherbergungssteuer" unter LOCALAPPDATA
|
|
|
|
Was schon da ist, wird benutzt; was fehlt, wird angelegt. Lagen die Dateien
|
|
noch direkt neben der EXE - so war es frueher -, ziehen sie mit um. Sonst waere
|
|
das Journal ploetzlich "leer" und die Meldung ans Amt faengt bei null an.
|
|
|
|
Aus der Quelle heraus (python app.py) bleibt alles, wo es immer war.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
log = logging.getLogger("bst.ablage")
|
|
|
|
if getattr(sys, "frozen", False):
|
|
EXE_DIR = os.path.dirname(os.path.abspath(sys.argv[0]))
|
|
else:
|
|
EXE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# Diese Dateien und Ordner gehoeren dem Programm und ziehen mit um.
|
|
EIGENE_DATEN = ("journal.sqlite3", "beherbergungssteuer.log", "berichte")
|
|
|
|
|
|
def _beschreibbar(ordner):
|
|
"""Kann in diesen Ordner geschrieben werden? Wird ausprobiert, nicht geraten."""
|
|
try:
|
|
os.makedirs(ordner, exist_ok=True)
|
|
probe = os.path.join(ordner, ".schreibprobe")
|
|
with open(probe, "w") as f:
|
|
f.write("x")
|
|
os.remove(probe)
|
|
return True
|
|
except OSError:
|
|
return False
|
|
|
|
|
|
def _zeiger_datei():
|
|
"""Merkzettel, WO der Datenordner liegt - liegt bewusst ausserhalb davon."""
|
|
heim = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~")
|
|
return os.path.join(heim, "Beherbergungssteuer", "datenordner.txt")
|
|
|
|
|
|
def _gemerkter_ordner():
|
|
"""(Pfad, Lage): Lage ist "ok", "unerreichbar" oder None (kein Merkzettel).
|
|
|
|
Faengt bewusst mehr als OSError ab: ein zerrissener Merkzettel (Stromausfall
|
|
beim Schreiben) enthaelt Null-Bytes oder kaputte Zeichen - der Fehler wuerde
|
|
sonst beim Import fliegen, also ohne Fenster.
|
|
"""
|
|
try:
|
|
with open(_zeiger_datei(), "r", encoding="utf-8") as f:
|
|
pfad = f.read().strip()
|
|
except (OSError, ValueError, UnicodeDecodeError) as e:
|
|
log.debug("Merkzettel nicht lesbar: %s", e)
|
|
return None, None
|
|
if not pfad or "\x00" in pfad or not os.path.isabs(pfad):
|
|
log.warning("Merkzettel unbrauchbar - es wird neu angelegt")
|
|
return None, None
|
|
try:
|
|
laufwerk = os.path.splitdrive(pfad)[0]
|
|
erreichbar = (not laufwerk) or os.path.exists(
|
|
laufwerk if laufwerk.startswith("\\\\") else laufwerk + os.sep)
|
|
except (OSError, ValueError):
|
|
erreichbar = False
|
|
if not erreichbar:
|
|
return pfad, "unerreichbar"
|
|
return (pfad, "ok") if _beschreibbar(pfad) else (pfad, "unerreichbar")
|
|
|
|
|
|
def _ordner_merken(pfad):
|
|
"""Erst daneben schreiben, dann umlegen - so ist der Merkzettel nie halb."""
|
|
ziel = _zeiger_datei()
|
|
try:
|
|
os.makedirs(os.path.dirname(ziel), exist_ok=True)
|
|
neben = ziel + ".neu"
|
|
with open(neben, "w", encoding="utf-8") as f:
|
|
f.write(pfad)
|
|
f.flush()
|
|
os.fsync(f.fileno())
|
|
os.replace(neben, ziel)
|
|
except (OSError, ValueError) as e:
|
|
log.warning("Datenordner konnte nicht gemerkt werden: %s", e)
|
|
|
|
|
|
def daten_ordner():
|
|
"""Der Ordner, in dem Journal, Berichte und Log liegen."""
|
|
if not getattr(sys, "frozen", False):
|
|
return EXE_DIR
|
|
|
|
gemerkt, lage = _gemerkter_ordner()
|
|
if lage == "ok":
|
|
return gemerkt
|
|
if lage == "unerreichbar":
|
|
# Stick ab, NAS aus: NICHT still woanders neu anfangen - sonst hat der
|
|
# Nutzer zwei Journale und keinen Weg zurueck.
|
|
log.warning("Datenordner %s ist nicht erreichbar - es wird nichts geändert", gemerkt)
|
|
return gemerkt if _beschreibbar(gemerkt) else EXE_DIR
|
|
|
|
kandidat = os.path.join(EXE_DIR, "Daten")
|
|
if not _beschreibbar(kandidat):
|
|
kandidat = os.path.join(os.environ.get("LOCALAPPDATA", EXE_DIR), "Beherbergungssteuer")
|
|
if not _beschreibbar(kandidat):
|
|
return EXE_DIR
|
|
_ordner_merken(kandidat)
|
|
for name in EIGENE_DATEN:
|
|
alt, neu = os.path.join(EXE_DIR, name), os.path.join(kandidat, name)
|
|
if os.path.exists(alt) and not os.path.exists(neu):
|
|
try:
|
|
os.replace(alt, neu)
|
|
log.info("%s in den Datenordner übernommen", name)
|
|
except OSError as e:
|
|
log.warning("%s ließ sich nicht übernehmen: %s", name, e)
|
|
return kandidat
|