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.Hour * 24 * 30) 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 } } }() }