feat: strengthen backup and mobile workflows
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def contained_file(root: Path, storage_name: str) -> Path:
|
||||
root = root.resolve()
|
||||
candidate = (root / storage_name).resolve()
|
||||
if candidate.parent != root:
|
||||
raise ValueError("attachment path escapes storage root")
|
||||
return candidate
|
||||
|
||||
|
||||
def quarantine_files(root: Path, storage_names: list[str], quarantine: Path) -> list[tuple[Path, Path]]:
|
||||
moved = []
|
||||
quarantine.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
for name in storage_names:
|
||||
source = contained_file(root, name)
|
||||
if source.exists():
|
||||
target = quarantine / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
source.replace(target)
|
||||
moved.append((source, target))
|
||||
return moved
|
||||
except Exception:
|
||||
restore_quarantine(moved)
|
||||
raise
|
||||
|
||||
|
||||
def restore_quarantine(moved: list[tuple[Path, Path]]) -> None:
|
||||
for original, quarantined in reversed(moved):
|
||||
if quarantined.exists():
|
||||
original.parent.mkdir(parents=True, exist_ok=True)
|
||||
quarantined.replace(original)
|
||||
|
||||
|
||||
def restore_quarantine_dir(root: Path, quarantine: Path) -> None:
|
||||
if not quarantine.exists():
|
||||
return
|
||||
for quarantined in sorted(quarantine.rglob("*")):
|
||||
if not quarantined.is_file():
|
||||
continue
|
||||
relative = quarantined.relative_to(quarantine)
|
||||
original = contained_file(root, relative.as_posix())
|
||||
original.parent.mkdir(parents=True, exist_ok=True)
|
||||
quarantined.replace(original)
|
||||
shutil.rmtree(quarantine)
|
||||
|
||||
|
||||
def remove_quarantine(path: Path) -> None:
|
||||
if path.exists():
|
||||
shutil.rmtree(path)
|
||||
Reference in New Issue
Block a user