| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | package cache | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ReneKroon/ttlcache" | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 	"github.com/sirupsen/logrus" | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-30 11:16:23 +02:00
										 |  |  | // StatusCache is a wrapper around ttlcache.Cache to provide URL and URI lookups for gtsmodel.Status | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-30 11:16:23 +02:00
										 |  |  | // NewStatusCache returns a new instantiated statusCache object | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 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{}) { | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 		status, ok := value.(*gtsmodel.Status) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			logrus.Panicf("StatusCache could not assert entry with key %s to *gtsmodel.Status", key) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		c.mutex.Lock() | 
					
						
							|  |  |  | 		delete(c.urls, status.URL) | 
					
						
							|  |  |  | 		delete(c.uris, status.URI) | 
					
						
							|  |  |  | 		c.mutex.Unlock() | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &c | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | // GetByID attempts to fetch a status from the cache by its ID, you will receive a copy for thread-safety | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | func (c *StatusCache) GetByID(id string) (*gtsmodel.Status, bool) { | 
					
						
							|  |  |  | 	c.mutex.Lock() | 
					
						
							|  |  |  | 	status, ok := c.getByID(id) | 
					
						
							|  |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | 	return status, ok | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | // GetByURL attempts to fetch a status from the cache by its URL, you will receive a copy for thread-safety | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | // GetByURI attempts to fetch a status from the cache by its URI, you will receive a copy for thread-safety | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | // getByID performs an unsafe (no mutex locks) lookup of status by ID, returning a copy of status in cache | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | func (c *StatusCache) getByID(id string) (*gtsmodel.Status, bool) { | 
					
						
							|  |  |  | 	v, ok := c.cache.Get(id) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil, false | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	return copyStatus(v.(*gtsmodel.Status)), true | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | // Put places a status in the cache, ensuring that the object place is a copy for thread-safety | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | func (c *StatusCache) Put(status *gtsmodel.Status) { | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	if status == nil || status.ID == "" { | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 		panic("invalid status") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	c.mutex.Lock() | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	c.cache.Set(status.ID, copyStatus(status)) | 
					
						
							|  |  |  | 	if status.URL != "" { | 
					
						
							|  |  |  | 		c.urls[status.URL] = status.ID | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if status.URI != "" { | 
					
						
							|  |  |  | 		c.uris[status.URI] = status.ID | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // copyStatus performs a surface-level copy of status, only keeping attached IDs intact, not the objects. | 
					
						
							|  |  |  | // due to all the data being copied being 99% primitive types or strings (which are immutable and passed by ptr) | 
					
						
							|  |  |  | // this should be a relatively cheap process | 
					
						
							|  |  |  | func copyStatus(status *gtsmodel.Status) *gtsmodel.Status { | 
					
						
							|  |  |  | 	return >smodel.Status{ | 
					
						
							|  |  |  | 		ID:                       status.ID, | 
					
						
							|  |  |  | 		URI:                      status.URI, | 
					
						
							|  |  |  | 		URL:                      status.URL, | 
					
						
							|  |  |  | 		Content:                  status.Content, | 
					
						
							|  |  |  | 		AttachmentIDs:            status.AttachmentIDs, | 
					
						
							|  |  |  | 		Attachments:              nil, | 
					
						
							|  |  |  | 		TagIDs:                   status.TagIDs, | 
					
						
							|  |  |  | 		Tags:                     nil, | 
					
						
							|  |  |  | 		MentionIDs:               status.MentionIDs, | 
					
						
							|  |  |  | 		Mentions:                 nil, | 
					
						
							|  |  |  | 		EmojiIDs:                 status.EmojiIDs, | 
					
						
							|  |  |  | 		Emojis:                   nil, | 
					
						
							|  |  |  | 		CreatedAt:                status.CreatedAt, | 
					
						
							|  |  |  | 		UpdatedAt:                status.UpdatedAt, | 
					
						
							|  |  |  | 		Local:                    status.Local, | 
					
						
							|  |  |  | 		AccountID:                status.AccountID, | 
					
						
							|  |  |  | 		Account:                  nil, | 
					
						
							|  |  |  | 		AccountURI:               status.AccountURI, | 
					
						
							|  |  |  | 		InReplyToID:              status.InReplyToID, | 
					
						
							|  |  |  | 		InReplyTo:                nil, | 
					
						
							|  |  |  | 		InReplyToURI:             status.InReplyToURI, | 
					
						
							|  |  |  | 		InReplyToAccountID:       status.InReplyToAccountID, | 
					
						
							|  |  |  | 		InReplyToAccount:         nil, | 
					
						
							|  |  |  | 		BoostOfID:                status.BoostOfID, | 
					
						
							|  |  |  | 		BoostOf:                  nil, | 
					
						
							|  |  |  | 		BoostOfAccountID:         status.BoostOfAccountID, | 
					
						
							|  |  |  | 		BoostOfAccount:           nil, | 
					
						
							|  |  |  | 		ContentWarning:           status.ContentWarning, | 
					
						
							|  |  |  | 		Visibility:               status.Visibility, | 
					
						
							|  |  |  | 		Sensitive:                status.Sensitive, | 
					
						
							|  |  |  | 		Language:                 status.Language, | 
					
						
							|  |  |  | 		CreatedWithApplicationID: status.CreatedWithApplicationID, | 
					
						
							| 
									
										
										
										
											2021-09-09 16:15:25 +02:00
										 |  |  | 		Federated:                status.Federated, | 
					
						
							|  |  |  | 		Boostable:                status.Boostable, | 
					
						
							|  |  |  | 		Replyable:                status.Replyable, | 
					
						
							|  |  |  | 		Likeable:                 status.Likeable, | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		ActivityStreamsType:      status.ActivityStreamsType, | 
					
						
							|  |  |  | 		Text:                     status.Text, | 
					
						
							|  |  |  | 		Pinned:                   status.Pinned, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |