mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-02 07:42:24 -06:00
simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
This commit is contained in:
parent
f8af213259
commit
53817c23fd
12 changed files with 311 additions and 279 deletions
|
|
@ -38,22 +38,6 @@ func (p *Processor) HomeTimelineGet(
|
|||
*apimodel.PageableResponse,
|
||||
gtserror.WithCode,
|
||||
) {
|
||||
var pageQuery url.Values
|
||||
var postFilter func(*gtsmodel.Status) (bool, error)
|
||||
|
||||
if local {
|
||||
// Set local = true query.
|
||||
pageQuery = localOnlyTrue
|
||||
|
||||
// Remove any non-local statuses if local-only requested.
|
||||
postFilter = func(s *gtsmodel.Status) (bool, error) {
|
||||
return !*s.Local, nil
|
||||
}
|
||||
} else {
|
||||
// Set local = false query.
|
||||
pageQuery = localOnlyFalse
|
||||
}
|
||||
|
||||
return p.getStatusTimeline(ctx,
|
||||
|
||||
// Auth'd
|
||||
|
|
@ -74,7 +58,19 @@ func (p *Processor) HomeTimelineGet(
|
|||
// page query flag, (this map
|
||||
// later gets copied before
|
||||
// any further usage).
|
||||
pageQuery,
|
||||
func() url.Values {
|
||||
var pageQuery url.Values
|
||||
|
||||
if local {
|
||||
// Set local = true query.
|
||||
pageQuery = localOnlyTrue
|
||||
} else {
|
||||
// Set local = false query.
|
||||
pageQuery = localOnlyFalse
|
||||
}
|
||||
|
||||
return pageQuery
|
||||
}(),
|
||||
|
||||
// Status filter context.
|
||||
statusfilter.FilterContextHome,
|
||||
|
|
@ -92,9 +88,5 @@ func (p *Processor) HomeTimelineGet(
|
|||
ok, err := p.visFilter.StatusHomeTimelineable(ctx, requester, s)
|
||||
return !ok, err
|
||||
},
|
||||
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
postFilter,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ func (p *Processor) ListTimelineGet(
|
|||
return p.state.DB.GetListTimeline(ctx, listID, pg)
|
||||
},
|
||||
|
||||
// Pre-filtering function,
|
||||
// Filtering function,
|
||||
// i.e. filter before caching.
|
||||
func(s *gtsmodel.Status) (bool, error) {
|
||||
|
||||
|
|
@ -101,9 +101,5 @@ func (p *Processor) ListTimelineGet(
|
|||
ok, err := p.visFilter.StatusHomeTimelineable(ctx, requester, s)
|
||||
return !ok, err
|
||||
},
|
||||
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,10 +89,6 @@ func (p *Processor) publicTimelineGet(
|
|||
ok, err := p.visFilter.StatusPublicTimelineable(ctx, requester, s)
|
||||
return !ok, err
|
||||
},
|
||||
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +130,7 @@ func (p *Processor) localTimelineGet(
|
|||
return p.state.DB.GetLocalTimeline(ctx, pg)
|
||||
},
|
||||
|
||||
// Pre-filtering function,
|
||||
// Filtering function,
|
||||
// i.e. filter before caching.
|
||||
func(s *gtsmodel.Status) (bool, error) {
|
||||
|
||||
|
|
@ -142,9 +138,5 @@ func (p *Processor) localTimelineGet(
|
|||
ok, err := p.visFilter.StatusPublicTimelineable(ctx, requester, s)
|
||||
return !ok, err
|
||||
},
|
||||
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,8 +70,7 @@ func (p *Processor) getStatusTimeline(
|
|||
pageQuery url.Values,
|
||||
filterCtx statusfilter.FilterContext,
|
||||
loadPage func(*paging.Page) (statuses []*gtsmodel.Status, err error),
|
||||
preFilter func(*gtsmodel.Status) (bool, error),
|
||||
postFilter func(*gtsmodel.Status) (bool, error),
|
||||
filter func(*gtsmodel.Status) (bool, error),
|
||||
) (
|
||||
*apimodel.PageableResponse,
|
||||
gtserror.WithCode,
|
||||
|
|
@ -128,13 +127,9 @@ func (p *Processor) getStatusTimeline(
|
|||
return p.state.DB.GetStatusesByIDs(ctx, ids)
|
||||
},
|
||||
|
||||
// Pre-filtering function,
|
||||
// Filtering function,
|
||||
// i.e. filter before caching.
|
||||
preFilter,
|
||||
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
postFilter,
|
||||
filter,
|
||||
|
||||
// Frontend API model preparation function.
|
||||
func(status *gtsmodel.Status) (*apimodel.Status, error) {
|
||||
|
|
|
|||
|
|
@ -682,8 +682,15 @@ func (p *clientAPI) CreateBlock(ctx context.Context, cMsg *messages.FromClientAP
|
|||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Perform any necessary timeline invalidation.
|
||||
p.surface.invalidateTimelinesForBlock(ctx, block)
|
||||
if block.Account.IsLocal() {
|
||||
// Perform timeline invalidation for block origin account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, block.AccountID)
|
||||
}
|
||||
|
||||
if block.TargetAccount.IsLocal() {
|
||||
// Perform timeline invalidation for block target account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, block.TargetAccountID)
|
||||
}
|
||||
|
||||
// TODO: same with notifications?
|
||||
// TODO: same with bookmarks?
|
||||
|
|
@ -843,6 +850,16 @@ func (p *clientAPI) UndoFollow(ctx context.Context, cMsg *messages.FromClientAPI
|
|||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
if follow.Account.IsLocal() {
|
||||
// Perform timeline invalidation for block origin account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, follow.AccountID)
|
||||
}
|
||||
|
||||
if follow.TargetAccount.IsLocal() {
|
||||
// Perform timeline invalidation for block target account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, follow.TargetAccountID)
|
||||
}
|
||||
|
||||
if err := p.federate.UndoFollow(ctx, follow); err != nil {
|
||||
log.Errorf(ctx, "error federating follow undo: %v", err)
|
||||
}
|
||||
|
|
@ -856,6 +873,16 @@ func (p *clientAPI) UndoBlock(ctx context.Context, cMsg *messages.FromClientAPI)
|
|||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
if block.Account.IsLocal() {
|
||||
// Perform timeline invalidation for block origin account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, block.AccountID)
|
||||
}
|
||||
|
||||
if block.TargetAccount.IsLocal() {
|
||||
// Perform timeline invalidation for block target account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, block.TargetAccountID)
|
||||
}
|
||||
|
||||
if err := p.federate.UndoBlock(ctx, block); err != nil {
|
||||
log.Errorf(ctx, "error federating block undo: %v", err)
|
||||
}
|
||||
|
|
@ -1010,6 +1037,25 @@ func (p *clientAPI) DeleteAccountOrUser(ctx context.Context, cMsg *messages.From
|
|||
p.state.Workers.Federator.Queue.Delete("Receiving.ID", account.ID)
|
||||
p.state.Workers.Federator.Queue.Delete("TargetURI", account.URI)
|
||||
|
||||
// Remove any entries authored by account from timelines.
|
||||
p.surface.removeTimelineEntriesByAccount(account.ID)
|
||||
|
||||
// Remove any of their cached timelines.
|
||||
p.state.Caches.Timelines.Public.Delete(account.ID)
|
||||
p.state.Caches.Timelines.Home.Delete(account.ID)
|
||||
p.state.Caches.Timelines.Local.Delete(account.ID)
|
||||
|
||||
// Get the IDs of all the lists owned by the given account ID.
|
||||
listIDs, err := p.state.DB.GetListIDsByAccountID(ctx, account.ID)
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "error getting lists for account %s: %v", account.ID, err)
|
||||
}
|
||||
|
||||
// Remove list timelines of account.
|
||||
for _, listID := range listIDs {
|
||||
p.state.Caches.Timelines.List.Delete(listID)
|
||||
}
|
||||
|
||||
if err := p.federate.DeleteAccount(ctx, cMsg.Target); err != nil {
|
||||
log.Errorf(ctx, "error federating account delete: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,9 +197,22 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
|
|||
// UNDO SOMETHING
|
||||
case ap.ActivityUndo:
|
||||
|
||||
switch fMsg.APObjectType {
|
||||
// UNDO FOLLOW
|
||||
case ap.ActivityFollow:
|
||||
return p.fediAPI.UndoFollow(ctx, fMsg)
|
||||
|
||||
// UNDO BLOCK
|
||||
case ap.ActivityBlock:
|
||||
return p.fediAPI.UndoBlock(ctx, fMsg)
|
||||
|
||||
// UNDO ANNOUNCE
|
||||
if fMsg.APObjectType == ap.ActivityAnnounce {
|
||||
case ap.ActivityAnnounce:
|
||||
return p.fediAPI.UndoAnnounce(ctx, fMsg)
|
||||
|
||||
// UNDO LIKE
|
||||
case ap.ActivityLike:
|
||||
return p.fediAPI.UndoFave(ctx, fMsg)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -701,8 +714,15 @@ func (p *fediAPI) CreateBlock(ctx context.Context, fMsg *messages.FromFediAPI) e
|
|||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Perform any necessary timeline invalidation.
|
||||
p.surface.invalidateTimelinesForBlock(ctx, block)
|
||||
if block.Account.IsLocal() {
|
||||
// Perform timeline invalidation for block origin account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, block.AccountID)
|
||||
}
|
||||
|
||||
if block.TargetAccount.IsLocal() {
|
||||
// Perform timeline invalidation for block target account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, block.TargetAccountID)
|
||||
}
|
||||
|
||||
// Remove any follows that existed between blocker + blockee.
|
||||
// (note this handles removing any necessary list entries).
|
||||
|
|
@ -1054,6 +1074,9 @@ func (p *fediAPI) DeleteAccount(ctx context.Context, fMsg *messages.FromFediAPI)
|
|||
p.state.Workers.Federator.Queue.Delete("Requesting.ID", account.ID)
|
||||
p.state.Workers.Federator.Queue.Delete("TargetURI", account.URI)
|
||||
|
||||
// Remove any entries authored by account from timelines.
|
||||
p.surface.removeTimelineEntriesByAccount(account.ID)
|
||||
|
||||
// First perform the actual account deletion.
|
||||
if err := p.account.Delete(ctx, account, account.ID); err != nil {
|
||||
log.Errorf(ctx, "error deleting account: %v", err)
|
||||
|
|
@ -1172,6 +1195,44 @@ func (p *fediAPI) RejectAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) UndoFollow(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
follow, ok := fMsg.GTSModel.(*gtsmodel.Follow)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Follow", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
if follow.Account.IsLocal() {
|
||||
// Perform timeline invalidation for block origin account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, follow.AccountID)
|
||||
}
|
||||
|
||||
if follow.TargetAccount.IsLocal() {
|
||||
// Perform timeline invalidation for block target account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, follow.TargetAccountID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) UndoBlock(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
block, ok := fMsg.GTSModel.(*gtsmodel.Block)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
if block.Account.IsLocal() {
|
||||
// Perform timeline invalidation for block origin account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, block.AccountID)
|
||||
}
|
||||
|
||||
if block.TargetAccount.IsLocal() {
|
||||
// Perform timeline invalidation for block target account.
|
||||
p.surface.invalidateTimelinesForAccount(ctx, block.TargetAccountID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) UndoAnnounce(
|
||||
ctx context.Context,
|
||||
fMsg *messages.FromFediAPI,
|
||||
|
|
@ -1200,3 +1261,16 @@ func (p *fediAPI) UndoAnnounce(
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) UndoFave(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
statusFave, ok := fMsg.GTSModel.(*gtsmodel.StatusFave)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.StatusFave", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Interaction counts changed on the faved status;
|
||||
// uncache the prepared version from all timelines.
|
||||
p.surface.invalidateStatusFromTimelines(statusFave.StatusID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -524,27 +524,6 @@ func (s *Surface) tagFollowersForStatus(
|
|||
return visibleTagFollowerAccounts, errs.Combine()
|
||||
}
|
||||
|
||||
// deleteStatusFromTimelines completely removes the given status from all timelines.
|
||||
// It will also stream deletion of the status to all open streams.
|
||||
func (s *Surface) deleteStatusFromTimelines(ctx context.Context, statusID string) {
|
||||
s.State.Caches.Timelines.Public.RemoveByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.Local.RemoveByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.Home.RemoveByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.List.RemoveByStatusIDs(statusID)
|
||||
s.Stream.Delete(ctx, statusID)
|
||||
}
|
||||
|
||||
// invalidateStatusFromTimelines does cache invalidation on the given status by
|
||||
// unpreparing it from all timelines, forcing it to be prepared again (with updated
|
||||
// stats, boost counts, etc) next time it's fetched by the timeline owner. This goes
|
||||
// both for the status itself, and for any boosts of the status.
|
||||
func (s *Surface) invalidateStatusFromTimelines(statusID string) {
|
||||
s.State.Caches.Timelines.Public.UnprepareByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.Local.UnprepareByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.Home.UnprepareByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.List.UnprepareByStatusIDs(statusID)
|
||||
}
|
||||
|
||||
// timelineStatusUpdate looks up HOME and LIST timelines of accounts
|
||||
// that follow the the status author or tags and pushes edit messages into any
|
||||
// active streams.
|
||||
|
|
@ -822,56 +801,52 @@ func (s *Surface) timelineStatusUpdateForTagFollowers(
|
|||
return errs.Combine()
|
||||
}
|
||||
|
||||
// invalidateTimelinesForBlock ...
|
||||
func (s *Surface) invalidateTimelinesForBlock(ctx context.Context, block *gtsmodel.Block) {
|
||||
// deleteStatusFromTimelines completely removes the given status from all timelines.
|
||||
// It will also stream deletion of the status to all open streams.
|
||||
func (s *Surface) deleteStatusFromTimelines(ctx context.Context, statusID string) {
|
||||
s.State.Caches.Timelines.Public.RemoveByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.Local.RemoveByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.Home.RemoveByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.List.RemoveByStatusIDs(statusID)
|
||||
s.Stream.Delete(ctx, statusID)
|
||||
}
|
||||
|
||||
// Check if origin is local account,
|
||||
// i.e. has status timeline caches.
|
||||
if block.Account.IsLocal() {
|
||||
// invalidateStatusFromTimelines does cache invalidation on the given status by
|
||||
// unpreparing it from all timelines, forcing it to be prepared again (with updated
|
||||
// stats, boost counts, etc) next time it's fetched by the timeline owner. This goes
|
||||
// both for the status itself, and for any boosts of the status.
|
||||
func (s *Surface) invalidateStatusFromTimelines(statusID string) {
|
||||
s.State.Caches.Timelines.Public.UnprepareByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.Local.UnprepareByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.Home.UnprepareByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.List.UnprepareByStatusIDs(statusID)
|
||||
}
|
||||
|
||||
// Remove target's statuses
|
||||
// from origin's home timeline.
|
||||
s.State.Caches.Timelines.Home.
|
||||
MustGet(block.AccountID).
|
||||
RemoveByAccountIDs(block.TargetAccountID)
|
||||
// removeTimelineEntriesByAccount removes all cached timeline entries authored by account ID.
|
||||
func (s *Surface) removeTimelineEntriesByAccount(accountID string) {
|
||||
s.State.Caches.Timelines.Public.RemoveByAccountIDs(accountID)
|
||||
s.State.Caches.Timelines.Home.RemoveByAccountIDs(accountID)
|
||||
s.State.Caches.Timelines.Local.RemoveByAccountIDs(accountID)
|
||||
s.State.Caches.Timelines.List.RemoveByAccountIDs(accountID)
|
||||
}
|
||||
|
||||
// Get the IDs of any lists created by origin account.
|
||||
listIDs, err := s.State.DB.GetListIDsByAccountID(ctx, block.AccountID)
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "error getting account's list IDs for %s: %v", block.URI, err)
|
||||
}
|
||||
// invalidateTimelinesForAccount invalidates all timeline caches stored for given account ID.
|
||||
func (s *Surface) invalidateTimelinesForAccount(ctx context.Context, accountID string) {
|
||||
|
||||
// Remove target's statuses from
|
||||
// any of origin's list timelines.
|
||||
for _, listID := range listIDs {
|
||||
s.State.Caches.Timelines.List.
|
||||
MustGet(listID).
|
||||
RemoveByAccountIDs(block.TargetAccountID)
|
||||
}
|
||||
// There's a lot of visibility changes to caclculate for any
|
||||
// relationship change, so just clear all account's timelines.
|
||||
s.State.Caches.Timelines.Public.Clear(accountID)
|
||||
s.State.Caches.Timelines.Home.Clear(accountID)
|
||||
s.State.Caches.Timelines.Local.Clear(accountID)
|
||||
|
||||
// Get the IDs of all the lists owned by the given account ID.
|
||||
listIDs, err := s.State.DB.GetListIDsByAccountID(ctx, accountID)
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "error getting lists for account %s: %v", accountID, err)
|
||||
}
|
||||
|
||||
// Check if target is local account,
|
||||
// i.e. has status timeline caches.
|
||||
if block.TargetAccount.IsLocal() {
|
||||
|
||||
// Remove origin's statuses
|
||||
// from target's home timeline.
|
||||
s.State.Caches.Timelines.Home.
|
||||
MustGet(block.TargetAccountID).
|
||||
RemoveByAccountIDs(block.AccountID)
|
||||
|
||||
// Get the IDs of any lists created by target account.
|
||||
listIDs, err := s.State.DB.GetListIDsByAccountID(ctx, block.TargetAccountID)
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "error getting target account's list IDs for %s: %v", block.URI, err)
|
||||
}
|
||||
|
||||
// Remove origin's statuses from
|
||||
// any of target's list timelines.
|
||||
for _, listID := range listIDs {
|
||||
s.State.Caches.Timelines.List.
|
||||
MustGet(listID).
|
||||
RemoveByAccountIDs(block.AccountID)
|
||||
}
|
||||
// Clear list timelines of account.
|
||||
for _, listID := range listIDs {
|
||||
s.State.Caches.Timelines.List.Clear(listID)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue