Skip to content

Commit cc46328

Browse files
OlivierSalaclaude
andcommitted
Version 1.6.2 - Retry on errors and section error tracking
New features: - Add --retry-on-errors option to automatically retry failed pages - Track export errors per section (HasExportErrors flag in manifest) - Sections with errors are forced to [LOAD] on next incremental export Bug fixes: - Fix pages in error disappearing when section is [SKIP] - Failed pages are now properly retried on subsequent exports Configuration: - RetryOnErrors: Enable automatic retry loop (default: false) - MaxRetryIterations: Max retry attempts (default: 10) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 589a1ff commit cc46328

File tree

14 files changed

+707
-29
lines changed

14 files changed

+707
-29
lines changed

ClaudeDocs/faq.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,149 @@ error NETSDK1045: le SDK .NET actuel ne prend pas en charge le ciblage .NET 10.0
4747

4848
---
4949

50+
### Application plante au démarrage - appSettings.json introuvable (SingleFile publish)
51+
52+
**Problème**: L'application compilée en Release (SingleFile) plante avec l'erreur:
53+
```
54+
System.IO.FileNotFoundException: The configuration file 'appSettings.json' was not found
55+
```
56+
57+
**Cause**: Les fichiers de configuration (`appSettings.json`, `LICENSE`, fichiers de traduction) étaient intégrés dans l'exécutable SingleFile au lieu d'être copiés séparément. L'application ne pouvait pas les lire car elle cherche ces fichiers sur le disque.
58+
59+
**Solution**: Ajouter `<ExcludeFromSingleFile>true</ExcludeFromSingleFile>` dans le fichier `.csproj` pour chaque fichier devant rester externe:
60+
```xml
61+
<None Update="appSettings.json">
62+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
63+
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
64+
</None>
65+
```
66+
67+
Cela s'applique à:
68+
- `appSettings.json`
69+
- `LICENSE`
70+
- `Resources/trad.*.json` (fichiers de traduction)
71+
72+
**Corrigé dans**: v1.6.1
73+
74+
---
75+
5076
### Fichier manifeste corrompu ou incompatible
5177

5278
**Problème**: Le manifeste `.export-manifest.json` semble corrompu ou génère des erreurs.
5379

5480
**Solution**: Supprimer le fichier `.export-manifest.json` du dossier d'export et relancer avec `--incremental`. Un nouveau manifeste sera créé avec un export complet.
5581

