diff options
| author | 2022-12-08 17:35:14 +0000 | |
|---|---|---|
| committer | 2022-12-08 18:35:14 +0100 | |
| commit | e58d2d81226c4cc4110747305d083b67903d621c (patch) | |
| tree | 249f8b3f4732ccdb1e77b2d4243fafb4ee4c1c86 | |
| parent | [chore] Remove deprecated linters (#1228) (diff) | |
| download | gotosocial-e58d2d81226c4cc4110747305d083b67903d621c.tar.xz | |
[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>
27 files changed, 725 insertions, 332 deletions
diff --git a/cmd/gotosocial/action/admin/account/account.go b/cmd/gotosocial/action/admin/account/account.go index 35c2f68ab..5cde9bdf3 100644 --- a/cmd/gotosocial/action/admin/account/account.go +++ b/cmd/gotosocial/action/admin/account/account.go @@ -27,17 +27,24 @@ import (  	"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db/bundb" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/superseriousbusiness/gotosocial/internal/validate"  	"golang.org/x/crypto/bcrypt"  )  // Create creates a new account in the database using the provided flags.  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbConn +  	username := config.GetAdminAccountUsername()  	if username == "" {  		return errors.New("no username set") @@ -88,11 +95,17 @@ var Create action.GTSAction = func(ctx context.Context) error {  // 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 { -	dbConn, err := bundb.NewBunDBService(ctx) +	var state state.State +	state.Caches.Init() + +	dbConn, err := bundb.NewBunDBService(ctx, &state)  	if err != nil {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbConn +  	username := config.GetAdminAccountUsername()  	if username == "" {  		return errors.New("no username set") @@ -125,11 +138,17 @@ var Confirm action.GTSAction = func(ctx context.Context) error {  // Promote sets a user to admin.  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbConn +  	username := config.GetAdminAccountUsername()  	if username == "" {  		return errors.New("no username set") @@ -159,11 +178,17 @@ var Promote action.GTSAction = func(ctx context.Context) error {  // Demote sets admin on a user to false.  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbConn +  	username := config.GetAdminAccountUsername()  	if username == "" {  		return errors.New("no username set") @@ -193,11 +218,17 @@ var Demote action.GTSAction = func(ctx context.Context) error {  // Disable sets Disabled to true on a user.  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbConn +  	username := config.GetAdminAccountUsername()  	if username == "" {  		return errors.New("no username set") @@ -227,11 +258,17 @@ var Disable action.GTSAction = func(ctx context.Context) error {  // Password sets the password of target account.  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbConn +  	username := config.GetAdminAccountUsername()  	if username == "" {  		return errors.New("no username set") diff --git a/cmd/gotosocial/action/admin/media/prune/orphaned.go b/cmd/gotosocial/action/admin/media/prune/orphaned.go index 4ceb356bd..ede00dca2 100644 --- a/cmd/gotosocial/action/admin/media/prune/orphaned.go +++ b/cmd/gotosocial/action/admin/media/prune/orphaned.go @@ -27,12 +27,16 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/db/bundb"  	"github.com/superseriousbusiness/gotosocial/internal/log"  	"github.com/superseriousbusiness/gotosocial/internal/media" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"  )  // Orphaned prunes orphaned media from storage.  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} @@ -54,7 +58,7 @@ var Orphaned action.GTSAction = func(ctx context.Context) error {  		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)  	} else {  		log.Infof("%d stored items were orphaned and pruned", pruned) diff --git a/cmd/gotosocial/action/admin/trans/export.go b/cmd/gotosocial/action/admin/trans/export.go index 2b5f82ee8..aa2cc9c26 100644 --- a/cmd/gotosocial/action/admin/trans/export.go +++ b/cmd/gotosocial/action/admin/trans/export.go @@ -26,16 +26,22 @@ import (  	"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db/bundb" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/superseriousbusiness/gotosocial/internal/trans"  )  // Export exports info from the database into a file  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbConn +  	exporter := trans.NewExporter(dbConn)  	path := config.GetAdminTransPath() diff --git a/cmd/gotosocial/action/admin/trans/import.go b/cmd/gotosocial/action/admin/trans/import.go index 75f2d6a5e..7d27f79a1 100644 --- a/cmd/gotosocial/action/admin/trans/import.go +++ b/cmd/gotosocial/action/admin/trans/import.go @@ -26,16 +26,22 @@ import (  	"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db/bundb" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/superseriousbusiness/gotosocial/internal/trans"  )  // Import imports info from a file into the database  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbConn +  	importer := trans.NewImporter(dbConn)  	path := config.GetAdminTransPath() diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go index 3da9e997c..9fb916850 100644 --- a/cmd/gotosocial/action/server/server.go +++ b/cmd/gotosocial/action/server/server.go @@ -65,6 +65,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/oidc"  	"github.com/superseriousbusiness/gotosocial/internal/processing"  	"github.com/superseriousbusiness/gotosocial/internal/router" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"  	"github.com/superseriousbusiness/gotosocial/internal/transport"  	"github.com/superseriousbusiness/gotosocial/internal/typeutils" @@ -73,11 +74,20 @@ import (  // Start creates and starts a gotosocial server  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 {  		return fmt.Errorf("error creating dbservice: %s", err)  	} +	// Set the state DB connection +	state.DB = dbService +  	if err := dbService.CreateInstanceAccount(ctx); err != nil {  		return fmt.Errorf("error creating instance account: %s", err)  	} diff --git a/internal/cache/ap.go b/internal/cache/ap.go new file mode 100644 index 000000000..ef2807cd9 --- /dev/null +++ b/internal/cache/ap.go @@ -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() {} diff --git a/internal/cache/cache.go b/internal/cache/cache.go new file mode 100644 index 000000000..0f2a59ef1 --- /dev/null +++ b/internal/cache/cache.go @@ -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() +} diff --git a/internal/cache/gts.go b/internal/cache/gts.go new file mode 100644 index 000000000..9d77f5386 --- /dev/null +++ b/internal/cache/gts.go @@ -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 >sCaches{} +} + +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) +} diff --git a/internal/cache/util.go b/internal/cache/util.go new file mode 100644 index 000000000..56be27e05 --- /dev/null +++ b/internal/cache/util.go @@ -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) +} diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go index ea0852d77..9f3fc8a16 100644 --- a/internal/db/bundb/account.go +++ b/internal/db/bundb/account.go @@ -25,39 +25,18 @@ import (  	"strings"  	"time" -	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  	"github.com/uptrace/bun/dialect"  )  type accountDB struct { -	conn   *DBConn -	cache  *result.Cache[*gtsmodel.Account] -	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) +	conn  *DBConn +	state *state.State  }  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) {  	// 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  		// 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 {  		// 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 {  			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 { -	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  		// 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  	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  		// 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  	} -	a.cache.Invalidate("ID", id) +	a.state.Caches.GTS.Account().Invalidate("ID", id)  	return nil  } @@ -514,7 +493,7 @@ func (a *accountDB) statusesFromIDs(ctx context.Context, statusIDs []string) ([]  	for _, id := range statusIDs {  		// 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 {  			log.Errorf("statusesFromIDs: error getting status %q: %v", id, err)  			continue diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go index 2a8851684..d3554f298 100644 --- a/internal/db/bundb/admin.go +++ b/internal/db/bundb/admin.go @@ -34,6 +34,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/id"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/superseriousbusiness/gotosocial/internal/uris"  	"github.com/uptrace/bun"  	"golang.org/x/crypto/bcrypt" @@ -43,9 +44,8 @@ import (  const rsaKeyBits = 2048  type adminDB struct { -	conn     *DBConn -	accounts *accountDB -	users    *userDB +	conn  *DBConn +	state *state.State  }  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! -		if err := a.accounts.PutAccount(ctx, acct); err != nil { +		if err := a.state.DB.PutAccount(ctx, acct); err != nil {  			return nil, err  		}  	} @@ -185,7 +185,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string,  	}  	// 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  	} @@ -241,7 +241,7 @@ func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error {  	}  	// 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  	} diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go index 163174456..fa32f32eb 100644 --- a/internal/db/bundb/bundb.go +++ b/internal/db/bundb/bundb.go @@ -40,6 +40,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/id"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  	"github.com/uptrace/bun/dialect/pgdialect"  	"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.  // 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 err error  	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)  	} -	// 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 := ¬ificationDB{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{ -		Account: account, +		Account: &accountDB{ +			conn:  conn, +			state: state, +		},  		Admin: &adminDB{ -			conn:     conn, -			accounts: account, -			users:    user, +			conn:  conn, +			state: state,  		},  		Basic: &basicDB{  			conn: conn,  		}, -		Domain: domain, -		Emoji:  emoji, +		Domain: &domainDB{ +			conn:  conn, +			state: state, +		}, +		Emoji: &emojiDB{ +			conn:  conn, +			state: state, +		},  		Instance: &instanceDB{  			conn: conn,  		},  		Media: &mediaDB{  			conn: conn,  		}, -		Mention:      mention, -		Notification: notif, -		Relationship: relationship, +		Mention: &mentionDB{ +			conn:  conn, +			state: state, +		}, +		Notification: ¬ificationDB{ +			conn:  conn, +			state: state, +		}, +		Relationship: &relationshipDB{ +			conn:  conn, +			state: state, +		},  		Session: &sessionDB{  			conn: conn,  		}, -		Status:    status, -		Timeline:  timeline, -		User:      user, -		Tombstone: tombstone, -		conn:      conn, +		Status: &statusDB{ +			conn:  conn, +			state: state, +		}, +		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 diff --git a/internal/db/bundb/bundbnew_test.go b/internal/db/bundb/bundbnew_test.go index 2bd945864..bdc1eae03 100644 --- a/internal/db/bundb/bundbnew_test.go +++ b/internal/db/bundb/bundbnew_test.go @@ -33,7 +33,7 @@ type BundbNewTestSuite struct {  func (suite *BundbNewTestSuite) TestCreateNewDB() {  	// 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.NotNil(db)  } @@ -42,7 +42,7 @@ func (suite *BundbNewTestSuite) TestCreateNewSqliteDBNoAddress() {  	// create a new db with no address specified  	config.SetDbAddress("")  	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.Nil(db)  } diff --git a/internal/db/bundb/domain.go b/internal/db/bundb/domain.go index ea2e4f077..a5d9f61e2 100644 --- a/internal/db/bundb/domain.go +++ b/internal/db/bundb/domain.go @@ -22,34 +22,18 @@ import (  	"context"  	"net/url"  	"strings" -	"time" -	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  	"golang.org/x/net/idna"  )  type domainDB struct {  	conn  *DBConn -	cache *result.Cache[*gtsmodel.DomainBlock] -} - -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) +	state *state.State  }  // normalizeDomain converts the given domain to lowercase @@ -71,7 +55,7 @@ func (d *domainDB) CreateDomainBlock(ctx context.Context, block *gtsmodel.Domain  		return err  	} -	return d.cache.Store(block, func() error { +	return d.state.Caches.GTS.DomainBlock().Store(block, func() error {  		_, err := d.conn.NewInsert().  			Model(block).  			Exec(ctx) @@ -87,7 +71,7 @@ func (d *domainDB) GetDomainBlock(ctx context.Context, domain string) (*gtsmodel  		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*  		if domain == "" || domain == config.GetAccountDomain() {  			return nil, db.ErrNoEntries @@ -125,7 +109,7 @@ func (d *domainDB) DeleteDomainBlock(ctx context.Context, domain string) db.Erro  	}  	// Clear domain from cache -	d.cache.Invalidate("Domain", domain) +	d.state.Caches.GTS.DomainBlock().Invalidate(domain)  	return nil  } diff --git a/internal/db/bundb/emoji.go b/internal/db/bundb/emoji.go index 554d8f560..4407fe81c 100644 --- a/internal/db/bundb/emoji.go +++ b/internal/db/bundb/emoji.go @@ -23,50 +23,17 @@ import (  	"strings"  	"time" -	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  	"github.com/uptrace/bun/dialect"  )  type emojiDB struct { -	conn          *DBConn -	emojiCache    *result.Cache[*gtsmodel.Emoji] -	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) +	conn  *DBConn +	state *state.State  }  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 { -	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)  		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)  	} -	e.emojiCache.Invalidate("ID", emoji.ID) +	e.state.Caches.GTS.Emoji().Invalidate("ID", emoji.ID)  	return emoji, nil  } @@ -139,7 +106,7 @@ func (e *emojiDB) DeleteEmojiByID(ctx context.Context, id string) db.Error {  		return err  	} -	e.emojiCache.Invalidate("ID", id) +	e.state.Caches.GTS.Emoji().Invalidate("ID", id)  	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) { @@ -276,7 +243,7 @@ func (e *emojiDB) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, db.E  		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) { @@ -338,7 +305,7 @@ func (e *emojiDB) GetEmojiByStaticURL(ctx context.Context, imageStaticURL string  }  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)  		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 e.emojiCategoriesFromIDs(ctx, emojiCategoryIDs) +	return e.GetEmojiCategoriesByIDs(ctx, emojiCategoryIDs)  }  func (e *emojiDB) GetEmojiCategory(ctx context.Context, id string) (*gtsmodel.EmojiCategory, db.Error) { @@ -383,7 +350,7 @@ func (e *emojiDB) GetEmojiCategoryByName(ctx context.Context, name string) (*gts  }  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  		// Not cached! Perform database query @@ -395,8 +362,7 @@ func (e *emojiDB) getEmoji(ctx context.Context, lookup string, dbQuery func(*gts  	}, keyParts...)  } -func (e *emojiDB) emojisFromIDs(ctx context.Context, emojiIDs []string) ([]*gtsmodel.Emoji, db.Error) { -	// Catch case of no emojis early +func (e *emojiDB) GetEmojisByIDs(ctx context.Context, emojiIDs []string) ([]*gtsmodel.Emoji, db.Error) {  	if len(emojiIDs) == 0 {  		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) { -	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  		// Not cached! Perform database query @@ -429,8 +395,7 @@ func (e *emojiDB) getEmojiCategory(ctx context.Context, lookup string, dbQuery f  	}, keyParts...)  } -func (e *emojiDB) emojiCategoriesFromIDs(ctx context.Context, emojiCategoryIDs []string) ([]*gtsmodel.EmojiCategory, db.Error) { -	// Catch case of no emoji categories early +func (e *emojiDB) GetEmojiCategoriesByIDs(ctx context.Context, emojiCategoryIDs []string) ([]*gtsmodel.EmojiCategory, db.Error) {  	if len(emojiCategoryIDs) == 0 {  		return nil, db.ErrNoEntries  	} diff --git a/internal/db/bundb/mention.go b/internal/db/bundb/mention.go index 303e16484..5ba56104c 100644 --- a/internal/db/bundb/mention.go +++ b/internal/db/bundb/mention.go @@ -20,33 +20,17 @@ package bundb  import (  	"context" -	"time" -	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  )  type mentionDB struct {  	conn  *DBConn -	cache *result.Cache[*gtsmodel.Mention] -} - -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) +	state *state.State  }  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) { -	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  		q := m.newMentionQ(&mention). diff --git a/internal/db/bundb/notification.go b/internal/db/bundb/notification.go index 6a8fab464..a65d799c3 100644 --- a/internal/db/bundb/notification.go +++ b/internal/db/bundb/notification.go @@ -20,37 +20,21 @@ package bundb  import (  	"context" -	"time" -	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  )  type notificationDB struct {  	conn  *DBConn -	cache *result.Cache[*gtsmodel.Notification] -} - -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) +	state *state.State  }  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  		q := n.conn.NewSelect(). @@ -130,6 +114,6 @@ func (n *notificationDB) ClearNotifications(ctx context.Context, accountID strin  		return n.conn.ProcessError(err)  	} -	n.cache.Clear() +	n.state.Caches.GTS.Notification().Clear()  	return nil  } diff --git a/internal/db/bundb/relationship.go b/internal/db/bundb/relationship.go index f6df95524..deab74d61 100644 --- a/internal/db/bundb/relationship.go +++ b/internal/db/bundb/relationship.go @@ -23,35 +23,16 @@ import (  	"database/sql"  	"errors"  	"fmt" -	"time" -	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  )  type relationshipDB struct { -	conn       *DBConn -	accounts   *accountDB -	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) +	conn  *DBConn +	state *state.State  }  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 -	block.Account, err = r.accounts.GetAccountByID(ctx, block.AccountID) +	block.Account, err = r.state.DB.GetAccountByID(ctx, block.AccountID)  	if err != nil {  		return nil, err  	}  	// 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 {  		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) { -	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  		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 { -	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)  		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 -	r.blockCache.Invalidate("ID", id) +	r.state.Caches.GTS.Block().Invalidate("ID", id)  	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 -	r.blockCache.Invalidate("URI", uri) +	r.state.Caches.GTS.Block().Invalidate("URI", uri)  	return nil  } diff --git a/internal/db/bundb/status.go b/internal/db/bundb/status.go index 2b33b84e9..20ca843be 100644 --- a/internal/db/bundb/status.go +++ b/internal/db/bundb/status.go @@ -26,36 +26,16 @@ import (  	"fmt"  	"time" -	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  )  type statusDB struct { -	conn     *DBConn -	cache    *result.Cache[*gtsmodel.Status] -	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) +	conn  *DBConn +	state *state.State  }  func (s *statusDB) newStatusQ(status interface{}) *bun.SelectQuery { @@ -111,7 +91,7 @@ func (s *statusDB) GetStatusByURL(ctx context.Context, url string) (*gtsmodel.St  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 -	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  		// 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 -	status.Account, err = s.accounts.GetAccountByID(ctx, status.AccountID) +	status.Account, err = s.state.DB.GetAccountByID(ctx, status.AccountID)  	if err != nil {  		return nil, fmt.Errorf("error getting status account: %w", err)  	}  	if id := status.BoostOfAccountID; id != "" {  		// 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 {  			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 != "" {  		// 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 {  			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 {  		// 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 {  			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 {  		// 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 {  			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 { -	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  		// 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 -	s.cache.Invalidate("ID", status.ID) +	s.state.Caches.GTS.Status().Invalidate("ID", status.ID)  	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 -	s.cache.Invalidate("ID", id) +	s.state.Caches.GTS.Status().Invalidate("ID", id)  	return nil  } diff --git a/internal/db/bundb/timeline.go b/internal/db/bundb/timeline.go index d15c07e9c..a6e3554f8 100644 --- a/internal/db/bundb/timeline.go +++ b/internal/db/bundb/timeline.go @@ -26,13 +26,14 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/id"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  	"golang.org/x/exp/slices"  )  type timelineDB struct { -	conn   *DBConn -	status *statusDB +	conn  *DBConn +	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) { @@ -111,7 +112,7 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI  	for _, id := range statusIDs {  		// Fetch status from db for ID -		status, err := t.status.GetStatusByID(ctx, id) +		status, err := t.state.DB.GetStatusByID(ctx, id)  		if err != nil {  			log.Errorf("GetHomeTimeline: error fetching status %q: %v", id, err)  			continue @@ -179,7 +180,7 @@ func (t *timelineDB) GetPublicTimeline(ctx context.Context, maxID string, sinceI  	for _, id := range statusIDs {  		// Fetch status from db for ID -		status, err := t.status.GetStatusByID(ctx, id) +		status, err := t.state.DB.GetStatusByID(ctx, id)  		if err != nil {  			log.Errorf("GetPublicTimeline: error fetching status %q: %v", id, err)  			continue @@ -239,7 +240,7 @@ func (t *timelineDB) GetFavedTimeline(ctx context.Context, accountID string, max  	for _, fave := range faves {  		// 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 {  			log.Errorf("GetFavedTimeline: error fetching status for fave %q: %v", fave.ID, err)  			continue diff --git a/internal/db/bundb/tombstone.go b/internal/db/bundb/tombstone.go index 309a39fd3..64c0a4508 100644 --- a/internal/db/bundb/tombstone.go +++ b/internal/db/bundb/tombstone.go @@ -20,38 +20,20 @@ package bundb  import (  	"context" -	"time"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun" - -	"codeberg.org/gruf/go-cache/v3/result"  )  type tombstoneDB struct {  	conn  *DBConn -	cache *result.Cache[*gtsmodel.Tombstone] -} - -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) +	state *state.State  }  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  		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 { -	return t.cache.Store(tombstone, func() error { +	return t.state.Caches.GTS.Tombstone().Store(tombstone, func() error {  		_, err := t.conn.  			NewInsert().  			Model(tombstone). @@ -95,7 +77,7 @@ func (t *tombstoneDB) DeleteTombstone(ctx context.Context, id string) db.Error {  	}  	// Invalidate from cache by ID -	t.cache.Invalidate("ID", id) +	t.state.Caches.GTS.Tombstone().Invalidate("ID", id)  	return nil  } diff --git a/internal/db/bundb/user.go b/internal/db/bundb/user.go index e7983556f..e90c06343 100644 --- a/internal/db/bundb/user.go +++ b/internal/db/bundb/user.go @@ -22,38 +22,19 @@ import (  	"context"  	"time" -	"codeberg.org/gruf/go-cache/v3/result"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/state"  	"github.com/uptrace/bun"  )  type userDB struct {  	conn  *DBConn -	cache *result.Cache[*gtsmodel.User] -} - -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) +	state *state.State  }  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  		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) { -	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  		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) { -	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  		q := u.conn. @@ -105,9 +86,9 @@ func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string)  		return &user, nil  	}, 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  		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) { -	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  		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 { -	return u.cache.Store(user, func() error { +	return u.state.Caches.GTS.User().Store(user, func() error {  		_, err := u.conn.  			NewInsert().  			Model(user). @@ -172,8 +153,8 @@ func (u *userDB) UpdateUser(ctx context.Context, user *gtsmodel.User, columns ..  		return u.conn.ProcessError(err)  	} -	// Invalidate in cache -	u.cache.Invalidate("ID", user.ID) +	// Invalidate user from cache +	u.state.Caches.GTS.User().Invalidate("ID", user.ID)  	return nil  } @@ -187,6 +168,6 @@ func (u *userDB) DeleteUserByID(ctx context.Context, userID string) db.Error {  	}  	// Invalidate user from cache -	u.cache.Invalidate("ID", userID) +	u.state.Caches.GTS.User().Invalidate("ID", userID)  	return nil  } diff --git a/internal/db/emoji.go b/internal/db/emoji.go index 267213b2d..a103985f6 100644 --- a/internal/db/emoji.go +++ b/internal/db/emoji.go @@ -37,6 +37,8 @@ type Emoji interface {  	UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, columns ...string) (*gtsmodel.Emoji, Error)  	// DeleteEmojiByID deletes one emoji by its database ID.  	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(ctx context.Context) ([]*gtsmodel.Emoji, Error)  	// 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)  	// PutEmojiCategory puts one new emoji category in the database.  	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(ctx context.Context) ([]*gtsmodel.EmojiCategory, Error)  	// GetEmojiCategory gets one emoji category by its id. diff --git a/internal/state/state.go b/internal/state/state.go new file mode 100644 index 000000000..fb6d6f41a --- /dev/null +++ b/internal/state/state.go @@ -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() {} diff --git a/internal/timeline/timeline.go b/internal/timeline/timeline.go index fb7671819..3de029332 100644 --- a/internal/timeline/timeline.go +++ b/internal/timeline/timeline.go @@ -148,7 +148,8 @@ func NewTimeline(  	grabFunction GrabFunction,  	filterFunction FilterFunction,  	prepareFunction PrepareFunction, -	skipInsertFunction SkipInsertFunction) (Timeline, error) { +	skipInsertFunction SkipInsertFunction, +) (Timeline, error) {  	return &timeline{  		indexedItems: &indexedItems{  			skipInsert: skipInsertFunction, diff --git a/internal/typeutils/internal.go b/internal/typeutils/internal.go index ce2c942fd..f0c501fa9 100644 --- a/internal/typeutils/internal.go +++ b/internal/typeutils/internal.go @@ -2,7 +2,6 @@ package typeutils  import (  	"context" -	"fmt"  	"time"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -13,7 +12,6 @@ import (  func (c *converter) FollowRequestToFollow(ctx context.Context, f *gtsmodel.FollowRequest) *gtsmodel.Follow {  	showReblogs := *f.ShowReblogs  	notify := *f.Notify -  	return >smodel.Follow{  		ID:              f.ID,  		CreatedAt:       f.CreatedAt, @@ -33,8 +31,9 @@ func (c *converter) StatusToBoost(ctx context.Context, s *gtsmodel.Status, boost  	if err != nil {  		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  	if boostingAccount.Domain != "" { diff --git a/testrig/db.go b/testrig/db.go index 6670b91db..508f10b76 100644 --- a/testrig/db.go +++ b/testrig/db.go @@ -28,6 +28,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/db/bundb"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/state"  )  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 {  		log.Panic(err)  	} + +	state.DB = testDB +  	return testDB  }  | 
