From e16b8013a6a77c8d755342b5ea91b7de3c6b4653 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Thu, 4 Jun 2026 20:28:19 +0200 Subject: [PATCH] chore(db, model): separate methods to drop a device vs its files (#10480) Fixes #10469 --------- Signed-off-by: Simon Frei Signed-off-by: Jakob Borg Co-authored-by: Marcus B Spencer Co-authored-by: Jakob Borg --- internal/db/interface.go | 1 + internal/db/metrics.go | 5 ++ internal/db/sqlite/db_folderdb.go | 11 ++++ internal/db/sqlite/db_test.go | 82 +++++++++++++++++++++++++++ internal/db/sqlite/folderdb_update.go | 7 +++ lib/model/model.go | 2 +- 6 files changed, 107 insertions(+), 1 deletion(-) diff --git a/internal/db/interface.go b/internal/db/interface.go index 980e12879..ed15acc94 100644 --- a/internal/db/interface.go +++ b/internal/db/interface.go @@ -79,6 +79,7 @@ type DB interface { // Cleanup DropAllFiles(folder string, device protocol.DeviceID) error + DropFolderDevice(folder string, device protocol.DeviceID) error DropDevice(device protocol.DeviceID) error DropFilesNamed(folder string, device protocol.DeviceID, names []string) error DropFolder(folder string) error diff --git a/internal/db/metrics.go b/internal/db/metrics.go index c9565eb20..8c8fe8f26 100644 --- a/internal/db/metrics.go +++ b/internal/db/metrics.go @@ -128,6 +128,11 @@ func (m metricsDB) DropAllFiles(folder string, device protocol.DeviceID) error { return m.DB.DropAllFiles(folder, device) } +func (m metricsDB) DropFolderDevice(folder string, device protocol.DeviceID) error { + defer m.account(folder, "DropFolderDevice")() + return m.DB.DropFolderDevice(folder, device) +} + func (m metricsDB) DropDevice(device protocol.DeviceID) error { defer m.account("-", "DropDevice")() return m.DB.DropDevice(device) diff --git a/internal/db/sqlite/db_folderdb.go b/internal/db/sqlite/db_folderdb.go index c520580c5..97039b39e 100644 --- a/internal/db/sqlite/db_folderdb.go +++ b/internal/db/sqlite/db_folderdb.go @@ -255,6 +255,17 @@ func (s *DB) DropAllFiles(folder string, device protocol.DeviceID) error { return fdb.DropAllFiles(device) } +func (s *DB) DropFolderDevice(folder string, device protocol.DeviceID) error { + fdb, err := s.getFolderDB(folder, false) + if errors.Is(err, errNoSuchFolder) { + return nil + } + if err != nil { + return err + } + return fdb.DropDevice(device) +} + func (s *DB) DropFilesNamed(folder string, device protocol.DeviceID, names []string) error { fdb, err := s.getFolderDB(folder, false) if errors.Is(err, errNoSuchFolder) { diff --git a/internal/db/sqlite/db_test.go b/internal/db/sqlite/db_test.go index 8406b12e7..bc85ae7e7 100644 --- a/internal/db/sqlite/db_test.go +++ b/internal/db/sqlite/db_test.go @@ -798,6 +798,13 @@ func TestDropAllFiles(t *testing.T) { t.Fatal(err) } + // The sequence is non-zero before the drop + if seq, err := db.GetDeviceSequence("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if seq == 0 { + t.Error("expected non-zero sequence before drop") + } + // Drop folder A if err := db.DropAllFiles("a", protocol.DeviceID{1}); err != nil { t.Fatal(err) @@ -825,6 +832,20 @@ func TestDropAllFiles(t *testing.T) { t.Error("expected count to be two") } + // The device sequence for the dropped folder is reset to zero. + if seq, err := db.GetDeviceSequence("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if seq != 0 { + t.Log(seq) + t.Error("expected sequence to be reset to zero after DropAllFiles") + } + // Sequence for the untouched folder is unaffected. + if seq, err := db.GetDeviceSequence("b", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if seq == 0 { + t.Error("expected non-zero sequence for untouched folder") + } + // Drop things that don't exist if err := db.DropAllFiles("a", protocol.DeviceID{99}); err != nil { t.Fatal(err) @@ -837,6 +858,67 @@ func TestDropAllFiles(t *testing.T) { } } +func TestDropFolderDevice(t *testing.T) { + db, err := Open(t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // Files from device 1 in folder a + err = db.Update("a", protocol.DeviceID{1}, []protocol.FileInfo{ + genFile("test1", 1, 101), + genFile("test2", 2, 102), + }) + if err != nil { + t.Fatal(err) + } + + // Device 1 has an index ID. + if err := db.SetIndexID("a", protocol.DeviceID{1}, protocol.IndexID(0xdeadbeef)); err != nil { + t.Fatal(err) + } + if id, err := db.GetIndexID("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if id != protocol.IndexID(0xdeadbeef) { + t.Errorf("expected index ID to be set, got %v", id) + } + + // Drop device 1 from folder a + if err := db.DropFolderDevice("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } + + // Files for device 1 in folder a are gone + if _, ok, err := db.GetDeviceFile("a", protocol.DeviceID{1}, "test1"); err != nil || ok { + t.Log(err, ok) + t.Error("expected device 1 file in folder A to not exist") + } + if c, err := db.CountLocal("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if c.Files != 0 { + t.Log(c) + t.Error("expected device 1 count in folder A to be zero") + } + + // The index ID for device 1 in folder A is gone. + if id, err := db.GetIndexID("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if id != 0 { + t.Errorf("expected index ID to be cleared, got %v", id) + } + if seq, err := db.GetDeviceSequence("a", protocol.DeviceID{1}); err != nil { + t.Fatal(err) + } else if seq != 0 { + t.Log(seq) + t.Error("expected sequence to be zero after DropFolderDevice") + } +} + func TestConcurrentUpdate(t *testing.T) { t.Parallel() diff --git a/internal/db/sqlite/folderdb_update.go b/internal/db/sqlite/folderdb_update.go index 1993c1bbc..56ff37216 100644 --- a/internal/db/sqlite/folderdb_update.go +++ b/internal/db/sqlite/folderdb_update.go @@ -235,6 +235,13 @@ func (s *folderDB) DropAllFiles(device protocol.DeviceID) error { defer tx.Rollback() //nolint:errcheck txp := &txPreparedStmts{Tx: tx} + if _, err := tx.Exec(` + UPDATE indexids SET sequence = 0 + WHERE device_idx = ? + `, deviceIdx); err != nil { + return wrap(err) + } + // Drop all the file entries result, err := tx.Exec(` diff --git a/lib/model/model.go b/lib/model/model.go index a57bd23ab..8f61f0e90 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -371,7 +371,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio for _, available := range devs { if _, ok := expected[available]; !ok { l.Debugln("dropping", folder, "state for", available) - _ = m.sdb.DropAllFiles(folder, available) + _ = m.sdb.DropFolderDevice(folder, available) } }