gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

index.go (6978B)


      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 	"errors"
     24 
     25 	"codeberg.org/gruf/go-kv"
     26 	"github.com/superseriousbusiness/gotosocial/internal/db"
     27 	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
     28 	"github.com/superseriousbusiness/gotosocial/internal/log"
     29 )
     30 
     31 func (t *timeline) indexXBetweenIDs(ctx context.Context, amount int, behindID string, beforeID string, frontToBack bool) error {
     32 	l := log.
     33 		WithContext(ctx).
     34 		WithFields(kv.Fields{
     35 			{"amount", amount},
     36 			{"behindID", behindID},
     37 			{"beforeID", beforeID},
     38 			{"frontToBack", frontToBack},
     39 		}...)
     40 	l.Trace("entering indexXBetweenIDs")
     41 
     42 	if beforeID >= behindID {
     43 		// This is an impossible situation, we
     44 		// can't index anything between these.
     45 		return nil
     46 	}
     47 
     48 	t.Lock()
     49 	defer t.Unlock()
     50 
     51 	// Lazily init indexed items.
     52 	if t.items.data == nil {
     53 		t.items.data = &list.List{}
     54 		t.items.data.Init()
     55 	}
     56 
     57 	// Start by mapping out the list so we know what
     58 	// we have to do. Depending on the current state
     59 	// of the list we might not have to do *anything*.
     60 	var (
     61 		position         int
     62 		listLen          = t.items.data.Len()
     63 		behindIDPosition int
     64 		beforeIDPosition int
     65 	)
     66 
     67 	for e := t.items.data.Front(); e != nil; e = e.Next() {
     68 		entry := e.Value.(*indexedItemsEntry) //nolint:forcetypeassert
     69 
     70 		position++
     71 
     72 		if entry.itemID > behindID {
     73 			l.Trace("item is too new, continuing")
     74 			continue
     75 		}
     76 
     77 		if behindIDPosition == 0 {
     78 			// Gone far enough through the list
     79 			// and found our behindID mark.
     80 			// We only need to set this once.
     81 			l.Tracef("found behindID mark %s at position %d", entry.itemID, position)
     82 			behindIDPosition = position
     83 		}
     84 
     85 		if entry.itemID >= beforeID {
     86 			// Push the beforeID mark back
     87 			// one place every iteration.
     88 			l.Tracef("setting beforeID mark %s at position %d", entry.itemID, position)
     89 			beforeIDPosition = position
     90 		}
     91 
     92 		if entry.itemID <= beforeID {
     93 			// We've gone beyond the bounds of
     94 			// items we're interested in; stop.
     95 			l.Trace("reached older items, breaking")
     96 			break
     97 		}
     98 	}
     99 
    100 	// We can now figure out if we need to make db calls.
    101 	var grabMore bool
    102 	switch {
    103 	case listLen < amount:
    104 		// The whole list is shorter than the
    105 		// amount we're being asked to return,
    106 		// make up the difference.
    107 		grabMore = true
    108 		amount -= listLen
    109 	case beforeIDPosition-behindIDPosition < amount:
    110 		// Not enough items between behindID and
    111 		// beforeID to return amount required,
    112 		// try to get more.
    113 		grabMore = true
    114 	}
    115 
    116 	if !grabMore {
    117 		// We're good!
    118 		return nil
    119 	}
    120 
    121 	// Fetch additional items.
    122 	items, err := t.grab(ctx, amount, behindID, beforeID, frontToBack)
    123 	if err != nil {
    124 		return err
    125 	}
    126 
    127 	// Index all the items we got. We already have
    128 	// a lock on the timeline, so don't call IndexOne
    129 	// here, since that will also try to get a lock!
    130 	for _, item := range items {
    131 		entry := &indexedItemsEntry{
    132 			itemID:           item.GetID(),
    133 			boostOfID:        item.GetBoostOfID(),
    134 			accountID:        item.GetAccountID(),
    135 			boostOfAccountID: item.GetBoostOfAccountID(),
    136 		}
    137 
    138 		if _, err := t.items.insertIndexed(ctx, entry); err != nil {
    139 			return gtserror.Newf("error inserting entry with itemID %s into index: %w", entry.itemID, err)
    140 		}
    141 	}
    142 
    143 	return nil
    144 }
    145 
    146 // grab wraps the timeline's grabFunction in paging + filtering logic.
    147 func (t *timeline) grab(ctx context.Context, amount int, behindID string, beforeID string, frontToBack bool) ([]Timelineable, error) {
    148 	var (
    149 		sinceID  string
    150 		minID    string
    151 		grabbed  int
    152 		maxID    = behindID
    153 		filtered = make([]Timelineable, 0, amount)
    154 	)
    155 
    156 	if frontToBack {
    157 		sinceID = beforeID
    158 	} else {
    159 		minID = beforeID
    160 	}
    161 
    162 	for attempts := 0; attempts < 5; attempts++ {
    163 		if grabbed >= amount {
    164 			// We got everything we needed.
    165 			break
    166 		}
    167 
    168 		items, stop, err := t.grabFunction(
    169 			ctx,
    170 			t.timelineID,
    171 			maxID,
    172 			sinceID,
    173 			minID,
    174 			// Don't grab more than we need to.
    175 			amount-grabbed,
    176 		)
    177 
    178 		if err != nil {
    179 			// Grab function already checks for
    180 			// db.ErrNoEntries, so if an error
    181 			// is returned then it's a real one.
    182 			return nil, err
    183 		}
    184 
    185 		if stop || len(items) == 0 {
    186 			// No items left.
    187 			break
    188 		}
    189 
    190 		// Set next query parameters.
    191 		if frontToBack {
    192 			// Page down.
    193 			maxID = items[len(items)-1].GetID()
    194 			if maxID <= beforeID {
    195 				// Can't go any further.
    196 				break
    197 			}
    198 		} else {
    199 			// Page up.
    200 			minID = items[0].GetID()
    201 			if minID >= behindID {
    202 				// Can't go any further.
    203 				break
    204 			}
    205 		}
    206 
    207 		for _, item := range items {
    208 			ok, err := t.filterFunction(ctx, t.timelineID, item)
    209 			if err != nil {
    210 				if !errors.Is(err, db.ErrNoEntries) {
    211 					// Real error here.
    212 					return nil, err
    213 				}
    214 				log.Warnf(ctx, "errNoEntries while filtering item %s: %s", item.GetID(), err)
    215 				continue
    216 			}
    217 
    218 			if ok {
    219 				filtered = append(filtered, item)
    220 				grabbed++ // count this as grabbed
    221 			}
    222 		}
    223 	}
    224 
    225 	return filtered, nil
    226 }
    227 
    228 func (t *timeline) IndexAndPrepareOne(ctx context.Context, statusID string, boostOfID string, accountID string, boostOfAccountID string) (bool, error) {
    229 	t.Lock()
    230 	defer t.Unlock()
    231 
    232 	postIndexEntry := &indexedItemsEntry{
    233 		itemID:           statusID,
    234 		boostOfID:        boostOfID,
    235 		accountID:        accountID,
    236 		boostOfAccountID: boostOfAccountID,
    237 	}
    238 
    239 	if inserted, err := t.items.insertIndexed(ctx, postIndexEntry); err != nil {
    240 		return false, gtserror.Newf("error inserting indexed: %w", err)
    241 	} else if !inserted {
    242 		// Entry wasn't inserted, so
    243 		// don't bother preparing it.
    244 		return false, nil
    245 	}
    246 
    247 	preparable, err := t.prepareFunction(ctx, t.timelineID, statusID)
    248 	if err != nil {
    249 		return true, gtserror.Newf("error preparing: %w", err)
    250 	}
    251 	postIndexEntry.prepared = preparable
    252 
    253 	return true, nil
    254 }
    255 
    256 func (t *timeline) Len() int {
    257 	t.Lock()
    258 	defer t.Unlock()
    259 
    260 	if t.items == nil || t.items.data == nil {
    261 		// indexedItems hasnt been initialized yet.
    262 		return 0
    263 	}
    264 
    265 	return t.items.data.Len()
    266 }
    267 
    268 func (t *timeline) OldestIndexedItemID() string {
    269 	t.Lock()
    270 	defer t.Unlock()
    271 
    272 	if t.items == nil || t.items.data == nil {
    273 		// indexedItems hasnt been initialized yet.
    274 		return ""
    275 	}
    276 
    277 	e := t.items.data.Back()
    278 	if e == nil {
    279 		// List was empty.
    280 		return ""
    281 	}
    282 
    283 	return e.Value.(*indexedItemsEntry).itemID //nolint:forcetypeassert
    284 }