Files
hotgo/server/internal/library/cache/file/file.go

323 lines
7.8 KiB
Go

// Package file
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package file
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/util/gconv"
"os"
"path/filepath"
"time"
)
type (
// AdapterFile is the gcache adapter implements using file server.
AdapterFile struct {
dir string
}
fileContent struct {
Duration int64 `json:"duration"`
Data interface{} `json:"data,omitempty"`
}
)
const perm = 0o666
var (
CacheExpiredErr = errors.New("cache expired")
)
// NewAdapterFile creates and returns a new memory cache object.
func NewAdapterFile(dir string) gcache.Adapter {
return &AdapterFile{
dir: dir,
}
}
func (c *AdapterFile) Set(ctx context.Context, key interface{}, value interface{}, lifeTime time.Duration) (err error) {
fileKey := gconv.String(key)
if value == nil || lifeTime < 0 {
return c.Delete(fileKey)
}
return c.Save(fileKey, gconv.String(value), lifeTime)
}
func (c *AdapterFile) SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) (err error) {
return gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExistFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) Get(ctx context.Context, key interface{}) (*gvar.Var, error) {
fetch, err := c.Fetch(gconv.String(key))
if err != nil {
return nil, err
}
return gvar.New(fetch), nil
}
func (c *AdapterFile) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) {
result, err = c.Get(ctx, key)
if err != nil && !errors.Is(err, CacheExpiredErr) {
return nil, err
}
if result.IsNil() {
return gvar.New(value), c.Set(ctx, key, value, duration)
}
return
}
func (c *AdapterFile) GetOrSetFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) {
v, err := c.Get(ctx, key)
if err != nil && !errors.Is(err, CacheExpiredErr) {
return nil, err
}
if v.IsNil() {
value, err := f(ctx)
if err != nil {
return nil, err
}
if value == nil {
return nil, nil
}
return gvar.New(value), c.Set(ctx, key, value, duration)
} else {
return v, nil
}
}
func (c *AdapterFile) GetOrSetFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) {
return c.GetOrSetFunc(ctx, key, f, duration)
}
func (c *AdapterFile) Contains(ctx context.Context, key interface{}) (bool, error) {
return c.Has(gconv.String(key)), nil
}
func (c *AdapterFile) Size(ctx context.Context) (size int, err error) {
return 0, nil
}
func (c *AdapterFile) Data(ctx context.Context) (data map[interface{}]interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Keys(ctx context.Context) (keys []interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Values(ctx context.Context) (values []interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) {
return nil, false, gerror.New("implement me")
}
func (c *AdapterFile) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) {
var (
v *gvar.Var
oldTTL int64
fileKey = gconv.String(key)
)
// TTL.
expire, err := c.GetExpire(ctx, fileKey)
if err != nil {
return
}
oldTTL = int64(expire)
if oldTTL == -2 {
// It does not exist.
return
}
oldDuration = time.Duration(oldTTL) * time.Second
// DEL.
if duration < 0 {
err = c.Delete(fileKey)
return
}
v, err = c.Get(ctx, fileKey)
if err != nil {
return
}
err = c.Set(ctx, fileKey, v.Val(), duration)
return
}
func (c *AdapterFile) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) {
content, err := c.read(gconv.String(key))
if err != nil {
return -1, nil
}
if content.Duration <= time.Now().Unix() {
return -1, nil
}
return time.Duration(time.Now().Unix()-content.Duration) * time.Second, nil
}
func (c *AdapterFile) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) {
if len(keys) == 0 {
return nil, nil
}
// Retrieves the last key value.
if lastValue, err = c.Get(ctx, gconv.String(keys[len(keys)-1])); err != nil {
return nil, err
}
// Deletes all given keys.
err = c.DeleteMulti(gconv.Strings(keys)...)
return
}
func (c *AdapterFile) Clear(ctx context.Context) error {
return c.Flush()
}
func (c *AdapterFile) Close(ctx context.Context) error {
return nil
}
func (c *AdapterFile) createName(key string) string {
h := sha256.New()
_, _ = h.Write([]byte(key))
hash := hex.EncodeToString(h.Sum(nil))
return filepath.Join(c.dir, fmt.Sprintf("%s.cache", hash))
}
func (c *AdapterFile) read(key string) (*fileContent, error) {
rp := gfile.RealPath(c.createName(key))
if rp == "" {
return nil, nil
}
value, err := os.ReadFile(rp)
if err != nil {
return nil, err
}
content := &fileContent{}
if err := json.Unmarshal(value, content); err != nil {
return nil, err
}
if content.Duration == 0 {
return content, nil
}
if content.Duration <= time.Now().Unix() {
_ = c.Delete(key)
return nil, CacheExpiredErr
}
return content, nil
}
// Has checks if the cached key exists into the File storage
func (c *AdapterFile) Has(key string) bool {
fc, err := c.read(key)
return err == nil && fc != nil
}
// Delete the cached key from File storage
func (c *AdapterFile) Delete(key string) error {
_, err := os.Stat(c.createName(key))
if err != nil && os.IsNotExist(err) {
return nil
}
return os.Remove(c.createName(key))
}
// DeleteMulti the cached key from File storage
func (c *AdapterFile) DeleteMulti(keys ...string) (err error) {
for _, key := range keys {
if err = c.Delete(key); err != nil {
return
}
}
return
}
// Fetch retrieves the cached value from key of the File storage
func (c *AdapterFile) Fetch(key string) (interface{}, error) {
content, err := c.read(key)
if err != nil {
return nil, err
}
if content == nil {
return nil, nil
}
return content.Data, nil
}
// FetchMulti retrieve multiple cached values from keys of the File storage
func (c *AdapterFile) FetchMulti(keys []string) map[string]interface{} {
result := make(map[string]interface{})
for _, key := range keys {
if value, err := c.Fetch(key); err == nil {
result[key] = value
}
}
return result
}
// Flush removes all cached keys of the File storage
func (c *AdapterFile) Flush() error {
dir, err := os.Open(c.dir)
if err != nil {
return err
}
defer func() {
_ = dir.Close()
}()
names, _ := dir.Readdirnames(-1)
for _, name := range names {
_ = os.Remove(filepath.Join(c.dir, name))
}
return nil
}
// Save a value in File storage by key
func (c *AdapterFile) Save(key string, value string, lifeTime time.Duration) error {
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
}
err = os.WriteFile(c.createName(key), data, perm)
return err
}