All checks were successful
PR Check / check-and-test (pull_request) Successful in 21s
- Updated .gitignore to include all .exe files and ensure .smashignore is tracked. - Expanded README.md with advanced configuration options for garbage collection algorithms, detailing available algorithms and use cases. - Modified launch.json to include memory and disk garbage collection flags for better configuration. - Refactored root.go to introduce memoryGC and diskGC flags for garbage collection algorithms. - Implemented hash extraction and verification in steamcache.go to ensure data integrity during caching. - Added new tests in steamcache_test.go for hash extraction and verification, ensuring correctness of caching behavior. - Enhanced garbage collection strategies in gc.go, introducing LFU, FIFO, Largest, Smallest, and Hybrid algorithms with corresponding metrics. - Updated caching logic to conditionally cache responses based on hash verification results.
198 lines
4.9 KiB
Go
198 lines
4.9 KiB
Go
// vfs/cache/cache.go
|
|
package cache
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"s1d3sw1ped/SteamCache2/vfs"
|
|
"s1d3sw1ped/SteamCache2/vfs/cachestate"
|
|
"s1d3sw1ped/SteamCache2/vfs/gc"
|
|
"s1d3sw1ped/SteamCache2/vfs/vfserror"
|
|
"sync"
|
|
)
|
|
|
|
// Ensure CacheFS implements VFS.
|
|
var _ vfs.VFS = (*CacheFS)(nil)
|
|
|
|
// CacheFS is a virtual file system that caches files in memory and on disk.
|
|
type CacheFS struct {
|
|
fast vfs.VFS
|
|
slow vfs.VFS
|
|
|
|
cacheHandler CacheHandler
|
|
|
|
keyLocks sync.Map // map[string]*sync.RWMutex for per-key locks
|
|
}
|
|
|
|
type CacheHandler func(*vfs.FileInfo, cachestate.CacheState) bool
|
|
|
|
// New creates a new CacheFS. fast is used for caching, and slow is used for storage. fast should obviously be faster than slow.
|
|
func New(cacheHandler CacheHandler) *CacheFS {
|
|
return &CacheFS{
|
|
cacheHandler: cacheHandler,
|
|
keyLocks: sync.Map{},
|
|
}
|
|
}
|
|
|
|
func (c *CacheFS) SetSlow(vfs vfs.VFS) {
|
|
if vfs == nil {
|
|
panic("vfs is nil") // panic if the vfs is nil
|
|
}
|
|
|
|
c.slow = vfs
|
|
}
|
|
|
|
func (c *CacheFS) SetFast(vfs vfs.VFS) {
|
|
c.fast = vfs
|
|
}
|
|
|
|
// getKeyLock returns a RWMutex for the given key, creating it if necessary.
|
|
func (c *CacheFS) getKeyLock(key string) *sync.RWMutex {
|
|
mu, _ := c.keyLocks.LoadOrStore(key, &sync.RWMutex{})
|
|
return mu.(*sync.RWMutex)
|
|
}
|
|
|
|
// cacheState returns the state of the file at key.
|
|
func (c *CacheFS) cacheState(key string) cachestate.CacheState {
|
|
if c.fast != nil {
|
|
if _, err := c.fast.Stat(key); err == nil {
|
|
return cachestate.CacheStateHit
|
|
}
|
|
}
|
|
|
|
if _, err := c.slow.Stat(key); err == nil {
|
|
return cachestate.CacheStateMiss
|
|
}
|
|
|
|
return cachestate.CacheStateNotFound
|
|
}
|
|
|
|
func (c *CacheFS) Name() string {
|
|
return fmt.Sprintf("CacheFS(%s, %s)", c.fast.Name(), c.slow.Name())
|
|
}
|
|
|
|
// Size returns the total size of the cache.
|
|
func (c *CacheFS) Size() int64 {
|
|
return c.slow.Size()
|
|
}
|
|
|
|
// Delete deletes the file at key from the cache.
|
|
func (c *CacheFS) Delete(key string) error {
|
|
mu := c.getKeyLock(key)
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
if c.fast != nil {
|
|
c.fast.Delete(key)
|
|
}
|
|
return c.slow.Delete(key)
|
|
}
|
|
|
|
// Open returns the file at key. If the file is not in the cache, it is fetched from the storage.
|
|
func (c *CacheFS) Open(key string) (io.ReadCloser, error) {
|
|
mu := c.getKeyLock(key)
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
state := c.cacheState(key)
|
|
|
|
switch state {
|
|
case cachestate.CacheStateHit:
|
|
// if c.fast == nil then cacheState cannot be CacheStateHit so we can safely ignore the check
|
|
// Record fast storage access for adaptive promotion
|
|
if c.fast != nil {
|
|
gc.RecordFastStorageAccess()
|
|
}
|
|
return c.fast.Open(key)
|
|
case cachestate.CacheStateMiss:
|
|
slowReader, err := c.slow.Open(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sstat, _ := c.slow.Stat(key)
|
|
if sstat != nil && c.fast != nil { // file found in slow storage and fast storage is available
|
|
// We are accessing the file from the slow storage, and the file has been accessed less then a minute ago so it popular, so we should update the fast storage with the latest file.
|
|
if c.cacheHandler != nil && c.cacheHandler(sstat, state) {
|
|
fastWriter, err := c.fast.Create(key, sstat.Size())
|
|
if err == nil {
|
|
return &teeReadCloser{
|
|
Reader: io.TeeReader(slowReader, fastWriter),
|
|
closers: []io.Closer{slowReader, fastWriter},
|
|
}, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return slowReader, nil
|
|
case cachestate.CacheStateNotFound:
|
|
return nil, vfserror.ErrNotFound
|
|
}
|
|
|
|
panic(vfserror.ErrUnreachable)
|
|
}
|
|
|
|
// Create creates a new file at key. If the file is already in the cache, it is replaced.
|
|
func (c *CacheFS) Create(key string, size int64) (io.WriteCloser, error) {
|
|
mu := c.getKeyLock(key)
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
state := c.cacheState(key)
|
|
|
|
switch state {
|
|
case cachestate.CacheStateHit:
|
|
if c.fast != nil {
|
|
c.fast.Delete(key)
|
|
}
|
|
return c.slow.Create(key, size)
|
|
case cachestate.CacheStateMiss, cachestate.CacheStateNotFound:
|
|
return c.slow.Create(key, size)
|
|
}
|
|
|
|
panic(vfserror.ErrUnreachable)
|
|
}
|
|
|
|
// Stat returns information about the file at key.
|
|
// Warning: This will return information about the file in the fastest storage its in.
|
|
func (c *CacheFS) Stat(key string) (*vfs.FileInfo, error) {
|
|
mu := c.getKeyLock(key)
|
|
mu.RLock()
|
|
defer mu.RUnlock()
|
|
|
|
state := c.cacheState(key)
|
|
|
|
switch state {
|
|
case cachestate.CacheStateHit:
|
|
// if c.fast == nil then cacheState cannot be CacheStateHit so we can safely ignore the check
|
|
return c.fast.Stat(key)
|
|
case cachestate.CacheStateMiss:
|
|
return c.slow.Stat(key)
|
|
case cachestate.CacheStateNotFound:
|
|
return nil, vfserror.ErrNotFound
|
|
}
|
|
|
|
panic(vfserror.ErrUnreachable)
|
|
}
|
|
|
|
// StatAll returns information about all files in the cache.
|
|
// Warning: This only returns information about the files in the slow storage.
|
|
func (c *CacheFS) StatAll() []*vfs.FileInfo {
|
|
return c.slow.StatAll()
|
|
}
|
|
|
|
type teeReadCloser struct {
|
|
io.Reader
|
|
closers []io.Closer
|
|
}
|
|
|
|
func (t *teeReadCloser) Close() error {
|
|
var err error
|
|
for _, c := range t.closers {
|
|
if e := c.Close(); e != nil {
|
|
err = e
|
|
}
|
|
}
|
|
return err
|
|
}
|