func newRWFolder(m *Model, shortID uint64, cfg config.FolderConfiguration) *rwFolder { return &rwFolder{ stateTracker: stateTracker{ folder: cfg.ID, mut: sync.NewMutex(), }, model: m, progressEmitter: m.progressEmitter, virtualMtimeRepo: db.NewVirtualMtimeRepo(m.db, cfg.ID), folder: cfg.ID, dir: cfg.Path(), scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second, ignorePerms: cfg.IgnorePerms, copiers: cfg.Copiers, pullers: cfg.Pullers, shortID: shortID, order: cfg.Order, encrypt: cfg.Encrypt, key: cfg.Passphrase, stop: make(chan struct{}), queue: newJobQueue(), pullTimer: time.NewTimer(shortPullIntv), scanTimer: time.NewTimer(time.Millisecond), // The first scan should be done immediately. delayScan: make(chan time.Duration), scanNow: make(chan rescanRequest), remoteIndex: make(chan struct{}, 1), // This needs to be 1-buffered so that we queue a notification if we're busy doing a pull when it comes. errorsMut: sync.NewMutex(), } }
func (m *Model) internalScanFolderSubs(folder string, subs []string) error { for i, sub := range subs { sub = osutil.NativeFilename(sub) if p := filepath.Clean(filepath.Join(folder, sub)); !strings.HasPrefix(p, folder) { return errors.New("invalid subpath") } subs[i] = sub } m.fmut.Lock() fs := m.folderFiles[folder] folderCfg := m.folderCfgs[folder] ignores := m.folderIgnores[folder] runner, ok := m.folderRunners[folder] m.fmut.Unlock() // Folders are added to folderRunners only when they are started. We can't // scan them before they have started, so that's what we need to check for // here. if !ok { return errors.New("no such folder") } _ = ignores.Load(filepath.Join(folderCfg.Path(), ".stignore")) // Ignore error, there might not be an .stignore // Required to make sure that we start indexing at a directory we're already // aware off. var unifySubs []string nextSub: for _, sub := range subs { for sub != "" { if _, ok = fs.Get(protocol.LocalDeviceID, sub); ok { break } sub = filepath.Dir(sub) if sub == "." || sub == string(filepath.Separator) { sub = "" } } for _, us := range unifySubs { if strings.HasPrefix(sub, us) { continue nextSub } } unifySubs = append(unifySubs, sub) } subs = unifySubs w := &scanner.Walker{ Dir: folderCfg.Path(), Subs: subs, Matcher: ignores, BlockSize: protocol.BlockSize, TempNamer: defTempNamer, TempLifetime: time.Duration(m.cfg.Options().KeepTemporariesH) * time.Hour, CurrentFiler: cFiler{m, folder}, MtimeRepo: db.NewVirtualMtimeRepo(m.db, folderCfg.ID), IgnorePerms: folderCfg.IgnorePerms, AutoNormalize: folderCfg.AutoNormalize, Hashers: m.numHashers(folder), ShortID: m.shortID, } runner.setState(FolderScanning) fchan, err := w.Walk() if err != nil { // The error we get here is likely an OS level error, which might not be // as readable as our health check errors. Check if we can get a health // check error first, and use that if it's available. if ferr := m.CheckFolderHealth(folder); ferr != nil { err = ferr } runner.setError(err) return err } batchSizeFiles := 100 batchSizeBlocks := 2048 // about 256 MB batch := make([]protocol.FileInfo, 0, batchSizeFiles) blocksHandled := 0 for f := range fchan { if len(batch) == batchSizeFiles || blocksHandled > batchSizeBlocks { if err := m.CheckFolderHealth(folder); err != nil { l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err) return err } m.updateLocals(folder, batch) batch = batch[:0] blocksHandled = 0 } batch = append(batch, f) blocksHandled += len(f.Blocks) } if err := m.CheckFolderHealth(folder); err != nil { l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err) return err } else if len(batch) > 0 { m.updateLocals(folder, batch) } batch = batch[:0] // TODO: We should limit the Have scanning to start at sub seenPrefix := false 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 if !f.IsDeleted() { 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) { // File has been ignored or an unsupported symlink. Set invalid bit. if debug { l.Debugln("setting invalid bit on ignored", f) } nf := protocol.FileInfo{ Name: f.Name, 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. // We don't specifically verify that the error is // os.IsNotExist because there is a corner case when a // directory is suddenly transformed into a file. When that // happens, files that were in the directory (that is now a // 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) } } 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 { l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err) return err } else if len(batch) > 0 { m.updateLocals(folder, batch) } runner.setState(FolderIdle) return nil }