indexeditems.go (3389B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package timeline 19 20 import ( 21 "container/list" 22 "context" 23 24 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 25 ) 26 27 type indexedItems struct { 28 data *list.List 29 skipInsert SkipInsertFunction 30 } 31 32 type indexedItemsEntry struct { 33 itemID string 34 boostOfID string 35 accountID string 36 boostOfAccountID string 37 prepared Preparable 38 } 39 40 // WARNING: ONLY CALL THIS FUNCTION IF YOU ALREADY HAVE 41 // A LOCK ON THE TIMELINE CONTAINING THIS INDEXEDITEMS! 42 func (i *indexedItems) insertIndexed(ctx context.Context, newEntry *indexedItemsEntry) (bool, error) { 43 // Lazily init indexed items. 44 if i.data == nil { 45 i.data = &list.List{} 46 i.data.Init() 47 } 48 49 if i.data.Len() == 0 { 50 // We have no entries yet, meaning this is both the 51 // newest + oldest entry, so just put it in the front. 52 i.data.PushFront(newEntry) 53 return true, nil 54 } 55 56 var ( 57 insertMark *list.Element 58 currentPosition int 59 ) 60 61 // We need to iterate through the index to make sure we put 62 // this item in the appropriate place according to its id. 63 // We also need to make sure we're not inserting a duplicate 64 // item -- this can happen sometimes and it's sucky UX. 65 for e := i.data.Front(); e != nil; e = e.Next() { 66 currentPosition++ 67 68 currentEntry := e.Value.(*indexedItemsEntry) //nolint:forcetypeassert 69 70 // Check if we need to skip inserting this item based on 71 // the current item. 72 // 73 // For example, if the new item is a boost, and the current 74 // item is the original, we may not want to insert the boost 75 // if it would appear very shortly after the original. 76 if skip, err := i.skipInsert( 77 ctx, 78 newEntry.itemID, 79 newEntry.accountID, 80 newEntry.boostOfID, 81 newEntry.boostOfAccountID, 82 currentEntry.itemID, 83 currentEntry.accountID, 84 currentEntry.boostOfID, 85 currentEntry.boostOfAccountID, 86 currentPosition, 87 ); err != nil { 88 return false, gtserror.Newf("error calling skipInsert: %w", err) 89 } else if skip { 90 // We don't need to insert this at all, 91 // so we can safely bail. 92 return false, nil 93 } 94 95 if insertMark != nil { 96 // We already found our mark. 97 continue 98 } 99 100 if currentEntry.itemID > newEntry.itemID { 101 // We're still in items newer than 102 // the one we're trying to insert. 103 continue 104 } 105 106 // We found our spot! 107 insertMark = e 108 } 109 110 if insertMark == nil { 111 // We looked through the whole timeline and didn't find 112 // a mark, so the new item is the oldest item we've seen; 113 // insert it at the back. 114 i.data.PushBack(newEntry) 115 return true, nil 116 } 117 118 i.data.InsertBefore(newEntry, insertMark) 119 return true, nil 120 }