lottsa stuff
This commit is contained in:
98
cacheFs/cacheFs.go
Normal file
98
cacheFs/cacheFs.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package cachefs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensure CacheFs implements afero.Fs.
|
||||||
|
var _ afero.Fs = &CacheFs{}
|
||||||
|
|
||||||
|
// CacheStatus represents the status of a file in the cache.
|
||||||
|
type CacheStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CacheHit indicates that the requested file was found in the cache.
|
||||||
|
CacheHit CacheStatus = iota
|
||||||
|
|
||||||
|
// CacheMiss indicates that the requested file was not found in the cache.
|
||||||
|
CacheMiss
|
||||||
|
)
|
||||||
|
|
||||||
|
type CacheFs struct {
|
||||||
|
// source filesystem
|
||||||
|
source afero.Fs
|
||||||
|
|
||||||
|
// cache filesystem
|
||||||
|
cache afero.Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCacheFs(source, cache afero.Fs) *CacheFs {
|
||||||
|
return &CacheFs{
|
||||||
|
source: source,
|
||||||
|
cache: cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create implements the Create method of afero.Fs, ensuring we don't exceed storage limits.
|
||||||
|
func (cfs *CacheFs) Create(name string) (afero.File, error) {
|
||||||
|
return cfs.source.Create(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
|
return cfs.source.Mkdir(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return cfs.source.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Open(name string) (afero.File, error) {
|
||||||
|
return cfs.source.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
|
||||||
|
return cfs.source.OpenFile(name, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Remove(name string) error {
|
||||||
|
return cfs.source.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) RemoveAll(path string) error {
|
||||||
|
return cfs.source.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Rename(oldname, newname string) error {
|
||||||
|
return cfs.source.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return cfs.source.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Name() string {
|
||||||
|
return "CacheFS"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
return cfs.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Chown(name string, uid, gid int) error {
|
||||||
|
return cfs.source.Chown(name, uid, gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfs *CacheFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return cfs.source.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure CacheFile implements afero.File.
|
||||||
|
var _ afero.File = &CacheFile{}
|
||||||
|
|
||||||
|
// CacheFile wraps an afero.File to manage space usage on write operations.
|
||||||
|
type CacheFile struct {
|
||||||
|
afero.File
|
||||||
|
}
|
||||||
7
go.mod
7
go.mod
@@ -1,3 +1,10 @@
|
|||||||
module s1d3sw1ped/SteamCache2
|
module s1d3sw1ped/SteamCache2
|
||||||
|
|
||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/docker/go-units v0.5.0
|
||||||
|
github.com/spf13/afero v1.12.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require golang.org/x/text v0.21.0 // indirect
|
||||||
|
|||||||
6
go.sum
Normal file
6
go.sum
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||||
|
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
192
limitedFs/limitedFs.go
Normal file
192
limitedFs/limitedFs.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package limitedFs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/gommon/log"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Ensure LimitedFs implements afero.Fs.
|
||||||
|
_ afero.Fs = &LimitedFs{}
|
||||||
|
|
||||||
|
// ErrNotEnoughSpace is returned when there's not enough space to write.
|
||||||
|
ErrNotEnoughSpace = errors.New("not enough space")
|
||||||
|
)
|
||||||
|
|
||||||
|
// LimitedFs wraps an afero.Fs to limit the storage space.
|
||||||
|
type LimitedFs struct {
|
||||||
|
fs afero.Fs
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
usedSpace int64
|
||||||
|
maxSpace int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLimitedFs creates a new LimitedFs with the specified max storage space.
|
||||||
|
func NewLimitedFs(source afero.Fs, maxSpace int64) *LimitedFs {
|
||||||
|
lfs := &LimitedFs{
|
||||||
|
fs: source,
|
||||||
|
usedSpace: 0,
|
||||||
|
maxSpace: maxSpace,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the source filesystem is a memory filesystem, there is no need to scan it.
|
||||||
|
if _, ok := source.(*afero.MemMapFs); !ok {
|
||||||
|
// Scan the source filesystem to calculate the used space.
|
||||||
|
log.Infof("Scanning source filesystem to calculate used space...")
|
||||||
|
err := afero.Walk(source, "/", func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lfs.usedSpace += info.Size()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lfs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create implements the Create method of afero.Fs, ensuring we don't exceed storage limits.
|
||||||
|
func (lfs *LimitedFs) Create(name string) (afero.File, error) {
|
||||||
|
file, err := lfs.fs.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &LimitedFile{
|
||||||
|
File: file,
|
||||||
|
lfs: lfs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
|
return lfs.fs.Mkdir(name, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return lfs.fs.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Open(name string) (afero.File, error) {
|
||||||
|
file, err := lfs.fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &LimitedFile{
|
||||||
|
File: file,
|
||||||
|
lfs: lfs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
|
||||||
|
file, err := lfs.fs.OpenFile(name, flag, perm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &LimitedFile{
|
||||||
|
File: file,
|
||||||
|
lfs: lfs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Remove(name string) error {
|
||||||
|
info, err := lfs.fs.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lfs.mu.Lock()
|
||||||
|
lfs.usedSpace -= info.Size()
|
||||||
|
lfs.mu.Unlock()
|
||||||
|
return lfs.fs.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) RemoveAll(path string) error {
|
||||||
|
// Calculate the space used by the directory and its children.
|
||||||
|
var size int64
|
||||||
|
err := afero.Walk(lfs.fs, path, func(subpath string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
size += info.Size()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs.mu.Lock()
|
||||||
|
lfs.usedSpace -= size
|
||||||
|
lfs.mu.Unlock()
|
||||||
|
|
||||||
|
return lfs.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Rename(oldname, newname string) error {
|
||||||
|
return lfs.fs.Rename(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return lfs.fs.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Name() string {
|
||||||
|
return "LimitedFS"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
return lfs.fs.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Chown(name string, uid, gid int) error {
|
||||||
|
return lfs.fs.Chown(name, uid, gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfs *LimitedFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return lfs.fs.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure LimitedFile implements afero.File.
|
||||||
|
var _ afero.File = &LimitedFile{}
|
||||||
|
|
||||||
|
// LimitedFile wraps an afero.File to manage space usage on write operations.
|
||||||
|
type LimitedFile struct {
|
||||||
|
afero.File
|
||||||
|
lfs *LimitedFs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write checks if there's enough space before writing.
|
||||||
|
func (lf *LimitedFile) Write(p []byte) (n int, err error) {
|
||||||
|
lf.lfs.mu.Lock()
|
||||||
|
if int64(len(p)) > lf.lfs.maxSpace-lf.lfs.usedSpace {
|
||||||
|
lf.lfs.mu.Unlock()
|
||||||
|
return 0, ErrNotEnoughSpace
|
||||||
|
}
|
||||||
|
lf.lfs.mu.Unlock()
|
||||||
|
n, err = lf.File.Write(p)
|
||||||
|
if err == nil {
|
||||||
|
lf.lfs.mu.Lock()
|
||||||
|
lf.lfs.usedSpace += int64(n)
|
||||||
|
lf.lfs.mu.Unlock()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close updates used space and calls the underlying file's Close method.
|
||||||
|
func (lf *LimitedFile) Close() error {
|
||||||
|
info, err := lf.File.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lf.lfs.mu.Lock()
|
||||||
|
lf.lfs.usedSpace -= info.Size()
|
||||||
|
lf.lfs.mu.Unlock()
|
||||||
|
return lf.File.Close()
|
||||||
|
}
|
||||||
44
limitedFs/limited_test.go
Normal file
44
limitedFs/limited_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package limitedFs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLimitedCreate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
memfs := afero.NewMemMapFs()
|
||||||
|
lfs := NewLimitedFs(memfs, 10)
|
||||||
|
|
||||||
|
for i := 0; i < 11; i++ {
|
||||||
|
_, err := lfs.Create(fmt.Sprintf("file.%d", i))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLimitedWrite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
memfs := afero.NewMemMapFs()
|
||||||
|
lfs := NewLimitedFs(memfs, 10)
|
||||||
|
|
||||||
|
file, err := lfs.Create("file")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 11; i++ {
|
||||||
|
_, err = file.Write([]byte("1"))
|
||||||
|
if i < 10 && err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if i == 10 && err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
steamcache/config.go
Normal file
51
steamcache/config.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package steamcache
|
||||||
|
|
||||||
|
import "github.com/docker/go-units"
|
||||||
|
|
||||||
|
type StorageType string
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Address string `yaml:"address"` // Address to listen on
|
||||||
|
Storage []StorageConfig `yaml:"storage"` // Storage configuration
|
||||||
|
// Ordered by speed so memory is first, then ssd, then hdd, etc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
Address: ":8080",
|
||||||
|
Storage: []StorageConfig{
|
||||||
|
{
|
||||||
|
Name: "memory",
|
||||||
|
MaxSizeStr: "1GB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "filesystem",
|
||||||
|
Path: "/example/path",
|
||||||
|
MaxSizeStr: "1TB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StorageConfig struct {
|
||||||
|
Name string `yaml:"name"` // Name of the storage
|
||||||
|
Path string `yaml:"path,omitempty"` // Path to the storage (if applicable) - empty for memory
|
||||||
|
MaxSizeStr string `yaml:"max_size"` // Maximum size of the storage in human readable format (e.g. 1GB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *StorageConfig) IsMemory() bool {
|
||||||
|
return sc.Path == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *StorageConfig) IsFilesystem() bool {
|
||||||
|
return sc.Path != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *StorageConfig) MaxSize() int64 {
|
||||||
|
x, err := units.FromHumanSize(sc.MaxSizeStr)
|
||||||
|
if err != nil {
|
||||||
|
return -1 // Unlimited
|
||||||
|
}
|
||||||
|
|
||||||
|
return x // Bytes
|
||||||
|
}
|
||||||
21
steamcache/steamcache.go
Normal file
21
steamcache/steamcache.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package steamcache
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type SteamCache struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *SteamCache {
|
||||||
|
return &SteamCache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SteamCache) Run() {
|
||||||
|
http.ListenAndServe(":8080", sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SteamCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
//TODO: proxy request to steam servers and cache the response
|
||||||
|
|
||||||
|
// for now, just return a simple response
|
||||||
|
w.Write([]byte("Hello, World!"))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user