timelines working pretty alright!

This commit is contained in:
tsmethurst 2021-06-13 18:34:36 +02:00
parent d9d9a7a626
commit a25e53af4e
12 changed files with 218 additions and 237 deletions

View File

@ -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(),
})

View File

@ -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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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(&notifications).Where("target_account_id = ?", accountID)
if maxID != "" {
n := &gtsmodel.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 := &gtsmodel.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 {

View File

@ -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)

View File

@ -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 := &gtsmodel.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)

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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()