diff --git a/lib/versioner/simple.go b/lib/versioner/simple.go
index d8cbcc504..06ab00b27 100644
--- a/lib/versioner/simple.go
+++ b/lib/versioner/simple.go
@@ -22,6 +22,7 @@ func init() {
type simple struct {
keep int
+ cleanoutDays int
folderFs fs.Filesystem
versionsFs fs.Filesystem
copyRangeMethod fs.CopyRangeMethod
@@ -29,12 +30,16 @@ type simple struct {
func newSimple(cfg config.FolderConfiguration) Versioner {
var keep, err = strconv.Atoi(cfg.Versioning.Params["keep"])
+ cleanoutDays, _ := strconv.Atoi(cfg.Versioning.Params["cleanoutDays"])
+ // On error we default to 0, "do not clean out the trash can"
+
if err != nil {
keep = 5 // A reasonable default
}
s := simple{
keep: keep,
+ cleanoutDays: cleanoutDays,
folderFs: cfg.Filesystem(),
versionsFs: versionerFsFromFolderCfg(cfg),
copyRangeMethod: cfg.CopyRangeMethod,
@@ -75,6 +80,6 @@ func (v simple) Restore(filepath string, versionTime time.Time) error {
return restoreFile(v.copyRangeMethod, v.versionsFs, v.folderFs, filepath, versionTime, TagFilename)
}
-func (v simple) Clean(_ context.Context) error {
- return nil
+func (v simple) Clean(ctx context.Context) error {
+ return cleanByDay(ctx, v.versionsFs, v.cleanoutDays)
}
diff --git a/lib/versioner/simple_test.go b/lib/versioner/simple_test.go
index 4491735d8..219492138 100644
--- a/lib/versioner/simple_test.go
+++ b/lib/versioner/simple_test.go
@@ -7,13 +7,14 @@
package versioner
import (
- "github.com/syncthing/syncthing/lib/config"
"io/ioutil"
"math"
"path/filepath"
"testing"
"time"
+ "github.com/syncthing/syncthing/lib/config"
+
"github.com/syncthing/syncthing/lib/fs"
)
diff --git a/lib/versioner/trashcan.go b/lib/versioner/trashcan.go
index b56fc6b50..2e8110259 100644
--- a/lib/versioner/trashcan.go
+++ b/lib/versioner/trashcan.go
@@ -56,51 +56,7 @@ func (t *trashcan) String() string {
}
func (t *trashcan) Clean(ctx context.Context) error {
- if t.cleanoutDays <= 0 {
- // no cleanout requested
- return nil
- }
-
- if _, err := t.versionsFs.Lstat("."); fs.IsNotExist(err) {
- return nil
- }
-
- cutoff := time.Now().Add(time.Duration(-24*t.cleanoutDays) * time.Hour)
- dirTracker := make(emptyDirTracker)
-
- walkFn := func(path string, info fs.FileInfo, err error) error {
- if err != nil {
- return err
- }
- select {
- case <-ctx.Done():
- return ctx.Err()
- default:
- }
-
- if info.IsDir() && !info.IsSymlink() {
- dirTracker.addDir(path)
- return nil
- }
-
- if info.ModTime().Before(cutoff) {
- // The file is too old; remove it.
- err = t.versionsFs.Remove(path)
- } else {
- // Keep this file, and remember it so we don't unnecessarily try
- // to remove this directory.
- dirTracker.addFile(path)
- }
- return err
- }
-
- if err := t.versionsFs.Walk(".", walkFn); err != nil {
- return err
- }
-
- dirTracker.deleteEmptyDirs(t.versionsFs)
-
- return nil
+ return cleanByDay(ctx, t.versionsFs, t.cleanoutDays)
}
func (t *trashcan) GetVersions() (map[string][]FileVersion, error) {
diff --git a/lib/versioner/trashcan_test.go b/lib/versioner/trashcan_test.go
index 94143a4b4..f90e856c4 100644
--- a/lib/versioner/trashcan_test.go
+++ b/lib/versioner/trashcan_test.go
@@ -7,10 +7,7 @@
package versioner
import (
- "context"
"io/ioutil"
- "os"
- "path/filepath"
"testing"
"time"
@@ -18,76 +15,6 @@ import (
"github.com/syncthing/syncthing/lib/fs"
)
-func TestTrashcanCleanout(t *testing.T) {
- // Verify that files older than the cutoff are removed, that files newer
- // than the cutoff are *not* removed, and that empty directories are
- // removed (best effort).
-
- var testcases = []struct {
- file string
- shouldRemove bool
- }{
- {"testdata/.stversions/file1", false},
- {"testdata/.stversions/file2", true},
- {"testdata/.stversions/keep1/file1", false},
- {"testdata/.stversions/keep1/file2", false},
- {"testdata/.stversions/keep2/file1", false},
- {"testdata/.stversions/keep2/file2", true},
- {"testdata/.stversions/keep3/keepsubdir/file1", false},
- {"testdata/.stversions/remove/file1", true},
- {"testdata/.stversions/remove/file2", true},
- {"testdata/.stversions/remove/removesubdir/file1", true},
- }
-
- os.RemoveAll("testdata")
- defer os.RemoveAll("testdata")
-
- oldTime := time.Now().Add(-8 * 24 * time.Hour)
- for _, tc := range testcases {
- os.MkdirAll(filepath.Dir(tc.file), 0777)
- if err := ioutil.WriteFile(tc.file, []byte("data"), 0644); err != nil {
- t.Fatal(err)
- }
- if tc.shouldRemove {
- if err := os.Chtimes(tc.file, oldTime, oldTime); err != nil {
- t.Fatal(err)
- }
- }
- }
-
- cfg := config.FolderConfiguration{
- FilesystemType: fs.FilesystemTypeBasic,
- Path: "testdata",
- Versioning: config.VersioningConfiguration{
- Params: map[string]string{
- "cleanoutDays": "7",
- },
- },
- }
-
- versioner := newTrashcan(cfg).(*trashcan)
- if err := versioner.Clean(context.Background()); err != nil {
- t.Fatal(err)
- }
-
- for _, tc := range testcases {
- _, err := os.Lstat(tc.file)
- if tc.shouldRemove && !os.IsNotExist(err) {
- t.Error(tc.file, "should have been removed")
- } else if !tc.shouldRemove && err != nil {
- t.Error(tc.file, "should not have been removed")
- }
- }
-
- if _, err := os.Lstat("testdata/.stversions/keep3"); os.IsNotExist(err) {
- t.Error("directory with non empty subdirs should not be removed")
- }
-
- if _, err := os.Lstat("testdata/.stversions/remove"); !os.IsNotExist(err) {
- t.Error("empty directory should have been removed")
- }
-}
-
func TestTrashcanArchiveRestoreSwitcharoo(t *testing.T) {
// This tests that trashcan versioner restoration correctly archives existing file, because trashcan versioner
// files are untagged, archiving existing file to replace with a restored version technically should collide in
diff --git a/lib/versioner/util.go b/lib/versioner/util.go
index 16e113b9d..3dc7cbec5 100644
--- a/lib/versioner/util.go
+++ b/lib/versioner/util.go
@@ -7,6 +7,7 @@
package versioner
import (
+ "context"
"path/filepath"
"regexp"
"sort"
@@ -293,3 +294,51 @@ func findAllVersions(fs fs.Filesystem, filePath string) []string {
return versions
}
+
+func cleanByDay(ctx context.Context, versionsFs fs.Filesystem, cleanoutDays int) error {
+ if cleanoutDays <= 0 {
+ return nil
+ }
+
+ if _, err := versionsFs.Lstat("."); fs.IsNotExist(err) {
+ return nil
+ }
+
+ cutoff := time.Now().Add(time.Duration(-24*cleanoutDays) * time.Hour)
+ dirTracker := make(emptyDirTracker)
+
+ walkFn := func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
+
+ if info.IsDir() && !info.IsSymlink() {
+ dirTracker.addDir(path)
+ return nil
+ }
+
+ if info.ModTime().Before(cutoff) {
+ // The file is too old; remove it.
+ err = versionsFs.Remove(path)
+ } else {
+ // Keep this file, and remember it so we don't unnecessarily try
+ // to remove this directory.
+ dirTracker.addFile(path)
+ }
+ return err
+ }
+
+ if err := versionsFs.Walk(".", walkFn); err != nil {
+ return err
+ }
+
+ dirTracker.deleteEmptyDirs(versionsFs)
+
+ return nil
+}
diff --git a/lib/versioner/versioner_test.go b/lib/versioner/versioner_test.go
new file mode 100644
index 000000000..4dfaaf322
--- /dev/null
+++ b/lib/versioner/versioner_test.go
@@ -0,0 +1,91 @@
+// Copyright (C) 2020 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 https://mozilla.org/MPL/2.0/.
+
+package versioner
+
+import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/syncthing/syncthing/lib/config"
+ "github.com/syncthing/syncthing/lib/fs"
+)
+
+func TestVersionerCleanOut(t *testing.T) {
+ cfg := config.FolderConfiguration{
+ FilesystemType: fs.FilesystemTypeBasic,
+ Path: "testdata",
+ Versioning: config.VersioningConfiguration{
+ Params: map[string]string{
+ "cleanoutDays": "7",
+ },
+ },
+ }
+
+ testCasesVersioner := map[string]Versioner{
+ "simple": newSimple(cfg),
+ "trashcan": newTrashcan(cfg),
+ }
+
+ var testcases = map[string]bool{
+ "testdata/.stversions/file1": false,
+ "testdata/.stversions/file2": true,
+ "testdata/.stversions/keep1/file1": false,
+ "testdata/.stversions/keep1/file2": false,
+ "testdata/.stversions/keep2/file1": false,
+ "testdata/.stversions/keep2/file2": true,
+ "testdata/.stversions/keep3/keepsubdir/file1": false,
+ "testdata/.stversions/remove/file1": true,
+ "testdata/.stversions/remove/file2": true,
+ "testdata/.stversions/remove/removesubdir/file1": true,
+ }
+
+ for versionerType, versioner := range testCasesVersioner {
+ t.Run(fmt.Sprintf("%v versioner trashcan clean up", versionerType), func(t *testing.T) {
+ os.RemoveAll("testdata")
+ defer os.RemoveAll("testdata")
+
+ oldTime := time.Now().Add(-8 * 24 * time.Hour)
+ for file, shouldRemove := range testcases {
+ os.MkdirAll(filepath.Dir(file), 0777)
+ if err := ioutil.WriteFile(file, []byte("data"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ if shouldRemove {
+ if err := os.Chtimes(file, oldTime, oldTime); err != nil {
+ t.Fatal(err)
+ }
+ }
+ }
+
+ if err := versioner.Clean(context.Background()); err != nil {
+ t.Fatal(err)
+ }
+
+ for file, shouldRemove := range testcases {
+ _, err := os.Lstat(file)
+ if shouldRemove && !os.IsNotExist(err) {
+ t.Error(file, "should have been removed")
+ } else if !shouldRemove && err != nil {
+ t.Error(file, "should not have been removed")
+ }
+ }
+
+ if _, err := os.Lstat("testdata/.stversions/keep3"); os.IsNotExist(err) {
+ t.Error("directory with non empty subdirs should not be removed")
+ }
+
+ if _, err := os.Lstat("testdata/.stversions/remove"); !os.IsNotExist(err) {
+ t.Error("empty directory should have been removed")
+ }
+ })
+ }
+}