diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index e7a5526b..e57a1a2c 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -829,7 +829,7 @@ func restGetSystemBrowse(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(current, pathSeparator) && !strings.HasSuffix(search, pathSeparator) { search = search + pathSeparator } - subdirectories, _ := filepath.Glob(search + "*") + subdirectories, _ := osutil.Glob(search + "*") ret := make([]string, 0, 10) for _, subdirectory := range subdirectories { info, err := os.Stat(subdirectory) diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index ef837dc3..1b035da6 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -1061,7 +1061,7 @@ func cleanConfigDirectory() { for pat, dur := range patterns { pat = filepath.Join(baseDirs["config"], pat) - files, err := filepath.Glob(pat) + files, err := osutil.Glob(pat) if err != nil { l.Infoln("Cleaning:", err) continue diff --git a/internal/osutil/glob_unix.go b/internal/osutil/glob_unix.go new file mode 100644 index 00000000..7270a269 --- /dev/null +++ b/internal/osutil/glob_unix.go @@ -0,0 +1,17 @@ +// Copyright (C) 2015 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build !windows + +package osutil + +import ( + "path/filepath" +) + +func Glob(pattern string) (matches []string, err error) { + return filepath.Glob(pattern) +} diff --git a/internal/osutil/glob_windows.go b/internal/osutil/glob_windows.go new file mode 100644 index 00000000..ef35bba6 --- /dev/null +++ b/internal/osutil/glob_windows.go @@ -0,0 +1,92 @@ +// Copyright (C) 2015 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build windows + +package osutil + +import ( + "os" + "path/filepath" + "sort" + "strings" +) + +// Deals with https://github.com/golang/go/issues/10577 +func Glob(pattern string) (matches []string, err error) { + if !hasMeta(pattern) { + if _, err = os.Lstat(pattern); err != nil { + return nil, nil + } + return []string{pattern}, nil + } + + dir, file := filepath.Split(pattern) + switch dir { + case "": + dir = "." + case string(filepath.Separator): + // nothing + default: + dir = dir[0 : len(dir)-1] // chop off trailing separator + } + + if !hasMeta(dir) { + return glob(dir, file, nil) + } + + var m []string + m, err = Glob(dir) + if err != nil { + return + } + for _, d := range m { + matches, err = glob(d, file, matches) + if err != nil { + return + } + } + return +} + +func hasMeta(path string) bool { + // Strip off Windows long path prefix if it exists. + if strings.HasPrefix(path, "\\\\?\\") { + path = path[4:] + } + // TODO(niemeyer): Should other magic characters be added here? + return strings.IndexAny(path, "*?[") >= 0 +} + +func glob(dir, pattern string, matches []string) (m []string, e error) { + m = matches + fi, err := os.Stat(dir) + if err != nil { + return + } + if !fi.IsDir() { + return + } + d, err := os.Open(dir) + if err != nil { + return + } + defer d.Close() + + names, _ := d.Readdirnames(-1) + sort.Strings(names) + + for _, n := range names { + matched, err := filepath.Match(pattern, n) + if err != nil { + return m, err + } + if matched { + m = append(m, filepath.Join(dir, n)) + } + } + return +} diff --git a/internal/versioner/simple.go b/internal/versioner/simple.go index d179bee4..190d3764 100644 --- a/internal/versioner/simple.go +++ b/internal/versioner/simple.go @@ -97,14 +97,14 @@ func (v Simple) Archive(filePath string) error { } // Glob according to the new file~timestamp.ext pattern. - newVersions, err := filepath.Glob(filepath.Join(dir, taggedFilename(file, TimeGlob))) + newVersions, err := osutil.Glob(filepath.Join(dir, taggedFilename(file, TimeGlob))) if err != nil { l.Warnln("globbing:", err) return nil } // Also according to the old file.ext~timestamp pattern. - oldVersions, err := filepath.Glob(filepath.Join(dir, file+"~"+TimeGlob)) + oldVersions, err := osutil.Glob(filepath.Join(dir, file+"~"+TimeGlob)) if err != nil { l.Warnln("globbing:", err) return nil diff --git a/internal/versioner/staggered.go b/internal/versioner/staggered.go index bf328de8..a8bd9486 100644 --- a/internal/versioner/staggered.go +++ b/internal/versioner/staggered.go @@ -329,14 +329,14 @@ func (v Staggered) Archive(filePath string) error { } // Glob according to the new file~timestamp.ext pattern. - newVersions, err := filepath.Glob(filepath.Join(dir, taggedFilename(file, TimeGlob))) + newVersions, err := osutil.Glob(filepath.Join(dir, taggedFilename(file, TimeGlob))) if err != nil { l.Warnln("globbing:", err) return nil } // Also according to the old file.ext~timestamp pattern. - oldVersions, err := filepath.Glob(filepath.Join(dir, file+"~"+TimeGlob)) + oldVersions, err := osutil.Glob(filepath.Join(dir, file+"~"+TimeGlob)) if err != nil { l.Warnln("globbing:", err) return nil diff --git a/internal/versioner/versioner_test.go b/internal/versioner/versioner_test.go index 22f7f734..2f9b1256 100644 --- a/internal/versioner/versioner_test.go +++ b/internal/versioner/versioner_test.go @@ -7,8 +7,12 @@ package versioner import ( + "io/ioutil" + "math" + "os" "path/filepath" "testing" + "time" ) func TestTaggedFilename(t *testing.T) { @@ -42,3 +46,45 @@ func TestTaggedFilename(t *testing.T) { } } } + +func TestSimpleVersioningVersionCount(t *testing.T) { + if testing.Short() { + t.Skip("Test takes some time, skipping.") + } + + dir, err := ioutil.TempDir("", "") + defer os.RemoveAll(dir) + if err != nil { + t.Error(err) + } + + v := NewSimple("", dir, map[string]string{"keep": "2"}) + versionDir := filepath.Join(dir, ".stversions") + + path := filepath.Join(dir, "test") + + for i := 1; i <= 3; i++ { + f, err := os.Create(path) + if err != nil { + t.Error(err) + } + f.Close() + v.Archive(path) + + d, err := os.Open(versionDir) + if err != nil { + t.Error(err) + } + n, err := d.Readdirnames(-1) + if err != nil { + t.Error(err) + } + + if float64(len(n)) != math.Min(float64(i), 2) { + t.Error("Wrong count") + } + d.Close() + + time.Sleep(time.Second) + } +} diff --git a/test/cli_test.go b/test/cli_test.go index f47c9d39..32a2d18f 100644 --- a/test/cli_test.go +++ b/test/cli_test.go @@ -49,7 +49,7 @@ func TestCLIReset(t *testing.T) { // Clean up - dirs, err = filepath.Glob("*.syncthing-reset-*") + dirs, err = osutil.Glob("*.syncthing-reset-*") if err != nil { t.Fatal(err) } diff --git a/test/conflict_test.go b/test/conflict_test.go index 90d28c87..2d70b867 100644 --- a/test/conflict_test.go +++ b/test/conflict_test.go @@ -120,7 +120,7 @@ func TestConflict(t *testing.T) { // The conflict is expected on the s2 side due to how we calculate which // file is the winner (based on device ID) - files, err := filepath.Glob("s2/*sync-conflict*") + files, err := osutil.Glob("s2/*sync-conflict*") if err != nil { t.Fatal(err) } @@ -170,7 +170,7 @@ func TestConflict(t *testing.T) { // The conflict should manifest on the s2 side again, where we should have // moved the file to a conflict copy instead of just deleting it. - files, err = filepath.Glob("s2/*sync-conflict*") + files, err = osutil.Glob("s2/*sync-conflict*") if err != nil { t.Fatal(err) } @@ -240,7 +240,7 @@ func TestInitialMergeConflicts(t *testing.T) { // s1 should have three-four files (there's a conflict from s2 which may or may not have synced yet) - files, err := filepath.Glob("s1/file*") + files, err := osutil.Glob("s1/file*") if err != nil { t.Fatal(err) } @@ -250,7 +250,7 @@ func TestInitialMergeConflicts(t *testing.T) { // s2 should have four files (there's a conflict) - files, err = filepath.Glob("s2/file*") + files, err = osutil.Glob("s2/file*") if err != nil { t.Fatal(err) } @@ -260,7 +260,7 @@ func TestInitialMergeConflicts(t *testing.T) { // file1 is in conflict, so there's two versions of that one - files, err = filepath.Glob("s2/file1*") + files, err = osutil.Glob("s2/file1*") if err != nil { t.Fatal(err) } @@ -316,7 +316,7 @@ func TestResetConflicts(t *testing.T) { // s1 should have three files - files, err := filepath.Glob("s1/file*") + files, err := osutil.Glob("s1/file*") if err != nil { t.Fatal(err) } @@ -326,7 +326,7 @@ func TestResetConflicts(t *testing.T) { // s2 should have three - files, err = filepath.Glob("s2/file*") + files, err = osutil.Glob("s2/file*") if err != nil { t.Fatal(err) } @@ -409,7 +409,7 @@ func TestResetConflicts(t *testing.T) { // s2 should have five files (three plus two conflicts) - files, err = filepath.Glob("s2/file*") + files, err = osutil.Glob("s2/file*") if err != nil { t.Fatal(err) } @@ -419,7 +419,7 @@ func TestResetConflicts(t *testing.T) { // file1 is in conflict, so there's two versions of that one - files, err = filepath.Glob("s2/file1*") + files, err = osutil.Glob("s2/file1*") if err != nil { t.Fatal(err) } @@ -429,7 +429,7 @@ func TestResetConflicts(t *testing.T) { // file2 is in conflict, so there's two versions of that one - files, err = filepath.Glob("s2/file2*") + files, err = osutil.Glob("s2/file2*") if err != nil { t.Fatal(err) } diff --git a/test/util.go b/test/util.go index 47194456..10a9eb1c 100644 --- a/test/util.go +++ b/test/util.go @@ -241,7 +241,7 @@ func (i *inifiteReader) Read(bs []byte) (int, error) { // rm -rf func removeAll(dirs ...string) error { for _, dir := range dirs { - files, err := filepath.Glob(dir) + files, err := osutil.Glob(dir) if err != nil { return err }