diff --git a/weed/shell/command_remote_uncache.go b/weed/shell/command_remote_uncache.go index f8763485c..48fd24c03 100644 --- a/weed/shell/command_remote_uncache.go +++ b/weed/shell/command_remote_uncache.go @@ -36,7 +36,8 @@ func (c *commandRemoteUncache) Help() string { remote.uncache -dir=/xxx/some/sub/dir -include=*.pdf remote.uncache -dir=/xxx/some/sub/dir -exclude=*.txt remote.uncache -minSize=1024000 # uncache files larger than 100K - remote.uncache -minAge=3600 # uncache files older than 1 hour + remote.uncache -minAge=3600 # uncache files older than 1 hour (created time) + remote.uncache -minCacheAge=3600 # uncache files older than 1 hour (cached time) ` } @@ -128,12 +129,14 @@ func (c *commandRemoteUncache) uncacheContentData(commandEnv *CommandEnv, writer } type FileFilter struct { - include *string - exclude *string - minSize *int64 - maxSize *int64 - minAge *int64 - maxAge *int64 + include *string + exclude *string + minSize *int64 + maxSize *int64 + minAge *int64 + maxAge *int64 + minCacheAge *int64 + now int64 } func newFileFilter(remoteMountCommand *flag.FlagSet) (ff *FileFilter) { @@ -142,12 +145,17 @@ func newFileFilter(remoteMountCommand *flag.FlagSet) (ff *FileFilter) { ff.exclude = remoteMountCommand.String("exclude", "", "pattens of file names, e.g., *.pdf, *.html, ab?d.txt") ff.minSize = remoteMountCommand.Int64("minSize", -1, "minimum file size in bytes") ff.maxSize = remoteMountCommand.Int64("maxSize", -1, "maximum file size in bytes") - ff.minAge = remoteMountCommand.Int64("minAge", -1, "minimum file age in seconds") - ff.maxAge = remoteMountCommand.Int64("maxAge", -1, "maximum file age in seconds") + ff.minAge = remoteMountCommand.Int64("minAge", -1, "minimum file age in seconds (created time)") + ff.maxAge = remoteMountCommand.Int64("maxAge", -1, "maximum file age in seconds (created time)") + ff.minCacheAge = remoteMountCommand.Int64("minCacheAge", -1, "minimum file cache age in seconds (last cached time)") + ff.now = time.Now().Unix() return } func (ff *FileFilter) matches(entry *filer_pb.Entry) bool { + if entry.Attributes == nil { + return false + } if *ff.include != "" { if ok, _ := filepath.Match(*ff.include, entry.Name); !ok { return false @@ -169,12 +177,21 @@ func (ff *FileFilter) matches(entry *filer_pb.Entry) bool { } } if *ff.minAge != -1 { - if entry.Attributes.Crtime+*ff.minAge > time.Now().Unix() { + if entry.Attributes.Crtime+*ff.minAge > ff.now { return false } } if *ff.maxAge != -1 { - if entry.Attributes.Crtime+*ff.maxAge < time.Now().Unix() { + if entry.Attributes.Crtime+*ff.maxAge < ff.now { + return false + } + } + if *ff.minCacheAge != -1 { + lastCachedTime := entry.Attributes.Crtime + if entry.RemoteEntry != nil && entry.RemoteEntry.LastLocalSyncTsNs > 0 { + lastCachedTime = entry.RemoteEntry.LastLocalSyncTsNs / 1e9 + } + if lastCachedTime+*ff.minCacheAge > ff.now { return false } } diff --git a/weed/shell/command_remote_uncache_test.go b/weed/shell/command_remote_uncache_test.go new file mode 100644 index 000000000..5de572db3 --- /dev/null +++ b/weed/shell/command_remote_uncache_test.go @@ -0,0 +1,104 @@ +package shell + +import ( + "testing" + "time" + + "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" +) + +func TestFileFilter_matches_minCacheAge(t *testing.T) { + now := time.Now().Unix() + + tests := []struct { + name string + minCacheAge int64 + entry *filer_pb.Entry + want bool + }{ + { + name: "no minCacheAge", + minCacheAge: -1, + entry: &filer_pb.Entry{ + Attributes: &filer_pb.FuseAttributes{Crtime: now - 100}, + }, + want: true, + }, + { + name: "recent cache, should not match", + minCacheAge: 3600, + entry: &filer_pb.Entry{ + Attributes: &filer_pb.FuseAttributes{Crtime: now - 7200}, + RemoteEntry: &filer_pb.RemoteEntry{ + LastLocalSyncTsNs: now * 1e9, + }, + }, + want: false, + }, + { + name: "old cache, should match", + minCacheAge: 3600, + entry: &filer_pb.Entry{ + Attributes: &filer_pb.FuseAttributes{Crtime: now - 7200}, + RemoteEntry: &filer_pb.RemoteEntry{ + LastLocalSyncTsNs: (now - 4000) * 1e9, + }, + }, + want: true, + }, + { + name: "no remote entry, uses crtime - recent, should not match", + minCacheAge: 3600, + entry: &filer_pb.Entry{ + Attributes: &filer_pb.FuseAttributes{Crtime: now - 100}, + }, + want: false, + }, + { + name: "no remote entry, uses crtime - old, should match", + minCacheAge: 3600, + entry: &filer_pb.Entry{ + Attributes: &filer_pb.FuseAttributes{Crtime: now - 4000}, + }, + want: true, + }, + { + name: "remote entry with 0 sync ts, uses crtime - recent, should not match", + minCacheAge: 3600, + entry: &filer_pb.Entry{ + Attributes: &filer_pb.FuseAttributes{Crtime: now - 100}, + RemoteEntry: &filer_pb.RemoteEntry{LastLocalSyncTsNs: 0}, + }, + want: false, + }, + { + name: "nil attributes, should not match", + minCacheAge: 3600, + entry: &filer_pb.Entry{ + Attributes: nil, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defaultString := "" + defaultInt64 := int64(-1) + ff := &FileFilter{ + include: &defaultString, + exclude: &defaultString, + minSize: &defaultInt64, + maxSize: &defaultInt64, + minAge: &defaultInt64, + maxAge: &defaultInt64, + minCacheAge: &tt.minCacheAge, + now: now, + } + + if got := ff.matches(tt.entry); got != tt.want { + t.Errorf("FileFilter.matches() = %v, want %v", got, tt.want) + } + }) + } +}