Files
2026-07-27 19:31:43 +08:00

180 lines
3.9 KiB
Go

package fusereadcache
import (
"context"
"io"
"path/filepath"
"sync"
"litepan/internal/settings"
)
type Service struct {
settings *settings.Service
store *storeLayer
mu sync.Mutex
}
type Options struct {
DataDir string
Settings *settings.Service
}
func New(ctx context.Context, opts Options) (*Service, error) {
root := filepath.Join(opts.DataDir, SubdirName)
if err := validateRoot(root); err != nil {
return nil, err
}
st, err := openStore(ctx, root)
if err != nil {
return nil, err
}
return &Service{settings: opts.Settings, store: st}, nil
}
func (s *Service) Close() error {
if s == nil {
return nil
}
return s.store.Close()
}
func (s *Service) Enabled(ctx context.Context) bool {
return LoadConfig(ctx, s.settings).Enabled
}
func (s *Service) Config(ctx context.Context) Config {
return LoadConfig(ctx, s.settings)
}
type Stats struct {
UsedBytes int64 `json:"used_bytes"`
LimitBytes int64 `json:"limit_bytes"`
BlockCount int64 `json:"block_count"`
RootPath string `json:"root_path"`
}
func (s *Service) Stats(ctx context.Context) (Stats, error) {
if s == nil {
cfg := LoadConfig(ctx, nil)
return Stats{LimitBytes: cfg.MaxBytes}, nil
}
cfg := LoadConfig(ctx, s.settings)
if s.store == nil {
return Stats{LimitBytes: cfg.MaxBytes}, nil
}
used, blocks, err := s.store.stats()
if err != nil {
return Stats{}, err
}
return Stats{
UsedBytes: used,
LimitBytes: cfg.MaxBytes,
BlockCount: blocks,
RootPath: s.store.rootPath(),
}, nil
}
func (s *Service) ReadAt(ctx context.Context, accountID int64, fileID string, dest []byte, off int64, fetch func([]byte, int64) (int, error)) (int, error) {
if len(dest) == 0 {
return 0, nil
}
if s == nil || !s.Enabled(ctx) {
return fetch(dest, off)
}
written := 0
for written < len(dest) {
curOff := off + int64(written)
blockIdx := curOff / BlockSize
blockOff := curOff % BlockSize
need := int64(len(dest) - written)
remainInBlock := BlockSize - blockOff
if need > remainInBlock {
need = remainInBlock
}
blockDest := dest[written : written+int(need)]
s.mu.Lock()
n, ok, err := s.store.loadBlockRange(accountID, fileID, blockIdx, blockOff, blockDest)
s.mu.Unlock()
if err != nil && err != io.EOF {
return written, err
}
if ok {
written += n
if n < len(blockDest) {
return written, nil
}
continue
}
buf := make([]byte, BlockSize)
fetchOff := blockIdx * BlockSize
n, err = fetch(buf, fetchOff)
if n <= 0 {
if written > 0 {
return written, nil
}
return 0, err
}
chunk := buf[:n]
s.mu.Lock()
putErr := s.putBlockWithPolicy(ctx, accountID, fileID, blockIdx, chunk)
s.mu.Unlock()
if putErr != nil {
return written, putErr
}
if int64(len(chunk)) <= blockOff {
if err == io.EOF {
return written, nil
}
return written, err
}
avail := int64(len(chunk)) - blockOff
if need > avail {
need = avail
}
copied := copy(dest[written:], chunk[blockOff:blockOff+need])
written += copied
if err == io.EOF || copied == 0 {
return written, err
}
}
return written, nil
}
func (s *Service) InvalidateFile(_ context.Context, accountID int64, fileID string) error {
if s == nil || s.store == nil || accountID <= 0 || fileID == "" {
return nil
}
s.mu.Lock()
defer s.mu.Unlock()
return s.store.invalidateFile(accountID, fileID)
}
func (s *Service) InvalidateAccount(_ context.Context, accountID int64) error {
if s == nil || s.store == nil || accountID <= 0 {
return nil
}
s.mu.Lock()
defer s.mu.Unlock()
return s.store.invalidateAccount(accountID)
}
func (s *Service) ClearAll(_ context.Context) error {
if s == nil || s.store == nil {
return nil
}
s.mu.Lock()
defer s.mu.Unlock()
return s.store.clearAll()
}
func (s *Service) UpdateSettings(ctx context.Context, patch map[string]string) error {
if s == nil || s.settings == nil {
return nil
}
return s.settings.Update(ctx, patch)
}