[chore] move caches to a separate State{} structure (#1078)

* move caches to a separate State{} structure

Signed-off-by: kim <grufwub@gmail.com>

* fix call to log.Panic not using formatted call

Signed-off-by: kim <grufwub@gmail.com>

* move caches to use interfaces, to make switchouts easier in future

Signed-off-by: kim <grufwub@gmail.com>

* fix rebase issue

Signed-off-by: kim <grufwub@gmail.com>

* improve code comment

Signed-off-by: kim <grufwub@gmail.com>

* fix further issues after rebase

Signed-off-by: kim <grufwub@gmail.com>

* heh

Signed-off-by: kim <grufwub@gmail.com>

* add missing license text

Signed-off-by: kim <grufwub@gmail.com>

Signed-off-by: kim <grufwub@gmail.com>
This commit is contained in:
kim 2022-12-08 17:35:14 +00:00 committed by GitHub
parent dd1a4cd892
commit e58d2d8122
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 725 additions and 332 deletions

View file

@ -27,17 +27,24 @@
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb" "github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/validate" "github.com/superseriousbusiness/gotosocial/internal/validate"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// Create creates a new account in the database using the provided flags. // Create creates a new account in the database using the provided flags.
var Create action.GTSAction = func(ctx context.Context) error { var Create action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx) var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername() username := config.GetAdminAccountUsername()
if username == "" { if username == "" {
return errors.New("no username set") return errors.New("no username set")
@ -88,11 +95,17 @@
// Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now. // Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now.
var Confirm action.GTSAction = func(ctx context.Context) error { var Confirm action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx) var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername() username := config.GetAdminAccountUsername()
if username == "" { if username == "" {
return errors.New("no username set") return errors.New("no username set")
@ -125,11 +138,17 @@
// Promote sets a user to admin. // Promote sets a user to admin.
var Promote action.GTSAction = func(ctx context.Context) error { var Promote action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx) var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername() username := config.GetAdminAccountUsername()
if username == "" { if username == "" {
return errors.New("no username set") return errors.New("no username set")
@ -159,11 +178,17 @@
// Demote sets admin on a user to false. // Demote sets admin on a user to false.
var Demote action.GTSAction = func(ctx context.Context) error { var Demote action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx) var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername() username := config.GetAdminAccountUsername()
if username == "" { if username == "" {
return errors.New("no username set") return errors.New("no username set")
@ -193,11 +218,17 @@
// Disable sets Disabled to true on a user. // Disable sets Disabled to true on a user.
var Disable action.GTSAction = func(ctx context.Context) error { var Disable action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx) var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername() username := config.GetAdminAccountUsername()
if username == "" { if username == "" {
return errors.New("no username set") return errors.New("no username set")
@ -227,11 +258,17 @@
// Password sets the password of target account. // Password sets the password of target account.
var Password action.GTSAction = func(ctx context.Context) error { var Password action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx) var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername() username := config.GetAdminAccountUsername()
if username == "" { if username == "" {
return errors.New("no username set") return errors.New("no username set")

View file

@ -27,12 +27,16 @@
"github.com/superseriousbusiness/gotosocial/internal/db/bundb" "github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/state"
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage" gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
) )
// Orphaned prunes orphaned media from storage. // Orphaned prunes orphaned media from storage.
var Orphaned action.GTSAction = func(ctx context.Context) error { var Orphaned action.GTSAction = func(ctx context.Context) error {
dbService, err := bundb.NewBunDBService(ctx) var state state.State
state.Caches.Init()
dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
@ -54,7 +58,7 @@
return fmt.Errorf("error pruning: %s", err) return fmt.Errorf("error pruning: %s", err)
} }
if dry { if dry /* dick heyyoooooo */ {
log.Infof("DRY RUN: %d stored items are orphaned and eligible to be pruned", pruned) log.Infof("DRY RUN: %d stored items are orphaned and eligible to be pruned", pruned)
} else { } else {
log.Infof("%d stored items were orphaned and pruned", pruned) log.Infof("%d stored items were orphaned and pruned", pruned)

View file

@ -26,16 +26,22 @@
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb" "github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/trans" "github.com/superseriousbusiness/gotosocial/internal/trans"
) )
// Export exports info from the database into a file // Export exports info from the database into a file
var Export action.GTSAction = func(ctx context.Context) error { var Export action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx) var state state.State
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbConn
exporter := trans.NewExporter(dbConn) exporter := trans.NewExporter(dbConn)
path := config.GetAdminTransPath() path := config.GetAdminTransPath()

View file

@ -26,16 +26,22 @@
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb" "github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/trans" "github.com/superseriousbusiness/gotosocial/internal/trans"
) )
// Import imports info from a file into the database // Import imports info from a file into the database
var Import action.GTSAction = func(ctx context.Context) error { var Import action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx) var state state.State
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbConn
importer := trans.NewImporter(dbConn) importer := trans.NewImporter(dbConn)
path := config.GetAdminTransPath() path := config.GetAdminTransPath()

View file

@ -65,6 +65,7 @@
"github.com/superseriousbusiness/gotosocial/internal/oidc" "github.com/superseriousbusiness/gotosocial/internal/oidc"
"github.com/superseriousbusiness/gotosocial/internal/processing" "github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
"github.com/superseriousbusiness/gotosocial/internal/state"
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage" gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/internal/transport" "github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/typeutils"
@ -73,11 +74,20 @@
// Start creates and starts a gotosocial server // Start creates and starts a gotosocial server
var Start action.GTSAction = func(ctx context.Context) error { var Start action.GTSAction = func(ctx context.Context) error {
dbService, err := bundb.NewBunDBService(ctx) var state state.State
// Initialize caches
state.Caches.Init()
// Open connection to the database
dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
// Set the state DB connection
state.DB = dbService
if err := dbService.CreateInstanceAccount(ctx); err != nil { if err := dbService.CreateInstanceAccount(ctx); err != nil {
return fmt.Errorf("error creating instance account: %s", err) return fmt.Errorf("error creating instance account: %s", err)
} }

44
internal/cache/ap.go vendored Normal file
View file

@ -0,0 +1,44 @@
/*
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
type APCaches interface {
// Init will initialize all the ActivityPub caches in this collection.
// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
Init()
// Start will attempt to start all of the ActivityPub caches, or panic.
Start()
// Stop will attempt to stop all of the ActivityPub caches, or panic.
Stop()
}
// NewAP returns a new default implementation of APCaches.
func NewAP() APCaches {
return &apCaches{}
}
type apCaches struct{}
func (c *apCaches) Init() {}
func (c *apCaches) Start() {}
func (c *apCaches) Stop() {}

60
internal/cache/cache.go vendored Normal file
View file

@ -0,0 +1,60 @@
/*
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
type Caches struct {
// GTS provides access to the collection of gtsmodel object caches.
GTS GTSCaches
// AP provides access to the collection of ActivityPub object caches.
AP APCaches
// prevent pass-by-value.
_ nocopy
}
// Init will (re)initialize both the GTS and AP cache collections.
// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
func (c *Caches) Init() {
if c.GTS == nil {
// use default impl
c.GTS = NewGTS()
}
if c.AP == nil {
// use default impl
c.AP = NewAP()
}
// initialize caches
c.GTS.Init()
c.AP.Init()
}
// Start will start both the GTS and AP cache collections.
func (c *Caches) Start() {
c.GTS.Start()
c.AP.Start()
}
// Stop will stop both the GTS and AP cache collections.
func (c *Caches) Stop() {
c.GTS.Stop()
c.AP.Stop()
}

313
internal/cache/gts.go vendored Normal file
View file

@ -0,0 +1,313 @@
/*
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/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
type GTSCaches interface {
// Init will initialize all the gtsmodel caches in this collection.
// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
Init()
// Start will attempt to start all of the gtsmodel caches, or panic.
Start()
// Stop will attempt to stop all of the gtsmodel caches, or panic.
Stop()
// Account provides access to the gtsmodel Account database cache.
Account() *result.Cache[*gtsmodel.Account]
// Block provides access to the gtsmodel Block (account) database cache.
Block() *result.Cache[*gtsmodel.Block]
// DomainBlock provides access to the gtsmodel DomainBlock database cache.
DomainBlock() *result.Cache[*gtsmodel.DomainBlock]
// Emoji provides access to the gtsmodel Emoji database cache.
Emoji() *result.Cache[*gtsmodel.Emoji]
// EmojiCategory provides access to the gtsmodel EmojiCategory database cache.
EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory]
// Mention provides access to the gtsmodel Mention database cache.
Mention() *result.Cache[*gtsmodel.Mention]
// Notification provides access to the gtsmodel Notification database cache.
Notification() *result.Cache[*gtsmodel.Notification]
// Status provides access to the gtsmodel Status database cache.
Status() *result.Cache[*gtsmodel.Status]
// Tombstone provides access to the gtsmodel Tombstone database cache.
Tombstone() *result.Cache[*gtsmodel.Tombstone]
// User provides access to the gtsmodel User database cache.
User() *result.Cache[*gtsmodel.User]
}
// NewGTS returns a new default implementation of GTSCaches.
func NewGTS() GTSCaches {
return &gtsCaches{}
}
type gtsCaches struct {
account *result.Cache[*gtsmodel.Account]
block *result.Cache[*gtsmodel.Block]
domainBlock *result.Cache[*gtsmodel.DomainBlock]
emoji *result.Cache[*gtsmodel.Emoji]
emojiCategory *result.Cache[*gtsmodel.EmojiCategory]
mention *result.Cache[*gtsmodel.Mention]
notification *result.Cache[*gtsmodel.Notification]
status *result.Cache[*gtsmodel.Status]
tombstone *result.Cache[*gtsmodel.Tombstone]
user *result.Cache[*gtsmodel.User]
}
func (c *gtsCaches) Init() {
c.initAccount()
c.initBlock()
c.initDomainBlock()
c.initEmoji()
c.initEmojiCategory()
c.initMention()
c.initNotification()
c.initStatus()
c.initTombstone()
c.initUser()
}
func (c *gtsCaches) Start() {
tryUntil("starting gtsmodel.Account cache", 5, func() bool {
return c.account.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Block cache", 5, func() bool {
return c.block.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.DomainBlock cache", 5, func() bool {
return c.domainBlock.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Emoji cache", 5, func() bool {
return c.emoji.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.EmojiCategory cache", 5, func() bool {
return c.emojiCategory.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Mention cache", 5, func() bool {
return c.mention.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Notification cache", 5, func() bool {
return c.notification.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Status cache", 5, func() bool {
return c.status.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Tombstone cache", 5, func() bool {
return c.tombstone.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.User cache", 5, func() bool {
return c.user.Start(time.Second * 10)
})
}
func (c *gtsCaches) Stop() {
tryUntil("stopping gtsmodel.Account cache", 5, c.account.Stop)
tryUntil("stopping gtsmodel.Block cache", 5, c.block.Stop)
tryUntil("stopping gtsmodel.DomainBlock cache", 5, c.domainBlock.Stop)
tryUntil("stopping gtsmodel.Emoji cache", 5, c.emoji.Stop)
tryUntil("stopping gtsmodel.EmojiCategory cache", 5, c.emojiCategory.Stop)
tryUntil("stopping gtsmodel.Mention cache", 5, c.mention.Stop)
tryUntil("stopping gtsmodel.Notification cache", 5, c.notification.Stop)
tryUntil("stopping gtsmodel.Status cache", 5, c.status.Stop)
tryUntil("stopping gtsmodel.Tombstone cache", 5, c.tombstone.Stop)
tryUntil("stopping gtsmodel.User cache", 5, c.user.Stop)
}
func (c *gtsCaches) Account() *result.Cache[*gtsmodel.Account] {
return c.account
}
func (c *gtsCaches) Block() *result.Cache[*gtsmodel.Block] {
return c.block
}
func (c *gtsCaches) DomainBlock() *result.Cache[*gtsmodel.DomainBlock] {
return c.domainBlock
}
func (c *gtsCaches) Emoji() *result.Cache[*gtsmodel.Emoji] {
return c.emoji
}
func (c *gtsCaches) EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory] {
return c.emojiCategory
}
func (c *gtsCaches) Mention() *result.Cache[*gtsmodel.Mention] {
return c.mention
}
func (c *gtsCaches) Notification() *result.Cache[*gtsmodel.Notification] {
return c.notification
}
func (c *gtsCaches) Status() *result.Cache[*gtsmodel.Status] {
return c.status
}
func (c *gtsCaches) Tombstone() *result.Cache[*gtsmodel.Tombstone] {
return c.tombstone
}
func (c *gtsCaches) User() *result.Cache[*gtsmodel.User] {
return c.user
}
func (c *gtsCaches) initAccount() {
c.account = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
{Name: "Username.Domain"},
{Name: "PublicKeyURI"},
}, func(a1 *gtsmodel.Account) *gtsmodel.Account {
a2 := new(gtsmodel.Account)
*a2 = *a1
return a2
}, 1000)
c.account.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initBlock() {
c.block = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "AccountID.TargetAccountID"},
{Name: "URI"},
}, func(b1 *gtsmodel.Block) *gtsmodel.Block {
b2 := new(gtsmodel.Block)
*b2 = *b1
return b2
}, 1000)
c.block.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initDomainBlock() {
c.domainBlock = result.NewSized([]result.Lookup{
{Name: "Domain"},
}, func(d1 *gtsmodel.DomainBlock) *gtsmodel.DomainBlock {
d2 := new(gtsmodel.DomainBlock)
*d2 = *d1
return d2
}, 1000)
c.domainBlock.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initEmoji() {
c.emoji = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "Shortcode.Domain"},
{Name: "ImageStaticURL"},
}, func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji {
e2 := new(gtsmodel.Emoji)
*e2 = *e1
return e2
}, 1000)
c.emoji.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initEmojiCategory() {
c.emojiCategory = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "Name"},
}, func(c1 *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory {
c2 := new(gtsmodel.EmojiCategory)
*c2 = *c1
return c2
}, 1000)
c.emojiCategory.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initMention() {
c.mention = result.NewSized([]result.Lookup{
{Name: "ID"},
}, func(m1 *gtsmodel.Mention) *gtsmodel.Mention {
m2 := new(gtsmodel.Mention)
*m2 = *m1
return m2
}, 1000)
c.mention.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initNotification() {
c.notification = result.NewSized([]result.Lookup{
{Name: "ID"},
}, func(n1 *gtsmodel.Notification) *gtsmodel.Notification {
n2 := new(gtsmodel.Notification)
*n2 = *n1
return n2
}, 1000)
c.notification.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initStatus() {
c.status = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
}, func(s1 *gtsmodel.Status) *gtsmodel.Status {
s2 := new(gtsmodel.Status)
*s2 = *s1
return s2
}, 1000)
c.status.SetTTL(time.Minute*5, false)
}
// initTombstone will initialize the gtsmodel.Tombstone cache.
func (c *gtsCaches) initTombstone() {
c.tombstone = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
}, func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone {
t2 := new(gtsmodel.Tombstone)
*t2 = *t1
return t2
}, 100)
c.tombstone.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initUser() {
c.user = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "AccountID"},
{Name: "Email"},
{Name: "ConfirmationToken"},
{Name: "ExternalID"},
}, func(u1 *gtsmodel.User) *gtsmodel.User {
u2 := new(gtsmodel.User)
*u2 = *u1
return u2
}, 1000)
c.user.SetTTL(time.Minute*5, false)
}

36
internal/cache/util.go vendored Normal file
View file

@ -0,0 +1,36 @@
/*
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 "github.com/superseriousbusiness/gotosocial/internal/log"
// nocopy when embedded will signal linter to
// error on pass-by-value of parent struct.
type nocopy struct{}
func (*nocopy) Lock() {}
func (*nocopy) Unlock() {}
// tryUntil will attempt to call 'do' for 'count' attempts, before panicking with 'msg'.
func tryUntil(msg string, count int, do func() bool) {
for i := 0; i < count && !do(); i++ {
}
log.Panicf("failed %s after %d tries", msg, count)
}

View file

@ -25,39 +25,18 @@
"strings" "strings"
"time" "time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"github.com/uptrace/bun/dialect" "github.com/uptrace/bun/dialect"
) )
type accountDB struct { type accountDB struct {
conn *DBConn conn *DBConn
cache *result.Cache[*gtsmodel.Account] state *state.State
emojis *emojiDB
status *statusDB
}
func (a *accountDB) init() {
// Initialize account result cache
a.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
{Name: "Username.Domain"},
{Name: "PublicKeyURI"},
}, func(a1 *gtsmodel.Account) *gtsmodel.Account {
a2 := new(gtsmodel.Account)
*a2 = *a1
return a2
}, 1000)
// Set cache TTL and start sweep routine
a.cache.SetTTL(time.Minute*5, false)
a.cache.Start(time.Second * 10)
} }
func (a *accountDB) newAccountQ(account *gtsmodel.Account) *bun.SelectQuery { func (a *accountDB) newAccountQ(account *gtsmodel.Account) *bun.SelectQuery {
@ -152,7 +131,7 @@ func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gts
func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Account) error, keyParts ...any) (*gtsmodel.Account, db.Error) { func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Account) error, keyParts ...any) (*gtsmodel.Account, db.Error) {
// Fetch account from database cache with loader callback // Fetch account from database cache with loader callback
account, err := a.cache.Load(lookup, func() (*gtsmodel.Account, error) { account, err := a.state.Caches.GTS.Account().Load(lookup, func() (*gtsmodel.Account, error) {
var account gtsmodel.Account var account gtsmodel.Account
// Not cached! Perform database query // Not cached! Perform database query
@ -168,7 +147,7 @@ func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(
if len(account.EmojiIDs) > 0 { if len(account.EmojiIDs) > 0 {
// Set the account's related emojis // Set the account's related emojis
account.Emojis, err = a.emojis.emojisFromIDs(ctx, account.EmojiIDs) account.Emojis, err = a.state.DB.GetEmojisByIDs(ctx, account.EmojiIDs)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting account emojis: %w", err) return nil, fmt.Errorf("error getting account emojis: %w", err)
} }
@ -178,7 +157,7 @@ func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(
} }
func (a *accountDB) PutAccount(ctx context.Context, account *gtsmodel.Account) db.Error { func (a *accountDB) PutAccount(ctx context.Context, account *gtsmodel.Account) db.Error {
return a.cache.Store(account, func() error { return a.state.Caches.GTS.Account().Store(account, func() error {
// It is safe to run this database transaction within cache.Store // It is safe to run this database transaction within cache.Store
// as the cache does not attempt a mutex lock until AFTER hook. // as the cache does not attempt a mutex lock until AFTER hook.
// //
@ -204,7 +183,7 @@ func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account
// Update the account's last-updated // Update the account's last-updated
account.UpdatedAt = time.Now() account.UpdatedAt = time.Now()
return a.cache.Store(account, func() error { return a.state.Caches.GTS.Account().Store(account, func() error {
// It is safe to run this database transaction within cache.Store // It is safe to run this database transaction within cache.Store
// as the cache does not attempt a mutex lock until AFTER hook. // as the cache does not attempt a mutex lock until AFTER hook.
// //
@ -263,7 +242,7 @@ func (a *accountDB) DeleteAccount(ctx context.Context, id string) db.Error {
return err return err
} }
a.cache.Invalidate("ID", id) a.state.Caches.GTS.Account().Invalidate("ID", id)
return nil return nil
} }
@ -514,7 +493,7 @@ func (a *accountDB) statusesFromIDs(ctx context.Context, statusIDs []string) ([]
for _, id := range statusIDs { for _, id := range statusIDs {
// Fetch from status from database by ID // Fetch from status from database by ID
status, err := a.status.GetStatusByID(ctx, id) status, err := a.state.DB.GetStatusByID(ctx, id)
if err != nil { if err != nil {
log.Errorf("statusesFromIDs: error getting status %q: %v", id, err) log.Errorf("statusesFromIDs: error getting status %q: %v", id, err)
continue continue

View file

@ -34,6 +34,7 @@
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/uris" "github.com/superseriousbusiness/gotosocial/internal/uris"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -43,9 +44,8 @@
const rsaKeyBits = 2048 const rsaKeyBits = 2048
type adminDB struct { type adminDB struct {
conn *DBConn conn *DBConn
accounts *accountDB state *state.State
users *userDB
} }
func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) { func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) {
@ -139,7 +139,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string,
} }
// insert the new account! // insert the new account!
if err := a.accounts.PutAccount(ctx, acct); err != nil { if err := a.state.DB.PutAccount(ctx, acct); err != nil {
return nil, err return nil, err
} }
} }
@ -185,7 +185,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string,
} }
// insert the user! // insert the user!
if err := a.users.PutUser(ctx, u); err != nil { if err := a.state.DB.PutUser(ctx, u); err != nil {
return nil, err return nil, err
} }
@ -241,7 +241,7 @@ func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error {
} }
// insert the new account! // insert the new account!
if err := a.accounts.PutAccount(ctx, acct); err != nil { if err := a.state.DB.PutAccount(ctx, acct); err != nil {
return err return err
} }

View file

@ -40,6 +40,7 @@
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/dialect/sqlitedialect" "github.com/uptrace/bun/dialect/sqlitedialect"
@ -122,7 +123,7 @@ func doMigration(ctx context.Context, db *bun.DB) error {
// NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface. // NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface.
// Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection. // Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection.
func NewBunDBService(ctx context.Context) (db.DB, error) { func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
var conn *DBConn var conn *DBConn
var err error var err error
dbType := strings.ToLower(config.GetDbType()) dbType := strings.ToLower(config.GetDbType())
@ -158,69 +159,64 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
return nil, fmt.Errorf("db migration error: %s", err) return nil, fmt.Errorf("db migration error: %s", err)
} }
// Create DB structs that require ptrs to each other
account := &accountDB{conn: conn}
admin := &adminDB{conn: conn}
domain := &domainDB{conn: conn}
mention := &mentionDB{conn: conn}
notif := &notificationDB{conn: conn}
status := &statusDB{conn: conn}
emoji := &emojiDB{conn: conn}
relationship := &relationshipDB{conn: conn}
timeline := &timelineDB{conn: conn}
tombstone := &tombstoneDB{conn: conn}
user := &userDB{conn: conn}
// Setup DB cross-referencing
account.emojis = emoji
account.status = status
admin.users = user
relationship.accounts = account
status.accounts = account
status.emojis = emoji
status.mentions = mention
timeline.status = status
// Initialize db structs
account.init()
domain.init()
emoji.init()
mention.init()
notif.init()
relationship.init()
status.init()
tombstone.init()
user.init()
ps := &DBService{ ps := &DBService{
Account: account, Account: &accountDB{
conn: conn,
state: state,
},
Admin: &adminDB{ Admin: &adminDB{
conn: conn, conn: conn,
accounts: account, state: state,
users: user,
}, },
Basic: &basicDB{ Basic: &basicDB{
conn: conn, conn: conn,
}, },
Domain: domain, Domain: &domainDB{
Emoji: emoji, conn: conn,
state: state,
},
Emoji: &emojiDB{
conn: conn,
state: state,
},
Instance: &instanceDB{ Instance: &instanceDB{
conn: conn, conn: conn,
}, },
Media: &mediaDB{ Media: &mediaDB{
conn: conn, conn: conn,
}, },
Mention: mention, Mention: &mentionDB{
Notification: notif, conn: conn,
Relationship: relationship, state: state,
},
Notification: &notificationDB{
conn: conn,
state: state,
},
Relationship: &relationshipDB{
conn: conn,
state: state,
},
Session: &sessionDB{ Session: &sessionDB{
conn: conn, conn: conn,
}, },
Status: status, Status: &statusDB{
Timeline: timeline, conn: conn,
User: user, state: state,
Tombstone: tombstone, },
conn: conn, Timeline: &timelineDB{
conn: conn,
state: state,
},
User: &userDB{
conn: conn,
state: state,
},
Tombstone: &tombstoneDB{
conn: conn,
state: state,
},
conn: conn,
} }
// we can confidently return this useable service now // we can confidently return this useable service now

View file

@ -33,7 +33,7 @@ type BundbNewTestSuite struct {
func (suite *BundbNewTestSuite) TestCreateNewDB() { func (suite *BundbNewTestSuite) TestCreateNewDB() {
// create a new db with standard test settings // create a new db with standard test settings
db, err := bundb.NewBunDBService(context.Background()) db, err := bundb.NewBunDBService(context.Background(), nil)
suite.NoError(err) suite.NoError(err)
suite.NotNil(db) suite.NotNil(db)
} }
@ -42,7 +42,7 @@ func (suite *BundbNewTestSuite) TestCreateNewSqliteDBNoAddress() {
// create a new db with no address specified // create a new db with no address specified
config.SetDbAddress("") config.SetDbAddress("")
config.SetDbType("sqlite") config.SetDbType("sqlite")
db, err := bundb.NewBunDBService(context.Background()) db, err := bundb.NewBunDBService(context.Background(), nil)
suite.EqualError(err, "'db-address' was not set when attempting to start sqlite") suite.EqualError(err, "'db-address' was not set when attempting to start sqlite")
suite.Nil(db) suite.Nil(db)
} }

View file

@ -22,34 +22,18 @@
"context" "context"
"net/url" "net/url"
"strings" "strings"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"golang.org/x/net/idna" "golang.org/x/net/idna"
) )
type domainDB struct { type domainDB struct {
conn *DBConn conn *DBConn
cache *result.Cache[*gtsmodel.DomainBlock] state *state.State
}
func (d *domainDB) init() {
// Initialize domain block result cache
d.cache = result.NewSized([]result.Lookup{
{Name: "Domain"},
}, func(d1 *gtsmodel.DomainBlock) *gtsmodel.DomainBlock {
d2 := new(gtsmodel.DomainBlock)
*d2 = *d1
return d2
}, 1000)
// Set cache TTL and start sweep routine
d.cache.SetTTL(time.Minute*5, false)
d.cache.Start(time.Second * 10)
} }
// normalizeDomain converts the given domain to lowercase // normalizeDomain converts the given domain to lowercase
@ -71,7 +55,7 @@ func (d *domainDB) CreateDomainBlock(ctx context.Context, block *gtsmodel.Domain
return err return err
} }
return d.cache.Store(block, func() error { return d.state.Caches.GTS.DomainBlock().Store(block, func() error {
_, err := d.conn.NewInsert(). _, err := d.conn.NewInsert().
Model(block). Model(block).
Exec(ctx) Exec(ctx)
@ -87,7 +71,7 @@ func (d *domainDB) GetDomainBlock(ctx context.Context, domain string) (*gtsmodel
return nil, err return nil, err
} }
return d.cache.Load("Domain", func() (*gtsmodel.DomainBlock, error) { return d.state.Caches.GTS.DomainBlock().Load("Domain", func() (*gtsmodel.DomainBlock, error) {
// Check for easy case, domain referencing *us* // Check for easy case, domain referencing *us*
if domain == "" || domain == config.GetAccountDomain() { if domain == "" || domain == config.GetAccountDomain() {
return nil, db.ErrNoEntries return nil, db.ErrNoEntries
@ -125,7 +109,7 @@ func (d *domainDB) DeleteDomainBlock(ctx context.Context, domain string) db.Erro
} }
// Clear domain from cache // Clear domain from cache
d.cache.Invalidate("Domain", domain) d.state.Caches.GTS.DomainBlock().Invalidate(domain)
return nil return nil
} }

View file

@ -23,50 +23,17 @@
"strings" "strings"
"time" "time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"github.com/uptrace/bun/dialect" "github.com/uptrace/bun/dialect"
) )
type emojiDB struct { type emojiDB struct {
conn *DBConn conn *DBConn
emojiCache *result.Cache[*gtsmodel.Emoji] state *state.State
categoryCache *result.Cache[*gtsmodel.EmojiCategory]
}
func (e *emojiDB) init() {
// Initialize emoji result cache
e.emojiCache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "Shortcode.Domain"},
{Name: "ImageStaticURL"},
}, func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji {
e2 := new(gtsmodel.Emoji)
*e2 = *e1
return e2
}, 1000)
// Set cache TTL and start sweep routine
e.emojiCache.SetTTL(time.Minute*5, false)
e.emojiCache.Start(time.Second * 10)
// Initialize category result cache
e.categoryCache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "Name"},
}, func(c1 *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory {
c2 := new(gtsmodel.EmojiCategory)
*c2 = *c1
return c2
}, 1000)
// Set cache TTL and start sweep routine
e.categoryCache.SetTTL(time.Minute*5, false)
e.categoryCache.Start(time.Second * 10)
} }
func (e *emojiDB) newEmojiQ(emoji *gtsmodel.Emoji) *bun.SelectQuery { func (e *emojiDB) newEmojiQ(emoji *gtsmodel.Emoji) *bun.SelectQuery {
@ -83,7 +50,7 @@ func (e *emojiDB) newEmojiCategoryQ(emojiCategory *gtsmodel.EmojiCategory) *bun.
} }
func (e *emojiDB) PutEmoji(ctx context.Context, emoji *gtsmodel.Emoji) db.Error { func (e *emojiDB) PutEmoji(ctx context.Context, emoji *gtsmodel.Emoji) db.Error {
return e.emojiCache.Store(emoji, func() error { return e.state.Caches.GTS.Emoji().Store(emoji, func() error {
_, err := e.conn.NewInsert().Model(emoji).Exec(ctx) _, err := e.conn.NewInsert().Model(emoji).Exec(ctx)
return e.conn.ProcessError(err) return e.conn.ProcessError(err)
}) })
@ -102,7 +69,7 @@ func (e *emojiDB) UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, column
return nil, e.conn.ProcessError(err) return nil, e.conn.ProcessError(err)
} }
e.emojiCache.Invalidate("ID", emoji.ID) e.state.Caches.GTS.Emoji().Invalidate("ID", emoji.ID)
return emoji, nil return emoji, nil
} }
@ -139,7 +106,7 @@ func (e *emojiDB) DeleteEmojiByID(ctx context.Context, id string) db.Error {
return err return err
} }
e.emojiCache.Invalidate("ID", id) e.state.Caches.GTS.Emoji().Invalidate("ID", id)
return nil return nil
} }
@ -257,7 +224,7 @@ func (e *emojiDB) GetEmojis(ctx context.Context, domain string, includeDisabled
} }
} }
return e.emojisFromIDs(ctx, emojiIDs) return e.GetEmojisByIDs(ctx, emojiIDs)
} }
func (e *emojiDB) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, db.Error) { func (e *emojiDB) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, db.Error) {
@ -276,7 +243,7 @@ func (e *emojiDB) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, db.E
return nil, e.conn.ProcessError(err) return nil, e.conn.ProcessError(err)
} }
return e.emojisFromIDs(ctx, emojiIDs) return e.GetEmojisByIDs(ctx, emojiIDs)
} }
func (e *emojiDB) GetEmojiByID(ctx context.Context, id string) (*gtsmodel.Emoji, db.Error) { func (e *emojiDB) GetEmojiByID(ctx context.Context, id string) (*gtsmodel.Emoji, db.Error) {
@ -338,7 +305,7 @@ func(emoji *gtsmodel.Emoji) error {
} }
func (e *emojiDB) PutEmojiCategory(ctx context.Context, emojiCategory *gtsmodel.EmojiCategory) db.Error { func (e *emojiDB) PutEmojiCategory(ctx context.Context, emojiCategory *gtsmodel.EmojiCategory) db.Error {
return e.categoryCache.Store(emojiCategory, func() error { return e.state.Caches.GTS.EmojiCategory().Store(emojiCategory, func() error {
_, err := e.conn.NewInsert().Model(emojiCategory).Exec(ctx) _, err := e.conn.NewInsert().Model(emojiCategory).Exec(ctx)
return e.conn.ProcessError(err) return e.conn.ProcessError(err)
}) })
@ -357,7 +324,7 @@ func (e *emojiDB) GetEmojiCategories(ctx context.Context) ([]*gtsmodel.EmojiCate
return nil, e.conn.ProcessError(err) return nil, e.conn.ProcessError(err)
} }
return e.emojiCategoriesFromIDs(ctx, emojiCategoryIDs) return e.GetEmojiCategoriesByIDs(ctx, emojiCategoryIDs)
} }
func (e *emojiDB) GetEmojiCategory(ctx context.Context, id string) (*gtsmodel.EmojiCategory, db.Error) { func (e *emojiDB) GetEmojiCategory(ctx context.Context, id string) (*gtsmodel.EmojiCategory, db.Error) {
@ -383,7 +350,7 @@ func(emojiCategory *gtsmodel.EmojiCategory) error {
} }
func (e *emojiDB) getEmoji(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Emoji) error, keyParts ...any) (*gtsmodel.Emoji, db.Error) { func (e *emojiDB) getEmoji(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Emoji) error, keyParts ...any) (*gtsmodel.Emoji, db.Error) {
return e.emojiCache.Load(lookup, func() (*gtsmodel.Emoji, error) { return e.state.Caches.GTS.Emoji().Load(lookup, func() (*gtsmodel.Emoji, error) {
var emoji gtsmodel.Emoji var emoji gtsmodel.Emoji
// Not cached! Perform database query // Not cached! Perform database query
@ -395,8 +362,7 @@ func (e *emojiDB) getEmoji(ctx context.Context, lookup string, dbQuery func(*gts
}, keyParts...) }, keyParts...)
} }
func (e *emojiDB) emojisFromIDs(ctx context.Context, emojiIDs []string) ([]*gtsmodel.Emoji, db.Error) { func (e *emojiDB) GetEmojisByIDs(ctx context.Context, emojiIDs []string) ([]*gtsmodel.Emoji, db.Error) {
// Catch case of no emojis early
if len(emojiIDs) == 0 { if len(emojiIDs) == 0 {
return nil, db.ErrNoEntries return nil, db.ErrNoEntries
} }
@ -417,7 +383,7 @@ func (e *emojiDB) emojisFromIDs(ctx context.Context, emojiIDs []string) ([]*gtsm
} }
func (e *emojiDB) getEmojiCategory(ctx context.Context, lookup string, dbQuery func(*gtsmodel.EmojiCategory) error, keyParts ...any) (*gtsmodel.EmojiCategory, db.Error) { func (e *emojiDB) getEmojiCategory(ctx context.Context, lookup string, dbQuery func(*gtsmodel.EmojiCategory) error, keyParts ...any) (*gtsmodel.EmojiCategory, db.Error) {
return e.categoryCache.Load(lookup, func() (*gtsmodel.EmojiCategory, error) { return e.state.Caches.GTS.EmojiCategory().Load(lookup, func() (*gtsmodel.EmojiCategory, error) {
var category gtsmodel.EmojiCategory var category gtsmodel.EmojiCategory
// Not cached! Perform database query // Not cached! Perform database query
@ -429,8 +395,7 @@ func (e *emojiDB) getEmojiCategory(ctx context.Context, lookup string, dbQuery f
}, keyParts...) }, keyParts...)
} }
func (e *emojiDB) emojiCategoriesFromIDs(ctx context.Context, emojiCategoryIDs []string) ([]*gtsmodel.EmojiCategory, db.Error) { func (e *emojiDB) GetEmojiCategoriesByIDs(ctx context.Context, emojiCategoryIDs []string) ([]*gtsmodel.EmojiCategory, db.Error) {
// Catch case of no emoji categories early
if len(emojiCategoryIDs) == 0 { if len(emojiCategoryIDs) == 0 {
return nil, db.ErrNoEntries return nil, db.ErrNoEntries
} }

View file

@ -20,33 +20,17 @@
import ( import (
"context" "context"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
type mentionDB struct { type mentionDB struct {
conn *DBConn conn *DBConn
cache *result.Cache[*gtsmodel.Mention] state *state.State
}
func (m *mentionDB) init() {
// Initialize notification result cache
m.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
}, func(m1 *gtsmodel.Mention) *gtsmodel.Mention {
m2 := new(gtsmodel.Mention)
*m2 = *m1
return m2
}, 1000)
// Set cache TTL and start sweep routine
m.cache.SetTTL(time.Minute*5, false)
m.cache.Start(time.Second * 10)
} }
func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery { func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
@ -59,7 +43,7 @@ func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
} }
func (m *mentionDB) GetMention(ctx context.Context, id string) (*gtsmodel.Mention, db.Error) { func (m *mentionDB) GetMention(ctx context.Context, id string) (*gtsmodel.Mention, db.Error) {
return m.cache.Load("ID", func() (*gtsmodel.Mention, error) { return m.state.Caches.GTS.Mention().Load("ID", func() (*gtsmodel.Mention, error) {
var mention gtsmodel.Mention var mention gtsmodel.Mention
q := m.newMentionQ(&mention). q := m.newMentionQ(&mention).

View file

@ -20,37 +20,21 @@
import ( import (
"context" "context"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
type notificationDB struct { type notificationDB struct {
conn *DBConn conn *DBConn
cache *result.Cache[*gtsmodel.Notification] state *state.State
}
func (n *notificationDB) init() {
// Initialize notification result cache
n.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
}, func(n1 *gtsmodel.Notification) *gtsmodel.Notification {
n2 := new(gtsmodel.Notification)
*n2 = *n1
return n2
}, 1000)
// Set cache TTL and start sweep routine
n.cache.SetTTL(time.Minute*5, false)
n.cache.Start(time.Second * 10)
} }
func (n *notificationDB) GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, db.Error) { func (n *notificationDB) GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, db.Error) {
return n.cache.Load("ID", func() (*gtsmodel.Notification, error) { return n.state.Caches.GTS.Notification().Load("ID", func() (*gtsmodel.Notification, error) {
var notif gtsmodel.Notification var notif gtsmodel.Notification
q := n.conn.NewSelect(). q := n.conn.NewSelect().
@ -130,6 +114,6 @@ func (n *notificationDB) ClearNotifications(ctx context.Context, accountID strin
return n.conn.ProcessError(err) return n.conn.ProcessError(err)
} }
n.cache.Clear() n.state.Caches.GTS.Notification().Clear()
return nil return nil
} }

View file

@ -23,35 +23,16 @@
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
type relationshipDB struct { type relationshipDB struct {
conn *DBConn conn *DBConn
accounts *accountDB state *state.State
blockCache *result.Cache[*gtsmodel.Block]
}
func (r *relationshipDB) init() {
// Initialize block result cache
r.blockCache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "AccountID.TargetAccountID"},
{Name: "URI"},
}, func(b1 *gtsmodel.Block) *gtsmodel.Block {
b2 := new(gtsmodel.Block)
*b2 = *b1
return b2
}, 1000)
// Set cache TTL and start sweep routine
r.blockCache.SetTTL(time.Minute*5, false)
r.blockCache.Start(time.Second * 10)
} }
func (r *relationshipDB) newFollowQ(follow interface{}) *bun.SelectQuery { func (r *relationshipDB) newFollowQ(follow interface{}) *bun.SelectQuery {
@ -94,13 +75,13 @@ func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2
} }
// Set the block originating account // Set the block originating account
block.Account, err = r.accounts.GetAccountByID(ctx, block.AccountID) block.Account, err = r.state.DB.GetAccountByID(ctx, block.AccountID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Set the block target account // Set the block target account
block.TargetAccount, err = r.accounts.GetAccountByID(ctx, block.TargetAccountID) block.TargetAccount, err = r.state.DB.GetAccountByID(ctx, block.TargetAccountID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -109,7 +90,7 @@ func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2
} }
func (r *relationshipDB) getBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) { func (r *relationshipDB) getBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) {
return r.blockCache.Load("AccountID.TargetAccountID", func() (*gtsmodel.Block, error) { return r.state.Caches.GTS.Block().Load("AccountID.TargetAccountID", func() (*gtsmodel.Block, error) {
var block gtsmodel.Block var block gtsmodel.Block
q := r.conn.NewSelect().Model(&block). q := r.conn.NewSelect().Model(&block).
@ -124,7 +105,7 @@ func (r *relationshipDB) getBlock(ctx context.Context, account1 string, account2
} }
func (r *relationshipDB) PutBlock(ctx context.Context, block *gtsmodel.Block) db.Error { func (r *relationshipDB) PutBlock(ctx context.Context, block *gtsmodel.Block) db.Error {
return r.blockCache.Store(block, func() error { return r.state.Caches.GTS.Block().Store(block, func() error {
_, err := r.conn.NewInsert().Model(block).Exec(ctx) _, err := r.conn.NewInsert().Model(block).Exec(ctx)
return r.conn.ProcessError(err) return r.conn.ProcessError(err)
}) })
@ -140,7 +121,7 @@ func (r *relationshipDB) DeleteBlockByID(ctx context.Context, id string) db.Erro
} }
// Drop any old value from cache by this ID // Drop any old value from cache by this ID
r.blockCache.Invalidate("ID", id) r.state.Caches.GTS.Block().Invalidate("ID", id)
return nil return nil
} }
@ -154,7 +135,7 @@ func (r *relationshipDB) DeleteBlockByURI(ctx context.Context, uri string) db.Er
} }
// Drop any old value from cache by this URI // Drop any old value from cache by this URI
r.blockCache.Invalidate("URI", uri) r.state.Caches.GTS.Block().Invalidate("URI", uri)
return nil return nil
} }

View file

@ -26,36 +26,16 @@
"fmt" "fmt"
"time" "time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
type statusDB struct { type statusDB struct {
conn *DBConn conn *DBConn
cache *result.Cache[*gtsmodel.Status] state *state.State
accounts *accountDB
emojis *emojiDB
mentions *mentionDB
}
func (s *statusDB) init() {
// Initialize status result cache
s.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
}, func(s1 *gtsmodel.Status) *gtsmodel.Status {
s2 := new(gtsmodel.Status)
*s2 = *s1
return s2
}, 1000)
// Set cache TTL and start sweep routine
s.cache.SetTTL(time.Minute*5, false)
s.cache.Start(time.Second * 10)
} }
func (s *statusDB) newStatusQ(status interface{}) *bun.SelectQuery { func (s *statusDB) newStatusQ(status interface{}) *bun.SelectQuery {
@ -111,7 +91,7 @@ func(status *gtsmodel.Status) error {
func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Status) error, keyParts ...any) (*gtsmodel.Status, db.Error) { func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Status) error, keyParts ...any) (*gtsmodel.Status, db.Error) {
// Fetch status from database cache with loader callback // Fetch status from database cache with loader callback
status, err := s.cache.Load(lookup, func() (*gtsmodel.Status, error) { status, err := s.state.Caches.GTS.Status().Load(lookup, func() (*gtsmodel.Status, error) {
var status gtsmodel.Status var status gtsmodel.Status
// Not cached! Perform database query // Not cached! Perform database query
@ -149,14 +129,14 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
} }
// Set the status author account // Set the status author account
status.Account, err = s.accounts.GetAccountByID(ctx, status.AccountID) status.Account, err = s.state.DB.GetAccountByID(ctx, status.AccountID)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting status account: %w", err) return nil, fmt.Errorf("error getting status account: %w", err)
} }
if id := status.BoostOfAccountID; id != "" { if id := status.BoostOfAccountID; id != "" {
// Set boost of status' author account // Set boost of status' author account
status.BoostOfAccount, err = s.accounts.GetAccountByID(ctx, id) status.BoostOfAccount, err = s.state.DB.GetAccountByID(ctx, id)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting boosted status account: %w", err) return nil, fmt.Errorf("error getting boosted status account: %w", err)
} }
@ -164,7 +144,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if id := status.InReplyToAccountID; id != "" { if id := status.InReplyToAccountID; id != "" {
// Set in-reply-to status' author account // Set in-reply-to status' author account
status.InReplyToAccount, err = s.accounts.GetAccountByID(ctx, id) status.InReplyToAccount, err = s.state.DB.GetAccountByID(ctx, id)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting in reply to status account: %w", err) return nil, fmt.Errorf("error getting in reply to status account: %w", err)
} }
@ -172,7 +152,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if len(status.EmojiIDs) > 0 { if len(status.EmojiIDs) > 0 {
// Fetch status emojis // Fetch status emojis
status.Emojis, err = s.emojis.emojisFromIDs(ctx, status.EmojiIDs) status.Emojis, err = s.state.DB.GetEmojisByIDs(ctx, status.EmojiIDs)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting status emojis: %w", err) return nil, fmt.Errorf("error getting status emojis: %w", err)
} }
@ -180,7 +160,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if len(status.MentionIDs) > 0 { if len(status.MentionIDs) > 0 {
// Fetch status mentions // Fetch status mentions
status.Mentions, err = s.mentions.GetMentions(ctx, status.MentionIDs) status.Mentions, err = s.state.DB.GetMentions(ctx, status.MentionIDs)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting status mentions: %w", err) return nil, fmt.Errorf("error getting status mentions: %w", err)
} }
@ -190,7 +170,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
} }
func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Error { func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Error {
return s.cache.Store(status, func() error { return s.state.Caches.GTS.Status().Store(status, func() error {
// It is safe to run this database transaction within cache.Store // It is safe to run this database transaction within cache.Store
// as the cache does not attempt a mutex lock until AFTER hook. // as the cache does not attempt a mutex lock until AFTER hook.
// //
@ -308,7 +288,7 @@ func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status) db
} }
// Drop any old value from cache by this ID // Drop any old value from cache by this ID
s.cache.Invalidate("ID", status.ID) s.state.Caches.GTS.Status().Invalidate("ID", status.ID)
return nil return nil
} }
@ -347,7 +327,7 @@ func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) db.Error {
} }
// Drop any old value from cache by this ID // Drop any old value from cache by this ID
s.cache.Invalidate("ID", id) s.state.Caches.GTS.Status().Invalidate("ID", id)
return nil return nil
} }

View file

@ -26,13 +26,14 @@
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
type timelineDB struct { type timelineDB struct {
conn *DBConn conn *DBConn
status *statusDB state *state.State
} }
func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) { func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) {
@ -111,7 +112,7 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
for _, id := range statusIDs { for _, id := range statusIDs {
// Fetch status from db for ID // Fetch status from db for ID
status, err := t.status.GetStatusByID(ctx, id) status, err := t.state.DB.GetStatusByID(ctx, id)
if err != nil { if err != nil {
log.Errorf("GetHomeTimeline: error fetching status %q: %v", id, err) log.Errorf("GetHomeTimeline: error fetching status %q: %v", id, err)
continue continue
@ -179,7 +180,7 @@ func (t *timelineDB) GetPublicTimeline(ctx context.Context, maxID string, sinceI
for _, id := range statusIDs { for _, id := range statusIDs {
// Fetch status from db for ID // Fetch status from db for ID
status, err := t.status.GetStatusByID(ctx, id) status, err := t.state.DB.GetStatusByID(ctx, id)
if err != nil { if err != nil {
log.Errorf("GetPublicTimeline: error fetching status %q: %v", id, err) log.Errorf("GetPublicTimeline: error fetching status %q: %v", id, err)
continue continue
@ -239,7 +240,7 @@ func (t *timelineDB) GetFavedTimeline(ctx context.Context, accountID string, max
for _, fave := range faves { for _, fave := range faves {
// Fetch status from db for corresponding favourite // Fetch status from db for corresponding favourite
status, err := t.status.GetStatusByID(ctx, fave.StatusID) status, err := t.state.DB.GetStatusByID(ctx, fave.StatusID)
if err != nil { if err != nil {
log.Errorf("GetFavedTimeline: error fetching status for fave %q: %v", fave.ID, err) log.Errorf("GetFavedTimeline: error fetching status for fave %q: %v", fave.ID, err)
continue continue

View file

@ -20,38 +20,20 @@
import ( import (
"context" "context"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"codeberg.org/gruf/go-cache/v3/result"
) )
type tombstoneDB struct { type tombstoneDB struct {
conn *DBConn conn *DBConn
cache *result.Cache[*gtsmodel.Tombstone] state *state.State
}
func (t *tombstoneDB) init() {
// Initialize tombstone result cache
t.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
}, func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone {
t2 := new(gtsmodel.Tombstone)
*t2 = *t1
return t2
}, 100)
// Set cache TTL and start sweep routine
t.cache.SetTTL(time.Minute*5, false)
t.cache.Start(time.Second * 10)
} }
func (t *tombstoneDB) GetTombstoneByURI(ctx context.Context, uri string) (*gtsmodel.Tombstone, db.Error) { func (t *tombstoneDB) GetTombstoneByURI(ctx context.Context, uri string) (*gtsmodel.Tombstone, db.Error) {
return t.cache.Load("URI", func() (*gtsmodel.Tombstone, error) { return t.state.Caches.GTS.Tombstone().Load("URI", func() (*gtsmodel.Tombstone, error) {
var tomb gtsmodel.Tombstone var tomb gtsmodel.Tombstone
q := t.conn. q := t.conn.
@ -76,7 +58,7 @@ func (t *tombstoneDB) TombstoneExistsWithURI(ctx context.Context, uri string) (b
} }
func (t *tombstoneDB) PutTombstone(ctx context.Context, tombstone *gtsmodel.Tombstone) db.Error { func (t *tombstoneDB) PutTombstone(ctx context.Context, tombstone *gtsmodel.Tombstone) db.Error {
return t.cache.Store(tombstone, func() error { return t.state.Caches.GTS.Tombstone().Store(tombstone, func() error {
_, err := t.conn. _, err := t.conn.
NewInsert(). NewInsert().
Model(tombstone). Model(tombstone).
@ -95,7 +77,7 @@ func (t *tombstoneDB) DeleteTombstone(ctx context.Context, id string) db.Error {
} }
// Invalidate from cache by ID // Invalidate from cache by ID
t.cache.Invalidate("ID", id) t.state.Caches.GTS.Tombstone().Invalidate("ID", id)
return nil return nil
} }

View file

@ -22,38 +22,19 @@
"context" "context"
"time" "time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
type userDB struct { type userDB struct {
conn *DBConn conn *DBConn
cache *result.Cache[*gtsmodel.User] state *state.State
}
func (u *userDB) init() {
// Initialize user result cache
u.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "AccountID"},
{Name: "Email"},
{Name: "ConfirmationToken"},
{Name: "ExternalID"},
}, func(u1 *gtsmodel.User) *gtsmodel.User {
u2 := new(gtsmodel.User)
*u2 = *u1
return u2
}, 1000)
// Set cache TTL and start sweep routine
u.cache.SetTTL(time.Minute*5, false)
u.cache.Start(time.Second * 10)
} }
func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db.Error) { func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
return u.cache.Load("ID", func() (*gtsmodel.User, error) { return u.state.Caches.GTS.User().Load("ID", func() (*gtsmodel.User, error) {
var user gtsmodel.User var user gtsmodel.User
q := u.conn. q := u.conn.
@ -71,7 +52,7 @@ func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db
} }
func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gtsmodel.User, db.Error) { func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gtsmodel.User, db.Error) {
return u.cache.Load("AccountID", func() (*gtsmodel.User, error) { return u.state.Caches.GTS.User().Load("AccountID", func() (*gtsmodel.User, error) {
var user gtsmodel.User var user gtsmodel.User
q := u.conn. q := u.conn.
@ -89,7 +70,7 @@ func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gts
} }
func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string) (*gtsmodel.User, db.Error) { func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string) (*gtsmodel.User, db.Error) {
return u.cache.Load("Email", func() (*gtsmodel.User, error) { return u.state.Caches.GTS.User().Load("Email", func() (*gtsmodel.User, error) {
var user gtsmodel.User var user gtsmodel.User
q := u.conn. q := u.conn.
@ -105,9 +86,9 @@ func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string)
return &user, nil return &user, nil
}, emailAddress) }, emailAddress)
} }
func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
return u.cache.Load("ExternalID", func() (*gtsmodel.User, error) { func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
return u.state.Caches.GTS.User().Load("ExternalID", func() (*gtsmodel.User, error) {
var user gtsmodel.User var user gtsmodel.User
q := u.conn. q := u.conn.
@ -125,7 +106,7 @@ func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.
} }
func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationToken string) (*gtsmodel.User, db.Error) { func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationToken string) (*gtsmodel.User, db.Error) {
return u.cache.Load("ConfirmationToken", func() (*gtsmodel.User, error) { return u.state.Caches.GTS.User().Load("ConfirmationToken", func() (*gtsmodel.User, error) {
var user gtsmodel.User var user gtsmodel.User
q := u.conn. q := u.conn.
@ -143,7 +124,7 @@ func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationTok
} }
func (u *userDB) PutUser(ctx context.Context, user *gtsmodel.User) db.Error { func (u *userDB) PutUser(ctx context.Context, user *gtsmodel.User) db.Error {
return u.cache.Store(user, func() error { return u.state.Caches.GTS.User().Store(user, func() error {
_, err := u.conn. _, err := u.conn.
NewInsert(). NewInsert().
Model(user). Model(user).
@ -172,8 +153,8 @@ func (u *userDB) UpdateUser(ctx context.Context, user *gtsmodel.User, columns ..
return u.conn.ProcessError(err) return u.conn.ProcessError(err)
} }
// Invalidate in cache // Invalidate user from cache
u.cache.Invalidate("ID", user.ID) u.state.Caches.GTS.User().Invalidate("ID", user.ID)
return nil return nil
} }
@ -187,6 +168,6 @@ func (u *userDB) DeleteUserByID(ctx context.Context, userID string) db.Error {
} }
// Invalidate user from cache // Invalidate user from cache
u.cache.Invalidate("ID", userID) u.state.Caches.GTS.User().Invalidate("ID", userID)
return nil return nil
} }

View file

@ -37,6 +37,8 @@ type Emoji interface {
UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, columns ...string) (*gtsmodel.Emoji, Error) UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, columns ...string) (*gtsmodel.Emoji, Error)
// DeleteEmojiByID deletes one emoji by its database ID. // DeleteEmojiByID deletes one emoji by its database ID.
DeleteEmojiByID(ctx context.Context, id string) Error DeleteEmojiByID(ctx context.Context, id string) Error
// GetEmojisByIDs gets emojis for the given IDs.
GetEmojisByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Emoji, Error)
// GetUseableEmojis gets all emojis which are useable by accounts on this instance. // GetUseableEmojis gets all emojis which are useable by accounts on this instance.
GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, Error) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, Error)
// GetEmojis gets emojis based on given parameters. Useful for admin actions. // GetEmojis gets emojis based on given parameters. Useful for admin actions.
@ -52,6 +54,8 @@ type Emoji interface {
GetEmojiByStaticURL(ctx context.Context, imageStaticURL string) (*gtsmodel.Emoji, Error) GetEmojiByStaticURL(ctx context.Context, imageStaticURL string) (*gtsmodel.Emoji, Error)
// PutEmojiCategory puts one new emoji category in the database. // PutEmojiCategory puts one new emoji category in the database.
PutEmojiCategory(ctx context.Context, emojiCategory *gtsmodel.EmojiCategory) Error PutEmojiCategory(ctx context.Context, emojiCategory *gtsmodel.EmojiCategory) Error
// GetEmojiCategoriesByIDs gets emoji categories for given IDs.
GetEmojiCategoriesByIDs(ctx context.Context, ids []string) ([]*gtsmodel.EmojiCategory, Error)
// GetEmojiCategories gets a slice of the names of all existing emoji categories. // GetEmojiCategories gets a slice of the names of all existing emoji categories.
GetEmojiCategories(ctx context.Context) ([]*gtsmodel.EmojiCategory, Error) GetEmojiCategories(ctx context.Context) ([]*gtsmodel.EmojiCategory, Error)
// GetEmojiCategory gets one emoji category by its id. // GetEmojiCategory gets one emoji category by its id.

49
internal/state/state.go Normal file
View file

@ -0,0 +1,49 @@
/*
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 state
import (
"github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/db"
)
// State provides a means of dependency injection and sharing of resources
// across different subpackages of the GoToSocial codebase. DO NOT assume
// that any particular field will be initialized if you are accessing this
// during initialization. A pointer to a State{} is often passed during
// subpackage initialization, while the returned subpackage type will later
// then be set and stored within the State{} itself.
type State struct {
// Caches provides access to this state's collection of caches.
Caches cache.Caches
// DB provides access to the database.
DB db.DB
// prevent pass-by-value.
_ nocopy
}
// nocopy when embedded will signal linter to
// error on pass-by-value of parent struct.
type nocopy struct{}
func (*nocopy) Lock() {}
func (*nocopy) Unlock() {}

View file

@ -148,7 +148,8 @@ func NewTimeline(
grabFunction GrabFunction, grabFunction GrabFunction,
filterFunction FilterFunction, filterFunction FilterFunction,
prepareFunction PrepareFunction, prepareFunction PrepareFunction,
skipInsertFunction SkipInsertFunction) (Timeline, error) { skipInsertFunction SkipInsertFunction,
) (Timeline, error) {
return &timeline{ return &timeline{
indexedItems: &indexedItems{ indexedItems: &indexedItems{
skipInsert: skipInsertFunction, skipInsert: skipInsertFunction,

View file

@ -2,7 +2,6 @@
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@ -13,7 +12,6 @@
func (c *converter) FollowRequestToFollow(ctx context.Context, f *gtsmodel.FollowRequest) *gtsmodel.Follow { func (c *converter) FollowRequestToFollow(ctx context.Context, f *gtsmodel.FollowRequest) *gtsmodel.Follow {
showReblogs := *f.ShowReblogs showReblogs := *f.ShowReblogs
notify := *f.Notify notify := *f.Notify
return &gtsmodel.Follow{ return &gtsmodel.Follow{
ID: f.ID, ID: f.ID,
CreatedAt: f.CreatedAt, CreatedAt: f.CreatedAt,
@ -33,8 +31,9 @@ func (c *converter) StatusToBoost(ctx context.Context, s *gtsmodel.Status, boost
if err != nil { if err != nil {
return nil, err return nil, err
} }
boostWrapperStatusURI := fmt.Sprintf("%s/%s", accountURIs.StatusesURI, boostWrapperStatusID)
boostWrapperStatusURL := fmt.Sprintf("%s/%s", accountURIs.StatusesURL, boostWrapperStatusID) boostWrapperStatusURI := accountURIs.StatusesURI + "/" + boostWrapperStatusID
boostWrapperStatusURL := accountURIs.StatusesURL + "/" + boostWrapperStatusID
local := true local := true
if boostingAccount.Domain != "" { if boostingAccount.Domain != "" {

View file

@ -28,6 +28,7 @@
"github.com/superseriousbusiness/gotosocial/internal/db/bundb" "github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
) )
var testModels = []interface{}{ var testModels = []interface{}{
@ -92,10 +93,16 @@ func NewTestDB() db.DB {
}) })
} }
testDB, err := bundb.NewBunDBService(context.Background()) var state state.State
state.Caches.Init()
testDB, err := bundb.NewBunDBService(context.Background(), &state)
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
state.DB = testDB
return testDB return testDB
} }