cluster.thumbnail.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. package main
  2. /*
  3. Thumbnails of cluster:/ files
  4. cluster:/ is a buffered drive, so the ordinary renderers cannot open its
  5. files, and a .metadata/.cache folder inside it would be a real cluster
  6. file that gets placed and replicated like any other. Instead the drive
  7. implements arozfs.ThumbnailRenderer through the backend below:
  8. - the key of a thumbnail is the SHA-256 of the file from its metadata
  9. record, so the cache on this host is keyed by content and a changed
  10. file never shows an old thumbnail
  11. - the image is rendered by a node holding a copy, this node first
  12. (mod/cluster/storage/thumbnail.go), with metadata.RenderLocalFile on
  13. that node's own copy; only the thumbnail crosses the network
  14. Earlier builds created empty .metadata/.cache folders in the namespace
  15. while trying to render. clusterRemoveThumbnailFolders removes them.
  16. */
  17. import (
  18. "context"
  19. "errors"
  20. "path"
  21. "strconv"
  22. "time"
  23. "imuslab.com/arozos/mod/filesystem/arozfs"
  24. "imuslab.com/arozos/mod/filesystem/metadata"
  25. )
  26. const (
  27. //clusterThumbnailTimeout bounds one thumbnail request, the frame grab of
  28. //a video on a slow holder included
  29. clusterThumbnailTimeout = 90 * time.Second
  30. //thumbnailCacheMaxAge is how long an unused thumbnail is kept on this host
  31. thumbnailCacheMaxAge = 30 * 24 * time.Hour
  32. )
  33. // ThumbnailKey identifies the content of a namespace file. It completes the
  34. // clusterfs.ThumbnailBackend interface.
  35. func (b *clusterBackend) ThumbnailKey(logical string) (string, error) {
  36. if clusterMetadata == nil {
  37. return "", errors.New("this node is not in a cluster")
  38. }
  39. rec, err := clusterMetadata.Stat(logical)
  40. if err != nil {
  41. return "", err
  42. }
  43. if rec.IsDir {
  44. return "", arozfs.ErrNoThumbnail
  45. }
  46. return clusterThumbnailKey(rec.Checksum, rec.ID, rec.Size, rec.ModTime), nil
  47. }
  48. // clusterThumbnailKey is the checksum of the file when it has one. A record
  49. // still being written has none yet, and is keyed by its identity instead.
  50. func clusterThumbnailKey(checksum string, fileID string, size int64, modTime int64) string {
  51. if checksum != "" {
  52. return "cluster-sha256:" + checksum
  53. }
  54. return "cluster-file:" + fileID + ":" + strconv.FormatInt(size, 10) + ":" + strconv.FormatInt(modTime, 10)
  55. }
  56. // RenderThumbnail has a node holding a copy render the thumbnail. It
  57. // completes the clusterfs.ThumbnailBackend interface.
  58. func (b *clusterBackend) RenderThumbnail(logical string) ([]byte, error) {
  59. ctx, cancel := context.WithTimeout(context.Background(), clusterThumbnailTimeout)
  60. defer cancel()
  61. return b.s.Thumbnail(ctx, logical, metadata.ThumbnailFeature(logical))
  62. }
  63. // clusterRenderThumbnail renders a copy on one of this node's volumes, for
  64. // this node or for another member asking it.
  65. func clusterRenderThumbnail(osPath string) ([]byte, error) {
  66. return metadata.RenderLocalFile(osPath)
  67. }
  68. // clusterRemoveThumbnailFolders removes the empty .metadata/.cache folders an
  69. // earlier build left in the namespace, and the .metadata folder around one
  70. // when nothing else (a trash folder, say) is in it. Only the master node does
  71. // it, so the members do not race each other over the same records.
  72. func clusterRemoveThumbnailFolders() int {
  73. if clusterStorage == nil || clusterMetadata == nil || !clusterManager.InCluster() || !clusterMetadata.IsLeader() {
  74. return 0
  75. }
  76. removed := 0
  77. for _, rec := range clusterMetadata.ListSubtree("/") {
  78. if !clusterIsThumbnailFolder(rec.Path, rec.IsDir) {
  79. continue
  80. }
  81. if len(clusterMetadata.ListSubtree(rec.Path)) > 0 {
  82. //Something was put in there; leave it alone
  83. continue
  84. }
  85. if clusterStorage.Remove(rec.Path, false) != nil {
  86. continue
  87. }
  88. removed++
  89. parent := path.Dir(rec.Path)
  90. if children, err := clusterMetadata.ListDir(parent); err == nil && len(children) == 0 {
  91. clusterStorage.Remove(parent, false)
  92. }
  93. }
  94. if removed > 0 {
  95. systemWideLogger.PrintAndLog("Cluster", "Removed "+strconv.Itoa(removed)+" unused thumbnail folder(s) from cluster:/", nil)
  96. }
  97. return removed
  98. }
  99. // clusterIsThumbnailFolder reports whether a namespace path is a
  100. // .metadata/.cache folder.
  101. func clusterIsThumbnailFolder(p string, isDir bool) bool {
  102. return isDir && path.Base(p) == ".cache" && path.Base(path.Dir(p)) == ".metadata"
  103. }