diff options
Diffstat (limited to 'internal/cache')
-rw-r--r-- | internal/cache/status.go | 106 | ||||
-rw-r--r-- | internal/cache/status_test.go | 41 |
2 files changed, 147 insertions, 0 deletions
diff --git a/internal/cache/status.go b/internal/cache/status.go new file mode 100644 index 000000000..895a5692c --- /dev/null +++ b/internal/cache/status.go @@ -0,0 +1,106 @@ +package cache + +import ( + "sync" + + "github.com/ReneKroon/ttlcache" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// statusCache is a wrapper around ttlcache.Cache to provide URL and URI lookups for gtsmodel.Status +type StatusCache struct { + cache *ttlcache.Cache // map of IDs -> cached statuses + urls map[string]string // map of status URLs -> IDs + uris map[string]string // map of status URIs -> IDs + mutex sync.Mutex +} + +// newStatusCache returns a new instantiated statusCache object +func NewStatusCache() *StatusCache { + c := StatusCache{ + cache: ttlcache.NewCache(), + urls: make(map[string]string, 100), + uris: make(map[string]string, 100), + mutex: sync.Mutex{}, + } + + // Set callback to purge lookup maps on expiration + c.cache.SetExpirationCallback(func(key string, value interface{}) { + status := value.(*gtsmodel.Status) + + c.mutex.Lock() + delete(c.urls, status.URL) + delete(c.uris, status.URI) + c.mutex.Unlock() + }) + + return &c +} + +// GetByID attempts to fetch a status from the cache by its ID +func (c *StatusCache) GetByID(id string) (*gtsmodel.Status, bool) { + c.mutex.Lock() + status, ok := c.getByID(id) + c.mutex.Unlock() + return status, ok +} + +// GetByURL attempts to fetch a status from the cache by its URL +func (c *StatusCache) GetByURL(url string) (*gtsmodel.Status, bool) { + // Perform safe ID lookup + c.mutex.Lock() + id, ok := c.urls[url] + + // Not found, unlock early + if !ok { + c.mutex.Unlock() + return nil, false + } + + // Attempt status lookup + status, ok := c.getByID(id) + c.mutex.Unlock() + return status, ok +} + +// GetByURI attempts to fetch a status from the cache by its URI +func (c *StatusCache) GetByURI(uri string) (*gtsmodel.Status, bool) { + // Perform safe ID lookup + c.mutex.Lock() + id, ok := c.uris[uri] + + // Not found, unlock early + if !ok { + c.mutex.Unlock() + return nil, false + } + + // Attempt status lookup + status, ok := c.getByID(id) + c.mutex.Unlock() + return status, ok +} + +// getByID performs an unsafe (no mutex locks) lookup of status by ID +func (c *StatusCache) getByID(id string) (*gtsmodel.Status, bool) { + v, ok := c.cache.Get(id) + if !ok { + return nil, false + } + return v.(*gtsmodel.Status), true +} + +// Put places a status in the cache +func (c *StatusCache) Put(status *gtsmodel.Status) { + if status == nil || status.ID == "" || + status.URL == "" || + status.URI == "" { + panic("invalid status") + } + + c.mutex.Lock() + c.cache.Set(status.ID, status) + c.urls[status.URL] = status.ID + c.uris[status.URI] = status.ID + c.mutex.Unlock() +} diff --git a/internal/cache/status_test.go b/internal/cache/status_test.go new file mode 100644 index 000000000..10dee5bca --- /dev/null +++ b/internal/cache/status_test.go @@ -0,0 +1,41 @@ +package cache_test + +import ( + "testing" + + "github.com/superseriousbusiness/gotosocial/internal/cache" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func TestStatusCache(t *testing.T) { + cache := cache.NewStatusCache() + + // Attempt to place a status + status := gtsmodel.Status{ + ID: "id", + URI: "uri", + URL: "url", + } + cache.Put(&status) + + var ok bool + var check *gtsmodel.Status + + // Check we can retrieve + check, ok = cache.GetByID(status.ID) + if !ok || !statusIs(&status, check) { + t.Fatal("Could not find expected status") + } + check, ok = cache.GetByURI(status.URI) + if !ok || !statusIs(&status, check) { + t.Fatal("Could not find expected status") + } + check, ok = cache.GetByURL(status.URL) + if !ok || !statusIs(&status, check) { + t.Fatal("Could not find expected status") + } +} + +func statusIs(status1, status2 *gtsmodel.Status) bool { + return status1.ID == status2.ID && status1.URI == status2.URI && status1.URL == status2.URL +} |