package candycache import ( "encoding/json" "errors" "fmt" "io" "reflect" "sync" "time" ) // Структура виде ключ-значение для возвращения списка элементов кэша с их ключами. type KeyItemPair struct { Key string Item Item } // Элемент в кэше - это данные и время их жизни. type Item struct { destroyTimestamp int64 // Момент в Unix-секундах, когда элемент становится устаревшим data interface{} // Данные } // Кэш - это хранилище элементов и инервал его очистки (ну и мьютекс на всякий случай). // Интервал очистки хранилища укахывается в НАНОСЕКУНДАХ (используй множители для преобразования во что-то другое). type Cache struct { sync.RWMutex // Мьютекс ждя реализации безопасного доступа к общим данным storage map[string]Item // Хранилище элементов cleanupInterval time.Duration // Интервал очистки хранилища в наносекундах } // Создает новый экземпляр Cache с интервалом очистки cleanupInterval. // Если cleanupInterval < 0, то кэш не будет очищаться автоматически. func Cacher(cleanupInterval time.Duration) *Cache { cache := &Cache{ storage: make(map[string]Item), cleanupInterval: cleanupInterval, } if cleanupInterval > 0 { go cache.gc(cleanupInterval) } return cache } // gc = Garbage Collector. func (c *Cache) gc(cleanupInterval time.Duration) { ticker := time.NewTicker(cleanupInterval) defer ticker.Stop() for range ticker.C { c.Cleanup() } } // Перебирает все элементы в кэше, удаляет устаревшие. func (c *Cache) Cleanup() { c.Lock() defer c.Unlock() for key, item := range c.storage { if item.destroyTimestamp <= time.Now().UnixNano() { delete(c.storage, key) } } } // Удаление всех элементов из кэша. func (c *Cache) Flush() { c.Lock() defer c.Unlock() for key := range c.storage { delete(c.storage, key) } } // Получение элемента из кэша по ключу. func (c *Cache) Get(key string) (interface{}, error) { c.RLock() defer c.RUnlock() item, found := c.storage[key] if !found { return nil, errors.New("key not found") } return item.data, nil } // Определяет является ли элемент устаревшим. // Вторым аргументов возвращается есть элемент в кэше или нет. // Первым - устаревший элемент или нет. func (c *Cache) IsExpired(key string) (bool, error) { c.RLock() defer c.RUnlock() item, found := c.storage[key] if !found { return false, errors.New("key not found") } if item.destroyTimestamp <= time.Now().UnixNano() { return true, nil } else { return false, nil } } // Удаление элемента по ключу. func (c *Cache) Delete(key string) error { c.Lock() defer c.Unlock() if _, found := c.storage[key]; !found { return errors.New("key not found") } delete(c.storage, key) return nil } // Добавление элемента в кэш. // key - ключ. // data - данные. // ttl - время жизни элемента (time to life) в наносекундах. func (c *Cache) Set(key string, data interface{}, ttl time.Duration) { c.Lock() defer c.Unlock() c.storage[key] = Item{ destroyTimestamp: time.Now().UnixNano() + int64(ttl), data: data, } } // Вернет количество элементов в кэше. func (c *Cache) Count() int { c.RLock() defer c.RUnlock() return len(c.storage) } // Возвращает список всех элементов кэша. func (c *Cache) List() []KeyItemPair { c.RLock() defer c.RUnlock() items := []KeyItemPair{} for key, item := range c.storage { items = append(items, KeyItemPair{Key: key, Item: item}) } return items } // Возвращает список всех устаревших элементов кэша. func (c *Cache) ExpiredList() []KeyItemPair { c.RLock() defer c.RUnlock() items := []KeyItemPair{} for key, item := range c.storage { if item.destroyTimestamp <= time.Now().UnixNano() { items = append(items, KeyItemPair{Key: key, Item: item}) } } return items } // Вернет размер всего кэша в байтах. func (c *Cache) Size() int { c.RLock() defer c.RUnlock() size := 0 for key, item := range c.storage { size += isize(key) + isize(item.data) + isize(item.destroyTimestamp) } return size } // Save сохраняет кэш в io.Writer в формате JSON, записывая каждый элемент по отдельности. func (c *Cache) Save(w io.Writer) error { c.RLock() defer c.RUnlock() if _, err := w.Write([]byte("[\n")); err != nil { return err } encoder := json.NewEncoder(w) first := true for key, item := range c.storage { entry := struct { Key string `json:"key"` DestroyTimestamp int64 `json:"destroyTimestamp"` Data interface{} `json:"data"` }{ Key: key, DestroyTimestamp: item.destroyTimestamp, Data: item.data, } if !first { if _, err := w.Write([]byte(",\n")); err != nil { return err } } first = false if err := encoder.Encode(entry); err != nil { return err } } if _, err := w.Write([]byte("]")); err != nil { return err } return nil } // Load загружает кэш из io.Reader в формате JSON. func (c *Cache) Load(r io.Reader) error { c.Lock() defer c.Unlock() decoder := json.NewDecoder(r) if _, err := decoder.Token(); err != nil { if err == io.EOF { return fmt.Errorf("файл пустой или имеет неправильный формат") } return err } for decoder.More() { var entry struct { Key string `json:"key"` DestroyTimestamp int64 `json:"destroyTimestamp"` Data interface{} `json:"data"` } if err := decoder.Decode(&entry); err != nil { return err } c.storage[entry.Key] = Item{ destroyTimestamp: entry.DestroyTimestamp, data: entry.Data, } } if _, err := decoder.Token(); err != nil { return err } return nil } func isize(i interface{}) int { if i == nil { return 0 } val := reflect.ValueOf(i) kind := val.Kind() size := 0 switch kind { case reflect.Slice, reflect.Array, reflect.String: len := val.Len() for i := 0; i < len; i++ { size += isize(val.Index(i).Interface()) } return size case reflect.Map: for _, key := range val.MapKeys() { size += isize(key.Interface()) + isize(val.MapIndex(key).Interface()) } return size case reflect.Struct: for i := 0; i < val.NumField(); i++ { size += isize(val.Field(i).Interface()) } return size default: return int(reflect.TypeOf(i).Size()) } } // Возвращает данные элемента кэша. func (i *Item) Data() interface{} { return i.data } // Возвращает момент смерти элемента кэша. func (i *Item) DestroyTimestamp() int64 { return i.destroyTimestamp } // Определяет является ли элемент устаревшим. func (i *Item) IsExpired() bool { if i.destroyTimestamp <= time.Now().UnixNano() { return true } else { return false } }