summaryrefslogtreecommitdiff
path: root/internal/timeline/get.go
diff options
context:
space:
mode:
authorLibravatar Tobi Smethurst <31960611+tsmethurst@users.noreply.github.com>2021-06-13 18:42:28 +0200
committerLibravatar GitHub <noreply@github.com>2021-06-13 18:42:28 +0200
commitb4288f3c47a9ff9254b933dcb9ee7274d4a4135c (patch)
tree3fe1bb1ab8d4b8c5d9a83df708e5088f35c3150a /internal/timeline/get.go
parentTidy + timeline embetterment (#38) (diff)
downloadgotosocial-b4288f3c47a9ff9254b933dcb9ee7274d4a4135c.tar.xz
Timeline manager (#40)
* start messing about with timeline manager * i have no idea what i'm doing * i continue to not know what i'm doing * it's coming along * bit more progress * update timeline with new posts as they come in * lint and fmt * Select accounts where empty string * restructure a bunch, get unfaves working * moving stuff around * federate status deletes properly * mention regex better but not 100% there * fix regex * some more hacking away at the timeline code phew * fix up some little things * i can't even * more timeline stuff * move to ulid * fiddley * some lil fixes for kibou compatibility * timelines working pretty alright! * tidy + lint
Diffstat (limited to 'internal/timeline/get.go')
-rw-r--r--internal/timeline/get.go309
1 files changed, 309 insertions, 0 deletions
diff --git a/internal/timeline/get.go b/internal/timeline/get.go
new file mode 100644
index 000000000..867e0940b
--- /dev/null
+++ b/internal/timeline/get.go
@@ -0,0 +1,309 @@
+package timeline
+
+import (
+ "container/list"
+ "errors"
+ "fmt"
+
+ "github.com/sirupsen/logrus"
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+)
+
+func (t *timeline) Get(amount int, maxID string, sinceID string, minID string) ([]*apimodel.Status, error) {
+ l := t.log.WithFields(logrus.Fields{
+ "func": "Get",
+ "accountID": t.accountID,
+ })
+
+ 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 func() {
+ if err := t.prepareNextQuery(amount, nextMaxID, "", ""); err != nil {
+ l.Errorf("error preparing next query: %s", err)
+ }
+ }()
+ }
+ }
+
+ // 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 func() {
+ if err := t.prepareNextQuery(amount, nextMaxID, "", ""); err != nil {
+ l.Errorf("error preparing next query: %s", err)
+ }
+ }()
+ }
+ }
+
+ // 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 t.preparedPosts.data == nil {
+ t.preparedPosts.data = &list.List{}
+ }
+
+ // make sure we have enough posts prepared to return
+ if t.preparedPosts.data.Len() < amount {
+ if err := t.PrepareFromTop(amount); err != nil {
+ return nil, err
+ }
+ }
+
+ // work through the prepared posts from the top and return
+ var served int
+ for e := t.preparedPosts.data.Front(); e != nil; e = e.Next() {
+ entry, ok := e.Value.(*preparedPostsEntry)
+ if !ok {
+ return nil, errors.New("GetXFromTop: could not parse e as a preparedPostsEntry")
+ }
+ statuses = append(statuses, entry.prepared)
+ served = served + 1
+ if served >= amount {
+ break
+ }
+ }
+
+ return statuses, nil
+}
+
+func (t *timeline) GetXBehindID(amount int, behindID string) ([]*apimodel.Status, error) {
+ // make a slice of statuses with the length we need to return
+ statuses := make([]*apimodel.Status, 0, amount)
+
+ if t.preparedPosts.data == nil {
+ t.preparedPosts.data = &list.List{}
+ }
+
+ // 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
+ entry, ok := e.Value.(*preparedPostsEntry)
+ if !ok {
+ return nil, errors.New("GetXBehindID: could not parse e as a preparedPostsEntry")
+ }
+
+ if entry.statusID == behindID {
+ behindIDMark = e
+ break 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, amount); err != nil {
+ return nil, fmt.Errorf("GetXBehindID: error indexing behind and including ID %s", behindID)
+ }
+ 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 {
+ if err := t.PrepareBehind(behindID, amount); err != nil {
+ return nil, err
+ }
+ }
+
+ // start serving from the entry right after the mark
+ var served int
+serveloop:
+ for e := behindIDMark.Next(); e != nil; e = e.Next() {
+ entry, ok := e.Value.(*preparedPostsEntry)
+ if !ok {
+ return nil, errors.New("GetXBehindID: could not parse e as a preparedPostsEntry")
+ }
+
+ // serve up to the amount requested
+ statuses = append(statuses, entry.prepared)
+ served = served + 1
+ if served >= amount {
+ break serveloop
+ }
+ }
+
+ return statuses, nil
+}
+
+func (t *timeline) GetXBeforeID(amount int, beforeID string, startFromTop bool) ([]*apimodel.Status, error) {
+ // make a slice of statuses with the length we need to return
+ statuses := make([]*apimodel.Status, 0, amount)
+
+ if t.preparedPosts.data == nil {
+ t.preparedPosts.data = &list.List{}
+ }
+
+ // iterate through the modified list until we hit the mark we're looking for
+ var beforeIDMark *list.Element
+findMarkLoop:
+ for e := t.preparedPosts.data.Front(); e != nil; e = e.Next() {
+ entry, ok := e.Value.(*preparedPostsEntry)
+ if !ok {
+ return nil, errors.New("GetXBeforeID: could not parse e as a preparedPostsEntry")
+ }
+
+ if entry.statusID == beforeID {
+ beforeIDMark = e
+ break findMarkLoop
+ }
+ }
+
+ // we didn't find it, so we need to make sure it's indexed and prepared and then try again
+ if beforeIDMark == nil {
+ if err := t.IndexBefore(beforeID, true, amount); err != nil {
+ return nil, fmt.Errorf("GetXBeforeID: error indexing before and including ID %s", beforeID)
+ }
+ if err := t.PrepareBefore(beforeID, true, amount); err != nil {
+ return nil, fmt.Errorf("GetXBeforeID: error preparing before and including ID %s", beforeID)
+ }
+ return t.GetXBeforeID(amount, beforeID, startFromTop)
+ }
+
+ var served int
+
+ if startFromTop {
+ // start serving from the front/top and keep going until we hit mark or get x amount statuses
+ serveloopFromTop:
+ for e := t.preparedPosts.data.Front(); e != nil; e = e.Next() {
+ entry, ok := e.Value.(*preparedPostsEntry)
+ if !ok {
+ return nil, errors.New("GetXBeforeID: could not parse e as a preparedPostsEntry")
+ }
+
+ if entry.statusID == beforeID {
+ break serveloopFromTop
+ }
+
+ // serve up to the amount requested
+ statuses = append(statuses, entry.prepared)
+ served = served + 1
+ if served >= amount {
+ break serveloopFromTop
+ }
+ }
+ } else if !startFromTop {
+ // start serving from the entry right before the mark
+ serveloopFromBottom:
+ for e := beforeIDMark.Prev(); e != nil; e = e.Prev() {
+ entry, ok := e.Value.(*preparedPostsEntry)
+ if !ok {
+ return nil, errors.New("GetXBeforeID: could not parse e as a preparedPostsEntry")
+ }
+
+ // serve up to the amount requested
+ statuses = append(statuses, entry.prepared)
+ served = served + 1
+ if served >= amount {
+ break serveloopFromBottom
+ }
+ }
+ }
+
+ return statuses, nil
+}
+
+func (t *timeline) GetXBetweenID(amount int, behindID string, beforeID string) ([]*apimodel.Status, error) {
+ // make a slice of statuses with the length we need to return
+ statuses := make([]*apimodel.Status, 0, amount)
+
+ if t.preparedPosts.data == nil {
+ t.preparedPosts.data = &list.List{}
+ }
+
+ // 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
+ entry, ok := e.Value.(*preparedPostsEntry)
+ if !ok {
+ return nil, errors.New("GetXBetweenID: could not parse e as a preparedPostsEntry")
+ }
+
+ if entry.statusID == behindID {
+ behindIDMark = e
+ break findMarkLoop
+ }
+ }
+
+ // we didn't find it
+ if behindIDMark == nil {
+ return nil, fmt.Errorf("GetXBetweenID: couldn't find status with ID %s", 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 {
+ if err := t.PrepareBehind(behindID, amount); err != nil {
+ return nil, err
+ }
+ }
+
+ // start serving from the entry right after the mark
+ var served int
+serveloop:
+ for e := behindIDMark.Next(); e != nil; e = e.Next() {
+ entry, ok := e.Value.(*preparedPostsEntry)
+ if !ok {
+ return nil, errors.New("GetXBetweenID: could not parse e as a preparedPostsEntry")
+ }
+
+ if entry.statusID == beforeID {
+ break serveloop
+ }
+
+ // serve up to the amount requested
+ statuses = append(statuses, entry.prepared)
+ served = served + 1
+ if served >= amount {
+ break serveloop
+ }
+ }
+
+ return statuses, nil
+}