mirror of
				https://github.com/filebrowser/filebrowser.git
				synced 2025-10-31 17:23:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			111 lines
		
	
	
		
			2.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			111 lines
		
	
	
		
			2.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package diskcache
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/sha1" //nolint:gosec
 | |
| 	"encoding/hex"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/spf13/afero"
 | |
| )
 | |
| 
 | |
| type FileCache struct {
 | |
| 	fs afero.Fs
 | |
| 
 | |
| 	// granular locks
 | |
| 	scopedLocks struct {
 | |
| 		sync.Mutex
 | |
| 		sync.Once
 | |
| 		locks map[string]sync.Locker
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func New(fs afero.Fs, root string) *FileCache {
 | |
| 	return &FileCache{
 | |
| 		fs: afero.NewBasePathFs(fs, root),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (f *FileCache) Store(ctx context.Context, key string, value []byte) error {
 | |
| 	mu := f.getScopedLocks(key)
 | |
| 	mu.Lock()
 | |
| 	defer mu.Unlock()
 | |
| 
 | |
| 	fileName := f.getFileName(key)
 | |
| 	if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (f *FileCache) Load(ctx context.Context, key string) (value []byte, exist bool, err error) {
 | |
| 	r, ok, err := f.open(key)
 | |
| 	if err != nil || !ok {
 | |
| 		return nil, ok, err
 | |
| 	}
 | |
| 	defer r.Close()
 | |
| 
 | |
| 	value, err = ioutil.ReadAll(r)
 | |
| 	if err != nil {
 | |
| 		return nil, false, err
 | |
| 	}
 | |
| 	return value, true, nil
 | |
| }
 | |
| 
 | |
| func (f *FileCache) Delete(ctx context.Context, key string) error {
 | |
| 	mu := f.getScopedLocks(key)
 | |
| 	mu.Lock()
 | |
| 	defer mu.Unlock()
 | |
| 
 | |
| 	fileName := f.getFileName(key)
 | |
| 	if err := f.fs.Remove(fileName); err != nil && !errors.Is(err, os.ErrNotExist) {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (f *FileCache) open(key string) (afero.File, bool, error) {
 | |
| 	fileName := f.getFileName(key)
 | |
| 	file, err := f.fs.Open(fileName)
 | |
| 	if err != nil {
 | |
| 		if errors.Is(err, os.ErrNotExist) {
 | |
| 			return nil, false, nil
 | |
| 		}
 | |
| 		return nil, false, err
 | |
| 	}
 | |
| 
 | |
| 	return file, true, nil
 | |
| }
 | |
| 
 | |
| // getScopedLocks pull lock from the map if found or create a new one
 | |
| func (f *FileCache) getScopedLocks(key string) (lock sync.Locker) {
 | |
| 	f.scopedLocks.Do(func() { f.scopedLocks.locks = map[string]sync.Locker{} })
 | |
| 
 | |
| 	f.scopedLocks.Lock()
 | |
| 	lock, ok := f.scopedLocks.locks[key]
 | |
| 	if !ok {
 | |
| 		lock = &sync.Mutex{}
 | |
| 		f.scopedLocks.locks[key] = lock
 | |
| 	}
 | |
| 	f.scopedLocks.Unlock()
 | |
| 
 | |
| 	return lock
 | |
| }
 | |
| 
 | |
| func (f *FileCache) getFileName(key string) string {
 | |
| 	hasher := sha1.New() //nolint:gosec
 | |
| 	_, _ = hasher.Write([]byte(key))
 | |
| 	hash := hex.EncodeToString(hasher.Sum(nil))
 | |
| 	return fmt.Sprintf("%s/%s/%s", hash[:1], hash[1:3], hash)
 | |
| }
 | 
