| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | package timeline | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-08-15 18:43:08 +02:00
										 |  |  | 	"container/list" | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"codeberg.org/gruf/go-kv" | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2023-06-04 18:55:30 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | func (t *timeline) indexXBetweenIDs(ctx context.Context, amount int, behindID string, beforeID string, frontToBack bool) error { | 
					
						
							|  |  |  | 	l := log. | 
					
						
							|  |  |  | 		WithContext(ctx). | 
					
						
							|  |  |  | 		WithFields(kv.Fields{ | 
					
						
							|  |  |  | 			{"amount", amount}, | 
					
						
							|  |  |  | 			{"behindID", behindID}, | 
					
						
							|  |  |  | 			{"beforeID", beforeID}, | 
					
						
							|  |  |  | 			{"frontToBack", frontToBack}, | 
					
						
							|  |  |  | 		}...) | 
					
						
							|  |  |  | 	l.Trace("entering indexXBetweenIDs") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if beforeID >= behindID { | 
					
						
							|  |  |  | 		// This is an impossible situation, we | 
					
						
							|  |  |  | 		// can't index anything between these. | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2021-06-23 18:42:20 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	t.Lock() | 
					
						
							|  |  |  | 	defer t.Unlock() | 
					
						
							| 
									
										
										
										
											2021-08-15 18:43:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	// Lazily init indexed items. | 
					
						
							|  |  |  | 	if t.items.data == nil { | 
					
						
							|  |  |  | 		t.items.data = &list.List{} | 
					
						
							|  |  |  | 		t.items.data.Init() | 
					
						
							| 
									
										
										
										
											2021-08-15 18:43:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	// Start by mapping out the list so we know what | 
					
						
							|  |  |  | 	// we have to do. Depending on the current state | 
					
						
							|  |  |  | 	// of the list we might not have to do *anything*. | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		position         int | 
					
						
							|  |  |  | 		listLen          = t.items.data.Len() | 
					
						
							|  |  |  | 		behindIDPosition int | 
					
						
							|  |  |  | 		beforeIDPosition int | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for e := t.items.data.Front(); e != nil; e = e.Next() { | 
					
						
							|  |  |  | 		entry := e.Value.(*indexedItemsEntry) //nolint:forcetypeassert | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		position++ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if entry.itemID > behindID { | 
					
						
							|  |  |  | 			l.Trace("item is too new, continuing") | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-08-15 18:43:08 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 		if behindIDPosition == 0 { | 
					
						
							|  |  |  | 			// Gone far enough through the list | 
					
						
							|  |  |  | 			// and found our behindID mark. | 
					
						
							|  |  |  | 			// We only need to set this once. | 
					
						
							|  |  |  | 			l.Tracef("found behindID mark %s at position %d", entry.itemID, position) | 
					
						
							|  |  |  | 			behindIDPosition = position | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if entry.itemID >= beforeID { | 
					
						
							|  |  |  | 			// Push the beforeID mark back | 
					
						
							|  |  |  | 			// one place every iteration. | 
					
						
							|  |  |  | 			l.Tracef("setting beforeID mark %s at position %d", entry.itemID, position) | 
					
						
							|  |  |  | 			beforeIDPosition = position | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if entry.itemID <= beforeID { | 
					
						
							|  |  |  | 			// We've gone beyond the bounds of | 
					
						
							|  |  |  | 			// items we're interested in; stop. | 
					
						
							|  |  |  | 			l.Trace("reached older items, breaking") | 
					
						
							|  |  |  | 			break | 
					
						
							| 
									
										
										
										
											2021-08-15 18:43:08 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-02-05 12:47:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	// We can now figure out if we need to make db calls. | 
					
						
							|  |  |  | 	var grabMore bool | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case listLen < amount: | 
					
						
							|  |  |  | 		// The whole list is shorter than the | 
					
						
							|  |  |  | 		// amount we're being asked to return, | 
					
						
							|  |  |  | 		// make up the difference. | 
					
						
							|  |  |  | 		grabMore = true | 
					
						
							|  |  |  | 		amount -= listLen | 
					
						
							|  |  |  | 	case beforeIDPosition-behindIDPosition < amount: | 
					
						
							|  |  |  | 		// Not enough items between behindID and | 
					
						
							|  |  |  | 		// beforeID to return amount required, | 
					
						
							|  |  |  | 		// try to get more. | 
					
						
							|  |  |  | 		grabMore = true | 
					
						
							| 
									
										
										
										
											2021-08-15 18:43:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	if !grabMore { | 
					
						
							|  |  |  | 		// We're good! | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	// Fetch additional items. | 
					
						
							|  |  |  | 	items, err := t.grab(ctx, amount, behindID, beforeID, frontToBack) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	// Index all the items we got. We already have | 
					
						
							|  |  |  | 	// a lock on the timeline, so don't call IndexOne | 
					
						
							|  |  |  | 	// here, since that will also try to get a lock! | 
					
						
							|  |  |  | 	for _, item := range items { | 
					
						
							|  |  |  | 		entry := &indexedItemsEntry{ | 
					
						
							|  |  |  | 			itemID:           item.GetID(), | 
					
						
							|  |  |  | 			boostOfID:        item.GetBoostOfID(), | 
					
						
							|  |  |  | 			accountID:        item.GetAccountID(), | 
					
						
							|  |  |  | 			boostOfAccountID: item.GetBoostOfAccountID(), | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 		if _, err := t.items.insertIndexed(ctx, entry); err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-04 18:55:30 +02:00
										 |  |  | 			return gtserror.Newf("error inserting entry with itemID %s into index: %w", entry.itemID, err) | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | // grab wraps the timeline's grabFunction in paging + filtering logic. | 
					
						
							|  |  |  | func (t *timeline) grab(ctx context.Context, amount int, behindID string, beforeID string, frontToBack bool) ([]Timelineable, error) { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		sinceID  string | 
					
						
							|  |  |  | 		minID    string | 
					
						
							|  |  |  | 		grabbed  int | 
					
						
							|  |  |  | 		maxID    = behindID | 
					
						
							|  |  |  | 		filtered = make([]Timelineable, 0, amount) | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	if frontToBack { | 
					
						
							|  |  |  | 		sinceID = beforeID | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		minID = beforeID | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	for attempts := 0; attempts < 5; attempts++ { | 
					
						
							|  |  |  | 		if grabbed >= amount { | 
					
						
							|  |  |  | 			// We got everything we needed. | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		items, stop, err := t.grabFunction( | 
					
						
							|  |  |  | 			ctx, | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 			t.timelineID, | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 			maxID, | 
					
						
							|  |  |  | 			sinceID, | 
					
						
							|  |  |  | 			minID, | 
					
						
							|  |  |  | 			// Don't grab more than we need to. | 
					
						
							|  |  |  | 			amount-grabbed, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			// Grab function already checks for | 
					
						
							|  |  |  | 			// db.ErrNoEntries, so if an error | 
					
						
							|  |  |  | 			// is returned then it's a real one. | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if stop || len(items) == 0 { | 
					
						
							|  |  |  | 			// No items left. | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Set next query parameters. | 
					
						
							|  |  |  | 		if frontToBack { | 
					
						
							|  |  |  | 			// Page down. | 
					
						
							|  |  |  | 			maxID = items[len(items)-1].GetID() | 
					
						
							|  |  |  | 			if maxID <= beforeID { | 
					
						
							|  |  |  | 				// Can't go any further. | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// Page up. | 
					
						
							|  |  |  | 			minID = items[0].GetID() | 
					
						
							|  |  |  | 			if minID >= behindID { | 
					
						
							|  |  |  | 				// Can't go any further. | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for _, item := range items { | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 			ok, err := t.filterFunction(ctx, t.timelineID, item) | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				if !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 					// Real error here. | 
					
						
							|  |  |  | 					return nil, err | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				log.Warnf(ctx, "errNoEntries while filtering item %s: %s", item.GetID(), err) | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if ok { | 
					
						
							|  |  |  | 				filtered = append(filtered, item) | 
					
						
							|  |  |  | 				grabbed++ // count this as grabbed | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return filtered, nil | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-05 12:47:38 +01:00
										 |  |  | func (t *timeline) IndexAndPrepareOne(ctx context.Context, statusID string, boostOfID string, accountID string, boostOfAccountID string) (bool, error) { | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	t.Lock() | 
					
						
							|  |  |  | 	defer t.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-22 19:38:10 +01:00
										 |  |  | 	postIndexEntry := &indexedItemsEntry{ | 
					
						
							| 
									
										
										
										
											2022-02-05 12:47:38 +01:00
										 |  |  | 		itemID:           statusID, | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 		boostOfID:        boostOfID, | 
					
						
							|  |  |  | 		accountID:        accountID, | 
					
						
							|  |  |  | 		boostOfAccountID: boostOfAccountID, | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	if inserted, err := t.items.insertIndexed(ctx, postIndexEntry); err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-04 18:55:30 +02:00
										 |  |  | 		return false, gtserror.Newf("error inserting indexed: %w", err) | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	} else if !inserted { | 
					
						
							|  |  |  | 		// Entry wasn't inserted, so | 
					
						
							|  |  |  | 		// don't bother preparing it. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 	preparable, err := t.prepareFunction(ctx, t.timelineID, statusID) | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-04 18:55:30 +02:00
										 |  |  | 		return true, gtserror.Newf("error preparing: %w", err) | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	postIndexEntry.prepared = preparable | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	return true, nil | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | func (t *timeline) Len() int { | 
					
						
							|  |  |  | 	t.Lock() | 
					
						
							|  |  |  | 	defer t.Unlock() | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	if t.items == nil || t.items.data == nil { | 
					
						
							|  |  |  | 		// indexedItems hasnt been initialized yet. | 
					
						
							|  |  |  | 		return 0 | 
					
						
							| 
									
										
										
										
											2021-08-15 18:43:08 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return t.items.data.Len() | 
					
						
							| 
									
										
										
										
											2021-08-15 18:43:08 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | func (t *timeline) OldestIndexedItemID() string { | 
					
						
							|  |  |  | 	t.Lock() | 
					
						
							|  |  |  | 	defer t.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if t.items == nil || t.items.data == nil { | 
					
						
							|  |  |  | 		// indexedItems hasnt been initialized yet. | 
					
						
							|  |  |  | 		return "" | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 	e := t.items.data.Back() | 
					
						
							|  |  |  | 	if e == nil { | 
					
						
							|  |  |  | 		// List was empty. | 
					
						
							|  |  |  | 		return "" | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-04-06 13:43:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return e.Value.(*indexedItemsEntry).itemID //nolint:forcetypeassert | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | } |