[chore] media and emoji refactoring (#3000)

* start updating media manager interface ready for storing attachments / emoji right away

* store emoji and media as uncached immediately, then (re-)cache on Processing{}.Load()

* remove now unused media workers

* fix tests and issues

* fix another test!

* fix emoji activitypub uri setting behaviour, fix remainder of test compilation issues

* fix more tests

* fix (most of) remaining tests, add debouncing to repeatedly failing media / emojis

* whoops, rebase issue

* remove kim's whacky experiments

* do some reshuffling, ensure emoji uri gets set

* ensure marked as not cached on cleanup

* tweaks to media / emoji processing to handle context canceled better

* ensure newly fetched emojis actually get set in returned slice

* use different varnames to be a bit more obvious

* move emoji refresh rate limiting to dereferencer

* add exported dereferencer functions for remote media, use these for recaching in processor

* add check for nil attachment in updateAttachment()

* remove unused emoji and media fields + columns

* see previous commit

* fix old migrations expecting image_updated_at to exists (from copies of old models)

* remove freshness checking code (seems to be broken...)

* fix error arg causing nil ptr exception

* finish documentating functions with comments, slight tweaks to media / emoji deref error logic

* remove some extra unneeded boolean checking

* finish writing documentation (code comments) for exported media manager methods

* undo changes to migration snapshot gtsmodels, updated failing migration to have its own snapshot

* move doesColumnExist() to util.go in migrations package
This commit is contained in:
kim 2024-06-26 15:01:16 +00:00 committed by GitHub
commit 21bb324156
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 2578 additions and 1926 deletions

View file

@ -33,7 +33,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
@ -730,18 +729,18 @@ func (d *Dereferencer) enrichAccount(
latestAcc.ID = account.ID
latestAcc.FetchedAt = time.Now()
// Ensure the account's avatar media is populated, passing in existing to check for changes.
if err := d.fetchRemoteAccountAvatar(ctx, tsport, account, latestAcc); err != nil {
// Ensure the account's avatar media is populated, passing in existing to check for chages.
if err := d.fetchAccountAvatar(ctx, requestUser, account, latestAcc); err != nil {
log.Errorf(ctx, "error fetching remote avatar for account %s: %v", uri, err)
}
// Ensure the account's avatar media is populated, passing in existing to check for changes.
if err := d.fetchRemoteAccountHeader(ctx, tsport, account, latestAcc); err != nil {
// Ensure the account's avatar media is populated, passing in existing to check for chages.
if err := d.fetchAccountHeader(ctx, requestUser, account, latestAcc); err != nil {
log.Errorf(ctx, "error fetching remote header for account %s: %v", uri, err)
}
// Fetch the latest remote account emoji IDs used in account display name/bio.
if _, err = d.fetchRemoteAccountEmojis(ctx, latestAcc, requestUser); err != nil {
if err = d.fetchAccountEmojis(ctx, account, latestAcc); err != nil {
log.Errorf(ctx, "error fetching remote emojis for account %s: %v", uri, err)
}
@ -779,9 +778,9 @@ func (d *Dereferencer) enrichAccount(
return latestAcc, apubAcc, nil
}
func (d *Dereferencer) fetchRemoteAccountAvatar(
func (d *Dereferencer) fetchAccountAvatar(
ctx context.Context,
tsport transport.Transport,
requestUser string,
existingAcc *gtsmodel.Account,
latestAcc *gtsmodel.Account,
) error {
@ -808,7 +807,7 @@ func (d *Dereferencer) fetchRemoteAccountAvatar(
// Ensuring existing attachment is up-to-date
// and any recaching is performed if required.
existing, err := d.updateAttachment(ctx,
tsport,
requestUser,
existing,
nil,
)
@ -830,18 +829,23 @@ func (d *Dereferencer) fetchRemoteAccountAvatar(
}
}
// Fetch newly changed avatar from remote.
attachment, err := d.loadAttachment(ctx,
tsport,
// Fetch newly changed avatar.
attachment, err := d.GetMedia(ctx,
requestUser,
latestAcc.ID,
latestAcc.AvatarRemoteURL,
&media.AdditionalMediaInfo{
media.AdditionalMediaInfo{
Avatar: util.Ptr(true),
RemoteURL: &latestAcc.AvatarRemoteURL,
},
)
if err != nil {
return gtserror.Newf("error loading attachment %s: %w", latestAcc.AvatarRemoteURL, err)
if attachment == nil {
return gtserror.Newf("error loading attachment %s: %w", latestAcc.AvatarRemoteURL, err)
}
// non-fatal error occurred during loading, still use it.
log.Warnf(ctx, "partially loaded attachment: %v", err)
}
// Set the avatar attachment on account model.
@ -851,9 +855,9 @@ func (d *Dereferencer) fetchRemoteAccountAvatar(
return nil
}
func (d *Dereferencer) fetchRemoteAccountHeader(
func (d *Dereferencer) fetchAccountHeader(
ctx context.Context,
tsport transport.Transport,
requestUser string,
existingAcc *gtsmodel.Account,
latestAcc *gtsmodel.Account,
) error {
@ -880,7 +884,7 @@ func (d *Dereferencer) fetchRemoteAccountHeader(
// Ensuring existing attachment is up-to-date
// and any recaching is performed if required.
existing, err := d.updateAttachment(ctx,
tsport,
requestUser,
existing,
nil,
)
@ -902,18 +906,23 @@ func (d *Dereferencer) fetchRemoteAccountHeader(
}
}
// Fetch newly changed header from remote.
attachment, err := d.loadAttachment(ctx,
tsport,
// Fetch newly changed header.
attachment, err := d.GetMedia(ctx,
requestUser,
latestAcc.ID,
latestAcc.HeaderRemoteURL,
&media.AdditionalMediaInfo{
media.AdditionalMediaInfo{
Header: util.Ptr(true),
RemoteURL: &latestAcc.HeaderRemoteURL,
},
)
if err != nil {
return gtserror.Newf("error loading attachment %s: %w", latestAcc.HeaderRemoteURL, err)
if attachment == nil {
return gtserror.Newf("error loading attachment %s: %w", latestAcc.HeaderRemoteURL, err)
}
// non-fatal error occurred during loading, still use it.
log.Warnf(ctx, "partially loaded attachment: %v", err)
}
// Set the header attachment on account model.
@ -923,119 +932,44 @@ func (d *Dereferencer) fetchRemoteAccountHeader(
return nil
}
func (d *Dereferencer) fetchRemoteAccountEmojis(ctx context.Context, targetAccount *gtsmodel.Account, requestingUsername string) (bool, error) {
maybeEmojis := targetAccount.Emojis
maybeEmojiIDs := targetAccount.EmojiIDs
// It's possible that the account had emoji IDs set on it, but not Emojis
// themselves, depending on how it was fetched before being passed to us.
//
// If we only have IDs, fetch the emojis from the db. We know they're in
// there or else they wouldn't have IDs.
if len(maybeEmojiIDs) > len(maybeEmojis) {
maybeEmojis = make([]*gtsmodel.Emoji, 0, len(maybeEmojiIDs))
for _, emojiID := range maybeEmojiIDs {
maybeEmoji, err := d.state.DB.GetEmojiByID(ctx, emojiID)
if err != nil {
return false, err
}
maybeEmojis = append(maybeEmojis, maybeEmoji)
}
}
// For all the maybe emojis we have, we either fetch them from the database
// (if we haven't already), or dereference them from the remote instance.
gotEmojis, err := d.populateEmojis(ctx, maybeEmojis, requestingUsername)
if err != nil {
return false, err
}
// Extract the ID of each fetched or dereferenced emoji, so we can attach
// this to the account if necessary.
gotEmojiIDs := make([]string, 0, len(gotEmojis))
for _, e := range gotEmojis {
gotEmojiIDs = append(gotEmojiIDs, e.ID)
}
var (
changed = false // have the emojis for this account changed?
maybeLen = len(maybeEmojis)
gotLen = len(gotEmojis)
func (d *Dereferencer) fetchAccountEmojis(
ctx context.Context,
existing *gtsmodel.Account,
account *gtsmodel.Account,
) error {
// Fetch the updated emojis for our account.
emojis, changed, err := d.fetchEmojis(ctx,
existing.Emojis,
account.Emojis,
)
// if the length of everything is zero, this is simple:
// nothing has changed and there's nothing to do
if maybeLen == 0 && gotLen == 0 {
return changed, nil
if err != nil {
return gtserror.Newf("error fetching emojis: %w", err)
}
// if the *amount* of emojis on the account has changed, then the got emojis
// are definitely different from the previous ones (if there were any) --
// the account has either more or fewer emojis set on it now, so take the
// discovered emojis as the new correct ones.
if maybeLen != gotLen {
changed = true
targetAccount.Emojis = gotEmojis
targetAccount.EmojiIDs = gotEmojiIDs
return changed, nil
if !changed {
// Use existing account emoji objects.
account.EmojiIDs = existing.EmojiIDs
account.Emojis = existing.Emojis
return nil
}
// if the lengths are the same but not all of the slices are
// zero, something *might* have changed, so we have to check
// Set latest emojis.
account.Emojis = emojis
// 1. did we have emojis before that we don't have now?
for _, maybeEmoji := range maybeEmojis {
var stillPresent bool
for _, gotEmoji := range gotEmojis {
if maybeEmoji.URI == gotEmoji.URI {
// the emoji we maybe had is still present now,
// so we can stop checking gotEmojis
stillPresent = true
break
}
}
if !stillPresent {
// at least one maybeEmoji is no longer present in
// the got emojis, so we can stop checking now
changed = true
targetAccount.Emojis = gotEmojis
targetAccount.EmojiIDs = gotEmojiIDs
return changed, nil
}
// Iterate over and set changed emoji IDs.
account.EmojiIDs = make([]string, len(emojis))
for i, emoji := range emojis {
account.EmojiIDs[i] = emoji.ID
}
// 2. do we have emojis now that we didn't have before?
for _, gotEmoji := range gotEmojis {
var wasPresent bool
for _, maybeEmoji := range maybeEmojis {
// check emoji IDs here as well, because unreferenced
// maybe emojis we didn't already have would not have
// had IDs set on them yet
if gotEmoji.URI == maybeEmoji.URI && gotEmoji.ID == maybeEmoji.ID {
// this got emoji was present already in the maybeEmoji,
// so we can stop checking through maybeEmojis
wasPresent = true
break
}
}
if !wasPresent {
// at least one gotEmojis was not present in
// the maybeEmojis, so we can stop checking now
changed = true
targetAccount.Emojis = gotEmojis
targetAccount.EmojiIDs = gotEmojiIDs
return changed, nil
}
}
return changed, nil
return nil
}
func (d *Dereferencer) dereferenceAccountStats(ctx context.Context, requestUser string, account *gtsmodel.Account) error {
func (d *Dereferencer) dereferenceAccountStats(
ctx context.Context,
requestUser string,
account *gtsmodel.Account,
) error {
// Ensure we have a stats model for this account.
if account.Stats == nil {
if err := d.state.DB.PopulateAccountStats(ctx, account); err != nil {