diff --git a/lib/model/folder.go b/lib/model/folder.go index 12b8ee1a..1779aa28 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -41,6 +41,8 @@ type folder struct { model *model shortID protocol.ShortID + fset *db.FileSet + ignores *ignore.Matcher ctx context.Context cancel context.CancelFunc @@ -73,7 +75,7 @@ type puller interface { pull() bool // true when successfull and should not be retried } -func newFolder(model *model, cfg config.FolderConfiguration) folder { +func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration) folder { ctx, cancel := context.WithCancel(context.Background()) return folder{ @@ -82,6 +84,8 @@ func newFolder(model *model, cfg config.FolderConfiguration) folder { model: model, shortID: model.shortID, + fset: fset, + ignores: ignores, ctx: ctx, cancel: cancel, @@ -285,11 +289,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { return err } - f.model.fmut.RLock() - fset := f.model.folderFiles[f.ID] - ignores := f.model.folderIgnores[f.ID] - f.model.fmut.RUnlock() - mtimefs := fset.MtimeFS() + mtimefs := f.fset.MtimeFS() f.setState(FolderScanWaiting) scanLimiter.take(1) @@ -311,16 +311,16 @@ func (f *folder) scanSubdirs(subDirs []string) error { // Check if the ignore patterns changed as part of scanning this folder. // If they did we should schedule a pull of the folder so that we // request things we might have suddenly become unignored and so on. - oldHash := ignores.Hash() + oldHash := f.ignores.Hash() defer func() { - if ignores.Hash() != oldHash { + if f.ignores.Hash() != oldHash { l.Debugln("Folder", f.Description(), "ignore patterns change detected while scanning; triggering puller") f.ignoresUpdated() f.SchedulePull() } }() - if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) { + if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) { err = fmt.Errorf("loading ignores: %v", err) f.setError(err) return err @@ -329,8 +329,8 @@ func (f *folder) scanSubdirs(subDirs []string) error { // Clean the list of subitems to ensure that we start at a known // directory, and don't scan subdirectories of things we've already // scanned. - subDirs = unifySubs(subDirs, func(f string) bool { - _, ok := fset.Get(protocol.LocalDeviceID, f) + subDirs = unifySubs(subDirs, func(file string) bool { + _, ok := f.fset.Get(protocol.LocalDeviceID, file) return ok }) @@ -339,14 +339,14 @@ func (f *folder) scanSubdirs(subDirs []string) error { fchan := scanner.Walk(f.ctx, scanner.Config{ Folder: f.ID, Subs: subDirs, - Matcher: ignores, + Matcher: f.ignores, TempLifetime: time.Duration(f.model.cfg.Options().KeepTemporariesH) * time.Hour, CurrentFiler: cFiler{f.model, f.ID}, Filesystem: mtimefs, IgnorePerms: f.IgnorePerms, AutoNormalize: f.AutoNormalize, Hashers: f.model.numHashers(f.ID), - ShortID: f.model.shortID, + ShortID: f.shortID, ProgressTickIntervalS: f.ScanProgressIntervalS, UseLargeBlocks: f.UseLargeBlocks, LocalFlags: f.localFlags, @@ -365,7 +365,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { oldBatchFn := batchFn // can't reference batchFn directly (recursion) batchFn = func(fs []protocol.FileInfo) error { for i := range fs { - switch gf, ok := fset.GetGlobal(fs[i].Name); { + switch gf, ok := f.fset.GetGlobal(fs[i].Name); { case !ok: continue case gf.IsEquivalentOptional(fs[i], false, false, protocol.FlagLocalReceiveOnly): @@ -425,7 +425,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { for _, sub := range subDirs { var iterError error - fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool { + f.fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool { file := fi.(db.FileInfoTruncated) if err := batch.flushIfFull(); err != nil { @@ -436,7 +436,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { if ignoredParent != "" && !fs.IsParent(file.Name, ignoredParent) { for _, file := range toIgnore { l.Debugln("marking file as ignored", file) - nf := file.ConvertToIgnoredFileInfo(f.model.id.Short()) + nf := file.ConvertToIgnoredFileInfo(f.shortID) batch.append(nf) changes++ if err := batch.flushIfFull(); err != nil { @@ -448,7 +448,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { ignoredParent = "" } - switch ignored := ignores.Match(file.Name).IsIgnored(); { + switch ignored := f.ignores.Match(file.Name).IsIgnored(); { case !file.IsIgnored() && ignored: // File was not ignored at last pass but has been ignored. if file.IsDirectory() { @@ -463,7 +463,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { } l.Debugln("marking file as ignored", f) - nf := file.ConvertToIgnoredFileInfo(f.model.id.Short()) + nf := file.ConvertToIgnoredFileInfo(f.shortID) batch.append(nf) changes++ @@ -490,9 +490,9 @@ func (f *folder) scanSubdirs(subDirs []string) error { Size: 0, ModifiedS: file.ModifiedS, ModifiedNs: file.ModifiedNs, - ModifiedBy: f.model.id.Short(), + ModifiedBy: f.shortID, Deleted: true, - Version: file.Version.Update(f.model.shortID), + Version: file.Version.Update(f.shortID), LocalFlags: f.localFlags, } // We do not want to override the global version @@ -501,7 +501,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { // other existing versions, which will be resolved // by the normal pulling mechanisms. if file.ShouldConflict() { - nf.Version = nf.Version.DropOthers(f.model.shortID) + nf.Version = nf.Version.DropOthers(f.shortID) } batch.append(nf) @@ -513,7 +513,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { if iterError == nil && len(toIgnore) > 0 { for _, file := range toIgnore { l.Debugln("marking file as ignored", f) - nf := file.ConvertToIgnoredFileInfo(f.model.id.Short()) + nf := file.ConvertToIgnoredFileInfo(f.shortID) batch.append(nf) changes++ if iterError = batch.flushIfFull(); iterError != nil { @@ -603,24 +603,21 @@ func (f *folder) restartWatch() { // this asynchronously, you should probably use scheduleWatchRestart instead. func (f *folder) startWatch() { ctx, cancel := context.WithCancel(f.ctx) - f.model.fmut.RLock() - ignores := f.model.folderIgnores[f.folderID] - f.model.fmut.RUnlock() f.watchMut.Lock() f.watchChan = make(chan []string) f.watchCancel = cancel f.watchMut.Unlock() - go f.startWatchAsync(ctx, ignores) + go f.startWatchAsync(ctx) } // startWatchAsync tries to start the filesystem watching and retries every minute on failure. // It is a convenience function that should not be used except in startWatch. -func (f *folder) startWatchAsync(ctx context.Context, ignores *ignore.Matcher) { +func (f *folder) startWatchAsync(ctx context.Context) { timer := time.NewTimer(0) for { select { case <-timer.C: - eventChan, err := f.Filesystem().Watch(".", ignores, ctx, f.IgnorePerms) + eventChan, err := f.Filesystem().Watch(".", f.ignores, ctx, f.IgnorePerms) f.watchMut.Lock() prevErr := f.watchErr f.watchErr = err diff --git a/lib/model/folder_recvonly.go b/lib/model/folder_recvonly.go index a9902397..6ba0c984 100644 --- a/lib/model/folder_recvonly.go +++ b/lib/model/folder_recvonly.go @@ -56,8 +56,8 @@ type receiveOnlyFolder struct { *sendReceiveFolder } -func newReceiveOnlyFolder(model *model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service { - sr := newSendReceiveFolder(model, cfg, ver, fs).(*sendReceiveFolder) +func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service { + sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, fs).(*sendReceiveFolder) sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files return &receiveOnlyFolder{sr} } @@ -66,18 +66,13 @@ func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.File f.setState(FolderScanning) defer f.setState(FolderIdle) - // XXX: This *really* should be given to us in the constructor... - f.model.fmut.RLock() - ignores := f.model.folderIgnores[f.folderID] - f.model.fmut.RUnlock() - scanChan := make(chan string) go f.pullScannerRoutine(scanChan) defer close(scanChan) delQueue := &deleteQueue{ handler: f, // for the deleteItemOnDisk and deleteDirOnDisk methods - ignores: ignores, + ignores: f.ignores, scanChan: scanChan, } @@ -171,8 +166,8 @@ func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.File // directories for last. type deleteQueue struct { handler interface { - deleteItemOnDisk(item protocol.FileInfo, ignores *ignore.Matcher, scanChan chan<- string) error - deleteDirOnDisk(dir string, ignores *ignore.Matcher, scanChan chan<- string) error + deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) error + deleteDirOnDisk(dir string, scanChan chan<- string) error } ignores *ignore.Matcher dirs []string @@ -193,7 +188,7 @@ func (q *deleteQueue) handle(fi protocol.FileInfo) (bool, error) { } // Kill it. - err := q.handler.deleteItemOnDisk(fi, q.ignores, q.scanChan) + err := q.handler.deleteItemOnDisk(fi, q.scanChan) return true, err } @@ -205,7 +200,7 @@ func (q *deleteQueue) flush() ([]string, error) { var deleted []string for _, dir := range q.dirs { - if err := q.handler.deleteDirOnDisk(dir, q.ignores, q.scanChan); err == nil { + if err := q.handler.deleteDirOnDisk(dir, q.scanChan); err == nil { deleted = append(deleted, dir) } else if err != nil && firstError == nil { firstError = err diff --git a/lib/model/folder_sendonly.go b/lib/model/folder_sendonly.go index 3deaa212..4180b25d 100644 --- a/lib/model/folder_sendonly.go +++ b/lib/model/folder_sendonly.go @@ -10,6 +10,7 @@ import ( "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/db" "github.com/syncthing/syncthing/lib/fs" + "github.com/syncthing/syncthing/lib/ignore" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/versioner" ) @@ -22,9 +23,9 @@ type sendOnlyFolder struct { folder } -func newSendOnlyFolder(model *model, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service { +func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service { f := &sendOnlyFolder{ - folder: newFolder(model, cfg), + folder: newFolder(model, fset, ignores, cfg), } f.folder.puller = f return f @@ -43,22 +44,17 @@ func (f *sendOnlyFolder) pull() bool { return false } - f.model.fmut.RLock() - folderFiles := f.model.folderFiles[f.folderID] - ignores := f.model.folderIgnores[f.folderID] - f.model.fmut.RUnlock() - batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles) batchSizeBytes := 0 - folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool { + f.fset.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool { if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes { f.model.updateLocalsFromPulling(f.folderID, batch) batch = batch[:0] batchSizeBytes = 0 } - if ignores.ShouldIgnore(intf.FileName()) { + if f.ignores.ShouldIgnore(intf.FileName()) { file := intf.(protocol.FileInfo) file.SetIgnored(f.shortID) batch = append(batch, file) @@ -67,7 +63,7 @@ func (f *sendOnlyFolder) pull() bool { return true } - curFile, ok := f.model.CurrentFolderFile(f.folderID, intf.FileName()) + curFile, ok := f.fset.Get(protocol.LocalDeviceID, intf.FileName()) if !ok { if intf.IsDeleted() { panic("Should never get a deleted file as needed when we don't have it") diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 0d3611db..ad211cb3 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -107,9 +107,9 @@ type sendReceiveFolder struct { pullErrorsMut sync.Mutex } -func newSendReceiveFolder(model *model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service { +func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service { f := &sendReceiveFolder{ - folder: newFolder(model, cfg), + folder: newFolder(model, fset, ignores, cfg), fs: fs, versioner: ver, queue: newJobQueue(), @@ -144,14 +144,9 @@ func (f *sendReceiveFolder) pull() bool { return true } - f.model.fmut.RLock() - ignores := f.model.folderIgnores[f.folderID] - folderFiles := f.model.folderFiles[f.folderID] - f.model.fmut.RUnlock() - // If there is nothing to do, don't even enter pulling state. abort := true - folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool { + f.fset.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool { abort = false return false }) @@ -165,13 +160,13 @@ func (f *sendReceiveFolder) pull() bool { } // Check if the ignore patterns changed. - oldHash := ignores.Hash() + oldHash := f.ignores.Hash() defer func() { - if ignores.Hash() != oldHash { + if f.ignores.Hash() != oldHash { f.ignoresUpdated() } }() - if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) { + if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) { err = fmt.Errorf("loading ignores: %v", err) f.setError(err) return false @@ -197,7 +192,7 @@ func (f *sendReceiveFolder) pull() bool { default: } - changed := f.pullerIteration(ignores, folderFiles, scanChan) + changed := f.pullerIteration(scanChan) l.Debugln(f, "changed", changed, "on try", tries+1) @@ -227,7 +222,7 @@ func (f *sendReceiveFolder) pull() bool { // returns the number items that should have been synced (even those that // might have failed). One puller iteration handles all files currently // flagged as needed in the folder. -func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, folderFiles *db.FileSet, scanChan chan<- string) int { +func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) int { pullChan := make(chan pullBlockState) copyChan := make(chan copyBlocksState) finisherChan := make(chan *sharedPullerState) @@ -266,11 +261,11 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, folderFiles doneWg.Add(1) // finisherRoutine finishes when finisherChan is closed go func() { - f.finisherRoutine(ignores, finisherChan, dbUpdateChan, scanChan) + f.finisherRoutine(finisherChan, dbUpdateChan, scanChan) doneWg.Done() }() - changed, fileDeletions, dirDeletions, err := f.processNeeded(ignores, folderFiles, dbUpdateChan, copyChan, finisherChan, scanChan) + changed, fileDeletions, dirDeletions, err := f.processNeeded(dbUpdateChan, copyChan, finisherChan, scanChan) // Signal copy and puller routines that we are done with the in data for // this iteration. Wait for them to finish. @@ -285,7 +280,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, folderFiles doneWg.Wait() if err == nil { - f.processDeletions(ignores, fileDeletions, dirDeletions, dbUpdateChan, scanChan) + f.processDeletions(fileDeletions, dirDeletions, dbUpdateChan, scanChan) } // Wait for db updates and scan scheduling to complete @@ -295,7 +290,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, folderFiles return changed } -func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles *db.FileSet, dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) { +func (f *sendReceiveFolder) processNeeded(dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) { changed := 0 var processDirectly []protocol.FileInfo var dirDeletions []protocol.FileInfo @@ -306,7 +301,7 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles * // Regular files to pull goes into the file queue, everything else // (directories, symlinks and deletes) goes into the "process directly" // pile. - folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool { + f.fset.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool { select { case <-f.ctx.Done(): return false @@ -321,7 +316,7 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles * file := intf.(protocol.FileInfo) switch { - case ignores.ShouldIgnore(file.Name): + case f.ignores.ShouldIgnore(file.Name): file.SetIgnored(f.shortID) l.Debugln(f, "Handling ignored file", file) dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate} @@ -337,7 +332,7 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles * dirDeletions = append(dirDeletions, file) } else { fileDeletions[file.Name] = file - df, ok := f.model.CurrentFolderFile(f.folderID, file.Name) + df, ok := f.fset.Get(protocol.LocalDeviceID, file.Name) // Local file can be already deleted, but with a lower version // number, hence the deletion coming in again as part of // WithNeed, furthermore, the file can simply be of the wrong @@ -397,11 +392,11 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles * switch { case fi.IsDirectory() && !fi.IsSymlink(): l.Debugln(f, "Handling directory", fi.Name) - f.handleDir(fi, ignores, dbUpdateChan, scanChan) + f.handleDir(fi, dbUpdateChan, scanChan) case fi.IsSymlink(): l.Debugln(f, "Handling symlink", fi.Name) - f.handleSymlink(fi, ignores, dbUpdateChan, scanChan) + f.handleSymlink(fi, dbUpdateChan, scanChan) default: l.Warnln(fi) @@ -441,7 +436,7 @@ nextFile: break } - fi, ok := f.model.CurrentGlobalFile(f.folderID, fileName) + fi, ok := f.fset.GetGlobal(fileName) if !ok { // File is no longer in the index. Mark it as done and drop it. f.queue.Done(fileName) @@ -474,7 +469,7 @@ nextFile: // desired state with the delete bit set is in the deletion // map. desired := fileDeletions[candidate.Name] - if err := f.renameFile(candidate, desired, fi, ignores, dbUpdateChan, scanChan); err != nil { + if err := f.renameFile(candidate, desired, fi, dbUpdateChan, scanChan); err != nil { // Failed to rename, try to handle files as separate // deletions and updates. break @@ -488,7 +483,7 @@ nextFile: } } - devices := folderFiles.Availability(fileName) + devices := f.fset.Availability(fileName) for _, dev := range devices { if _, ok := f.model.Connection(dev); ok { changed++ @@ -503,7 +498,7 @@ nextFile: return changed, fileDeletions, dirDeletions, nil } -func (f *sendReceiveFolder) processDeletions(ignores *ignore.Matcher, fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { for _, file := range fileDeletions { select { case <-f.ctx.Done(): @@ -528,12 +523,12 @@ func (f *sendReceiveFolder) processDeletions(ignores *ignore.Matcher, fileDeleti dir := dirDeletions[len(dirDeletions)-i-1] l.Debugln(f, "Deleting dir", dir.Name) - f.deleteDir(dir, ignores, dbUpdateChan, scanChan) + f.deleteDir(dir, dbUpdateChan, scanChan) } } // handleDir creates or updates the given directory -func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, ignores *ignore.Matcher, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error @@ -561,7 +556,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, ignores *ignore.Ma } if shouldDebug() { - curFile, _ := f.model.CurrentFolderFile(f.folderID, file.Name) + curFile, _ := f.fset.Get(protocol.LocalDeviceID, file.Name) l.Debugf("need dir\n\t%v\n\t%v", file, curFile) } @@ -596,7 +591,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, ignores *ignore.Ma return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, f.fs, curFile.Name) } else { - err = f.deleteItemOnDisk(file, ignores, scanChan) + err = f.deleteItemOnDisk(file, scanChan) } if err != nil { f.newPullError(file.Name, err) @@ -691,7 +686,7 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo } // handleSymlink creates or updates the given symlink -func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, ignores *ignore.Matcher, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error @@ -714,7 +709,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, ignores *ignor }() if shouldDebug() { - curFile, _ := f.model.CurrentFolderFile(f.folderID, file.Name) + curFile, _ := f.fset.Get(protocol.LocalDeviceID, file.Name) l.Debugf("need symlink\n\t%v\n\t%v", file, curFile) } @@ -752,7 +747,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, ignores *ignor return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, f.fs, curFile.Name) } else { - err = f.deleteItemOnDisk(file, ignores, scanChan) + err = f.deleteItemOnDisk(file, scanChan) } if err != nil { f.newPullError(file.Name, errors.Wrap(err, "symlink remove")) @@ -777,7 +772,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, ignores *ignor } // deleteDir attempts to remove a directory that was deleted on a remote -func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, ignores *ignore.Matcher, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error @@ -799,7 +794,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, ignores *ignore.Ma }) }() - if err = f.deleteDirOnDisk(file.Name, ignores, scanChan); err != nil { + if err = f.deleteDirOnDisk(file.Name, scanChan); err != nil { f.newPullError(file.Name, errors.Wrap(err, "delete dir")) return } @@ -830,7 +825,7 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, scanChan chan<- s }) }() - cur, ok := f.model.CurrentFolderFile(f.folderID, file.Name) + cur, ok := f.fset.Get(protocol.LocalDeviceID, file.Name) if !ok { // We should never try to pull a deletion for a file we don't have in the DB. l.Debugln(f, "not deleting file we don't have", file.Name) @@ -879,7 +874,7 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, scanChan chan<- s // renameFile attempts to rename an existing file to a destination // and set the right attributes on it. -func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, ignores *ignore.Matcher, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { +func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error @@ -921,7 +916,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, ig return err } // Check that the target corresponds to what we have in the DB - curTarget, ok := f.model.CurrentFolderFile(f.folderID, target.Name) + curTarget, ok := f.fset.Get(protocol.LocalDeviceID, target.Name) switch stat, serr := f.fs.Lstat(target.Name); { case serr != nil && fs.IsNotExist(serr): if !ok || curTarget.IsDeleted() { @@ -977,7 +972,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, ig // of the source and the creation of the target temp file. Fix-up the metadata, // update the local index of the target file and rename from temp to real name. - if err = f.performFinish(ignores, target, curTarget, true, tempName, dbUpdateChan, scanChan); err != nil { + if err = f.performFinish(target, curTarget, true, tempName, dbUpdateChan, scanChan); err != nil { return err } @@ -1023,7 +1018,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, ig // handleFile queues the copies and pulls as necessary for a single new or // changed file. func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState, dbUpdateChan chan<- dbUpdateJob) { - curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name) + curFile, hasCurFile := f.fset.Get(protocol.LocalDeviceID, file.Name) have, need := blockDiff(curFile.Blocks, file.Blocks) @@ -1230,12 +1225,10 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch folderFilesystems := make(map[string]fs.Filesystem) var folders []string - f.model.fmut.RLock() - for folder, cfg := range f.model.folderCfgs { + for folder, cfg := range f.model.cfg.Folders() { folderFilesystems[folder] = cfg.Filesystem() folders = append(folders, folder) } - f.model.fmut.RUnlock() var file fs.File var weakHashFinder *weakhash.Finder @@ -1491,7 +1484,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu out <- state.sharedPullerState } -func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, file, curFile protocol.FileInfo, hasCurFile bool, tempName string, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { +func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCurFile bool, tempName string, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { // Set the correct permission bits on the new file if !f.IgnorePerms && !file.NoPermissions { if err := f.fs.Chmod(tempName, fs.FileMode(file.Permissions&0777)); err != nil { @@ -1528,7 +1521,7 @@ func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, file, curFile return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, f.fs, curFile.Name) } else { - err = f.deleteItemOnDisk(file, ignores, scanChan) + err = f.deleteItemOnDisk(file, scanChan) } if err != nil { return err @@ -1549,7 +1542,7 @@ func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, file, curFile return nil } -func (f *sendReceiveFolder) finisherRoutine(ignores *ignore.Matcher, in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { +func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) { for state := range in { if closed, err := state.finalClose(); closed { l.Debugln(f, "closing", state.file.Name) @@ -1557,7 +1550,7 @@ func (f *sendReceiveFolder) finisherRoutine(ignores *ignore.Matcher, in <-chan * f.queue.Done(state.file.Name) if err == nil { - err = f.performFinish(ignores, state.file, state.curFile, state.hasCurFile, state.tempName, dbUpdateChan, scanChan) + err = f.performFinish(state.file, state.curFile, state.hasCurFile, state.tempName, dbUpdateChan, scanChan) } if err != nil { @@ -1804,7 +1797,7 @@ func (f *sendReceiveFolder) Errors() []FileError { } // deleteItemOnDisk deletes the file represented by old that is about to be replaced by new. -func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, ignores *ignore.Matcher, scanChan chan<- string) (err error) { +func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, scanChan chan<- string) (err error) { defer func() { err = errors.Wrap(err, contextRemovingOldItem) }() @@ -1813,7 +1806,7 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, ignores *ig case item.IsDirectory(): // Directories aren't archived and need special treatment due // to potential children. - return f.deleteDirOnDisk(item.Name, ignores, scanChan) + return f.deleteDirOnDisk(item.Name, scanChan) case !item.IsSymlink() && f.versioner != nil: // If we should use versioning, let the versioner archive the @@ -1829,7 +1822,7 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, ignores *ig // deleteDirOnDisk attempts to delete a directory. It checks for files/dirs inside // the directory and removes them if possible or returns an error if it fails -func (f *sendReceiveFolder) deleteDirOnDisk(dir string, ignores *ignore.Matcher, scanChan chan<- string) error { +func (f *sendReceiveFolder) deleteDirOnDisk(dir string, scanChan chan<- string) error { files, _ := f.fs.DirNames(dir) toBeDeleted := make([]string, 0, len(files)) @@ -1840,11 +1833,11 @@ func (f *sendReceiveFolder) deleteDirOnDisk(dir string, ignores *ignore.Matcher, for _, dirFile := range files { fullDirFile := filepath.Join(dir, dirFile) - if fs.IsTemporary(dirFile) || ignores.Match(fullDirFile).IsDeletable() { + if fs.IsTemporary(dirFile) || f.ignores.Match(fullDirFile).IsDeletable() { toBeDeleted = append(toBeDeleted, fullDirFile) - } else if ignores != nil && ignores.Match(fullDirFile).IsIgnored() { + } else if f.ignores != nil && f.ignores.Match(fullDirFile).IsIgnored() { hasIgnored = true - } else if cf, ok := f.model.CurrentFolderFile(f.ID, fullDirFile); !ok || cf.IsDeleted() || cf.IsInvalid() { + } else if cf, ok := f.fset.Get(protocol.LocalDeviceID, fullDirFile); !ok || cf.IsDeleted() || cf.IsInvalid() { // Something appeared in the dir that we either are not aware of // at all, that we think should be deleted or that is invalid, // but not currently ignored -> schedule scan. The scanChan diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index cf1e9948..84d86519 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -103,6 +103,7 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol folder: folder{ stateTracker: newStateTracker("default"), model: model, + fset: model.folderFiles[fcfg.ID], initialScanFinished: make(chan struct{}), ctx: context.TODO(), FolderConfiguration: fcfg, @@ -490,7 +491,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 1) go f.copierRoutine(copyChan, pullChan, finisherBufferChan) - go f.finisherRoutine(ignore.New(defaultFs), finisherChan, dbUpdateChan, make(chan string)) + go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string)) f.handleFile(file, copyChan, finisherChan, dbUpdateChan) @@ -581,7 +582,7 @@ func TestDeregisterOnFailInPull(t *testing.T) { go f.copierRoutine(copyChan, pullChan, finisherBufferChan) go f.pullerRoutine(pullChan, finisherBufferChan) - go f.finisherRoutine(ignore.New(defaultFs), finisherChan, dbUpdateChan, make(chan string)) + go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string)) f.handleFile(file, copyChan, finisherChan, dbUpdateChan) @@ -653,10 +654,11 @@ func TestIssue3164(t *testing.T) { matcher := ignore.New(ffs) must(t, matcher.Parse(bytes.NewBufferString("(?d)oktodelete"), "")) + f.ignores = matcher dbUpdateChan := make(chan dbUpdateJob, 1) - f.deleteDir(file, matcher, dbUpdateChan, make(chan string)) + f.deleteDir(file, dbUpdateChan, make(chan string)) if _, err := ffs.Stat("issue3164"); !fs.IsNotExist(err) { t.Fatal(err) @@ -798,7 +800,7 @@ func TestCopyOwner(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 1) defer close(dbUpdateChan) - f.handleDir(dir, ignore.New(f.fs), dbUpdateChan, nil) + f.handleDir(dir, dbUpdateChan, nil) <-dbUpdateChan // empty the channel for later info, err := f.fs.Lstat("foo/bar") @@ -829,7 +831,7 @@ func TestCopyOwner(t *testing.T) { copierChan := make(chan copyBlocksState) defer close(copierChan) go f.copierRoutine(copierChan, nil, finisherChan) - go f.finisherRoutine(nil, finisherChan, dbUpdateChan, nil) + go f.finisherRoutine(finisherChan, dbUpdateChan, nil) f.handleFile(file, copierChan, nil, nil) <-dbUpdateChan @@ -849,7 +851,7 @@ func TestCopyOwner(t *testing.T) { SymlinkTarget: "over the rainbow", } - f.handleSymlink(symlink, ignore.New(f.fs), dbUpdateChan, nil) + f.handleSymlink(symlink, dbUpdateChan, nil) <-dbUpdateChan info, err = f.fs.Lstat("foo/bar/sym") @@ -887,7 +889,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 1) scanChan := make(chan string, 1) - f.handleDir(file, ignore.New(f.fs), dbUpdateChan, scanChan) + f.handleDir(file, dbUpdateChan, scanChan) if confls := existingConflicts(name, ffs); len(confls) != 1 { t.Fatal("Expected one conflict, got", len(confls)) @@ -923,7 +925,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) { dbUpdateChan := make(chan dbUpdateJob, 1) scanChan := make(chan string, 1) - f.handleSymlink(file, ignore.New(f.fs), dbUpdateChan, scanChan) + f.handleSymlink(file, dbUpdateChan, scanChan) if confls := existingConflicts(name, ffs); len(confls) != 1 { t.Fatal("Expected one conflict, got", len(confls)) diff --git a/lib/model/model.go b/lib/model/model.go index 9f5df97e..ce6d0501 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -162,7 +162,7 @@ type model struct { foldersRunning int32 // for testing only } -type folderFactory func(*model, config.FolderConfiguration, versioner.Versioner, fs.Filesystem) service +type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, fs.Filesystem) service var ( folderFactories = make(map[config.FolderType]folderFactory) @@ -263,15 +263,15 @@ func (m *model) startFolderLocked(folder string) config.FolderType { panic(fmt.Sprintf("unknown folder type 0x%x", cfg.Type)) } - fs := m.folderFiles[folder] + fset := m.folderFiles[folder] // Find any devices for which we hold the index in the db, but the folder // is not shared, and drop it. expected := mapDevices(cfg.DeviceIDs()) - for _, available := range fs.ListDevices() { + for _, available := range fset.ListDevices() { if _, ok := expected[available]; !ok { l.Debugln("dropping", folder, "state for", available) - fs.Drop(available) + fset.Drop(available) } } @@ -280,7 +280,7 @@ func (m *model) startFolderLocked(folder string) config.FolderType { m.closeLocked(id, fmt.Errorf("started folder %v", cfg.Description())) } - v, ok := fs.Sequence(protocol.LocalDeviceID), true + v, ok := fset.Sequence(protocol.LocalDeviceID), true indexHasFiles := ok && v > 0 if !indexHasFiles { // It's a blank folder, so this may the first time we're looking at @@ -305,14 +305,14 @@ func (m *model) startFolderLocked(folder string) config.FolderType { m.folderRunnerTokens[folder] = append(m.folderRunnerTokens[folder], token) } - ffs := fs.MtimeFS() + ffs := fset.MtimeFS() // These are our metadata files, and they should always be hidden. ffs.Hide(config.DefaultMarkerName) ffs.Hide(".stversions") ffs.Hide(".stignore") - p := folderFactory(m, cfg, ver, ffs) + p := folderFactory(m, fset, m.folderIgnores[folder], cfg, ver, ffs) m.folderRunners[folder] = p