summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Frei <freisim93@gmail.com>2021-11-10 09:46:21 +0100
committerGitHub <noreply@github.com>2021-11-10 09:46:21 +0100
commit591e4d8af1a60768a573dbb5409016d30d6b18b1 (patch)
tree25bb4e66132367a1d3c1c97946fd0f564bdf00e5
parentdec6f80d2bc9067347d80cba692da49c38cbdfde (diff)
gui, lib: Fix tracking deleted locally-changed on encrypted (fixes #7715) (#7726)v1.18.5-rc.1
-rw-r--r--gui/default/index.html6
-rwxr-xr-xgui/default/syncthing/core/syncthingController.js23
-rw-r--r--lib/db/lowlevel.go117
-rw-r--r--lib/db/set.go16
-rw-r--r--lib/model/fakeconns_test.go1
-rw-r--r--lib/model/folder.go84
-rw-r--r--lib/model/folder_recvonly_test.go37
-rw-r--r--lib/model/folder_sendrecv_test.go16
-rw-r--r--lib/model/model.go4
-rw-r--r--lib/model/model_test.go74
-rw-r--r--lib/model/requests_test.go20
-rw-r--r--lib/model/testutils_test.go15
12 files changed, 271 insertions, 142 deletions
diff --git a/gui/default/index.html b/gui/default/index.html
index 07cd90d69..43ae81434 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>&nbsp;<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>&nbsp;<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 8e91fda41..fa565b8b8 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 7f4916da4..88792a36d 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 a1c0cb649..12a7083cb 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 79b4e8552..9b5495b5f 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 18ef41a06..41b9fe980 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 9a48c78f2..740a31202 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 2894fe14f..06bf7d6d7 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 c1b3ddaa8..28a3a5469 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 b665abc01..121bcdffe 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 a0c491d71..35df4fdd9 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 6c2a29071..4ee390f5d 100644
--- a/lib/model/testutils_test.go
+++ b/lib/model/testutils_test.go