package gc import ( "fmt" "s1d3sw1ped/SteamCache2/vfs" "s1d3sw1ped/SteamCache2/vfs/vfserror" "sync" "time" ) // Ensure GCFS implements VFS. var _ vfs.VFS = (*GCFS)(nil) // GCFS is a virtual file system that calls a GC handler when the disk is full. The GC handler is responsible for freeing up space on the disk. The GCFS is a wrapper around another VFS. type GCFS struct { vfs.VFS multiplier int // protected by mu gcHanderFunc GCHandlerFunc lifetimeBytes, lifetimeFiles uint reclaimedBytes, deletedFiles uint gcTime time.Duration mu sync.Mutex } // GCHandlerFunc is a function that is called when the disk is full and the GCFS needs to free up space. It is passed the VFS and the size of the file that needs to be written. Its up to the implementation to free up space. How much space is freed is also up to the implementation. type GCHandlerFunc func(vfs vfs.VFS, size uint) (reclaimedBytes uint, deletedFiles uint) func New(vfs vfs.VFS, multiplier int, gcHandlerFunc GCHandlerFunc) *GCFS { if multiplier <= 0 { multiplier = 1 // if the multiplier is less than or equal to 0 set it to 1 will be slow but the user can set it to a higher value if they want } return &GCFS{ VFS: vfs, multiplier: multiplier, gcHanderFunc: gcHandlerFunc, } } // Stats returns the lifetime bytes, lifetime files, reclaimed bytes and deleted files. // The lifetime bytes and lifetime files are the total bytes and files that have been freed up by the GC handler. // The reclaimed bytes and deleted files are the bytes and files that have been freed up by the GC handler since last call to Stats. // The gc time is the total time spent in the GC handler since last call to Stats. // The reclaimed bytes and deleted files and gc time are reset to 0 after the call to Stats. func (g *GCFS) Stats() (lifetimeBytes, lifetimeFiles, reclaimedBytes, deletedFiles uint, gcTime time.Duration) { g.mu.Lock() defer g.mu.Unlock() g.lifetimeBytes += g.reclaimedBytes g.lifetimeFiles += g.deletedFiles lifetimeBytes = g.lifetimeBytes lifetimeFiles = g.lifetimeFiles reclaimedBytes = g.reclaimedBytes deletedFiles = g.deletedFiles gcTime = g.gcTime g.reclaimedBytes = 0 g.deletedFiles = 0 g.gcTime = time.Duration(0) return } // Set overrides the Set method of the VFS interface. It tries to set the key and src, if it fails due to disk full error, it calls the GC handler and tries again. If it still fails it returns the error. func (g *GCFS) Set(key string, src []byte) error { g.mu.Lock() defer g.mu.Unlock() err := g.VFS.Set(key, src) // try to set the key and src if err == vfserror.ErrDiskFull && g.gcHanderFunc != nil { // if the error is disk full and there is a GC handler tstart := time.Now() reclaimedBytes, deletedFiles := g.gcHanderFunc(g.VFS, uint(len(src)*g.multiplier)) // call the GC handler g.gcTime += time.Since(tstart) g.reclaimedBytes += reclaimedBytes g.deletedFiles += deletedFiles err = g.VFS.Set(key, src) // try again after GC if it still fails return the error } return err } func (g *GCFS) Name() string { return fmt.Sprintf("GCFS(%s)", g.VFS.Name()) // wrap the name of the VFS with GCFS so we can see that its a GCFS }