|
@@ -8,7 +8,9 @@ package scanner
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
+ "errors"
|
|
|
"runtime"
|
|
|
+ "strings"
|
|
|
"sync/atomic"
|
|
|
"time"
|
|
|
"unicode/utf8"
|
|
@@ -48,8 +50,8 @@ type Config struct {
|
|
|
Matcher *ignore.Matcher
|
|
|
// Number of hours to keep temporary files for
|
|
|
TempLifetime time.Duration
|
|
|
- // If CurrentFiler is not nil, it is queried for the current file before rescanning.
|
|
|
- CurrentFiler CurrentFiler
|
|
|
+ // Walks over file infos as present in the db before the scan alphabetically.
|
|
|
+ Have haveWalker
|
|
|
// The Filesystem provides an abstraction on top of the actual filesystem.
|
|
|
Filesystem fs.Filesystem
|
|
|
// If IgnorePerms is true, changes to permission bits will not be
|
|
@@ -70,16 +72,28 @@ type Config struct {
|
|
|
UseWeakHashes bool
|
|
|
}
|
|
|
|
|
|
-type CurrentFiler interface {
|
|
|
- // CurrentFile returns the file as seen at last scan.
|
|
|
- CurrentFile(name string) (protocol.FileInfo, bool)
|
|
|
+type haveWalker interface {
|
|
|
+ // Walk passes all local file infos from the db which start with prefix
|
|
|
+ // to out and aborts early if ctx is cancelled.
|
|
|
+ Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo)
|
|
|
}
|
|
|
|
|
|
-func Walk(ctx context.Context, cfg Config) chan protocol.FileInfo {
|
|
|
+type fsWalkResult struct {
|
|
|
+ path string
|
|
|
+ info fs.FileInfo
|
|
|
+ err error
|
|
|
+}
|
|
|
+
|
|
|
+type ScanResult struct {
|
|
|
+ New protocol.FileInfo
|
|
|
+ Old protocol.FileInfo
|
|
|
+}
|
|
|
+
|
|
|
+func Walk(ctx context.Context, cfg Config) chan ScanResult {
|
|
|
w := walker{cfg}
|
|
|
|
|
|
- if w.CurrentFiler == nil {
|
|
|
- w.CurrentFiler = noCurrentFiler{}
|
|
|
+ if w.Have == nil {
|
|
|
+ w.Have = noHaveWalker{}
|
|
|
}
|
|
|
if w.Filesystem == nil {
|
|
|
panic("no filesystem specified")
|
|
@@ -97,25 +111,19 @@ type walker struct {
|
|
|
|
|
|
// Walk returns the list of files found in the local folder by scanning the
|
|
|
// file system. Files are blockwise hashed.
|
|
|
-func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
|
|
|
+func (w *walker) walk(ctx context.Context) chan ScanResult {
|
|
|
l.Debugln("Walk", w.Subs, w.BlockSize, w.Matcher)
|
|
|
|
|
|
- toHashChan := make(chan protocol.FileInfo)
|
|
|
- finishedChan := make(chan protocol.FileInfo)
|
|
|
+ haveChan := make(chan protocol.FileInfo)
|
|
|
+ haveCtx, haveCancel := context.WithCancel(ctx)
|
|
|
+ go w.dbWalkerRoutine(haveCtx, haveChan)
|
|
|
|
|
|
- // A routine which walks the filesystem tree, and sends files which have
|
|
|
- // been modified to the counter routine.
|
|
|
- go func() {
|
|
|
- hashFiles := w.walkAndHashFiles(ctx, toHashChan, finishedChan)
|
|
|
- if len(w.Subs) == 0 {
|
|
|
- w.Filesystem.Walk(".", hashFiles)
|
|
|
- } else {
|
|
|
- for _, sub := range w.Subs {
|
|
|
- w.Filesystem.Walk(sub, hashFiles)
|
|
|
- }
|
|
|
- }
|
|
|
- close(toHashChan)
|
|
|
- }()
|
|
|
+ fsChan := make(chan fsWalkResult)
|
|
|
+ go w.fsWalkerRoutine(ctx, fsChan, haveCancel)
|
|
|
+
|
|
|
+ toHashChan := make(chan ScanResult)
|
|
|
+ finishedChan := make(chan ScanResult)
|
|
|
+ go w.processWalkResults(ctx, fsChan, haveChan, toHashChan, finishedChan)
|
|
|
|
|
|
// We're not required to emit scan progress events, just kick off hashers,
|
|
|
// and feed inputs directly from the walker.
|
|
@@ -139,15 +147,15 @@ func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
|
|
|
// Parallel hasher is stopped by this routine when we close the channel over
|
|
|
// which it receives the files we ask it to hash.
|
|
|
go func() {
|
|
|
- var filesToHash []protocol.FileInfo
|
|
|
+ var filesToHash []ScanResult
|
|
|
var total int64 = 1
|
|
|
|
|
|
for file := range toHashChan {
|
|
|
filesToHash = append(filesToHash, file)
|
|
|
- total += file.Size
|
|
|
+ total += file.New.Size
|
|
|
}
|
|
|
|
|
|
- realToHashChan := make(chan protocol.FileInfo)
|
|
|
+ realToHashChan := make(chan ScanResult)
|
|
|
done := make(chan struct{})
|
|
|
progress := newByteCounter()
|
|
|
|
|
@@ -183,7 +191,7 @@ func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
|
|
|
|
|
|
loop:
|
|
|
for _, file := range filesToHash {
|
|
|
- l.Debugln("real to hash:", file.Name)
|
|
|
+ l.Debugln("real to hash:", file.New.Name)
|
|
|
select {
|
|
|
case realToHashChan <- file:
|
|
|
case <-ctx.Done():
|
|
@@ -196,15 +204,49 @@ func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
|
|
|
return finishedChan
|
|
|
}
|
|
|
|
|
|
-func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protocol.FileInfo) fs.WalkFunc {
|
|
|
- now := time.Now()
|
|
|
- return func(path string, info fs.FileInfo, err error) error {
|
|
|
+// dbWalkerRoutine walks the db and sends back file infos to be compared to scan results.
|
|
|
+func (w *walker) dbWalkerRoutine(ctx context.Context, haveChan chan<- protocol.FileInfo) {
|
|
|
+ defer close(haveChan)
|
|
|
+
|
|
|
+ if len(w.Subs) == 0 {
|
|
|
+ w.Have.Walk("", ctx, haveChan)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, sub := range w.Subs {
|
|
|
select {
|
|
|
case <-ctx.Done():
|
|
|
- return ctx.Err()
|
|
|
+ return
|
|
|
default:
|
|
|
}
|
|
|
+ w.Have.Walk(sub, ctx, haveChan)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// fsWalkerRoutine walks the filesystem tree and sends back file infos and potential
|
|
|
+// errors at paths that need to be processed.
|
|
|
+func (w *walker) fsWalkerRoutine(ctx context.Context, fsChan chan<- fsWalkResult, haveCancel context.CancelFunc) {
|
|
|
+ defer close(fsChan)
|
|
|
+
|
|
|
+ walkFn := w.createFSWalkFn(ctx, fsChan)
|
|
|
+ if len(w.Subs) == 0 {
|
|
|
+ if err := w.Filesystem.Walk(".", walkFn); err != nil {
|
|
|
+ haveCancel()
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
+ for _, sub := range w.Subs {
|
|
|
+ if err := w.Filesystem.Walk(sub, walkFn); err != nil {
|
|
|
+ haveCancel()
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (w *walker) createFSWalkFn(ctx context.Context, fsChan chan<- fsWalkResult) fs.WalkFunc {
|
|
|
+ now := time.Now()
|
|
|
+ return func(path string, info fs.FileInfo, err error) error {
|
|
|
// Return value used when we are returning early and don't want to
|
|
|
// process the item. For directories, this means do-not-descend.
|
|
|
var skip error // nil
|
|
@@ -213,21 +255,14 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
|
|
skip = fs.SkipDir
|
|
|
}
|
|
|
|
|
|
- if err != nil {
|
|
|
- l.Debugln("error:", path, info, err)
|
|
|
- return skip
|
|
|
- }
|
|
|
-
|
|
|
if path == "." {
|
|
|
+ if err != nil {
|
|
|
+ fsWalkError(ctx, fsChan, path, err)
|
|
|
+ return skip
|
|
|
+ }
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- info, err = w.Filesystem.Lstat(path)
|
|
|
- // An error here would be weird as we've already gotten to this point, but act on it nonetheless
|
|
|
- if err != nil {
|
|
|
- return skip
|
|
|
- }
|
|
|
-
|
|
|
if fs.IsTemporary(path) {
|
|
|
l.Debugln("temporary:", path)
|
|
|
if info.IsRegular() && info.ModTime().Add(w.TempLifetime).Before(now) {
|
|
@@ -238,48 +273,177 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
|
|
}
|
|
|
|
|
|
if fs.IsInternal(path) {
|
|
|
- l.Debugln("ignored (internal):", path)
|
|
|
+ l.Debugln("skip walking (internal):", path)
|
|
|
return skip
|
|
|
}
|
|
|
|
|
|
if w.Matcher.Match(path).IsIgnored() {
|
|
|
- l.Debugln("ignored (patterns):", path)
|
|
|
+ l.Debugln("skip walking (patterns):", path)
|
|
|
+ return skip
|
|
|
+ }
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ if sendErr := fsWalkError(ctx, fsChan, path, err); sendErr != nil {
|
|
|
+ return sendErr
|
|
|
+ }
|
|
|
return skip
|
|
|
}
|
|
|
|
|
|
if !utf8.ValidString(path) {
|
|
|
+ if err := fsWalkError(ctx, fsChan, path, errors.New("path isn't a valid utf8 string")); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
l.Warnf("File name %q is not in UTF8 encoding; skipping.", path)
|
|
|
return skip
|
|
|
}
|
|
|
|
|
|
path, shouldSkip := w.normalizePath(path, info)
|
|
|
if shouldSkip {
|
|
|
+ if err := fsWalkError(ctx, fsChan, path, errors.New("failed to normalize path")); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
return skip
|
|
|
}
|
|
|
|
|
|
- switch {
|
|
|
- case info.IsSymlink():
|
|
|
- if err := w.walkSymlink(ctx, path, dchan); err != nil {
|
|
|
- return err
|
|
|
+ select {
|
|
|
+ case fsChan <- fsWalkResult{
|
|
|
+ path: path,
|
|
|
+ info: info,
|
|
|
+ err: nil,
|
|
|
+ }:
|
|
|
+ case <-ctx.Done():
|
|
|
+ return ctx.Err()
|
|
|
+ }
|
|
|
+
|
|
|
+ // under no circumstances shall we descend into a symlink
|
|
|
+ if info.IsSymlink() && info.IsDir() {
|
|
|
+ l.Debugln("skip walking (symlinked directory):", path)
|
|
|
+ return skip
|
|
|
+ }
|
|
|
+
|
|
|
+ return err
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func fsWalkError(ctx context.Context, dst chan<- fsWalkResult, path string, err error) error {
|
|
|
+ select {
|
|
|
+ case dst <- fsWalkResult{
|
|
|
+ path: path,
|
|
|
+ info: nil,
|
|
|
+ err: err,
|
|
|
+ }:
|
|
|
+ case <-ctx.Done():
|
|
|
+ return ctx.Err()
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (w *walker) processWalkResults(ctx context.Context, fsChan <-chan fsWalkResult, haveChan <-chan protocol.FileInfo, toHashChan, finishedChan chan<- ScanResult) {
|
|
|
+ ctxChan := ctx.Done()
|
|
|
+ fsRes, fsChanOpen := <-fsChan
|
|
|
+ currDBFile, haveChanOpen := <-haveChan
|
|
|
+ for fsChanOpen {
|
|
|
+ if haveChanOpen {
|
|
|
+ // File infos below an error walking the filesystem tree
|
|
|
+ // may be marked as ignored but should not be deleted.
|
|
|
+ if fsRes.err != nil && (strings.HasPrefix(currDBFile.Name, fsRes.path+string(fs.PathSeparator)) || fsRes.path == ".") {
|
|
|
+ w.checkIgnoredAndInvalidate(currDBFile, finishedChan, ctxChan)
|
|
|
+ currDBFile, haveChanOpen = <-haveChan
|
|
|
+ continue
|
|
|
}
|
|
|
- if info.IsDir() {
|
|
|
- // under no circumstances shall we descend into a symlink
|
|
|
- return fs.SkipDir
|
|
|
+ // Delete file infos that were not encountered when
|
|
|
+ // walking the filesystem tree, except on error (see
|
|
|
+ // above) or if they are ignored.
|
|
|
+ if currDBFile.Name < fsRes.path {
|
|
|
+ w.checkIgnoredAndDelete(currDBFile, finishedChan, ctxChan)
|
|
|
+ currDBFile, haveChanOpen = <-haveChan
|
|
|
+ continue
|
|
|
}
|
|
|
- return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ var oldFile protocol.FileInfo
|
|
|
+ if haveChanOpen && currDBFile.Name == fsRes.path {
|
|
|
+ oldFile = currDBFile
|
|
|
+ currDBFile, haveChanOpen = <-haveChan
|
|
|
+ }
|
|
|
+
|
|
|
+ if fsRes.err != nil {
|
|
|
+ if fs.IsNotExist(fsRes.err) && !oldFile.IsEmpty() && !oldFile.Deleted {
|
|
|
+ select {
|
|
|
+ case finishedChan <- ScanResult{
|
|
|
+ New: oldFile.DeletedCopy(w.ShortID),
|
|
|
+ Old: oldFile,
|
|
|
+ }:
|
|
|
+ case <-ctx.Done():
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ fsRes, fsChanOpen = <-fsChan
|
|
|
+ continue
|
|
|
+ }
|
|
|
|
|
|
- case info.IsDir():
|
|
|
- err = w.walkDir(ctx, path, info, dchan)
|
|
|
+ switch {
|
|
|
+ case fsRes.info.IsDir():
|
|
|
+ w.walkDir(ctx, fsRes.path, fsRes.info, oldFile, finishedChan)
|
|
|
+
|
|
|
+ case fsRes.info.IsSymlink():
|
|
|
+ w.walkSymlink(ctx, fsRes.path, oldFile, finishedChan)
|
|
|
|
|
|
- case info.IsRegular():
|
|
|
- err = w.walkRegular(ctx, path, info, fchan)
|
|
|
+ case fsRes.info.IsRegular():
|
|
|
+ w.walkRegular(ctx, fsRes.path, fsRes.info, oldFile, toHashChan)
|
|
|
}
|
|
|
|
|
|
- return err
|
|
|
+ fsRes, fsChanOpen = <-fsChan
|
|
|
+ }
|
|
|
+
|
|
|
+ // Filesystem tree walking finished, if there is anything left in the
|
|
|
+ // db, mark it as deleted, except when it's ignored.
|
|
|
+ if haveChanOpen {
|
|
|
+ w.checkIgnoredAndDelete(currDBFile, finishedChan, ctxChan)
|
|
|
+ for currDBFile = range haveChan {
|
|
|
+ w.checkIgnoredAndDelete(currDBFile, finishedChan, ctxChan)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ close(toHashChan)
|
|
|
+}
|
|
|
+
|
|
|
+func (w *walker) checkIgnoredAndDelete(f protocol.FileInfo, finishedChan chan<- ScanResult, done <-chan struct{}) {
|
|
|
+ if w.checkIgnoredAndInvalidate(f, finishedChan, done) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if !f.Invalid && !f.Deleted {
|
|
|
+ select {
|
|
|
+ case finishedChan <- ScanResult{
|
|
|
+ New: f.DeletedCopy(w.ShortID),
|
|
|
+ Old: f,
|
|
|
+ }:
|
|
|
+ case <-done:
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileInfo, fchan chan protocol.FileInfo) error {
|
|
|
+func (w *walker) checkIgnoredAndInvalidate(f protocol.FileInfo, finishedChan chan<- ScanResult, done <-chan struct{}) bool {
|
|
|
+ if !w.Matcher.Match(f.Name).IsIgnored() {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if !f.Invalid {
|
|
|
+ select {
|
|
|
+ case finishedChan <- ScanResult{
|
|
|
+ New: f.InvalidatedCopy(w.ShortID),
|
|
|
+ Old: f,
|
|
|
+ }:
|
|
|
+ case <-done:
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileInfo, cf protocol.FileInfo, toHashChan chan<- ScanResult) {
|
|
|
curMode := uint32(info.Mode())
|
|
|
if runtime.GOOS == "windows" && osutil.IsWindowsExecutable(relPath) {
|
|
|
curMode |= 0111
|
|
@@ -294,40 +458,38 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
|
|
|
// - was not a symlink (since it's a file now)
|
|
|
// - was not invalid (since it looks valid now)
|
|
|
// - has the same size as previously
|
|
|
- cf, ok := w.CurrentFiler.CurrentFile(relPath)
|
|
|
- permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, curMode)
|
|
|
- if ok && permUnchanged && !cf.IsDeleted() && cf.ModTime().Equal(info.ModTime()) && !cf.IsDirectory() &&
|
|
|
- !cf.IsSymlink() && !cf.IsInvalid() && cf.Size == info.Size() {
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- if ok {
|
|
|
+ if !cf.IsEmpty() {
|
|
|
+ permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, curMode)
|
|
|
+ if permUnchanged && !cf.IsDeleted() && cf.ModTime().Equal(info.ModTime()) && !cf.IsDirectory() &&
|
|
|
+ !cf.IsSymlink() && !cf.IsInvalid() && cf.Size == info.Size() {
|
|
|
+ return
|
|
|
+ }
|
|
|
l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&fs.ModePerm)
|
|
|
}
|
|
|
|
|
|
- f := protocol.FileInfo{
|
|
|
- Name: relPath,
|
|
|
- Type: protocol.FileInfoTypeFile,
|
|
|
- Version: cf.Version.Update(w.ShortID),
|
|
|
- Permissions: curMode & uint32(maskModePerm),
|
|
|
- NoPermissions: w.IgnorePerms,
|
|
|
- ModifiedS: info.ModTime().Unix(),
|
|
|
- ModifiedNs: int32(info.ModTime().Nanosecond()),
|
|
|
- ModifiedBy: w.ShortID,
|
|
|
- Size: info.Size(),
|
|
|
+ f := ScanResult{
|
|
|
+ New: protocol.FileInfo{
|
|
|
+ Name: relPath,
|
|
|
+ Type: protocol.FileInfoTypeFile,
|
|
|
+ Version: cf.Version.Update(w.ShortID),
|
|
|
+ Permissions: curMode & uint32(maskModePerm),
|
|
|
+ NoPermissions: w.IgnorePerms,
|
|
|
+ ModifiedS: info.ModTime().Unix(),
|
|
|
+ ModifiedNs: int32(info.ModTime().Nanosecond()),
|
|
|
+ ModifiedBy: w.ShortID,
|
|
|
+ Size: info.Size(),
|
|
|
+ },
|
|
|
+ Old: cf,
|
|
|
}
|
|
|
l.Debugln("to hash:", relPath, f)
|
|
|
|
|
|
select {
|
|
|
- case fchan <- f:
|
|
|
+ case toHashChan <- f:
|
|
|
case <-ctx.Done():
|
|
|
- return ctx.Err()
|
|
|
}
|
|
|
-
|
|
|
- return nil
|
|
|
}
|
|
|
|
|
|
-func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, dchan chan protocol.FileInfo) error {
|
|
|
+func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, cf protocol.FileInfo, finishedChan chan<- ScanResult) {
|
|
|
// A directory is "unchanged", if it
|
|
|
// - exists
|
|
|
// - has the same permissions as previously, unless we are ignoring permissions
|
|
@@ -335,40 +497,41 @@ func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo,
|
|
|
// - was a directory previously (not a file or something else)
|
|
|
// - was not a symlink (since it's a directory now)
|
|
|
// - was not invalid (since it looks valid now)
|
|
|
- cf, ok := w.CurrentFiler.CurrentFile(relPath)
|
|
|
- permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, uint32(info.Mode()))
|
|
|
- if ok && permUnchanged && !cf.IsDeleted() && cf.IsDirectory() && !cf.IsSymlink() && !cf.IsInvalid() {
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- f := protocol.FileInfo{
|
|
|
- Name: relPath,
|
|
|
- Type: protocol.FileInfoTypeDirectory,
|
|
|
- Version: cf.Version.Update(w.ShortID),
|
|
|
- Permissions: uint32(info.Mode() & maskModePerm),
|
|
|
- NoPermissions: w.IgnorePerms,
|
|
|
- ModifiedS: info.ModTime().Unix(),
|
|
|
- ModifiedNs: int32(info.ModTime().Nanosecond()),
|
|
|
- ModifiedBy: w.ShortID,
|
|
|
+ if !cf.IsEmpty() {
|
|
|
+ permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, uint32(info.Mode()))
|
|
|
+ if permUnchanged && !cf.IsDeleted() && cf.IsDirectory() && !cf.IsSymlink() && !cf.IsInvalid() {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ f := ScanResult{
|
|
|
+ New: protocol.FileInfo{
|
|
|
+ Name: relPath,
|
|
|
+ Type: protocol.FileInfoTypeDirectory,
|
|
|
+ Version: cf.Version.Update(w.ShortID),
|
|
|
+ Permissions: uint32(info.Mode() & maskModePerm),
|
|
|
+ NoPermissions: w.IgnorePerms,
|
|
|
+ ModifiedS: info.ModTime().Unix(),
|
|
|
+ ModifiedNs: int32(info.ModTime().Nanosecond()),
|
|
|
+ ModifiedBy: w.ShortID,
|
|
|
+ },
|
|
|
+ Old: cf,
|
|
|
}
|
|
|
l.Debugln("dir:", relPath, f)
|
|
|
|
|
|
select {
|
|
|
- case dchan <- f:
|
|
|
+ case finishedChan <- f:
|
|
|
case <-ctx.Done():
|
|
|
- return ctx.Err()
|
|
|
}
|
|
|
-
|
|
|
- return nil
|
|
|
}
|
|
|
|
|
|
// walkSymlink returns nil or an error, if the error is of the nature that
|
|
|
// it should stop the entire walk.
|
|
|
-func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan protocol.FileInfo) error {
|
|
|
+func (w *walker) walkSymlink(ctx context.Context, relPath string, cf protocol.FileInfo, finishedChan chan<- ScanResult) {
|
|
|
// Symlinks are not supported on Windows. We ignore instead of returning
|
|
|
// an error.
|
|
|
if runtime.GOOS == "windows" {
|
|
|
- return nil
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
// We always rehash symlinks as they have no modtime or
|
|
@@ -379,7 +542,7 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan pro
|
|
|
target, err := w.Filesystem.ReadSymlink(relPath)
|
|
|
if err != nil {
|
|
|
l.Debugln("readlink error:", relPath, err)
|
|
|
- return nil
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
// A symlink is "unchanged", if
|
|
@@ -388,28 +551,27 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan pro
|
|
|
// - it was a symlink
|
|
|
// - it wasn't invalid
|
|
|
// - the target was the same
|
|
|
- cf, ok := w.CurrentFiler.CurrentFile(relPath)
|
|
|
- if ok && !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && cf.SymlinkTarget == target {
|
|
|
- return nil
|
|
|
+ if !cf.IsEmpty() && !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && cf.SymlinkTarget == target {
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- f := protocol.FileInfo{
|
|
|
- Name: relPath,
|
|
|
- Type: protocol.FileInfoTypeSymlink,
|
|
|
- Version: cf.Version.Update(w.ShortID),
|
|
|
- NoPermissions: true, // Symlinks don't have permissions of their own
|
|
|
- SymlinkTarget: target,
|
|
|
+ f := ScanResult{
|
|
|
+ New: protocol.FileInfo{
|
|
|
+ Name: relPath,
|
|
|
+ Type: protocol.FileInfoTypeSymlink,
|
|
|
+ Version: cf.Version.Update(w.ShortID),
|
|
|
+ NoPermissions: true, // Symlinks don't have permissions of their own
|
|
|
+ SymlinkTarget: target,
|
|
|
+ },
|
|
|
+ Old: cf,
|
|
|
}
|
|
|
|
|
|
l.Debugln("symlink changedb:", relPath, f)
|
|
|
|
|
|
select {
|
|
|
- case dchan <- f:
|
|
|
+ case finishedChan <- f:
|
|
|
case <-ctx.Done():
|
|
|
- return ctx.Err()
|
|
|
}
|
|
|
-
|
|
|
- return nil
|
|
|
}
|
|
|
|
|
|
// normalizePath returns the normalized relative path (possibly after fixing
|
|
@@ -532,10 +694,6 @@ func (c *byteCounter) Close() {
|
|
|
close(c.stop)
|
|
|
}
|
|
|
|
|
|
-// A no-op CurrentFiler
|
|
|
-
|
|
|
-type noCurrentFiler struct{}
|
|
|
+type noHaveWalker struct{}
|
|
|
|
|
|
-func (noCurrentFiler) CurrentFile(name string) (protocol.FileInfo, bool) {
|
|
|
- return protocol.FileInfo{}, false
|
|
|
-}
|
|
|
+func (noHaveWalker) Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo) {}
|