mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-06 22:48:08 -06:00
[chore] update database caching library (#1040)
* convert most of the caches to use result.Cache{}
* add caching of emojis
* fix issues causing failing tests
* update go-cache/v2 instances with v3
* fix getnotification
* add a note about the left-in StatusCreate comment
* update EmojiCategory db access to use new result.Cache{}
* fix possible panic in getstatusparents
* further proof that kim is not stinky
This commit is contained in:
parent
9ab60136dd
commit
8598dea98b
55 changed files with 725 additions and 2289 deletions
171
internal/cache/account.go
vendored
171
internal/cache/account.go
vendored
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-cache/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// AccountCache is a cache wrapper to provide URL and URI lookups for gtsmodel.Account
|
||||
type AccountCache struct {
|
||||
cache cache.LookupCache[string, string, *gtsmodel.Account]
|
||||
}
|
||||
|
||||
// NewAccountCache returns a new instantiated AccountCache object
|
||||
func NewAccountCache() *AccountCache {
|
||||
c := &AccountCache{}
|
||||
c.cache = cache.NewLookup(cache.LookupCfg[string, string, *gtsmodel.Account]{
|
||||
RegisterLookups: func(lm *cache.LookupMap[string, string]) {
|
||||
lm.RegisterLookup("uri")
|
||||
lm.RegisterLookup("url")
|
||||
lm.RegisterLookup("pubkeyid")
|
||||
lm.RegisterLookup("usernamedomain")
|
||||
},
|
||||
|
||||
AddLookups: func(lm *cache.LookupMap[string, string], acc *gtsmodel.Account) {
|
||||
if uri := acc.URI; uri != "" {
|
||||
lm.Set("uri", uri, acc.ID)
|
||||
}
|
||||
if url := acc.URL; url != "" {
|
||||
lm.Set("url", url, acc.ID)
|
||||
}
|
||||
lm.Set("pubkeyid", acc.PublicKeyURI, acc.ID)
|
||||
lm.Set("usernamedomain", usernameDomainKey(acc.Username, acc.Domain), acc.ID)
|
||||
},
|
||||
|
||||
DeleteLookups: func(lm *cache.LookupMap[string, string], acc *gtsmodel.Account) {
|
||||
if uri := acc.URI; uri != "" {
|
||||
lm.Delete("uri", uri)
|
||||
}
|
||||
if url := acc.URL; url != "" {
|
||||
lm.Delete("url", url)
|
||||
}
|
||||
lm.Delete("pubkeyid", acc.PublicKeyURI)
|
||||
lm.Delete("usernamedomain", usernameDomainKey(acc.Username, acc.Domain))
|
||||
},
|
||||
})
|
||||
c.cache.SetTTL(time.Minute*5, false)
|
||||
c.cache.Start(time.Second * 10)
|
||||
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) {
|
||||
return c.cache.Get(id)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return c.cache.GetBy("url", url)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return c.cache.GetBy("uri", uri)
|
||||
}
|
||||
|
||||
// GettByUsernameDomain attempts to fetch an account from the cache by its username@domain combo (or just username), you will receive a copy for thread-safety.
|
||||
func (c *AccountCache) GetByUsernameDomain(username string, domain string) (*gtsmodel.Account, bool) {
|
||||
return c.cache.GetBy("usernamedomain", usernameDomainKey(username, domain))
|
||||
}
|
||||
|
||||
// GetByPubkeyID attempts to fetch an account from the cache by its public key URI (ID), you will receive a copy for thread-safety.
|
||||
func (c *AccountCache) GetByPubkeyID(id string) (*gtsmodel.Account, bool) {
|
||||
return c.cache.GetBy("pubkeyid", id)
|
||||
}
|
||||
|
||||
// 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.cache.Set(account.ID, copyAccount(account))
|
||||
}
|
||||
|
||||
// Invalidate removes (invalidates) one account from the cache by its ID.
|
||||
func (c *AccountCache) Invalidate(id string) {
|
||||
c.cache.Invalidate(id)
|
||||
}
|
||||
|
||||
// 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,
|
||||
EmojiIDs: account.EmojiIDs,
|
||||
Emojis: nil,
|
||||
Fields: account.Fields,
|
||||
Note: account.Note,
|
||||
NoteRaw: account.NoteRaw,
|
||||
Memorial: copyBoolPtr(account.Memorial),
|
||||
MovedToAccountID: account.MovedToAccountID,
|
||||
Bot: copyBoolPtr(account.Bot),
|
||||
CreatedAt: account.CreatedAt,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
Reason: account.Reason,
|
||||
Locked: copyBoolPtr(account.Locked),
|
||||
Discoverable: copyBoolPtr(account.Discoverable),
|
||||
Privacy: account.Privacy,
|
||||
Sensitive: copyBoolPtr(account.Sensitive),
|
||||
Language: account.Language,
|
||||
StatusFormat: account.StatusFormat,
|
||||
CustomCSS: account.CustomCSS,
|
||||
URI: account.URI,
|
||||
URL: account.URL,
|
||||
LastWebfingeredAt: account.LastWebfingeredAt,
|
||||
InboxURI: account.InboxURI,
|
||||
SharedInboxURI: account.SharedInboxURI,
|
||||
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: copyBoolPtr(account.HideCollections),
|
||||
SuspensionOrigin: account.SuspensionOrigin,
|
||||
EnableRSS: copyBoolPtr(account.EnableRSS),
|
||||
}
|
||||
}
|
||||
|
||||
func usernameDomainKey(username string, domain string) string {
|
||||
u := "@" + username
|
||||
if domain != "" {
|
||||
return u + "@" + domain
|
||||
}
|
||||
return u
|
||||
}
|
||||
96
internal/cache/account_test.go
vendored
96
internal/cache/account_test.go
vendored
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/cache"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type AccountCacheTestSuite struct {
|
||||
suite.Suite
|
||||
data map[string]*gtsmodel.Account
|
||||
cache *cache.AccountCache
|
||||
}
|
||||
|
||||
func (suite *AccountCacheTestSuite) SetupSuite() {
|
||||
suite.data = testrig.NewTestAccounts()
|
||||
}
|
||||
|
||||
func (suite *AccountCacheTestSuite) SetupTest() {
|
||||
suite.cache = cache.NewAccountCache()
|
||||
}
|
||||
|
||||
func (suite *AccountCacheTestSuite) TearDownTest() {
|
||||
suite.data = nil
|
||||
suite.cache = nil
|
||||
}
|
||||
|
||||
func (suite *AccountCacheTestSuite) TestAccountCache() {
|
||||
for _, account := range suite.data {
|
||||
// Place in the cache
|
||||
suite.cache.Put(account)
|
||||
}
|
||||
|
||||
for _, account := range suite.data {
|
||||
var ok bool
|
||||
var check *gtsmodel.Account
|
||||
|
||||
// Check we can retrieve
|
||||
check, ok = suite.cache.GetByID(account.ID)
|
||||
if !ok && !accountIs(account, check) {
|
||||
suite.Fail(fmt.Sprintf("Failed to fetch expected account with ID: %s", account.ID))
|
||||
}
|
||||
check, ok = suite.cache.GetByURI(account.URI)
|
||||
if account.URI != "" && !ok && !accountIs(account, check) {
|
||||
suite.Fail(fmt.Sprintf("Failed to fetch expected account with URI: %s", account.URI))
|
||||
}
|
||||
check, ok = suite.cache.GetByURL(account.URL)
|
||||
if account.URL != "" && !ok && !accountIs(account, check) {
|
||||
suite.Fail(fmt.Sprintf("Failed to fetch expected account with URL: %s", account.URL))
|
||||
}
|
||||
check, ok = suite.cache.GetByPubkeyID(account.PublicKeyURI)
|
||||
if account.PublicKeyURI != "" && !ok && !accountIs(account, check) {
|
||||
suite.Fail(fmt.Sprintf("Failed to fetch expected account with public key URI: %s", account.PublicKeyURI))
|
||||
}
|
||||
check, ok = suite.cache.GetByUsernameDomain(account.Username, account.Domain)
|
||||
if !ok && !accountIs(account, check) {
|
||||
suite.Fail(fmt.Sprintf("Failed to fetch expected account with username/domain: %s/%s", account.Username, account.Domain))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountCache(t *testing.T) {
|
||||
suite.Run(t, &AccountCacheTestSuite{})
|
||||
}
|
||||
|
||||
func accountIs(account1, account2 *gtsmodel.Account) bool {
|
||||
if account1 == nil || account2 == nil {
|
||||
return account1 == account2
|
||||
}
|
||||
return account1.ID == account2.ID &&
|
||||
account1.URI == account2.URI &&
|
||||
account1.URL == account2.URL &&
|
||||
account1.PublicKeyURI == account2.PublicKeyURI
|
||||
}
|
||||
106
internal/cache/domain.go
vendored
106
internal/cache/domain.go
vendored
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-cache/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// DomainCache is a cache wrapper to provide URL and URI lookups for gtsmodel.Status
|
||||
type DomainBlockCache struct {
|
||||
cache cache.LookupCache[string, string, *gtsmodel.DomainBlock]
|
||||
}
|
||||
|
||||
// NewStatusCache returns a new instantiated statusCache object
|
||||
func NewDomainBlockCache() *DomainBlockCache {
|
||||
c := &DomainBlockCache{}
|
||||
c.cache = cache.NewLookup(cache.LookupCfg[string, string, *gtsmodel.DomainBlock]{
|
||||
RegisterLookups: func(lm *cache.LookupMap[string, string]) {
|
||||
lm.RegisterLookup("id")
|
||||
},
|
||||
|
||||
AddLookups: func(lm *cache.LookupMap[string, string], block *gtsmodel.DomainBlock) {
|
||||
// Block can be equal to nil when sentinel
|
||||
if block != nil && block.ID != "" {
|
||||
lm.Set("id", block.ID, block.Domain)
|
||||
}
|
||||
},
|
||||
|
||||
DeleteLookups: func(lm *cache.LookupMap[string, string], block *gtsmodel.DomainBlock) {
|
||||
// Block can be equal to nil when sentinel
|
||||
if block != nil && block.ID != "" {
|
||||
lm.Delete("id", block.ID)
|
||||
}
|
||||
},
|
||||
})
|
||||
c.cache.SetTTL(time.Minute*5, false)
|
||||
c.cache.Start(time.Second * 10)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetByID attempts to fetch a status from the cache by its ID, you will receive a copy for thread-safety
|
||||
func (c *DomainBlockCache) GetByID(id string) (*gtsmodel.DomainBlock, bool) {
|
||||
return c.cache.GetBy("id", id)
|
||||
}
|
||||
|
||||
// GetByURL attempts to fetch a status from the cache by its URL, you will receive a copy for thread-safety
|
||||
func (c *DomainBlockCache) GetByDomain(domain string) (*gtsmodel.DomainBlock, bool) {
|
||||
return c.cache.Get(domain)
|
||||
}
|
||||
|
||||
// Put places a status in the cache, ensuring that the object place is a copy for thread-safety
|
||||
func (c *DomainBlockCache) Put(domain string, block *gtsmodel.DomainBlock) {
|
||||
if domain == "" {
|
||||
panic("invalid domain")
|
||||
}
|
||||
|
||||
if block == nil {
|
||||
// This is a sentinel value for (no block)
|
||||
c.cache.Set(domain, nil)
|
||||
} else {
|
||||
// This is a valid domain block
|
||||
c.cache.Set(domain, copyDomainBlock(block))
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateByDomain will invalidate a domain block from the cache by domain name.
|
||||
func (c *DomainBlockCache) InvalidateByDomain(domain string) {
|
||||
c.cache.Invalidate(domain)
|
||||
}
|
||||
|
||||
// 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 copyDomainBlock(block *gtsmodel.DomainBlock) *gtsmodel.DomainBlock {
|
||||
return >smodel.DomainBlock{
|
||||
ID: block.ID,
|
||||
CreatedAt: block.CreatedAt,
|
||||
UpdatedAt: block.UpdatedAt,
|
||||
Domain: block.Domain,
|
||||
CreatedByAccountID: block.CreatedByAccountID,
|
||||
CreatedByAccount: nil,
|
||||
PrivateComment: block.PrivateComment,
|
||||
PublicComment: block.PublicComment,
|
||||
Obfuscate: block.Obfuscate,
|
||||
SubscriptionID: block.SubscriptionID,
|
||||
}
|
||||
}
|
||||
131
internal/cache/emoji.go
vendored
131
internal/cache/emoji.go
vendored
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-cache/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// EmojiCache is a cache wrapper to provide ID and URI lookups for gtsmodel.Emoji
|
||||
type EmojiCache struct {
|
||||
cache cache.LookupCache[string, string, *gtsmodel.Emoji]
|
||||
}
|
||||
|
||||
// NewEmojiCache returns a new instantiated EmojiCache object
|
||||
func NewEmojiCache() *EmojiCache {
|
||||
c := &EmojiCache{}
|
||||
c.cache = cache.NewLookup(cache.LookupCfg[string, string, *gtsmodel.Emoji]{
|
||||
RegisterLookups: func(lm *cache.LookupMap[string, string]) {
|
||||
lm.RegisterLookup("uri")
|
||||
lm.RegisterLookup("shortcodedomain")
|
||||
lm.RegisterLookup("imagestaticurl")
|
||||
},
|
||||
|
||||
AddLookups: func(lm *cache.LookupMap[string, string], emoji *gtsmodel.Emoji) {
|
||||
lm.Set("shortcodedomain", shortcodeDomainKey(emoji.Shortcode, emoji.Domain), emoji.ID)
|
||||
if uri := emoji.URI; uri != "" {
|
||||
lm.Set("uri", uri, emoji.ID)
|
||||
}
|
||||
if imageStaticURL := emoji.ImageStaticURL; imageStaticURL != "" {
|
||||
lm.Set("imagestaticurl", imageStaticURL, emoji.ID)
|
||||
}
|
||||
},
|
||||
|
||||
DeleteLookups: func(lm *cache.LookupMap[string, string], emoji *gtsmodel.Emoji) {
|
||||
lm.Delete("shortcodedomain", shortcodeDomainKey(emoji.Shortcode, emoji.Domain))
|
||||
if uri := emoji.URI; uri != "" {
|
||||
lm.Delete("uri", uri)
|
||||
}
|
||||
if imageStaticURL := emoji.ImageStaticURL; imageStaticURL != "" {
|
||||
lm.Delete("imagestaticurl", imageStaticURL)
|
||||
}
|
||||
},
|
||||
})
|
||||
c.cache.SetTTL(time.Minute*5, false)
|
||||
c.cache.Start(time.Second * 10)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetByID attempts to fetch an emoji from the cache by its ID, you will receive a copy for thread-safety
|
||||
func (c *EmojiCache) GetByID(id string) (*gtsmodel.Emoji, bool) {
|
||||
return c.cache.Get(id)
|
||||
}
|
||||
|
||||
// GetByURI attempts to fetch an emoji from the cache by its URI, you will receive a copy for thread-safety
|
||||
func (c *EmojiCache) GetByURI(uri string) (*gtsmodel.Emoji, bool) {
|
||||
return c.cache.GetBy("uri", uri)
|
||||
}
|
||||
|
||||
func (c *EmojiCache) GetByShortcodeDomain(shortcode string, domain string) (*gtsmodel.Emoji, bool) {
|
||||
return c.cache.GetBy("shortcodedomain", shortcodeDomainKey(shortcode, domain))
|
||||
}
|
||||
|
||||
func (c *EmojiCache) GetByImageStaticURL(imageStaticURL string) (*gtsmodel.Emoji, bool) {
|
||||
return c.cache.GetBy("imagestaticurl", imageStaticURL)
|
||||
}
|
||||
|
||||
// Put places an emoji in the cache, ensuring that the object place is a copy for thread-safety
|
||||
func (c *EmojiCache) Put(emoji *gtsmodel.Emoji) {
|
||||
if emoji == nil || emoji.ID == "" {
|
||||
panic("invalid emoji")
|
||||
}
|
||||
c.cache.Set(emoji.ID, copyEmoji(emoji))
|
||||
}
|
||||
|
||||
func (c *EmojiCache) Invalidate(emojiID string) {
|
||||
c.cache.Invalidate(emojiID)
|
||||
}
|
||||
|
||||
// copyEmoji performs a surface-level copy of emoji, 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 copyEmoji(emoji *gtsmodel.Emoji) *gtsmodel.Emoji {
|
||||
return >smodel.Emoji{
|
||||
ID: emoji.ID,
|
||||
CreatedAt: emoji.CreatedAt,
|
||||
UpdatedAt: emoji.UpdatedAt,
|
||||
Shortcode: emoji.Shortcode,
|
||||
Domain: emoji.Domain,
|
||||
ImageRemoteURL: emoji.ImageRemoteURL,
|
||||
ImageStaticRemoteURL: emoji.ImageStaticRemoteURL,
|
||||
ImageURL: emoji.ImageURL,
|
||||
ImageStaticURL: emoji.ImageStaticURL,
|
||||
ImagePath: emoji.ImagePath,
|
||||
ImageStaticPath: emoji.ImageStaticPath,
|
||||
ImageContentType: emoji.ImageContentType,
|
||||
ImageStaticContentType: emoji.ImageStaticContentType,
|
||||
ImageFileSize: emoji.ImageFileSize,
|
||||
ImageStaticFileSize: emoji.ImageStaticFileSize,
|
||||
ImageUpdatedAt: emoji.ImageUpdatedAt,
|
||||
Disabled: copyBoolPtr(emoji.Disabled),
|
||||
URI: emoji.URI,
|
||||
VisibleInPicker: copyBoolPtr(emoji.VisibleInPicker),
|
||||
CategoryID: emoji.CategoryID,
|
||||
}
|
||||
}
|
||||
|
||||
func shortcodeDomainKey(shortcode string, domain string) string {
|
||||
if domain != "" {
|
||||
return shortcode + "@" + domain
|
||||
}
|
||||
return shortcode
|
||||
}
|
||||
84
internal/cache/emojicategory.go
vendored
84
internal/cache/emojicategory.go
vendored
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-cache/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// EmojiCategoryCache is a cache wrapper to provide ID lookups for gtsmodel.EmojiCategory
|
||||
type EmojiCategoryCache struct {
|
||||
cache cache.LookupCache[string, string, *gtsmodel.EmojiCategory]
|
||||
}
|
||||
|
||||
// NewEmojiCategoryCache returns a new instantiated EmojiCategoryCache object
|
||||
func NewEmojiCategoryCache() *EmojiCategoryCache {
|
||||
c := &EmojiCategoryCache{}
|
||||
c.cache = cache.NewLookup(cache.LookupCfg[string, string, *gtsmodel.EmojiCategory]{
|
||||
RegisterLookups: func(lm *cache.LookupMap[string, string]) {
|
||||
lm.RegisterLookup("name")
|
||||
},
|
||||
|
||||
AddLookups: func(lm *cache.LookupMap[string, string], emojiCategory *gtsmodel.EmojiCategory) {
|
||||
lm.Set(("name"), strings.ToLower(emojiCategory.Name), emojiCategory.ID)
|
||||
},
|
||||
|
||||
DeleteLookups: func(lm *cache.LookupMap[string, string], emojiCategory *gtsmodel.EmojiCategory) {
|
||||
lm.Delete("name", strings.ToLower(emojiCategory.Name))
|
||||
},
|
||||
})
|
||||
c.cache.SetTTL(time.Minute*5, false)
|
||||
c.cache.Start(time.Second * 10)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetByID attempts to fetch an emojiCategory from the cache by its ID, you will receive a copy for thread-safety
|
||||
func (c *EmojiCategoryCache) GetByID(id string) (*gtsmodel.EmojiCategory, bool) {
|
||||
return c.cache.Get(id)
|
||||
}
|
||||
|
||||
// GetByName attempts to fetch an emojiCategory from the cache by its name, you will receive a copy for thread-safety
|
||||
func (c *EmojiCategoryCache) GetByName(name string) (*gtsmodel.EmojiCategory, bool) {
|
||||
return c.cache.GetBy("name", strings.ToLower(name))
|
||||
}
|
||||
|
||||
// Put places an emojiCategory in the cache, ensuring that the object place is a copy for thread-safety
|
||||
func (c *EmojiCategoryCache) Put(emoji *gtsmodel.EmojiCategory) {
|
||||
if emoji == nil || emoji.ID == "" {
|
||||
panic("invalid emoji")
|
||||
}
|
||||
c.cache.Set(emoji.ID, copyEmojiCategory(emoji))
|
||||
}
|
||||
|
||||
func (c *EmojiCategoryCache) Invalidate(emojiID string) {
|
||||
c.cache.Invalidate(emojiID)
|
||||
}
|
||||
|
||||
func copyEmojiCategory(emojiCategory *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory {
|
||||
return >smodel.EmojiCategory{
|
||||
ID: emojiCategory.ID,
|
||||
CreatedAt: emojiCategory.CreatedAt,
|
||||
UpdatedAt: emojiCategory.UpdatedAt,
|
||||
Name: emojiCategory.Name,
|
||||
}
|
||||
}
|
||||
138
internal/cache/status.go
vendored
138
internal/cache/status.go
vendored
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-cache/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// StatusCache is a cache wrapper to provide URL and URI lookups for gtsmodel.Status
|
||||
type StatusCache struct {
|
||||
cache cache.LookupCache[string, string, *gtsmodel.Status]
|
||||
}
|
||||
|
||||
// NewStatusCache returns a new instantiated statusCache object
|
||||
func NewStatusCache() *StatusCache {
|
||||
c := &StatusCache{}
|
||||
c.cache = cache.NewLookup(cache.LookupCfg[string, string, *gtsmodel.Status]{
|
||||
RegisterLookups: func(lm *cache.LookupMap[string, string]) {
|
||||
lm.RegisterLookup("uri")
|
||||
lm.RegisterLookup("url")
|
||||
},
|
||||
|
||||
AddLookups: func(lm *cache.LookupMap[string, string], status *gtsmodel.Status) {
|
||||
if uri := status.URI; uri != "" {
|
||||
lm.Set("uri", uri, status.ID)
|
||||
}
|
||||
if url := status.URL; url != "" {
|
||||
lm.Set("url", url, status.ID)
|
||||
}
|
||||
},
|
||||
|
||||
DeleteLookups: func(lm *cache.LookupMap[string, string], status *gtsmodel.Status) {
|
||||
if uri := status.URI; uri != "" {
|
||||
lm.Delete("uri", uri)
|
||||
}
|
||||
if url := status.URL; url != "" {
|
||||
lm.Delete("url", url)
|
||||
}
|
||||
},
|
||||
})
|
||||
c.cache.SetTTL(time.Minute*5, false)
|
||||
c.cache.Start(time.Second * 10)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetByID attempts to fetch a status from the cache by its ID, you will receive a copy for thread-safety
|
||||
func (c *StatusCache) GetByID(id string) (*gtsmodel.Status, bool) {
|
||||
return c.cache.Get(id)
|
||||
}
|
||||
|
||||
// GetByURL attempts to fetch a status from the cache by its URL, you will receive a copy for thread-safety
|
||||
func (c *StatusCache) GetByURL(url string) (*gtsmodel.Status, bool) {
|
||||
return c.cache.GetBy("url", url)
|
||||
}
|
||||
|
||||
// GetByURI attempts to fetch a status from the cache by its URI, you will receive a copy for thread-safety
|
||||
func (c *StatusCache) GetByURI(uri string) (*gtsmodel.Status, bool) {
|
||||
return c.cache.GetBy("uri", uri)
|
||||
}
|
||||
|
||||
// Put places a status in the cache, ensuring that the object place is a copy for thread-safety
|
||||
func (c *StatusCache) Put(status *gtsmodel.Status) {
|
||||
if status == nil || status.ID == "" {
|
||||
panic("invalid status")
|
||||
}
|
||||
c.cache.Set(status.ID, copyStatus(status))
|
||||
}
|
||||
|
||||
// Invalidate invalidates one status from the cache using the ID of the status as key.
|
||||
func (c *StatusCache) Invalidate(statusID string) {
|
||||
c.cache.Invalidate(statusID)
|
||||
}
|
||||
|
||||
// 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,
|
||||
Local: copyBoolPtr(status.Local),
|
||||
CreatedAt: status.CreatedAt,
|
||||
UpdatedAt: status.UpdatedAt,
|
||||
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: copyBoolPtr(status.Sensitive),
|
||||
Language: status.Language,
|
||||
CreatedWithApplicationID: status.CreatedWithApplicationID,
|
||||
ActivityStreamsType: status.ActivityStreamsType,
|
||||
Text: status.Text,
|
||||
Pinned: copyBoolPtr(status.Pinned),
|
||||
Federated: copyBoolPtr(status.Federated),
|
||||
Boostable: copyBoolPtr(status.Boostable),
|
||||
Replyable: copyBoolPtr(status.Replyable),
|
||||
Likeable: copyBoolPtr(status.Likeable),
|
||||
}
|
||||
}
|
||||
113
internal/cache/status_test.go
vendored
113
internal/cache/status_test.go
vendored
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/cache"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type StatusCacheTestSuite struct {
|
||||
suite.Suite
|
||||
data map[string]*gtsmodel.Status
|
||||
cache *cache.StatusCache
|
||||
}
|
||||
|
||||
func (suite *StatusCacheTestSuite) SetupSuite() {
|
||||
suite.data = testrig.NewTestStatuses()
|
||||
}
|
||||
|
||||
func (suite *StatusCacheTestSuite) SetupTest() {
|
||||
suite.cache = cache.NewStatusCache()
|
||||
}
|
||||
|
||||
func (suite *StatusCacheTestSuite) TearDownTest() {
|
||||
suite.data = nil
|
||||
suite.cache = nil
|
||||
}
|
||||
|
||||
func (suite *StatusCacheTestSuite) TestStatusCache() {
|
||||
for _, status := range suite.data {
|
||||
// Place in the cache
|
||||
suite.cache.Put(status)
|
||||
}
|
||||
|
||||
for _, status := range suite.data {
|
||||
var ok bool
|
||||
var check *gtsmodel.Status
|
||||
|
||||
// Check we can retrieve
|
||||
check, ok = suite.cache.GetByID(status.ID)
|
||||
if !ok && !statusIs(status, check) {
|
||||
suite.Fail(fmt.Sprintf("Failed to fetch expected account with ID: %s", status.ID))
|
||||
}
|
||||
check, ok = suite.cache.GetByURI(status.URI)
|
||||
if status.URI != "" && !ok && !statusIs(status, check) {
|
||||
suite.Fail(fmt.Sprintf("Failed to fetch expected account with URI: %s", status.URI))
|
||||
}
|
||||
check, ok = suite.cache.GetByURL(status.URL)
|
||||
if status.URL != "" && !ok && !statusIs(status, check) {
|
||||
suite.Fail(fmt.Sprintf("Failed to fetch expected account with URL: %s", status.URL))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *StatusCacheTestSuite) TestBoolPointerCopying() {
|
||||
originalStatus := suite.data["local_account_1_status_1"]
|
||||
|
||||
// mark the status as pinned + cache it
|
||||
pinned := true
|
||||
originalStatus.Pinned = &pinned
|
||||
suite.cache.Put(originalStatus)
|
||||
|
||||
// retrieve it
|
||||
cachedStatus, ok := suite.cache.GetByID(originalStatus.ID)
|
||||
if !ok {
|
||||
suite.FailNow("status wasn't retrievable from cache")
|
||||
}
|
||||
|
||||
// we should be able to change the original status values + cached
|
||||
// values independently since they use different pointers
|
||||
suite.True(*cachedStatus.Pinned)
|
||||
*originalStatus.Pinned = false
|
||||
suite.False(*originalStatus.Pinned)
|
||||
suite.True(*cachedStatus.Pinned)
|
||||
*originalStatus.Pinned = true
|
||||
*cachedStatus.Pinned = false
|
||||
suite.True(*originalStatus.Pinned)
|
||||
suite.False(*cachedStatus.Pinned)
|
||||
}
|
||||
|
||||
func TestStatusCache(t *testing.T) {
|
||||
suite.Run(t, &StatusCacheTestSuite{})
|
||||
}
|
||||
|
||||
func statusIs(status1, status2 *gtsmodel.Status) bool {
|
||||
if status1 == nil || status2 == nil {
|
||||
return status1 == status2
|
||||
}
|
||||
return status1.ID == status2.ID &&
|
||||
status1.URI == status2.URI &&
|
||||
status1.URL == status2.URL
|
||||
}
|
||||
141
internal/cache/user.go
vendored
141
internal/cache/user.go
vendored
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-cache/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// UserCache is a cache wrapper to provide lookups for gtsmodel.User
|
||||
type UserCache struct {
|
||||
cache cache.LookupCache[string, string, *gtsmodel.User]
|
||||
}
|
||||
|
||||
// NewUserCache returns a new instantiated UserCache object
|
||||
func NewUserCache() *UserCache {
|
||||
c := &UserCache{}
|
||||
c.cache = cache.NewLookup(cache.LookupCfg[string, string, *gtsmodel.User]{
|
||||
RegisterLookups: func(lm *cache.LookupMap[string, string]) {
|
||||
lm.RegisterLookup("accountid")
|
||||
lm.RegisterLookup("email")
|
||||
lm.RegisterLookup("unconfirmedemail")
|
||||
lm.RegisterLookup("confirmationtoken")
|
||||
},
|
||||
|
||||
AddLookups: func(lm *cache.LookupMap[string, string], user *gtsmodel.User) {
|
||||
lm.Set("accountid", user.AccountID, user.ID)
|
||||
if email := user.Email; email != "" {
|
||||
lm.Set("email", email, user.ID)
|
||||
}
|
||||
if unconfirmedEmail := user.UnconfirmedEmail; unconfirmedEmail != "" {
|
||||
lm.Set("unconfirmedemail", unconfirmedEmail, user.ID)
|
||||
}
|
||||
if confirmationToken := user.ConfirmationToken; confirmationToken != "" {
|
||||
lm.Set("confirmationtoken", confirmationToken, user.ID)
|
||||
}
|
||||
},
|
||||
|
||||
DeleteLookups: func(lm *cache.LookupMap[string, string], user *gtsmodel.User) {
|
||||
lm.Delete("accountid", user.AccountID)
|
||||
if email := user.Email; email != "" {
|
||||
lm.Delete("email", email)
|
||||
}
|
||||
if unconfirmedEmail := user.UnconfirmedEmail; unconfirmedEmail != "" {
|
||||
lm.Delete("unconfirmedemail", unconfirmedEmail)
|
||||
}
|
||||
if confirmationToken := user.ConfirmationToken; confirmationToken != "" {
|
||||
lm.Delete("confirmationtoken", confirmationToken)
|
||||
}
|
||||
},
|
||||
})
|
||||
c.cache.SetTTL(time.Minute*5, false)
|
||||
c.cache.Start(time.Second * 10)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetByID attempts to fetch a user from the cache by its ID, you will receive a copy for thread-safety
|
||||
func (c *UserCache) GetByID(id string) (*gtsmodel.User, bool) {
|
||||
return c.cache.Get(id)
|
||||
}
|
||||
|
||||
// GetByAccountID attempts to fetch a user from the cache by its account ID, you will receive a copy for thread-safety
|
||||
func (c *UserCache) GetByAccountID(accountID string) (*gtsmodel.User, bool) {
|
||||
return c.cache.GetBy("accountid", accountID)
|
||||
}
|
||||
|
||||
// GetByEmail attempts to fetch a user from the cache by its email address, you will receive a copy for thread-safety
|
||||
func (c *UserCache) GetByEmail(email string) (*gtsmodel.User, bool) {
|
||||
return c.cache.GetBy("email", email)
|
||||
}
|
||||
|
||||
// GetByUnconfirmedEmail attempts to fetch a user from the cache by its confirmation token, you will receive a copy for thread-safety
|
||||
func (c *UserCache) GetByConfirmationToken(token string) (*gtsmodel.User, bool) {
|
||||
return c.cache.GetBy("confirmationtoken", token)
|
||||
}
|
||||
|
||||
// Put places a user in the cache, ensuring that the object place is a copy for thread-safety
|
||||
func (c *UserCache) Put(user *gtsmodel.User) {
|
||||
if user == nil || user.ID == "" {
|
||||
panic("invalid user")
|
||||
}
|
||||
c.cache.Set(user.ID, copyUser(user))
|
||||
}
|
||||
|
||||
// Invalidate invalidates one user from the cache using the ID of the user as key.
|
||||
func (c *UserCache) Invalidate(userID string) {
|
||||
c.cache.Invalidate(userID)
|
||||
}
|
||||
|
||||
func copyUser(user *gtsmodel.User) *gtsmodel.User {
|
||||
return >smodel.User{
|
||||
ID: user.ID,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
Email: user.Email,
|
||||
AccountID: user.AccountID,
|
||||
Account: nil,
|
||||
EncryptedPassword: user.EncryptedPassword,
|
||||
SignUpIP: user.SignUpIP,
|
||||
CurrentSignInAt: user.CurrentSignInAt,
|
||||
CurrentSignInIP: user.CurrentSignInIP,
|
||||
LastSignInAt: user.LastSignInAt,
|
||||
LastSignInIP: user.LastSignInIP,
|
||||
SignInCount: user.SignInCount,
|
||||
InviteID: user.InviteID,
|
||||
ChosenLanguages: user.ChosenLanguages,
|
||||
FilteredLanguages: user.FilteredLanguages,
|
||||
Locale: user.Locale,
|
||||
CreatedByApplicationID: user.CreatedByApplicationID,
|
||||
CreatedByApplication: nil,
|
||||
LastEmailedAt: user.LastEmailedAt,
|
||||
ConfirmationToken: user.ConfirmationToken,
|
||||
ConfirmationSentAt: user.ConfirmationSentAt,
|
||||
ConfirmedAt: user.ConfirmedAt,
|
||||
UnconfirmedEmail: user.UnconfirmedEmail,
|
||||
Moderator: copyBoolPtr(user.Moderator),
|
||||
Admin: copyBoolPtr(user.Admin),
|
||||
Disabled: copyBoolPtr(user.Disabled),
|
||||
Approved: copyBoolPtr(user.Approved),
|
||||
ResetPasswordToken: user.ResetPasswordToken,
|
||||
ResetPasswordSentAt: user.ResetPasswordSentAt,
|
||||
}
|
||||
}
|
||||
31
internal/cache/util.go
vendored
31
internal/cache/util.go
vendored
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
// copyBoolPtr returns a bool pointer with the same value as the pointer passed into it.
|
||||
//
|
||||
// Useful when copying things from the cache to a caller.
|
||||
func copyBoolPtr(in *bool) *bool {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
b := new(bool)
|
||||
*b = *in
|
||||
return b
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue