Eigener Datenordner (ablage.py)
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
This commit is contained in:
parent
65eec9937a
commit
de71fce64f
2 changed files with 126 additions and 1 deletions
123
ablage.py
Normal file
123
ablage.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# -*- 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
|
||||
4
app.py
4
app.py
|
|
@ -21,11 +21,13 @@ import db
|
|||
import xlsx_io
|
||||
import firma as firma_lib
|
||||
import bericht_pdf
|
||||
import ablage
|
||||
from modell import MONATE, STANDARD_SATZ, summiere, eur
|
||||
from gui_monat import MonatsTab, SummenLeiste
|
||||
from gui_dialoge import BuchungDialog
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(sys.argv[0] if getattr(sys, "frozen", False) else __file__))
|
||||
# Als EXE liegt alles in einem eigenen Datenordner - siehe ablage.py.
|
||||
BASE_DIR = ablage.daten_ordner()
|
||||
JOURNAL = os.path.join(BASE_DIR, "journal.sqlite3")
|
||||
LOGDATEI = os.path.join(BASE_DIR, "beherbergungssteuer.log")
|
||||
BERICHTE = os.path.join(BASE_DIR, "berichte")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue