Files
buchwerk/backend/archiv.py
T
team3andClaude Fable 5.1 be19abc065 Archiv: Quellen und Arbeitsdateien fertiger Versionen auf ARCHIV_DIR auslagern (z. B. Storage Box)
Nach jedem fertigen Lauf und beim Start nachholend; QUELLEN.md bleibt. Löschen und Zurücksetzen räumen das Archiv mit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-14 15:31:35 +02:00

80 lines
2.8 KiB
Python
Raw 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.
"""Quellen und Arbeitsdateien fertiger Versionen ins Archiv verschieben (config.ARCHIV, auf dem Server eine Storage Box).
Nichts liest danach noch daraus: neue Versionen recherchieren neu, fertige Läufe setzen nie fort. QUELLEN.md bleibt im Speicher,
die Buchansicht zeigt sie. Erst kopieren, dann löschen – ein Abbruch lässt den Speicher vollständig, der nächste Start holt nach."""
import asyncio
import logging
import shutil
from pathlib import Path
from . import config, state
log = logging.getLogger("buchwerk")
ORDNER = ("quellen", "arbeit")
BLEIBT = {"QUELLEN.md"}
_aufgaben: set[asyncio.Task] = set()
def faellig(key: str) -> bool:
"""Fertig und unter quellen/ oder arbeit/ liegt noch etwas."""
v = state.verzeichnis(key)
return state.zustand(key)["status"] == "fertig" and any(_eintraege(v / u) for u in ORDNER)
def _eintraege(d: Path) -> list[Path]:
return [p for p in d.iterdir() if p.name not in BLEIBT] if d.is_dir() else []
def auslagern(key: str) -> None:
"""Verschiebt quellen/ (außer QUELLEN.md) und arbeit/ nach ARCHIV/<key>/. Blockiert – im Thread aufrufen."""
v = state.verzeichnis(key)
for u in ORDNER:
for p in _eintraege(v / u):
_kopieren(p, config.ARCHIV / key / u / p.name)
if p.is_dir() and not p.is_symlink():
shutil.rmtree(p)
else:
p.unlink()
def _kopieren(quelle: Path, ziel: Path) -> None:
"""Ohne Rechte, Zeiten und Symlinks: CIFS kennt beides nicht. Links in Klonen entfallen."""
if quelle.is_symlink():
return
if quelle.is_dir():
ziel.mkdir(parents=True, exist_ok=True)
for k in quelle.iterdir():
_kopieren(k, ziel / k.name)
elif quelle.is_file():
ziel.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(quelle, ziel)
async def _auslagern(key: str) -> None:
state.ereignis(key, "archiv", "start", f"Quellen und Arbeitsdateien nach {config.ARCHIV / key}")
try:
await asyncio.to_thread(auslagern, key)
state.ereignis(key, "archiv", "ende", "ausgelagert")
except OSError as e:
log.exception("Archiv %s", key)
state.ereignis(key, "archiv", "fehler", f"{type(e).__name__}: {e}")
state.speichern(key)
def anstossen(key: str) -> None:
"""Im Hintergrund, der Lauf gilt schon als fertig."""
if config.ARCHIV and faellig(key):
t = asyncio.create_task(_auslagern(key))
_aufgaben.add(t)
t.add_done_callback(_aufgaben.discard)
async def nachholen() -> None:
"""Beim Start: alle fertigen Versionen mit Restbestand, nacheinander (Bestand und abgebrochene Kopien)."""
if not config.ARCHIV:
return
for t in state.themen():
for v in state.versionen(t["name"]):
key = f"{t['name']}/v{v}"
if faellig(key):
await _auslagern(key)