diff options
| author | 2023-02-09 09:27:07 +0100 | |
|---|---|---|
| committer | 2023-02-09 09:27:07 +0100 | |
| commit | 95715f9251da56bfb7a8c7c48ca5bf38d2392f03 (patch) | |
| tree | 80d173e530865fedb9d2cef96814d11f9df9539f | |
| parent | [chore] Fix report username wrapping (#1464) (diff) | |
| download | gotosocial-95715f9251da56bfb7a8c7c48ca5bf38d2392f03.tar.xz | |
[performance] Don't fetch avatar + header if uri hasn't changed (#1463)
| -rw-r--r-- | internal/federation/dereferencing/account.go | 225 | ||||
| -rw-r--r-- | internal/federation/dereferencing/finger.go | 11 | 
2 files changed, 73 insertions, 163 deletions
diff --git a/internal/federation/dereferencing/account.go b/internal/federation/dereferencing/account.go index be2ec9cee..ceb7820dc 100644 --- a/internal/federation/dereferencing/account.go +++ b/internal/federation/dereferencing/account.go @@ -36,6 +36,7 @@ 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"  )  func (d *deref) GetAccountByURI(ctx context.Context, requestUser string, uri *url.URL, block bool) (*gtsmodel.Account, error) { @@ -145,9 +146,14 @@ func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.  		}  	} +	transport, err := d.transportController.NewTransportForUsername(ctx, requestUser) +	if err != nil { +		return nil, fmt.Errorf("enrichAccount: couldn't create transport: %w", err) +	} +  	if account.Username != "" {  		// A username was provided so we can attempt a webfinger, this ensures up-to-date accountdomain info. -		accDomain, accURI, err := d.fingerRemoteAccount(ctx, requestUser, account.Username, account.Domain) +		accDomain, accURI, err := d.fingerRemoteAccount(ctx, transport, account.Username, account.Domain)  		if err != nil && account.URI == "" {  			// this is a new account (to us) with username@domain but failed @@ -185,7 +191,7 @@ func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.  	defer d.stopHandshake(requestUser, uri)  	// Dereference this account to get the latest available. -	apubAcc, err := d.dereferenceAccountable(ctx, requestUser, uri) +	apubAcc, err := d.dereferenceAccountable(ctx, transport, uri)  	if err != nil {  		return nil, fmt.Errorf("enrichAccount: error dereferencing account %s: %w", uri, err)  	} @@ -202,7 +208,7 @@ func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.  		// No username was provided, so no webfinger was attempted earlier.  		//  		// Now we have a username we can attempt it now, this ensures up-to-date accountdomain info. -		accDomain, _, err := d.fingerRemoteAccount(ctx, requestUser, latestAcc.Username, uri.Host) +		accDomain, _, err := d.fingerRemoteAccount(ctx, transport, latestAcc.Username, uri.Host)  		if err == nil {  			// Update account with latest info. @@ -214,9 +220,32 @@ func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.  	latestAcc.ID = account.ID  	latestAcc.FetchedAt = time.Now() -	// Fetch latest account media (TODO: check for changed URI to previous). -	if err = d.fetchRemoteAccountMedia(ctx, latestAcc, requestUser, block); err != nil { -		log.Errorf("error fetching remote media for account %s: %v", uri, err) +	// Fetch latest account avatar only if remote URI has changed +	if latestAcc.AvatarRemoteURL != "" && latestAcc.AvatarRemoteURL != account.AvatarRemoteURL { +		d.dereferencingAvatarsLock.Lock() +		newAvatarID, err := d.fetchRemoteAccountMedia(ctx, transport, latestAcc.AvatarRemoteURL, latestAcc.ID, d.dereferencingAvatars, true, false) +		d.dereferencingAvatarsLock.Unlock() +		if err != nil { +			log.Errorf("error fetching remote avatar for account %s: %v", uri, err) +		} else { +			latestAcc.AvatarMediaAttachmentID = newAvatarID +		} +	} else { +		latestAcc.AvatarMediaAttachmentID = account.AvatarMediaAttachmentID // no change / empty url +	} + +	// Fetch latest account header only if remote URI has changed +	if latestAcc.AvatarRemoteURL != "" && latestAcc.AvatarRemoteURL != account.AvatarRemoteURL { +		d.dereferencingHeadersLock.Lock() +		newHeaderID, err := d.fetchRemoteAccountMedia(ctx, transport, latestAcc.HeaderRemoteURL, latestAcc.ID, d.dereferencingHeaders, false, true) +		d.dereferencingHeadersLock.Unlock() +		if err != nil { +			log.Errorf("error fetching remote header for account %s: %v", uri, err) +		} else { +			latestAcc.HeaderMediaAttachmentID = newHeaderID +		} +	} else { +		latestAcc.HeaderMediaAttachmentID = account.HeaderMediaAttachmentID // no change / empty url  	}  	// Fetch the latest remote account emoji IDs used in account display name/bio. @@ -258,12 +287,7 @@ func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.  // it finds as something that an account model can be constructed out of.  //  // Will work for Person, Application, or Service models. -func (d *deref) dereferenceAccountable(ctx context.Context, username string, remoteAccountID *url.URL) (ap.Accountable, error) { -	transport, err := d.transportController.NewTransportForUsername(ctx, username) -	if err != nil { -		return nil, fmt.Errorf("DereferenceAccountable: transport err: %w", err) -	} - +func (d *deref) dereferenceAccountable(ctx context.Context, transport transport.Transport, remoteAccountID *url.URL) (ap.Accountable, error) {  	b, err := transport.Dereference(ctx, remoteAccountID)  	if err != nil {  		return nil, fmt.Errorf("DereferenceAccountable: error deferencing %s: %w", remoteAccountID.String(), err) @@ -279,7 +303,7 @@ func (d *deref) dereferenceAccountable(ctx context.Context, username string, rem  		return nil, fmt.Errorf("DereferenceAccountable: error resolving json into ap vocab type: %w", err)  	} -	//nolint shutup linter +	//nolint:forcetypeassert  	switch t.GetTypeName() {  	case ap.ActorApplication:  		return t.(vocab.ActivityStreamsApplication), nil @@ -296,156 +320,47 @@ func (d *deref) dereferenceAccountable(ctx context.Context, username string, rem  	return nil, newErrWrongType(fmt.Errorf("DereferenceAccountable: type name %s not supported as Accountable", t.GetTypeName()))  } -// fetchRemoteAccountMedia fetches and stores the header and avatar for a remote account, -// using a transport on behalf of requestingUsername. -// -// The returned boolean indicates whether anything changed -- in other words, whether the -// account should be updated in the database. -// -// targetAccount's AvatarMediaAttachmentID and HeaderMediaAttachmentID will be updated as necessary. -// -// If refresh is true, then the media will be fetched again even if it's already been fetched before. -// -// If blocking is true, then the calls to the media manager made by this function will be blocking: -// in other words, the function won't return until the header and the avatar have been fully processed. -func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsmodel.Account, requestingUsername string, blocking bool) error { -	// Fetch a transport beforehand for either(or both) avatar / header dereferencing. -	tsport, err := d.transportController.NewTransportForUsername(ctx, requestingUsername) +func (d *deref) fetchRemoteAccountMedia( +	ctx context.Context, +	transport transport.Transport, +	mediaRemoteURL string, +	targetAccountID string, +	dereferencingMap map[string]*media.ProcessingMedia, +	avatar bool, +	header bool, +) (string, error) { +	// first check if we're already processing this media +	if alreadyProcessing, ok := dereferencingMap[targetAccountID]; ok { +		// we're already on it, nothing else to do +		return alreadyProcessing.AttachmentID(), nil +	} + +	avatarIRI, err := url.Parse(mediaRemoteURL)  	if err != nil { -		return fmt.Errorf("fetchRemoteAccountMedia: error getting transport for user: %s", err) +		return "", err  	} -	if targetAccount.AvatarRemoteURL != "" { -		var processingMedia *media.ProcessingMedia - -		// Parse the target account's avatar URL into URL object. -		avatarIRI, err := url.Parse(targetAccount.AvatarRemoteURL) -		if err != nil { -			return err -		} - -		d.dereferencingAvatarsLock.Lock() // LOCK HERE -		// first check if we're already processing this media -		if alreadyProcessing, ok := d.dereferencingAvatars[targetAccount.ID]; ok { -			// we're already on it, no worries -			processingMedia = alreadyProcessing -		} else { -			data := func(innerCtx context.Context) (io.ReadCloser, int64, error) { -				return tsport.DereferenceMedia(innerCtx, avatarIRI) -			} - -			avatar := true -			newProcessing, err := d.mediaManager.ProcessMedia(ctx, data, nil, targetAccount.ID, &media.AdditionalMediaInfo{ -				RemoteURL: &targetAccount.AvatarRemoteURL, -				Avatar:    &avatar, -			}) -			if err != nil { -				d.dereferencingAvatarsLock.Unlock() -				return err -			} - -			// store it in our map to indicate it's in process -			d.dereferencingAvatars[targetAccount.ID] = newProcessing -			processingMedia = newProcessing -		} -		d.dereferencingAvatarsLock.Unlock() // UNLOCK HERE - -		load := func(innerCtx context.Context) error { -			_, err := processingMedia.LoadAttachment(innerCtx) -			return err -		} - -		cleanup := func() { -			d.dereferencingAvatarsLock.Lock() -			delete(d.dereferencingAvatars, targetAccount.ID) -			d.dereferencingAvatarsLock.Unlock() -		} - -		// block until loaded if required... -		if blocking { -			if err := loadAndCleanup(ctx, load, cleanup); err != nil { -				return err -			} -		} else { -			// ...otherwise do it async -			go func() { -				dlCtx, done := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute)) -				if err := loadAndCleanup(dlCtx, load, cleanup); err != nil { -					log.Errorf("fetchRemoteAccountMedia: error during async lock and load of avatar: %s", err) -				} -				done() -			}() -		} - -		targetAccount.AvatarMediaAttachmentID = processingMedia.AttachmentID() +	data := func(innerCtx context.Context) (io.ReadCloser, int64, error) { +		return transport.DereferenceMedia(innerCtx, avatarIRI)  	} -	if targetAccount.HeaderRemoteURL != "" { -		var processingMedia *media.ProcessingMedia - -		// Parse the target account's header URL into URL object. -		headerIRI, err := url.Parse(targetAccount.HeaderRemoteURL) -		if err != nil { -			return err -		} - -		d.dereferencingHeadersLock.Lock() // LOCK HERE -		// first check if we're already processing this media -		if alreadyProcessing, ok := d.dereferencingHeaders[targetAccount.ID]; ok { -			// we're already on it, no worries -			processingMedia = alreadyProcessing -		} else { -			data := func(innerCtx context.Context) (io.ReadCloser, int64, error) { -				return tsport.DereferenceMedia(innerCtx, headerIRI) -			} - -			header := true -			newProcessing, err := d.mediaManager.ProcessMedia(ctx, data, nil, targetAccount.ID, &media.AdditionalMediaInfo{ -				RemoteURL: &targetAccount.HeaderRemoteURL, -				Header:    &header, -			}) -			if err != nil { -				d.dereferencingAvatarsLock.Unlock() -				return err -			} - -			// store it in our map to indicate it's in process -			d.dereferencingHeaders[targetAccount.ID] = newProcessing -			processingMedia = newProcessing -		} -		d.dereferencingHeadersLock.Unlock() // UNLOCK HERE - -		load := func(innerCtx context.Context) error { -			_, err := processingMedia.LoadAttachment(innerCtx) -			return err -		} - -		cleanup := func() { -			d.dereferencingHeadersLock.Lock() -			delete(d.dereferencingHeaders, targetAccount.ID) -			d.dereferencingHeadersLock.Unlock() -		} - -		// block until loaded if required... -		if blocking { -			if err := loadAndCleanup(ctx, load, cleanup); err != nil { -				return err -			} -		} else { -			// ...otherwise do it async -			go func() { -				dlCtx, done := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute)) -				if err := loadAndCleanup(dlCtx, load, cleanup); err != nil { -					log.Errorf("fetchRemoteAccountMedia: error during async lock and load of header: %s", err) -				} -				done() -			}() -		} +	processingMedia, err := d.mediaManager.ProcessMedia(ctx, data, nil, targetAccountID, &media.AdditionalMediaInfo{ +		RemoteURL: &mediaRemoteURL, +		Avatar:    &avatar, +		Header:    &header, +	}) +	if err != nil { +		return "", err +	} -		targetAccount.HeaderMediaAttachmentID = processingMedia.AttachmentID() +	// store it in our map to indicate it's in process +	dereferencingMap[targetAccountID] = processingMedia +	defer delete(dereferencingMap, targetAccountID) +	if _, err := processingMedia.LoadAttachment(ctx); err != nil { +		return "", err  	} -	return nil +	return processingMedia.AttachmentID(), nil  }  func (d *deref) fetchRemoteAccountEmojis(ctx context.Context, targetAccount *gtsmodel.Account, requestingUsername string) (bool, error) { diff --git a/internal/federation/dereferencing/finger.go b/internal/federation/dereferencing/finger.go index 03eecf1bb..6950157f1 100644 --- a/internal/federation/dereferencing/finger.go +++ b/internal/federation/dereferencing/finger.go @@ -27,17 +27,12 @@ import (  	"strings"  	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/transport"  	"github.com/superseriousbusiness/gotosocial/internal/util"  ) -func (d *deref) fingerRemoteAccount(ctx context.Context, username string, targetUsername string, targetHost string) (accountDomain string, accountURI *url.URL, err error) { -	t, err := d.transportController.NewTransportForUsername(ctx, username) -	if err != nil { -		err = fmt.Errorf("fingerRemoteAccount: error getting transport for %s: %s", username, err) -		return -	} - -	b, err := t.Finger(ctx, targetUsername, targetHost) +func (d *deref) fingerRemoteAccount(ctx context.Context, transport transport.Transport, targetUsername string, targetHost string) (accountDomain string, accountURI *url.URL, err error) { +	b, err := transport.Finger(ctx, targetUsername, targetHost)  	if err != nil {  		err = fmt.Errorf("fingerRemoteAccount: error fingering @%s@%s: %s", targetUsername, targetHost, err)  		return  | 
