github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/cache/locations/fs.go (about)

     1  package locations
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"sync"
     9  
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
    11  )
    12  
    13  const FS_PERMISSIONS = 0755
    14  
    15  // Each Storer implementation is global and thread-safe to save resources
    16  // when reading/writing large number of items
    17  var fsInstance *fileSystem
    18  var fsInitOnce sync.Once
    19  
    20  // fsReadWriteCloser is a wrapper around os.File that removes any file locks
    21  // when Close() is called
    22  type fsReadWriteCloser struct {
    23  	*os.File
    24  }
    25  
    26  // Close closes the underlying os.File and removes file lock
    27  func (f *fsReadWriteCloser) Close() error {
    28  	// We unlock file when the caller is done
    29  	if err := file.Unlock(f.File); err != nil {
    30  		return err
    31  	}
    32  	return f.File.Close()
    33  }
    34  
    35  type fileSystem struct{}
    36  
    37  // FileSystem returns an instance of fileSystem Storer, ready to be used
    38  func FileSystem() (*fileSystem, error) {
    39  	fsInitOnce.Do(func() {
    40  		fsInstance = new(fileSystem)
    41  	})
    42  	return fsInstance, nil
    43  }
    44  
    45  // Writer returns io.WriterCloser for the item at given path
    46  func (l *fileSystem) Writer(path string) (io.WriteCloser, error) {
    47  	if err := l.makeParentDirectories(path); err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	output, err := file.WaitOnLock(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	return &fsReadWriteCloser{output}, nil
    57  }
    58  
    59  // Reader returns io.ReaderCloser for the item at given path
    60  func (l *fileSystem) Reader(path string) (io.ReadCloser, error) {
    61  	input, err := file.WaitOnLock(path, os.O_RDWR)
    62  	if err != nil {
    63  		if errors.Is(err, os.ErrNotExist) {
    64  			return nil, ErrNotFound
    65  		}
    66  		return nil, err
    67  	}
    68  
    69  	return &fsReadWriteCloser{input}, nil
    70  }
    71  
    72  // Remove removes the item at given path
    73  func (l *fileSystem) Remove(path string) error {
    74  	if err := os.Remove(path); err != nil {
    75  		return err
    76  	}
    77  
    78  	dirPath, _ := filepath.Split(path)
    79  	if empty, _ := file.IsFolderEmpty(dirPath); empty {
    80  		// we ignore the error
    81  		os.RemoveAll(dirPath)
    82  	}
    83  	return nil
    84  }
    85  
    86  func (l *fileSystem) Stat(path string) (*ItemInfo, error) {
    87  	info, err := os.Stat(path)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	return &ItemInfo{
    93  		fileSize: int(info.Size()),
    94  		name:     info.Name(),
    95  	}, err
    96  }
    97  
    98  func (l *fileSystem) makeParentDirectories(path string) error {
    99  	dirPath, _ := filepath.Split(path)
   100  	return os.MkdirAll(dirPath, FS_PERMISSIONS)
   101  }