mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-08-26 16:46:14 +08:00
323 lines
7.8 KiB
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
|
|
}
|