committed by
Jakob Borg
parent
bf744ded31
commit
afde0727fe
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
modTime := f.ModTime().Truncate(time.Second)
|
||||
|
||||
path = osutil.NormalizedFilename(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 == "" {
|
||||
versionTime := f.ModTime().Truncate(time.Second)
|
||||
files[path] = append(files[path], FileVersion{
|
||||
VersionTime: versionTime,
|
||||
ModTime: versionTime,
|
||||
VersionTime: modTime,
|
||||
ModTime: modTime,
|
||||
Size: f.Size(),
|
||||
})
|
||||
return nil
|
||||
@@ -107,15 +109,11 @@ func retrieveVersions(fileSystem fs.Filesystem) (map[string][]FileVersion, error
|
||||
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
|
||||
})
|
||||
@@ -156,30 +154,38 @@ func archiveFile(srcFs, dstFs fs.Filesystem, filePath string, tagger fileTagger)
|
||||
}
|
||||
}
|
||||
|
||||
l.Debugln("archiving", filePath)
|
||||
|
||||
file := filepath.Base(filePath)
|
||||
inFolderPath := filepath.Dir(filePath)
|
||||
|
||||
err = dstFs.MkdirAll(inFolderPath, 0755)
|
||||
if err != nil && !fs.IsExist(err) {
|
||||
l.Debugln("archiving", filePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
ver := tagger(file, info.ModTime().Format(TimeFormat))
|
||||
now := time.Now()
|
||||
|
||||
ver := tagger(file, now.Format(TimeFormat))
|
||||
dst := filepath.Join(inFolderPath, ver)
|
||||
l.Debugln("moving to", dst)
|
||||
l.Debugln("archiving", filePath, "moving to", 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
|
||||
}
|
||||
|
||||
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
|
||||
// remove if it's a symlink, or fail if it's a directory
|
||||
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)
|
||||
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 := ""
|
||||
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 == "" {
|
||||
@@ -240,7 +245,9 @@ func restoreFile(src, dst fs.Filesystem, filePath string, versionTime time.Time,
|
||||
}
|
||||
|
||||
_ = 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) {
|
||||
@@ -260,33 +267,23 @@ func fsFromParams(folderFs fs.Filesystem, params map[string]string) (versionsFs
|
||||
_ = fsType.UnmarshalText([]byte(params["fsType"]))
|
||||
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
|
||||
}
|
||||
|
||||
type versionWithMtime struct {
|
||||
name string
|
||||
mtime time.Time
|
||||
}
|
||||
func findAllVersions(fs fs.Filesystem, filePath string) []string {
|
||||
inFolderPath := filepath.Dir(filePath)
|
||||
file := filepath.Base(filePath)
|
||||
|
||||
func versionsToVersionsWithMtime(fs fs.Filesystem, versions []string) []versionWithMtime {
|
||||
versionsWithMtimes := make([]versionWithMtime, 0, len(versions))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user