github.com/jstaf/onedriver@v0.14.2-0.20240420231225-f07678f9e6ef/fs/content_cache.go (about) 1 package fs 2 3 import ( 4 "io" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "runtime" 9 "sync" 10 ) 11 12 // LoopbackCache stores the content for files under a folder as regular files 13 type LoopbackCache struct { 14 directory string 15 fds sync.Map 16 } 17 18 func NewLoopbackCache(directory string) *LoopbackCache { 19 os.Mkdir(directory, 0700) 20 return &LoopbackCache{ 21 directory: directory, 22 fds: sync.Map{}, 23 } 24 } 25 26 // contentPath returns the path for the given content file 27 func (l *LoopbackCache) contentPath(id string) string { 28 return filepath.Join(l.directory, id) 29 } 30 31 // Get reads a file's content from disk. 32 func (l *LoopbackCache) Get(id string) []byte { 33 content, _ := ioutil.ReadFile(l.contentPath(id)) 34 return content 35 } 36 37 // InsertContent writes file content to disk in a single bulk insert. 38 func (l *LoopbackCache) Insert(id string, content []byte) error { 39 return ioutil.WriteFile(l.contentPath(id), content, 0600) 40 } 41 42 // InsertStream inserts a stream of data 43 func (l *LoopbackCache) InsertStream(id string, reader io.Reader) (int64, error) { 44 fd, err := l.Open(id) 45 if err != nil { 46 return 0, err 47 } 48 return io.Copy(fd, reader) 49 } 50 51 // Delete closes the fd AND deletes content from disk. 52 func (l *LoopbackCache) Delete(id string) error { 53 l.Close(id) 54 return os.Remove(l.contentPath(id)) 55 } 56 57 // Move moves content from one ID to another 58 func (l *LoopbackCache) Move(oldID string, newID string) error { 59 return os.Rename(l.contentPath(oldID), l.contentPath(newID)) 60 } 61 62 // IsOpen returns true if the file is already opened somewhere 63 func (l *LoopbackCache) IsOpen(id string) bool { 64 _, ok := l.fds.Load(id) 65 return ok 66 } 67 68 // HasContent is used to find if we have a file or not in cache (in any state) 69 func (l *LoopbackCache) HasContent(id string) bool { 70 // is it already open? 71 _, ok := l.fds.Load(id) 72 if ok { 73 return ok 74 } 75 // is it on disk? 76 _, err := os.Stat(l.contentPath(id)) 77 return err == nil 78 } 79 80 // Open returns a filehandle for subsequent access 81 func (l *LoopbackCache) Open(id string) (*os.File, error) { 82 if fd, ok := l.fds.Load(id); ok { 83 // already opened, return existing fd 84 return fd.(*os.File), nil 85 } 86 87 fd, err := os.OpenFile(l.contentPath(id), os.O_CREATE|os.O_RDWR, 0600) 88 if err != nil { 89 return nil, err 90 } 91 92 // Since we explicitly want to store *os.Files, we need to prevent the Go 93 // GC from trying to be "helpful" and closing files for us behind the 94 // scenes. 95 // https://github.com/hanwen/go-fuse/issues/371#issuecomment-694799535 96 runtime.SetFinalizer(fd, nil) 97 l.fds.Store(id, fd) 98 return fd, nil 99 } 100 101 // Close closes the currently open fd 102 func (l *LoopbackCache) Close(id string) { 103 if fd, ok := l.fds.Load(id); ok { 104 file := fd.(*os.File) 105 file.Sync() 106 file.Close() 107 l.fds.Delete(id) 108 } 109 }