| 
									
										
										
										
											2023-08-09 19:14:33 +02: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/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package workers | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-26 15:34:10 +02:00
										 |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/cache/timeline" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/gtscontext" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/gtserror" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/gtsmodel" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/log" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/stream" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/util" | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // timelineAndNotifyStatus inserts the given status into the HOME | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | // and/or LIST timelines of accounts that follow the status author, | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | // as well as the HOME timelines of accounts that follow tags used by the status. | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | // | 
					
						
							|  |  |  | // It will also handle notifications for any mentions attached to | 
					
						
							| 
									
										
										
										
											2024-07-23 12:44:31 -07:00
										 |  |  | // the account, notifications for any local accounts that want | 
					
						
							|  |  |  | // to know when this account posts, and conversations containing the status. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.Status) error { | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	// Ensure status fully populated; including account, mentions, etc. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | 	if err := s.State.DB.PopulateStatus(ctx, status); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		return gtserror.Newf("error populating status with id %s: %w", status.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get all local followers of the account that posted the status. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | 	follows, err := s.State.DB.GetAccountLocalFollowers(ctx, status.AccountID) | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If the poster is also local, add a fake entry for them | 
					
						
							|  |  |  | 	// so they can see their own status in their timeline. | 
					
						
							|  |  |  | 	if status.Account.IsLocal() { | 
					
						
							|  |  |  | 		follows = append(follows, >smodel.Follow{ | 
					
						
							|  |  |  | 			AccountID:   status.AccountID, | 
					
						
							|  |  |  | 			Account:     status.Account, | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 			Notify:      util.Ptr(false), // Account shouldn't notify itself. | 
					
						
							|  |  |  | 			ShowReblogs: util.Ptr(true),  // Account should show own reblogs. | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	// Timeline the status for each local follower of this account. This will | 
					
						
							|  |  |  | 	// also handle notifying any followers with notify set to true on their follow. | 
					
						
							|  |  |  | 	homeTimelinedAccountIDs := s.timelineAndNotifyStatusForFollowers(ctx, status, follows) | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 	// Timeline the status for each local account who follows a tag used by this status. | 
					
						
							|  |  |  | 	if err := s.timelineAndNotifyStatusForTagFollowers(ctx, status, homeTimelinedAccountIDs); err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error timelining status %s for tag followers: %w", status.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	// Notify each local account that's mentioned by this status. | 
					
						
							| 
									
										
										
										
											2023-10-25 16:04:53 +02:00
										 |  |  | 	if err := s.notifyMentions(ctx, status); err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		return gtserror.Newf("error notifying status mentions for status %s: %w", status.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-23 12:44:31 -07:00
										 |  |  | 	// Update any conversations containing this status, and send conversation notifications. | 
					
						
							|  |  |  | 	notifications, err := s.Conversations.UpdateConversationsForStatus(ctx, status) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error updating conversations for status %s: %w", status.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, notification := range notifications { | 
					
						
							|  |  |  | 		s.Stream.Conversation(ctx, notification.AccountID, notification.Conversation) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // timelineAndNotifyStatusForFollowers iterates through the given | 
					
						
							|  |  |  | // slice of followers of the account that posted the given status, | 
					
						
							|  |  |  | // adding the status to list timelines + home timelines of each | 
					
						
							|  |  |  | // follower, as appropriate, and notifying each follower of the | 
					
						
							|  |  |  | // new status, if the status is eligible for notification. | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | // | 
					
						
							|  |  |  | // Returns a list of accounts which had this status inserted into their home timelines. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | // This will be used to prevent duplicate inserts when handling followed tags. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) timelineAndNotifyStatusForFollowers( | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	follows []*gtsmodel.Follow, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | ) (homeTimelinedAccountIDs []string) { | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	var ( | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		boost = (status.BoostOfID != "") | 
					
						
							|  |  |  | 		reply = (status.InReplyToURI != "") | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, follow := range follows { | 
					
						
							| 
									
										
										
										
											2023-09-29 10:39:35 +02:00
										 |  |  | 		// Check to see if the status is timelineable for this follower, | 
					
						
							|  |  |  | 		// taking account of its visibility, who it replies to, and, if | 
					
						
							|  |  |  | 		// it's a reblog, whether follower account wants to see reblogs. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// If it's not timelineable, we can just stop early, since lists | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		// are pretty much subsets of the home timeline, so if it shouldn't | 
					
						
							| 
									
										
										
										
											2023-09-29 10:39:35 +02:00
										 |  |  | 		// appear there, it shouldn't appear in lists either. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		// | 
					
						
							|  |  |  | 		// Exclusive lists don't change this: | 
					
						
							|  |  |  | 		// if something is hometimelineable according to this filter, | 
					
						
							|  |  |  | 		// it's also eligible to appear in exclusive lists, | 
					
						
							|  |  |  | 		// even if it ultimately doesn't appear on the home timeline. | 
					
						
							| 
									
										
										
										
											2025-05-31 17:30:57 +02:00
										 |  |  | 		timelineable, err := s.VisFilter.StatusHomeTimelineable(ctx, | 
					
						
							|  |  |  | 			follow.Account, | 
					
						
							|  |  |  | 			status, | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2025-05-31 17:30:57 +02:00
										 |  |  | 			log.Errorf(ctx, "error checking status home visibility: %v", err) | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !timelineable { | 
					
						
							|  |  |  | 			// Nothing to do. | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-31 17:30:57 +02:00
										 |  |  | 		// Check if the status is muted by this follower. | 
					
						
							|  |  |  | 		muted, err := s.MuteFilter.StatusMuted(ctx, | 
					
						
							|  |  |  | 			follow.Account, | 
					
						
							|  |  |  | 			status, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Errorf(ctx, "error checking status mute: %v", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if muted { | 
					
						
							|  |  |  | 			// Nothing to do. | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		// Add status to any relevant lists for this follow, if applicable. | 
					
						
							|  |  |  | 		listTimelined, exclusive, err := s.listTimelineStatusForFollow(ctx, | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			status, | 
					
						
							|  |  |  | 			follow, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Errorf(ctx, "error list timelining status: %v", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		var homeTimelined bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// If this was timelined into | 
					
						
							|  |  |  | 		// list with exclusive flag set, | 
					
						
							|  |  |  | 		// don't add to home timeline. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		if !exclusive { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Add status to home timeline for owner of | 
					
						
							|  |  |  | 			// this follow (origin account), if applicable. | 
					
						
							| 
									
										
										
										
											2025-05-06 14:19:58 +00:00
										 |  |  | 			if homeTimelined = s.timelineStatus(ctx, | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 				s.State.Caches.Timelines.Home.MustGet(follow.AccountID), | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 				follow.Account, | 
					
						
							|  |  |  | 				status, | 
					
						
							|  |  |  | 				stream.TimelineHome, | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 				gtsmodel.FilterContextHome, | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 			); homeTimelined { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// If hometimelined, add to list of returned account IDs. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 				homeTimelinedAccountIDs = append(homeTimelinedAccountIDs, follow.AccountID) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		if !(homeTimelined || listTimelined) { | 
					
						
							|  |  |  | 			// If status wasn't added to home or list | 
					
						
							|  |  |  | 			// timelines, we shouldn't notify it. | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !*follow.Notify { | 
					
						
							|  |  |  | 			// This follower doesn't have notifs | 
					
						
							|  |  |  | 			// set for this account's new posts. | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if boost || reply { | 
					
						
							|  |  |  | 			// Don't notify for boosts or replies. | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// If we reach here, we know: | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		//   - This status is hometimelineable. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		//   - This status was added to the home timeline and/or list timelines for this follower. | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		//   - This follower wants to be notified when this account posts. | 
					
						
							|  |  |  | 		//   - This is a top-level post (not a reply or boost). | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// That means we can officially notify this one. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | 		if err := s.Notify(ctx, | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			gtsmodel.NotificationStatus, | 
					
						
							| 
									
										
										
										
											2023-11-08 14:32:17 +00:00
										 |  |  | 			follow.Account, | 
					
						
							|  |  |  | 			status.Account, | 
					
						
							| 
									
										
										
										
											2025-05-31 17:30:57 +02:00
										 |  |  | 			status, | 
					
						
							|  |  |  | 			nil, | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		); err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			log.Errorf(ctx, "error notifying status for account: %v", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	return homeTimelinedAccountIDs | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // listTimelineStatusForFollow puts the given status | 
					
						
							|  |  |  | // in any eligible lists owned by the given follower. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | // | 
					
						
							|  |  |  | // It returns whether the status was added to any lists, | 
					
						
							|  |  |  | // and whether the status author is on any exclusive lists | 
					
						
							|  |  |  | // (in which case the status shouldn't be added to the home timeline). | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) listTimelineStatusForFollow( | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	follow *gtsmodel.Follow, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | ) (timelined bool, exclusive bool, err error) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get all lists that contain this given follow. | 
					
						
							|  |  |  | 	lists, err := s.State.DB.GetListsContainingFollowID( | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// We don't need list sub-models. | 
					
						
							|  |  |  | 		gtscontext.SetBarebones(ctx), | 
					
						
							|  |  |  | 		follow.ID, | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		return false, false, gtserror.Newf("error getting lists for follow: %w", err) | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	for _, list := range lists { | 
					
						
							|  |  |  | 		// Check whether list is eligible for this status. | 
					
						
							|  |  |  | 		eligible, err := s.listEligible(ctx, list, status) | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			log.Errorf(ctx, "error checking list eligibility: %v", err) | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !eligible { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		// Update exclusive flag if list is so. | 
					
						
							|  |  |  | 		exclusive = exclusive || *list.Exclusive | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		// At this point we are certain this status | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 		// should be included in timeline of this list. | 
					
						
							|  |  |  | 		listTimelined := s.timelineStatus(ctx, | 
					
						
							|  |  |  | 			s.State.Caches.Timelines.List.MustGet(list.ID), | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			follow.Account, | 
					
						
							|  |  |  | 			status, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			stream.TimelineList+":"+list.ID, // key streamType to this specific list | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 			gtsmodel.FilterContextHome, | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		// Update flag based on if timelined. | 
					
						
							|  |  |  | 		timelined = timelined || listTimelined | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	return timelined, exclusive, nil | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // listEligible checks if the given status is eligible | 
					
						
							|  |  |  | // for inclusion in the list that that the given listEntry | 
					
						
							|  |  |  | // belongs to, based on the replies policy of the list. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) listEligible( | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	list *gtsmodel.List, | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | ) (bool, error) { | 
					
						
							|  |  |  | 	if status.InReplyToURI == "" { | 
					
						
							|  |  |  | 		// If status is not a reply, | 
					
						
							|  |  |  | 		// then it's all gravy baby. | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if status.InReplyToID == "" { | 
					
						
							|  |  |  | 		// Status is a reply but we don't | 
					
						
							|  |  |  | 		// have the replied-to account! | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch list.RepliesPolicy { | 
					
						
							|  |  |  | 	case gtsmodel.RepliesPolicyNone: | 
					
						
							|  |  |  | 		// This list should not show | 
					
						
							|  |  |  | 		// replies at all, so skip it. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case gtsmodel.RepliesPolicyList: | 
					
						
							|  |  |  | 		// This list should show replies | 
					
						
							|  |  |  | 		// only to other people in the list. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// Check if replied-to account is | 
					
						
							|  |  |  | 		// also included in this list. | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		in, err := s.State.DB.IsAccountInList(ctx, | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			list.ID, | 
					
						
							|  |  |  | 			status.InReplyToAccountID, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			err := gtserror.Newf("db error checking if account in list: %w", err) | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			return false, err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		return in, nil | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	case gtsmodel.RepliesPolicyFollowed: | 
					
						
							|  |  |  | 		// This list should show replies | 
					
						
							|  |  |  | 		// only to people that the list | 
					
						
							|  |  |  | 		// owner also follows. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// Check if replied-to account is | 
					
						
							|  |  |  | 		// followed by list owner account. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | 		follows, err := s.State.DB.IsFollowing( | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			ctx, | 
					
						
							|  |  |  | 			list.AccountID, | 
					
						
							|  |  |  | 			status.InReplyToAccountID, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			err := gtserror.Newf("db error checking if account followed: %w", err) | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 			return false, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return follows, nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		log.Panicf(ctx, "unknown reply policy: %s", list.RepliesPolicy) | 
					
						
							|  |  |  | 		return false, nil // unreachable code | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | // timelineStatus will insert the given status into the given timeline, if it's | 
					
						
							|  |  |  | // timelineable. if the status was inserted into the timeline, true will be returned. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) timelineStatus( | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 	timeline *timeline.StatusTimeline, | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	account *gtsmodel.Account, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	streamType string, | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	filterCtx gtsmodel.FilterContext, | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | ) bool { | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	// Check whether status is filtered in this context by timeline account. | 
					
						
							|  |  |  | 	filtered, hide, err := s.StatusFilter.StatusFilterResultsInContext(ctx, | 
					
						
							|  |  |  | 		account, | 
					
						
							|  |  |  | 		status, | 
					
						
							|  |  |  | 		filterCtx, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Errorf(ctx, "error filtering status %s: %v", status.URI, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if hide { | 
					
						
							|  |  |  | 		// Don't even show to | 
					
						
							|  |  |  | 		// timeline account. | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 	// Attempt to convert status to frontend API representation, | 
					
						
							|  |  |  | 	// this will check whether status is filtered / muted. | 
					
						
							|  |  |  | 	apiModel, err := s.Converter.StatusToAPIStatus(ctx, | 
					
						
							| 
									
										
										
										
											2024-05-06 04:49:08 -07:00
										 |  |  | 		status, | 
					
						
							|  |  |  | 		account, | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 		log.Error(ctx, "error converting status %s to frontend: %v", status.URI, err) | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Attach any filter results. | 
					
						
							|  |  |  | 		apiModel.Filtered = filtered | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 	// Insert status to timeline cache regardless of | 
					
						
							|  |  |  | 	// if API model was succesfully prepared or not. | 
					
						
							|  |  |  | 	repeatBoost := timeline.InsertOne(status, apiModel) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !repeatBoost { | 
					
						
							|  |  |  | 		// Only stream if not repeated boost of recent status. | 
					
						
							|  |  |  | 		s.Stream.Update(ctx, account, apiModel, streamType) | 
					
						
							| 
									
										
										
										
											2024-09-23 11:53:42 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 	return true | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | // timelineAndNotifyStatusForTagFollowers inserts the status into the | 
					
						
							|  |  |  | // home timeline of each local account which follows a useable tag from the status, | 
					
						
							|  |  |  | // skipping accounts for which it would have already been inserted. | 
					
						
							|  |  |  | func (s *Surface) timelineAndNotifyStatusForTagFollowers( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	alreadyHomeTimelinedAccountIDs []string, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	tagFollowerAccounts, err := s.tagFollowersForStatus(ctx, status, alreadyHomeTimelinedAccountIDs) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if status.BoostOf != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		// Unwrap boost and work | 
					
						
							|  |  |  | 		// with the original status. | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 		status = status.BoostOf | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Insert the status into the home timeline of each tag follower. | 
					
						
							|  |  |  | 	errs := gtserror.MultiError{} | 
					
						
							|  |  |  | 	for _, tagFollowerAccount := range tagFollowerAccounts { | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 		_ = s.timelineStatus(ctx, | 
					
						
							|  |  |  | 			s.State.Caches.Timelines.Home.MustGet(tagFollowerAccount.ID), | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 			tagFollowerAccount, | 
					
						
							|  |  |  | 			status, | 
					
						
							|  |  |  | 			stream.TimelineHome, | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 			gtsmodel.FilterContextHome, | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 	return errs.Combine() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // tagFollowersForStatus gets local accounts which follow any useable tags from the status, | 
					
						
							|  |  |  | // skipping any with IDs in the provided list, and any that shouldn't be able to see it due to blocks. | 
					
						
							|  |  |  | func (s *Surface) tagFollowersForStatus( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	skipAccountIDs []string, | 
					
						
							|  |  |  | ) ([]*gtsmodel.Account, error) { | 
					
						
							|  |  |  | 	// If the status is a boost, look at the tags from the boosted status. | 
					
						
							|  |  |  | 	taggedStatus := status | 
					
						
							|  |  |  | 	if status.BoostOf != nil { | 
					
						
							|  |  |  | 		taggedStatus = status.BoostOf | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if taggedStatus.Visibility != gtsmodel.VisibilityPublic || len(taggedStatus.Tags) == 0 { | 
					
						
							|  |  |  | 		// Only public statuses with tags are eligible for tag processing. | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Build list of useable tag IDs. | 
					
						
							|  |  |  | 	useableTagIDs := make([]string, 0, len(taggedStatus.Tags)) | 
					
						
							|  |  |  | 	for _, tag := range taggedStatus.Tags { | 
					
						
							|  |  |  | 		if *tag.Useable { | 
					
						
							|  |  |  | 			useableTagIDs = append(useableTagIDs, tag.ID) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(useableTagIDs) == 0 { | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get IDs for all accounts who follow one or more of the useable tags from this status. | 
					
						
							|  |  |  | 	allTagFollowerAccountIDs, err := s.State.DB.GetAccountIDsFollowingTagIDs(ctx, useableTagIDs) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, gtserror.Newf("DB error getting followers for tags of status %s: %w", taggedStatus.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(allTagFollowerAccountIDs) == 0 { | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Build set for faster lookup of account IDs to skip. | 
					
						
							|  |  |  | 	skipAccountIDSet := make(map[string]struct{}, len(skipAccountIDs)) | 
					
						
							|  |  |  | 	for _, accountID := range skipAccountIDs { | 
					
						
							|  |  |  | 		skipAccountIDSet[accountID] = struct{}{} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Build list of tag follower account IDs, | 
					
						
							|  |  |  | 	// except those which have already had this status inserted into their timeline. | 
					
						
							|  |  |  | 	tagFollowerAccountIDs := make([]string, 0, len(allTagFollowerAccountIDs)) | 
					
						
							|  |  |  | 	for _, accountID := range allTagFollowerAccountIDs { | 
					
						
							|  |  |  | 		if _, skip := skipAccountIDSet[accountID]; skip { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		tagFollowerAccountIDs = append(tagFollowerAccountIDs, accountID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(tagFollowerAccountIDs) == 0 { | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Retrieve accounts for remaining tag followers. | 
					
						
							|  |  |  | 	tagFollowerAccounts, err := s.State.DB.GetAccountsByIDs(ctx, tagFollowerAccountIDs) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, gtserror.Newf("DB error getting accounts for followers of tags of status %s: %w", taggedStatus.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check the visibility of the *input* status for each account. | 
					
						
							|  |  |  | 	// This accounts for the visibility of the boost as well as the original, if the input status is a boost. | 
					
						
							|  |  |  | 	errs := gtserror.MultiError{} | 
					
						
							|  |  |  | 	visibleTagFollowerAccounts := make([]*gtsmodel.Account, 0, len(tagFollowerAccounts)) | 
					
						
							|  |  |  | 	for _, account := range tagFollowerAccounts { | 
					
						
							|  |  |  | 		visible, err := s.VisFilter.StatusVisible(ctx, account, status) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			errs.Appendf( | 
					
						
							|  |  |  | 				"error checking visibility of status %s to account %s", | 
					
						
							|  |  |  | 				status.ID, | 
					
						
							|  |  |  | 				account.ID, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if visible { | 
					
						
							|  |  |  | 			visibleTagFollowerAccounts = append(visibleTagFollowerAccounts, account) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return visibleTagFollowerAccounts, errs.Combine() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | // timelineStatusUpdate looks up HOME and LIST timelines of accounts | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | // that follow the the status author or tags and pushes edit messages into any | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | // active streams. | 
					
						
							|  |  |  | // Note that calling invalidateStatusFromTimelines takes care of the | 
					
						
							|  |  |  | // state in general, we just need to do this for any streams that are | 
					
						
							|  |  |  | // open right now. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) timelineStatusUpdate(ctx context.Context, status *gtsmodel.Status) error { | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	// Ensure status fully populated; including account, mentions, etc. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | 	if err := s.State.DB.PopulateStatus(ctx, status); err != nil { | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 		return gtserror.Newf("error populating status with id %s: %w", status.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get all local followers of the account that posted the status. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | 	follows, err := s.State.DB.GetAccountLocalFollowers(ctx, status.AccountID) | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If the poster is also local, add a fake entry for them | 
					
						
							|  |  |  | 	// so they can see their own status in their timeline. | 
					
						
							|  |  |  | 	if status.Account.IsLocal() { | 
					
						
							|  |  |  | 		follows = append(follows, >smodel.Follow{ | 
					
						
							|  |  |  | 			AccountID:   status.AccountID, | 
					
						
							|  |  |  | 			Account:     status.Account, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			Notify:      util.Ptr(false), // Account shouldn't notify itself. | 
					
						
							|  |  |  | 			ShowReblogs: util.Ptr(true),  // Account should show own reblogs. | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	// Push updated status to streams for each local follower of this account. | 
					
						
							|  |  |  | 	homeTimelinedAccountIDs := s.timelineStatusUpdateForFollowers(ctx, status, follows) | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	// Push updated status to streams for each local follower of tags in status, if applicable. | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 	if err := s.timelineStatusUpdateForTagFollowers(ctx, status, homeTimelinedAccountIDs); err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error timelining status %s for tag followers: %w", status.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // timelineStatusUpdateForFollowers iterates through the given | 
					
						
							|  |  |  | // slice of followers of the account that posted the given status, | 
					
						
							|  |  |  | // pushing update messages into open list/home streams of each | 
					
						
							|  |  |  | // follower. | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | // | 
					
						
							|  |  |  | // Returns a list of accounts which had this status updated in their home timelines. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) timelineStatusUpdateForFollowers( | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	follows []*gtsmodel.Follow, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | ) (homeTimelinedAccountIDs []string) { | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	for _, follow := range follows { | 
					
						
							|  |  |  | 		// Check to see if the status is timelineable for this follower, | 
					
						
							|  |  |  | 		// taking account of its visibility, who it replies to, and, if | 
					
						
							|  |  |  | 		// it's a reblog, whether follower account wants to see reblogs. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// If it's not timelineable, we can just stop early, since lists | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		// are pretty much subsets of the home timeline, so if it shouldn't | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 		// appear there, it shouldn't appear in lists either. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		// | 
					
						
							|  |  |  | 		// Exclusive lists don't change this: | 
					
						
							|  |  |  | 		// if something is hometimelineable according to this filter, | 
					
						
							|  |  |  | 		// it's also eligible to appear in exclusive lists, | 
					
						
							|  |  |  | 		// even if it ultimately doesn't appear on the home timeline. | 
					
						
							| 
									
										
										
										
											2024-07-24 13:27:42 +02:00
										 |  |  | 		timelineable, err := s.VisFilter.StatusHomeTimelineable( | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 			ctx, follow.Account, status, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			log.Errorf(ctx, "error checking status home visibility for follow: %v", err) | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !timelineable { | 
					
						
							|  |  |  | 			// Nothing to do. | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		// Add status to relevant lists for this follow, if applicable. | 
					
						
							|  |  |  | 		_, exclusive, err := s.listTimelineStatusUpdateForFollow(ctx, | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 			status, | 
					
						
							|  |  |  | 			follow, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Errorf(ctx, "error list timelining status: %v", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		// If this was timelined into | 
					
						
							|  |  |  | 		// list with exclusive flag set, | 
					
						
							|  |  |  | 		// don't add to home timeline. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 		if exclusive { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		// Add status to home timeline for owner of | 
					
						
							|  |  |  | 		// this follow (origin account), if applicable. | 
					
						
							|  |  |  | 		homeTimelined, err := s.timelineStreamStatusUpdate(ctx, | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 			follow.Account, | 
					
						
							|  |  |  | 			status, | 
					
						
							|  |  |  | 			stream.TimelineHome, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			log.Errorf(ctx, "error home timelining status: %v", err) | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 		if homeTimelined { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			// If hometimelined, add to list of returned account IDs. | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 			homeTimelinedAccountIDs = append(homeTimelinedAccountIDs, follow.AccountID) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	return homeTimelinedAccountIDs | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // listTimelineStatusUpdateForFollow pushes edits of the given status | 
					
						
							|  |  |  | // into any eligible lists streams opened by the given follower. | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | // | 
					
						
							|  |  |  | // It returns whether the status author is on any exclusive lists | 
					
						
							|  |  |  | // (in which case the status shouldn't be added to the home timeline). | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) listTimelineStatusUpdateForFollow( | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	follow *gtsmodel.Follow, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | ) (bool, bool, error) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get all lists that contain this given follow. | 
					
						
							|  |  |  | 	lists, err := s.State.DB.GetListsContainingFollowID( | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// We don't need list sub-models. | 
					
						
							|  |  |  | 		gtscontext.SetBarebones(ctx), | 
					
						
							|  |  |  | 		follow.ID, | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		return false, false, gtserror.Newf("error getting lists for follow: %w", err) | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	var exclusive, timelined bool | 
					
						
							|  |  |  | 	for _, list := range lists { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Check whether list is eligible for this status. | 
					
						
							|  |  |  | 		eligible, err := s.listEligible(ctx, list, status) | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			log.Errorf(ctx, "error checking list eligibility: %v", err) | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !eligible { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		// Update exclusive flag if list is so. | 
					
						
							|  |  |  | 		exclusive = exclusive || *list.Exclusive | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 		// At this point we are certain this status | 
					
						
							|  |  |  | 		// should be included in the timeline of the | 
					
						
							|  |  |  | 		// list that this list entry belongs to. | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		listTimelined, err := s.timelineStreamStatusUpdate( | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 			ctx, | 
					
						
							|  |  |  | 			follow.Account, | 
					
						
							|  |  |  | 			status, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 			stream.TimelineList+":"+list.ID, // key streamType to this specific list | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Errorf(ctx, "error adding status to list timeline: %v", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Update flag based on if timelined. | 
					
						
							|  |  |  | 		timelined = timelined || listTimelined | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-09-09 15:56:58 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	return timelined, exclusive, nil | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // timelineStatusUpdate streams the edited status to the user using the | 
					
						
							|  |  |  | // given streamType. | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | // | 
					
						
							|  |  |  | // Returns whether it was actually streamed. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | func (s *Surface) timelineStreamStatusUpdate( | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	account *gtsmodel.Account, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	streamType string, | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | ) (bool, error) { | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	// Check whether status is filtered in this context by timeline account. | 
					
						
							|  |  |  | 	filtered, hide, err := s.StatusFilter.StatusFilterResultsInContext(ctx, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		account, | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 		status, | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 		gtsmodel.FilterContextHome, | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	) | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false, gtserror.Newf("error filtering status: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	if hide { | 
					
						
							|  |  |  | 		// Don't even show to | 
					
						
							|  |  |  | 		// timeline account. | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 		return false, nil | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	// Convert updated database model to frontend model. | 
					
						
							|  |  |  | 	apiStatus, err := s.Converter.StatusToAPIStatus(ctx, | 
					
						
							|  |  |  | 		status, | 
					
						
							|  |  |  | 		account, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 		return false, gtserror.Newf("error converting status: %w", err) | 
					
						
							| 
									
										
										
										
											2024-05-06 04:49:08 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-04 15:30:39 +02:00
										 |  |  | 	// Attach any filter results. | 
					
						
							|  |  |  | 	apiStatus.Filtered = filtered | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 	// The status was updated so stream it to the user. | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | 	s.Stream.StatusUpdate(ctx, account, apiStatus, streamType) | 
					
						
							| 
									
										
										
										
											2024-09-16 16:46:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-29 11:26:31 -07:00
										 |  |  | 	return true, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // timelineStatusUpdateForTagFollowers streams update notifications to the | 
					
						
							|  |  |  | // home timeline of each local account which follows a tag used by the status, | 
					
						
							|  |  |  | // skipping accounts for which it would have already been streamed. | 
					
						
							|  |  |  | func (s *Surface) timelineStatusUpdateForTagFollowers( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	alreadyHomeTimelinedAccountIDs []string, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	tagFollowerAccounts, err := s.tagFollowersForStatus(ctx, status, alreadyHomeTimelinedAccountIDs) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if status.BoostOf != nil { | 
					
						
							|  |  |  | 		// Unwrap boost and work with the original status. | 
					
						
							|  |  |  | 		status = status.BoostOf | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Stream the update to the home timeline of each tag follower. | 
					
						
							|  |  |  | 	errs := gtserror.MultiError{} | 
					
						
							|  |  |  | 	for _, tagFollowerAccount := range tagFollowerAccounts { | 
					
						
							|  |  |  | 		if _, err := s.timelineStreamStatusUpdate( | 
					
						
							|  |  |  | 			ctx, | 
					
						
							|  |  |  | 			tagFollowerAccount, | 
					
						
							|  |  |  | 			status, | 
					
						
							|  |  |  | 			stream.TimelineHome, | 
					
						
							|  |  |  | 		); err != nil { | 
					
						
							|  |  |  | 			errs.Appendf( | 
					
						
							|  |  |  | 				"error updating status %s on home timeline for account %s: %w", | 
					
						
							|  |  |  | 				status.ID, | 
					
						
							|  |  |  | 				tagFollowerAccount.ID, | 
					
						
							|  |  |  | 				err, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return errs.Combine() | 
					
						
							| 
									
										
										
										
											2023-12-16 11:55:49 +00:00
										 |  |  | } | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // deleteStatusFromTimelines completely removes the given status from all timelines. | 
					
						
							|  |  |  | // It will also stream deletion of the status to all open streams. | 
					
						
							|  |  |  | func (s *Surface) deleteStatusFromTimelines(ctx context.Context, statusID string) { | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.Home.RemoveByStatusIDs(statusID) | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.List.RemoveByStatusIDs(statusID) | 
					
						
							|  |  |  | 	s.Stream.Delete(ctx, statusID) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // invalidateStatusFromTimelines does cache invalidation on the given status by | 
					
						
							|  |  |  | // unpreparing it from all timelines, forcing it to be prepared again (with updated | 
					
						
							|  |  |  | // stats, boost counts, etc) next time it's fetched by the timeline owner. This goes | 
					
						
							|  |  |  | // both for the status itself, and for any boosts of the status. | 
					
						
							|  |  |  | func (s *Surface) invalidateStatusFromTimelines(statusID string) { | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.Home.UnprepareByStatusIDs(statusID) | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.List.UnprepareByStatusIDs(statusID) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // removeTimelineEntriesByAccount removes all cached timeline entries authored by account ID. | 
					
						
							|  |  |  | func (s *Surface) removeTimelineEntriesByAccount(accountID string) { | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.Home.RemoveByAccountIDs(accountID) | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.List.RemoveByAccountIDs(accountID) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-02 16:01:30 +00:00
										 |  |  | // removeTimelineEntriesByAccount invalidates all cached timeline entries authored by account ID. | 
					
						
							|  |  |  | func (s *Surface) invalidateTimelineEntriesByAccount(accountID string) { | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.Home.UnprepareByAccountIDs(accountID) | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.List.UnprepareByAccountIDs(accountID) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
											
										 
											2025-04-26 09:56:15 +00:00
										 |  |  | func (s *Surface) removeRelationshipFromTimelines(ctx context.Context, timelineAccountID string, targetAccountID string) { | 
					
						
							|  |  |  | 	// Remove all statuses by target account | 
					
						
							|  |  |  | 	// from given account's home timeline. | 
					
						
							|  |  |  | 	s.State.Caches.Timelines.Home. | 
					
						
							|  |  |  | 		MustGet(timelineAccountID). | 
					
						
							|  |  |  | 		RemoveByAccountIDs(targetAccountID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the IDs of all the lists owned by the given account ID. | 
					
						
							|  |  |  | 	listIDs, err := s.State.DB.GetListIDsByAccountID(ctx, timelineAccountID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Errorf(ctx, "error getting lists for account %s: %v", timelineAccountID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, listID := range listIDs { | 
					
						
							|  |  |  | 		// Remove all statuses by target account | 
					
						
							|  |  |  | 		// from given account's list timelines. | 
					
						
							|  |  |  | 		s.State.Caches.Timelines.List.MustGet(listID). | 
					
						
							|  |  |  | 			RemoveByAccountIDs(targetAccountID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |