fonzarely/regine-photos-archiverpublic Fork
⑂ main
Commits
⬇ Clone ▾
git clone https://git.rickub.com/fonzarely/regine-photos-archiver.git
git clone ssh://git@rickub.com/fonzarely/regine-photos-archiver.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
refactor 19cf29c · on main · Fabien Champigny · Sep 24, 2026
research.md · 69 lines · 11.7 KBmarkdown
Blame HistoryOpen raw

Research: Interface graphique de tri et de consultation

1. Technologie d'interface graphique : PySide6 natif

Decision: PySide6 (Qt natif Python), importé directement dans le même processus que regine-core — pas de serveur local, pas de pont technique supplémentaire.

Rationale: docs/interface-cli-gui-architecture.md avait déjà exploré la question sans trancher, avec une « inclination de départ vers PySide6 » pour deux raisons qui restent valables aujourd'hui : (1) cohérence avec la décision déjà actée d'une exécution « un seul processus par lancement, pas de démon » — une GUI web, même packagée en application de bureau, nécessiterait un mini-serveur local ou un pont plus lourd (Tauri + sidecar), ce qui casse partiellement ce principe ; (2) précédent solide dans le domaine (digiKam, RawTherapee construits sur Qt) pour une grille de miniatures performante et une sélection clavier/souris, exactement le cas d'usage du tri/culling (US1).

Alternatives considered:

  • GUI web (HTML/CSS/JS) packagée en application de bureau — rejetée pour cette itération : flexibilité d'interface supérieure, mais nécessite un processus ou un pont supplémentaire, contredisant la contrainte technique déjà actée. Reste une option révisable plus tard si l'itération visuelle rapide devient prioritaire (cf. discussion déjà actée dans docs/interface-cli-gui-architecture.md, non fermée définitivement).

2. Stratégie de test de la GUI

Decision: Aucune suite de tests automatisée pour l'interface graphique elle-même dans cette itération. Toute la logique métier qu'elle appelle (regroupement RAW+JPEG, promotion/rétrogradation, recherche, restauration) vit dans regine-core et y est testée exhaustivement (unitaire + intégration, comme pour tous les modules précédents du projet). La GUI est validée manuellement via les scénarios de quickstart.md, exactement comme les façades CLI précédentes l'ont été (specs/001 à 005) avant qu'aucune suite automatisée regine-cli n'existe (la première, test_cli_config.py, n'a été ajoutée qu'avec specs/003).

Rationale: pytest-qt existe et permettrait de piloter des widgets Qt en test, mais ajouterait une dépendance de test et une complexité d'installation (environnement graphique/headless) non justifiées tant que la façade GUI n'a pas encore de première version fonctionnelle à couvrir. Cohérent avec le principe déjà appliqué dans ce projet : pas de complexité non justifiée pour un besoin hypothétique (Principe VI). Cette décision est révisable dès qu'un premier écran existe et que des régressions d'interface deviennent un risque réel.

Alternatives considered:

  • pytest-qt dès cette itération — rejeté pour l'instant, cf. rationale ci-dessus.
  • Tests d'acceptation end-to-end pilotés par capture d'écran — rejeté, complexité largement disproportionnée par rapport à la taille de cette première itération GUI.

3. Frontière entre dossier.root et dossier.selection

Decision: Nouveau module regine_core.dossier.selection, distinct de regine_core.dossier.root (specs/004, inchangé). root.py porte le répertoire racine d'archive (premier niveau année/catégorie sous l'archive) ; selection.py porte la racine de sélection (niveau, à l'intérieur d'un dossier, où vivent les fichiers promus) — deux concepts que docs/lexique.md distingue déjà explicitement comme un « point de vigilance » (les deux emploient le mot « racine » mais désignent des niveaux différents de la hiérarchie).

Rationale: Mélanger les deux dans un même fichier créerait exactement la confusion que le lexique met déjà en garde d'éviter. Garder root.py inchangé évite aussi de re-tester une logique déjà stable (specs/004, 28/28 tâches, testée) pour une fonctionnalité qui n'a aucune dépendance réelle dessus.

Alternatives considered:

  • Étendre root.py avec la logique de promotion — rejeté, cf. rationale (confusion de nommage, couplage non justifié entre deux concepts indépendants).

4. Correspondance extension → dossier de format (raw/, jpeg/, tiff/)

Decision: selection.py introduit une petite fonction de correspondance extension → nom de dossier de format, réutilisant l'ensemble d'extensions RAW déjà défini dans regine_core.import_carte.copie.EXTENSIONS_MAITRES plutôt que de le dupliquer (raf/cr2/cr3/nef/arw/orf/rw2/pef/srw/dng → raw/, à l'exception du DNG qui reste un cas à trancher à l'implémentation puisque docs/archivage-photo-elements-cles.md section 11 le traite à part pour le hash mais pas pour le rangement par dossier de format ; tiff/tif → tiff/ ; jpg/jpeg → jpeg/).

Rationale: Cette correspondance n'existe dans aucun module existant (vérifié : ni import_carte, ni dossier, ni archive n'en portent une). docs/archivage-photo-elements-cles.md section 10 la décrit précisément (« raw/ est une catégorie fonctionnelle qui regroupe toutes les extensions RAW... contrairement à jpeg/ et tiff/, qui correspondent chacun à une extension précise »), donc reste conforme à une conception déjà actée, pas une invention pour ce plan.

Alternatives considered: Aucune — la logique est déjà spécifiée par les notes de conception existantes, seule son implémentation manquait.

5. Restauration ciblée (US2) : copie vérifiée, sans mécanisme partagé dédié

Decision: regine_core.archive.consultation.restaurer implémente sa propre copie vérifiée par somme de contrôle (source → destination, avec le même refus d'écrasement silencieux qu'ailleurs dans le projet, cf. push.CollisionNomLocalError, renommée depuis CollisionNomArchiveError — specs/001-import-photos révision 2026-09-22), plutôt que d'extraire un utilitaire partagé avec les fonctions de copie déjà privées de push.py/checkout.py.

Rationale: Le projet a déjà deux copies vérifiées quasi identiques mais privées et non partagées (import_carte.push._copier_verifie_sans_ecraser, archive.checkout._copier_verifie) sans que cela ait posé de problème de maintenance à ce stade (~10 lignes chacune). Une troisième variante minimaliste (pas de vérification de collision requise ici, la restauration cible un fichier choisi par l'utilisateur) reste plus simple qu'une abstraction partagée prématurée. À reconsidérer si une quatrième variante apparaissait.

Alternatives considered: Extraire un utilitaire commun regine_core.integrity.copie_verifiee — rejeté pour l'instant, complexité d'abstraction non encore justifiée par le nombre de cas (Principe VI, pas de complexité non justifiée).

6. Onglet d'import (US3) : appel direct de regine_core.import_carte, jamais de regine_cli.import_cmd

Decision: L'onglet d'import de la GUI (import_view.py) appelle directement les fonctions de bas niveau de regine_core.import_carte (copier_carte, resoudre_collisions_boitiers, decouper_en_groupes, jours_candidats_au_detachement, detacher_jours, resoudre_destination/resoudre_fusion, construire_nom_dossier*, renommer_fichiers, classer_par_format, attribuer_identifiants, preparer_resume, publier_localement) — jamais regine_cli.import_cmd._cmd_import ni les fonctions privées _traiter_groupe/_proposer_detachement/etc. de ce module. (archiver/publier_copie_locale cités dans une version antérieure de cette décision ont depuis été remplacées par publier_localement côté specs/001-import-photos, révision 2026-09-22 — l'import ne pousse plus vers l'archive, cf. § 7 ci-dessous.)

Rationale: regine_cli/import_cmd.py mélange déjà deux choses distinctes : la logique métier réelle (entièrement dans regine_core.import_carte, déjà testée) et l'orchestration interactive propre à la CLI (input() pour chaque décision — titre, détachement, destination, confirmation). Cette orchestration par input() est spécifique au terminal, pas une règle métier réutilisable : une GUI ne « répond » pas à input(), elle répond à des clics/formulaires. Appeler regine_core.import_carte directement, comme le fait déjà import_cmd.py lui-même, est donc la même conformité au Principe VI que pour les onglets Tri/Consultation/Configuration — pas un écart. Exactement le même choix que celui déjà documenté dans contracts/regine-core-api.md (« Notes d'implémentation ») pour config.smb.resoudre_archive_locale, ajoutée précisément pour éviter que deux façades dupliquent une même résolution.

Alternatives considered:

  • Faire appeler par la GUI la fonction _cmd_import de regine_cli (ou l'exécuter en sous-processus) — rejeté : cette fonction est bâtie autour de input()/print() bloquants, incompatible avec une boucle d'événements Qt, et son orchestration n'a aucune règle propre à en réutiliser (déjà entièrement dans regine_core.import_carte).
  • Extraire un nouveau module d'orchestration neutre (« assistant d'import ») partagé entre CLI et GUI — rejeté pour cette itération : aucune règle métier supplémentaire n'apparaît en écrivant import_view.py (chaque étape appelle une fonction déjà testée de import_carte), donc aucune duplication réelle à éliminer, contrairement au cas SMB déjà corrigé. À reconsidérer si une troisième façade (ou une évolution de l'orchestration CLI) faisait apparaître une vraie règle dupliquée.

7. Import GUI et archivage différé (révision 2026-09-22) : thread d'arrière-plan pour la publication locale, plus aucune écriture NAS

Decision: Suite à la révision de specs/001-import-photos (l'import s'arrête à une copie de travail locale classée par format, jamais une écriture NAS — cf. Clarifications de ce spec), l'onglet d'import ne bloque plus sur la disponibilité du partage SMB (config.smb.chemin_local_du_partage, calcul pur, remplace resoudre_archive_locale qui exigeait un montage réel) et exécute classement + attribution d'identifiants + copie finale (_PublicationLocaleWorker, ex-_ArchivageWorker) sur un QThread dédié plutôt que sur le thread principal.

Rationale: Deux bugs réels trouvés par test manuel de l'utilisateur sur une vraie carte (pas seulement le script headless) : (1) l'archivage immédiat gelait entièrement la fenêtre le temps du transfert (plusieurs sommes de contrôle par fichier, potentiellement long), sans retour visuel pendant l'exécution malgré le journal déjà en place — violation de FR-017 ; (2) le photographe n'avait aucune occasion de relire sa sélection avant l'écriture sur le NAS, et testait la GUI sans accès à un vrai NAS. Déplacer le travail lourd sur un thread résout le premier point ; arrêter l'import à une copie locale (spec révisée) résout le second sans nécessiter de bascule "mode test" dédiée — ne pas écrire sur l'archive à l'import rend l'accessibilité du NAS non bloquante par construction.

Alternatives considered:

  • Ajouter un simple indicateur "mode test, ne pas archiver" dans la GUI en conservant l'archivage immédiat pour l'usage réel — rejeté : duplique un état supplémentaire à maintenir (utile seulement en test) alors que la vraie demande du photographe (relire avant d'archiver) s'applique aussi en usage réel, pas seulement en test.
  • Garder l'archivage synchrone mais appeler QApplication.processEvents() par lot — rejeté : ne règle rien si le blocage vient d'un appel bloquant unique (ex. lecture réseau lente), contrairement à un vrai thread séparé.

Résumé

Tous les points ouverts du Technical Context du plan sont résolus par les décisions ci-dessus. Une seule nouvelle dépendance externe introduite au total pour cette feature : PySide6, exclusivement pour regine-gui (déjà en place pour US1/US2, réutilisée telle quelle par US3).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# Research: Interface graphique de tri et de consultation

## 1. Technologie d'interface graphique : PySide6 natif

**Decision**: PySide6 (Qt natif Python), importé directement dans le même processus que `regine-core` — pas de serveur local, pas de pont technique supplémentaire.

**Rationale**: `docs/interface-cli-gui-architecture.md` avait déjà exploré la question sans trancher, avec une « inclination de départ vers PySide6 » pour deux raisons qui restent valables aujourd'hui : (1) cohérence avec la décision déjà actée d'une exécution « un seul processus par lancement, pas de démon » — une GUI web, même packagée en application de bureau, nécessiterait un mini-serveur local ou un pont plus lourd (Tauri + sidecar), ce qui casse partiellement ce principe ; (2) précédent solide dans le domaine (digiKam, RawTherapee construits sur Qt) pour une grille de miniatures performante et une sélection clavier/souris, exactement le cas d'usage du tri/culling (US1).

**Alternatives considered**:
- GUI web (HTML/CSS/JS) packagée en application de bureau — rejetée pour cette itération : flexibilité d'interface supérieure, mais nécessite un processus ou un pont supplémentaire, contredisant la contrainte technique déjà actée. Reste une option révisable plus tard si l'itération visuelle rapide devient prioritaire (cf. discussion déjà actée dans `docs/interface-cli-gui-architecture.md`, non fermée définitivement).

## 2. Stratégie de test de la GUI

**Decision**: Aucune suite de tests automatisée pour l'interface graphique elle-même dans cette itération. Toute la logique métier qu'elle appelle (regroupement RAW+JPEG, promotion/rétrogradation, recherche, restauration) vit dans `regine-core` et y est testée exhaustivement (unitaire + intégration, comme pour tous les modules précédents du projet). La GUI est validée manuellement via les scénarios de `quickstart.md`, exactement comme les façades CLI précédentes l'ont été (specs/001 à 005) avant qu'aucune suite automatisée `regine-cli` n'existe (la première, `test_cli_config.py`, n'a été ajoutée qu'avec `specs/003`).

**Rationale**: `pytest-qt` existe et permettrait de piloter des widgets Qt en test, mais ajouterait une dépendance de test et une complexité d'installation (environnement graphique/headless) non justifiées tant que la façade GUI n'a pas encore de première version fonctionnelle à couvrir. Cohérent avec le principe déjà appliqué dans ce projet : pas de complexité non justifiée pour un besoin hypothétique (Principe VI). Cette décision est révisable dès qu'un premier écran existe et que des régressions d'interface deviennent un risque réel.

**Alternatives considered**:
- `pytest-qt` dès cette itération — rejeté pour l'instant, cf. rationale ci-dessus.
- Tests d'acceptation end-to-end pilotés par capture d'écran — rejeté, complexité largement disproportionnée par rapport à la taille de cette première itération GUI.

## 3. Frontière entre `dossier.root` et `dossier.selection`

**Decision**: Nouveau module `regine_core.dossier.selection`, distinct de `regine_core.dossier.root` (specs/004, inchangé). `root.py` porte le **répertoire racine d'archive** (premier niveau année/catégorie sous l'archive) ; `selection.py` porte la **racine de sélection** (niveau, à l'intérieur d'un dossier, où vivent les fichiers promus) — deux concepts que `docs/lexique.md` distingue déjà explicitement comme un « point de vigilance » (les deux emploient le mot « racine » mais désignent des niveaux différents de la hiérarchie).

**Rationale**: Mélanger les deux dans un même fichier créerait exactement la confusion que le lexique met déjà en garde d'éviter. Garder `root.py` inchangé évite aussi de re-tester une logique déjà stable (specs/004, 28/28 tâches, testée) pour une fonctionnalité qui n'a aucune dépendance réelle dessus.

**Alternatives considered**:
- Étendre `root.py` avec la logique de promotion — rejeté, cf. rationale (confusion de nommage, couplage non justifié entre deux concepts indépendants).

## 4. Correspondance extension → dossier de format (`raw/`, `jpeg/`, `tiff/`)

