145 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			145 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package cache
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"log/slog"
 | |
| 	"os"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| const storeFileName = "store.json"
 | |
| 
 | |
| // var MemCache Cache
 | |
| var (
 | |
| 	MemCache *MemoryCache
 | |
| )
 | |
| 
 | |
| 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 {
 | |
| 		slog.Error("failed to load store from file")
 | |
| 	}
 | |
| 	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)
 | |
| 					slog.Debug("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 {
 | |
| 				slog.Warn("failed to marshal", "err", err)
 | |
| 				continue
 | |
| 			}
 | |
| 			err = os.WriteFile(storeFileName, jsonString, os.ModePerm)
 | |
| 			if err != nil {
 | |
| 				slog.Warn("failed to write", "err", err)
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| }
 | 
