Skip empty tonie folders instead of pruning all chapters

Previously, an accidentally empty tonie folder combined with pruning
(the default) would delete every existing chapter on the tonie. Now:

- syncer.BuildPlan detects an empty local folder and returns a
  Plan{Skipped: true, SkipReason: ...} instead of computing a diff
- syncer.ApplyPlan is a no-op for a skipped plan (defense in depth)
- Plan.NeedsChanges() reports false for skipped plans
- cmd/toni-sync sync prints "Skipped: <reason>" for such folders and
  moves on, regardless of --no-prune
- Added a test covering the skip behavior end-to-end (BuildPlan +
  ApplyPlan no-op)
- README documents the safety behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-08-14 19:27:58 +02:00
co-authored by Copilot
parent eb77c0ecbd
commit 4e1a89cb05
4 changed files with 62 additions and 0 deletions
+26
View File
@@ -129,6 +129,32 @@ func TestBuildPlanNoPruneKeepsStaleChapters(t *testing.T) {
}
}
func TestBuildPlanSkipsEmptyFolder(t *testing.T) {
dir := t.TempDir() // no audio files
tonie := makeTonie([]tonieapi.Chapter{{ID: "c1", Title: "existing chapter"}})
client := &fakeClient{tonieSequence: []tonieapi.CreativeTonie{tonie}}
m := Target{TonieID: "t1", Folder: dir, Prune: true}
plan, err := BuildPlan(client, m)
if err != nil {
t.Fatal(err)
}
if !plan.Skipped {
t.Fatal("expected plan to be skipped for empty folder")
}
if plan.NeedsChanges() {
t.Fatal("expected NeedsChanges to be false for a skipped plan")
}
if err := ApplyPlan(client, m, plan); err != nil {
t.Fatal(err)
}
if len(client.sortedTitles) != 0 || len(client.uploadedTitles) != 0 {
t.Fatalf("expected ApplyPlan to be a no-op for a skipped plan, got uploads=%v sorted=%v", client.uploadedTitles, client.sortedTitles)
}
}
func TestApplyPlanUploadsAndSkipsReorderWhenAlreadyMatching(t *testing.T) {
dir := setupFolder(t)
before := makeTonie([]tonieapi.Chapter{{ID: "c1", Title: "01 - First"}})