You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
4.6 KiB

3 years ago
  1. package shell
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "github.com/chrislusf/seaweedfs/weed/filer"
  7. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  8. "github.com/chrislusf/seaweedfs/weed/util"
  9. "io"
  10. "path/filepath"
  11. "strings"
  12. )
  13. func init() {
  14. Commands = append(Commands, &commandRemoteUncache{})
  15. }
  16. type commandRemoteUncache struct {
  17. }
  18. func (c *commandRemoteUncache) Name() string {
  19. return "remote.uncache"
  20. }
  21. func (c *commandRemoteUncache) Help() string {
  22. return `keep the metadata but remote cache the file content for mounted directories or files
  23. This is designed to run regularly. So you can add it to some cronjob.
  24. If a file is not synchronized with the remote copy, the file will be skipped to avoid loss of data.
  25. remote.uncache -dir=/xxx
  26. remote.uncache -dir=/xxx/some/sub/dir
  27. remote.uncache -dir=/xxx/some/sub/dir -include=*.pdf
  28. remote.uncache -dir=/xxx/some/sub/dir -exclude=*.txt
  29. remote.uncache -minSize=1024000 # uncache files larger than 100K
  30. remote.uncache -minAge=3600 # uncache files older than 1 hour
  31. `
  32. }
  33. func (c *commandRemoteUncache) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  34. remoteUnmountCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  35. dir := remoteUnmountCommand.String("dir", "", "a directory in filer")
  36. fileFiler := newFileFilter(remoteUnmountCommand)
  37. if err = remoteUnmountCommand.Parse(args); err != nil {
  38. return nil
  39. }
  40. mappings, listErr := filer.ReadMountMappings(commandEnv.option.GrpcDialOption, commandEnv.option.FilerAddress)
  41. if listErr != nil {
  42. return listErr
  43. }
  44. if *dir == "" {
  45. jsonPrintln(writer, mappings)
  46. fmt.Fprintln(writer, "need to specify '-dir' option")
  47. return nil
  48. }
  49. var localMountedDir string
  50. for k := range mappings.Mappings {
  51. if strings.HasPrefix(*dir, k) {
  52. localMountedDir = k
  53. }
  54. }
  55. if localMountedDir == "" {
  56. jsonPrintln(writer, mappings)
  57. fmt.Fprintf(writer, "%s is not mounted\n", *dir)
  58. return nil
  59. }
  60. // pull content from remote
  61. if err = c.uncacheContentData(commandEnv, writer, util.FullPath(*dir), fileFiler); err != nil {
  62. return fmt.Errorf("cache content data: %v", err)
  63. }
  64. return nil
  65. }
  66. func (c *commandRemoteUncache) uncacheContentData(commandEnv *CommandEnv, writer io.Writer, dirToCache util.FullPath, fileFilter *FileFilter) error {
  67. return recursivelyTraverseDirectory(commandEnv, dirToCache, func(dir util.FullPath, entry *filer_pb.Entry) bool {
  68. if !mayHaveCachedToLocal(entry) {
  69. return true // true means recursive traversal should continue
  70. }
  71. if fileFilter.matches(entry) {
  72. return true
  73. }
  74. if entry.RemoteEntry.LastLocalSyncTsNs/1e9 < entry.Attributes.Mtime {
  75. return true // should not uncache an entry that is not synchronized with remote
  76. }
  77. entry.RemoteEntry.LastLocalSyncTsNs = 0
  78. entry.Chunks = nil
  79. println(dir, entry.Name)
  80. err := commandEnv.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  81. _, updateErr := client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
  82. Directory: string(dir),
  83. Entry: entry,
  84. })
  85. return updateErr
  86. })
  87. if err != nil {
  88. fmt.Fprintf(writer, "uncache %+v: %v\n", dir.Child(entry.Name), err)
  89. return false
  90. }
  91. return true
  92. })
  93. }
  94. type FileFilter struct {
  95. include *string
  96. exclude *string
  97. minSize *int64
  98. maxSize *int64
  99. minAge *int64
  100. maxAge *int64
  101. }
  102. func newFileFilter(remoteMountCommand *flag.FlagSet) (ff *FileFilter) {
  103. ff = &FileFilter{}
  104. ff.include = remoteMountCommand.String("include", "", "pattens of file names, e.g., *.pdf, *.html, ab?d.txt")
  105. ff.exclude = remoteMountCommand.String("exclude", "", "pattens of file names, e.g., *.pdf, *.html, ab?d.txt")
  106. ff.minSize = remoteMountCommand.Int64("minSize", -1, "minimum file size in bytes")
  107. ff.maxSize = remoteMountCommand.Int64("maxSize", -1, "maximum file size in bytes")
  108. ff.minAge = remoteMountCommand.Int64("minAge", -1, "minimum file age in seconds")
  109. ff.maxAge = remoteMountCommand.Int64("maxAge", -1, "maximum file age in seconds")
  110. return
  111. }
  112. func (ff *FileFilter) matches(entry *filer_pb.Entry) bool {
  113. if *ff.include != "" {
  114. if ok, _ := filepath.Match(*ff.include, entry.Name); !ok {
  115. return true
  116. }
  117. }
  118. if *ff.exclude != "" {
  119. if ok, _ := filepath.Match(*ff.exclude, entry.Name); ok {
  120. return true
  121. }
  122. }
  123. if *ff.minSize != -1 {
  124. if int64(entry.Attributes.FileSize) < *ff.minSize {
  125. return false
  126. }
  127. }
  128. if *ff.maxSize != -1 {
  129. if int64(entry.Attributes.FileSize) > *ff.maxSize {
  130. return false
  131. }
  132. }
  133. if *ff.minAge != -1 {
  134. if entry.Attributes.Crtime < *ff.minAge {
  135. return false
  136. }
  137. }
  138. if *ff.maxAge != -1 {
  139. if entry.Attributes.Crtime > *ff.maxAge {
  140. return false
  141. }
  142. }
  143. return false
  144. }