summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/federation/dereferencing/account.go183
-rw-r--r--internal/federation/dereferencing/status.go61
-rw-r--r--internal/federation/dereferencing/util.go111
3 files changed, 241 insertions, 114 deletions
diff --git a/internal/federation/dereferencing/account.go b/internal/federation/dereferencing/account.go
index bd97b91ed..33a71ceb9 100644
--- a/internal/federation/dereferencing/account.go
+++ b/internal/federation/dereferencing/account.go
@@ -20,7 +20,6 @@ package dereferencing
import (
"context"
"errors"
- "io"
"net/url"
"time"
@@ -752,128 +751,146 @@ func (d *Dereferencer) enrichAccount(
return latestAcc, apubAcc, nil
}
-func (d *Dereferencer) fetchRemoteAccountAvatar(ctx context.Context, tsport transport.Transport, existing, latestAcc *gtsmodel.Account) error {
+func (d *Dereferencer) fetchRemoteAccountAvatar(
+ ctx context.Context,
+ tsport transport.Transport,
+ existingAcc *gtsmodel.Account,
+ latestAcc *gtsmodel.Account,
+) error {
if latestAcc.AvatarRemoteURL == "" {
// No avatar set on newest model, leave
// latest avatar attachment ID empty.
return nil
}
- // By default we keep the previous media attachment ID. This will only
- // be changed if and when we have the new media loaded into storage.
- latestAcc.AvatarMediaAttachmentID = existing.AvatarMediaAttachmentID
-
- // If we had a media attachment ID already, and the URL
- // of the attachment hasn't changed from existing -> latest,
- // then we may be able to just keep our existing attachment
- // without having to make any remote calls.
- if latestAcc.AvatarMediaAttachmentID != "" &&
- existing.AvatarRemoteURL == latestAcc.AvatarRemoteURL {
+ // Check for an existing stored media attachment
+ // specifically with unchanged remote URL we can use.
+ if existingAcc.AvatarMediaAttachmentID != "" &&
+ existingAcc.AvatarRemoteURL == latestAcc.AvatarRemoteURL {
- // Ensure we have media attachment with the known ID.
- media, err := d.state.DB.GetAttachmentByID(ctx, existing.AvatarMediaAttachmentID)
+ // Fetch the existing avatar media attachment with ID.
+ existing, err := d.state.DB.GetAttachmentByID(ctx,
+ existingAcc.AvatarMediaAttachmentID,
+ )
if err != nil && !errors.Is(err, db.ErrNoEntries) {
- return gtserror.Newf("error getting attachment %s: %w", existing.AvatarMediaAttachmentID, err)
+ return gtserror.Newf("error getting attachment %s: %w", existingAcc.AvatarMediaAttachmentID, err)
}
- // Ensure attachment has correct properties.
- if media != nil && media.RemoteURL == latestAcc.AvatarRemoteURL {
- // We already have the most up-to-date
- // media attachment, keep using it.
- return nil
- }
- }
+ if existing != nil {
+ // Ensuring existing attachment is up-to-date
+ // and any recaching is performed if required.
+ existing, err := d.updateAttachment(ctx,
+ tsport,
+ existing,
+ nil,
+ )
- // If we reach here, we know we need to fetch the most
- // up-to-date version of the attachment from remote.
+ if err != nil {
+ log.Errorf(ctx, "error updating existing attachment: %v", err)
- // Parse and validate the newly provided media URL.
- avatarURI, err := url.Parse(latestAcc.AvatarRemoteURL)
- if err != nil {
- return gtserror.Newf("error parsing url %s: %w", latestAcc.AvatarRemoteURL, err)
- }
+ // specifically do NOT return nil here,
+ // we already have a model, we don't
+ // want to drop it from the account, just
+ // log that an update for it failed.
+ }
- // Set the media data function to dereference avatar from URI.
- data := func(ctx context.Context) (io.ReadCloser, int64, error) {
- return tsport.DereferenceMedia(ctx, avatarURI)
- }
+ // Set the avatar attachment on account model.
+ latestAcc.AvatarMediaAttachment = existing
+ latestAcc.AvatarMediaAttachmentID = existing.ID
- // Create new media processing request from the media manager instance.
- processing := d.mediaManager.PreProcessMedia(data, latestAcc.ID, &media.AdditionalMediaInfo{
- Avatar: func() *bool { v := true; return &v }(),
- RemoteURL: &latestAcc.AvatarRemoteURL,
- })
+ return nil
+ }
+ }
- // Start media attachment loading (blocking call).
- if _, err := processing.LoadAttachment(ctx); err != nil {
+ // Fetch newly changed avatar from remote.
+ attachment, err := d.loadAttachment(ctx,
+ tsport,
+ latestAcc.ID,
+ latestAcc.AvatarRemoteURL,
+ &media.AdditionalMediaInfo{
+ Avatar: util.Ptr(true),
+ RemoteURL: &latestAcc.AvatarRemoteURL,
+ },
+ )
+ if err != nil {
return gtserror.Newf("error loading attachment %s: %w", latestAcc.AvatarRemoteURL, err)
}
- // Set the newly loaded avatar media attachment ID.
- latestAcc.AvatarMediaAttachmentID = processing.AttachmentID()
+ // Set the avatar attachment on account model.
+ latestAcc.AvatarMediaAttachment = attachment
+ latestAcc.AvatarMediaAttachmentID = attachment.ID
return nil
}
-func (d *Dereferencer) fetchRemoteAccountHeader(ctx context.Context, tsport transport.Transport, existing, latestAcc *gtsmodel.Account) error {
+func (d *Dereferencer) fetchRemoteAccountHeader(
+ ctx context.Context,
+ tsport transport.Transport,
+ existingAcc *gtsmodel.Account,
+ latestAcc *gtsmodel.Account,
+) error {
if latestAcc.HeaderRemoteURL == "" {
// No header set on newest model, leave
// latest header attachment ID empty.
return nil
}
- // By default we keep the previous media attachment ID. This will only
- // be changed if and when we have the new media loaded into storage.
- latestAcc.HeaderMediaAttachmentID = existing.HeaderMediaAttachmentID
-
- // If we had a media attachment ID already, and the URL
- // of the attachment hasn't changed from existing -> latest,
- // then we may be able to just keep our existing attachment
- // without having to make any remote calls.
- if latestAcc.HeaderMediaAttachmentID != "" &&
- existing.HeaderRemoteURL == latestAcc.HeaderRemoteURL {
+ // Check for an existing stored media attachment
+ // specifically with unchanged remote URL we can use.
+ if existingAcc.HeaderMediaAttachmentID != "" &&
+ existingAcc.HeaderRemoteURL == latestAcc.HeaderRemoteURL {
- // Ensure we have media attachment with the known ID.
- media, err := d.state.DB.GetAttachmentByID(ctx, existing.HeaderMediaAttachmentID)
+ // Fetch the existing header media attachment with ID.
+ existing, err := d.state.DB.GetAttachmentByID(ctx,
+ existingAcc.HeaderMediaAttachmentID,
+ )
if err != nil && !errors.Is(err, db.ErrNoEntries) {
- return gtserror.Newf("error getting attachment %s: %w", existing.HeaderMediaAttachmentID, err)
+ return gtserror.Newf("error getting attachment %s: %w", existingAcc.HeaderMediaAttachmentID, err)
}
- // Ensure attachment has correct properties.
- if media != nil && media.RemoteURL == latestAcc.HeaderRemoteURL {
- // We already have the most up-to-date
- // media attachment, keep using it.
- return nil
- }
- }
+ if existing != nil {
+ // Ensuring existing attachment is up-to-date
+ // and any recaching is performed if required.
+ existing, err := d.updateAttachment(ctx,
+ tsport,
+ existing,
+ nil,
+ )
- // If we reach here, we know we need to fetch the most
- // up-to-date version of the attachment from remote.
+ if err != nil {
+ log.Errorf(ctx, "error updating existing attachment: %v", err)
- // Parse and validate the newly provided media URL.
- headerURI, err := url.Parse(latestAcc.HeaderRemoteURL)
- if err != nil {
- return gtserror.Newf("error parsing url %s: %w", latestAcc.HeaderRemoteURL, err)
- }
+ // specifically do NOT return nil here,
+ // we already have a model, we don't
+ // want to drop it from the account, just
+ // log that an update for it failed.
+ }
- // Set the media data function to dereference avatar from URI.
- data := func(ctx context.Context) (io.ReadCloser, int64, error) {
- return tsport.DereferenceMedia(ctx, headerURI)
- }
+ // Set the header attachment on account model.
+ latestAcc.HeaderMediaAttachment = existing
+ latestAcc.HeaderMediaAttachmentID = existing.ID
- // Create new media processing request from the media manager instance.
- processing := d.mediaManager.PreProcessMedia(data, latestAcc.ID, &media.AdditionalMediaInfo{
- Header: func() *bool { v := true; return &v }(),
- RemoteURL: &latestAcc.HeaderRemoteURL,
- })
+ return nil
+ }
+ }
- // Start media attachment loading (blocking call).
- if _, err := processing.LoadAttachment(ctx); err != nil {
+ // Fetch newly changed header from remote.
+ attachment, err := d.loadAttachment(ctx,
+ tsport,
+ latestAcc.ID,
+ latestAcc.HeaderRemoteURL,
+ &media.AdditionalMediaInfo{
+ Header: util.Ptr(true),
+ RemoteURL: &latestAcc.HeaderRemoteURL,
+ },
+ )
+ if err != nil {
return gtserror.Newf("error loading attachment %s: %w", latestAcc.HeaderRemoteURL, err)
}
- // Set the newly loaded avatar media attachment ID.
- latestAcc.HeaderMediaAttachmentID = processing.AttachmentID()
+ // Set the header attachment on account model.
+ latestAcc.HeaderMediaAttachment = attachment
+ latestAcc.HeaderMediaAttachmentID = attachment.ID
return nil
}
diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go
index 69627adc2..add12c31f 100644
--- a/internal/federation/dereferencing/status.go
+++ b/internal/federation/dereferencing/status.go
@@ -20,7 +20,6 @@ package dereferencing
import (
"context"
"errors"
- "io"
"net/url"
"slices"
"time"
@@ -1000,45 +999,45 @@ func (d *Dereferencer) fetchStatusAttachments(ctx context.Context, tsport transp
// Look for existing media attachment with remote URL first.
existing, ok := existing.GetAttachmentByRemoteURL(attachment.RemoteURL)
- if ok && existing.ID != "" && *existing.Cached {
+ if ok && existing.ID != "" {
+
+ // Ensure the existing media attachment is up-to-date and cached.
+ existing, err := d.updateAttachment(ctx, tsport, existing, attachment)
+ if err != nil {
+ log.Errorf(ctx, "error updating existing attachment: %v", err)
+
+ // specifically do NOT continue here,
+ // we already have a model, we don't
+ // want to drop it from the status, just
+ // log that an update for it failed.
+ }
+
+ // Set the existing attachment.
status.Attachments[i] = existing
status.AttachmentIDs[i] = existing.ID
continue
}
- // Ensure a valid media attachment remote URL.
- remoteURL, err := url.Parse(attachment.RemoteURL)
- if err != nil {
- log.Errorf(ctx, "invalid remote media url %q: %v", attachment.RemoteURL, err)
+ // Load this new media attachment.
+ attachment, err := d.loadAttachment(
+ ctx,
+ tsport,
+ status.AccountID,
+ attachment.RemoteURL,
+ &media.AdditionalMediaInfo{
+ StatusID: &status.ID,
+ RemoteURL: &attachment.RemoteURL,
+ Description: &attachment.Description,
+ Blurhash: &attachment.Blurhash,
+ },
+ )
+ if err != nil && attachment == nil {
+ log.Errorf(ctx, "error loading attachment: %v", err)
continue
}
- data := func(ctx context.Context) (io.ReadCloser, int64, error) {
- return tsport.DereferenceMedia(ctx, remoteURL)
- }
-
- ai := &media.AdditionalMediaInfo{
- StatusID: &status.ID,
- RemoteURL: &attachment.RemoteURL,
- Description: &attachment.Description,
- Blurhash: &attachment.Blurhash,
- }
-
- // Start pre-processing remote media at remote URL.
- processing := d.mediaManager.PreProcessMedia(data, status.AccountID, ai)
-
- // Force attachment loading *right now*.
- attachment, err = processing.LoadAttachment(ctx)
if err != nil {
- if attachment == nil {
- // Totally failed to load;
- // bail on this attachment.
- log.Errorf(ctx, "error loading attachment: %v", err)
- continue
- }
-
- // Partially loaded. Keep as
- // placeholder and try again later.
+ // A non-fatal error occurred during loading.
log.Warnf(ctx, "partially loaded attachment: %v", err)
}
diff --git a/internal/federation/dereferencing/util.go b/internal/federation/dereferencing/util.go
index 38622f6c1..5cb7a0106 100644
--- a/internal/federation/dereferencing/util.go
+++ b/internal/federation/dereferencing/util.go
@@ -18,11 +18,122 @@
package dereferencing
import (
+ "context"
+ "io"
+ "net/url"
"slices"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/media"
+ "github.com/superseriousbusiness/gotosocial/internal/transport"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
)
+// loadAttachment handles the case of a new media attachment
+// that requires loading. it stores and caches from given data.
+func (d *Dereferencer) loadAttachment(
+ ctx context.Context,
+ tsport transport.Transport,
+ accountID string, // media account owner
+ remoteURL string,
+ info *media.AdditionalMediaInfo,
+) (
+ *gtsmodel.MediaAttachment,
+ error,
+) {
+ // Parse str as valid URL object.
+ url, err := url.Parse(remoteURL)
+ if err != nil {
+ return nil, gtserror.Newf("invalid remote media url %q: %v", remoteURL, err)
+ }
+
+ // Start pre-processing remote media at remote URL.
+ processing := d.mediaManager.PreProcessMedia(
+ func(ctx context.Context) (io.ReadCloser, int64, error) {
+ return tsport.DereferenceMedia(ctx, url)
+ },
+ accountID,
+ info,
+ )
+
+ // Force attachment loading *right now*.
+ return processing.LoadAttachment(ctx)
+}
+
+// updateAttachment handles the case of an existing media attachment
+// that *may* have changes or need recaching. it checks for changed
+// fields, updating in the database if so, and recaches uncached media.
+func (d *Dereferencer) updateAttachment(
+ ctx context.Context,
+ tsport transport.Transport,
+ existing *gtsmodel.MediaAttachment, // existing attachment
+ media *gtsmodel.MediaAttachment, // (optional) changed media
+) (
+ *gtsmodel.MediaAttachment, // always set
+ error,
+) {
+ if media != nil {
+ // Possible changed media columns.
+ changed := make([]string, 0, 3)
+
+ // Check if attachment description has changed.
+ if existing.Description != media.Description {
+ changed = append(changed, "description")
+ existing.Description = media.Description
+ }
+
+ // Check if attachment blurhash has changed (i.e. content change).
+ if existing.Blurhash != media.Blurhash && media.Blurhash != "" {
+ changed = append(changed, "blurhash", "cached")
+ existing.Blurhash = media.Blurhash
+ existing.Cached = util.Ptr(false)
+ }
+
+ if len(changed) > 0 {
+ // Update the existing attachment model in the database.
+ err := d.state.DB.UpdateAttachment(ctx, existing, changed...)
+ if err != nil {
+ return media, gtserror.Newf("error updating media: %w", err)
+ }
+ }
+ }
+
+ // Check if cached.
+ if *existing.Cached {
+ return existing, nil
+ }
+
+ // Parse str as valid URL object.
+ url, err := url.Parse(existing.RemoteURL)
+ if err != nil {
+ return nil, gtserror.Newf("invalid remote media url %q: %v", media.RemoteURL, err)
+ }
+
+ // Start pre-processing remote media recaching from remote.
+ processing, err := d.mediaManager.PreProcessMediaRecache(
+ ctx,
+ func(ctx context.Context) (io.ReadCloser, int64, error) {
+ return tsport.DereferenceMedia(ctx, url)
+ },
+ existing.ID,
+ )
+ if err != nil {
+ return nil, gtserror.Newf("error processing recache: %w", err)
+ }
+
+ // Force load attachment recache *right now*.
+ recached, err := processing.LoadAttachment(ctx)
+
+ // Always return the error we
+ // receive, but ensure we return
+ // most up-to-date media file.
+ if recached != nil {
+ return recached, err
+ }
+ return existing, err
+}
+
// pollChanged returns whether a poll has changed in way that
// indicates that this should be an entirely new poll. i.e. if
// the available options have changed, or the expiry has increased.