mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-22 22:58:25 +00:00
This reverts commit 6d3f9d5154b4c6b39f8083255cc9ed36b5c51146.
This commit is contained in:
parent
b57c9b6af5
commit
68c1b2dd47
@ -194,9 +194,9 @@ func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
|
|||||||
s.db.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn))
|
s.db.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileSet) WithPrefixedHave(device protocol.DeviceID, prefix string, fn Iterator) {
|
func (s *FileSet) WithPrefixedHaveTruncated(device protocol.DeviceID, prefix string, fn Iterator) {
|
||||||
l.Debugf("%s WithPrefixedHave(%v)", s.folder, device)
|
l.Debugf("%s WithPrefixedHaveTruncated(%v)", s.folder, device)
|
||||||
s.db.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), false, nativeFileIterator(fn))
|
s.db.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(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)
|
||||||
|
@ -10,10 +10,7 @@
|
|||||||
|
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import "path/filepath"
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WalkFunc is the type of the function called for each file or directory
|
// WalkFunc is the type of the function called for each file or directory
|
||||||
// visited by Walk. The path argument contains the argument to Walk as a
|
// visited by Walk. The path argument contains the argument to Walk as a
|
||||||
@ -57,7 +54,6 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return walkFn(path, info, err)
|
return walkFn(path, info, err)
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
filename := filepath.Join(path, name)
|
filename := filepath.Join(path, name)
|
||||||
|
@ -1395,21 +1395,14 @@ func (m *Model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo
|
|||||||
return fs.GetGlobal(file)
|
return fs.GetGlobal(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
type haveWalker struct {
|
type cFiler struct {
|
||||||
fset *db.FileSet
|
m *Model
|
||||||
|
r string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h haveWalker) Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo) {
|
// Implements scanner.CurrentFiler
|
||||||
ctxChan := ctx.Done()
|
func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) {
|
||||||
h.fset.WithPrefixedHave(protocol.LocalDeviceID, prefix, func(fi db.FileIntf) bool {
|
return cf.m.CurrentFolderFile(cf.r, file)
|
||||||
f := fi.(protocol.FileInfo)
|
|
||||||
select {
|
|
||||||
case out <- f:
|
|
||||||
case <-ctxChan:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection returns the current connection for device, and a boolean wether a connection was found.
|
// Connection returns the current connection for device, and a boolean wether a connection was found.
|
||||||
@ -1962,14 +1955,13 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
|
|
||||||
runner.setState(FolderScanning)
|
runner.setState(FolderScanning)
|
||||||
|
|
||||||
haveWalker := haveWalker{fset}
|
fchan := scanner.Walk(ctx, scanner.Config{
|
||||||
rchan := scanner.Walk(ctx, scanner.Config{
|
|
||||||
Folder: folderCfg.ID,
|
Folder: folderCfg.ID,
|
||||||
Subs: subDirs,
|
Subs: subDirs,
|
||||||
Matcher: ignores,
|
Matcher: ignores,
|
||||||
BlockSize: protocol.BlockSize,
|
BlockSize: protocol.BlockSize,
|
||||||
TempLifetime: time.Duration(m.cfg.Options().KeepTemporariesH) * time.Hour,
|
TempLifetime: time.Duration(m.cfg.Options().KeepTemporariesH) * time.Hour,
|
||||||
Have: haveWalker,
|
CurrentFiler: cFiler{m, folder},
|
||||||
Filesystem: mtimefs,
|
Filesystem: mtimefs,
|
||||||
IgnorePerms: folderCfg.IgnorePerms,
|
IgnorePerms: folderCfg.IgnorePerms,
|
||||||
AutoNormalize: folderCfg.AutoNormalize,
|
AutoNormalize: folderCfg.AutoNormalize,
|
||||||
@ -1986,17 +1978,6 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
|
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
|
||||||
batchSizeBytes := 0
|
batchSizeBytes := 0
|
||||||
changes := 0
|
changes := 0
|
||||||
checkBatch := func() error {
|
|
||||||
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
|
||||||
if err := runner.CheckHealth(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.updateLocalsFromScanning(folder, batch)
|
|
||||||
batch = batch[:0]
|
|
||||||
batchSizeBytes = 0
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule a pull after scanning, but only if we actually detected any
|
// Schedule a pull after scanning, but only if we actually detected any
|
||||||
// changes.
|
// changes.
|
||||||
@ -2006,49 +1987,98 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var delDirStack []protocol.FileInfo
|
for f := range fchan {
|
||||||
for r := range rchan {
|
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
||||||
if err := checkBatch(); err != nil {
|
if err := runner.CheckHealth(); err != nil {
|
||||||
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append deleted dirs from stack if the current file isn't a child,
|
|
||||||
// which means all children were already processed.
|
|
||||||
for len(delDirStack) != 0 && !strings.HasPrefix(r.New.Name, delDirStack[len(delDirStack)-1].Name+string(fs.PathSeparator)) {
|
|
||||||
lastDelDir := delDirStack[len(delDirStack)-1]
|
|
||||||
batch = append(batch, lastDelDir)
|
|
||||||
batchSizeBytes += lastDelDir.ProtoSize()
|
|
||||||
changes++
|
|
||||||
if err := checkBatch(); err != nil {
|
|
||||||
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
delDirStack = delDirStack[:len(delDirStack)-1]
|
m.updateLocalsFromScanning(folder, batch)
|
||||||
|
batch = batch[:0]
|
||||||
|
batchSizeBytes = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay appending deleted dirs until all its children are processed
|
batch = append(batch, f)
|
||||||
if r.Old.IsDirectory() && (r.New.Deleted || !r.New.IsDirectory()) {
|
batchSizeBytes += f.ProtoSize()
|
||||||
delDirStack = append(delDirStack, r.New)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debugln("Appending", r)
|
|
||||||
batch = append(batch, r.New)
|
|
||||||
batchSizeBytes += r.New.ProtoSize()
|
|
||||||
changes++
|
changes++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append remaining deleted dirs.
|
if err := runner.CheckHealth(); err != nil {
|
||||||
for i := len(delDirStack) - 1; i >= 0; i-- {
|
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||||
if err := checkBatch(); err != nil {
|
return err
|
||||||
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
} else if len(batch) > 0 {
|
||||||
return err
|
m.updateLocalsFromScanning(folder, batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
batch = append(batch, delDirStack[i])
|
if len(subDirs) == 0 {
|
||||||
batchSizeBytes += delDirStack[i].ProtoSize()
|
// If we have no specific subdirectories to traverse, set it to one
|
||||||
changes++
|
// empty prefix so we traverse the entire folder contents once.
|
||||||
|
subDirs = []string{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a scan of the database for each prefix, to check for deleted and
|
||||||
|
// ignored files.
|
||||||
|
batch = batch[:0]
|
||||||
|
batchSizeBytes = 0
|
||||||
|
for _, sub := range subDirs {
|
||||||
|
var iterError error
|
||||||
|
|
||||||
|
fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
|
||||||
|
f := fi.(db.FileInfoTruncated)
|
||||||
|
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
||||||
|
if err := runner.CheckHealth(); err != nil {
|
||||||
|
iterError = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.updateLocalsFromScanning(folder, batch)
|
||||||
|
batch = batch[:0]
|
||||||
|
batchSizeBytes = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !f.IsInvalid() && ignores.Match(f.Name).IsIgnored():
|
||||||
|
// File was valid at last pass but has been ignored. Set invalid bit.
|
||||||
|
l.Debugln("setting invalid bit on ignored", f)
|
||||||
|
nf := f.ConvertToInvalidFileInfo(m.id.Short())
|
||||||
|
batch = append(batch, nf)
|
||||||
|
batchSizeBytes += nf.ProtoSize()
|
||||||
|
changes++
|
||||||
|
|
||||||
|
case !f.IsInvalid() && !f.IsDeleted():
|
||||||
|
// The file is valid and not deleted. Lets check if it's
|
||||||
|
// still here.
|
||||||
|
|
||||||
|
if _, err := mtimefs.Lstat(f.Name); err != nil {
|
||||||
|
// We don't specifically verify that the error is
|
||||||
|
// fs.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,
|
||||||
|
Type: f.Type,
|
||||||
|
Size: 0,
|
||||||
|
ModifiedS: f.ModifiedS,
|
||||||
|
ModifiedNs: f.ModifiedNs,
|
||||||
|
ModifiedBy: m.id.Short(),
|
||||||
|
Deleted: true,
|
||||||
|
Version: f.Version.Update(m.shortID),
|
||||||
|
}
|
||||||
|
|
||||||
|
batch = append(batch, nf)
|
||||||
|
batchSizeBytes += nf.ProtoSize()
|
||||||
|
changes++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if iterError != nil {
|
||||||
|
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), iterError)
|
||||||
|
return iterError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runner.CheckHealth(); err != nil {
|
if err := runner.CheckHealth(); err != nil {
|
||||||
|
@ -2808,185 +2808,6 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestIssue2571 tests replacing a directory with content with a symlink
|
|
||||||
func TestIssue2571(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skip("Symlinks aren't supported by fs and scanner on windows")
|
|
||||||
}
|
|
||||||
err := defaultFs.MkdirAll("replaceDir", 0755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
defaultFs.RemoveAll("replaceDir")
|
|
||||||
}()
|
|
||||||
|
|
||||||
testFs := fs.NewFilesystem(fs.FilesystemTypeBasic, filepath.Join(defaultFs.URI(), "replaceDir"))
|
|
||||||
|
|
||||||
for _, dir := range []string{"toLink", "linkTarget"} {
|
|
||||||
err := testFs.MkdirAll(dir, 0775)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fd, err := testFs.Create(filepath.Join(dir, "a"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
dbi := db.OpenMemory()
|
|
||||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
|
||||||
m.AddFolder(defaultFolderConfig)
|
|
||||||
m.StartFolder("default")
|
|
||||||
m.ServeBackground()
|
|
||||||
defer m.Stop()
|
|
||||||
m.ScanFolder("default")
|
|
||||||
|
|
||||||
if err = testFs.RemoveAll("toLink"); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := osutil.DebugSymlinkForTestsOnly(filepath.Join(testFs.URI(), "linkTarget"), filepath.Join(testFs.URI(), "toLink")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.ScanFolder("default")
|
|
||||||
|
|
||||||
if dir, ok := m.CurrentFolderFile("default", filepath.Join("replaceDir", "toLink")); !ok {
|
|
||||||
t.Fatalf("Dir missing in db")
|
|
||||||
} else if !dir.IsSymlink() {
|
|
||||||
t.Errorf("Dir wasn't changed to symlink")
|
|
||||||
}
|
|
||||||
if file, ok := m.CurrentFolderFile("default", filepath.Join("replaceDir", "toLink", "a")); !ok {
|
|
||||||
t.Fatalf("File missing in db")
|
|
||||||
} else if !file.Deleted {
|
|
||||||
t.Errorf("File below symlink has not been marked as deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestIssue4573 tests that contents of an unavailable dir aren't marked deleted
|
|
||||||
func TestIssue4573(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
t.Skip("Can't make the dir inaccessible on windows")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := defaultFs.MkdirAll("inaccessible", 0755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
defaultFs.Chmod("inaccessible", 0777)
|
|
||||||
defaultFs.RemoveAll("inaccessible")
|
|
||||||
}()
|
|
||||||
|
|
||||||
file := filepath.Join("inaccessible", "a")
|
|
||||||
fd, err := defaultFs.Create(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
|
|
||||||
dbi := db.OpenMemory()
|
|
||||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
|
||||||
m.AddFolder(defaultFolderConfig)
|
|
||||||
m.StartFolder("default")
|
|
||||||
m.ServeBackground()
|
|
||||||
defer m.Stop()
|
|
||||||
m.ScanFolder("default")
|
|
||||||
|
|
||||||
err = defaultFs.Chmod("inaccessible", 0000)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.ScanFolder("default")
|
|
||||||
|
|
||||||
if file, ok := m.CurrentFolderFile("default", file); !ok {
|
|
||||||
t.Fatalf("File missing in db")
|
|
||||||
} else if file.Deleted {
|
|
||||||
t.Errorf("Inaccessible file has been marked as deleted.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestInternalScan checks whether various fs operations are correctly represented
|
|
||||||
// in the db after scanning.
|
|
||||||
func TestInternalScan(t *testing.T) {
|
|
||||||
err := defaultFs.MkdirAll("internalScan", 0755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
defaultFs.RemoveAll("internalScan")
|
|
||||||
}()
|
|
||||||
|
|
||||||
testFs := fs.NewFilesystem(fs.FilesystemTypeBasic, filepath.Join(defaultFs.URI(), "internalScan"))
|
|
||||||
|
|
||||||
testCases := map[string]func(protocol.FileInfo) bool{
|
|
||||||
"removeDir": func(f protocol.FileInfo) bool {
|
|
||||||
return !f.Deleted
|
|
||||||
},
|
|
||||||
"dirToFile": func(f protocol.FileInfo) bool {
|
|
||||||
return f.Deleted || f.IsDirectory()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
baseDirs := []string{"dirToFile", "removeDir"}
|
|
||||||
for _, dir := range baseDirs {
|
|
||||||
sub := filepath.Join(dir, "subDir")
|
|
||||||
for _, dir := range []string{dir, sub} {
|
|
||||||
err := testFs.MkdirAll(dir, 0775)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v: %v", dir, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
testCases[sub] = func(f protocol.FileInfo) bool {
|
|
||||||
return !f.Deleted
|
|
||||||
}
|
|
||||||
for _, dir := range []string{dir, sub} {
|
|
||||||
file := filepath.Join(dir, "a")
|
|
||||||
fd, err := testFs.Create(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
testCases[file] = func(f protocol.FileInfo) bool {
|
|
||||||
return !f.Deleted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dbi := db.OpenMemory()
|
|
||||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", dbi, nil)
|
|
||||||
m.AddFolder(defaultFolderConfig)
|
|
||||||
m.StartFolder("default")
|
|
||||||
m.ServeBackground()
|
|
||||||
defer m.Stop()
|
|
||||||
m.ScanFolder("default")
|
|
||||||
|
|
||||||
for _, dir := range baseDirs {
|
|
||||||
if err = testFs.RemoveAll(dir); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := testFs.Create("dirToFile")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
|
|
||||||
m.ScanFolder("default")
|
|
||||||
|
|
||||||
for path, cond := range testCases {
|
|
||||||
if f, ok := m.CurrentFolderFile("default", filepath.Join("internalScan", path)); !ok {
|
|
||||||
t.Fatalf("%v missing in db", path)
|
|
||||||
} else if cond(f) {
|
|
||||||
t.Errorf("Incorrect db entry for %v", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCustomMarkerName(t *testing.T) {
|
func TestCustomMarkerName(t *testing.T) {
|
||||||
ldb := db.OpenMemory()
|
ldb := db.OpenMemory()
|
||||||
set := db.NewFileSet("default", defaultFs, ldb)
|
set := db.NewFileSet("default", defaultFs, ldb)
|
||||||
|
@ -122,10 +122,6 @@ func (f FileInfo) WinsConflict(other FileInfo) bool {
|
|||||||
return f.Version.Compare(other.Version) == ConcurrentGreater
|
return f.Version.Compare(other.Version) == ConcurrentGreater
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FileInfo) IsEmpty() bool {
|
|
||||||
return f.Version.Counters == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileInfo) Invalidate(invalidatedBy ShortID) {
|
func (f *FileInfo) Invalidate(invalidatedBy ShortID) {
|
||||||
f.Invalid = true
|
f.Invalid = true
|
||||||
f.ModifiedBy = invalidatedBy
|
f.ModifiedBy = invalidatedBy
|
||||||
@ -133,23 +129,6 @@ func (f *FileInfo) Invalidate(invalidatedBy ShortID) {
|
|||||||
f.Sequence = 0
|
f.Sequence = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileInfo) InvalidatedCopy(invalidatedBy ShortID) FileInfo {
|
|
||||||
copy := *f
|
|
||||||
copy.Invalidate(invalidatedBy)
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileInfo) DeletedCopy(deletedBy ShortID) FileInfo {
|
|
||||||
copy := *f
|
|
||||||
copy.Size = 0
|
|
||||||
copy.ModifiedBy = deletedBy
|
|
||||||
copy.Deleted = true
|
|
||||||
copy.Version = f.Version.Update(deletedBy)
|
|
||||||
copy.Sequence = 0
|
|
||||||
copy.Blocks = nil
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BlockInfo) String() string {
|
func (b BlockInfo) String() string {
|
||||||
return fmt.Sprintf("Block{%d/%d/%d/%x}", b.Offset, b.Size, b.WeakHash, b.Hash)
|
return fmt.Sprintf("Block{%d/%d/%d/%x}", b.Offset, b.Size, b.WeakHash, b.Hash)
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,6 @@ var (
|
|||||||
quickCfg = &quick.Config{}
|
quickCfg = &quick.Config{}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
fileSize = 1 << 40
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPing(t *testing.T) {
|
func TestPing(t *testing.T) {
|
||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
@ -247,6 +243,12 @@ func TestMarshalledIndexMessageSize(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxMessageSize = MaxMessageLen
|
||||||
|
fileSize = 1 << 40
|
||||||
|
blockSize = BlockSize
|
||||||
|
)
|
||||||
|
|
||||||
f := FileInfo{
|
f := FileInfo{
|
||||||
Name: "a normal length file name withoout any weird stuff.txt",
|
Name: "a normal length file name withoout any weird stuff.txt",
|
||||||
Type: FileInfoTypeFile,
|
Type: FileInfoTypeFile,
|
||||||
@ -254,12 +256,12 @@ func TestMarshalledIndexMessageSize(t *testing.T) {
|
|||||||
Permissions: 0666,
|
Permissions: 0666,
|
||||||
ModifiedS: time.Now().Unix(),
|
ModifiedS: time.Now().Unix(),
|
||||||
Version: Vector{Counters: []Counter{{ID: 1 << 60, Value: 1}, {ID: 2 << 60, Value: 1}}},
|
Version: Vector{Counters: []Counter{{ID: 1 << 60, Value: 1}, {ID: 2 << 60, Value: 1}}},
|
||||||
Blocks: make([]BlockInfo, fileSize/BlockSize),
|
Blocks: make([]BlockInfo, fileSize/blockSize),
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < fileSize/BlockSize; i++ {
|
for i := 0; i < fileSize/blockSize; i++ {
|
||||||
f.Blocks[i].Offset = int64(i) * BlockSize
|
f.Blocks[i].Offset = int64(i) * blockSize
|
||||||
f.Blocks[i].Size = BlockSize
|
f.Blocks[i].Size = blockSize
|
||||||
f.Blocks[i].Hash = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 1, 2}
|
f.Blocks[i].Hash = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 1, 2}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,8 +271,8 @@ func TestMarshalledIndexMessageSize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
msgSize := idx.ProtoSize()
|
msgSize := idx.ProtoSize()
|
||||||
if msgSize > MaxMessageLen {
|
if msgSize > maxMessageSize {
|
||||||
t.Errorf("Message size %d bytes is larger than max %d", msgSize, MaxMessageLen)
|
t.Errorf("Message size %d bytes is larger than max %d", msgSize, maxMessageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,31 +400,3 @@ func TestCheckConsistency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCopyFileInfo(t *testing.T) {
|
|
||||||
f := FileInfo{
|
|
||||||
Name: "a normal length file name withoout any weird stuff.txt",
|
|
||||||
Type: FileInfoTypeFile,
|
|
||||||
Size: fileSize,
|
|
||||||
Permissions: 0666,
|
|
||||||
ModifiedS: time.Now().Unix(),
|
|
||||||
Version: Vector{Counters: []Counter{{ID: 1 << 60, Value: 1}, {ID: 2 << 60, Value: 1}}},
|
|
||||||
Blocks: make([]BlockInfo, fileSize/BlockSize),
|
|
||||||
}
|
|
||||||
|
|
||||||
del := f.DeletedCopy(LocalDeviceID.Short())
|
|
||||||
if f.Deleted {
|
|
||||||
t.Errorf("Source file info was deleted on copy")
|
|
||||||
}
|
|
||||||
if !del.Deleted {
|
|
||||||
t.Errorf("Returned file info was not deleted on copy")
|
|
||||||
}
|
|
||||||
|
|
||||||
inv := f.InvalidatedCopy(LocalDeviceID.Short())
|
|
||||||
if f.Invalid {
|
|
||||||
t.Errorf("Source file info was invalid on copy")
|
|
||||||
}
|
|
||||||
if !inv.Invalid {
|
|
||||||
t.Errorf("Returned file info was not invalid on copy")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -65,15 +65,15 @@ type parallelHasher struct {
|
|||||||
fs fs.Filesystem
|
fs fs.Filesystem
|
||||||
blockSize int
|
blockSize int
|
||||||
workers int
|
workers int
|
||||||
outbox chan<- ScanResult
|
outbox chan<- protocol.FileInfo
|
||||||
inbox <-chan ScanResult
|
inbox <-chan protocol.FileInfo
|
||||||
counter Counter
|
counter Counter
|
||||||
done chan<- struct{}
|
done chan<- struct{}
|
||||||
useWeakHashes bool
|
useWeakHashes bool
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func newParallelHasher(ctx context.Context, fs fs.Filesystem, blockSize, workers int, outbox chan<- ScanResult, inbox <-chan ScanResult, counter Counter, done chan<- struct{}, useWeakHashes bool) {
|
func newParallelHasher(ctx context.Context, fs fs.Filesystem, blockSize, workers int, outbox chan<- protocol.FileInfo, inbox <-chan protocol.FileInfo, counter Counter, done chan<- struct{}, useWeakHashes bool) {
|
||||||
ph := ¶llelHasher{
|
ph := ¶llelHasher{
|
||||||
fs: fs,
|
fs: fs,
|
||||||
blockSize: blockSize,
|
blockSize: blockSize,
|
||||||
@ -104,25 +104,25 @@ func (ph *parallelHasher) hashFiles(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.New.IsDirectory() || f.New.IsDeleted() {
|
if f.IsDirectory() || f.IsDeleted() {
|
||||||
panic("Bug. Asked to hash a directory or a deleted file.")
|
panic("Bug. Asked to hash a directory or a deleted file.")
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks, err := HashFile(ctx, ph.fs, f.New.Name, ph.blockSize, ph.counter, ph.useWeakHashes)
|
blocks, err := HashFile(ctx, ph.fs, f.Name, ph.blockSize, ph.counter, ph.useWeakHashes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugln("hash error:", f.New.Name, err)
|
l.Debugln("hash error:", f.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
f.New.Blocks = blocks
|
f.Blocks = blocks
|
||||||
|
|
||||||
// The size we saw when initially deciding to hash the file
|
// The size we saw when initially deciding to hash the file
|
||||||
// might not have been the size it actually had when we hashed
|
// might not have been the size it actually had when we hashed
|
||||||
// it. Update the size from the block list.
|
// it. Update the size from the block list.
|
||||||
|
|
||||||
f.New.Size = 0
|
f.Size = 0
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
f.New.Size += int64(b.Size)
|
f.Size += int64(b.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@ -8,9 +8,7 @@ package scanner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@ -50,8 +48,8 @@ type Config struct {
|
|||||||
Matcher *ignore.Matcher
|
Matcher *ignore.Matcher
|
||||||
// Number of hours to keep temporary files for
|
// Number of hours to keep temporary files for
|
||||||
TempLifetime time.Duration
|
TempLifetime time.Duration
|
||||||
// Walks over file infos as present in the db before the scan alphabetically.
|
// If CurrentFiler is not nil, it is queried for the current file before rescanning.
|
||||||
Have haveWalker
|
CurrentFiler CurrentFiler
|
||||||
// The Filesystem provides an abstraction on top of the actual filesystem.
|
// The Filesystem provides an abstraction on top of the actual filesystem.
|
||||||
Filesystem fs.Filesystem
|
Filesystem fs.Filesystem
|
||||||
// If IgnorePerms is true, changes to permission bits will not be
|
// If IgnorePerms is true, changes to permission bits will not be
|
||||||
@ -72,28 +70,16 @@ type Config struct {
|
|||||||
UseWeakHashes bool
|
UseWeakHashes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type haveWalker interface {
|
type CurrentFiler interface {
|
||||||
// Walk passes all local file infos from the db which start with prefix
|
// CurrentFile returns the file as seen at last scan.
|
||||||
// to out and aborts early if ctx is cancelled.
|
CurrentFile(name string) (protocol.FileInfo, bool)
|
||||||
Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fsWalkResult struct {
|
func Walk(ctx context.Context, cfg Config) chan protocol.FileInfo {
|
||||||
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}
|
w := walker{cfg}
|
||||||
|
|
||||||
if w.Have == nil {
|
if w.CurrentFiler == nil {
|
||||||
w.Have = noHaveWalker{}
|
w.CurrentFiler = noCurrentFiler{}
|
||||||
}
|
}
|
||||||
if w.Filesystem == nil {
|
if w.Filesystem == nil {
|
||||||
panic("no filesystem specified")
|
panic("no filesystem specified")
|
||||||
@ -111,19 +97,25 @@ type walker struct {
|
|||||||
|
|
||||||
// Walk returns the list of files found in the local folder by scanning the
|
// Walk returns the list of files found in the local folder by scanning the
|
||||||
// file system. Files are blockwise hashed.
|
// file system. Files are blockwise hashed.
|
||||||
func (w *walker) walk(ctx context.Context) chan ScanResult {
|
func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
|
||||||
l.Debugln("Walk", w.Subs, w.BlockSize, w.Matcher)
|
l.Debugln("Walk", w.Subs, w.BlockSize, w.Matcher)
|
||||||
|
|
||||||
haveChan := make(chan protocol.FileInfo)
|
toHashChan := make(chan protocol.FileInfo)
|
||||||
haveCtx, haveCancel := context.WithCancel(ctx)
|
finishedChan := make(chan protocol.FileInfo)
|
||||||
go w.dbWalkerRoutine(haveCtx, haveChan)
|
|
||||||
|
|
||||||
fsChan := make(chan fsWalkResult)
|
// A routine which walks the filesystem tree, and sends files which have
|
||||||
go w.fsWalkerRoutine(ctx, fsChan, haveCancel)
|
// been modified to the counter routine.
|
||||||
|
go func() {
|
||||||
toHashChan := make(chan ScanResult)
|
hashFiles := w.walkAndHashFiles(ctx, toHashChan, finishedChan)
|
||||||
finishedChan := make(chan ScanResult)
|
if len(w.Subs) == 0 {
|
||||||
go w.processWalkResults(ctx, fsChan, haveChan, toHashChan, finishedChan)
|
w.Filesystem.Walk(".", hashFiles)
|
||||||
|
} else {
|
||||||
|
for _, sub := range w.Subs {
|
||||||
|
w.Filesystem.Walk(sub, hashFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(toHashChan)
|
||||||
|
}()
|
||||||
|
|
||||||
// We're not required to emit scan progress events, just kick off hashers,
|
// We're not required to emit scan progress events, just kick off hashers,
|
||||||
// and feed inputs directly from the walker.
|
// and feed inputs directly from the walker.
|
||||||
@ -147,15 +139,15 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
|
|||||||
// Parallel hasher is stopped by this routine when we close the channel over
|
// Parallel hasher is stopped by this routine when we close the channel over
|
||||||
// which it receives the files we ask it to hash.
|
// which it receives the files we ask it to hash.
|
||||||
go func() {
|
go func() {
|
||||||
var filesToHash []ScanResult
|
var filesToHash []protocol.FileInfo
|
||||||
var total int64 = 1
|
var total int64 = 1
|
||||||
|
|
||||||
for file := range toHashChan {
|
for file := range toHashChan {
|
||||||
filesToHash = append(filesToHash, file)
|
filesToHash = append(filesToHash, file)
|
||||||
total += file.New.Size
|
total += file.Size
|
||||||
}
|
}
|
||||||
|
|
||||||
realToHashChan := make(chan ScanResult)
|
realToHashChan := make(chan protocol.FileInfo)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
progress := newByteCounter()
|
progress := newByteCounter()
|
||||||
|
|
||||||
@ -191,7 +183,7 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
|
|||||||
|
|
||||||
loop:
|
loop:
|
||||||
for _, file := range filesToHash {
|
for _, file := range filesToHash {
|
||||||
l.Debugln("real to hash:", file.New.Name)
|
l.Debugln("real to hash:", file.Name)
|
||||||
select {
|
select {
|
||||||
case realToHashChan <- file:
|
case realToHashChan <- file:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@ -204,49 +196,15 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
|
|||||||
return finishedChan
|
return finishedChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// dbWalkerRoutine walks the db and sends back file infos to be compared to scan results.
|
func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protocol.FileInfo) fs.WalkFunc {
|
||||||
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
|
|
||||||
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()
|
now := time.Now()
|
||||||
return func(path string, info fs.FileInfo, err error) error {
|
return func(path string, info fs.FileInfo, err error) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
// Return value used when we are returning early and don't want to
|
// Return value used when we are returning early and don't want to
|
||||||
// process the item. For directories, this means do-not-descend.
|
// process the item. For directories, this means do-not-descend.
|
||||||
var skip error // nil
|
var skip error // nil
|
||||||
@ -256,9 +214,7 @@ func (w *walker) createFSWalkFn(ctx context.Context, fsChan chan<- fsWalkResult)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if sendErr := fsWalkError(ctx, fsChan, path, err); sendErr != nil {
|
l.Debugln("error:", path, info, err)
|
||||||
return sendErr
|
|
||||||
}
|
|
||||||
return skip
|
return skip
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,6 +222,12 @@ func (w *walker) createFSWalkFn(ctx context.Context, fsChan chan<- fsWalkResult)
|
|||||||
return nil
|
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) {
|
if fs.IsTemporary(path) {
|
||||||
l.Debugln("temporary:", path)
|
l.Debugln("temporary:", path)
|
||||||
if info.IsRegular() && info.ModTime().Add(w.TempLifetime).Before(now) {
|
if info.IsRegular() && info.ModTime().Add(w.TempLifetime).Before(now) {
|
||||||
@ -276,170 +238,48 @@ func (w *walker) createFSWalkFn(ctx context.Context, fsChan chan<- fsWalkResult)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fs.IsInternal(path) {
|
if fs.IsInternal(path) {
|
||||||
l.Debugln("skip walking (internal):", path)
|
l.Debugln("ignored (internal):", path)
|
||||||
return skip
|
return skip
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.Matcher.Match(path).IsIgnored() {
|
if w.Matcher.Match(path).IsIgnored() {
|
||||||
l.Debugln("skip walking (patterns):", path)
|
l.Debugln("ignored (patterns):", path)
|
||||||
return skip
|
return skip
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utf8.ValidString(path) {
|
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)
|
l.Warnf("File name %q is not in UTF8 encoding; skipping.", path)
|
||||||
return skip
|
return skip
|
||||||
}
|
}
|
||||||
|
|
||||||
path, shouldSkip := w.normalizePath(path, info)
|
path, shouldSkip := w.normalizePath(path, info)
|
||||||
if shouldSkip {
|
if shouldSkip {
|
||||||
if err := fsWalkError(ctx, fsChan, path, errors.New("failed to normalize path")); err != nil {
|
return skip
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case info.IsSymlink():
|
||||||
|
if err := w.walkSymlink(ctx, path, dchan); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return skip
|
if info.IsDir() {
|
||||||
}
|
// under no circumstances shall we descend into a symlink
|
||||||
|
return fs.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
select {
|
case info.IsDir():
|
||||||
case fsChan <- fsWalkResult{
|
err = w.walkDir(ctx, path, info, dchan)
|
||||||
path: path,
|
|
||||||
info: info,
|
|
||||||
err: nil,
|
|
||||||
}:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// under no circumstances shall we descend into a symlink
|
case info.IsRegular():
|
||||||
if info.IsSymlink() && info.IsDir() {
|
err = w.walkRegular(ctx, path, info, fchan)
|
||||||
l.Debugln("skip walking (symlinked directory):", path)
|
|
||||||
return skip
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fsWalkError(ctx context.Context, dst chan<- fsWalkResult, path string, err error) error {
|
func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileInfo, fchan chan protocol.FileInfo) 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
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 fsRes.info.IsRegular():
|
|
||||||
w.walkRegular(ctx, fsRes.path, fsRes.info, oldFile, toHashChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) 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())
|
curMode := uint32(info.Mode())
|
||||||
if runtime.GOOS == "windows" && osutil.IsWindowsExecutable(relPath) {
|
if runtime.GOOS == "windows" && osutil.IsWindowsExecutable(relPath) {
|
||||||
curMode |= 0111
|
curMode |= 0111
|
||||||
@ -454,38 +294,40 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
|
|||||||
// - was not a symlink (since it's a file now)
|
// - was not a symlink (since it's a file now)
|
||||||
// - was not invalid (since it looks valid now)
|
// - was not invalid (since it looks valid now)
|
||||||
// - has the same size as previously
|
// - has the same size as previously
|
||||||
if !cf.IsEmpty() {
|
cf, ok := w.CurrentFiler.CurrentFile(relPath)
|
||||||
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, curMode)
|
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, curMode)
|
||||||
if permUnchanged && !cf.IsDeleted() && cf.ModTime().Equal(info.ModTime()) && !cf.IsDirectory() &&
|
if ok && permUnchanged && !cf.IsDeleted() && cf.ModTime().Equal(info.ModTime()) && !cf.IsDirectory() &&
|
||||||
!cf.IsSymlink() && !cf.IsInvalid() && cf.Size == info.Size() {
|
!cf.IsSymlink() && !cf.IsInvalid() && cf.Size == info.Size() {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&fs.ModePerm)
|
l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&fs.ModePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
f := ScanResult{
|
f := protocol.FileInfo{
|
||||||
New: protocol.FileInfo{
|
Name: relPath,
|
||||||
Name: relPath,
|
Type: protocol.FileInfoTypeFile,
|
||||||
Type: protocol.FileInfoTypeFile,
|
Version: cf.Version.Update(w.ShortID),
|
||||||
Version: cf.Version.Update(w.ShortID),
|
Permissions: curMode & uint32(maskModePerm),
|
||||||
Permissions: curMode & uint32(maskModePerm),
|
NoPermissions: w.IgnorePerms,
|
||||||
NoPermissions: w.IgnorePerms,
|
ModifiedS: info.ModTime().Unix(),
|
||||||
ModifiedS: info.ModTime().Unix(),
|
ModifiedNs: int32(info.ModTime().Nanosecond()),
|
||||||
ModifiedNs: int32(info.ModTime().Nanosecond()),
|
ModifiedBy: w.ShortID,
|
||||||
ModifiedBy: w.ShortID,
|
Size: info.Size(),
|
||||||
Size: info.Size(),
|
|
||||||
},
|
|
||||||
Old: cf,
|
|
||||||
}
|
}
|
||||||
l.Debugln("to hash:", relPath, f)
|
l.Debugln("to hash:", relPath, f)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case toHashChan <- f:
|
case fchan <- f:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, cf protocol.FileInfo, finishedChan chan<- ScanResult) {
|
func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, dchan chan protocol.FileInfo) error {
|
||||||
// A directory is "unchanged", if it
|
// A directory is "unchanged", if it
|
||||||
// - exists
|
// - exists
|
||||||
// - has the same permissions as previously, unless we are ignoring permissions
|
// - has the same permissions as previously, unless we are ignoring permissions
|
||||||
@ -493,41 +335,40 @@ func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo,
|
|||||||
// - was a directory previously (not a file or something else)
|
// - was a directory previously (not a file or something else)
|
||||||
// - was not a symlink (since it's a directory now)
|
// - was not a symlink (since it's a directory now)
|
||||||
// - was not invalid (since it looks valid now)
|
// - was not invalid (since it looks valid now)
|
||||||
if !cf.IsEmpty() {
|
cf, ok := w.CurrentFiler.CurrentFile(relPath)
|
||||||
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, uint32(info.Mode()))
|
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, uint32(info.Mode()))
|
||||||
if permUnchanged && !cf.IsDeleted() && cf.IsDirectory() && !cf.IsSymlink() && !cf.IsInvalid() {
|
if ok && permUnchanged && !cf.IsDeleted() && cf.IsDirectory() && !cf.IsSymlink() && !cf.IsInvalid() {
|
||||||
return
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f := ScanResult{
|
f := protocol.FileInfo{
|
||||||
New: protocol.FileInfo{
|
Name: relPath,
|
||||||
Name: relPath,
|
Type: protocol.FileInfoTypeDirectory,
|
||||||
Type: protocol.FileInfoTypeDirectory,
|
Version: cf.Version.Update(w.ShortID),
|
||||||
Version: cf.Version.Update(w.ShortID),
|
Permissions: uint32(info.Mode() & maskModePerm),
|
||||||
Permissions: uint32(info.Mode() & maskModePerm),
|
NoPermissions: w.IgnorePerms,
|
||||||
NoPermissions: w.IgnorePerms,
|
ModifiedS: info.ModTime().Unix(),
|
||||||
ModifiedS: info.ModTime().Unix(),
|
ModifiedNs: int32(info.ModTime().Nanosecond()),
|
||||||
ModifiedNs: int32(info.ModTime().Nanosecond()),
|
ModifiedBy: w.ShortID,
|
||||||
ModifiedBy: w.ShortID,
|
|
||||||
},
|
|
||||||
Old: cf,
|
|
||||||
}
|
}
|
||||||
l.Debugln("dir:", relPath, f)
|
l.Debugln("dir:", relPath, f)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case finishedChan <- f:
|
case dchan <- f:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// walkSymlink returns nil or an error, if the error is of the nature that
|
// walkSymlink returns nil or an error, if the error is of the nature that
|
||||||
// it should stop the entire walk.
|
// it should stop the entire walk.
|
||||||
func (w *walker) walkSymlink(ctx context.Context, relPath string, cf protocol.FileInfo, finishedChan chan<- ScanResult) {
|
func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan protocol.FileInfo) error {
|
||||||
// Symlinks are not supported on Windows. We ignore instead of returning
|
// Symlinks are not supported on Windows. We ignore instead of returning
|
||||||
// an error.
|
// an error.
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always rehash symlinks as they have no modtime or
|
// We always rehash symlinks as they have no modtime or
|
||||||
@ -538,7 +379,7 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, cf protocol.Fi
|
|||||||
target, err := w.Filesystem.ReadSymlink(relPath)
|
target, err := w.Filesystem.ReadSymlink(relPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugln("readlink error:", relPath, err)
|
l.Debugln("readlink error:", relPath, err)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// A symlink is "unchanged", if
|
// A symlink is "unchanged", if
|
||||||
@ -547,27 +388,28 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, cf protocol.Fi
|
|||||||
// - it was a symlink
|
// - it was a symlink
|
||||||
// - it wasn't invalid
|
// - it wasn't invalid
|
||||||
// - the target was the same
|
// - the target was the same
|
||||||
if !cf.IsEmpty() && !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && cf.SymlinkTarget == target {
|
cf, ok := w.CurrentFiler.CurrentFile(relPath)
|
||||||
return
|
if ok && !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && cf.SymlinkTarget == target {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
f := ScanResult{
|
f := protocol.FileInfo{
|
||||||
New: protocol.FileInfo{
|
Name: relPath,
|
||||||
Name: relPath,
|
Type: protocol.FileInfoTypeSymlink,
|
||||||
Type: protocol.FileInfoTypeSymlink,
|
Version: cf.Version.Update(w.ShortID),
|
||||||
Version: cf.Version.Update(w.ShortID),
|
NoPermissions: true, // Symlinks don't have permissions of their own
|
||||||
NoPermissions: true, // Symlinks don't have permissions of their own
|
SymlinkTarget: target,
|
||||||
SymlinkTarget: target,
|
|
||||||
},
|
|
||||||
Old: cf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debugln("symlink changedb:", relPath, f)
|
l.Debugln("symlink changedb:", relPath, f)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case finishedChan <- f:
|
case dchan <- f:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizePath returns the normalized relative path (possibly after fixing
|
// normalizePath returns the normalized relative path (possibly after fixing
|
||||||
@ -690,6 +532,10 @@ func (c *byteCounter) Close() {
|
|||||||
close(c.stop)
|
close(c.stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
type noHaveWalker struct{}
|
// A no-op CurrentFiler
|
||||||
|
|
||||||
func (noHaveWalker) Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo) {}
|
type noCurrentFiler struct{}
|
||||||
|
|
||||||
|
func (noCurrentFiler) CurrentFile(name string) (protocol.FileInfo, bool) {
|
||||||
|
return protocol.FileInfo{}, false
|
||||||
|
}
|
||||||
|
@ -69,7 +69,7 @@ func TestWalkSub(t *testing.T) {
|
|||||||
Matcher: ignores,
|
Matcher: ignores,
|
||||||
Hashers: 2,
|
Hashers: 2,
|
||||||
})
|
})
|
||||||
var files []ScanResult
|
var files []protocol.FileInfo
|
||||||
for f := range fchan {
|
for f := range fchan {
|
||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
@ -80,10 +80,10 @@ func TestWalkSub(t *testing.T) {
|
|||||||
if len(files) != 2 {
|
if len(files) != 2 {
|
||||||
t.Fatalf("Incorrect length %d != 2", len(files))
|
t.Fatalf("Incorrect length %d != 2", len(files))
|
||||||
}
|
}
|
||||||
if files[0].New.Name != "dir2" {
|
if files[0].Name != "dir2" {
|
||||||
t.Errorf("Incorrect file %v != dir2", files[0])
|
t.Errorf("Incorrect file %v != dir2", files[0])
|
||||||
}
|
}
|
||||||
if files[1].New.Name != filepath.Join("dir2", "cfile") {
|
if files[1].Name != filepath.Join("dir2", "cfile") {
|
||||||
t.Errorf("Incorrect file %v != dir2/cfile", files[1])
|
t.Errorf("Incorrect file %v != dir2/cfile", files[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +103,7 @@ func TestWalk(t *testing.T) {
|
|||||||
Hashers: 2,
|
Hashers: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
var tmp []ScanResult
|
var tmp []protocol.FileInfo
|
||||||
for f := range fchan {
|
for f := range fchan {
|
||||||
tmp = append(tmp, f)
|
tmp = append(tmp, f)
|
||||||
}
|
}
|
||||||
@ -251,9 +251,9 @@ func TestNormalization(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue1507(t *testing.T) {
|
func TestIssue1507(t *testing.T) {
|
||||||
w := &walker{Config{Matcher: ignore.New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."))}}
|
w := &walker{}
|
||||||
c := make(chan fsWalkResult, 100)
|
c := make(chan protocol.FileInfo, 100)
|
||||||
fn := w.createFSWalkFn(context.TODO(), c)
|
fn := w.walkAndHashFiles(context.TODO(), c, c)
|
||||||
|
|
||||||
fn("", nil, protocol.ErrClosed)
|
fn("", nil, protocol.ErrClosed)
|
||||||
}
|
}
|
||||||
@ -274,14 +274,15 @@ func TestWalkSymlinkUnix(t *testing.T) {
|
|||||||
// Scan it
|
// Scan it
|
||||||
files, _ := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, "_symlinks"), path)
|
files, _ := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, "_symlinks"), path)
|
||||||
|
|
||||||
|
// Verify that we got one symlink and with the correct attributes
|
||||||
if len(files) != 1 {
|
if len(files) != 1 {
|
||||||
t.Errorf("expected 1 symlink, not %d", len(files))
|
t.Errorf("expected 1 symlink, not %d", len(files))
|
||||||
}
|
}
|
||||||
if len(files[0].New.Blocks) != 0 {
|
if len(files[0].Blocks) != 0 {
|
||||||
t.Errorf("expected zero blocks for symlink, not %d", len(files[0].New.Blocks))
|
t.Errorf("expected zero blocks for symlink, not %d", len(files[0].Blocks))
|
||||||
}
|
}
|
||||||
if files[0].New.SymlinkTarget != "../testdata" {
|
if files[0].SymlinkTarget != "../testdata" {
|
||||||
t.Errorf("expected symlink to have target destination, not %q", files[0].New.SymlinkTarget)
|
t.Errorf("expected symlink to have target destination, not %q", files[0].SymlinkTarget)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,7 +342,7 @@ func TestWalkRootSymlink(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkDir(fs fs.Filesystem, dir string) ([]ScanResult, error) {
|
func walkDir(fs fs.Filesystem, dir string) ([]protocol.FileInfo, error) {
|
||||||
fchan := Walk(context.TODO(), Config{
|
fchan := Walk(context.TODO(), Config{
|
||||||
Filesystem: fs,
|
Filesystem: fs,
|
||||||
Subs: []string{dir},
|
Subs: []string{dir},
|
||||||
@ -350,7 +351,7 @@ func walkDir(fs fs.Filesystem, dir string) ([]ScanResult, error) {
|
|||||||
Hashers: 2,
|
Hashers: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
var tmp []ScanResult
|
var tmp []protocol.FileInfo
|
||||||
for f := range fchan {
|
for f := range fchan {
|
||||||
tmp = append(tmp, f)
|
tmp = append(tmp, f)
|
||||||
}
|
}
|
||||||
@ -359,14 +360,14 @@ func walkDir(fs fs.Filesystem, dir string) ([]ScanResult, error) {
|
|||||||
return tmp, nil
|
return tmp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type fileList []ScanResult
|
type fileList []protocol.FileInfo
|
||||||
|
|
||||||
func (l fileList) Len() int {
|
func (l fileList) Len() int {
|
||||||
return len(l)
|
return len(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l fileList) Less(a, b int) bool {
|
func (l fileList) Less(a, b int) bool {
|
||||||
return l[a].New.Name < l[b].New.Name
|
return l[a].Name < l[b].Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l fileList) Swap(a, b int) {
|
func (l fileList) Swap(a, b int) {
|
||||||
@ -376,12 +377,12 @@ func (l fileList) Swap(a, b int) {
|
|||||||
func (l fileList) testfiles() testfileList {
|
func (l fileList) testfiles() testfileList {
|
||||||
testfiles := make(testfileList, len(l))
|
testfiles := make(testfileList, len(l))
|
||||||
for i, f := range l {
|
for i, f := range l {
|
||||||
if len(f.New.Blocks) > 1 {
|
if len(f.Blocks) > 1 {
|
||||||
panic("simple test case stuff only supports a single block per file")
|
panic("simple test case stuff only supports a single block per file")
|
||||||
}
|
}
|
||||||
testfiles[i] = testfile{name: f.New.Name, length: f.New.FileSize()}
|
testfiles[i] = testfile{name: f.Name, length: f.FileSize()}
|
||||||
if len(f.New.Blocks) == 1 {
|
if len(f.Blocks) == 1 {
|
||||||
testfiles[i].hash = fmt.Sprintf("%x", f.New.Blocks[0].Hash)
|
testfiles[i].hash = fmt.Sprintf("%x", f.Blocks[0].Hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return testfiles
|
return testfiles
|
||||||
@ -464,13 +465,13 @@ func TestStopWalk(t *testing.T) {
|
|||||||
for {
|
for {
|
||||||
f := <-fchan
|
f := <-fchan
|
||||||
t.Log("Scanned", f)
|
t.Log("Scanned", f)
|
||||||
if f.New.IsDirectory() {
|
if f.IsDirectory() {
|
||||||
if len(f.New.Name) == 0 || f.New.Permissions == 0 {
|
if len(f.Name) == 0 || f.Permissions == 0 {
|
||||||
t.Error("Bad directory entry", f)
|
t.Error("Bad directory entry", f)
|
||||||
}
|
}
|
||||||
dirs++
|
dirs++
|
||||||
} else {
|
} else {
|
||||||
if len(f.New.Name) == 0 || len(f.New.Blocks) == 0 || f.New.Permissions == 0 {
|
if len(f.Name) == 0 || len(f.Blocks) == 0 || f.Permissions == 0 {
|
||||||
t.Error("Bad file entry", f)
|
t.Error("Bad file entry", f)
|
||||||
}
|
}
|
||||||
files++
|
files++
|
||||||
@ -528,69 +529,3 @@ func verify(r io.Reader, blocksize int, blocks []protocol.BlockInfo) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following (randomish) scenario produced an error uncovered by integration tests
|
|
||||||
func TestWalkIntegration(t *testing.T) {
|
|
||||||
tmpDir, err := ioutil.TempDir(".", "_request-")
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to create temporary testing dir")
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir)
|
|
||||||
fs.Mkdir("a", 0777)
|
|
||||||
toDel := filepath.Join("a", "b")
|
|
||||||
for _, f := range []string{"b", toDel} {
|
|
||||||
fi, err := fs.Create(f)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fi.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := Config{
|
|
||||||
Filesystem: fs,
|
|
||||||
BlockSize: 128 * 1024,
|
|
||||||
Hashers: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
rchan := Walk(context.TODO(), conf)
|
|
||||||
|
|
||||||
var res []ScanResult
|
|
||||||
for r := range rchan {
|
|
||||||
res = append(res, r)
|
|
||||||
}
|
|
||||||
sort.Sort(fileList(res))
|
|
||||||
thw := make([]protocol.FileInfo, 0, len(res))
|
|
||||||
for _, r := range res {
|
|
||||||
thw = append(thw, r.New)
|
|
||||||
}
|
|
||||||
conf.Have = testHaveWalker(thw)
|
|
||||||
|
|
||||||
if err = fs.Remove(toDel); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rchan = Walk(context.TODO(), conf)
|
|
||||||
|
|
||||||
for r := range rchan {
|
|
||||||
if r.New.Name != toDel {
|
|
||||||
t.Fatalf("Received unexpected result %v", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testHaveWalker []protocol.FileInfo
|
|
||||||
|
|
||||||
func (thw testHaveWalker) Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo) {
|
|
||||||
if prefix != "" {
|
|
||||||
panic("cannot walk with prefix")
|
|
||||||
}
|
|
||||||
for _, f := range thw {
|
|
||||||
select {
|
|
||||||
case out <- f:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user