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  }