mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-12 14:27:29 -06:00
more work integration new timeline code
This commit is contained in:
parent
49d9a008d9
commit
771fbe2d5e
14 changed files with 419 additions and 606 deletions
|
|
@ -306,25 +306,10 @@ func (p *Processor) InvalidateTimelinedStatus(ctx context.Context, accountID str
|
|||
return gtserror.Newf("db error getting lists for account %s: %w", accountID, err)
|
||||
}
|
||||
|
||||
// Start new log entry with
|
||||
// the above calling func's name.
|
||||
l := log.
|
||||
WithContext(ctx).
|
||||
WithField("caller", log.Caller(3)).
|
||||
WithField("accountID", accountID).
|
||||
WithField("statusID", statusID)
|
||||
|
||||
// Unprepare item from home + list timelines, just log
|
||||
// if something goes wrong since this is not a showstopper.
|
||||
|
||||
if err := p.state.Timelines.Home.UnprepareItem(ctx, accountID, statusID); err != nil {
|
||||
l.Errorf("error unpreparing item from home timeline: %v", err)
|
||||
}
|
||||
|
||||
// Unprepare item from home + list timelines.
|
||||
p.state.Caches.Timelines.Home.MustGet(accountID).UnprepareByStatusIDs(statusID)
|
||||
for _, list := range lists {
|
||||
if err := p.state.Timelines.List.UnprepareItem(ctx, list.ID, statusID); err != nil {
|
||||
l.Errorf("error unpreparing item from list timeline %s: %v", list.ID, err)
|
||||
}
|
||||
p.state.Caches.Timelines.List.MustGet(list.ID).UnprepareByStatusIDs(statusID)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -19,11 +19,9 @@ package timeline
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"net/url"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
|
|
@ -40,16 +38,14 @@ func (p *Processor) HomeTimelineGet(
|
|||
*apimodel.PageableResponse,
|
||||
gtserror.WithCode,
|
||||
) {
|
||||
|
||||
// Load timeline data.
|
||||
return p.getTimeline(ctx,
|
||||
return p.getStatusTimeline(ctx,
|
||||
|
||||
// Auth'd
|
||||
// account.
|
||||
requester,
|
||||
|
||||
// Home timeline cache for authorized account.
|
||||
p.state.Caches.Timelines.Home.Get(requester.ID),
|
||||
// Per-account home timeline cache.
|
||||
p.state.Caches.Timelines.Home.MustGet(requester.ID),
|
||||
|
||||
// Current
|
||||
// page.
|
||||
|
|
@ -58,70 +54,45 @@ func (p *Processor) HomeTimelineGet(
|
|||
// Home timeline endpoint.
|
||||
"/api/v1/timelines/home",
|
||||
|
||||
// No page
|
||||
// query.
|
||||
nil,
|
||||
// Set local-only timeline
|
||||
// page query flag, (this map
|
||||
// later gets copied before
|
||||
// any further usage).
|
||||
func() url.Values {
|
||||
if local {
|
||||
return localOnlyTrue
|
||||
}
|
||||
return localOnlyFalse
|
||||
}(),
|
||||
|
||||
// Status filter context.
|
||||
statusfilter.FilterContextHome,
|
||||
|
||||
// Timeline cache load function, used to further hydrate cache where necessary.
|
||||
func(page *paging.Page) (statuses []*gtsmodel.Status, next *paging.Page, err error) {
|
||||
|
||||
// Fetch requesting account's home timeline page.
|
||||
statuses, err = p.state.DB.GetHomeTimeline(ctx,
|
||||
requester.ID,
|
||||
page,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, nil, gtserror.Newf("error getting statuses: %w", err)
|
||||
}
|
||||
|
||||
if len(statuses) == 0 {
|
||||
// No more to load.
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for next pg.
|
||||
lo := statuses[len(statuses)-1].ID
|
||||
hi := statuses[0].ID
|
||||
|
||||
// Set next paging value.
|
||||
page = page.Next(lo, hi)
|
||||
|
||||
for i := 0; i < len(statuses); {
|
||||
// Get status at idx.
|
||||
status := statuses[i]
|
||||
|
||||
// Check whether status should be show on home timeline.
|
||||
visible, err := p.visFilter.StatusHomeTimelineable(ctx,
|
||||
requester,
|
||||
status,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, gtserror.Newf("error checking visibility: %w", err)
|
||||
}
|
||||
|
||||
if !visible {
|
||||
// Status not visible to home timeline.
|
||||
statuses = slices.Delete(statuses, i, i+1)
|
||||
continue
|
||||
}
|
||||
|
||||
// Iter.
|
||||
i++
|
||||
}
|
||||
|
||||
return
|
||||
// Database load function.
|
||||
func(pg *paging.Page) (statuses []*gtsmodel.Status, err error) {
|
||||
return p.state.DB.GetHomeTimeline(ctx, requester.ID, pg)
|
||||
},
|
||||
|
||||
// Per-request filtering function.
|
||||
func(s *gtsmodel.Status) bool {
|
||||
if local {
|
||||
return !*s.Local
|
||||
// Pre-filtering function,
|
||||
// i.e. filter before caching.
|
||||
func(s *gtsmodel.Status) (bool, error) {
|
||||
|
||||
// Check the visibility of passed status to requesting user.
|
||||
ok, err := p.visFilter.StatusHomeTimelineable(ctx, requester, s)
|
||||
return !ok, err
|
||||
},
|
||||
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
func(s *gtsmodel.Status) (bool, error) {
|
||||
|
||||
// Remove any non-local statuses
|
||||
// if requester wants local-only.
|
||||
if local && !*s.Local {
|
||||
return true, nil
|
||||
}
|
||||
return false
|
||||
|
||||
return false, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ package timeline
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
|
@ -65,21 +64,21 @@ func (p *Processor) ListTimelineGet(
|
|||
return nil, gtserror.NewErrorNotFound(err)
|
||||
}
|
||||
|
||||
// Load timeline data.
|
||||
return p.getTimeline(ctx,
|
||||
// Fetch status timeline for list.
|
||||
return p.getStatusTimeline(ctx,
|
||||
|
||||
// Auth'd
|
||||
// account.
|
||||
requester,
|
||||
|
||||
// List timeline cache for list with ID.
|
||||
p.state.Caches.Timelines.List.Get(listID),
|
||||
// Per-account home timeline cache.
|
||||
p.state.Caches.Timelines.List.MustGet(requester.ID),
|
||||
|
||||
// Current
|
||||
// page.
|
||||
page,
|
||||
|
||||
// List timeline endpoint.
|
||||
// List timeline ID's endpoint.
|
||||
"/api/v1/timelines/list/"+listID,
|
||||
|
||||
// No page
|
||||
|
|
@ -89,59 +88,22 @@ func (p *Processor) ListTimelineGet(
|
|||
// Status filter context.
|
||||
statusfilter.FilterContextHome,
|
||||
|
||||
// Timeline cache load function, used to further hydrate cache where necessary.
|
||||
func(page *paging.Page) (statuses []*gtsmodel.Status, next *paging.Page, err error) {
|
||||
|
||||
// Fetch requesting account's list timeline page.
|
||||
statuses, err = p.state.DB.GetListTimeline(ctx,
|
||||
listID,
|
||||
page,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, nil, gtserror.Newf("error getting statuses: %w", err)
|
||||
}
|
||||
|
||||
if len(statuses) == 0 {
|
||||
// No more to load.
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for next pg.
|
||||
lo := statuses[len(statuses)-1].ID
|
||||
hi := statuses[0].ID
|
||||
|
||||
// Set next paging value.
|
||||
page = page.Next(lo, hi)
|
||||
|
||||
for i := 0; i < len(statuses); {
|
||||
// Get status at idx.
|
||||
status := statuses[i]
|
||||
|
||||
// Check whether status should be show on home timeline.
|
||||
visible, err := p.visFilter.StatusHomeTimelineable(ctx,
|
||||
requester,
|
||||
status,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, gtserror.Newf("error checking visibility: %w", err)
|
||||
}
|
||||
|
||||
if !visible {
|
||||
// Status not visible to home timeline.
|
||||
statuses = slices.Delete(statuses, i, i+1)
|
||||
continue
|
||||
}
|
||||
|
||||
// Iter.
|
||||
i++
|
||||
}
|
||||
|
||||
return
|
||||
// Database load function.
|
||||
func(pg *paging.Page) (statuses []*gtsmodel.Status, err error) {
|
||||
return p.state.DB.GetListTimeline(ctx, requester.ID, pg)
|
||||
},
|
||||
|
||||
// No furthering
|
||||
// filter function.
|
||||
// Pre-filtering function,
|
||||
// i.e. filter before caching.
|
||||
func(s *gtsmodel.Status) (bool, error) {
|
||||
|
||||
// Check the visibility of passed status to requesting user.
|
||||
ok, err := p.visFilter.StatusHomeTimelineable(ctx, requester, s)
|
||||
return !ok, err
|
||||
},
|
||||
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,9 @@ package timeline
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
|
|
@ -42,9 +38,7 @@ func (p *Processor) PublicTimelineGet(
|
|||
*apimodel.PageableResponse,
|
||||
gtserror.WithCode,
|
||||
) {
|
||||
|
||||
// Load timeline data.
|
||||
return p.getTimeline(ctx,
|
||||
return p.getStatusTimeline(ctx,
|
||||
|
||||
// Auth'd
|
||||
// account.
|
||||
|
|
@ -60,68 +54,42 @@ func (p *Processor) PublicTimelineGet(
|
|||
// Public timeline endpoint.
|
||||
"/api/v1/timelines/public",
|
||||
|
||||
// Set local-only timeline page query flag.
|
||||
url.Values{"local": {strconv.FormatBool(local)}},
|
||||
// Set local-only timeline
|
||||
// page query flag, (this map
|
||||
// later gets copied before
|
||||
// any further usage).
|
||||
func() url.Values {
|
||||
if local {
|
||||
return localOnlyTrue
|
||||
}
|
||||
return localOnlyFalse
|
||||
}(),
|
||||
|
||||
// Status filter context.
|
||||
statusfilter.FilterContextPublic,
|
||||
|
||||
// Timeline cache load function, used to further hydrate cache where necessary.
|
||||
func(page *paging.Page) (statuses []*gtsmodel.Status, next *paging.Page, err error) {
|
||||
|
||||
// Fetch the global public status timeline page.
|
||||
statuses, err = p.state.DB.GetPublicTimeline(ctx,
|
||||
page,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, nil, gtserror.Newf("error getting statuses: %w", err)
|
||||
}
|
||||
|
||||
if len(statuses) == 0 {
|
||||
// No more to load.
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for next pg.
|
||||
lo := statuses[len(statuses)-1].ID
|
||||
hi := statuses[0].ID
|
||||
|
||||
// Set next paging value.
|
||||
page = page.Next(lo, hi)
|
||||
|
||||
for i := 0; i < len(statuses); {
|
||||
// Get status at idx.
|
||||
status := statuses[i]
|
||||
|
||||
// Check whether status should be show on public timeline.
|
||||
visible, err := p.visFilter.StatusPublicTimelineable(ctx,
|
||||
requester,
|
||||
status,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, gtserror.Newf("error checking visibility: %w", err)
|
||||
}
|
||||
|
||||
if !visible {
|
||||
// Status not visible to home timeline.
|
||||
statuses = slices.Delete(statuses, i, i+1)
|
||||
continue
|
||||
}
|
||||
|
||||
// Iter.
|
||||
i++
|
||||
}
|
||||
|
||||
return
|
||||
// Database load function.
|
||||
func(pg *paging.Page) (statuses []*gtsmodel.Status, err error) {
|
||||
return p.state.DB.GetPublicTimeline(ctx, pg)
|
||||
},
|
||||
|
||||
// Per-request filtering function.
|
||||
func(s *gtsmodel.Status) bool {
|
||||
if local {
|
||||
return !*s.Local
|
||||
// Pre-filtering function,
|
||||
// i.e. filter before caching.
|
||||
nil,
|
||||
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
func(s *gtsmodel.Status) (bool, error) {
|
||||
|
||||
// Remove any non-local statuses
|
||||
// if requester wants local-only.
|
||||
if local && !*s.Local {
|
||||
return true, nil
|
||||
}
|
||||
return false
|
||||
|
||||
// Check the visibility of passed status to requesting user.
|
||||
ok, err := p.visFilter.StatusPublicTimelineable(ctx, requester, s)
|
||||
return !ok, err
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,10 @@ package timeline
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/cache"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/cache/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status"
|
||||
|
|
@ -32,10 +31,19 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
||||
)
|
||||
|
||||
var (
|
||||
// pre-prepared URL values to be passed in to
|
||||
// paging response forms. The paging package always
|
||||
// copies values before any modifications so it's
|
||||
// safe to only use a single map variable for these.
|
||||
localOnlyTrue = url.Values{"local": {"true"}}
|
||||
localOnlyFalse = url.Values{"local": {"false"}}
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
|
|
@ -62,7 +70,7 @@ func (p *Processor) getStatusTimeline(
|
|||
filterCtx statusfilter.FilterContext,
|
||||
loadPage func(*paging.Page) (statuses []*gtsmodel.Status, err error),
|
||||
preFilter func(*gtsmodel.Status) (bool, error),
|
||||
postFilter func(*timeline.StatusMeta) bool,
|
||||
postFilter func(*gtsmodel.Status) (bool, error),
|
||||
) (
|
||||
*apimodel.PageableResponse,
|
||||
gtserror.WithCode,
|
||||
|
|
@ -99,7 +107,8 @@ func (p *Processor) getStatusTimeline(
|
|||
}
|
||||
|
||||
// ...
|
||||
statuses, err := timeline.Load(ctx,
|
||||
apiStatuses, lo, hi, err := timeline.Load(ctx,
|
||||
|
||||
page,
|
||||
|
||||
// ...
|
||||
|
|
@ -110,10 +119,12 @@ func (p *Processor) getStatusTimeline(
|
|||
return p.state.DB.GetStatusesByIDs(ctx, ids)
|
||||
},
|
||||
|
||||
// ...
|
||||
// Pre-filtering function,
|
||||
// i.e. filter before caching.
|
||||
preFilter,
|
||||
|
||||
// ...
|
||||
// Post-filtering function,
|
||||
// i.e. filter after caching.
|
||||
postFilter,
|
||||
|
||||
// ...
|
||||
|
|
@ -132,192 +143,16 @@ func (p *Processor) getStatusTimeline(
|
|||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) getTimeline(
|
||||
ctx context.Context,
|
||||
requester *gtsmodel.Account,
|
||||
timeline *timeline.StatusTimeline,
|
||||
page *paging.Page,
|
||||
pgPath string, // timeline page path
|
||||
pgQuery url.Values, // timeline query parameters
|
||||
filterCtx statusfilter.FilterContext,
|
||||
load func(*paging.Page) (statuses []*gtsmodel.Status, next *paging.Page, err error), // timeline cache load function
|
||||
filter func(*gtsmodel.Status) bool, // per-request filtering function, done AFTER timeline caching
|
||||
) (
|
||||
*apimodel.PageableResponse,
|
||||
gtserror.WithCode,
|
||||
) {
|
||||
// Load timeline with cache / loader funcs.
|
||||
statuses, errWithCode := p.loadTimeline(ctx,
|
||||
timeline,
|
||||
page,
|
||||
load,
|
||||
filter,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
err := gtserror.Newf("error loading timeline: %w", err)
|
||||
return nil, gtserror.WrapWithCode(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
if len(statuses) == 0 {
|
||||
// Check for an empty timeline rsp.
|
||||
return paging.EmptyResponse(), nil
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo := statuses[len(statuses)-1].ID
|
||||
hi := statuses[0].ID
|
||||
|
||||
var (
|
||||
filters []*gtsmodel.Filter
|
||||
mutes *usermute.CompiledUserMuteList
|
||||
)
|
||||
|
||||
if requester != nil {
|
||||
var err error
|
||||
|
||||
// Fetch all filters relevant for requesting account.
|
||||
filters, err = p.state.DB.GetFiltersForAccountID(ctx,
|
||||
requester.ID,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("error getting account filters: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Get a list of all account mutes for requester.
|
||||
allMutes, err := p.state.DB.GetAccountMutes(ctx,
|
||||
requester.ID,
|
||||
nil, // nil page, i.e. all
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("error getting account mutes: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Compile all account mutes to useable form.
|
||||
mutes = usermute.NewCompiledUserMuteList(allMutes)
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
// Right now this is not ideal, as we perform mute and
|
||||
// status filtering *after* the above load loop, so we
|
||||
// could end up with no statuses still AFTER all loading.
|
||||
//
|
||||
// In a PR coming *soon* we will move the filtering and
|
||||
// status muting into separate module similar to the visibility
|
||||
// filtering and caching which should move it to the above
|
||||
// load loop and provided function.
|
||||
|
||||
// API response requires them in interface{} form.
|
||||
items := make([]interface{}, 0, len(statuses))
|
||||
|
||||
for _, status := range statuses {
|
||||
// Convert internal status model to frontend model.
|
||||
apiStatus, err := p.converter.StatusToAPIStatus(ctx,
|
||||
status,
|
||||
requester,
|
||||
filterCtx,
|
||||
filters,
|
||||
mutes,
|
||||
)
|
||||
if err != nil && !errors.Is(err, statusfilter.ErrHideStatus) {
|
||||
log.Errorf(ctx, "error converting status: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if apiStatus != nil {
|
||||
// Append status to return slice.
|
||||
items = append(items, apiStatus)
|
||||
}
|
||||
}
|
||||
|
||||
// Package converted API statuses as pageable response.
|
||||
// Package returned API statuses as pageable response.
|
||||
return paging.PackageResponse(paging.ResponseParams{
|
||||
Items: items,
|
||||
Items: xslices.ToAny(apiStatuses),
|
||||
Path: pgPath,
|
||||
Next: page.Next(lo, hi),
|
||||
Prev: page.Prev(lo, hi),
|
||||
Path: pgPath,
|
||||
Query: pgQuery,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *Processor) loadTimeline(
|
||||
ctx context.Context,
|
||||
timeline *cache.TimelineCache[*gtsmodel.Status],
|
||||
page *paging.Page,
|
||||
load func(*paging.Page) (statuses []*gtsmodel.Status, next *paging.Page, err error),
|
||||
filter func(*gtsmodel.Status) bool,
|
||||
) (
|
||||
[]*gtsmodel.Status,
|
||||
gtserror.WithCode,
|
||||
) {
|
||||
if load == nil {
|
||||
// nil check outside
|
||||
// below main loop.
|
||||
panic("nil func")
|
||||
}
|
||||
|
||||
if page == nil {
|
||||
const text = "timeline must be paged"
|
||||
return nil, gtserror.NewErrorBadRequest(
|
||||
errors.New(text),
|
||||
text,
|
||||
)
|
||||
}
|
||||
|
||||
// Try load statuses from cache.
|
||||
statuses := timeline.Select(page)
|
||||
|
||||
// Filter statuses using provided function.
|
||||
statuses = slices.DeleteFunc(statuses, filter)
|
||||
|
||||
// Check if more statuses need to be loaded.
|
||||
if limit := page.Limit; len(statuses) < limit {
|
||||
|
||||
// Set first page
|
||||
// query to load.
|
||||
nextPg := page
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
var err error
|
||||
var next []*gtsmodel.Status
|
||||
|
||||
// Load next timeline statuses.
|
||||
next, nextPg, err = load(nextPg)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error loading timeline: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// An empty next page means no more.
|
||||
if len(next) == 0 && nextPg == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Cache loaded statuses.
|
||||
timeline.Insert(next...)
|
||||
|
||||
// Filter statuses using provided function,
|
||||
// this must be done AFTER cache insert but
|
||||
// BEFORE adding to slice, as this is used
|
||||
// for request-specific timeline filtering,
|
||||
// as opposed to filtering for entire cache.
|
||||
next = slices.DeleteFunc(next, filter)
|
||||
|
||||
// Append loaded statuses to return.
|
||||
statuses = append(statuses, next...)
|
||||
|
||||
if len(statuses) >= limit {
|
||||
// We loaded all the statuses
|
||||
// that were requested of us!
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statuses, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -682,13 +682,8 @@ func (p *clientAPI) CreateBlock(ctx context.Context, cMsg *messages.FromClientAP
|
|||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Remove blocker's statuses from blocker's timeline.
|
||||
p.state.Caches.Timelines.Home.InvalidateFrom(block.AccountID, "AccountID", block.TargetAccountID)
|
||||
p.state.Caches.Timelines.Home.InvalidateFrom(block.AccountID, "BoostOfAccountID", block.TargetAccountID)
|
||||
|
||||
// Remove blockee's statuses from blockee's timeline.
|
||||
p.state.Caches.Timelines.Home.InvalidateFrom(block.TargetAccountID, "AccountID", block.AccountID)
|
||||
p.state.Caches.Timelines.Home.InvalidateFrom(block.TargetAccountID, "BoostOfAccountID", block.AccountID)
|
||||
// Perform any necessary timeline invalidation.
|
||||
p.surface.invalidateTimelinesForBlock(ctx, block)
|
||||
|
||||
// TODO: same with notifications?
|
||||
// TODO: same with bookmarks?
|
||||
|
|
|
|||
|
|
@ -701,53 +701,19 @@ func (p *fediAPI) CreateBlock(ctx context.Context, fMsg *messages.FromFediAPI) e
|
|||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Remove each account's posts from the other's timelines.
|
||||
//
|
||||
// First home timelines.
|
||||
if err := p.state.Timelines.Home.WipeItemsFromAccountID(
|
||||
ctx,
|
||||
block.AccountID,
|
||||
block.TargetAccountID,
|
||||
); err != nil {
|
||||
log.Errorf(ctx, "error wiping items from block -> target's home timeline: %v", err)
|
||||
}
|
||||
|
||||
if err := p.state.Timelines.Home.WipeItemsFromAccountID(
|
||||
ctx,
|
||||
block.TargetAccountID,
|
||||
block.AccountID,
|
||||
); err != nil {
|
||||
log.Errorf(ctx, "error wiping items from target -> block's home timeline: %v", err)
|
||||
}
|
||||
|
||||
// Now list timelines.
|
||||
if err := p.state.Timelines.List.WipeItemsFromAccountID(
|
||||
ctx,
|
||||
block.AccountID,
|
||||
block.TargetAccountID,
|
||||
); err != nil {
|
||||
log.Errorf(ctx, "error wiping items from block -> target's list timeline(s): %v", err)
|
||||
}
|
||||
|
||||
if err := p.state.Timelines.List.WipeItemsFromAccountID(
|
||||
ctx,
|
||||
block.TargetAccountID,
|
||||
block.AccountID,
|
||||
); err != nil {
|
||||
log.Errorf(ctx, "error wiping items from target -> block's list timeline(s): %v", err)
|
||||
}
|
||||
// Perform any necessary timeline invalidation.
|
||||
p.surface.invalidateTimelinesForBlock(ctx, block)
|
||||
|
||||
// Remove any follows that existed between blocker + blockee.
|
||||
if err := p.state.DB.DeleteFollow(
|
||||
ctx,
|
||||
// (note this handles removing any necessary list entries).
|
||||
if err := p.state.DB.DeleteFollow(ctx,
|
||||
block.AccountID,
|
||||
block.TargetAccountID,
|
||||
); err != nil {
|
||||
log.Errorf(ctx, "error deleting follow from block -> target: %v", err)
|
||||
}
|
||||
|
||||
if err := p.state.DB.DeleteFollow(
|
||||
ctx,
|
||||
if err := p.state.DB.DeleteFollow(ctx,
|
||||
block.TargetAccountID,
|
||||
block.AccountID,
|
||||
); err != nil {
|
||||
|
|
@ -755,16 +721,14 @@ func (p *fediAPI) CreateBlock(ctx context.Context, fMsg *messages.FromFediAPI) e
|
|||
}
|
||||
|
||||
// Remove any follow requests that existed between blocker + blockee.
|
||||
if err := p.state.DB.DeleteFollowRequest(
|
||||
ctx,
|
||||
if err := p.state.DB.DeleteFollowRequest(ctx,
|
||||
block.AccountID,
|
||||
block.TargetAccountID,
|
||||
); err != nil {
|
||||
log.Errorf(ctx, "error deleting follow request from block -> target: %v", err)
|
||||
}
|
||||
|
||||
if err := p.state.DB.DeleteFollowRequest(
|
||||
ctx,
|
||||
if err := p.state.DB.DeleteFollowRequest(ctx,
|
||||
block.TargetAccountID,
|
||||
block.AccountID,
|
||||
); err != nil {
|
||||
|
|
|
|||
|
|
@ -553,13 +553,8 @@ func (s *Surface) tagFollowersForStatus(
|
|||
// 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) error {
|
||||
if err := s.State.Timelines.Home.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.State.Timelines.List.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.State.Caches.Timelines.Home.RemoveByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.List.RemoveByStatusIDs(statusID)
|
||||
s.Stream.Delete(ctx, statusID)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -569,19 +564,8 @@ func (s *Surface) deleteStatusFromTimelines(ctx context.Context, statusID string
|
|||
// 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(ctx context.Context, statusID string) {
|
||||
if err := s.State.Timelines.Home.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
log.
|
||||
WithContext(ctx).
|
||||
WithField("statusID", statusID).
|
||||
Errorf("error unpreparing status from home timelines: %v", err)
|
||||
}
|
||||
|
||||
if err := s.State.Timelines.List.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
log.
|
||||
WithContext(ctx).
|
||||
WithField("statusID", statusID).
|
||||
Errorf("error unpreparing status from list timelines: %v", err)
|
||||
}
|
||||
s.State.Caches.Timelines.Home.UnprepareByStatusIDs(statusID)
|
||||
s.State.Caches.Timelines.List.UnprepareByStatusIDs(statusID)
|
||||
}
|
||||
|
||||
// timelineStatusUpdate looks up HOME and LIST timelines of accounts
|
||||
|
|
@ -860,3 +844,57 @@ func (s *Surface) timelineStatusUpdateForTagFollowers(
|
|||
}
|
||||
return errs.Combine()
|
||||
}
|
||||
|
||||
// invalidateTimelinesForBlock ...
|
||||
func (s *Surface) invalidateTimelinesForBlock(ctx context.Context, block *gtsmodel.Block) {
|
||||
|
||||
// Check if origin is local account,
|
||||
// i.e. has status timeline caches.
|
||||
if block.Account.IsLocal() {
|
||||
|
||||
// Remove target's statuses
|
||||
// from origin's home timeline.
|
||||
s.State.Caches.Timelines.Home.
|
||||
MustGet(block.AccountID).
|
||||
RemoveByAccountIDs(block.TargetAccountID)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue