127 lines
4.1 KiB
Python
127 lines
4.1 KiB
Python
"""Sauvegarde / chargement de l'état complet de l'application (extraits +
|
|
réglages de style + page en cours) dans un fichier "projet" JSON.
|
|
|
|
Utilisé aussi bien pour la sauvegarde manuelle (Enregistrer / Enregistrer
|
|
sous / Ouvrir) que pour la sauvegarde automatique périodique et à la
|
|
fermeture — voir main_window.py, qui est seul responsable de décider
|
|
*quel* fichier cible (projet explicite ou fichier de récupération par
|
|
défaut).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import asdict, dataclass, fields
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
from PySide6.QtCore import QSettings
|
|
|
|
from .ai_config import APP_NAME, ORG_NAME
|
|
from .models import Clip
|
|
from .pdf_style import PdfStyle
|
|
|
|
PROJECT_FILE_SUFFIX = ".pdfscrap"
|
|
PROJECT_FILE_FILTER = "Projet PdfScrapper (*.pdfscrap)"
|
|
|
|
_FORMAT_VERSION = 1
|
|
_SETTINGS_GROUP = "session"
|
|
_SETTINGS_KEY_LAST_PATH = "last_path"
|
|
|
|
|
|
@dataclass
|
|
class ProjectState:
|
|
"""Tout ce qui définit une session de capture : les extraits, le
|
|
style d'export PDF, le réglage d'insertion, et la page en cours (pour
|
|
reprendre la navigation là où elle s'était arrêtée)."""
|
|
|
|
clips: list[Clip]
|
|
pdf_style: PdfStyle
|
|
insert_above: bool
|
|
current_url: str
|
|
|
|
|
|
def save_project(path: str | Path, state: ProjectState) -> None:
|
|
"""Écrit `state` en JSON dans `path`, en créant le dossier parent si
|
|
besoin. Écriture atomique (fichier temporaire puis remplacement) pour
|
|
ne jamais laisser un fichier à moitié écrit si l'appli est
|
|
interrompue en plein autosave."""
|
|
data = {
|
|
"format_version": _FORMAT_VERSION,
|
|
"insert_above": state.insert_above,
|
|
"current_url": state.current_url,
|
|
"pdf_style": asdict(state.pdf_style),
|
|
"clips": [
|
|
{
|
|
"text": clip.text,
|
|
"title": clip.title,
|
|
"url": clip.url,
|
|
"captured_at": clip.captured_at.isoformat(),
|
|
"linked_to_next": clip.linked_to_next,
|
|
}
|
|
for clip in state.clips
|
|
],
|
|
}
|
|
|
|
path = Path(path)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp_path = path.with_name(path.name + ".tmp")
|
|
tmp_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
tmp_path.replace(path)
|
|
|
|
|
|
def load_project(path: str | Path) -> ProjectState:
|
|
"""Reconstruit un `ProjectState` depuis un fichier écrit par
|
|
save_project(). Tolérant aux champs manquants/inconnus (filtre les
|
|
clés de pdf_style sur les champs actuels de PdfStyle, valeurs par
|
|
défaut pour les champs de Clip absents) — robustesse si le format
|
|
évolue dans une version future de l'appli."""
|
|
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
|
|
known_style_fields = {f.name for f in fields(PdfStyle)}
|
|
style_data = {
|
|
key: value
|
|
for key, value in (data.get("pdf_style") or {}).items()
|
|
if key in known_style_fields
|
|
}
|
|
pdf_style = PdfStyle(**style_data)
|
|
|
|
clips = [
|
|
Clip(
|
|
text=raw["text"],
|
|
title=raw["title"],
|
|
url=raw["url"],
|
|
captured_at=datetime.fromisoformat(raw["captured_at"]),
|
|
linked_to_next=raw.get("linked_to_next", False),
|
|
)
|
|
for raw in data.get("clips", [])
|
|
]
|
|
|
|
return ProjectState(
|
|
clips=clips,
|
|
pdf_style=pdf_style,
|
|
insert_above=data.get("insert_above", True),
|
|
current_url=data.get("current_url", ""),
|
|
)
|
|
|
|
|
|
def load_last_session_path() -> Path | None:
|
|
"""Chemin du dernier fichier actif (projet explicite ou fichier de
|
|
récupération par défaut), pour la reprise automatique au démarrage."""
|
|
settings = QSettings(ORG_NAME, APP_NAME)
|
|
settings.beginGroup(_SETTINGS_GROUP)
|
|
try:
|
|
raw = settings.value(_SETTINGS_KEY_LAST_PATH, "", type=str)
|
|
finally:
|
|
settings.endGroup()
|
|
return Path(raw) if raw else None
|
|
|
|
|
|
def save_last_session_path(path: str | Path) -> None:
|
|
settings = QSettings(ORG_NAME, APP_NAME)
|
|
settings.beginGroup(_SETTINGS_GROUP)
|
|
try:
|
|
settings.setValue(_SETTINGS_KEY_LAST_PATH, str(path))
|
|
finally:
|
|
settings.endGroup()
|