diff options
-rw-r--r-- | gui/default/index.html | 6 | ||||
-rwxr-xr-x | gui/default/syncthing/core/syncthingController.js | 23 | ||||
-rw-r--r-- | lib/db/lowlevel.go | 117 | ||||
-rw-r--r-- | lib/db/set.go | 16 | ||||
-rw-r--r-- | lib/model/fakeconns_test.go | 1 | ||||
-rw-r--r-- | lib/model/folder.go | 84 | ||||
-rw-r--r-- | lib/model/folder_recvonly_test.go | 37 | ||||
-rw-r--r-- | lib/model/folder_sendrecv_test.go | 16 | ||||
-rw-r--r-- | lib/model/model.go | 4 | ||||
-rw-r--r-- | lib/model/model_test.go | 74 | ||||
-rw-r--r-- | lib/model/requests_test.go | 20 | ||||
-rw-r--r-- | lib/model/testutils_test.go | 15 |
12 files changed, 271 insertions, 142 deletions
diff --git a/gui/default/index.html b/gui/default/index.html index 07cd90d694..43ae81434d 100644 --- a/gui/default/index.html +++ b/gui/default/index.html @@ -455,12 +455,6 @@ <a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a> </td> </tr> - <tr ng-if="hasReceiveEncryptedItems(folder)"> - <th><span class="fas fa-fw fa-exclamation-circle"></span> <span translate>Locally Changed Items</span></th> - <td class="text-right"> - <a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{receiveEncryptedItemsCount(folder) | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a> - </td> - </tr> <tr ng-if="folder.type != 'sendreceive'"> <th><span class="fas fa-fw fa-folder"></span> <span translate>Folder Type</span></th> <td class="text-right"> diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index 8e91fda419..fa565b8b8d 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -931,9 +931,9 @@ angular.module('syncthing.core') return 'faileditems'; } if ($scope.hasReceiveOnlyChanged(folderCfg)) { - return 'localadditions'; - } - if ($scope.hasReceiveEncryptedItems(folderCfg)) { + if (folderCfg.type === "receiveonly") { + return 'localadditions'; + } return 'localunencrypted'; } if (folderCfg.devices.length <= 1) { @@ -2609,28 +2609,13 @@ angular.module('syncthing.core') }; $scope.hasReceiveOnlyChanged = function (folderCfg) { - if (!folderCfg || folderCfg.type !== "receiveonly") { + if (!folderCfg || folderCfg.type !== ["receiveonly", "receiveencrypted"].indexOf(folderCfg.type) === -1) { return false; } var counts = $scope.model[folderCfg.id]; return counts && counts.receiveOnlyTotalItems > 0; }; - $scope.hasReceiveEncryptedItems = function (folderCfg) { - if (!folderCfg || folderCfg.type !== "receiveencrypted") { - return false; - } - return $scope.receiveEncryptedItemsCount(folderCfg) > 0; - }; - - $scope.receiveEncryptedItemsCount = function (folderCfg) { - var counts = $scope.model[folderCfg.id]; - if (!counts) { - return 0; - } - return counts.receiveOnlyTotalItems - counts.receiveOnlyChangedDeletes; - } - $scope.revertOverride = function () { $http.post( urlbase + "/db/" + $scope.revertOverrideParams.operation +"?folder=" diff --git a/lib/db/lowlevel.go b/lib/db/lowlevel.go index 7f4916da48..88792a36d5 100644 --- a/lib/db/lowlevel.go +++ b/lib/db/lowlevel.go @@ -223,35 +223,10 @@ func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta blocksHashSame := ok && bytes.Equal(ef.BlocksHash, f.BlocksHash) if ok { - if len(ef.Blocks) != 0 && !ef.IsInvalid() && ef.Size > 0 { - for _, block := range ef.Blocks { - keyBuf, err = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name) - if err != nil { - return err - } - if err := t.Delete(keyBuf); err != nil { - return err - } - } - if !blocksHashSame { - keyBuf, err := db.keyer.GenerateBlockListMapKey(keyBuf, folder, ef.BlocksHash, name) - if err != nil { - return err - } - if err = t.Delete(keyBuf); err != nil { - return err - } - } - } - - keyBuf, err = db.keyer.GenerateSequenceKey(keyBuf, folder, ef.SequenceNo()) + keyBuf, err = db.removeLocalBlockAndSequenceInfo(keyBuf, folder, name, ef, !blocksHashSame, &t) if err != nil { return err } - if err := t.Delete(keyBuf); err != nil { - return err - } - l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, ef.SequenceNo(), ef.FileName()) } f.Sequence = meta.nextLocalSeq() @@ -314,6 +289,96 @@ func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta return t.Commit() } +func (db *Lowlevel) removeLocalFiles(folder []byte, nameStrs []string, meta *metadataTracker) error { + db.gcMut.RLock() + defer db.gcMut.RUnlock() + + t, err := db.newReadWriteTransaction(meta.CommitHook(folder)) + if err != nil { + return err + } + defer t.close() + + var dk, gk, buf []byte + for _, nameStr := range nameStrs { + name := []byte(nameStr) + dk, err = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name) + if err != nil { + return err + } + + ef, ok, err := t.getFileByKey(dk) + if err != nil { + return err + } + if !ok { + l.Debugf("remove (local); folder=%q %v: file doesn't exist", folder, nameStr) + continue + } + + buf, err = db.removeLocalBlockAndSequenceInfo(buf, folder, name, ef, true, &t) + if err != nil { + return err + } + + meta.removeFile(protocol.LocalDeviceID, ef) + + gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name) + if err != nil { + return err + } + buf, err = t.removeFromGlobal(gk, buf, folder, protocol.LocalDeviceID[:], name, meta) + if err != nil { + return err + } + + err = t.Delete(dk) + if err != nil { + return err + } + + if err := t.Checkpoint(); err != nil { + return err + } + } + + return t.Commit() +} + +func (db *Lowlevel) removeLocalBlockAndSequenceInfo(keyBuf, folder, name []byte, ef protocol.FileInfo, removeFromBlockListMap bool, t *readWriteTransaction) ([]byte, error) { + var err error + if len(ef.Blocks) != 0 && !ef.IsInvalid() && ef.Size > 0 { + for _, block := range ef.Blocks { + keyBuf, err = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name) + if err != nil { + return nil, err + } + if err := t.Delete(keyBuf); err != nil { + return nil, err + } + } + if removeFromBlockListMap { + keyBuf, err := db.keyer.GenerateBlockListMapKey(keyBuf, folder, ef.BlocksHash, name) + if err != nil { + return nil, err + } + if err = t.Delete(keyBuf); err != nil { + return nil, err + } + } + } + + keyBuf, err = db.keyer.GenerateSequenceKey(keyBuf, folder, ef.SequenceNo()) + if err != nil { + return nil, err + } + if err := t.Delete(keyBuf); err != nil { + return nil, err + } + l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, ef.SequenceNo(), ef.FileName()) + return keyBuf, nil +} + func (db *Lowlevel) dropFolder(folder []byte) error { db.gcMut.RLock() defer db.gcMut.RUnlock() diff --git a/lib/db/set.go b/lib/db/set.go index a1c0cb6496..12a7083cb8 100644 --- a/lib/db/set.go +++ b/lib/db/set.go @@ -144,6 +144,22 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) { } } +func (s *FileSet) RemoveLocalItems(items []string) { + opStr := fmt.Sprintf("%s RemoveLocalItems([%d])", s.folder, len(items)) + l.Debugf(opStr) + + s.updateMutex.Lock() + defer s.updateMutex.Unlock() + + for i := range items { + items[i] = osutil.NormalizedFilename(items[i]) + } + + if err := s.db.removeLocalFiles([]byte(s.folder), items, s.meta); err != nil && !backend.IsClosed(err) { + fatalError(err, opStr, s.db) + } +} + type Snapshot struct { folder string t readOnlyTransaction diff --git a/lib/model/fakeconns_test.go b/lib/model/fakeconns_test.go index 79b4e85520..9b5495b5f1 100644 --- a/lib/model/fakeconns_test.go +++ b/lib/model/fakeconns_test.go @@ -162,6 +162,7 @@ func (f *fakeConnection) sendIndexUpdate() { func addFakeConn(m *testModel, dev protocol.DeviceID, folderID string) *fakeConnection { fc := newFakeConnection(dev, m) + fc.folder = folderID m.AddConnection(fc, protocol.Hello{}) m.ClusterConfig(dev, protocol.ClusterConfig{ diff --git a/lib/model/folder.go b/lib/model/folder.go index 18ef41a065..41b9fe9806 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -532,16 +532,20 @@ func (f *folder) scanSubdirs(subDirs []string) error { return nil } +const maxToRemove = 1000 + type scanBatch struct { - *db.FileInfoBatch - f *folder + f *folder + updateBatch *db.FileInfoBatch + toRemove []string } func (f *folder) newScanBatch() *scanBatch { b := &scanBatch{ - f: f, + f: f, + toRemove: make([]string, 0, maxToRemove), } - b.FileInfoBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error { + b.updateBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error { if err := b.f.getHealthErrorWithoutIgnores(); err != nil { l.Debugf("Stopping scan of folder %s due to: %s", b.f.Description(), err) return err @@ -552,9 +556,32 @@ func (f *folder) newScanBatch() *scanBatch { return b } -// Append adds the fileinfo to the batch for updating, and does a few checks. +func (b *scanBatch) Remove(item string) { + b.toRemove = append(b.toRemove, item) +} + +func (b *scanBatch) flushToRemove() { + if len(b.toRemove) > 0 { + b.f.fset.RemoveLocalItems(b.toRemove) + b.toRemove = b.toRemove[:0] + } +} + +func (b *scanBatch) Flush() error { + b.flushToRemove() + return b.updateBatch.Flush() +} + +func (b *scanBatch) FlushIfFull() error { + if len(b.toRemove) >= maxToRemove { + b.flushToRemove() + } + return b.updateBatch.FlushIfFull() +} + +// Update adds the fileinfo to the batch for updating, and does a few checks. // It returns false if the checks result in the file not going to be updated or removed. -func (b *scanBatch) Append(fi protocol.FileInfo, snap *db.Snapshot) bool { +func (b *scanBatch) Update(fi protocol.FileInfo, snap *db.Snapshot) bool { // Check for a "virtual" parent directory of encrypted files. We don't track // it, but check if anything still exists within and delete it otherwise. if b.f.Type == config.FolderTypeReceiveEncrypted && fi.IsDirectory() && protocol.IsEncryptedParent(fs.PathComponents(fi.Name)) { @@ -565,20 +592,21 @@ func (b *scanBatch) Append(fi protocol.FileInfo, snap *db.Snapshot) bool { } // Resolve receive-only items which are identical with the global state or // the global item is our own receive-only item. - // Except if they are in a receive-encrypted folder and are locally added. - // Those must never be sent in index updates and thus must retain the flag. switch gf, ok := snap.GetGlobal(fi.Name); { case !ok: case gf.IsReceiveOnlyChanged(): - if b.f.Type == config.FolderTypeReceiveOnly && fi.Deleted { - l.Debugf("%v scanning: Marking deleted item as not locally changed", b.f, fi) - fi.LocalFlags &^= protocol.FlagLocalReceiveOnly + if fi.IsDeleted() { + // Our item is deleted and the global item is our own receive only + // file. No point in keeping track of that. + b.Remove(fi.Name) + return true } case gf.IsEquivalentOptional(fi, b.f.modTimeWindow, false, false, protocol.FlagLocalReceiveOnly): - l.Debugf("%v scanning: Replacing scanned file info with global as it's equivalent", b.f, fi) + // What we have locally is equivalent to the global file. + l.Debugf("%v scanning: Merging identical locally changed item with global", b.f, fi) fi = gf } - b.FileInfoBatch.Append(fi) + b.updateBatch.Append(fi) return true } @@ -634,7 +662,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i return changes, err } - if batch.Append(res.File, snap) { + if batch.Update(res.File, snap) { changes++ } @@ -642,7 +670,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted: default: if nf, ok := f.findRename(snap, res.File, alreadyUsedOrExisting); ok { - if batch.Append(nf, snap) { + if batch.Update(nf, snap) { changes++ } } @@ -683,7 +711,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch for _, file := range toIgnore { l.Debugln("marking file as ignored", file) nf := file.ConvertToIgnoredFileInfo() - if batch.Append(nf, snap) { + if batch.Update(nf, snap) { changes++ } if err := batch.FlushIfFull(); err != nil { @@ -713,7 +741,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch l.Debugln("marking file as ignored", file) nf := file.ConvertToIgnoredFileInfo() - if batch.Append(nf, snap) { + if batch.Update(nf, snap) { changes++ } @@ -743,24 +771,24 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch nf.Version = protocol.Vector{} } l.Debugln("marking file as deleted", nf) - if batch.Append(nf, snap) { + if batch.Update(nf, snap) { changes++ } case file.IsDeleted() && file.IsReceiveOnlyChanged(): switch f.Type { case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted: - if gf, _ := snap.GetGlobal(file.Name); gf.IsDeleted() { + switch gf, ok := snap.GetGlobal(file.Name); { + case !ok: + case gf.IsReceiveOnlyChanged(): + l.Debugln("removing deleted, receive-only item that is globally receive-only from db", file) + batch.Remove(file.Name) + changes++ + case gf.IsDeleted(): // Our item is deleted and the global item is deleted too. We just // pretend it is a normal deleted file (nobody cares about that). - // Except if this is a receive-encrypted folder and it - // is a locally added file. Those must never be sent - // in index updates and thus must retain the flag. - if f.Type == config.FolderTypeReceiveEncrypted && gf.IsReceiveOnlyChanged() { - return true - } l.Debugf("%v scanning: Marking globally deleted item as not locally changed: %v", f, file.Name) file.LocalFlags &^= protocol.FlagLocalReceiveOnly - if batch.Append(file.ConvertDeletedToFileInfo(), snap) { + if batch.Update(file.ConvertDeletedToFileInfo(), snap) { changes++ } } @@ -769,7 +797,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch // deleted and just the folder type/local flags changed. file.LocalFlags &^= protocol.FlagLocalReceiveOnly l.Debugln("removing receive-only flag on deleted item", file) - if batch.Append(file.ConvertDeletedToFileInfo(), snap) { + if batch.Update(file.ConvertDeletedToFileInfo(), snap) { changes++ } } @@ -788,7 +816,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch for _, file := range toIgnore { l.Debugln("marking file as ignored", f) nf := file.ConvertToIgnoredFileInfo() - if batch.Append(nf, snap) { + if batch.Update(nf, snap) { changes++ } if iterError = batch.FlushIfFull(); iterError != nil { diff --git a/lib/model/folder_recvonly_test.go b/lib/model/folder_recvonly_test.go index 9a48c78f23..740a31202c 100644 --- a/lib/model/folder_recvonly_test.go +++ b/lib/model/folder_recvonly_test.go @@ -37,9 +37,9 @@ func TestRecvOnlyRevertDeletes(t *testing.T) { for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} { must(t, ffs.MkdirAll(dir, 0755)) } - must(t, writeFile(ffs, "ignDir/ignFile", []byte("hello\n"), 0644)) - must(t, writeFile(ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644)) - must(t, writeFile(ffs, ".stignore", []byte("ignDir\n"), 0644)) + writeFilePerm(t, ffs, "ignDir/ignFile", []byte("hello\n"), 0644) + writeFilePerm(t, ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644) + writeFilePerm(t, ffs, ".stignore", []byte("ignDir\n"), 0644) knownFiles := setupKnownFiles(t, ffs, []byte("hello\n")) @@ -151,7 +151,7 @@ func TestRecvOnlyRevertNeeds(t *testing.T) { // Update the file. newData := []byte("totally different data\n") - must(t, writeFile(ffs, "knownDir/knownFile", newData, 0644)) + writeFilePerm(t, ffs, "knownDir/knownFile", newData, 0644) // Rescan. @@ -241,8 +241,8 @@ func TestRecvOnlyUndoChanges(t *testing.T) { // Create a file and modify another const file = "foo" - must(t, writeFile(ffs, file, []byte("hello\n"), 0644)) - must(t, writeFile(ffs, "knownDir/knownFile", []byte("bye\n"), 0644)) + writeFilePerm(t, ffs, file, []byte("hello\n"), 0644) + writeFilePerm(t, ffs, "knownDir/knownFile", []byte("bye\n"), 0644) must(t, m.ScanFolder("ro")) @@ -254,7 +254,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) { // Remove the file again and undo the modification must(t, ffs.Remove(file)) - must(t, writeFile(ffs, "knownDir/knownFile", oldData, 0644)) + writeFilePerm(t, ffs, "knownDir/knownFile", oldData, 0644) must(t, ffs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime())) must(t, m.ScanFolder("ro")) @@ -377,8 +377,8 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) { const file = "foo" knownFile := filepath.Join("knownDir", "knownFile") - must(t, writeFile(ffs, file, []byte("hello\n"), 0644)) - must(t, writeFile(ffs, knownFile, []byte("bye\n"), 0644)) + writeFilePerm(t, ffs, file, []byte("hello\n"), 0644) + writeFilePerm(t, ffs, knownFile, []byte("bye\n"), 0644) must(t, m.ScanFolder("ro")) @@ -434,7 +434,7 @@ func TestRecvOnlyRevertOwnID(t *testing.T) { must(t, ffs.MkdirAll(".stfolder", 0755)) data := []byte("hello\n") name := "foo" - must(t, writeFile(ffs, name, data, 0644)) + writeFilePerm(t, ffs, name, data, 0644) // Make sure the file is scanned and locally changed must(t, m.ScanFolder("ro")) @@ -484,7 +484,7 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi t.Helper() must(t, ffs.MkdirAll("knownDir", 0755)) - must(t, writeFile(ffs, "knownDir/knownFile", data, 0644)) + writeFilePerm(t, ffs, "knownDir/knownFile", data, 0644) t0 := time.Now().Add(-1 * time.Minute) must(t, ffs.Chtimes("knownDir/knownFile", t0, t0)) @@ -541,18 +541,3 @@ func setupROFolder(t *testing.T) (*testModel, *receiveOnlyFolder, context.Cancel return m, f, cancel } - -func writeFile(fs fs.Filesystem, filename string, data []byte, perm fs.FileMode) error { - fd, err := fs.Create(filename) - if err != nil { - return err - } - _, err = fd.Write(data) - if err != nil { - return err - } - if err := fd.Close(); err != nil { - return err - } - return fs.Chmod(filename, perm) -} diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index 2894fe14f5..06bf7d6d79 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -77,12 +77,10 @@ func setupFile(filename string, blockNumbers []int) protocol.FileInfo { } } -func createFile(t *testing.T, name string, fs fs.Filesystem) protocol.FileInfo { +func createEmptyFileInfo(t *testing.T, name string, fs fs.Filesystem) protocol.FileInfo { t.Helper() - f, err := fs.Create(name) - must(t, err) - f.Close() + writeFile(t, fs, name, nil) fi, err := fs.Stat(name) must(t, err) file, err := scanner.CreateFileInfo(fi, name, fs) @@ -913,7 +911,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) { name := "foo" // create local file - file := createFile(t, name, ffs) + file := createEmptyFileInfo(t, name, ffs) file.Version = protocol.Vector{}.Update(myID.Short()) f.updateLocalsFromScanning([]protocol.FileInfo{file}) @@ -945,7 +943,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) { name := "foo" // create local file - file := createFile(t, name, ffs) + file := createEmptyFileInfo(t, name, ffs) file.Version = protocol.Vector{}.Update(myID.Short()) f.updateLocalsFromScanning([]protocol.FileInfo{file}) @@ -983,7 +981,7 @@ func TestDeleteBehindSymlink(t *testing.T) { file := filepath.Join(link, "file") must(t, ffs.MkdirAll(link, 0755)) - fi := createFile(t, file, ffs) + fi := createEmptyFileInfo(t, file, ffs) f.updateLocalsFromScanning([]protocol.FileInfo{fi}) must(t, osutil.RenameOrCopy(fs.CopyRangeMethodStandard, ffs, destFs, file, "file")) must(t, ffs.RemoveAll(link)) @@ -1099,7 +1097,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) { name := "foo" contents := []byte("contents") - must(t, writeFile(ffs, name, contents, 0644)) + writeFile(t, ffs, name, contents) must(t, f.scanSubdirs(nil)) var cur protocol.FileInfo @@ -1122,7 +1120,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) { remote.Version = protocol.Vector{}.Update(device1.Short()) remote.Name = strings.ToUpper(cur.Name) temp := fs.TempName(remote.Name) - must(t, writeFile(ffs, temp, contents, 0644)) + writeFile(t, ffs, temp, contents) scanChan := make(chan string, 1) dbUpdateChan := make(chan dbUpdateJob, 1) diff --git a/lib/model/model.go b/lib/model/model.go index c1b3ddaa81..28a3a5469e 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -1053,7 +1053,6 @@ func (m *model) RemoteNeedFolderFiles(folder string, device protocol.DeviceID, p func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, error) { m.fmut.RLock() rf, ok := m.folderFiles[folder] - cfg := m.folderCfgs[folder] m.fmut.RUnlock() if !ok { @@ -1071,11 +1070,10 @@ func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]db. } p := newPager(page, perpage) - recvEnc := cfg.Type == config.FolderTypeReceiveEncrypted files := make([]db.FileInfoTruncated, 0, perpage) snap.WithHaveTruncated(protocol.LocalDeviceID, func(f protocol.FileIntf) bool { - if !f.IsReceiveOnlyChanged() || (recvEnc && f.IsDeleted()) { + if !f.IsReceiveOnlyChanged() { return true } if p.skip() { diff --git a/lib/model/model_test.go b/lib/model/model_test.go index b665abc01d..121bcdffee 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -299,7 +299,7 @@ func BenchmarkRequestInSingleFile(b *testing.B) { mustRemove(b, defaultFs.RemoveAll("request")) defer func() { mustRemove(b, defaultFs.RemoveAll("request")) }() must(b, defaultFs.MkdirAll("request/for/a/file/in/a/couple/of/dirs", 0755)) - writeFile(defaultFs, "request/for/a/file/in/a/couple/of/dirs/128k", buf, 0644) + writeFile(b, defaultFs, "request/for/a/file/in/a/couple/of/dirs/128k", buf) b.ResetTimer() @@ -1489,7 +1489,7 @@ func TestIgnores(t *testing.T) { // Assure a clean start state mustRemove(t, defaultFs.RemoveAll(config.DefaultMarkerName)) mustRemove(t, defaultFs.MkdirAll(config.DefaultMarkerName, 0644)) - writeFile(defaultFs, ".stignore", []byte(".*\nquux\n"), 0644) + writeFile(t, defaultFs, ".stignore", []byte(".*\nquux\n")) m := setupModel(t, defaultCfgWrapper) defer cleanupModel(m) @@ -2030,8 +2030,8 @@ func benchmarkTree(b *testing.B, n1, n2 int) { func TestIssue3028(t *testing.T) { // Create two files that we'll delete, one with a name that is a prefix of the other. - must(t, writeFile(defaultFs, "testrm", []byte("Hello"), 0644)) - must(t, writeFile(defaultFs, "testrm2", []byte("Hello"), 0644)) + writeFile(t, defaultFs, "testrm", []byte("Hello")) + writeFile(t, defaultFs, "testrm2", []byte("Hello")) defer func() { mustRemove(t, defaultFs.Remove("testrm")) mustRemove(t, defaultFs.Remove("testrm2")) @@ -3403,7 +3403,7 @@ func TestRenameSequenceOrder(t *testing.T) { ffs := fcfg.Filesystem() for i := 0; i < numFiles; i++ { v := fmt.Sprintf("%d", i) - must(t, writeFile(ffs, v, []byte(v), 0644)) + writeFile(t, ffs, v, []byte(v)) } m.ScanFolders() @@ -3426,7 +3426,7 @@ func TestRenameSequenceOrder(t *testing.T) { continue } v := fmt.Sprintf("%d", i) - must(t, writeFile(ffs, v, []byte(v+"-new"), 0644)) + writeFile(t, ffs, v, []byte(v+"-new")) } // Rename must(t, ffs.Rename("3", "17")) @@ -3470,7 +3470,7 @@ func TestRenameSameFile(t *testing.T) { defer cleanupModel(m) ffs := fcfg.Filesystem() - must(t, writeFile(ffs, "file", []byte("file"), 0644)) + writeFile(t, ffs, "file", []byte("file")) m.ScanFolders() @@ -3522,8 +3522,8 @@ func TestRenameEmptyFile(t *testing.T) { ffs := fcfg.Filesystem() - must(t, writeFile(ffs, "file", []byte("data"), 0644)) - must(t, writeFile(ffs, "empty", nil, 0644)) + writeFile(t, ffs, "file", []byte("data")) + writeFile(t, ffs, "empty", nil) m.ScanFolders() @@ -3598,11 +3598,11 @@ func TestBlockListMap(t *testing.T) { defer cleanupModel(m) ffs := fcfg.Filesystem() - must(t, writeFile(ffs, "one", []byte("content"), 0644)) - must(t, writeFile(ffs, "two", []byte("content"), 0644)) - must(t, writeFile(ffs, "three", []byte("content"), 0644)) - must(t, writeFile(ffs, "four", []byte("content"), 0644)) - must(t, writeFile(ffs, "five", []byte("content"), 0644)) + writeFile(t, ffs, "one", []byte("content")) + writeFile(t, ffs, "two", []byte("content")) + writeFile(t, ffs, "three", []byte("content")) + writeFile(t, ffs, "four", []byte("content")) + writeFile(t, ffs, "five", []byte("content")) m.ScanFolders() @@ -3631,7 +3631,7 @@ func TestBlockListMap(t *testing.T) { // Modify must(t, ffs.Remove("two")) - must(t, writeFile(ffs, "two", []byte("mew-content"), 0644)) + writeFile(t, ffs, "two", []byte("mew-content")) // Rename must(t, ffs.Rename("three", "new-three")) @@ -3667,7 +3667,7 @@ func TestScanRenameCaseOnly(t *testing.T) { ffs := fcfg.Filesystem() name := "foo" - must(t, writeFile(ffs, name, []byte("contents"), 0644)) + writeFile(t, ffs, name, []byte("contents")) m.ScanFolders() @@ -3791,7 +3791,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) { name := "foo" - must(t, writeFile(ffs, name, []byte(name), 0644)) + writeFile(t, ffs, name, []byte(name)) m.ScanFolders() file, ok := m.testCurrentFolderFile(fcfg.ID, name) @@ -4255,6 +4255,46 @@ func TestPendingFolder(t *testing.T) { } } +func TestDeletedNotLocallyChangedReceiveOnly(t *testing.T) { + deletedNotLocallyChanged(t, config.FolderTypeReceiveOnly) +} + +func TestDeletedNotLocallyChangedReceiveEncrypted(t *testing.T) { + deletedNotLocallyChanged(t, config.FolderTypeReceiveEncrypted) +} + +func deletedNotLocallyChanged(t *testing.T, ft config.FolderType) { + w, fcfg, wCancel := tmpDefaultWrapper() + tfs := fcfg.Filesystem() + fcfg.Type = ft + setFolder(t, w, fcfg) + defer wCancel() + m := setupModel(t, w) + defer cleanupModelAndRemoveDir(m, tfs.URI()) + + name := "foo" + writeFile(t, tfs, name, nil) + must(t, m.ScanFolder(fcfg.ID)) + + fi, ok, err := m.CurrentFolderFile(fcfg.ID, name) + must(t, err) + if !ok { + t.Fatal("File hasn't been added") + } + if !fi.IsReceiveOnlyChanged() { + t.Fatal("File isn't receive-only-changed") + } + + must(t, tfs.Remove(name)) + must(t, m.ScanFolder(fcfg.ID)) + + _, ok, err = m.CurrentFolderFile(fcfg.ID, name) + must(t, err) + if ok { + t.Error("Expected file to be removed from db") + } +} + func equalStringsInAnyOrder(a, b []string) bool { if len(a) != len(b) { return false diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go index a0c491d71b..35df4fdd9e 100644 --- a/lib/model/requests_test.go +++ b/lib/model/requests_test.go @@ -330,9 +330,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) { fc.deleteFile(invDel) fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents) fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents) - if err := writeFile(fss, ignExisting, otherContents, 0644); err != nil { - panic(err) - } + writeFile(t, fss, ignExisting, otherContents) done := make(chan struct{}) fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error { @@ -486,7 +484,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) { payload := []byte("hello") - must(t, writeFile(tfs, "foo", payload, 0777)) + writeFile(t, tfs, "foo", payload) received := make(chan []protocol.FileInfo) fc.setIndexFn(func(_ context.Context, _ string, fs []protocol.FileInfo) error { @@ -526,7 +524,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) { payload = []byte("bye") buf = make([]byte, len(payload)) - must(t, writeFile(tfs, "foo", payload, 0777)) + writeFile(t, tfs, "foo", payload) _, err = m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false) if err == nil { @@ -1066,9 +1064,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) { return nil }) - if err := writeFile(fss, file, contents, 0644); err != nil { - panic(err) - } + writeFile(t, fss, file, contents) m.ScanFolders() select { @@ -1430,6 +1426,14 @@ func TestRequestReceiveEncrypted(t *testing.T) { // Simulate request from device that is untrusted too, i.e. with non-empty, but garbage hash _, err := m.Request(device1, fcfg.ID, name, 0, 1064, 0, []byte("garbage"), 0, false) must(t, err) + + changed, err := m.LocalChangedFolderFiles(fcfg.ID, 1, 10) + must(t, err) + if l := len(changed); l != 1 { + t.Errorf("Expected one locally changed file, got %v", l) + } else if changed[0].Name != files[0].Name { + t.Errorf("Expected %v, got %v", files[0].Name, changed[0].Name) + } } func TestRequestGlobalInvalidToValid(t *testing.T) { diff --git a/lib/model/testutils_test.go b/lib/model/testutils_test.go index 6c2a29071e..4ee390f5d8 100644 --- a/lib/model/testutils_test.go +++ b/lib/model/testutils_test.go @@ -435,3 +435,18 @@ func addDevice2(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration) must(t, err) waiter.Wait() } + +func writeFile(t testing.TB, filesystem fs.Filesystem, name string, data []byte) { + t.Helper() + fd, err := filesystem.Create(name) + must(t, err) + defer fd.Close() + _, err = fd.Write(data) + must(t, err) +} + +func writeFilePerm(t testing.TB, filesystem fs.Filesystem, name string, data []byte, perm fs.FileMode) { + t.Helper() + writeFile(t, filesystem, name, data) + must(t, filesystem.Chmod(name, perm)) +} |