5682
---
83+
84+
### Export incrémental - Optimisation Phase 1 (v2.0 du manifeste)
85+
86+
**Problème**: La Phase 1 de l'export (construction de l'arborescence) était lente car elle chargeait les pages de toutes les sections, même si aucune modification n'avait eu lieu.
87+
88+
**Solution implémentée**: Le manifeste v2.0 stocke maintenant les métadonnées des sections en plus des pages. Lors d'un export incrémental:
89+
- Seules les sections modifiées ou nouvelles déclenchent un appel à OneNote pour charger leurs pages
90+
- Les sections inchangées utilisent les données en cache du manifeste
91+
- Gains de performance estimés: 80-99% selon le % de sections modifiées
92+
93+
**Comportement de migration**:
94+
- Les manifestes v1.0 existants sont automatiquement migrés en v2.0
95+
- Après migration, le premier export scanne toutes les sections (comportement normal)
96+
- Les exports suivants bénéficient de l'optimisation
97+
98+
**Messages dans les logs**:
99+
- `[SKIP]` - Section inchangée, pages récupérées depuis le cache
100+
- `[NEW]` - Nouvelle section détectée
101+
- `[LOAD]` - Section modifiée, pages chargées depuis OneNote
102+
103+
**Structure du manifeste v2.0**:
104+
```json
105+
{
106+
"version": "2.0",
107+
"sections": {
108+
"section-onenote-id": {
109+
"title": "Section Name",
110+
"oneNoteId": "...",
111+
"lastModificationDate": "2024-01-15T10:30:00",
112+
"path": "Notebook/Section",
113+
"isSectionGroup": false
114+
}
115+
},
116+
"pages": {
117+
"page-onenote-id": {
118+
"title": "Page Title",
119+
"sectionId": "section-onenote-id",
120+
...
121+
}
122+
}
123+
}
124+
```
125+
126+
---
127+
128+
### Export incrémental - Reprise après interruption
129+
130+
**Problème**: Si l'export est interrompu (Ctrl+C, crash, etc.), les pages déjà exportées étaient considérées comme "nouvelles" au relancement car le manifeste n'était sauvegardé qu'à la fin.
131+
132+
**Solution implémentée**: Le manifeste est maintenant sauvegardé après chaque page exportée avec succès. Cela permet:
133+
- De reprendre l'export là où il s'était arrêté
134+
- De ne pas ré-exporter les pages déjà traitées lors de la session interrompue
135+
- Une progression fiable même en cas d'interruption
136+
137+
**Comportement**:
138+
- Chaque page exportée est immédiatement enregistrée dans le manifeste
139+
- Au relancement, les pages déjà exportées sont détectées comme "unchanged" et ignorées
140+
- Légère augmentation des écritures disque (1 sauvegarde/page au lieu de 1 à la fin)
141+
142+
---
143+
144+
### Export incrémental - Boucle automatique sur erreurs
145+
146+
**Problème**: Lorsque des pages échouent à l'export (erreurs OneNote, pages corrompues, non synchronisées), il fallait relancer manuellement l'export pour réessayer.
147+
148+
**Solution implémentée**: Nouvelle option `--retry-on-errors` qui boucle automatiquement tant qu'il y a des erreurs.
149+
150+
**Utilisation**:
151+
- CLI : `OneNoteMdExporter.exe --incremental --retry-on-errors`
152+
- Interactif : Répondre `[2]` à la question "Voulez-vous activer la boucle automatique en cas d'erreurs ?"
153+
- appSettings.json : `"RetryOnErrors": true`
154+
155+
**Protection contre les boucles infinies**:
156+
- Arrêt si aucun progrès (même nombre d'erreurs qu'à l'itération précédente)
157+
- Arrêt si nombre max d'itérations atteint (par défaut : 10, configurable via `MaxRetryIterations`)
158+
159+
**Messages dans les logs**:
160+
- `*** Retry iteration X (Y errors remaining) ***` - Début d'une nouvelle itération
161+
- `--> Stopping retry: no progress made` - Arrêt car aucun progrès
162+
- `--> All pages exported successfully after X iterations` - Succès après plusieurs tentatives
163+
164+
**Prérequis**:
165+
- Mode incrémental (`--incremental`) obligatoire pour que le retry fonctionne
166+
- Les pages en erreur ne sont pas ajoutées au manifeste, donc elles seront réessayées
167+
168+
---
169+
170+
### Export incrémental - Pages en erreur et sections [SKIP]
171+
172+
**Problème**: Si une page échouait à l'export, elle n'était pas ajoutée au manifeste. Si la section parente n'avait pas changé, elle était marquée [SKIP] au prochain export et la page en erreur disparaissait complètement (non rechargée depuis OneNote).
173+
174+
**Solution implémentée**: Le manifeste v2.0 stocke maintenant un flag `HasExportErrors` par section.
175+
- Quand une page échoue, sa section est marquée `HasExportErrors = true`
176+
- Au prochain export incrémental, les sections avec `HasExportErrors = true` sont forcées en [LOAD]
177+
- Les pages en erreur sont ainsi rechargées depuis OneNote et réessayées
178+
179+
**Comportement attendu**:
180+
```
181+
# Premier export - erreur sur une page
182+
- [LOAD] Section A
183+
- Page 1 ✓
184+
- Page 2 ✗ (erreur)
185+
- Page 3 ✓
186+
→ Section A marquée HasExportErrors=true dans le manifeste
187+
188+
# Deuxième export - la section est forcée en LOAD
189+
- [LOAD] Section A ← forcé car HasExportErrors=true
190+
- [SKIP] Page 1
191+
- [UPDATE] Page 2 ← réessayée
192+
- [SKIP] Page 3
193+
```
194+
195+
---

src/OneNoteMdExporter/Infrastructure/AppSettings.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ public class AppSettings
7878
/// </summary>
7979
public static bool CleanupDeletedPages { get; set; } = true;
8080

81+
/// <summary>
82+
/// When enabled, automatically retry export if there are pages with errors until all pages are exported successfully
83+
/// or no progress is made (same number of errors as previous iteration)
84+
/// </summary>
85+
public static bool RetryOnErrors { get; set; } = false;
86+
87+
/// <summary>
88+
/// Maximum number of retry iterations when RetryOnErrors is enabled (0 = unlimited)
89+
/// </summary>
90+
public static int MaxRetryIterations { get; set; } = 10;
91+
8192

8293
/*
8394
* Markdown rendering Settings

src/OneNoteMdExporter/Models/ExportManifest.cs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@
44
namespace alxnbl.OneNoteMdExporter.Models
55
{
66
/// <summary>
7-
/// Manifest file that tracks exported pages for incremental export
7+
/// Manifest file that tracks exported pages and sections for incremental export
88
/// </summary>
99
public class ExportManifest
1010
{
11+
/// <summary>
12+
/// Current manifest format version
13+
/// </summary>
14+
public const string CurrentVersion = "2.0";
15+
1116
/// <summary>
1217
/// Version of the manifest format
18+
/// v1.0: Pages only
19+
/// v2.0: Pages + Sections for Phase 1 optimization
1320
/// </summary>
14-
public string Version { get; set; } = "1.0";
21+
public string Version { get; set; } = CurrentVersion;
1522

1623
/// <summary>
1724
/// OneNote ID of the notebook
@@ -33,12 +40,54 @@ public class ExportManifest
3340
/// </summary>
3441
public DateTime LastExportDate { get; set; }
3542

43+
/// <summary>
44+
/// Dictionary of exported sections, keyed by OneNote section ID (v2.0+)
45+
/// </summary>
46+
public Dictionary<string, SectionManifestEntry> Sections { get; set; } = new Dictionary<string, SectionManifestEntry>();
47+
3648
/// <summary>
3749
/// Dictionary of exported pages, keyed by OneNote page ID
3850
/// </summary>
3951
public Dictionary<string, PageManifestEntry> Pages { get; set; } = new Dictionary<string, PageManifestEntry>();
4052
}
4153

54+
/// <summary>
55+
/// Entry for a single exported section in the manifest (v2.0+)
56+
/// </summary>
57+
public class SectionManifestEntry
58+
{
59+
/// <summary>
60+
/// Title of the section
61+
/// </summary>
62+
public string Title { get; set; }
63+
64+
/// <summary>
65+
/// OneNote ID of the section
66+
/// </summary>
67+
public string OneNoteId { get; set; }
68+
69+
/// <summary>
70+
/// Last modification date from OneNote
71+
/// </summary>
72+
public DateTime LastModificationDate { get; set; }
73+
74+
/// <summary>
75+
/// Relative path of the section within the notebook hierarchy
76+
/// </summary>
77+
public string Path { get; set; }
78+
79+
/// <summary>
80+
/// Whether this is a section group (true) or a regular section (false)
81+
/// </summary>
82+
public bool IsSectionGroup { get; set; }
83+
84+
/// <summary>
85+
/// Whether the last export had errors for pages in this section.
86+
/// If true, the section will be reloaded from OneNote on next incremental export.
87+
/// </summary>
88+
public bool HasExportErrors { get; set; }
89+
}
90+
4291
/// <summary>
4392
/// Entry for a single exported page in the manifest
4493
/// </summary>
@@ -54,6 +103,11 @@ public class PageManifestEntry
54103
/// </summary>
55104
public string OneNoteId { get; set; }
56105

106+
/// <summary>
107+
/// OneNote ID of the parent section (v2.0+)
108+
/// </summary>
109+
public string SectionId { get; set; }
110+
57111
/// <summary>
58112
/// Last modification date from OneNote
59113
/// </summary>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Collections.Generic;
2+
3+
namespace alxnbl.OneNoteMdExporter.Models
4+
{
5+
/// <summary>
6+
/// Represents the differences between current OneNote sections and last export manifest
7+
/// Used to optimize Phase 1 by only loading pages from modified sections
8+
/// </summary>
9+
public class SectionDiff
10+
{
11+
/// <summary>
12+
/// Sections that are new (not in the previous manifest)
13+
/// </summary>
14+
public List<Section> NewSections { get; set; } = new List<Section>();
15+
16+
/// <summary>
17+
/// Sections that have been modified since the last export
18+
/// </summary>
19+
public List<Section> ModifiedSections { get; set; } = new List<Section>();
20+
21+
/// <summary>
22+
/// Sections that have not changed since the last export
23+
/// </summary>
24+
public List<Section> UnchangedSections { get; set; } = new List<Section>();
25+
26+
/// <summary>
27+
/// Sections that were in the previous manifest but no longer exist in OneNote
28+
/// </summary>
29+
public List<SectionManifestEntry> DeletedSections { get; set; } = new List<SectionManifestEntry>();
30+
31+
/// <summary>
32+
/// Total number of sections requiring page loading (new + modified)
33+
/// </summary>
34+
public int SectionsToLoad => NewSections.Count + ModifiedSections.Count;
35+
36+
/// <summary>
37+
/// Total number of sections
38+
/// </summary>
39+
public int TotalSections => NewSections.Count + ModifiedSections.Count + UnchangedSections.Count;
40+
}
41+
}

src/OneNoteMdExporter/OneNoteMdExporter.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
88
<PublishReadyToRun>true</PublishReadyToRun>
99
<PublishTrimmed>False</PublishTrimmed>
10-
<Version>1.6.1</Version>
10+
<Version>1.6.2</Version>
1111
<PackageId>OneNoteMdExporter</PackageId>
1212
<Authors>alxnbl</Authors>
1313
<Company />

0 commit comments

Comments
 (0)