mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-22 10:58:57 +00:00
lib/fs, lib/api, lib/model: Expose mtime remappings as part of /db/file (#7624)
* lib/fs, lib/api, lib/model: Expose mtime remappings as part of /db/file * Fix wrong error returned by CLI * Gofmt * Better names * Review comments * Review comments
This commit is contained in:
parent
f09dcb98eb
commit
87a0eecc31
@ -135,11 +135,11 @@ func (c *apiClient) Post(url, body string) (*http.Response, error) {
|
||||
}
|
||||
|
||||
func checkResponse(response *http.Response) error {
|
||||
if response.StatusCode == 404 {
|
||||
if response.StatusCode == http.StatusNotFound {
|
||||
return errors.New("invalid endpoint or API call")
|
||||
} else if response.StatusCode == 403 {
|
||||
} else if response.StatusCode == http.StatusUnauthorized {
|
||||
return errors.New("invalid API key")
|
||||
} else if response.StatusCode != 200 {
|
||||
} else if response.StatusCode != http.StatusOK {
|
||||
data, err := responseToBArray(response)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -32,7 +32,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
metrics "github.com/rcrowley/go-metrics"
|
||||
"github.com/rcrowley/go-metrics"
|
||||
"github.com/thejerf/suture/v4"
|
||||
"github.com/vitrun/qart/qr"
|
||||
"golang.org/x/text/runes"
|
||||
@ -915,11 +915,16 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
mtimeMapping, mtimeErr := s.model.GetMtimeMapping(folder, file)
|
||||
|
||||
sendJSON(w, map[string]interface{}{
|
||||
"global": jsonFileInfo(gf),
|
||||
"local": jsonFileInfo(lf),
|
||||
"availability": av,
|
||||
"mtime": map[string]interface{}{
|
||||
"err": mtimeErr,
|
||||
"value": mtimeMapping,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -934,6 +939,8 @@ func (s *service) getDebugFile(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
mtimeMapping, mtimeErr := s.model.GetMtimeMapping(folder, file)
|
||||
|
||||
lf, _ := snap.Get(protocol.LocalDeviceID, file)
|
||||
gf, _ := snap.GetGlobal(file)
|
||||
av := snap.Availability(file)
|
||||
@ -944,6 +951,10 @@ func (s *service) getDebugFile(w http.ResponseWriter, r *http.Request) {
|
||||
"local": jsonFileInfo(lf),
|
||||
"availability": av,
|
||||
"globalVersions": vl.String(),
|
||||
"mtime": map[string]interface{}{
|
||||
"err": mtimeErr,
|
||||
"value": mtimeMapping,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ const (
|
||||
// KeyTypeFolderStatistic <folder ID as string> <some string> = some value
|
||||
KeyTypeFolderStatistic byte = 4
|
||||
|
||||
// KeyTypeVirtualMtime <int32 folder ID> <file name> = dbMtime
|
||||
// KeyTypeVirtualMtime <int32 folder ID> <file name> = mtimeMapping
|
||||
KeyTypeVirtualMtime byte = 5
|
||||
|
||||
// KeyTypeFolderIdx <int32 id> = string value
|
||||
|
@ -330,6 +330,14 @@ func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
|
||||
return os.SameFile(f1.osFileInfo(), f2.osFileInfo())
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) underlying() (Filesystem, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) wrapperType() filesystemWrapperType {
|
||||
return filesystemWrapperTypeNone
|
||||
}
|
||||
|
||||
// basicFile implements the fs.File interface on top of an os.File
|
||||
type basicFile struct {
|
||||
*os.File
|
||||
|
@ -339,6 +339,14 @@ func (f *caseFilesystem) Unhide(name string) error {
|
||||
return f.Filesystem.Unhide(name)
|
||||
}
|
||||
|
||||
func (f *caseFilesystem) underlying() (Filesystem, bool) {
|
||||
return f.Filesystem, true
|
||||
}
|
||||
|
||||
func (f *caseFilesystem) wrapperType() filesystemWrapperType {
|
||||
return filesystemWrapperTypeCase
|
||||
}
|
||||
|
||||
func (f *caseFilesystem) checkCase(name string) error {
|
||||
var err error
|
||||
if name, err = Canonicalize(name); err != nil {
|
||||
|
@ -161,7 +161,10 @@ func BenchmarkWalkCaseFakeFS100k(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.Run("rawfs", func(b *testing.B) {
|
||||
fakefs := unwrapFilesystem(fsys).(*fakefs)
|
||||
var fakefs *fakeFS
|
||||
if ffs, ok := unwrapFilesystem(fsys, filesystemWrapperTypeNone); ok {
|
||||
fakefs = ffs.(*fakeFS)
|
||||
}
|
||||
fakefs.resetCounters()
|
||||
benchmarkWalkFakeFS(b, fsys, paths, 0, "")
|
||||
fakefs.reportMetricsPerOp(b)
|
||||
@ -174,7 +177,10 @@ func BenchmarkWalkCaseFakeFS100k(b *testing.B) {
|
||||
Filesystem: fsys,
|
||||
realCaser: newDefaultRealCaser(fsys),
|
||||
}
|
||||
fakefs := unwrapFilesystem(fsys).(*fakefs)
|
||||
var fakefs *fakeFS
|
||||
if ffs, ok := unwrapFilesystem(fsys, filesystemWrapperTypeNone); ok {
|
||||
fakefs = ffs.(*fakeFS)
|
||||
}
|
||||
fakefs.resetCounters()
|
||||
benchmarkWalkFakeFS(b, casefs, paths, 0, "")
|
||||
fakefs.reportMetricsPerOp(b)
|
||||
@ -197,7 +203,10 @@ func BenchmarkWalkCaseFakeFS100k(b *testing.B) {
|
||||
Filesystem: fsys,
|
||||
realCaser: newDefaultRealCaser(fsys),
|
||||
}
|
||||
fakefs := unwrapFilesystem(fsys).(*fakefs)
|
||||
var fakefs *fakeFS
|
||||
if ffs, ok := unwrapFilesystem(fsys, filesystemWrapperTypeNone); ok {
|
||||
fakefs = ffs.(*fakeFS)
|
||||
}
|
||||
fakefs.resetCounters()
|
||||
benchmarkWalkFakeFS(b, casefs, paths, otherOpEvery, otherOpPath)
|
||||
fakefs.reportMetricsPerOp(b)
|
||||
|
@ -18,7 +18,8 @@ import (
|
||||
// reason for existence is the Windows version, which allows creating
|
||||
// symlinks when non-elevated.
|
||||
func DebugSymlinkForTestsOnly(oldFs, newFs Filesystem, oldname, newname string) error {
|
||||
if caseFs, ok := unwrapFilesystem(newFs).(*caseFilesystem); ok {
|
||||
if fs, ok := unwrapFilesystem(newFs, filesystemWrapperTypeCase); ok {
|
||||
caseFs := fs.(*caseFilesystem)
|
||||
if err := caseFs.checkCase(newname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -52,3 +52,11 @@ func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
|
||||
func (fs *errorFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||
return nil, nil, fs.err
|
||||
}
|
||||
|
||||
func (fs *errorFilesystem) underlying() (Filesystem, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (fs *errorFilesystem) wrapperType() filesystemWrapperType {
|
||||
return filesystemWrapperTypeError
|
||||
}
|
||||
|
106
lib/fs/fakefs.go
106
lib/fs/fakefs.go
@ -27,7 +27,7 @@ import (
|
||||
// see readShortAt()
|
||||
const randomBlockShift = 14 // 128k
|
||||
|
||||
// fakefs is a fake filesystem for testing and benchmarking. It has the
|
||||
// fakeFS is a fake filesystem for testing and benchmarking. It has the
|
||||
// following properties:
|
||||
//
|
||||
// - File metadata is kept in RAM. Specifically, we remember which files and
|
||||
@ -37,7 +37,7 @@ const randomBlockShift = 14 // 128k
|
||||
// - File contents are generated pseudorandomly with just the file name as
|
||||
// seed. Writes are discarded, other than having the effect of increasing
|
||||
// the file size. If you only write data that you've read from a file with
|
||||
// the same name on a different fakefs, you'll never know the difference...
|
||||
// the same name on a different fakeFS, you'll never know the difference...
|
||||
//
|
||||
// - We totally ignore permissions - pretend you are root.
|
||||
//
|
||||
@ -51,10 +51,10 @@ const randomBlockShift = 14 // 128k
|
||||
// insens=b "true" makes filesystem case-insensitive Windows- or OSX-style (default false)
|
||||
// latency=d to set the amount of time each "disk" operation takes, where d is time.ParseDuration format
|
||||
//
|
||||
// - Two fakefs:s pointing at the same root path see the same files.
|
||||
// - Two fakeFS:s pointing at the same root path see the same files.
|
||||
//
|
||||
type fakefs struct {
|
||||
counters fakefsCounters
|
||||
type fakeFS struct {
|
||||
counters fakeFSCounters
|
||||
uri string
|
||||
mut sync.Mutex
|
||||
root *fakeEntry
|
||||
@ -63,7 +63,7 @@ type fakefs struct {
|
||||
latency time.Duration
|
||||
}
|
||||
|
||||
type fakefsCounters struct {
|
||||
type fakeFSCounters struct {
|
||||
Chmod int64
|
||||
Lchown int64
|
||||
Chtimes int64
|
||||
@ -81,13 +81,13 @@ type fakefsCounters struct {
|
||||
}
|
||||
|
||||
var (
|
||||
fakefsMut sync.Mutex
|
||||
fakefsFs = make(map[string]*fakefs)
|
||||
fakeFSMut sync.Mutex
|
||||
fakeFSCache = make(map[string]*fakeFS)
|
||||
)
|
||||
|
||||
func newFakeFilesystem(rootURI string, _ ...Option) *fakefs {
|
||||
fakefsMut.Lock()
|
||||
defer fakefsMut.Unlock()
|
||||
func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
|
||||
fakeFSMut.Lock()
|
||||
defer fakeFSMut.Unlock()
|
||||
|
||||
root := rootURI
|
||||
var params url.Values
|
||||
@ -97,12 +97,12 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakefs {
|
||||
params = uri.Query()
|
||||
}
|
||||
|
||||
if fs, ok := fakefsFs[rootURI]; ok {
|
||||
if fs, ok := fakeFSCache[rootURI]; ok {
|
||||
// Already have an fs at this path
|
||||
return fs
|
||||
}
|
||||
|
||||
fs := &fakefs{
|
||||
fs := &fakeFS{
|
||||
uri: "fake://" + rootURI,
|
||||
root: &fakeEntry{
|
||||
name: "/",
|
||||
@ -157,7 +157,7 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakefs {
|
||||
// the filesystem initially.
|
||||
fs.latency, _ = time.ParseDuration(params.Get("latency"))
|
||||
|
||||
fakefsFs[root] = fs
|
||||
fakeFSCache[root] = fs
|
||||
return fs
|
||||
}
|
||||
|
||||
@ -183,7 +183,7 @@ type fakeEntry struct {
|
||||
content []byte
|
||||
}
|
||||
|
||||
func (fs *fakefs) entryForName(name string) *fakeEntry {
|
||||
func (fs *fakeFS) entryForName(name string) *fakeEntry {
|
||||
// bug: lookup doesn't work through symlinks.
|
||||
if fs.insens {
|
||||
name = UnicodeLowercase(name)
|
||||
@ -210,7 +210,7 @@ func (fs *fakefs) entryForName(name string) *fakeEntry {
|
||||
return entry
|
||||
}
|
||||
|
||||
func (fs *fakefs) Chmod(name string, mode FileMode) error {
|
||||
func (fs *fakeFS) Chmod(name string, mode FileMode) error {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Chmod++
|
||||
@ -223,7 +223,7 @@ func (fs *fakefs) Chmod(name string, mode FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Lchown(name string, uid, gid int) error {
|
||||
func (fs *fakeFS) Lchown(name string, uid, gid int) error {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Lchown++
|
||||
@ -237,7 +237,7 @@ func (fs *fakefs) Lchown(name string, uid, gid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
func (fs *fakeFS) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Chtimes++
|
||||
@ -250,7 +250,7 @@ func (fs *fakefs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) create(name string) (*fakeEntry, error) {
|
||||
func (fs *fakeFS) create(name string) (*fakeEntry, error) {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Create++
|
||||
@ -296,7 +296,7 @@ func (fs *fakefs) create(name string) (*fakeEntry, error) {
|
||||
return new, nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Create(name string) (File, error) {
|
||||
func (fs *fakeFS) Create(name string) (File, error) {
|
||||
entry, err := fs.create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -307,7 +307,7 @@ func (fs *fakefs) Create(name string) (File, error) {
|
||||
return &fakeFile{fakeEntry: entry}, nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) CreateSymlink(target, name string) error {
|
||||
func (fs *fakeFS) CreateSymlink(target, name string) error {
|
||||
entry, err := fs.create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -317,7 +317,7 @@ func (fs *fakefs) CreateSymlink(target, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) DirNames(name string) ([]string, error) {
|
||||
func (fs *fakeFS) DirNames(name string) ([]string, error) {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.DirNames++
|
||||
@ -336,7 +336,7 @@ func (fs *fakefs) DirNames(name string) ([]string, error) {
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Lstat(name string) (FileInfo, error) {
|
||||
func (fs *fakeFS) Lstat(name string) (FileInfo, error) {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Lstat++
|
||||
@ -355,7 +355,7 @@ func (fs *fakefs) Lstat(name string) (FileInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Mkdir(name string, perm FileMode) error {
|
||||
func (fs *fakeFS) Mkdir(name string, perm FileMode) error {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Mkdir++
|
||||
@ -389,7 +389,7 @@ func (fs *fakefs) Mkdir(name string, perm FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) MkdirAll(name string, perm FileMode) error {
|
||||
func (fs *fakeFS) MkdirAll(name string, perm FileMode) error {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.MkdirAll++
|
||||
@ -426,7 +426,7 @@ func (fs *fakefs) MkdirAll(name string, perm FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Open(name string) (File, error) {
|
||||
func (fs *fakeFS) Open(name string) (File, error) {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Open++
|
||||
@ -443,7 +443,7 @@ func (fs *fakefs) Open(name string) (File, error) {
|
||||
return &fakeFile{fakeEntry: entry}, nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) OpenFile(name string, flags int, mode FileMode) (File, error) {
|
||||
func (fs *fakeFS) OpenFile(name string, flags int, mode FileMode) (File, error) {
|
||||
if flags&os.O_CREATE == 0 {
|
||||
return fs.Open(name)
|
||||
}
|
||||
@ -486,7 +486,7 @@ func (fs *fakefs) OpenFile(name string, flags int, mode FileMode) (File, error)
|
||||
return &fakeFile{fakeEntry: newEntry}, nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) ReadSymlink(name string) (string, error) {
|
||||
func (fs *fakeFS) ReadSymlink(name string) (string, error) {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.ReadSymlink++
|
||||
@ -501,7 +501,7 @@ func (fs *fakefs) ReadSymlink(name string) (string, error) {
|
||||
return entry.dest, nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Remove(name string) error {
|
||||
func (fs *fakeFS) Remove(name string) error {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Remove++
|
||||
@ -524,7 +524,7 @@ func (fs *fakefs) Remove(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) RemoveAll(name string) error {
|
||||
func (fs *fakeFS) RemoveAll(name string) error {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.RemoveAll++
|
||||
@ -536,7 +536,7 @@ func (fs *fakefs) RemoveAll(name string) error {
|
||||
|
||||
entry := fs.entryForName(filepath.Dir(name))
|
||||
if entry == nil {
|
||||
return nil // all tested real systems exibit this behaviour
|
||||
return nil // all tested real systems exhibit this behaviour
|
||||
}
|
||||
|
||||
// RemoveAll is easy when the file system uses garbage collection under
|
||||
@ -545,7 +545,7 @@ func (fs *fakefs) RemoveAll(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Rename(oldname, newname string) error {
|
||||
func (fs *fakeFS) Rename(oldname, newname string) error {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
fs.counters.Rename++
|
||||
@ -595,56 +595,56 @@ func (fs *fakefs) Rename(oldname, newname string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Stat(name string) (FileInfo, error) {
|
||||
func (fs *fakeFS) Stat(name string) (FileInfo, error) {
|
||||
return fs.Lstat(name)
|
||||
}
|
||||
|
||||
func (fs *fakefs) SymlinksSupported() bool {
|
||||
func (fs *fakeFS) SymlinksSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (fs *fakefs) Walk(name string, walkFn WalkFunc) error {
|
||||
func (fs *fakeFS) Walk(name string, walkFn WalkFunc) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (fs *fakefs) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||
func (fs *fakeFS) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||
return nil, nil, ErrWatchNotSupported
|
||||
}
|
||||
|
||||
func (fs *fakefs) Hide(name string) error {
|
||||
func (fs *fakeFS) Hide(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Unhide(name string) error {
|
||||
func (fs *fakeFS) Unhide(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Glob(pattern string) ([]string, error) {
|
||||
func (fs *fakeFS) Glob(pattern string) ([]string, error) {
|
||||
// gnnh we don't seem to actually require this in practice
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (fs *fakefs) Roots() ([]string, error) {
|
||||
func (fs *fakeFS) Roots() ([]string, error) {
|
||||
return []string{"/"}, nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) Usage(name string) (Usage, error) {
|
||||
func (fs *fakeFS) Usage(name string) (Usage, error) {
|
||||
return Usage{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (fs *fakefs) Type() FilesystemType {
|
||||
func (fs *fakeFS) Type() FilesystemType {
|
||||
return FilesystemTypeFake
|
||||
}
|
||||
|
||||
func (fs *fakefs) URI() string {
|
||||
func (fs *fakeFS) URI() string {
|
||||
return fs.uri
|
||||
}
|
||||
|
||||
func (fs *fakefs) Options() []Option {
|
||||
func (fs *fakeFS) Options() []Option {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *fakefs) SameFile(fi1, fi2 FileInfo) bool {
|
||||
func (fs *fakeFS) SameFile(fi1, fi2 FileInfo) bool {
|
||||
// BUG: real systems base file sameness on path, inodes, etc
|
||||
// we try our best, but FileInfo just doesn't have enough data
|
||||
// so there be false positives, especially on Windows
|
||||
@ -659,17 +659,25 @@ func (fs *fakefs) SameFile(fi1, fi2 FileInfo) bool {
|
||||
return ok && fi1.ModTime().Equal(fi2.ModTime()) && fi1.Mode() == fi2.Mode() && fi1.IsDir() == fi2.IsDir() && fi1.IsRegular() == fi2.IsRegular() && fi1.IsSymlink() == fi2.IsSymlink() && fi1.Owner() == fi2.Owner() && fi1.Group() == fi2.Group()
|
||||
}
|
||||
|
||||
func (fs *fakefs) resetCounters() {
|
||||
func (fs *fakeFS) underlying() (Filesystem, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (fs *fakeFS) wrapperType() filesystemWrapperType {
|
||||
return filesystemWrapperTypeNone
|
||||
}
|
||||
|
||||
func (fs *fakeFS) resetCounters() {
|
||||
fs.mut.Lock()
|
||||
fs.counters = fakefsCounters{}
|
||||
fs.counters = fakeFSCounters{}
|
||||
fs.mut.Unlock()
|
||||
}
|
||||
|
||||
func (fs *fakefs) reportMetricsPerOp(b *testing.B) {
|
||||
func (fs *fakeFS) reportMetricsPerOp(b *testing.B) {
|
||||
fs.reportMetricsPer(b, 1, "op")
|
||||
}
|
||||
|
||||
func (fs *fakefs) reportMetricsPer(b *testing.B, divisor float64, unit string) {
|
||||
func (fs *fakeFS) reportMetricsPer(b *testing.B, divisor float64, unit string) {
|
||||
fs.mut.Lock()
|
||||
defer fs.mut.Unlock()
|
||||
b.ReportMetric(float64(fs.counters.Lstat)/divisor/float64(b.N), "Lstat/"+unit)
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
func TestFakeFS(t *testing.T) {
|
||||
// Test some basic aspects of the fakefs
|
||||
// Test some basic aspects of the fakeFS
|
||||
|
||||
fs := newFakeFilesystem("/foo/bar/baz")
|
||||
|
||||
@ -131,7 +131,7 @@ func TestFakeFS(t *testing.T) {
|
||||
}
|
||||
|
||||
func testFakeFSRead(t *testing.T, fs Filesystem) {
|
||||
// Test some basic aspects of the fakefs
|
||||
// Test some basic aspects of the fakeFS
|
||||
// Create
|
||||
fd, _ := fs.Create("test")
|
||||
defer fd.Close()
|
||||
@ -201,7 +201,7 @@ func TestFakeFSCaseSensitive(t *testing.T) {
|
||||
{"FileName", testFakeFSFileName},
|
||||
}
|
||||
var filesystems = []testFS{
|
||||
{"fakefs", newFakeFilesystem("/foo")},
|
||||
{"fakeFS", newFakeFilesystem("/foo")},
|
||||
}
|
||||
|
||||
testDir, sensitive := createTestDir(t)
|
||||
@ -237,7 +237,7 @@ func TestFakeFSCaseInsensitive(t *testing.T) {
|
||||
}
|
||||
|
||||
var filesystems = []testFS{
|
||||
{"fakefs", newFakeFilesystem("/foobar?insens=true")},
|
||||
{"fakeFS", newFakeFilesystem("/foobar?insens=true")},
|
||||
}
|
||||
|
||||
testDir, sensitive := createTestDir(t)
|
||||
@ -891,7 +891,7 @@ func testFakeFSCreateInsens(t *testing.T, fs Filesystem) {
|
||||
t.Errorf("name of created file \"fOo\" is %s", fd2.Name())
|
||||
}
|
||||
|
||||
// one would expect DirNames to show the last variant, but in fact it shows
|
||||
// one would expect DirNames to show the last wrapperType, but in fact it shows
|
||||
// the original one
|
||||
assertDir(t, fs, "/", []string{"FOO"})
|
||||
}
|
||||
|
@ -16,6 +16,17 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type filesystemWrapperType int32
|
||||
|
||||
const (
|
||||
filesystemWrapperTypeNone filesystemWrapperType = iota
|
||||
filesystemWrapperTypeMtime
|
||||
filesystemWrapperTypeCase
|
||||
filesystemWrapperTypeError
|
||||
filesystemWrapperTypeWalk
|
||||
filesystemWrapperTypeLog
|
||||
)
|
||||
|
||||
// The Filesystem interface abstracts access to the file system.
|
||||
type Filesystem interface {
|
||||
Chmod(name string, mode FileMode) error
|
||||
@ -49,6 +60,10 @@ type Filesystem interface {
|
||||
URI() string
|
||||
Options() []Option
|
||||
SameFile(fi1, fi2 FileInfo) bool
|
||||
|
||||
// Used for unwrapping things
|
||||
underlying() (Filesystem, bool)
|
||||
wrapperType() filesystemWrapperType
|
||||
}
|
||||
|
||||
// The File interface abstracts access to a regular file, being a somewhat
|
||||
@ -284,18 +299,16 @@ func wrapFilesystem(fs Filesystem, wrapFn func(Filesystem) Filesystem) Filesyste
|
||||
return fs
|
||||
}
|
||||
|
||||
// unwrapFilesystem removes "wrapping" filesystems to expose the underlying filesystem.
|
||||
func unwrapFilesystem(fs Filesystem) Filesystem {
|
||||
// unwrapFilesystem removes "wrapping" filesystems to expose the filesystem of the requested wrapperType, if it exists.
|
||||
func unwrapFilesystem(fs Filesystem, wrapperType filesystemWrapperType) (Filesystem, bool) {
|
||||
var ok bool
|
||||
for {
|
||||
switch sfs := fs.(type) {
|
||||
case *logFilesystem:
|
||||
fs = sfs.Filesystem
|
||||
case *walkFilesystem:
|
||||
fs = sfs.Filesystem
|
||||
case *mtimeFS:
|
||||
fs = sfs.Filesystem
|
||||
default:
|
||||
return sfs
|
||||
if fs.wrapperType() == wrapperType {
|
||||
return fs, true
|
||||
}
|
||||
fs, ok = fs.underlying()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,3 +163,11 @@ func (fs *logFilesystem) Usage(name string) (Usage, error) {
|
||||
l.Debugln(getCaller(), fs.Type(), fs.URI(), "Usage", name, usage, err)
|
||||
return usage, err
|
||||
}
|
||||
|
||||
func (fs *logFilesystem) underlying() (Filesystem, bool) {
|
||||
return fs.Filesystem, true
|
||||
}
|
||||
|
||||
func (fs *logFilesystem) wrapperType() filesystemWrapperType {
|
||||
return filesystemWrapperTypeLog
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -69,14 +70,14 @@ func (f *mtimeFS) Stat(name string) (FileInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
real, virtual, err := f.load(name)
|
||||
mtimeMapping, err := f.load(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if real == info.ModTime() {
|
||||
if mtimeMapping.Real == info.ModTime() {
|
||||
info = mtimeFileInfo{
|
||||
FileInfo: info,
|
||||
mtime: virtual,
|
||||
mtime: mtimeMapping.Virtual,
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,14 +90,14 @@ func (f *mtimeFS) Lstat(name string) (FileInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
real, virtual, err := f.load(name)
|
||||
mtimeMapping, err := f.load(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if real == info.ModTime() {
|
||||
if mtimeMapping.Real == info.ModTime() {
|
||||
info = mtimeFileInfo{
|
||||
FileInfo: info,
|
||||
mtime: virtual,
|
||||
mtime: mtimeMapping.Virtual,
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,15 +107,15 @@ func (f *mtimeFS) Lstat(name string) (FileInfo, error) {
|
||||
func (f *mtimeFS) Walk(root string, walkFn WalkFunc) error {
|
||||
return f.Filesystem.Walk(root, func(path string, info FileInfo, err error) error {
|
||||
if info != nil {
|
||||
real, virtual, loadErr := f.load(path)
|
||||
mtimeMapping, loadErr := f.load(path)
|
||||
if loadErr != nil && err == nil {
|
||||
// The iterator gets to deal with the error
|
||||
err = loadErr
|
||||
}
|
||||
if real == info.ModTime() {
|
||||
if mtimeMapping.Real == info.ModTime() {
|
||||
info = mtimeFileInfo{
|
||||
FileInfo: info,
|
||||
mtime: virtual,
|
||||
mtime: mtimeMapping.Virtual,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -146,8 +147,13 @@ func (f *mtimeFS) OpenFile(name string, flags int, mode FileMode) (File, error)
|
||||
return mtimeFile{fd, f}, nil
|
||||
}
|
||||
|
||||
// "real" is the on disk timestamp
|
||||
// "virtual" is what want the timestamp to be
|
||||
func (f *mtimeFS) underlying() (Filesystem, bool) {
|
||||
return f.Filesystem, true
|
||||
}
|
||||
|
||||
func (f *mtimeFS) wrapperType() filesystemWrapperType {
|
||||
return filesystemWrapperTypeMtime
|
||||
}
|
||||
|
||||
func (f *mtimeFS) save(name string, real, virtual time.Time) {
|
||||
if f.caseInsensitive {
|
||||
@ -161,32 +167,31 @@ func (f *mtimeFS) save(name string, real, virtual time.Time) {
|
||||
return
|
||||
}
|
||||
|
||||
mtime := dbMtime{
|
||||
real: real,
|
||||
virtual: virtual,
|
||||
mtime := MtimeMapping{
|
||||
Real: real,
|
||||
Virtual: virtual,
|
||||
}
|
||||
bs, _ := mtime.Marshal() // Can't fail
|
||||
f.db.PutBytes(name, bs)
|
||||
}
|
||||
|
||||
func (f *mtimeFS) load(name string) (real, virtual time.Time, err error) {
|
||||
func (f *mtimeFS) load(name string) (MtimeMapping, error) {
|
||||
if f.caseInsensitive {
|
||||
name = UnicodeLowercase(name)
|
||||
}
|
||||
|
||||
data, exists, err := f.db.Bytes(name)
|
||||
if err != nil {
|
||||
return time.Time{}, time.Time{}, err
|
||||
return MtimeMapping{}, err
|
||||
} else if !exists {
|
||||
return time.Time{}, time.Time{}, nil
|
||||
return MtimeMapping{}, nil
|
||||
}
|
||||
|
||||
var mtime dbMtime
|
||||
var mtime MtimeMapping
|
||||
if err := mtime.Unmarshal(data); err != nil {
|
||||
return time.Time{}, time.Time{}, err
|
||||
return MtimeMapping{}, err
|
||||
}
|
||||
|
||||
return mtime.real, mtime.virtual, nil
|
||||
return mtime, nil
|
||||
}
|
||||
|
||||
// The mtimeFileInfo is an os.FileInfo that lies about the ModTime().
|
||||
@ -211,43 +216,57 @@ func (f mtimeFile) Stat() (FileInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
real, virtual, err := f.fs.load(f.Name())
|
||||
mtimeMapping, err := f.fs.load(f.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if real == info.ModTime() {
|
||||
if mtimeMapping.Real == info.ModTime() {
|
||||
info = mtimeFileInfo{
|
||||
FileInfo: info,
|
||||
mtime: virtual,
|
||||
mtime: mtimeMapping.Virtual,
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Used by copyRange to unwrap to the real file and access SyscallConn
|
||||
func (f mtimeFile) unwrap() File {
|
||||
return f.File
|
||||
}
|
||||
|
||||
// The dbMtime is our database representation
|
||||
|
||||
type dbMtime struct {
|
||||
real time.Time
|
||||
virtual time.Time
|
||||
// MtimeMapping represents the mapping as stored in the database
|
||||
type MtimeMapping struct {
|
||||
// "Real" is the on disk timestamp
|
||||
Real time.Time `json:"real"`
|
||||
// "Virtual" is what want the timestamp to be
|
||||
Virtual time.Time `json:"virtual"`
|
||||
}
|
||||
|
||||
func (t *dbMtime) Marshal() ([]byte, error) {
|
||||
bs0, _ := t.real.MarshalBinary()
|
||||
bs1, _ := t.virtual.MarshalBinary()
|
||||
func (t *MtimeMapping) Marshal() ([]byte, error) {
|
||||
bs0, _ := t.Real.MarshalBinary()
|
||||
bs1, _ := t.Virtual.MarshalBinary()
|
||||
return append(bs0, bs1...), nil
|
||||
}
|
||||
|
||||
func (t *dbMtime) Unmarshal(bs []byte) error {
|
||||
if err := t.real.UnmarshalBinary(bs[:len(bs)/2]); err != nil {
|
||||
func (t *MtimeMapping) Unmarshal(bs []byte) error {
|
||||
if err := t.Real.UnmarshalBinary(bs[:len(bs)/2]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.virtual.UnmarshalBinary(bs[len(bs)/2:]); err != nil {
|
||||
if err := t.Virtual.UnmarshalBinary(bs[len(bs)/2:]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMtimeMapping(fs Filesystem, file string) (MtimeMapping, error) {
|
||||
fs, ok := unwrapFilesystem(fs, filesystemWrapperTypeMtime)
|
||||
if !ok {
|
||||
return MtimeMapping{}, errors.New("failed to unwrap")
|
||||
}
|
||||
mtimeFs, ok := fs.(*mtimeFS)
|
||||
if !ok {
|
||||
return MtimeMapping{}, errors.New("unwrapping failed")
|
||||
}
|
||||
return mtimeFs.load(file)
|
||||
}
|
||||
|
@ -149,3 +149,11 @@ func (f *walkFilesystem) Walk(root string, walkFn WalkFunc) error {
|
||||
}
|
||||
return f.walk(root, info, walkFn, ancestors)
|
||||
}
|
||||
|
||||
func (f *walkFilesystem) underlying() (Filesystem, bool) {
|
||||
return f.Filesystem, true
|
||||
}
|
||||
|
||||
func (f *walkFilesystem) wrapperType() filesystemWrapperType {
|
||||
return filesystemWrapperTypeWalk
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/stats"
|
||||
@ -249,6 +250,20 @@ type Model struct {
|
||||
getHelloReturnsOnCall map[int]struct {
|
||||
result1 protocol.HelloIntf
|
||||
}
|
||||
GetMtimeMappingStub func(string, string) (fs.MtimeMapping, error)
|
||||
getMtimeMappingMutex sync.RWMutex
|
||||
getMtimeMappingArgsForCall []struct {
|
||||
arg1 string
|
||||
arg2 string
|
||||
}
|
||||
getMtimeMappingReturns struct {
|
||||
result1 fs.MtimeMapping
|
||||
result2 error
|
||||
}
|
||||
getMtimeMappingReturnsOnCall map[int]struct {
|
||||
result1 fs.MtimeMapping
|
||||
result2 error
|
||||
}
|
||||
GlobalDirectoryTreeStub func(string, string, int, bool) ([]*model.TreeEntry, error)
|
||||
globalDirectoryTreeMutex sync.RWMutex
|
||||
globalDirectoryTreeArgsForCall []struct {
|
||||
@ -1691,6 +1706,71 @@ func (fake *Model) GetHelloReturnsOnCall(i int, result1 protocol.HelloIntf) {
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Model) GetMtimeMapping(arg1 string, arg2 string) (fs.MtimeMapping, error) {
|
||||
fake.getMtimeMappingMutex.Lock()
|
||||
ret, specificReturn := fake.getMtimeMappingReturnsOnCall[len(fake.getMtimeMappingArgsForCall)]
|
||||
fake.getMtimeMappingArgsForCall = append(fake.getMtimeMappingArgsForCall, struct {
|
||||
arg1 string
|
||||
arg2 string
|
||||
}{arg1, arg2})
|
||||
stub := fake.GetMtimeMappingStub
|
||||
fakeReturns := fake.getMtimeMappingReturns
|
||||
fake.recordInvocation("GetMtimeMapping", []interface{}{arg1, arg2})
|
||||
fake.getMtimeMappingMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
}
|
||||
|
||||
func (fake *Model) GetMtimeMappingCallCount() int {
|
||||
fake.getMtimeMappingMutex.RLock()
|
||||
defer fake.getMtimeMappingMutex.RUnlock()
|
||||
return len(fake.getMtimeMappingArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *Model) GetMtimeMappingCalls(stub func(string, string) (fs.MtimeMapping, error)) {
|
||||
fake.getMtimeMappingMutex.Lock()
|
||||
defer fake.getMtimeMappingMutex.Unlock()
|
||||
fake.GetMtimeMappingStub = stub
|
||||
}
|
||||
|
||||
func (fake *Model) GetMtimeMappingArgsForCall(i int) (string, string) {
|
||||
fake.getMtimeMappingMutex.RLock()
|
||||
defer fake.getMtimeMappingMutex.RUnlock()
|
||||
argsForCall := fake.getMtimeMappingArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2
|
||||
}
|
||||
|
||||
func (fake *Model) GetMtimeMappingReturns(result1 fs.MtimeMapping, result2 error) {
|
||||
fake.getMtimeMappingMutex.Lock()
|
||||
defer fake.getMtimeMappingMutex.Unlock()
|
||||
fake.GetMtimeMappingStub = nil
|
||||
fake.getMtimeMappingReturns = struct {
|
||||
result1 fs.MtimeMapping
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *Model) GetMtimeMappingReturnsOnCall(i int, result1 fs.MtimeMapping, result2 error) {
|
||||
fake.getMtimeMappingMutex.Lock()
|
||||
defer fake.getMtimeMappingMutex.Unlock()
|
||||
fake.GetMtimeMappingStub = nil
|
||||
if fake.getMtimeMappingReturnsOnCall == nil {
|
||||
fake.getMtimeMappingReturnsOnCall = make(map[int]struct {
|
||||
result1 fs.MtimeMapping
|
||||
result2 error
|
||||
})
|
||||
}
|
||||
fake.getMtimeMappingReturnsOnCall[i] = struct {
|
||||
result1 fs.MtimeMapping
|
||||
result2 error
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *Model) GlobalDirectoryTree(arg1 string, arg2 string, arg3 int, arg4 bool) ([]*model.TreeEntry, error) {
|
||||
fake.globalDirectoryTreeMutex.Lock()
|
||||
ret, specificReturn := fake.globalDirectoryTreeReturnsOnCall[len(fake.globalDirectoryTreeArgsForCall)]
|
||||
@ -3186,6 +3266,8 @@ func (fake *Model) Invocations() map[string][][]interface{} {
|
||||
defer fake.getFolderVersionsMutex.RUnlock()
|
||||
fake.getHelloMutex.RLock()
|
||||
defer fake.getHelloMutex.RUnlock()
|
||||
fake.getMtimeMappingMutex.RLock()
|
||||
defer fake.getMtimeMappingMutex.RUnlock()
|
||||
fake.globalDirectoryTreeMutex.RLock()
|
||||
defer fake.globalDirectoryTreeMutex.RUnlock()
|
||||
fake.indexMutex.RLock()
|
||||
|
@ -95,6 +95,7 @@ type Model interface {
|
||||
|
||||
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool, error)
|
||||
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool, error)
|
||||
GetMtimeMapping(folder string, file string) (fs.MtimeMapping, error)
|
||||
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) ([]Availability, error)
|
||||
|
||||
Completion(device protocol.DeviceID, folder string) (FolderCompletion, error)
|
||||
@ -2038,12 +2039,12 @@ func (m *model) CurrentFolderFile(folder string, file string) (protocol.FileInfo
|
||||
|
||||
func (m *model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) {
|
||||
m.fmut.RLock()
|
||||
fs, ok := m.folderFiles[folder]
|
||||
ffs, ok := m.folderFiles[folder]
|
||||
m.fmut.RUnlock()
|
||||
if !ok {
|
||||
return protocol.FileInfo{}, false, ErrFolderMissing
|
||||
}
|
||||
snap, err := fs.Snapshot()
|
||||
snap, err := ffs.Snapshot()
|
||||
if err != nil {
|
||||
return protocol.FileInfo{}, false, err
|
||||
}
|
||||
@ -2052,6 +2053,16 @@ func (m *model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo
|
||||
return f, ok, nil
|
||||
}
|
||||
|
||||
func (m *model) GetMtimeMapping(folder string, file string) (fs.MtimeMapping, error) {
|
||||
m.fmut.RLock()
|
||||
ffs, ok := m.folderFiles[folder]
|
||||
m.fmut.RUnlock()
|
||||
if !ok {
|
||||
return fs.MtimeMapping{}, ErrFolderMissing
|
||||
}
|
||||
return fs.GetMtimeMapping(ffs.MtimeFS(), file)
|
||||
}
|
||||
|
||||
// Connection returns the current connection for device, and a boolean whether a connection was found.
|
||||
func (m *model) Connection(deviceID protocol.DeviceID) (protocol.Connection, bool) {
|
||||
m.pmut.RLock()
|
||||
|
Loading…
Reference in New Issue
Block a user