mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 21:32:25 -05:00 
			
		
		
		
	
		
			
	
	
		
			158 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			158 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package cache | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"sync" | ||
|  | 
 | ||
|  | 	"github.com/ReneKroon/ttlcache" | ||
|  | 	"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{}) { | ||
|  | 		account := value.(*gtsmodel.Account) | ||
|  | 
 | ||
|  | 		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, | ||
|  | 	} | ||
|  | } |