|
@@ -17,6 +17,7 @@ import (
|
|
"github.com/pkg/errors"
|
|
"github.com/pkg/errors"
|
|
"github.com/syncthing/syncthing/lib/fs"
|
|
"github.com/syncthing/syncthing/lib/fs"
|
|
"github.com/syncthing/syncthing/lib/osutil"
|
|
"github.com/syncthing/syncthing/lib/osutil"
|
|
|
|
+ "github.com/syncthing/syncthing/lib/util"
|
|
)
|
|
)
|
|
|
|
|
|
var errDirectory = fmt.Errorf("cannot restore on top of a directory")
|
|
var errDirectory = fmt.Errorf("cannot restore on top of a directory")
|
|
@@ -87,15 +88,16 @@ func retrieveVersions(fileSystem fs.Filesystem) (map[string][]FileVersion, error
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ modTime := f.ModTime().Truncate(time.Second)
|
|
|
|
+
|
|
path = osutil.NormalizedFilename(path)
|
|
path = osutil.NormalizedFilename(path)
|
|
|
|
|
|
name, tag := UntagFilename(path)
|
|
name, tag := UntagFilename(path)
|
|
- // Something invalid, assume it's an untagged file
|
|
|
|
|
|
+ // Something invalid, assume it's an untagged file (trashcan versioner stuff)
|
|
if name == "" || tag == "" {
|
|
if name == "" || tag == "" {
|
|
- versionTime := f.ModTime().Truncate(time.Second)
|
|
|
|
files[path] = append(files[path], FileVersion{
|
|
files[path] = append(files[path], FileVersion{
|
|
- VersionTime: versionTime,
|
|
|
|
- ModTime: versionTime,
|
|
|
|
|
|
+ VersionTime: modTime,
|
|
|
|
+ ModTime: modTime,
|
|
Size: f.Size(),
|
|
Size: f.Size(),
|
|
})
|
|
})
|
|
return nil
|
|
return nil
|
|
@@ -107,15 +109,11 @@ func retrieveVersions(fileSystem fs.Filesystem) (map[string][]FileVersion, error
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
- if err == nil {
|
|
|
|
- files[name] = append(files[name], FileVersion{
|
|
|
|
- // This looks backwards, but mtime of the file is when we archived it, making that the version time
|
|
|
|
- // The mod time of the file before archiving is embedded in the file name.
|
|
|
|
- VersionTime: f.ModTime().Truncate(time.Second),
|
|
|
|
- ModTime: versionTime.Truncate(time.Second),
|
|
|
|
- Size: f.Size(),
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
|
|
+ files[name] = append(files[name], FileVersion{
|
|
|
|
+ VersionTime: versionTime,
|
|
|
|
+ ModTime: modTime,
|
|
|
|
+ Size: f.Size(),
|
|
|
|
+ })
|
|
|
|
|
|
return nil
|
|
return nil
|
|
})
|
|
})
|
|
@@ -156,30 +154,38 @@ func archiveFile(srcFs, dstFs fs.Filesystem, filePath string, tagger fileTagger)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- l.Debugln("archiving", filePath)
|
|
|
|
-
|
|
|
|
file := filepath.Base(filePath)
|
|
file := filepath.Base(filePath)
|
|
inFolderPath := filepath.Dir(filePath)
|
|
inFolderPath := filepath.Dir(filePath)
|
|
|
|
|
|
err = dstFs.MkdirAll(inFolderPath, 0755)
|
|
err = dstFs.MkdirAll(inFolderPath, 0755)
|
|
if err != nil && !fs.IsExist(err) {
|
|
if err != nil && !fs.IsExist(err) {
|
|
|
|
+ l.Debugln("archiving", filePath, err)
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- ver := tagger(file, info.ModTime().Format(TimeFormat))
|
|
|
|
|
|
+ now := time.Now()
|
|
|
|
+
|
|
|
|
+ ver := tagger(file, now.Format(TimeFormat))
|
|
dst := filepath.Join(inFolderPath, ver)
|
|
dst := filepath.Join(inFolderPath, ver)
|
|
- l.Debugln("moving to", dst)
|
|
|
|
|
|
+ l.Debugln("archiving", filePath, "moving to", dst)
|
|
err = osutil.RenameOrCopy(srcFs, dstFs, filePath, dst)
|
|
err = osutil.RenameOrCopy(srcFs, dstFs, filePath, dst)
|
|
|
|
|
|
- // Set the mtime to the time the file was deleted. This can be used by the
|
|
|
|
- // cleanout routine. If this fails things won't work optimally but there's
|
|
|
|
- // not much we can do about it so we ignore the error.
|
|
|
|
- _ = dstFs.Chtimes(dst, time.Now(), time.Now())
|
|
|
|
|
|
+ mtime := info.ModTime()
|
|
|
|
+ // If it's a trashcan versioner type thing, then it does not have version time in the name
|
|
|
|
+ // so use mtime for that.
|
|
|
|
+ if ver == file {
|
|
|
|
+ mtime = now
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _ = dstFs.Chtimes(dst, mtime, mtime)
|
|
|
|
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
func restoreFile(src, dst fs.Filesystem, filePath string, versionTime time.Time, tagger fileTagger) error {
|
|
func restoreFile(src, dst fs.Filesystem, filePath string, versionTime time.Time, tagger fileTagger) error {
|
|
|
|
+ tag := versionTime.In(time.Local).Truncate(time.Second).Format(TimeFormat)
|
|
|
|
+ taggedFilePath := tagger(filePath, tag)
|
|
|
|
+
|
|
// If the something already exists where we are restoring to, archive existing file for versioning
|
|
// If the something already exists where we are restoring to, archive existing file for versioning
|
|
// remove if it's a symlink, or fail if it's a directory
|
|
// remove if it's a symlink, or fail if it's a directory
|
|
if info, err := dst.Lstat(filePath); err == nil {
|
|
if info, err := dst.Lstat(filePath); err == nil {
|
|
@@ -203,28 +209,27 @@ func restoreFile(src, dst fs.Filesystem, filePath string, versionTime time.Time,
|
|
}
|
|
}
|
|
|
|
|
|
filePath = osutil.NativeFilename(filePath)
|
|
filePath = osutil.NativeFilename(filePath)
|
|
- tag := versionTime.In(time.Local).Truncate(time.Second).Format(TimeFormat)
|
|
|
|
-
|
|
|
|
- taggedFilename := TagFilename(filePath, tag)
|
|
|
|
- oldTaggedFilename := filePath + tag
|
|
|
|
- untaggedFileName := filePath
|
|
|
|
|
|
|
|
- // Check that the thing we've been asked to restore is actually a file
|
|
|
|
- // and that it exists.
|
|
|
|
|
|
+ // Try and find a file that has the correct mtime
|
|
sourceFile := ""
|
|
sourceFile := ""
|
|
- for _, candidate := range []string{taggedFilename, oldTaggedFilename, untaggedFileName} {
|
|
|
|
- if info, err := src.Lstat(candidate); fs.IsNotExist(err) || !info.IsRegular() {
|
|
|
|
- continue
|
|
|
|
- } else if err != nil {
|
|
|
|
- // All other errors are fatal
|
|
|
|
- return err
|
|
|
|
- } else if candidate == untaggedFileName && !info.ModTime().Truncate(time.Second).Equal(versionTime) {
|
|
|
|
- // No error, and untagged file, but mtime does not match, skip
|
|
|
|
- continue
|
|
|
|
|
|
+ sourceMtime := time.Time{}
|
|
|
|
+ if info, err := src.Lstat(taggedFilePath); err == nil && info.IsRegular() {
|
|
|
|
+ sourceFile = taggedFilePath
|
|
|
|
+ sourceMtime = info.ModTime()
|
|
|
|
+ } else if err == nil {
|
|
|
|
+ l.Debugln("restore:", taggedFilePath, "not regular")
|
|
|
|
+ } else {
|
|
|
|
+ l.Debugln("restore:", taggedFilePath, err.Error())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Check for untagged file
|
|
|
|
+ if sourceFile == "" {
|
|
|
|
+ info, err := src.Lstat(filePath)
|
|
|
|
+ if err == nil && info.IsRegular() && info.ModTime().Truncate(time.Second).Equal(versionTime) {
|
|
|
|
+ sourceFile = filePath
|
|
|
|
+ sourceMtime = info.ModTime()
|
|
}
|
|
}
|
|
|
|
|
|
- sourceFile = candidate
|
|
|
|
- break
|
|
|
|
}
|
|
}
|
|
|
|
|
|
if sourceFile == "" {
|
|
if sourceFile == "" {
|
|
@@ -240,7 +245,9 @@ func restoreFile(src, dst fs.Filesystem, filePath string, versionTime time.Time,
|
|
}
|
|
}
|
|
|
|
|
|
_ = dst.MkdirAll(filepath.Dir(filePath), 0755)
|
|
_ = dst.MkdirAll(filepath.Dir(filePath), 0755)
|
|
- return osutil.RenameOrCopy(src, dst, sourceFile, filePath)
|
|
|
|
|
|
+ err := osutil.RenameOrCopy(src, dst, sourceFile, filePath)
|
|
|
|
+ _ = dst.Chtimes(filePath, sourceMtime, sourceMtime)
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
func fsFromParams(folderFs fs.Filesystem, params map[string]string) (versionsFs fs.Filesystem) {
|
|
func fsFromParams(folderFs fs.Filesystem, params map[string]string) (versionsFs fs.Filesystem) {
|
|
@@ -260,33 +267,23 @@ func fsFromParams(folderFs fs.Filesystem, params map[string]string) (versionsFs
|
|
_ = fsType.UnmarshalText([]byte(params["fsType"]))
|
|
_ = fsType.UnmarshalText([]byte(params["fsType"]))
|
|
versionsFs = fs.NewFilesystem(fsType, params["fsPath"])
|
|
versionsFs = fs.NewFilesystem(fsType, params["fsPath"])
|
|
}
|
|
}
|
|
- l.Debugln("%s (%s) folder using %s (%s) versioner dir", folderFs.URI(), folderFs.Type(), versionsFs.URI(), versionsFs.Type())
|
|
|
|
|
|
+ l.Debugf("%s (%s) folder using %s (%s) versioner dir", folderFs.URI(), folderFs.Type(), versionsFs.URI(), versionsFs.Type())
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
-type versionWithMtime struct {
|
|
|
|
- name string
|
|
|
|
- mtime time.Time
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func versionsToVersionsWithMtime(fs fs.Filesystem, versions []string) []versionWithMtime {
|
|
|
|
- versionsWithMtimes := make([]versionWithMtime, 0, len(versions))
|
|
|
|
|
|
+func findAllVersions(fs fs.Filesystem, filePath string) []string {
|
|
|
|
+ inFolderPath := filepath.Dir(filePath)
|
|
|
|
+ file := filepath.Base(filePath)
|
|
|
|
|
|
- for _, version := range versions {
|
|
|
|
- if stat, err := fs.Stat(version); err != nil {
|
|
|
|
- // Welp, assume it's gone?
|
|
|
|
- continue
|
|
|
|
- } else {
|
|
|
|
- versionsWithMtimes = append(versionsWithMtimes, versionWithMtime{
|
|
|
|
- name: version,
|
|
|
|
- mtime: stat.ModTime(),
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
|
|
+ // Glob according to the new file~timestamp.ext pattern.
|
|
|
|
+ pattern := filepath.Join(inFolderPath, TagFilename(file, TimeGlob))
|
|
|
|
+ versions, err := fs.Glob(pattern)
|
|
|
|
+ if err != nil {
|
|
|
|
+ l.Warnln("globbing:", err, "for", pattern)
|
|
|
|
+ return nil
|
|
}
|
|
}
|
|
|
|
+ versions = util.UniqueTrimmedStrings(versions)
|
|
|
|
+ sort.Strings(versions)
|
|
|
|
|
|
- sort.Slice(versionsWithMtimes, func(i, j int) bool {
|
|
|
|
- return versionsWithMtimes[i].mtime.Before(versionsWithMtimes[j].mtime)
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- return versionsWithMtimes
|
|
|
|
|
|
+ return versions
|
|
}
|
|
}
|