diff --git a/file/file.go b/file/file.go index 023ef10..8edd09d 100644 --- a/file/file.go +++ b/file/file.go @@ -5,11 +5,11 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" - "errors" "fmt" "io/ioutil" "os" "path/filepath" + "sync" "time" "github.com/faabiosr/cachego" @@ -18,6 +18,7 @@ import ( type ( file struct { dir string + sync.RWMutex } fileContent struct { @@ -30,7 +31,8 @@ const perm = 0o666 // New creates an instance of File cache func New(dir string) cachego.Cache { - return &file{dir} + _ = os.MkdirAll(dir, os.ModePerm) + return &file{dir: dir} } func (f *file) createName(key string) string { @@ -42,6 +44,9 @@ func (f *file) createName(key string) string { } func (f *file) read(key string) (*fileContent, error) { + f.RLock() + defer f.RUnlock() + value, err := ioutil.ReadFile(f.createName(key)) if err != nil { return nil, err @@ -56,22 +61,28 @@ func (f *file) read(key string) (*fileContent, error) { return content, nil } - if content.Duration <= time.Now().Unix() { - _ = f.Delete(key) - return nil, errors.New("cache expired") - } - return content, nil } // Contains checks if the cached key exists into the File storage func (f *file) Contains(key string) bool { - _, err := f.read(key) - return err == nil + content, err := f.read(key) + if err != nil { + return false + } + + if f.isExpired(content) { + _ = f.Delete(key) + return false + } + return true } // Delete the cached key from File storage func (f *file) Delete(key string) error { + f.Lock() + defer f.Unlock() + _, err := os.Stat(f.createName(key)) if err != nil && os.IsNotExist(err) { return nil @@ -87,9 +98,18 @@ func (f *file) Fetch(key string) (string, error) { return "", err } + if f.isExpired(content) { + _ = f.Delete(key) + return "", cachego.ErrCacheExpired + } + return content.Data, nil } +func (f *file) isExpired(content *fileContent) bool { + return content.Duration > 0 && content.Duration <= time.Now().Unix() +} + // FetchMulti retrieve multiple cached values from keys of the File storage func (f *file) FetchMulti(keys []string) map[string]string { result := make(map[string]string) @@ -105,6 +125,9 @@ func (f *file) FetchMulti(keys []string) map[string]string { // Flush removes all cached keys of the File storage func (f *file) Flush() error { + f.Lock() + defer f.Unlock() + dir, err := os.Open(f.dir) if err != nil { return err @@ -125,14 +148,15 @@ func (f *file) Flush() error { // Save a value in File storage by key func (f *file) Save(key string, value string, lifeTime time.Duration) error { - duration := int64(0) + f.Lock() + defer f.Unlock() + duration := int64(0) if lifeTime > 0 { duration = time.Now().Unix() + int64(lifeTime.Seconds()) } content := &fileContent{duration, value} - data, err := json.Marshal(content) if err != nil { return err diff --git a/file/file_test.go b/file/file_test.go index 1ada012..2b55d8c 100644 --- a/file/file_test.go +++ b/file/file_test.go @@ -4,6 +4,9 @@ import ( "io/ioutil" "testing" "time" + + "github.com/faabiosr/cachego" + "github.com/stretchr/testify/assert" ) const ( @@ -19,51 +22,41 @@ func TestFile(t *testing.T) { c := New(dir) - if err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil { - t.Errorf("save fail: expected nil, got %v", err) - } + err = c.Save(testKey, testValue, 1*time.Nanosecond) + assert.Equal(t, nil, err) - if _, err := c.Fetch(testKey); err == nil { - t.Errorf("fetch fail: expected an error, got %v", err) - } + res, err := c.Fetch(testKey) + assert.Equal(t, "", res) + assert.Equal(t, cachego.ErrCacheExpired, err) - _ = c.Save(testKey, testValue, 10*time.Second) + err = c.Save(testKey, testValue, 10*time.Second) + assert.Equal(t, nil, err) - if res, _ := c.Fetch(testKey); res != testValue { - t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res) - } + res, err = c.Fetch(testKey) + assert.Equal(t, nil, err) + assert.Equal(t, testValue, res) _ = c.Save(testKey, testValue, 0) - - if !c.Contains(testKey) { - t.Errorf("contains failed: the key %s should be exist", testKey) - } + assert.Equal(t, true, c.Contains(testKey)) _ = c.Save("bar", testValue, 0) - if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 { - t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values)) - } + values := c.FetchMulti([]string{testKey, "bar"}) + assert.Equal(t, 2, len(values)) - if err := c.Flush(); err != nil { - t.Errorf("flush failed: expected nil, got %v", err) - } + err = c.Flush() + assert.Equal(t, nil, err) - if c.Contains(testKey) { - t.Errorf("contains failed: the key %s should not be exist", testKey) - } + assert.Equal(t, false, c.Contains(testKey)) c = New("./test/") + err = c.Save(testKey, testValue, 0) + assert.Equal(t, nil, err) - if err := c.Save(testKey, testValue, 0); err == nil { - t.Errorf("save failed: expected an error, got %v", err) - } - - if _, err := c.Fetch(testKey); err == nil { - t.Errorf("fetch failed: expected and error, got %v", err) - } + res, err = c.Fetch(testKey) + assert.Equal(t, testValue, res) + assert.Equal(t, nil, err) - if err := c.Flush(); err == nil { - t.Errorf("flush failed: expected an error, got %v", err) - } + err = c.Flush() + assert.Equal(t, nil, err) } diff --git a/go.mod b/go.mod index de95f0e..6f099a3 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,21 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 github.com/mattn/go-sqlite3 v1.14.16 github.com/redis/go-redis/v9 v9.0.2 + github.com/stretchr/testify v1.8.1 go.etcd.io/bbolt v1.3.7 go.mongodb.org/mongo-driver v1.11.3 ) require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/golang/snappy v0.0.1 // indirect github.com/google/go-cmp v0.5.5 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.1 // indirect github.com/xdg-go/stringprep v1.0.3 // indirect @@ -27,4 +30,5 @@ require ( golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index afe1d94..7be6964 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,13 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE= github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -67,6 +72,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=