79 lines
3.0 KiB
Python
79 lines
3.0 KiB
Python
"""Quellen und Arbeitsdateien fertiger Versionen als tar ins Archiv (config.ARCHIV, auf dem Server die Storage Box).
|
||
Je Version quellen.tar (ohne QUELLEN.md, die bleibt im Speicher und steht in der Buchansicht) und arbeit.tar. Ein tar statt
|
||
einzelner Dateien, weil SMB je Datei mehrere Round-Trips braucht (25 Dateien/s), am Stück aber 90 MB/s schafft.
|
||
Nichts liest danach noch daraus: neue Versionen recherchieren neu, fertige Läufe setzen nie fort.
|
||
Erst packen, dann löschen – ein Abbruch lässt den Speicher vollständig, der nächste Start holt nach."""
|
||
import asyncio
|
||
import logging
|
||
import shutil
|
||
import subprocess
|
||
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:
|
||
"""Packt quellen/ (außer QUELLEN.md) und arbeit/ nach ARCHIV/<key>/<ordner>.tar und löscht sie. Blockiert – im Thread aufrufen."""
|
||
v = state.verzeichnis(key)
|
||
ziel = config.ARCHIV / key
|
||
ziel.mkdir(parents=True, exist_ok=True)
|
||
for u in ORDNER:
|
||
eintraege = _eintraege(v / u)
|
||
if not eintraege:
|
||
continue
|
||
tmp = ziel / f"{u}.tar.tmp"
|
||
r = subprocess.run(["tar", "-cf", str(tmp), "-C", str(v / u), *[p.name for p in eintraege]], capture_output=True, text=True)
|
||
if r.returncode:
|
||
raise OSError(f"tar {u}/ von {key}: {r.stderr.strip()[-300:]}")
|
||
tmp.replace(ziel / f"{u}.tar")
|
||
for p in eintraege:
|
||
if p.is_dir() and not p.is_symlink():
|
||
shutil.rmtree(p)
|
||
else:
|
||
p.unlink()
|
||
|
||
|
||
async def _auslagern(key: str) -> None:
|
||
state.ereignis(key, "archiv", "start", f"Quellen und Arbeitsdateien als tar 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 Läufe)."""
|
||
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)
|