diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/cache/interface.go | 9 | ||||
-rw-r--r-- | pkg/cache/main.go | 146 |
2 files changed, 155 insertions, 0 deletions
diff --git a/pkg/cache/interface.go b/pkg/cache/interface.go new file mode 100644 index 0000000..606f50f --- /dev/null +++ b/pkg/cache/interface.go @@ -0,0 +1,9 @@ +package cache + +type Cache interface { + Get(key string) ([]byte, error) + Set(key string, value []byte) + Expire(key string, exp int64) + GetAll() (resp map[string][]byte) + RemoveKey(key string) +} diff --git a/pkg/cache/main.go b/pkg/cache/main.go new file mode 100644 index 0000000..d617f49 --- /dev/null +++ b/pkg/cache/main.go @@ -0,0 +1,146 @@ +package cache + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log/slog" + "os" + "sync" + "time" +) + +const storeFileName = "store.json" + +// var MemCache Cache +var ( + MemCache *MemoryCache + log = slog.New(slog.NewJSONHandler(os.Stdout, nil)) +) + +func readJSON(fileName string) (map[string][]byte, error) { + data := make(map[string][]byte) + file, err := os.Open(fileName) + if err != nil { + return data, err + } + defer file.Close() + decoder := json.NewDecoder(file) + if err := decoder.Decode(&data); err != nil { + return data, err + } + return data, nil +} + +func init() { + data, err := readJSON(storeFileName) + if err != nil { + log.Warn("failed to load store from file", "error", err) + } + MemCache = &MemoryCache{ + data: data, + timeMap: make(map[string]time.Time), + lock: &sync.RWMutex{}, + } + MemCache.StartExpiryRoutine(time.Minute) + MemCache.StartBackupRoutine(time.Minute) +} + +type MemoryCache struct { + data map[string][]byte + timeMap map[string]time.Time + lock *sync.RWMutex +} + +// Get a value by key from the cache +func (mc *MemoryCache) Get(key string) (value []byte, err error) { + var ok bool + mc.lock.RLock() + if value, ok = mc.data[key]; !ok { + err = fmt.Errorf("not found data in mc for the key: %v", key) + } + mc.lock.RUnlock() + return value, err +} + +// Update a single value in the cache +func (mc *MemoryCache) Set(key string, value []byte) { + // no async writing + mc.lock.Lock() + mc.data[key] = value + mc.lock.Unlock() +} + +func (mc *MemoryCache) Expire(key string, exp int64) { + mc.lock.RLock() + mc.timeMap[key] = time.Now().Add(time.Duration(exp) * time.Second) + mc.lock.RUnlock() +} + +func (mc *MemoryCache) GetAll() (resp map[string][]byte) { + resp = make(map[string][]byte) + mc.lock.RLock() + for k, v := range mc.data { + resp[k] = v + } + mc.lock.RUnlock() + return +} + +func (mc *MemoryCache) GetAllTime() (resp map[string]time.Time) { + resp = make(map[string]time.Time) + mc.lock.RLock() + for k, v := range mc.timeMap { + resp[k] = v + } + mc.lock.RUnlock() + return +} + +func (mc *MemoryCache) RemoveKey(key string) { + mc.lock.RLock() + delete(mc.data, key) + delete(mc.timeMap, key) + mc.lock.RUnlock() +} + +func (mc *MemoryCache) StartExpiryRoutine(n time.Duration) { + ticker := time.NewTicker(n) + go func() { + for { + <-ticker.C + // get all + timeData := mc.GetAllTime() + // check time + currentTS := time.Now() + for k, ts := range timeData { + if ts.Before(currentTS) { + // delete exp keys + mc.RemoveKey(k) + log.Info("remove by expiry", "key", k) + } + } + } + }() +} + +func (mc *MemoryCache) StartBackupRoutine(n time.Duration) { + ticker := time.NewTicker(n) + go func() { + for { + <-ticker.C + // get all + data := mc.GetAll() + jsonString, err := json.Marshal(data) + if err != nil { + log.Warn("failed to marshal kv store", "error", err) + continue + } + err = ioutil.WriteFile(storeFileName, jsonString, os.ModePerm) + if err != nil { + log.Warn("failed to write to json file", "error", err) + continue + } + } + }() +} |