record.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package keyvalue
  2. import (
  3. "sync"
  4. "sync/atomic"
  5. "time"
  6. "github.com/hack-pad/hackpadfs"
  7. "github.com/hack-pad/hackpadfs/keyvalue/blob"
  8. )
  9. // FileRecord represents a file inside a Store.
  10. // A FileRecord's receivers may only be called once and their return values cached by the wrapping FS. Therefore, each receiver must return consistent values unless otherwise specified.
  11. //
  12. // NOTE: Does not require retrieving the file's name. The name can be stored separately to simplify your store.
  13. type FileRecord interface {
  14. // Data returns the Blob representing a copy of this file's contents. Returns an error if file is a directory.
  15. Data() (blob.Blob, error)
  16. // ReadDir returns this file's directory entries. Returns an error if not a directory or failed during retrieval.
  17. ReadDirNames() ([]string, error)
  18. // Size returns the number of bytes in this file's contents.
  19. // May return the size at initial fetch time, rather than at call time.
  20. Size() int64
  21. // Mode returns this file's FileMode.
  22. Mode() hackpadfs.FileMode
  23. // ModTime returns this file's last modified time.
  24. ModTime() time.Time
  25. // Sys returns the underlying data source (can return nil)
  26. Sys() interface{}
  27. }
  28. var (
  29. _ FileRecord = &BaseFileRecord{}
  30. )
  31. // BaseFileRecord is a FileRecord with a convenient constructor for easier Store implementations.
  32. type BaseFileRecord struct {
  33. modTime time.Time
  34. sys interface{}
  35. getData func() (blob.Blob, error)
  36. getDirNames func() ([]string, error)
  37. initialSize int64
  38. mode hackpadfs.FileMode
  39. }
  40. // NewBaseFileRecord returns a new BaseFileRecord for the given file's metadata and getters.
  41. // getData and getDirNames may be set to nil if not applicable to this file's type.
  42. //
  43. // Initial size is the currently known byte size of the record. This value is used for optimized Stat() calls.
  44. // Sys may be set to nil, it's returned as the result of FileInfo.Sys().
  45. // 'getData' must return the contents of the file or an error if retrieval fails, but may be nil if this is a directory.
  46. // 'getDirNames' must return this directory's child names, but may be nil if this is a regular file.
  47. func NewBaseFileRecord(
  48. initialSize int64,
  49. modTime time.Time,
  50. mode hackpadfs.FileMode,
  51. sys interface{},
  52. getData func() (blob.Blob, error),
  53. getDirNames func() ([]string, error),
  54. ) *BaseFileRecord {
  55. return &BaseFileRecord{
  56. getData: getData,
  57. getDirNames: getDirNames,
  58. initialSize: initialSize,
  59. modTime: modTime,
  60. mode: mode,
  61. sys: sys,
  62. }
  63. }
  64. // Data implements keyvalue.FileRecord
  65. func (b *BaseFileRecord) Data() (blob.Blob, error) {
  66. if b.getData == nil {
  67. if b.mode.IsDir() {
  68. return nil, hackpadfs.ErrIsDir
  69. }
  70. return nil, hackpadfs.ErrNotImplemented
  71. }
  72. return b.getData()
  73. }
  74. // ReadDirNames implements keyvalue.FileRecord
  75. func (b *BaseFileRecord) ReadDirNames() ([]string, error) {
  76. if b.getDirNames == nil {
  77. if !b.mode.IsDir() {
  78. return nil, hackpadfs.ErrNotDir
  79. }
  80. return nil, hackpadfs.ErrNotImplemented
  81. }
  82. return b.getDirNames()
  83. }
  84. // Size implements keyvalue.FileRecord
  85. func (b *BaseFileRecord) Size() int64 {
  86. return b.initialSize
  87. }
  88. // Mode implements keyvalue.FileRecord
  89. func (b *BaseFileRecord) Mode() hackpadfs.FileMode {
  90. return b.mode
  91. }
  92. // ModTime implements keyvalue.FileRecord
  93. func (b *BaseFileRecord) ModTime() time.Time {
  94. return b.modTime
  95. }
  96. // Sys implements keyvalue.FileRecord
  97. func (b *BaseFileRecord) Sys() interface{} {
  98. return b.sys
  99. }
  100. type runOnceFileRecord struct {
  101. modTime time.Time
  102. record FileRecord
  103. data blob.Blob
  104. sys interface{}
  105. dataErr error
  106. dirNamesErr error
  107. dirNames []string
  108. size int64
  109. dataDone atomic.Bool
  110. dataOnce sync.Once
  111. dirNamesOnce sync.Once
  112. modeOnce sync.Once
  113. modTimeOnce sync.Once
  114. sizeOnce sync.Once
  115. sysOnce sync.Once
  116. mode hackpadfs.FileMode
  117. }
  118. func (r *runOnceFileRecord) Data() (blob.Blob, error) {
  119. r.dataOnce.Do(func() {
  120. r.data, r.dataErr = r.record.Data()
  121. r.dataDone.Store(true)
  122. })
  123. return r.data, r.dataErr
  124. }
  125. func (r *runOnceFileRecord) ReadDirNames() ([]string, error) {
  126. r.dirNamesOnce.Do(func() {
  127. r.dirNames, r.dirNamesErr = r.record.ReadDirNames()
  128. })
  129. return r.dirNames, r.dirNamesErr
  130. }
  131. func (r *runOnceFileRecord) Size() int64 {
  132. r.sizeOnce.Do(func() {
  133. r.size = r.record.Size()
  134. })
  135. if r.dataDone.Load() {
  136. return int64(r.data.Len())
  137. }
  138. return r.size
  139. }
  140. func (r *runOnceFileRecord) Mode() hackpadfs.FileMode {
  141. r.modeOnce.Do(func() {
  142. r.mode = r.record.Mode()
  143. })
  144. return r.mode
  145. }
  146. func (r *runOnceFileRecord) ModTime() time.Time {
  147. r.modTimeOnce.Do(func() {
  148. r.modTime = r.record.ModTime()
  149. })
  150. return r.modTime
  151. }
  152. func (r *runOnceFileRecord) Sys() interface{} {
  153. r.sysOnce.Do(func() {
  154. r.sys = r.record.Sys()
  155. })
  156. return r.sys
  157. }