From a25e53af4e6bf86af56db26c48381a9485776c58 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Sun, 13 Jun 2021 18:34:36 +0200 Subject: [PATCH] timelines working pretty alright! --- internal/api/s2s/webfinger/webfingerget.go | 2 +- internal/db/pg/pg.go | 83 +++++++----------- internal/federation/federatingdb/accept.go | 2 +- internal/processing/timeline.go | 80 ++---------------- internal/timeline/get.go | 71 +++++++++++++--- internal/timeline/index.go | 97 ++++++++++++++-------- internal/timeline/manager.go | 15 +--- internal/timeline/postindex.go | 6 +- internal/timeline/prepare.go | 49 +++++++++-- internal/timeline/preparedposts.go | 8 +- internal/timeline/timeline.go | 41 +++------ internal/typeutils/astointernal.go | 1 - 12 files changed, 218 insertions(+), 237 deletions(-) diff --git a/internal/api/s2s/webfinger/webfingerget.go b/internal/api/s2s/webfinger/webfingerget.go index 3a97378..ace7c4d 100644 --- a/internal/api/s2s/webfinger/webfingerget.go +++ b/internal/api/s2s/webfinger/webfingerget.go @@ -30,7 +30,7 @@ import ( // WebfingerGETRequest handles requests to, for example, https://example.org/.well-known/webfinger?resource=acct:some_user@example.org func (m *Module) WebfingerGETRequest(c *gin.Context) { l := m.log.WithFields(logrus.Fields{ - "func": "WebfingerGETRequest", + "func": "WebfingerGETRequest", "user-agent": c.Request.UserAgent(), }) diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go index 5e92c1a..2866a11 100644 --- a/internal/db/pg/pg.go +++ b/internal/db/pg/pg.go @@ -1165,33 +1165,19 @@ func (ps *postgresService) GetStatusesWhereFollowing(accountID string, maxID str q = q.ColumnExpr("status.*"). Join("JOIN follows AS f ON f.target_account_id = status.account_id"). - Where("f.account_id = ?", accountID) + Where("f.account_id = ?", accountID). + Order("status.id DESC") if maxID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", maxID).Select(); err == nil { - q = q.Where("status.created_at < ?", s.CreatedAt) - } + q = q.Where("status.id < ?", maxID) } if sinceID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", sinceID).Select(); err == nil { - q = q.Where("status.created_at > ?", s.CreatedAt) - } + q = q.Where("status.id > ?", sinceID) } if minID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", minID).Select(); err == nil { - q = q.Where("status.created_at > ?", s.CreatedAt) - } - } - - if minID != "" { - q = q.Order("status.created_at") - } else { - q = q.Order("status.created_at DESC") + q = q.Where("status.id > ?", minID) } if local { @@ -1204,9 +1190,14 @@ func (ps *postgresService) GetStatusesWhereFollowing(accountID string, maxID str err := q.Select() if err != nil { - if err != pg.ErrNoRows { - return nil, err + if err == pg.ErrNoRows { + return nil, db.ErrNoEntries{} } + return nil, err + } + + if len(statuses) == 0 { + return nil, db.ErrNoEntries{} } return statuses, nil @@ -1219,42 +1210,34 @@ func (ps *postgresService) GetPublicTimelineForAccount(accountID string, maxID s Where("visibility = ?", gtsmodel.VisibilityPublic). Where("? IS NULL", pg.Ident("in_reply_to_id")). Where("? IS NULL", pg.Ident("boost_of_id")). - Limit(limit). - Order("created_at DESC") + Order("status.id DESC") if maxID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", maxID).Select(); err != nil { - return nil, err - } - q = q.Where("created_at < ?", s.CreatedAt) - } - - if minID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", minID).Select(); err != nil { - return nil, err - } - q = q.Where("created_at > ?", s.CreatedAt) + q = q.Where("status.id < ?", maxID) } if sinceID != "" { - s := >smodel.Status{} - if err := ps.conn.Model(s).Where("id = ?", sinceID).Select(); err != nil { - return nil, err - } - q = q.Where("created_at > ?", s.CreatedAt) + q = q.Where("status.id > ?", sinceID) + } + + if minID != "" { + q = q.Where("status.id > ?", minID) } if local { - q = q.Where("local = ?", local) + q = q.Where("status.local = ?", local) + } + + if limit > 0 { + q = q.Limit(limit) } err := q.Select() if err != nil { - if err != pg.ErrNoRows { - return nil, err + if err == pg.ErrNoRows { + return nil, db.ErrNoEntries{} } + return nil, err } return statuses, nil @@ -1266,19 +1249,11 @@ func (ps *postgresService) GetNotificationsForAccount(accountID string, limit in q := ps.conn.Model(¬ifications).Where("target_account_id = ?", accountID) if maxID != "" { - n := >smodel.Notification{} - if err := ps.conn.Model(n).Where("id = ?", maxID).Select(); err != nil { - return nil, err - } - q = q.Where("created_at < ?", n.CreatedAt) + q = q.Where("id < ?", maxID) } if sinceID != "" { - n := >smodel.Notification{} - if err := ps.conn.Model(n).Where("id = ?", sinceID).Select(); err != nil { - return nil, err - } - q = q.Where("created_at > ?", n.CreatedAt) + q = q.Where("id > ?", sinceID) } if limit != 0 { diff --git a/internal/federation/federatingdb/accept.go b/internal/federation/federatingdb/accept.go index 5d7aea3..78cff40 100644 --- a/internal/federation/federatingdb/accept.go +++ b/internal/federation/federatingdb/accept.go @@ -95,7 +95,7 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA continue } switch iter.GetType().GetTypeName() { - // we have the whole object so we can figure out what we're accepting + // we have the whole object so we can figure out what we're accepting case string(gtsmodel.ActivityStreamsFollow): // ACCEPT FOLLOW asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) diff --git a/internal/processing/timeline.go b/internal/processing/timeline.go index c4c20d7..80e6331 100644 --- a/internal/processing/timeline.go +++ b/internal/processing/timeline.go @@ -21,9 +21,7 @@ package processing import ( "fmt" "net/url" - "sort" "sync" - "time" "github.com/sirupsen/logrus" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -38,80 +36,14 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st Statuses: []*apimodel.Status{}, } - apiStatuses := []*apimodel.Status{} - - maxIDMarker := maxID - sinceIDMarker := sinceID - minIDMarker := minID - - gtsStatuses, err := p.db.GetStatusesWhereFollowing(authed.Account.ID, maxIDMarker, sinceIDMarker, minIDMarker, limit, local) + apiStatuses, err := p.timelineManager.HomeTimeline(authed.Account.ID, maxID, sinceID, minID, limit, local) if err != nil { - if _, ok := err.(db.ErrNoEntries); !ok { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("HomeTimelineGet: error getting statuses from db: %s", err)) - } + return nil, gtserror.NewErrorInternalError(err) } - - for _, gtsStatus := range gtsStatuses { - // pull relevant accounts from the status -- we need this both for checking visibility and for serializing - relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(gtsStatus) - if err != nil { - continue - } - visible, err := p.db.StatusVisible(gtsStatus, authed.Account, relevantAccounts) - if err != nil { - continue - } - - if visible { - // check if this is a boost... - var reblogOfStatus *gtsmodel.Status - if gtsStatus.BoostOfID != "" { - s := >smodel.Status{} - if err := p.db.GetByID(s.BoostOfID, s); err != nil { - continue - } - reblogOfStatus = s - } - - // serialize the status (or, at least, convert it to a form that's ready to be serialized) - apiStatus, err := p.tc.StatusToMasto(gtsStatus, relevantAccounts.StatusAuthor, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, reblogOfStatus) - if err != nil { - continue - } - - apiStatuses = append(apiStatuses, apiStatus) - sort.Slice(apiStatuses, func(i int, j int) bool { - is, err := time.Parse(time.RFC3339, apiStatuses[i].CreatedAt) - if err != nil { - panic(err) - } - - js, err := time.Parse(time.RFC3339, apiStatuses[j].CreatedAt) - if err != nil { - panic(err) - } - - return is.After(js) - }) - - if len(apiStatuses) == limit { - // we have enough - break - } - } - if len(apiStatuses) != 0 { - if maxIDMarker != "" { - maxIDMarker = apiStatuses[len(apiStatuses)-1].ID - } - if minIDMarker != "" { - minIDMarker = apiStatuses[0].ID - } - } - } - resp.Statuses = apiStatuses - if len(resp.Statuses) != 0 { + // prepare the next and previous links + if len(apiStatuses) != 0 { nextLink := &url.URL{ Scheme: p.config.Protocol, Host: p.config.Host, @@ -259,7 +191,9 @@ func (p *processor) initTimelineFor(account *gtsmodel.Account, wg *sync.WaitGrou statuses, err := p.db.GetStatusesWhereFollowing(account.ID, "", "", "", desiredIndexLength, false) if err != nil { - l.Error(fmt.Errorf("initTimelineFor: error getting statuses: %s", err)) + if _, ok := err.(db.ErrNoEntries); !ok { + l.Error(fmt.Errorf("initTimelineFor: error getting statuses: %s", err)) + } return } p.indexAndIngest(statuses, account, desiredIndexLength) diff --git a/internal/timeline/get.go b/internal/timeline/get.go index 8ad70c9..f9da589 100644 --- a/internal/timeline/get.go +++ b/internal/timeline/get.go @@ -8,11 +8,53 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" ) +func (t *timeline) Get(amount int, maxID string, sinceID string, minID string) ([]*apimodel.Status, error) { + var statuses []*apimodel.Status + var err error + + // no params are defined to just fetch from the top + if maxID == "" && sinceID == "" && minID == "" { + statuses, err = t.GetXFromTop(amount) + // aysnchronously prepare the next predicted query so it's ready when the user asks for it + if len(statuses) != 0 { + nextMaxID := statuses[len(statuses)-1].ID + go t.prepareNextQuery(amount, nextMaxID, "", "") + } + } + + // maxID is defined but sinceID isn't so take from behind + if maxID != "" && sinceID == "" { + statuses, err = t.GetXBehindID(amount, maxID) + // aysnchronously prepare the next predicted query so it's ready when the user asks for it + if len(statuses) != 0 { + nextMaxID := statuses[len(statuses)-1].ID + go t.prepareNextQuery(amount, nextMaxID, "", "") + } + } + + // maxID is defined and sinceID || minID are as well, so take a slice between them + if maxID != "" && sinceID != "" { + statuses, err = t.GetXBetweenID(amount, maxID, minID) + } + if maxID != "" && minID != "" { + statuses, err = t.GetXBetweenID(amount, maxID, minID) + } + + // maxID isn't defined, but sinceID || minID are, so take x before + if maxID == "" && sinceID != "" { + statuses, err = t.GetXBeforeID(amount, sinceID, true) + } + if maxID == "" && minID != "" { + statuses, err = t.GetXBeforeID(amount, minID, true) + } + + return statuses, err +} + func (t *timeline) GetXFromTop(amount int) ([]*apimodel.Status, error) { // make a slice of statuses with the length we need to return statuses := make([]*apimodel.Status, 0, amount) - // if there are no prepared posts, just return the empty slice if t.preparedPosts.data == nil { t.preparedPosts.data = &list.List{} } @@ -45,7 +87,6 @@ func (t *timeline) GetXBehindID(amount int, behindID string) ([]*apimodel.Status // make a slice of statuses with the length we need to return statuses := make([]*apimodel.Status, 0, amount) - // if there are no prepared posts, just return the empty slice if t.preparedPosts.data == nil { t.preparedPosts.data = &list.List{} } @@ -53,6 +94,7 @@ func (t *timeline) GetXBehindID(amount int, behindID string) ([]*apimodel.Status // iterate through the modified list until we hit the mark we're looking for var position int var behindIDMark *list.Element + findMarkLoop: for e := t.preparedPosts.data.Front(); e != nil; e = e.Next() { position = position + 1 @@ -62,7 +104,6 @@ findMarkLoop: } if entry.statusID == behindID { - fmt.Printf("\n\n\n GETXBEHINDID: FOUND BEHINDID %s WITH POSITION %d AND CREATEDAT %s \n\n\n", behindID, position, entry.createdAt.String()) behindIDMark = e break findMarkLoop } @@ -70,22 +111,29 @@ findMarkLoop: // we didn't find it, so we need to make sure it's indexed and prepared and then try again if behindIDMark == nil { - if err := t.IndexBehind(behindID, true, amount); err != nil { + if err := t.IndexBehind(behindID, amount); err != nil { return nil, fmt.Errorf("GetXBehindID: error indexing behind and including ID %s", behindID) } - if err := t.PrepareBehind(behindID, true, amount); err != nil { + if err := t.PrepareBehind(behindID, amount); err != nil { return nil, fmt.Errorf("GetXBehindID: error preparing behind and including ID %s", behindID) } + oldestID, err := t.OldestPreparedPostID() + if err != nil { + return nil, err + } + if oldestID == "" || oldestID == behindID { + // there is no oldest prepared post, or the oldest prepared post is still the post we're looking for entries after + // this means we should just return the empty statuses slice since we don't have any more posts to offer + return statuses, nil + } return t.GetXBehindID(amount, behindID) } // make sure we have enough posts prepared behind it to return what we're being asked for if t.preparedPosts.data.Len() < amount+position { - fmt.Printf("\n\n\n GETXBEHINDID: PREPARED POSTS LENGTH %d WAS LESS THAN AMOUNT %d PLUS POSITION %d", t.preparedPosts.data.Len(), amount, position) - if err := t.PrepareBehind(behindID, false, amount); err != nil { + if err := t.PrepareBehind(behindID, amount); err != nil { return nil, err } - fmt.Printf("\n\n\n GETXBEHINDID: PREPARED POSTS LENGTH IS NOW %d", t.preparedPosts.data.Len()) } // start serving from the entry right after the mark @@ -97,7 +145,6 @@ serveloop: return nil, errors.New("GetXBehindID: could not parse e as a preparedPostsEntry") } - fmt.Printf("\n\n\n GETXBEHINDID: SERVING STATUS ID %s WITH CREATEDAT %s \n\n\n", entry.statusID, entry.createdAt.String()) // serve up to the amount requested statuses = append(statuses, entry.prepared) served = served + 1 @@ -165,8 +212,7 @@ findMarkLoop: break serveloopFromTop } } - - } else if startFromTop { + } else if !startFromTop { // start serving from the entry right before the mark serveloopFromBottom: for e := beforeIDMark.Prev(); e != nil; e = e.Prev() { @@ -191,7 +237,6 @@ func (t *timeline) GetXBetweenID(amount int, behindID string, beforeID string) ( // make a slice of statuses with the length we need to return statuses := make([]*apimodel.Status, 0, amount) - // if there are no prepared posts, just return the empty slice if t.preparedPosts.data == nil { t.preparedPosts.data = &list.List{} } @@ -220,7 +265,7 @@ findMarkLoop: // make sure we have enough posts prepared behind it to return what we're being asked for if t.preparedPosts.data.Len() < amount+position { - if err := t.PrepareBehind(behindID, false, amount); err != nil { + if err := t.PrepareBehind(behindID, amount); err != nil { return nil, err } } diff --git a/internal/timeline/index.go b/internal/timeline/index.go index f8bd49d..9a77961 100644 --- a/internal/timeline/index.go +++ b/internal/timeline/index.go @@ -1,8 +1,12 @@ package timeline import ( + "errors" "fmt" "time" + + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) func (t *timeline) IndexBefore(statusID string, include bool, amount int) error { @@ -44,41 +48,43 @@ func (t *timeline) IndexBefore(statusID string, include bool, amount int) error return nil } -func (t *timeline) IndexBehind(statusID string, include bool, amount int) error { - // filtered := []*gtsmodel.Status{} - // offsetStatus := statusID +func (t *timeline) IndexBehind(statusID string, amount int) error { + filtered := []*gtsmodel.Status{} + offsetStatus := statusID - // grabloop: - // for len(filtered) < amount { - // statuses, err := t.db.GetStatusesWhereFollowing(t.accountID, amount, offsetStatus, include, false) - // if err != nil { - // if _, ok := err.(db.ErrNoEntries); !ok { - // return fmt.Errorf("IndexBehindAndIncluding: error getting statuses from db: %s", err) - // } - // break grabloop // we just don't have enough statuses left in the db so index what we've got and then bail - // } + fmt.Println("\n\n\nENTERING GRABLOOP\n\n\n") +grabloop: + for len(filtered) < amount { + statuses, err := t.db.GetStatusesWhereFollowing(t.accountID, offsetStatus, "", "", amount, false) + if err != nil { + if _, ok := err.(db.ErrNoEntries); ok { + break grabloop // we just don't have enough statuses left in the db so index what we've got and then bail + } + return fmt.Errorf("IndexBehindAndIncluding: error getting statuses from db: %s", err) + } - // for _, s := range statuses { - // relevantAccounts, err := t.db.PullRelevantAccountsFromStatus(s) - // if err != nil { - // continue - // } - // visible, err := t.db.StatusVisible(s, t.account, relevantAccounts) - // if err != nil { - // continue - // } - // if visible { - // filtered = append(filtered, s) - // } - // offsetStatus = s.ID - // } - // } + for _, s := range statuses { + relevantAccounts, err := t.db.PullRelevantAccountsFromStatus(s) + if err != nil { + continue + } + visible, err := t.db.StatusVisible(s, t.account, relevantAccounts) + if err != nil { + continue + } + if visible { + filtered = append(filtered, s) + } + offsetStatus = s.ID + } + } + fmt.Println("\n\n\nLEAVING GRABLOOP\n\n\n") - // for _, s := range filtered { - // if err := t.IndexOne(s.CreatedAt, s.ID); err != nil { - // return fmt.Errorf("IndexBehindAndIncluding: error indexing status with id %s: %s", s.ID, err) - // } - // } + for _, s := range filtered { + if err := t.IndexOne(s.CreatedAt, s.ID); err != nil { + return fmt.Errorf("IndexBehindAndIncluding: error indexing status with id %s: %s", s.ID, err) + } + } return nil } @@ -92,8 +98,7 @@ func (t *timeline) IndexOne(statusCreatedAt time.Time, statusID string) error { defer t.Unlock() postIndexEntry := &postIndexEntry{ - createdAt: statusCreatedAt, - statusID: statusID, + statusID: statusID, } return t.postIndex.insertIndexed(postIndexEntry) @@ -104,8 +109,7 @@ func (t *timeline) IndexAndPrepareOne(statusCreatedAt time.Time, statusID string defer t.Unlock() postIndexEntry := &postIndexEntry{ - createdAt: statusCreatedAt, - statusID: statusID, + statusID: statusID, } if err := t.postIndex.insertIndexed(postIndexEntry); err != nil { @@ -118,3 +122,24 @@ func (t *timeline) IndexAndPrepareOne(statusCreatedAt time.Time, statusID string return nil } + +func (t *timeline) OldestIndexedPostID() (string, error) { + var id string + if t.postIndex == nil || t.postIndex.data == nil { + // return an empty string if postindex hasn't been initialized yet + return id, nil + } + + e := t.postIndex.data.Back() + + if e == nil { + // return an empty string if there's no back entry (ie., the index list hasn't been initialized yet) + return id, nil + } + + entry, ok := e.Value.(*postIndexEntry) + if !ok { + return id, errors.New("OldestIndexedPostID: could not parse e as a postIndexEntry") + } + return entry.statusID, nil +} diff --git a/internal/timeline/manager.go b/internal/timeline/manager.go index b26526f..50234ff 100644 --- a/internal/timeline/manager.go +++ b/internal/timeline/manager.go @@ -60,7 +60,7 @@ type Manager interface { IngestAndPrepare(status *gtsmodel.Status, timelineAccountID string) error // HomeTimeline returns limit n amount of entries from the home timeline of the given account ID, in descending chronological order. // If maxID is provided, it will return entries from that maxID onwards, inclusive. - HomeTimeline(timelineAccountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, error) + HomeTimeline(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*apimodel.Status, error) // GetIndexedLength returns the amount of posts/statuses that have been *indexed* for the given account ID. GetIndexedLength(timelineAccountID string) int // GetDesiredIndexLength returns the amount of posts that we, ideally, index for each user. @@ -143,18 +143,7 @@ func (m *manager) HomeTimeline(timelineAccountID string, maxID string, sinceID s t := m.getOrCreateTimeline(timelineAccountID) - var err error - var statuses []*apimodel.Status - if maxID != "" && sinceID != "" { - statuses, err = t.GetXBetweenID(limit, maxID, sinceID) - } else if maxID != "" { - statuses, err = t.GetXBehindID(limit, maxID) - } else if sinceID != "" { - statuses, err = t.GetXBeforeID(limit, sinceID, true) - } else { - statuses, err = t.GetXFromTop(limit) - } - + statuses, err := t.Get(limit, maxID, sinceID, minID) if err != nil { l.Errorf("error getting statuses: %s", err) } diff --git a/internal/timeline/postindex.go b/internal/timeline/postindex.go index 609a196..2ab65e0 100644 --- a/internal/timeline/postindex.go +++ b/internal/timeline/postindex.go @@ -3,7 +3,6 @@ package timeline import ( "container/list" "errors" - "time" ) type postIndex struct { @@ -11,8 +10,7 @@ type postIndex struct { } type postIndexEntry struct { - createdAt time.Time - statusID string + statusID string } func (p *postIndex) insertIndexed(i *postIndexEntry) error { @@ -37,7 +35,7 @@ func (p *postIndex) insertIndexed(i *postIndexEntry) error { // if the post to index is newer than e, insert it before e in the list if insertMark == nil { - if i.createdAt.After(entry.createdAt) { + if i.statusID > entry.statusID { insertMark = e } } diff --git a/internal/timeline/prepare.go b/internal/timeline/prepare.go index 11aa170..1fb1cd7 100644 --- a/internal/timeline/prepare.go +++ b/internal/timeline/prepare.go @@ -8,7 +8,26 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (t *timeline) PrepareBehind(statusID string, include bool, amount int) error { +func (t *timeline) prepareNextQuery(amount int, maxID string, sinceID string, minID string) error { + var err error + + // maxID is defined but sinceID isn't so take from behind + if maxID != "" && sinceID == "" { + err = t.PrepareBehind(maxID, amount) + } + + // maxID isn't defined, but sinceID || minID are, so take x before + if maxID == "" && sinceID != "" { + err = t.PrepareBefore(sinceID, false, amount) + } + if maxID == "" && minID != "" { + err = t.PrepareBefore(minID, false, amount) + } + + return err +} + +func (t *timeline) PrepareBehind(statusID string, amount int) error { t.Lock() defer t.Unlock() @@ -25,9 +44,6 @@ prepareloop: // we haven't hit the position we need to prepare from yet if entry.statusID == statusID { preparing = true - if !include { - continue - } } } @@ -171,10 +187,29 @@ func (t *timeline) prepare(statusID string) error { // shove it in prepared posts as a prepared posts entry preparedPostsEntry := &preparedPostsEntry{ - createdAt: gtsStatus.CreatedAt, - statusID: statusID, - prepared: apiModelStatus, + statusID: statusID, + prepared: apiModelStatus, } return t.preparedPosts.insertPrepared(preparedPostsEntry) } + +func (t *timeline) OldestPreparedPostID() (string, error) { + var id string + if t.preparedPosts == nil || t.preparedPosts.data == nil { + // return an empty string if prepared posts hasn't been initialized yet + return id, nil + } + + e := t.preparedPosts.data.Back() + if e == nil { + // return an empty string if there's no back entry (ie., the index list hasn't been initialized yet) + return id, nil + } + + entry, ok := e.Value.(*preparedPostsEntry) + if !ok { + return id, errors.New("OldestPreparedPostID: could not parse e as a preparedPostsEntry") + } + return entry.statusID, nil +} diff --git a/internal/timeline/preparedposts.go b/internal/timeline/preparedposts.go index 159e6b4..429ce54 100644 --- a/internal/timeline/preparedposts.go +++ b/internal/timeline/preparedposts.go @@ -3,7 +3,6 @@ package timeline import ( "container/list" "errors" - "time" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" ) @@ -13,9 +12,8 @@ type preparedPosts struct { } type preparedPostsEntry struct { - createdAt time.Time - statusID string - prepared *apimodel.Status + statusID string + prepared *apimodel.Status } func (p *preparedPosts) insertPrepared(i *preparedPostsEntry) error { @@ -40,7 +38,7 @@ func (p *preparedPosts) insertPrepared(i *preparedPostsEntry) error { // if the post to index is newer than e, insert it before e in the list if insertMark == nil { - if i.createdAt.After(entry.createdAt) { + if i.statusID > entry.statusID { insertMark = e } } diff --git a/internal/timeline/timeline.go b/internal/timeline/timeline.go index 2f2aade..6ac30e7 100644 --- a/internal/timeline/timeline.go +++ b/internal/timeline/timeline.go @@ -19,7 +19,6 @@ package timeline import ( - "errors" "sync" "time" @@ -39,6 +38,7 @@ type Timeline interface { RETRIEVAL FUNCTIONS */ + Get(amount int, maxID string, sinceID string, minID string) ([]*apimodel.Status, error) // GetXFromTop returns x amount of posts from the top of the timeline, from newest to oldest. GetXFromTop(amount int) ([]*apimodel.Status, error) // GetXBehindID returns x amount of posts from the given id onwards, from newest to oldest. @@ -63,12 +63,7 @@ type Timeline interface { // IndexOne puts a status into the timeline at the appropriate place according to its 'createdAt' property. IndexOne(statusCreatedAt time.Time, statusID string) error - // Remove removes a status from both the index and prepared posts. - // - // If a status has multiple entries in a timeline, they will all be removed. - // - // The returned int indicates the amount of entries that were removed. - Remove(statusID string) (int, error) + // OldestIndexedPostID returns the id of the rearmost (ie., the oldest) indexed post, or an error if something goes wrong. // If nothing goes wrong but there's no oldest post, an empty string will be returned so make sure to check for this. OldestIndexedPostID() (string, error) @@ -81,10 +76,13 @@ type Timeline interface { PrepareFromTop(amount int) error // PrepareBehind instructs the timeline to prepare the next amount of entries for serialization, from position onwards. // If include is true, then the given status ID will also be prepared, otherwise only entries behind it will be prepared. - PrepareBehind(statusID string, include bool, amount int) error + PrepareBehind(statusID string, amount int) error // IndexOne puts a status into the timeline at the appropriate place according to its 'createdAt' property, // and then immediately prepares it. IndexAndPrepareOne(statusCreatedAt time.Time, statusID string) error + // OldestPreparedPostID returns the id of the rearmost (ie., the oldest) prepared post, or an error if something goes wrong. + // If nothing goes wrong but there's no oldest post, an empty string will be returned so make sure to check for this. + OldestPreparedPostID() (string, error) /* INFO FUNCTIONS @@ -99,6 +97,12 @@ type Timeline interface { // Reset instructs the timeline to reset to its base state -- cache only the minimum amount of posts. Reset() error + // Remove removes a status from both the index and prepared posts. + // + // If a status has multiple entries in a timeline, they will all be removed. + // + // The returned int indicates the amount of entries that were removed. + Remove(statusID string) (int, error) } // timeline fulfils the Timeline interface @@ -134,24 +138,3 @@ func (t *timeline) PostIndexLength() int { return t.postIndex.data.Len() } - -func (t *timeline) OldestIndexedPostID() (string, error) { - var id string - if t.postIndex == nil || t.postIndex.data == nil { - // return an empty string if postindex hasn't been initialized yet - return id, nil - } - - e := t.postIndex.data.Back() - - if e == nil { - // return an empty string if there's no back entry (ie., the index list hasn't been initialized yet) - return id, nil - } - - entry, ok := e.Value.(*postIndexEntry) - if !ok { - return id, errors.New("OldestIndexedPostID: could not parse e as a postIndexEntry") - } - return entry.statusID, nil -} diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index c0442b5..5990e75 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -125,7 +125,6 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo acct.URL = uri.String() } - // InboxURI if accountable.GetActivityStreamsInbox() != nil && accountable.GetActivityStreamsInbox().GetIRI() != nil { acct.InboxURI = accountable.GetActivityStreamsInbox().GetIRI().String()