diff options
Diffstat (limited to 'internal/federation')
-rw-r--r-- | internal/federation/dereferencing/account.go | 104 | ||||
-rw-r--r-- | internal/federation/dereferencing/dereferencer.go | 38 | ||||
-rw-r--r-- | internal/federation/dereferencing/status.go | 60 |
3 files changed, 137 insertions, 65 deletions
diff --git a/internal/federation/dereferencing/account.go b/internal/federation/dereferencing/account.go index 86ea9b7fd..7eec6b5b9 100644 --- a/internal/federation/dereferencing/account.go +++ b/internal/federation/dereferencing/account.go @@ -40,41 +40,55 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/util" ) -// accountUpToDate returns whether the given account model is both updateable (i.e. -// non-instance remote account) and whether it needs an update based on `fetched_at`. -func accountUpToDate(account *gtsmodel.Account, force bool) bool { - if !account.SuspendedAt.IsZero() { - // Can't update suspended accounts. - return true +// accountFresh returns true if the given account is +// still considered "fresh" according to the desired +// freshness window (falls back to default if nil). +// +// Local accounts will always be considered fresh because +// there's no remote state that could have changed. +// +// True is also returned for suspended accounts, since +// we'll never want to try to refresh one of these. +// +// Return value of false indicates that the account +// is not fresh and should be refreshed from remote. +func accountFresh( + account *gtsmodel.Account, + window *FreshnessWindow, +) bool { + if window == nil { + window = DefaultAccountFreshness } if account.IsLocal() { - // Can't update local accounts. + // Can't refresh + // local accounts. return true } - if account.IsInstance() && !account.IsNew() { - // Existing instance account. No need for update. + if !account.SuspendedAt.IsZero() { + // Can't refresh + // suspended accounts. return true } - // Default limit we allow - // statuses to be refreshed. - limit := 6 * time.Hour - - if force { - // We specifically allow the force flag - // to force an early refresh (on a much - // smaller cooldown period). - limit = 5 * time.Minute - } - - // If account was updated recently (within limit), we return as-is. - if next := account.FetchedAt.Add(limit); time.Now().Before(next) { + if account.IsInstance() && + !account.IsNew() { + // Existing instance account. + // No need for refresh. return true } - return false + // Moment when the account is + // considered stale according to + // desired freshness window. + staleAt := account.FetchedAt.Add( + time.Duration(*window), + ) + + // It's still fresh if the time now + // is not past the point of staleness. + return !time.Now().After(staleAt) } // GetAccountByURI will attempt to fetch an accounts by its URI, first checking the database. In the case of a newly-met remote model, or a remote model @@ -146,7 +160,7 @@ func (d *Dereferencer) getAccountByURI(ctx context.Context, requestUser string, }, nil) } - if accountUpToDate(account, false) { + if accountFresh(account, nil) { // This is an existing account that is up-to-date, // before returning ensure it is fully populated. if err := d.state.DB.PopulateAccount(ctx, account); err != nil { @@ -248,7 +262,7 @@ func (d *Dereferencer) getAccountByUsernameDomain( requestUser, account, nil, - false, + nil, ) if err != nil { // Fallback to existing. @@ -265,22 +279,28 @@ func (d *Dereferencer) getAccountByUsernameDomain( return latest, accountable, nil } -// RefreshAccount updates the given account if remote and last_fetched is -// beyond fetch interval, or if force is set. An updated account model is -// returned, but in the case of dereferencing, some low-priority account info -// may be enqueued for asynchronous fetching, e.g. featured account statuses (pins). -// An ActivityPub object indicates the account was dereferenced (i.e. updated). +// RefreshAccount updates the given account if it's a +// remote account, and considered stale / not fresh +// based on Account.FetchedAt and desired freshness. +// +// An updated account model is returned, but in the +// case of dereferencing, some low-priority account +// info may be enqueued for asynchronous fetching, +// e.g. featured account statuses (pins). +// +// An ActivityPub object indicates the account was +// dereferenced (i.e. updated). func (d *Dereferencer) RefreshAccount( ctx context.Context, requestUser string, account *gtsmodel.Account, accountable ap.Accountable, - force bool, + window *FreshnessWindow, ) (*gtsmodel.Account, ap.Accountable, error) { // If no incoming data is provided, - // check whether account needs update. + // check whether account needs refresh. if accountable == nil && - accountUpToDate(account, force) { + accountFresh(account, window) { return account, nil, nil } @@ -314,21 +334,25 @@ func (d *Dereferencer) RefreshAccount( return latest, accountable, nil } -// RefreshAccountAsync enqueues the given account for an asychronous -// update fetching, if last_fetched is beyond fetch interval, or if force -// is set. This is a more optimized form of manually enqueueing .UpdateAccount() -// to the federation worker, since it only enqueues update if necessary. +// RefreshAccountAsync enqueues the given account for +// an asychronous update fetching, if it's a remote +// account, and considered stale / not fresh based on +// Account.FetchedAt and desired freshness. +// +// This is a more optimized form of manually enqueueing +// .UpdateAccount() to the federation worker, since it +// only enqueues update if necessary. func (d *Dereferencer) RefreshAccountAsync( ctx context.Context, requestUser string, account *gtsmodel.Account, accountable ap.Accountable, - force bool, + window *FreshnessWindow, ) { // If no incoming data is provided, - // check whether account needs update. + // check whether account needs refresh. if accountable == nil && - accountUpToDate(account, force) { + accountFresh(account, window) { return } diff --git a/internal/federation/dereferencing/dereferencer.go b/internal/federation/dereferencing/dereferencer.go index 5bd16c1e0..f4596935e 100644 --- a/internal/federation/dereferencing/dereferencer.go +++ b/internal/federation/dereferencing/dereferencer.go @@ -20,11 +20,49 @@ package dereferencing import ( "net/url" "sync" + "time" "github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/state" "github.com/superseriousbusiness/gotosocial/internal/transport" "github.com/superseriousbusiness/gotosocial/internal/typeutils" + "github.com/superseriousbusiness/gotosocial/internal/util" +) + +// FreshnessWindow represents a duration in which a +// Status or Account is still considered to be "fresh" +// (ie., not in need of a refresh from remote), if its +// last FetchedAt value falls within the window. +// +// For example, if an Account was FetchedAt 09:00, and it +// is now 12:00, then it would be considered "fresh" +// according to DefaultAccountFreshness, but not according +// to Fresh, which would indicate that the Account requires +// refreshing from remote. +type FreshnessWindow time.Duration + +var ( + // 6 hours. + // + // Default window for doing a + // fresh dereference of an Account. + DefaultAccountFreshness = util.Ptr(FreshnessWindow(6 * time.Hour)) + + // 2 hours. + // + // Default window for doing a + // fresh dereference of a Status. + DefaultStatusFreshness = util.Ptr(FreshnessWindow(2 * time.Hour)) + + // 5 minutes. + // + // Fresh is useful when you're wanting + // a more up-to-date model of something + // that exceeds default freshness windows. + // + // This is tuned to be quite fresh without + // causing loads of dereferencing calls. + Fresh = util.Ptr(FreshnessWindow(5 * time.Minute)) ) // Dereferencer wraps logic and functionality for doing dereferencing diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go index 71cc5c530..23c6e98c8 100644 --- a/internal/federation/dereferencing/status.go +++ b/internal/federation/dereferencing/status.go @@ -38,31 +38,41 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/util" ) -// statusUpToDate returns whether the given status model is both updateable -// (i.e. remote status) and whether it needs an update based on `fetched_at`. -func statusUpToDate(status *gtsmodel.Status, force bool) bool { - if status.Local != nil && *status.Local { - // Can't update local statuses. - return true - } - - // Default limit we allow - // statuses to be refreshed. - limit := 2 * time.Hour - - if force { - // We specifically allow the force flag - // to force an early refresh (on a much - // smaller cooldown period). - limit = 5 * time.Minute +// statusFresh returns true if the given status is still +// considered "fresh" according to the desired freshness +// window (falls back to default status freshness if nil). +// +// Local statuses will always be considered fresh, +// because there's no remote state that may have changed. +// +// Return value of false indicates that the status +// is not fresh and should be refreshed from remote. +func statusFresh( + status *gtsmodel.Status, + window *FreshnessWindow, +) bool { + // Take default if no + // freshness window preferred. + if window == nil { + window = DefaultStatusFreshness } - // If this status was updated recently (within limit), return as-is. - if next := status.FetchedAt.Add(limit); time.Now().Before(next) { + if status.IsLocal() { + // Can't refresh + // local statuses. return true } - return false + // Moment when the status is + // considered stale according to + // desired freshness window. + staleAt := status.FetchedAt.Add( + time.Duration(*window), + ) + + // It's still fresh if the time now + // is not past the point of staleness. + return !time.Now().After(staleAt) } // GetStatusByURI will attempt to fetch a status by its URI, first checking the database. In the case of a newly-met remote model, or a remote model whose 'last_fetched' date @@ -146,7 +156,7 @@ func (d *Dereferencer) getStatusByURI(ctx context.Context, requestUser string, u }, nil) } - if statusUpToDate(status, false) { + if statusFresh(status, DefaultStatusFreshness) { // This is an existing status that is up-to-date, // before returning ensure it is fully populated. if err := d.state.DB.PopulateStatus(ctx, status); err != nil { @@ -181,12 +191,12 @@ func (d *Dereferencer) RefreshStatus( requestUser string, status *gtsmodel.Status, statusable ap.Statusable, - force bool, + window *FreshnessWindow, ) (*gtsmodel.Status, ap.Statusable, error) { // If no incoming data is provided, // check whether status needs update. if statusable == nil && - statusUpToDate(status, force) { + statusFresh(status, window) { return status, nil, nil } @@ -228,12 +238,12 @@ func (d *Dereferencer) RefreshStatusAsync( requestUser string, status *gtsmodel.Status, statusable ap.Statusable, - force bool, + window *FreshnessWindow, ) { // If no incoming data is provided, // check whether status needs update. if statusable == nil && - statusUpToDate(status, force) { + statusFresh(status, window) { return } |