diff --git a/lib/db/set.go b/lib/db/set.go index 887f5dcb3..2728981bf 100644 --- a/lib/db/set.go +++ b/lib/db/set.go @@ -333,9 +333,7 @@ func (s *Snapshot) NeedSize() Counts { return result } -// LocalChangedFiles returns a paginated list of currently needed files in -// progress, queued, and to be queued on next puller iteration, as well as the -// total number of files currently needed. +// LocalChangedFiles returns a paginated list of files that were changed locally. func (s *Snapshot) LocalChangedFiles(page, perpage int) []FileInfoTruncated { if s.ReceiveOnlyChangedSize().TotalItems() == 0 { return nil diff --git a/lib/db/structs.go b/lib/db/structs.go index af8a629af..0fad0cbe3 100644 --- a/lib/db/structs.go +++ b/lib/db/structs.go @@ -140,6 +140,14 @@ func (f FileInfoTruncated) ConvertToDeletedFileInfo(by protocol.ShortID) protoco return file } +// ConvertDeletedToFileInfo converts a deleted truncated file info to a regular file info +func (f FileInfoTruncated) ConvertDeletedToFileInfo() protocol.FileInfo { + if !f.Deleted { + panic("ConvertDeletedToFileInfo must only be called on deleted items") + } + return f.copyToFileInfo() +} + // copyToFileInfo just copies all members of FileInfoTruncated to protocol.FileInfo func (f FileInfoTruncated) copyToFileInfo() protocol.FileInfo { return protocol.FileInfo{ diff --git a/lib/model/folder.go b/lib/model/folder.go index fa2294c8e..46d22cdd7 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -556,6 +556,20 @@ func (f *folder) scanSubdirs(subDirs []string) error { batch.append(nf) changes++ } + + // Check for deleted, locally changed items that noone else has. + if f.localFlags&protocol.FlagLocalReceiveOnly == 0 { + return true + } + if !fi.IsDeleted() || !fi.IsReceiveOnlyChanged() || len(snap.Availability(fi.FileName())) > 0 { + return true + } + nf := fi.(db.FileInfoTruncated).ConvertDeletedToFileInfo() + nf.LocalFlags = 0 + nf.Version = protocol.Vector{} + batch.append(nf) + changes++ + return true }) diff --git a/lib/model/folder_recvonly_test.go b/lib/model/folder_recvonly_test.go index a3d48b6dc..69523f080 100644 --- a/lib/model/folder_recvonly_test.go +++ b/lib/model/folder_recvonly_test.go @@ -263,6 +263,69 @@ func TestRecvOnlyUndoChanges(t *testing.T) { } } +func TestRecvOnlyDeletedRemoteDrop(t *testing.T) { + // Get us a model up and running + + m, f := setupROFolder(t) + ffs := f.Filesystem() + defer cleanupModel(m) + + // Create some test data + + must(t, ffs.MkdirAll(".stfolder", 0755)) + oldData := []byte("hello\n") + knownFiles := setupKnownFiles(t, ffs, oldData) + + // Send an index update for the known stuff + + m.Index(device1, "ro", knownFiles) + f.updateLocalsFromScanning(knownFiles) + + // Scan the folder. + + must(t, m.ScanFolder("ro")) + + // Everything should be in sync. + + size := globalSize(t, m, "ro") + if size.Files != 1 || size.Directories != 1 { + t.Fatalf("Global: expected 1 file and 1 directory: %+v", size) + } + size = localSize(t, m, "ro") + if size.Files != 1 || size.Directories != 1 { + t.Fatalf("Local: expected 1 file and 1 directory: %+v", size) + } + size = needSize(t, m, "ro") + if size.Files+size.Directories > 0 { + t.Fatalf("Need: expected nothing: %+v", size) + } + size = receiveOnlyChangedSize(t, m, "ro") + if size.Files+size.Directories > 0 { + t.Fatalf("ROChanged: expected nothing: %+v", size) + } + + // Delete our file + + must(t, ffs.Remove(knownFiles[1].Name)) + + must(t, m.ScanFolder("ro")) + + size = receiveOnlyChangedSize(t, m, "ro") + if size.Deleted != 1 { + t.Fatalf("Receive only: expected 1 deleted: %+v", size) + } + + // Drop the remote + + f.fset.Drop(device1) + must(t, m.ScanFolder("ro")) + + size = receiveOnlyChangedSize(t, m, "ro") + if size.Deleted != 0 { + t.Fatalf("Receive only: expected no deleted: %+v", size) + } +} + func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.FileInfo { t.Helper() @@ -305,11 +368,13 @@ func setupROFolder(t *testing.T) (*model, *receiveOnlyFolder) { t.Helper() w := createTmpWrapper(defaultCfg) + cfg := w.RawCopy() fcfg := testFolderConfigFake() fcfg.ID = "ro" fcfg.Label = "ro" fcfg.Type = config.FolderTypeReceiveOnly - w.SetFolder(fcfg) + cfg.Folders = []config.FolderConfiguration{fcfg} + w.Replace(cfg) m := newModel(w, myID, "syncthing", "dev", db.NewLowlevel(backend.OpenMemory()), nil) m.ServeBackground()