**Decision**: `selection.py` introduit une petite fonction de correspondance extension → nom de dossier de format, réutilisant l'ensemble d'extensions RAW déjà défini dans `regine_core.import_carte.copie.EXTENSIONS_MAITRES` plutôt que de le dupliquer (`raf`/`cr2`/`cr3`/`nef`/`arw`/`orf`/`rw2`/`pef`/`srw`/`dng` → `raw/`, à l'exception du DNG qui reste un cas à trancher à l'implémentation puisque `docs/archivage-photo-elements-cles.md` section 11 le traite à part pour le hash mais pas pour le rangement par dossier de format ; `tiff`/`tif` → `tiff/` ; `jpg`/`jpeg` → `jpeg/`).

**Rationale**: Cette correspondance n'existe dans aucun module existant (vérifié : ni `import_carte`, ni `dossier`, ni `archive` n'en portent une). `docs/archivage-photo-elements-cles.md` section 10 la décrit précisément (« `raw/` est une catégorie fonctionnelle qui regroupe toutes les extensions RAW... contrairement à `jpeg/` et `tiff/`, qui correspondent chacun à une extension précise »), donc reste conforme à une conception déjà actée, pas une invention pour ce plan.

**Alternatives considered**: Aucune — la logique est déjà spécifiée par les notes de conception existantes, seule son implémentation manquait.

## 5. Restauration ciblée (US2) : copie vérifiée, sans mécanisme partagé dédié

**Decision**: `regine_core.archive.consultation.restaurer` implémente sa propre copie vérifiée par somme de contrôle (source → destination, avec le même refus d'écrasement silencieux qu'ailleurs dans le projet, cf. `push.CollisionNomLocalError`, renommée depuis `CollisionNomArchiveError` — `specs/001-import-photos` révision 2026-09-22), plutôt que d'extraire un utilitaire partagé avec les fonctions de copie déjà privées de `push.py`/`checkout.py`.

**Rationale**: Le projet a déjà deux copies vérifiées quasi identiques mais privées et non partagées (`import_carte.push._copier_verifie_sans_ecraser`, `archive.checkout._copier_verifie`) sans que cela ait posé de problème de maintenance à ce stade (~10 lignes chacune). Une troisième variante minimaliste (pas de vérification de collision requise ici, la restauration cible un fichier choisi par l'utilisateur) reste plus simple qu'une abstraction partagée prématurée. À reconsidérer si une quatrième variante apparaissait.

**Alternatives considered**: Extraire un utilitaire commun `regine_core.integrity.copie_verifiee` — rejeté pour l'instant, complexité d'abstraction non encore justifiée par le nombre de cas (Principe VI, pas de complexité non justifiée).

## 6. Onglet d'import (US3) : appel direct de `regine_core.import_carte`, jamais de `regine_cli.import_cmd`

**Decision**: L'onglet d'import de la GUI (`import_view.py`) appelle directement les fonctions de bas niveau de `regine_core.import_carte` (`copier_carte`, `resoudre_collisions_boitiers`, `decouper_en_groupes`, `jours_candidats_au_detachement`, `detacher_jours`, `resoudre_destination`/`resoudre_fusion`, `construire_nom_dossier*`, `renommer_fichiers`, `classer_par_format`, `attribuer_identifiants`, `preparer_resume`, `publier_localement`) — jamais `regine_cli.import_cmd._cmd_import` ni les fonctions privées `_traiter_groupe`/`_proposer_detachement`/etc. de ce module. (`archiver`/`publier_copie_locale` cités dans une version antérieure de cette décision ont depuis été remplacées par `publier_localement` côté `specs/001-import-photos`, révision 2026-09-22 — l'import ne pousse plus vers l'archive, cf. § 7 ci-dessous.)

**Rationale**: `regine_cli/import_cmd.py` mélange déjà deux choses distinctes : la logique métier réelle (entièrement dans `regine_core.import_carte`, déjà testée) et l'orchestration interactive propre à la CLI (`input()` pour chaque décision — titre, détachement, destination, confirmation). Cette orchestration par `input()` est spécifique au terminal, pas une règle métier réutilisable : une GUI ne « répond » pas à `input()`, elle répond à des clics/formulaires. Appeler `regine_core.import_carte` directement, comme le fait déjà `import_cmd.py` lui-même, est donc la même conformité au Principe VI que pour les onglets Tri/Consultation/Configuration — pas un écart. Exactement le même choix que celui déjà documenté dans `contracts/regine-core-api.md` (« Notes d'implémentation ») pour `config.smb.resoudre_archive_locale`, ajoutée précisément pour éviter que deux façades dupliquent une même résolution.

