mirror of
https://github.com/octoleo/syncthing.git
synced 2025-02-02 11:58:28 +00:00
lib/model: Properly handle deleting multiple files when doing scans with subs (fixes #2851)
This commit is contained in:
parent
2df001fe5c
commit
aba2cc4db2
@ -259,11 +259,11 @@ func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, l
|
|||||||
return maxLocalVer
|
return maxLocalVer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Instance) withHave(folder, device []byte, truncate bool, fn Iterator) {
|
func (db *Instance) withHave(folder, device, prefix []byte, truncate bool, fn Iterator) {
|
||||||
t := db.newReadOnlyTransaction()
|
t := db.newReadOnlyTransaction()
|
||||||
defer t.close()
|
defer t.close()
|
||||||
|
|
||||||
dbi := t.NewIterator(util.BytesPrefix(db.deviceKey(folder, device, nil)[:keyPrefixLen+keyFolderLen+keyDeviceLen]), nil)
|
dbi := t.NewIterator(util.BytesPrefix(db.deviceKey(folder, device, prefix)[:keyPrefixLen+keyFolderLen+keyDeviceLen+len(prefix)]), nil)
|
||||||
defer dbi.Release()
|
defer dbi.Release()
|
||||||
|
|
||||||
for dbi.Next() {
|
for dbi.Next() {
|
||||||
|
@ -171,14 +171,18 @@ func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
|
|||||||
|
|
||||||
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
|
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
|
||||||
l.Debugf("%s WithHave(%v)", s.folder, device)
|
l.Debugf("%s WithHave(%v)", s.folder, device)
|
||||||
s.db.withHave([]byte(s.folder), device[:], false, nativeFileIterator(fn))
|
s.db.withHave([]byte(s.folder), device[:], nil, false, nativeFileIterator(fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
|
func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
|
||||||
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
|
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
|
||||||
s.db.withHave([]byte(s.folder), device[:], true, nativeFileIterator(fn))
|
s.db.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FileSet) WithPrefixedHaveTruncated(device protocol.DeviceID, prefix string, fn Iterator) {
|
||||||
|
l.Debugf("%s WithPrefixedHaveTruncated(%v)", s.folder, device)
|
||||||
|
s.db.withHave([]byte(s.folder), device[:], []byte(prefix), true, nativeFileIterator(fn))
|
||||||
|
}
|
||||||
func (s *FileSet) WithGlobal(fn Iterator) {
|
func (s *FileSet) WithGlobal(fn Iterator) {
|
||||||
l.Debugf("%s WithGlobal()", s.folder)
|
l.Debugf("%s WithGlobal()", s.folder)
|
||||||
s.db.withGlobal([]byte(s.folder), nil, false, nativeFileIterator(fn))
|
s.db.withGlobal([]byte(s.folder), nil, false, nativeFileIterator(fn))
|
||||||
|
@ -1392,76 +1392,69 @@ func (m *Model) internalScanFolderSubs(folder string, subs []string) error {
|
|||||||
m.updateLocals(folder, batch)
|
m.updateLocals(folder, batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(subs) == 0 {
|
||||||
|
// If we have no specific subdirectories to traverse, set it to one
|
||||||
|
// empty prefix so we traverse the entire folder contents once.
|
||||||
|
subs = []string{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a scan of the database for each prefix, to check for deleted files.
|
||||||
batch = batch[:0]
|
batch = batch[:0]
|
||||||
// TODO: We should limit the Have scanning to start at sub
|
for _, sub := range subs {
|
||||||
seenPrefix := false
|
var iterError error
|
||||||
var iterError error
|
|
||||||
fs.WithHaveTruncated(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
|
|
||||||
f := fi.(db.FileInfoTruncated)
|
|
||||||
hasPrefix := len(subs) == 0
|
|
||||||
for _, sub := range subs {
|
|
||||||
if strings.HasPrefix(f.Name, sub) {
|
|
||||||
hasPrefix = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return true so that we keep iterating, until we get to the part
|
|
||||||
// of the tree we are interested in. Then return false so we stop
|
|
||||||
// iterating when we've passed the end of the subtree.
|
|
||||||
if !hasPrefix {
|
|
||||||
return !seenPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
seenPrefix = true
|
fs.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
|
||||||
if !f.IsDeleted() {
|
f := fi.(db.FileInfoTruncated)
|
||||||
if f.IsInvalid() {
|
if !f.IsDeleted() {
|
||||||
return true
|
if f.IsInvalid() {
|
||||||
}
|
return true
|
||||||
|
|
||||||
if len(batch) == batchSizeFiles {
|
|
||||||
if err := m.CheckFolderHealth(folder); err != nil {
|
|
||||||
iterError = err
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
m.updateLocals(folder, batch)
|
|
||||||
batch = batch[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if ignores.Match(f.Name) || symlinkInvalid(folder, f) {
|
if len(batch) == batchSizeFiles {
|
||||||
// File has been ignored or an unsupported symlink. Set invalid bit.
|
if err := m.CheckFolderHealth(folder); err != nil {
|
||||||
l.Debugln("setting invalid bit on ignored", f)
|
iterError = err
|
||||||
nf := protocol.FileInfo{
|
return false
|
||||||
Name: f.Name,
|
}
|
||||||
Flags: f.Flags | protocol.FlagInvalid,
|
m.updateLocals(folder, batch)
|
||||||
Modified: f.Modified,
|
batch = batch[:0]
|
||||||
Version: f.Version, // The file is still the same, so don't bump version
|
|
||||||
}
|
}
|
||||||
batch = append(batch, nf)
|
|
||||||
} else if _, err := osutil.Lstat(filepath.Join(folderCfg.Path(), f.Name)); err != nil {
|
|
||||||
// File has been deleted.
|
|
||||||
|
|
||||||
// We don't specifically verify that the error is
|
if ignores.Match(f.Name) || symlinkInvalid(folder, f) {
|
||||||
// os.IsNotExist because there is a corner case when a
|
// File has been ignored or an unsupported symlink. Set invalid bit.
|
||||||
// directory is suddenly transformed into a file. When that
|
l.Debugln("setting invalid bit on ignored", f)
|
||||||
// happens, files that were in the directory (that is now a
|
nf := protocol.FileInfo{
|
||||||
// file) are deleted but will return a confusing error ("not a
|
Name: f.Name,
|
||||||
// directory") when we try to Lstat() them.
|
Flags: f.Flags | protocol.FlagInvalid,
|
||||||
|
Modified: f.Modified,
|
||||||
|
Version: f.Version, // The file is still the same, so don't bump version
|
||||||
|
}
|
||||||
|
batch = append(batch, nf)
|
||||||
|
} else if _, err := osutil.Lstat(filepath.Join(folderCfg.Path(), f.Name)); err != nil {
|
||||||
|
// File has been deleted.
|
||||||
|
|
||||||
nf := protocol.FileInfo{
|
// We don't specifically verify that the error is
|
||||||
Name: f.Name,
|
// os.IsNotExist because there is a corner case when a
|
||||||
Flags: f.Flags | protocol.FlagDeleted,
|
// directory is suddenly transformed into a file. When that
|
||||||
Modified: f.Modified,
|
// happens, files that were in the directory (that is now a
|
||||||
Version: f.Version.Update(m.shortID),
|
// file) are deleted but will return a confusing error ("not a
|
||||||
|
// directory") when we try to Lstat() them.
|
||||||
|
|
||||||
|
nf := protocol.FileInfo{
|
||||||
|
Name: f.Name,
|
||||||
|
Flags: f.Flags | protocol.FlagDeleted,
|
||||||
|
Modified: f.Modified,
|
||||||
|
Version: f.Version.Update(m.shortID),
|
||||||
|
}
|
||||||
|
batch = append(batch, nf)
|
||||||
}
|
}
|
||||||
batch = append(batch, nf)
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if iterError != nil {
|
||||||
|
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, iterError)
|
||||||
|
return iterError
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if iterError != nil {
|
|
||||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, iterError)
|
|
||||||
return iterError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.CheckFolderHealth(folder); err != nil {
|
if err := m.CheckFolderHealth(folder); err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user