152 lines
4.0 KiB
Go
152 lines
4.0 KiB
Go
package cache
|
|
|
|
import (
|
|
"fmt"
|
|
"s1d3sw1ped/SteamCache2/vfs"
|
|
"s1d3sw1ped/SteamCache2/vfs/cachestate"
|
|
"s1d3sw1ped/SteamCache2/vfs/vfserror"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
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(fast, slow vfs.VFS, cacheHandler CacheHandler) *CacheFS {
|
|
if slow == nil {
|
|
panic("slow is nil")
|
|
}
|
|
|
|
if fast == slow {
|
|
panic("fast and slow are the same")
|
|
}
|
|
|
|
return &CacheFS{
|
|
fast: fast,
|
|
slow: slow,
|
|
|
|
cacheHandler: cacheHandler,
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
// Set sets the file at key to src. If the file is already in the cache, it is replaced.
|
|
func (c *CacheFS) Set(key string, src []byte) error {
|
|
state := c.cacheState(key)
|
|
|
|
switch state {
|
|
case cachestate.CacheStateHit:
|
|
if c.fast != nil {
|
|
c.fast.Delete(key)
|
|
}
|
|
return c.slow.Set(key, src)
|
|
case cachestate.CacheStateMiss, cachestate.CacheStateNotFound:
|
|
return c.slow.Set(key, src)
|
|
}
|
|
|
|
panic(vfserror.ErrUnreachable)
|
|
}
|
|
|
|
// Delete deletes the file at key from the cache.
|
|
func (c *CacheFS) Delete(key string) error {
|
|
if c.fast != nil {
|
|
c.fast.Delete(key)
|
|
}
|
|
return c.slow.Delete(key)
|
|
}
|
|
|
|
// Get returns the file at key. If the file is not in the cache, it is fetched from the storage.
|
|
func (c *CacheFS) Get(key string) ([]byte, error) {
|
|
src, _, err := c.GetS(key)
|
|
return src, err
|
|
}
|
|
|
|
// GetS returns the file at key. If the file is not in the cache, it is fetched from the storage. It also returns the cache state.
|
|
func (c *CacheFS) GetS(key string) ([]byte, cachestate.CacheState, error) {
|
|
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
|
|
src, err := c.fast.Get(key)
|
|
return src, state, err
|
|
case cachestate.CacheStateMiss:
|
|
src, err := c.slow.Get(key)
|
|
if err != nil {
|
|
return nil, state, 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) {
|
|
if err := c.fast.Set(key, src); err != nil {
|
|
return nil, state, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return src, state, nil
|
|
case cachestate.CacheStateNotFound:
|
|
return nil, state, vfserror.ErrNotFound
|
|
}
|
|
|
|
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) {
|
|
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()
|
|
}
|