| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | package cache | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ReneKroon/ttlcache" | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 	"github.com/sirupsen/logrus" | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AccountCache is a wrapper around ttlcache.Cache to provide URL and URI lookups for gtsmodel.Account | 
					
						
							|  |  |  | type AccountCache struct { | 
					
						
							|  |  |  | 	cache *ttlcache.Cache   // map of IDs -> cached accounts | 
					
						
							|  |  |  | 	urls  map[string]string // map of account URLs -> IDs | 
					
						
							|  |  |  | 	uris  map[string]string // map of account URIs -> IDs | 
					
						
							|  |  |  | 	mutex sync.Mutex | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewAccountCache returns a new instantiated AccountCache object | 
					
						
							|  |  |  | func NewAccountCache() *AccountCache { | 
					
						
							|  |  |  | 	c := AccountCache{ | 
					
						
							|  |  |  | 		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
										 |  |  | 		account, ok := value.(*gtsmodel.Account) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			logrus.Panicf("AccountCache could not assert entry with key %s to *gtsmodel.Account", key) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		c.mutex.Lock() | 
					
						
							|  |  |  | 		delete(c.urls, account.URL) | 
					
						
							|  |  |  | 		delete(c.uris, account.URI) | 
					
						
							|  |  |  | 		c.mutex.Unlock() | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &c | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetByID attempts to fetch a account from the cache by its ID, you will receive a copy for thread-safety | 
					
						
							|  |  |  | func (c *AccountCache) GetByID(id string) (*gtsmodel.Account, bool) { | 
					
						
							|  |  |  | 	c.mutex.Lock() | 
					
						
							|  |  |  | 	account, ok := c.getByID(id) | 
					
						
							|  |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | 	return account, ok | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetByURL attempts to fetch a account from the cache by its URL, you will receive a copy for thread-safety | 
					
						
							|  |  |  | func (c *AccountCache) GetByURL(url string) (*gtsmodel.Account, 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 account lookup | 
					
						
							|  |  |  | 	account, ok := c.getByID(id) | 
					
						
							|  |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | 	return account, ok | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetByURI attempts to fetch a account from the cache by its URI, you will receive a copy for thread-safety | 
					
						
							|  |  |  | func (c *AccountCache) GetByURI(uri string) (*gtsmodel.Account, 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 account lookup | 
					
						
							|  |  |  | 	account, ok := c.getByID(id) | 
					
						
							|  |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | 	return account, ok | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // getByID performs an unsafe (no mutex locks) lookup of account by ID, returning a copy of account in cache | 
					
						
							|  |  |  | func (c *AccountCache) getByID(id string) (*gtsmodel.Account, bool) { | 
					
						
							|  |  |  | 	v, ok := c.cache.Get(id) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil, false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return copyAccount(v.(*gtsmodel.Account)), true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Put places a account in the cache, ensuring that the object place is a copy for thread-safety | 
					
						
							|  |  |  | func (c *AccountCache) Put(account *gtsmodel.Account) { | 
					
						
							|  |  |  | 	if account == nil || account.ID == "" { | 
					
						
							|  |  |  | 		panic("invalid account") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	c.mutex.Lock() | 
					
						
							|  |  |  | 	c.cache.Set(account.ID, copyAccount(account)) | 
					
						
							|  |  |  | 	if account.URL != "" { | 
					
						
							|  |  |  | 		c.urls[account.URL] = account.ID | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if account.URI != "" { | 
					
						
							|  |  |  | 		c.uris[account.URI] = account.ID | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // copyAccount performs a surface-level copy of account, 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 copyAccount(account *gtsmodel.Account) *gtsmodel.Account { | 
					
						
							|  |  |  | 	return >smodel.Account{ | 
					
						
							|  |  |  | 		ID:                      account.ID, | 
					
						
							|  |  |  | 		Username:                account.Username, | 
					
						
							|  |  |  | 		Domain:                  account.Domain, | 
					
						
							|  |  |  | 		AvatarMediaAttachmentID: account.AvatarMediaAttachmentID, | 
					
						
							|  |  |  | 		AvatarMediaAttachment:   nil, | 
					
						
							|  |  |  | 		AvatarRemoteURL:         account.AvatarRemoteURL, | 
					
						
							|  |  |  | 		HeaderMediaAttachmentID: account.HeaderMediaAttachmentID, | 
					
						
							|  |  |  | 		HeaderMediaAttachment:   nil, | 
					
						
							|  |  |  | 		HeaderRemoteURL:         account.HeaderRemoteURL, | 
					
						
							|  |  |  | 		DisplayName:             account.DisplayName, | 
					
						
							|  |  |  | 		Fields:                  account.Fields, | 
					
						
							|  |  |  | 		Note:                    account.Note, | 
					
						
							|  |  |  | 		Memorial:                account.Memorial, | 
					
						
							|  |  |  | 		MovedToAccountID:        account.MovedToAccountID, | 
					
						
							|  |  |  | 		CreatedAt:               account.CreatedAt, | 
					
						
							|  |  |  | 		UpdatedAt:               account.UpdatedAt, | 
					
						
							|  |  |  | 		Bot:                     account.Bot, | 
					
						
							|  |  |  | 		Reason:                  account.Reason, | 
					
						
							|  |  |  | 		Locked:                  account.Locked, | 
					
						
							|  |  |  | 		Discoverable:            account.Discoverable, | 
					
						
							|  |  |  | 		Privacy:                 account.Privacy, | 
					
						
							|  |  |  | 		Sensitive:               account.Sensitive, | 
					
						
							|  |  |  | 		Language:                account.Language, | 
					
						
							|  |  |  | 		URI:                     account.URI, | 
					
						
							|  |  |  | 		URL:                     account.URL, | 
					
						
							|  |  |  | 		LastWebfingeredAt:       account.LastWebfingeredAt, | 
					
						
							|  |  |  | 		InboxURI:                account.InboxURI, | 
					
						
							|  |  |  | 		OutboxURI:               account.OutboxURI, | 
					
						
							|  |  |  | 		FollowingURI:            account.FollowingURI, | 
					
						
							|  |  |  | 		FollowersURI:            account.FollowersURI, | 
					
						
							|  |  |  | 		FeaturedCollectionURI:   account.FeaturedCollectionURI, | 
					
						
							|  |  |  | 		ActorType:               account.ActorType, | 
					
						
							|  |  |  | 		AlsoKnownAs:             account.AlsoKnownAs, | 
					
						
							|  |  |  | 		PrivateKey:              account.PrivateKey, | 
					
						
							|  |  |  | 		PublicKey:               account.PublicKey, | 
					
						
							|  |  |  | 		PublicKeyURI:            account.PublicKeyURI, | 
					
						
							|  |  |  | 		SensitizedAt:            account.SensitizedAt, | 
					
						
							|  |  |  | 		SilencedAt:              account.SilencedAt, | 
					
						
							|  |  |  | 		SuspendedAt:             account.SuspendedAt, | 
					
						
							|  |  |  | 		HideCollections:         account.HideCollections, | 
					
						
							|  |  |  | 		SuspensionOrigin:        account.SuspensionOrigin, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |