mirror of
https://github.com/rclone/rclone
synced 2025-03-20 01:44:24 +01:00
fs: add ChangeNotify and backend support for it (#2094)
* fs: rename DirChangeNotify to ChangeNotify * cache: switch to ChangeNotify * ChangeNotify: keep order of notifications
This commit is contained in:
parent
b3f55d6bda
commit
70f07fd3ac
@ -19,7 +19,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -1207,20 +1206,19 @@ func (o *Object) MimeType() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirChangeNotify polls for changes from the remote and hands the path to the
|
// ChangeNotify calls the passed function with a path that has had changes.
|
||||||
// given function. Only changes that can be resolved to a path through the
|
// If the implementation uses polling, it should adhere to the given interval.
|
||||||
// DirCache will handled.
|
|
||||||
//
|
//
|
||||||
// Automatically restarts itself in case of unexpected behaviour of the remote.
|
// Automatically restarts itself in case of unexpected behaviour of the remote.
|
||||||
//
|
//
|
||||||
// Close the returned channel to stop being notified.
|
// Close the returned channel to stop being notified.
|
||||||
func (f *Fs) DirChangeNotify(notifyFunc func(string), pollInterval time.Duration) chan bool {
|
func (f *Fs) ChangeNotify(notifyFunc func(string, fs.EntryType), pollInterval time.Duration) chan bool {
|
||||||
checkpoint := config.FileGet(f.name, "checkpoint")
|
checkpoint := config.FileGet(f.name, "checkpoint")
|
||||||
|
|
||||||
quit := make(chan bool)
|
quit := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
checkpoint = f.dirchangeNotifyRunner(notifyFunc, checkpoint)
|
checkpoint = f.changeNotifyRunner(notifyFunc, checkpoint)
|
||||||
if err := config.SetValueAndSave(f.name, "checkpoint", checkpoint); err != nil {
|
if err := config.SetValueAndSave(f.name, "checkpoint", checkpoint); err != nil {
|
||||||
fs.Debugf(f, "Unable to save checkpoint: %v", err)
|
fs.Debugf(f, "Unable to save checkpoint: %v", err)
|
||||||
}
|
}
|
||||||
@ -1234,7 +1232,7 @@ func (f *Fs) DirChangeNotify(notifyFunc func(string), pollInterval time.Duration
|
|||||||
return quit
|
return quit
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fs) dirchangeNotifyRunner(notifyFunc func(string), checkpoint string) string {
|
func (f *Fs) changeNotifyRunner(notifyFunc func(string, fs.EntryType), checkpoint string) string {
|
||||||
var err error
|
var err error
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
var reachedEnd bool
|
var reachedEnd bool
|
||||||
@ -1251,7 +1249,11 @@ func (f *Fs) dirchangeNotifyRunner(notifyFunc func(string), checkpoint string) s
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pathsToClear := make([]string, 0)
|
type entryType struct {
|
||||||
|
path string
|
||||||
|
entryType fs.EntryType
|
||||||
|
}
|
||||||
|
var pathsToClear []entryType
|
||||||
csCount++
|
csCount++
|
||||||
nodeCount += len(changeSet.Nodes)
|
nodeCount += len(changeSet.Nodes)
|
||||||
if changeSet.End {
|
if changeSet.End {
|
||||||
@ -1262,20 +1264,40 @@ func (f *Fs) dirchangeNotifyRunner(notifyFunc func(string), checkpoint string) s
|
|||||||
}
|
}
|
||||||
for _, node := range changeSet.Nodes {
|
for _, node := range changeSet.Nodes {
|
||||||
if path, ok := f.dirCache.GetInv(*node.Id); ok {
|
if path, ok := f.dirCache.GetInv(*node.Id); ok {
|
||||||
pathsToClear = append(pathsToClear, path)
|
if node.IsFile() {
|
||||||
|
pathsToClear = append(pathsToClear, entryType{path: path, entryType: fs.EntryObject})
|
||||||
|
} else {
|
||||||
|
pathsToClear = append(pathsToClear, entryType{path: path, entryType: fs.EntryDirectory})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.IsFile() {
|
||||||
|
// translate the parent dir of this object
|
||||||
|
if len(node.Parents) > 0 {
|
||||||
|
if path, ok := f.dirCache.GetInv(node.Parents[0]); ok {
|
||||||
|
// and append the drive file name to compute the full file name
|
||||||
|
if len(path) > 0 {
|
||||||
|
path = path + "/" + *node.Name
|
||||||
|
} else {
|
||||||
|
path = *node.Name
|
||||||
|
}
|
||||||
|
// this will now clear the actual file too
|
||||||
|
pathsToClear = append(pathsToClear, entryType{path: path, entryType: fs.EntryObject})
|
||||||
|
}
|
||||||
|
} else { // a true root object that is changed
|
||||||
|
pathsToClear = append(pathsToClear, entryType{path: *node.Name, entryType: fs.EntryObject})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notified := false
|
visitedPaths := make(map[string]bool)
|
||||||
lastNotifiedPath := ""
|
for _, entry := range pathsToClear {
|
||||||
sort.Strings(pathsToClear)
|
if _, ok := visitedPaths[entry.path]; ok {
|
||||||
for _, path := range pathsToClear {
|
|
||||||
if notified && strings.HasPrefix(path+"/", lastNotifiedPath+"/") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastNotifiedPath = path
|
visitedPaths[entry.path] = true
|
||||||
notified = true
|
notifyFunc(entry.path, entry.entryType)
|
||||||
notifyFunc(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -1303,10 +1325,10 @@ var (
|
|||||||
_ fs.Fs = (*Fs)(nil)
|
_ fs.Fs = (*Fs)(nil)
|
||||||
_ fs.Purger = (*Fs)(nil)
|
_ fs.Purger = (*Fs)(nil)
|
||||||
// _ fs.Copier = (*Fs)(nil)
|
// _ fs.Copier = (*Fs)(nil)
|
||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
_ fs.DirMover = (*Fs)(nil)
|
_ fs.DirMover = (*Fs)(nil)
|
||||||
_ fs.DirCacheFlusher = (*Fs)(nil)
|
_ fs.DirCacheFlusher = (*Fs)(nil)
|
||||||
_ fs.DirChangeNotifier = (*Fs)(nil)
|
_ fs.ChangeNotifier = (*Fs)(nil)
|
||||||
_ fs.Object = (*Object)(nil)
|
_ fs.Object = (*Object)(nil)
|
||||||
_ fs.MimeTyper = &Object{}
|
_ fs.MimeTyper = &Object{}
|
||||||
)
|
)
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -54,7 +54,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
205
backend/cache/cache.go
vendored
205
backend/cache/cache.go
vendored
@ -174,7 +174,10 @@ type Fs struct {
|
|||||||
plexConnector *plexConnector
|
plexConnector *plexConnector
|
||||||
backgroundRunner *backgroundWriter
|
backgroundRunner *backgroundWriter
|
||||||
cleanupChan chan bool
|
cleanupChan chan bool
|
||||||
parentsForgetFn []func(string)
|
parentsForgetFn []func(string, fs.EntryType)
|
||||||
|
notifiedRemotes map[string]bool
|
||||||
|
notifiedMu sync.Mutex
|
||||||
|
parentsForgetMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseRootPath returns a cleaned root path and a nil error or "" and an error when the path is invalid
|
// parseRootPath returns a cleaned root path and a nil error or "" and an error when the path is invalid
|
||||||
@ -263,6 +266,7 @@ func NewFs(name, rootPath string) (fs.Fs, error) {
|
|||||||
tempWritePath: *cacheTempWritePath,
|
tempWritePath: *cacheTempWritePath,
|
||||||
tempWriteWait: waitTime,
|
tempWriteWait: waitTime,
|
||||||
cleanupChan: make(chan bool, 1),
|
cleanupChan: make(chan bool, 1),
|
||||||
|
notifiedRemotes: make(map[string]bool),
|
||||||
}
|
}
|
||||||
if f.chunkTotalSize < (f.chunkSize * int64(f.totalWorkers)) {
|
if f.chunkTotalSize < (f.chunkSize * int64(f.totalWorkers)) {
|
||||||
return nil, errors.Errorf("don't set cache-total-chunk-size(%v) less than cache-chunk-size(%v) * cache-workers(%v)",
|
return nil, errors.Errorf("don't set cache-total-chunk-size(%v) less than cache-chunk-size(%v) * cache-workers(%v)",
|
||||||
@ -381,8 +385,8 @@ func NewFs(name, rootPath string) (fs.Fs, error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if doDirChangeNotify := wrappedFs.Features().DirChangeNotify; doDirChangeNotify != nil {
|
if doChangeNotify := wrappedFs.Features().ChangeNotify; doChangeNotify != nil {
|
||||||
doDirChangeNotify(f.receiveDirChangeNotify, f.chunkCleanInterval)
|
doChangeNotify(f.receiveChangeNotify, f.chunkCleanInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.features = (&fs.Features{
|
f.features = (&fs.Features{
|
||||||
@ -390,7 +394,7 @@ func NewFs(name, rootPath string) (fs.Fs, error) {
|
|||||||
DuplicateFiles: false, // storage doesn't permit this
|
DuplicateFiles: false, // storage doesn't permit this
|
||||||
}).Fill(f).Mask(wrappedFs).WrapsFs(f, wrappedFs)
|
}).Fill(f).Mask(wrappedFs).WrapsFs(f, wrappedFs)
|
||||||
// override only those features that use a temp fs and it doesn't support them
|
// override only those features that use a temp fs and it doesn't support them
|
||||||
f.features.DirChangeNotify = f.DirChangeNotify
|
//f.features.ChangeNotify = f.ChangeNotify
|
||||||
if f.tempWritePath != "" {
|
if f.tempWritePath != "" {
|
||||||
if f.tempFs.Features().Copy == nil {
|
if f.tempFs.Features().Copy == nil {
|
||||||
f.features.Copy = nil
|
f.features.Copy = nil
|
||||||
@ -414,85 +418,73 @@ func NewFs(name, rootPath string) (fs.Fs, error) {
|
|||||||
return f, fsErr
|
return f, fsErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fs) receiveDirChangeNotify(forgetPath string) {
|
// receiveChangeNotify is a wrapper to notifications sent from the wrapped FS about changed files
|
||||||
|
func (f *Fs) receiveChangeNotify(forgetPath string, entryType fs.EntryType) {
|
||||||
fs.Debugf(f, "notify: expiring cache for '%v'", forgetPath)
|
fs.Debugf(f, "notify: expiring cache for '%v'", forgetPath)
|
||||||
// notify upstreams too (vfs)
|
// notify upstreams too (vfs)
|
||||||
f.notifyDirChange(forgetPath)
|
f.notifyChangeUpstream(forgetPath, entryType)
|
||||||
|
|
||||||
var cd *Directory
|
var cd *Directory
|
||||||
co := NewObject(f, forgetPath)
|
if entryType == fs.EntryObject {
|
||||||
err := f.cache.GetObject(co)
|
co := NewObject(f, forgetPath)
|
||||||
if err == nil {
|
err := f.cache.GetObject(co)
|
||||||
|
if err != nil {
|
||||||
|
fs.Debugf(f, "ignoring change notification for non cached entry %v", co)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// expire the entry
|
||||||
|
co.CacheTs = time.Now().Add(f.fileAge * -1)
|
||||||
|
err = f.cache.AddObject(co)
|
||||||
|
if err != nil {
|
||||||
|
fs.Errorf(forgetPath, "notify: error expiring '%v': %v", co, err)
|
||||||
|
} else {
|
||||||
|
fs.Debugf(forgetPath, "notify: expired %v", co)
|
||||||
|
}
|
||||||
cd = NewDirectory(f, cleanPath(path.Dir(co.Remote())))
|
cd = NewDirectory(f, cleanPath(path.Dir(co.Remote())))
|
||||||
} else {
|
} else {
|
||||||
cd = NewDirectory(f, forgetPath)
|
cd = NewDirectory(f, forgetPath)
|
||||||
}
|
// we expire the dir
|
||||||
|
err := f.cache.ExpireDir(cd)
|
||||||
// we list all the cached objects and expire all of them
|
if err != nil {
|
||||||
entries, err := f.cache.GetDirEntries(cd)
|
fs.Errorf(forgetPath, "notify: error expiring '%v': %v", cd, err)
|
||||||
if err != nil {
|
} else {
|
||||||
fs.Debugf(forgetPath, "notify: ignoring notification on non cached dir")
|
fs.Debugf(forgetPath, "notify: expired '%v'", cd)
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; i < len(entries); i++ {
|
|
||||||
if co, ok := entries[i].(*Object); ok {
|
|
||||||
co.CacheTs = time.Now().Add(f.fileAge * -1)
|
|
||||||
err = f.cache.AddObject(co)
|
|
||||||
if err != nil {
|
|
||||||
fs.Errorf(forgetPath, "notify: error expiring '%v': %v", co, err)
|
|
||||||
} else {
|
|
||||||
fs.Debugf(forgetPath, "notify: expired %v", co)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// finally, we expire the dir as well
|
|
||||||
err = f.cache.ExpireDir(cd)
|
f.notifiedMu.Lock()
|
||||||
if err != nil {
|
defer f.notifiedMu.Unlock()
|
||||||
fs.Errorf(forgetPath, "notify: error expiring '%v': %v", cd, err)
|
f.notifiedRemotes[forgetPath] = true
|
||||||
} else {
|
f.notifiedRemotes[cd.Remote()] = true
|
||||||
fs.Debugf(forgetPath, "notify: expired '%v'", cd)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// notifyDirChange takes a remote (can be dir or entry) and
|
// notifyChangeUpstreamIfNeeded will check if the wrapped remote doesn't notify on changes
|
||||||
// tries to determine which is it and notify upstreams of the dir change
|
|
||||||
func (f *Fs) notifyDirChange(remote string) {
|
|
||||||
var cd *Directory
|
|
||||||
co := NewObject(f, remote)
|
|
||||||
err := f.cache.GetObject(co)
|
|
||||||
if err == nil {
|
|
||||||
pd := cleanPath(path.Dir(remote))
|
|
||||||
cd = NewDirectory(f, pd)
|
|
||||||
} else {
|
|
||||||
cd = NewDirectory(f, remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.notifyDirChangeUpstream(cd.Remote())
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifyDirChangeUpstreamIfNeeded will check if the wrapped remote doesn't notify on dir changes
|
|
||||||
// or if we use a temp fs
|
// or if we use a temp fs
|
||||||
func (f *Fs) notifyDirChangeUpstreamIfNeeded(remote string) {
|
func (f *Fs) notifyChangeUpstreamIfNeeded(remote string, entryType fs.EntryType) {
|
||||||
if f.Fs.Features().DirChangeNotify == nil || f.tempWritePath != "" {
|
if f.Fs.Features().ChangeNotify == nil || f.tempWritePath != "" {
|
||||||
f.notifyDirChangeUpstream(remote)
|
f.notifyChangeUpstream(remote, entryType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// notifyDirChangeUpstream will loop through all the upstreams and notify
|
// notifyChangeUpstream will loop through all the upstreams and notify
|
||||||
// of the provided remote (should be only a dir)
|
// of the provided remote (should be only a dir)
|
||||||
func (f *Fs) notifyDirChangeUpstream(remote string) {
|
func (f *Fs) notifyChangeUpstream(remote string, entryType fs.EntryType) {
|
||||||
|
f.parentsForgetMu.Lock()
|
||||||
|
defer f.parentsForgetMu.Unlock()
|
||||||
if len(f.parentsForgetFn) > 0 {
|
if len(f.parentsForgetFn) > 0 {
|
||||||
for _, fn := range f.parentsForgetFn {
|
for _, fn := range f.parentsForgetFn {
|
||||||
fn(remote)
|
fn(remote, entryType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirChangeNotify can subsribe multiple callers
|
// ChangeNotify can subsribe multiple callers
|
||||||
// this is coupled with the wrapped fs DirChangeNotify (if it supports it)
|
// this is coupled with the wrapped fs ChangeNotify (if it supports it)
|
||||||
// and also notifies other caches (i.e VFS) to clear out whenever something changes
|
// and also notifies other caches (i.e VFS) to clear out whenever something changes
|
||||||
func (f *Fs) DirChangeNotify(notifyFunc func(string), pollInterval time.Duration) chan bool {
|
func (f *Fs) ChangeNotify(notifyFunc func(string, fs.EntryType), pollInterval time.Duration) chan bool {
|
||||||
fs.Debugf(f, "subscribing to DirChangeNotify")
|
f.parentsForgetMu.Lock()
|
||||||
|
defer f.parentsForgetMu.Unlock()
|
||||||
|
fs.Debugf(f, "subscribing to ChangeNotify")
|
||||||
f.parentsForgetFn = append(f.parentsForgetFn, notifyFunc)
|
f.parentsForgetFn = append(f.parentsForgetFn, notifyFunc)
|
||||||
return make(chan bool)
|
return make(chan bool)
|
||||||
}
|
}
|
||||||
@ -649,12 +641,13 @@ func (f *Fs) List(dir string) (entries fs.DirEntries, err error) {
|
|||||||
fs.Debugf(dir, "list: cached object: %v", co)
|
fs.Debugf(dir, "list: cached object: %v", co)
|
||||||
case fs.Directory:
|
case fs.Directory:
|
||||||
cdd := DirectoryFromOriginal(f, o)
|
cdd := DirectoryFromOriginal(f, o)
|
||||||
err := f.cache.AddDir(cdd)
|
// FIXME this overrides a possible expired dir
|
||||||
if err != nil {
|
//err := f.cache.AddDir(cdd)
|
||||||
fs.Errorf(dir, "list: error caching dir from listing %v", o)
|
//if err != nil {
|
||||||
} else {
|
// fs.Errorf(dir, "list: error caching dir from listing %v", o)
|
||||||
fs.Debugf(dir, "list: cached dir: %v", cdd)
|
//} else {
|
||||||
}
|
// fs.Debugf(dir, "list: cached dir: %v", cdd)
|
||||||
|
//}
|
||||||
cachedEntries = append(cachedEntries, cdd)
|
cachedEntries = append(cachedEntries, cdd)
|
||||||
default:
|
default:
|
||||||
fs.Debugf(entry, "list: Unknown object type %T", entry)
|
fs.Debugf(entry, "list: Unknown object type %T", entry)
|
||||||
@ -759,8 +752,8 @@ func (f *Fs) Mkdir(dir string) error {
|
|||||||
} else {
|
} else {
|
||||||
fs.Infof(parentCd, "mkdir: cache expired")
|
fs.Infof(parentCd, "mkdir: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
f.notifyDirChangeUpstreamIfNeeded(parentCd.Remote())
|
f.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -801,7 +794,7 @@ func (f *Fs) Rmdir(dir string) error {
|
|||||||
fs.Debugf(dir, "rmdir: read %v from temp fs", len(queuedEntries))
|
fs.Debugf(dir, "rmdir: read %v from temp fs", len(queuedEntries))
|
||||||
fs.Debugf(dir, "rmdir: temp fs entries: %v", queuedEntries)
|
fs.Debugf(dir, "rmdir: temp fs entries: %v", queuedEntries)
|
||||||
if len(queuedEntries) > 0 {
|
if len(queuedEntries) > 0 {
|
||||||
fs.Errorf(dir, "rmdir: temporary dir not empty")
|
fs.Errorf(dir, "rmdir: temporary dir not empty: %v", queuedEntries)
|
||||||
return fs.ErrorDirectoryNotEmpty
|
return fs.ErrorDirectoryNotEmpty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -829,8 +822,8 @@ func (f *Fs) Rmdir(dir string) error {
|
|||||||
} else {
|
} else {
|
||||||
fs.Infof(parentCd, "rmdir: cache expired")
|
fs.Infof(parentCd, "rmdir: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
f.notifyDirChangeUpstreamIfNeeded(parentCd.Remote())
|
f.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -939,8 +932,8 @@ cleanup:
|
|||||||
} else {
|
} else {
|
||||||
fs.Debugf(srcParent, "dirmove: cache expired")
|
fs.Debugf(srcParent, "dirmove: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
f.notifyDirChangeUpstreamIfNeeded(srcParent.Remote())
|
f.notifyChangeUpstreamIfNeeded(srcParent.Remote(), fs.EntryDirectory)
|
||||||
|
|
||||||
// expire parent dir at the destination path
|
// expire parent dir at the destination path
|
||||||
dstParent := NewDirectory(f, cleanPath(path.Dir(dstRemote)))
|
dstParent := NewDirectory(f, cleanPath(path.Dir(dstRemote)))
|
||||||
@ -950,8 +943,8 @@ cleanup:
|
|||||||
} else {
|
} else {
|
||||||
fs.Debugf(dstParent, "dirmove: cache expired")
|
fs.Debugf(dstParent, "dirmove: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
f.notifyDirChangeUpstreamIfNeeded(dstParent.Remote())
|
f.notifyChangeUpstreamIfNeeded(dstParent.Remote(), fs.EntryDirectory)
|
||||||
// TODO: precache dst dir and save the chunks
|
// TODO: precache dst dir and save the chunks
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -1030,6 +1023,11 @@ func (f *Fs) put(in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, put p
|
|||||||
|
|
||||||
// queue for upload and store in temp fs if configured
|
// queue for upload and store in temp fs if configured
|
||||||
if f.tempWritePath != "" {
|
if f.tempWritePath != "" {
|
||||||
|
// we need to clear the caches before a put through temp fs
|
||||||
|
parentCd := NewDirectory(f, cleanPath(path.Dir(src.Remote())))
|
||||||
|
_ = f.cache.ExpireDir(parentCd)
|
||||||
|
f.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
|
||||||
|
|
||||||
obj, err = f.tempFs.Put(in, src, options...)
|
obj, err = f.tempFs.Put(in, src, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Errorf(obj, "put: failed to upload in temp fs: %v", err)
|
fs.Errorf(obj, "put: failed to upload in temp fs: %v", err)
|
||||||
@ -1074,8 +1072,8 @@ func (f *Fs) put(in io.Reader, src fs.ObjectInfo, options []fs.OpenOption, put p
|
|||||||
} else {
|
} else {
|
||||||
fs.Infof(parentCd, "put: cache expired")
|
fs.Infof(parentCd, "put: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify
|
// advertise to ChangeNotify
|
||||||
f.notifyDirChangeUpstreamIfNeeded(parentCd.Remote())
|
f.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
|
||||||
|
|
||||||
return cachedObj, nil
|
return cachedObj, nil
|
||||||
}
|
}
|
||||||
@ -1164,8 +1162,8 @@ func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
|
|||||||
} else {
|
} else {
|
||||||
fs.Infof(parentCd, "copy: cache expired")
|
fs.Infof(parentCd, "copy: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
f.notifyDirChangeUpstreamIfNeeded(parentCd.Remote())
|
f.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
|
||||||
// expire src parent
|
// expire src parent
|
||||||
srcParent := NewDirectory(f, cleanPath(path.Dir(src.Remote())))
|
srcParent := NewDirectory(f, cleanPath(path.Dir(src.Remote())))
|
||||||
err = f.cache.ExpireDir(srcParent)
|
err = f.cache.ExpireDir(srcParent)
|
||||||
@ -1174,8 +1172,8 @@ func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
|
|||||||
} else {
|
} else {
|
||||||
fs.Infof(srcParent, "copy: cache expired")
|
fs.Infof(srcParent, "copy: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
f.notifyDirChangeUpstreamIfNeeded(srcParent.Remote())
|
f.notifyChangeUpstreamIfNeeded(srcParent.Remote(), fs.EntryDirectory)
|
||||||
|
|
||||||
return co, nil
|
return co, nil
|
||||||
}
|
}
|
||||||
@ -1260,8 +1258,8 @@ func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
|
|||||||
} else {
|
} else {
|
||||||
fs.Infof(parentCd, "move: cache expired")
|
fs.Infof(parentCd, "move: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
f.notifyDirChangeUpstreamIfNeeded(parentCd.Remote())
|
f.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
|
||||||
// persist new
|
// persist new
|
||||||
cachedObj := ObjectFromOriginal(f, obj).persist()
|
cachedObj := ObjectFromOriginal(f, obj).persist()
|
||||||
fs.Debugf(cachedObj, "move: added to cache")
|
fs.Debugf(cachedObj, "move: added to cache")
|
||||||
@ -1273,8 +1271,8 @@ func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
|
|||||||
} else {
|
} else {
|
||||||
fs.Infof(parentCd, "move: cache expired")
|
fs.Infof(parentCd, "move: cache expired")
|
||||||
}
|
}
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
f.notifyDirChangeUpstreamIfNeeded(parentCd.Remote())
|
f.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
|
||||||
|
|
||||||
return cachedObj, nil
|
return cachedObj, nil
|
||||||
}
|
}
|
||||||
@ -1416,6 +1414,19 @@ func (f *Fs) GetBackgroundUploadChannel() chan BackgroundUploadState {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fs) isNotifiedRemote(remote string) bool {
|
||||||
|
f.notifiedMu.Lock()
|
||||||
|
defer f.notifiedMu.Unlock()
|
||||||
|
|
||||||
|
n, ok := f.notifiedRemotes[remote]
|
||||||
|
if !ok || !n {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(f.notifiedRemotes, remote)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
func cleanPath(p string) string {
|
func cleanPath(p string) string {
|
||||||
p = path.Clean(p)
|
p = path.Clean(p)
|
||||||
if p == "." || p == "/" {
|
if p == "." || p == "/" {
|
||||||
@ -1427,16 +1438,16 @@ func cleanPath(p string) string {
|
|||||||
|
|
||||||
// Check the interfaces are satisfied
|
// Check the interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fs.Fs = (*Fs)(nil)
|
_ fs.Fs = (*Fs)(nil)
|
||||||
_ fs.Purger = (*Fs)(nil)
|
_ fs.Purger = (*Fs)(nil)
|
||||||
_ fs.Copier = (*Fs)(nil)
|
_ fs.Copier = (*Fs)(nil)
|
||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
_ fs.DirMover = (*Fs)(nil)
|
_ fs.DirMover = (*Fs)(nil)
|
||||||
_ fs.PutUncheckeder = (*Fs)(nil)
|
_ fs.PutUncheckeder = (*Fs)(nil)
|
||||||
_ fs.PutStreamer = (*Fs)(nil)
|
_ fs.PutStreamer = (*Fs)(nil)
|
||||||
_ fs.CleanUpper = (*Fs)(nil)
|
_ fs.CleanUpper = (*Fs)(nil)
|
||||||
_ fs.UnWrapper = (*Fs)(nil)
|
_ fs.UnWrapper = (*Fs)(nil)
|
||||||
_ fs.Wrapper = (*Fs)(nil)
|
_ fs.Wrapper = (*Fs)(nil)
|
||||||
_ fs.ListRer = (*Fs)(nil)
|
_ fs.ListRer = (*Fs)(nil)
|
||||||
_ fs.DirChangeNotifier = (*Fs)(nil)
|
_ fs.ChangeNotifier = (*Fs)(nil)
|
||||||
)
|
)
|
||||||
|
134
backend/cache/cache_internal_test.go
vendored
134
backend/cache/cache_internal_test.go
vendored
@ -44,6 +44,7 @@ const (
|
|||||||
cryptPassword2 = "NlgTBEIe-qibA7v-FoMfuX6Cw8KlLai_aMvV" // mv4mZW572HM
|
cryptPassword2 = "NlgTBEIe-qibA7v-FoMfuX6Cw8KlLai_aMvV" // mv4mZW572HM
|
||||||
cryptedTextBase64 = "UkNMT05FAAC320i2xIee0BiNyknSPBn+Qcw3q9FhIFp3tvq6qlqvbsno3PnxmEFeJG3jDBnR/wku2gHWeQ==" // one content
|
cryptedTextBase64 = "UkNMT05FAAC320i2xIee0BiNyknSPBn+Qcw3q9FhIFp3tvq6qlqvbsno3PnxmEFeJG3jDBnR/wku2gHWeQ==" // one content
|
||||||
cryptedText2Base64 = "UkNMT05FAAATcQkVsgjBh8KafCKcr0wdTa1fMmV0U8hsCLGFoqcvxKVmvv7wx3Hf5EXxFcki2FFV4sdpmSrb9Q==" // updated content
|
cryptedText2Base64 = "UkNMT05FAAATcQkVsgjBh8KafCKcr0wdTa1fMmV0U8hsCLGFoqcvxKVmvv7wx3Hf5EXxFcki2FFV4sdpmSrb9Q==" // updated content
|
||||||
|
cryptedText3Base64 = "UkNMT05FAAB/f7YtYKbPfmk9+OX/ffN3qG3OEdWT+z74kxCX9V/YZwJ4X2DN3HOnUC3gKQ4Gcoud5UtNvQ==" // test content
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -444,32 +445,134 @@ func TestInternalWrappedFsChangeNotSeen(t *testing.T) {
|
|||||||
runInstance.writeRemoteBytes(t, rootFs, "data.bin", testData)
|
runInstance.writeRemoteBytes(t, rootFs, "data.bin", testData)
|
||||||
|
|
||||||
// update in the wrapped fs
|
// update in the wrapped fs
|
||||||
|
originalSize, err := runInstance.size(t, rootFs, "data.bin")
|
||||||
|
require.NoError(t, err)
|
||||||
|
log.Printf("original size: %v", originalSize)
|
||||||
|
|
||||||
o, err := cfs.UnWrap().NewObject(runInstance.encryptRemoteIfNeeded(t, "data.bin"))
|
o, err := cfs.UnWrap().NewObject(runInstance.encryptRemoteIfNeeded(t, "data.bin"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
wrappedTime := time.Now().Add(time.Hour * -1)
|
expectedSize := int64(len([]byte("test content")))
|
||||||
err = o.SetModTime(wrappedTime)
|
var data2 []byte
|
||||||
|
if runInstance.rootIsCrypt {
|
||||||
|
data2, err = base64.StdEncoding.DecodeString(cryptedText3Base64)
|
||||||
|
require.NoError(t, err)
|
||||||
|
expectedSize = expectedSize + 1 // FIXME newline gets in, likely test data issue
|
||||||
|
} else {
|
||||||
|
data2 = []byte("test content")
|
||||||
|
}
|
||||||
|
objInfo := object.NewStaticObjectInfo(runInstance.encryptRemoteIfNeeded(t, "data.bin"), time.Now(), int64(len(data2)), true, nil, cfs.UnWrap())
|
||||||
|
err = o.Update(bytes.NewReader(data2), objInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(len(data2)), o.Size())
|
||||||
|
log.Printf("updated size: %v", len(data2))
|
||||||
|
|
||||||
// get a new instance from the cache
|
// get a new instance from the cache
|
||||||
if runInstance.wrappedIsExternal {
|
if runInstance.wrappedIsExternal {
|
||||||
err = runInstance.retryBlock(func() error {
|
err = runInstance.retryBlock(func() error {
|
||||||
coModTime, err := runInstance.modTime(t, rootFs, "data.bin")
|
coSize, err := runInstance.size(t, rootFs, "data.bin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if coModTime.Unix() != o.ModTime().Unix() {
|
if coSize != expectedSize {
|
||||||
return errors.Errorf("%v <> %v", coModTime, o.ModTime())
|
return errors.Errorf("%v <> %v", coSize, expectedSize)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, 12, time.Second*10)
|
}, 12, time.Second*10)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
coModTime, err := runInstance.modTime(t, rootFs, "data.bin")
|
coSize, err := runInstance.size(t, rootFs, "data.bin")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, coModTime.Unix(), o.ModTime().Unix())
|
require.NotEqual(t, coSize, expectedSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInternalMoveWithNotify(t *testing.T) {
|
||||||
|
id := fmt.Sprintf("timwn%v", time.Now().Unix())
|
||||||
|
rootFs, boltDb := runInstance.newCacheFs(t, remoteName, id, false, true, nil, nil)
|
||||||
|
defer runInstance.cleanupFs(t, rootFs, boltDb)
|
||||||
|
if !runInstance.wrappedIsExternal {
|
||||||
|
t.Skipf("Not external")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfs, err := runInstance.getCacheFs(rootFs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
srcName := runInstance.encryptRemoteIfNeeded(t, "test") + "/" + runInstance.encryptRemoteIfNeeded(t, "one") + "/" + runInstance.encryptRemoteIfNeeded(t, "data.bin")
|
||||||
|
dstName := runInstance.encryptRemoteIfNeeded(t, "test") + "/" + runInstance.encryptRemoteIfNeeded(t, "second") + "/" + runInstance.encryptRemoteIfNeeded(t, "data.bin")
|
||||||
|
// create some rand test data
|
||||||
|
var testData []byte
|
||||||
|
if runInstance.rootIsCrypt {
|
||||||
|
testData, err = base64.StdEncoding.DecodeString(cryptedTextBase64)
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
testData = []byte("test content")
|
||||||
|
}
|
||||||
|
_ = cfs.UnWrap().Mkdir(runInstance.encryptRemoteIfNeeded(t, "test"))
|
||||||
|
_ = cfs.UnWrap().Mkdir(runInstance.encryptRemoteIfNeeded(t, "test/one"))
|
||||||
|
_ = cfs.UnWrap().Mkdir(runInstance.encryptRemoteIfNeeded(t, "test/second"))
|
||||||
|
srcObj := runInstance.writeObjectBytes(t, cfs.UnWrap(), srcName, testData)
|
||||||
|
|
||||||
|
// list in mount
|
||||||
|
_, err = runInstance.list(t, rootFs, "test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = runInstance.list(t, rootFs, "test/one")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// move file
|
||||||
|
_, err = cfs.UnWrap().Features().Move(srcObj, dstName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = runInstance.retryBlock(func() error {
|
||||||
|
li, err := runInstance.list(t, rootFs, "test")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(li) != 2 {
|
||||||
|
log.Printf("not expected listing /test: %v", li)
|
||||||
|
return errors.Errorf("not expected listing /test: %v", li)
|
||||||
|
}
|
||||||
|
|
||||||
|
li, err = runInstance.list(t, rootFs, "test/one")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(li) != 0 {
|
||||||
|
log.Printf("not expected listing /test/one: %v", li)
|
||||||
|
return errors.Errorf("not expected listing /test/one: %v", li)
|
||||||
|
}
|
||||||
|
|
||||||
|
li, err = runInstance.list(t, rootFs, "test/second")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(li) != 1 {
|
||||||
|
log.Printf("not expected listing /test/second: %v", li)
|
||||||
|
return errors.Errorf("not expected listing /test/second: %v", li)
|
||||||
|
}
|
||||||
|
if fi, ok := li[0].(os.FileInfo); ok {
|
||||||
|
if fi.Name() != "data.bin" {
|
||||||
|
log.Printf("not expected name: %v", fi.Name())
|
||||||
|
return errors.Errorf("not expected name: %v", fi.Name())
|
||||||
|
}
|
||||||
|
} else if di, ok := li[0].(fs.DirEntry); ok {
|
||||||
|
if di.Remote() != "test/second/data.bin" {
|
||||||
|
log.Printf("not expected remote: %v", di.Remote())
|
||||||
|
return errors.Errorf("not expected remote: %v", di.Remote())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("unexpected listing: %v", li)
|
||||||
|
return errors.Errorf("unexpected listing: %v", li)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("complete listing: %v", li)
|
||||||
|
return nil
|
||||||
|
}, 12, time.Second*10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestInternalChangeSeenAfterDirCacheFlush(t *testing.T) {
|
func TestInternalChangeSeenAfterDirCacheFlush(t *testing.T) {
|
||||||
id := fmt.Sprintf("ticsadcf%v", time.Now().Unix())
|
id := fmt.Sprintf("ticsadcf%v", time.Now().Unix())
|
||||||
rootFs, boltDb := runInstance.newCacheFs(t, remoteName, id, false, true, nil, nil)
|
rootFs, boltDb := runInstance.newCacheFs(t, remoteName, id, false, true, nil, nil)
|
||||||
@ -1661,6 +1764,23 @@ func (r *run) modTime(t *testing.T, rootFs fs.Fs, src string) (time.Time, error)
|
|||||||
return obj1.ModTime(), nil
|
return obj1.ModTime(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *run) size(t *testing.T, rootFs fs.Fs, src string) (int64, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if r.useMount {
|
||||||
|
fi, err := os.Stat(path.Join(runInstance.mntDir, src))
|
||||||
|
if err != nil {
|
||||||
|
return int64(0), err
|
||||||
|
}
|
||||||
|
return fi.Size(), nil
|
||||||
|
}
|
||||||
|
obj1, err := rootFs.NewObject(src)
|
||||||
|
if err != nil {
|
||||||
|
return int64(0), err
|
||||||
|
}
|
||||||
|
return obj1.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *run) updateData(t *testing.T, rootFs fs.Fs, src, data, append string) error {
|
func (r *run) updateData(t *testing.T, rootFs fs.Fs, src, data, append string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
2
backend/cache/cache_test.go
vendored
2
backend/cache/cache_test.go
vendored
@ -55,7 +55,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
2
backend/cache/handle.go
vendored
2
backend/cache/handle.go
vendored
@ -658,7 +658,7 @@ func (b *backgroundWriter) run() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Errorf(parentCd, "background upload: cache expire error: %v", err)
|
fs.Errorf(parentCd, "background upload: cache expire error: %v", err)
|
||||||
}
|
}
|
||||||
b.fs.notifyDirChange(remote)
|
b.fs.notifyChangeUpstream(remote, fs.EntryObject)
|
||||||
fs.Infof(remote, "finished background upload")
|
fs.Infof(remote, "finished background upload")
|
||||||
b.notify(remote, BackgroundUploadCompleted, nil)
|
b.notify(remote, BackgroundUploadCompleted, nil)
|
||||||
}
|
}
|
||||||
|
22
backend/cache/object.go
vendored
22
backend/cache/object.go
vendored
@ -132,19 +132,36 @@ func (o *Object) abs() string {
|
|||||||
|
|
||||||
// ModTime returns the cached ModTime
|
// ModTime returns the cached ModTime
|
||||||
func (o *Object) ModTime() time.Time {
|
func (o *Object) ModTime() time.Time {
|
||||||
|
_ = o.refresh()
|
||||||
return time.Unix(0, o.CacheModTime)
|
return time.Unix(0, o.CacheModTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the cached Size
|
// Size returns the cached Size
|
||||||
func (o *Object) Size() int64 {
|
func (o *Object) Size() int64 {
|
||||||
|
_ = o.refresh()
|
||||||
return o.CacheSize
|
return o.CacheSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// Storable returns the cached Storable
|
// Storable returns the cached Storable
|
||||||
func (o *Object) Storable() bool {
|
func (o *Object) Storable() bool {
|
||||||
|
_ = o.refresh()
|
||||||
return o.CacheStorable
|
return o.CacheStorable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refresh will check if the object info is expired and request the info from source if it is
|
||||||
|
// all these conditions must be true to ignore a refresh
|
||||||
|
// 1. cache ts didn't expire yet
|
||||||
|
// 2. is not pending a notification from the wrapped fs
|
||||||
|
func (o *Object) refresh() error {
|
||||||
|
isNotified := o.CacheFs.isNotifiedRemote(o.Remote())
|
||||||
|
isExpired := time.Now().After(o.CacheTs.Add(o.CacheFs.fileAge))
|
||||||
|
if !isExpired && !isNotified {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.refreshFromSource(true)
|
||||||
|
}
|
||||||
|
|
||||||
// refreshFromSource requests the original FS for the object in case it comes from a cached entry
|
// refreshFromSource requests the original FS for the object in case it comes from a cached entry
|
||||||
func (o *Object) refreshFromSource(force bool) error {
|
func (o *Object) refreshFromSource(force bool) error {
|
||||||
o.refreshMutex.Lock()
|
o.refreshMutex.Lock()
|
||||||
@ -274,8 +291,8 @@ func (o *Object) Remove() error {
|
|||||||
_ = o.CacheFs.cache.removePendingUpload(o.abs())
|
_ = o.CacheFs.cache.removePendingUpload(o.abs())
|
||||||
parentCd := NewDirectory(o.CacheFs, cleanPath(path.Dir(o.Remote())))
|
parentCd := NewDirectory(o.CacheFs, cleanPath(path.Dir(o.Remote())))
|
||||||
_ = o.CacheFs.cache.ExpireDir(parentCd)
|
_ = o.CacheFs.cache.ExpireDir(parentCd)
|
||||||
// advertise to DirChangeNotify if wrapped doesn't do that
|
// advertise to ChangeNotify if wrapped doesn't do that
|
||||||
o.CacheFs.notifyDirChangeUpstreamIfNeeded(parentCd.Remote())
|
o.CacheFs.notifyChangeUpstreamIfNeeded(parentCd.Remote(), fs.EntryDirectory)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -283,6 +300,7 @@ func (o *Object) Remove() error {
|
|||||||
// Hash requests a hash of the object and stores in the cache
|
// Hash requests a hash of the object and stores in the cache
|
||||||
// since it might or might not be called, this is lazy loaded
|
// since it might or might not be called, this is lazy loaded
|
||||||
func (o *Object) Hash(ht hash.Type) (string, error) {
|
func (o *Object) Hash(ht hash.Type) (string, error) {
|
||||||
|
_ = o.refresh()
|
||||||
if o.CacheHashes == nil {
|
if o.CacheHashes == nil {
|
||||||
o.CacheHashes = make(map[hash.Type]string)
|
o.CacheHashes = make(map[hash.Type]string)
|
||||||
}
|
}
|
||||||
|
@ -143,18 +143,18 @@ func NewFs(name, rpath string) (fs.Fs, error) {
|
|||||||
CanHaveEmptyDirectories: true,
|
CanHaveEmptyDirectories: true,
|
||||||
}).Fill(f).Mask(wrappedFs).WrapsFs(f, wrappedFs)
|
}).Fill(f).Mask(wrappedFs).WrapsFs(f, wrappedFs)
|
||||||
|
|
||||||
doDirChangeNotify := wrappedFs.Features().DirChangeNotify
|
doChangeNotify := wrappedFs.Features().ChangeNotify
|
||||||
if doDirChangeNotify != nil {
|
if doChangeNotify != nil {
|
||||||
f.features.DirChangeNotify = func(notifyFunc func(string), pollInterval time.Duration) chan bool {
|
f.features.ChangeNotify = func(notifyFunc func(string, fs.EntryType), pollInterval time.Duration) chan bool {
|
||||||
wrappedNotifyFunc := func(path string) {
|
wrappedNotifyFunc := func(path string, entryType fs.EntryType) {
|
||||||
decrypted, err := f.DecryptFileName(path)
|
decrypted, err := f.DecryptFileName(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Logf(f, "DirChangeNotify was unable to decrypt %q: %s", path, err)
|
fs.Logf(f, "ChangeNotify was unable to decrypt %q: %s", path, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
notifyFunc(decrypted)
|
notifyFunc(decrypted, entryType)
|
||||||
}
|
}
|
||||||
return doDirChangeNotify(wrappedNotifyFunc, pollInterval)
|
return doChangeNotify(wrappedNotifyFunc, pollInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ func TestFsMove2(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove2(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove2(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull2(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull2(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision2(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision2(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify2(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify2(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString2(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString2(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs2(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs2(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote2(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote2(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -52,7 +52,7 @@ func TestFsMove3(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove3(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove3(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull3(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull3(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision3(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision3(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify3(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify3(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString3(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString3(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs3(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs3(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote3(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote3(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -52,7 +52,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -16,7 +16,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -1182,14 +1181,13 @@ func (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirChangeNotify polls for changes from the remote and hands the path to the
|
// ChangeNotify calls the passed function with a path that has had changes.
|
||||||
// given function. Only changes that can be resolved to a path through the
|
// If the implementation uses polling, it should adhere to the given interval.
|
||||||
// DirCache will handled.
|
|
||||||
//
|
//
|
||||||
// Automatically restarts itself in case of unexpected behaviour of the remote.
|
// Automatically restarts itself in case of unexpected behaviour of the remote.
|
||||||
//
|
//
|
||||||
// Close the returned channel to stop being notified.
|
// Close the returned channel to stop being notified.
|
||||||
func (f *Fs) DirChangeNotify(notifyFunc func(string), pollInterval time.Duration) chan bool {
|
func (f *Fs) ChangeNotify(notifyFunc func(string, fs.EntryType), pollInterval time.Duration) chan bool {
|
||||||
quit := make(chan bool)
|
quit := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
@ -1197,7 +1195,7 @@ func (f *Fs) DirChangeNotify(notifyFunc func(string), pollInterval time.Duration
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
for {
|
for {
|
||||||
f.dirchangeNotifyRunner(notifyFunc, pollInterval)
|
f.changeNotifyRunner(notifyFunc, pollInterval)
|
||||||
fs.Debugf(f, "Notify listener service ran into issues, restarting shortly.")
|
fs.Debugf(f, "Notify listener service ran into issues, restarting shortly.")
|
||||||
time.Sleep(pollInterval)
|
time.Sleep(pollInterval)
|
||||||
}
|
}
|
||||||
@ -1206,11 +1204,8 @@ func (f *Fs) DirChangeNotify(notifyFunc func(string), pollInterval time.Duration
|
|||||||
return quit
|
return quit
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fs) dirchangeNotifyRunner(notifyFunc func(string), pollInterval time.Duration) {
|
func (f *Fs) changeNotifyRunner(notifyFunc func(string, fs.EntryType), pollInterval time.Duration) {
|
||||||
var err error
|
var err error
|
||||||
var changeList *drive.ChangeList
|
|
||||||
var pageToken string
|
|
||||||
|
|
||||||
var startPageToken *drive.StartPageToken
|
var startPageToken *drive.StartPageToken
|
||||||
err = f.pacer.Call(func() (bool, error) {
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
startPageToken, err = f.svc.Changes.GetStartPageToken().SupportsTeamDrives(f.isTeamDrive).Do()
|
startPageToken, err = f.svc.Changes.GetStartPageToken().SupportsTeamDrives(f.isTeamDrive).Do()
|
||||||
@ -1220,12 +1215,14 @@ func (f *Fs) dirchangeNotifyRunner(notifyFunc func(string), pollInterval time.Du
|
|||||||
fs.Debugf(f, "Failed to get StartPageToken: %v", err)
|
fs.Debugf(f, "Failed to get StartPageToken: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pageToken = startPageToken.StartPageToken
|
pageToken := startPageToken.StartPageToken
|
||||||
|
|
||||||
for {
|
for {
|
||||||
fs.Debugf(f, "Checking for changes on remote")
|
fs.Debugf(f, "Checking for changes on remote")
|
||||||
|
var changeList *drive.ChangeList
|
||||||
|
|
||||||
err = f.pacer.Call(func() (bool, error) {
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
changesCall := f.svc.Changes.List(pageToken).Fields("nextPageToken,newStartPageToken,changes(fileId,file/parents)")
|
changesCall := f.svc.Changes.List(pageToken).Fields("nextPageToken,newStartPageToken,changes(fileId,file(name,parents,mimeType))")
|
||||||
if *driveListChunk > 0 {
|
if *driveListChunk > 0 {
|
||||||
changesCall = changesCall.PageSize(*driveListChunk)
|
changesCall = changesCall.PageSize(*driveListChunk)
|
||||||
}
|
}
|
||||||
@ -1237,28 +1234,47 @@ func (f *Fs) dirchangeNotifyRunner(notifyFunc func(string), pollInterval time.Du
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pathsToClear := make([]string, 0)
|
type entryType struct {
|
||||||
|
path string
|
||||||
|
entryType fs.EntryType
|
||||||
|
}
|
||||||
|
var pathsToClear []entryType
|
||||||
for _, change := range changeList.Changes {
|
for _, change := range changeList.Changes {
|
||||||
if path, ok := f.dirCache.GetInv(change.FileId); ok {
|
if path, ok := f.dirCache.GetInv(change.FileId); ok {
|
||||||
pathsToClear = append(pathsToClear, path)
|
if change.File != nil && change.File.MimeType != driveFolderType {
|
||||||
|
pathsToClear = append(pathsToClear, entryType{path: path, entryType: fs.EntryObject})
|
||||||
|
} else {
|
||||||
|
pathsToClear = append(pathsToClear, entryType{path: path, entryType: fs.EntryDirectory})
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if change.File != nil {
|
if change.File != nil && change.File.MimeType != driveFolderType {
|
||||||
for _, parent := range change.File.Parents {
|
// translate the parent dir of this object
|
||||||
if path, ok := f.dirCache.GetInv(parent); ok {
|
if len(change.File.Parents) > 0 {
|
||||||
pathsToClear = append(pathsToClear, path)
|
if path, ok := f.dirCache.GetInv(change.File.Parents[0]); ok {
|
||||||
|
// and append the drive file name to compute the full file name
|
||||||
|
if len(path) > 0 {
|
||||||
|
path = path + "/" + change.File.Name
|
||||||
|
} else {
|
||||||
|
path = change.File.Name
|
||||||
|
}
|
||||||
|
// this will now clear the actual file too
|
||||||
|
pathsToClear = append(pathsToClear, entryType{path: path, entryType: fs.EntryObject})
|
||||||
}
|
}
|
||||||
|
} else { // a true root object that is changed
|
||||||
|
pathsToClear = append(pathsToClear, entryType{path: change.File.Name, entryType: fs.EntryObject})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastNotifiedPath := ""
|
|
||||||
sort.Strings(pathsToClear)
|
visitedPaths := make(map[string]bool)
|
||||||
for _, path := range pathsToClear {
|
for _, entry := range pathsToClear {
|
||||||
if lastNotifiedPath != "" && (path == lastNotifiedPath || strings.HasPrefix(path+"/", lastNotifiedPath)) {
|
if _, ok := visitedPaths[entry.path]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastNotifiedPath = path
|
visitedPaths[entry.path] = true
|
||||||
notifyFunc(path)
|
notifyFunc(entry.path, entry.entryType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if changeList.NewStartPageToken != "" {
|
if changeList.NewStartPageToken != "" {
|
||||||
@ -1567,17 +1583,17 @@ func (o *Object) MimeType() string {
|
|||||||
|
|
||||||
// Check the interfaces are satisfied
|
// Check the interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fs.Fs = (*Fs)(nil)
|
_ fs.Fs = (*Fs)(nil)
|
||||||
_ fs.Purger = (*Fs)(nil)
|
_ fs.Purger = (*Fs)(nil)
|
||||||
_ fs.CleanUpper = (*Fs)(nil)
|
_ fs.CleanUpper = (*Fs)(nil)
|
||||||
_ fs.PutStreamer = (*Fs)(nil)
|
_ fs.PutStreamer = (*Fs)(nil)
|
||||||
_ fs.Copier = (*Fs)(nil)
|
_ fs.Copier = (*Fs)(nil)
|
||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
_ fs.DirMover = (*Fs)(nil)
|
_ fs.DirMover = (*Fs)(nil)
|
||||||
_ fs.DirCacheFlusher = (*Fs)(nil)
|
_ fs.DirCacheFlusher = (*Fs)(nil)
|
||||||
_ fs.DirChangeNotifier = (*Fs)(nil)
|
_ fs.ChangeNotifier = (*Fs)(nil)
|
||||||
_ fs.PutUncheckeder = (*Fs)(nil)
|
_ fs.PutUncheckeder = (*Fs)(nil)
|
||||||
_ fs.MergeDirser = (*Fs)(nil)
|
_ fs.MergeDirser = (*Fs)(nil)
|
||||||
_ fs.Object = (*Object)(nil)
|
_ fs.Object = (*Object)(nil)
|
||||||
_ fs.MimeTyper = &Object{}
|
_ fs.MimeTyper = &Object{}
|
||||||
)
|
)
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -54,7 +54,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -54,7 +54,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -51,7 +51,7 @@ func TestFsMove(t *testing.T) { fstests.TestFsMove(t) }
|
|||||||
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
func TestFsDirMove(t *testing.T) { fstests.TestFsDirMove(t) }
|
||||||
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
func TestFsRmdirFull(t *testing.T) { fstests.TestFsRmdirFull(t) }
|
||||||
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
func TestFsPrecision(t *testing.T) { fstests.TestFsPrecision(t) }
|
||||||
func TestFsDirChangeNotify(t *testing.T) { fstests.TestFsDirChangeNotify(t) }
|
func TestFsChangeNotify(t *testing.T) { fstests.TestFsChangeNotify(t) }
|
||||||
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
func TestObjectString(t *testing.T) { fstests.TestObjectString(t) }
|
||||||
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
func TestObjectFs(t *testing.T) { fstests.TestObjectFs(t) }
|
||||||
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
func TestObjectRemote(t *testing.T) { fstests.TestObjectRemote(t) }
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncw/rclone/fs"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -178,11 +179,11 @@ func TestDirCacheFlush(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// expect newly created "subdir" on remote to not show up
|
// expect newly created "subdir" on remote to not show up
|
||||||
root.ForgetPath("otherdir")
|
root.ForgetPath("otherdir", fs.EntryDirectory)
|
||||||
run.readLocal(t, localDm, "")
|
run.readLocal(t, localDm, "")
|
||||||
assert.Equal(t, dm, localDm, "expected vs fuse mount")
|
assert.Equal(t, dm, localDm, "expected vs fuse mount")
|
||||||
|
|
||||||
root.ForgetPath("dir")
|
root.ForgetPath("dir", fs.EntryDirectory)
|
||||||
dm = newDirMap("otherdir/|otherdir/file 1|dir/|dir/file 1|dir/subdir/")
|
dm = newDirMap("otherdir/|otherdir/file 1|dir/|dir/file 1|dir/subdir/")
|
||||||
run.readLocal(t, localDm, "")
|
run.readLocal(t, localDm, "")
|
||||||
assert.Equal(t, dm, localDm, "expected vs fuse mount")
|
assert.Equal(t, dm, localDm, "expected vs fuse mount")
|
||||||
|
31
fs/fs.go
31
fs/fs.go
@ -19,6 +19,9 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// EntryType can be associated with remote paths to identify their type
|
||||||
|
type EntryType int
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const (
|
const (
|
||||||
// ModTimeNotSupported is a very large precision value to show
|
// ModTimeNotSupported is a very large precision value to show
|
||||||
@ -26,6 +29,10 @@ const (
|
|||||||
ModTimeNotSupported = 100 * 365 * 24 * time.Hour
|
ModTimeNotSupported = 100 * 365 * 24 * time.Hour
|
||||||
// MaxLevel is a sentinel representing an infinite depth for listings
|
// MaxLevel is a sentinel representing an infinite depth for listings
|
||||||
MaxLevel = math.MaxInt32
|
MaxLevel = math.MaxInt32
|
||||||
|
// EntryDirectory should be used to classify remote paths in directories
|
||||||
|
EntryDirectory EntryType = iota // 0
|
||||||
|
// EntryObject should be used to classify remote paths in objects
|
||||||
|
EntryObject // 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// Globals
|
// Globals
|
||||||
@ -303,10 +310,10 @@ type Features struct {
|
|||||||
// If destination exists then return fs.ErrorDirExists
|
// If destination exists then return fs.ErrorDirExists
|
||||||
DirMove func(src Fs, srcRemote, dstRemote string) error
|
DirMove func(src Fs, srcRemote, dstRemote string) error
|
||||||
|
|
||||||
// DirChangeNotify calls the passed function with a path
|
// ChangeNotify calls the passed function with a path
|
||||||
// of a directory that has had changes. If the implementation
|
// that has had changes. If the implementation
|
||||||
// uses polling, it should adhere to the given interval.
|
// uses polling, it should adhere to the given interval.
|
||||||
DirChangeNotify func(func(string), time.Duration) chan bool
|
ChangeNotify func(func(string, EntryType), time.Duration) chan bool
|
||||||
|
|
||||||
// UnWrap returns the Fs that this Fs is wrapping
|
// UnWrap returns the Fs that this Fs is wrapping
|
||||||
UnWrap func() Fs
|
UnWrap func() Fs
|
||||||
@ -423,8 +430,8 @@ func (ft *Features) Fill(f Fs) *Features {
|
|||||||
if do, ok := f.(DirMover); ok {
|
if do, ok := f.(DirMover); ok {
|
||||||
ft.DirMove = do.DirMove
|
ft.DirMove = do.DirMove
|
||||||
}
|
}
|
||||||
if do, ok := f.(DirChangeNotifier); ok {
|
if do, ok := f.(ChangeNotifier); ok {
|
||||||
ft.DirChangeNotify = do.DirChangeNotify
|
ft.ChangeNotify = do.ChangeNotify
|
||||||
}
|
}
|
||||||
if do, ok := f.(UnWrapper); ok {
|
if do, ok := f.(UnWrapper); ok {
|
||||||
ft.UnWrap = do.UnWrap
|
ft.UnWrap = do.UnWrap
|
||||||
@ -480,8 +487,8 @@ func (ft *Features) Mask(f Fs) *Features {
|
|||||||
if mask.DirMove == nil {
|
if mask.DirMove == nil {
|
||||||
ft.DirMove = nil
|
ft.DirMove = nil
|
||||||
}
|
}
|
||||||
if mask.DirChangeNotify == nil {
|
if mask.ChangeNotify == nil {
|
||||||
ft.DirChangeNotify = nil
|
ft.ChangeNotify = nil
|
||||||
}
|
}
|
||||||
// if mask.UnWrap == nil {
|
// if mask.UnWrap == nil {
|
||||||
// ft.UnWrap = nil
|
// ft.UnWrap = nil
|
||||||
@ -583,12 +590,12 @@ type DirMover interface {
|
|||||||
DirMove(src Fs, srcRemote, dstRemote string) error
|
DirMove(src Fs, srcRemote, dstRemote string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirChangeNotifier is an optional interface for Fs
|
// ChangeNotifier is an optional interface for Fs
|
||||||
type DirChangeNotifier interface {
|
type ChangeNotifier interface {
|
||||||
// DirChangeNotify calls the passed function with a path
|
// ChangeNotify calls the passed function with a path
|
||||||
// of a directory that has had changes. If the implementation
|
// that has had changes. If the implementation
|
||||||
// uses polling, it should adhere to the given interval.
|
// uses polling, it should adhere to the given interval.
|
||||||
DirChangeNotify(func(string), time.Duration) chan bool
|
ChangeNotify(func(string, EntryType), time.Duration) chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnWrapper is an optional interfaces for Fs
|
// UnWrapper is an optional interfaces for Fs
|
||||||
|
@ -685,34 +685,51 @@ func TestFsPrecision(t *testing.T) {
|
|||||||
// FIXME check expected precision
|
// FIXME check expected precision
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFsDirChangeNotify tests that changes to directories are properly
|
// TestFsChangeNotify tests that changes are properly
|
||||||
// propagated
|
// propagated
|
||||||
//
|
//
|
||||||
// go test -v -remote TestDrive: -run '^Test(Setup|Init|FsDirChangeNotify)$' -verbose
|
// go test -v -remote TestDrive: -run '^Test(Setup|Init|FsChangeNotify)$' -verbose
|
||||||
func TestFsDirChangeNotify(t *testing.T) {
|
func TestFsChangeNotify(t *testing.T) {
|
||||||
skipIfNotOk(t)
|
skipIfNotOk(t)
|
||||||
|
|
||||||
// Check have DirChangeNotify
|
// Check have ChangeNotify
|
||||||
doDirChangeNotify := remote.Features().DirChangeNotify
|
doChangeNotify := remote.Features().ChangeNotify
|
||||||
if doDirChangeNotify == nil {
|
if doChangeNotify == nil {
|
||||||
t.Skip("FS has no DirChangeNotify interface")
|
t.Skip("FS has no ChangeNotify interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := operations.Mkdir(remote, "dir")
|
err := operations.Mkdir(remote, "dir")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
changes := []string{}
|
dirChanges := []string{}
|
||||||
quitChannel := doDirChangeNotify(func(x string) {
|
objChanges := []string{}
|
||||||
changes = append(changes, x)
|
quitChannel := doChangeNotify(func(x string, e fs.EntryType) {
|
||||||
|
if e == fs.EntryDirectory {
|
||||||
|
dirChanges = append(dirChanges, x)
|
||||||
|
} else if e == fs.EntryObject {
|
||||||
|
objChanges = append(objChanges, x)
|
||||||
|
}
|
||||||
}, time.Second)
|
}, time.Second)
|
||||||
defer func() { close(quitChannel) }()
|
defer func() { close(quitChannel) }()
|
||||||
|
|
||||||
err = operations.Mkdir(remote, "dir/subdir")
|
for _, idx := range []int{1, 3, 2} {
|
||||||
require.NoError(t, err)
|
err = operations.Mkdir(remote, fmt.Sprintf("dir/subdir%d", idx))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
contents := fstest.RandomString(100)
|
||||||
|
buf := bytes.NewBufferString(contents)
|
||||||
|
|
||||||
assert.Equal(t, []string{"dir"}, changes)
|
for _, idx := range []int{2, 4, 3} {
|
||||||
|
obji := object.NewStaticObjectInfo(fmt.Sprintf("dir/file%d", idx), time.Now(), int64(buf.Len()), true, nil, nil)
|
||||||
|
_, err = remote.Put(buf, obji)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"dir/subdir1", "dir/subdir3", "dir/subdir2"}, dirChanges)
|
||||||
|
assert.Equal(t, []string{"dir/file2", "dir/file4", "dir/file3"}, objChanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestObjectString tests the Object String method
|
// TestObjectString tests the Object String method
|
||||||
|
10
vfs/dir.go
10
vfs/dir.go
@ -95,7 +95,7 @@ func (d *Dir) Node() Node {
|
|||||||
// ForgetAll ensures the directory and all its children are purged
|
// ForgetAll ensures the directory and all its children are purged
|
||||||
// from the cache.
|
// from the cache.
|
||||||
func (d *Dir) ForgetAll() {
|
func (d *Dir) ForgetAll() {
|
||||||
d.ForgetPath("")
|
d.ForgetPath("", fs.EntryDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForgetPath clears the cache for itself and all subdirectories if
|
// ForgetPath clears the cache for itself and all subdirectories if
|
||||||
@ -103,9 +103,13 @@ func (d *Dir) ForgetAll() {
|
|||||||
// directory it is called from.
|
// directory it is called from.
|
||||||
// It is not possible to traverse the directory tree upwards, i.e.
|
// It is not possible to traverse the directory tree upwards, i.e.
|
||||||
// you cannot clear the cache for the Dir's ancestors or siblings.
|
// you cannot clear the cache for the Dir's ancestors or siblings.
|
||||||
func (d *Dir) ForgetPath(relativePath string) {
|
func (d *Dir) ForgetPath(relativePath string, entryType fs.EntryType) {
|
||||||
|
// if we are requested to forget a file, we use its parent
|
||||||
absPath := path.Join(d.path, relativePath)
|
absPath := path.Join(d.path, relativePath)
|
||||||
if absPath == "." {
|
if entryType != fs.EntryDirectory {
|
||||||
|
absPath = path.Dir(absPath)
|
||||||
|
}
|
||||||
|
if absPath == "." || absPath == "/" {
|
||||||
absPath = ""
|
absPath = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncw/rclone/fs"
|
||||||
"github.com/ncw/rclone/fstest"
|
"github.com/ncw/rclone/fstest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -113,11 +114,11 @@ func TestDirForgetPath(t *testing.T) {
|
|||||||
assert.Equal(t, 1, len(root.items))
|
assert.Equal(t, 1, len(root.items))
|
||||||
assert.Equal(t, 1, len(dir.items))
|
assert.Equal(t, 1, len(dir.items))
|
||||||
|
|
||||||
root.ForgetPath("dir")
|
root.ForgetPath("dir", fs.EntryDirectory)
|
||||||
assert.Equal(t, 1, len(root.items))
|
assert.Equal(t, 1, len(root.items))
|
||||||
assert.Equal(t, 0, len(dir.items))
|
assert.Equal(t, 0, len(dir.items))
|
||||||
|
|
||||||
root.ForgetPath("not/in/cache")
|
root.ForgetPath("not/in/cache", fs.EntryDirectory)
|
||||||
assert.Equal(t, 1, len(root.items))
|
assert.Equal(t, 1, len(root.items))
|
||||||
assert.Equal(t, 0, len(dir.items))
|
assert.Equal(t, 0, len(dir.items))
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ func New(f fs.Fs, opt *Options) *VFS {
|
|||||||
|
|
||||||
// Start polling if required
|
// Start polling if required
|
||||||
if vfs.Opt.PollInterval > 0 {
|
if vfs.Opt.PollInterval > 0 {
|
||||||
if do := vfs.f.Features().DirChangeNotify; do != nil {
|
if do := vfs.f.Features().ChangeNotify; do != nil {
|
||||||
do(vfs.root.ForgetPath, vfs.Opt.PollInterval)
|
do(vfs.root.ForgetPath, vfs.Opt.PollInterval)
|
||||||
} else {
|
} else {
|
||||||
fs.Infof(f, "poll-interval is not supported by this remote")
|
fs.Infof(f, "poll-interval is not supported by this remote")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user