**Alternatives considered**:
- Faire appeler par la GUI la fonction `_cmd_import` de `regine_cli` (ou l'exécuter en sous-processus) — rejeté : cette fonction est bâtie autour de `input()`/`print()` bloquants, incompatible avec une boucle d'événements Qt, et son orchestration n'a aucune règle propre à en réutiliser (déjà entièrement dans `regine_core.import_carte`).
- Extraire un nouveau module d'orchestration neutre (« assistant d'import ») partagé entre CLI et GUI — rejeté pour cette itération : aucune règle métier supplémentaire n'apparaît en écrivant `import_view.py` (chaque étape appelle une fonction déjà testée de `import_carte`), donc aucune duplication réelle à éliminer, contrairement au cas SMB déjà corrigé. À reconsidérer si une troisième façade (ou une évolution de l'orchestration CLI) faisait apparaître une vraie règle dupliquée.

## 7. Import GUI et archivage différé (révision 2026-09-22) : thread d'arrière-plan pour la publication locale, plus aucune écriture NAS

**Decision**: Suite à la révision de `specs/001-import-photos` (l'import s'arrête à une copie de travail locale classée par format, jamais une écriture NAS — cf. Clarifications de ce spec), l'onglet d'import ne bloque plus sur la disponibilité du partage SMB (`config.smb.chemin_local_du_partage`, calcul pur, remplace `resoudre_archive_locale` qui exigeait un montage réel) et exécute classement + attribution d'identifiants + copie finale (`_PublicationLocaleWorker`, ex-`_ArchivageWorker`) sur un `QThread` dédié plutôt que sur le thread principal.

**Rationale**: Deux bugs réels trouvés par test manuel de l'utilisateur sur une vraie carte (pas seulement le script headless) : (1) l'archivage immédiat gelait entièrement la fenêtre le temps du transfert (plusieurs sommes de contrôle par fichier, potentiellement long), sans retour visuel pendant l'exécution malgré le journal déjà en place — violation de FR-017 ; (2) le photographe n'avait aucune occasion de relire sa sélection avant l'écriture sur le NAS, et testait la GUI sans accès à un vrai NAS. Déplacer le travail lourd sur un thread résout le premier point ; arrêter l'import à une copie locale (spec révisée) résout le second sans nécessiter de bascule "mode test" dédiée — ne pas écrire sur l'archive à l'import rend l'accessibilité du NAS non bloquante par construction.

**Alternatives considered**:
- Ajouter un simple indicateur "mode test, ne pas archiver" dans la GUI en conservant l'archivage immédiat pour l'usage réel — rejeté : duplique un état supplémentaire à maintenir (utile seulement en test) alors que la vraie demande du photographe (relire avant d'archiver) s'applique aussi en usage réel, pas seulement en test.
- Garder l'archivage synchrone mais appeler `QApplication.processEvents()` par lot — rejeté : ne règle rien si le blocage vient d'un appel bloquant unique (ex. lecture réseau lente), contrairement à un vrai thread séparé.

## Résumé

Tous les points ouverts du Technical Context du plan sont résolus par les décisions ci-dessus. Une seule nouvelle dépendance externe introduite au total pour cette feature : PySide6, exclusivement pour `regine-gui` (déjà en place pour US1/US2, réutilisée telle quelle par US3).