| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2022-09-12 13:03:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | package dereferencing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2022-09-12 13:03:23 +02:00
										 |  |  | 	"io" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2024-06-06 08:50:14 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2022-09-12 13:03:23 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | 
					
						
							| 
									
										
										
										
											2024-02-09 12:38:51 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | 
					
						
							| 
									
										
										
										
											2022-09-12 13:03:23 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | // GetEmoji fetches the emoji with given shortcode, | 
					
						
							|  |  |  | // domain and remote URL to dereference it by. This | 
					
						
							|  |  |  | // handles the case of existing emojis by passing them | 
					
						
							|  |  |  | // to RefreshEmoji(), which in the case of a local | 
					
						
							|  |  |  | // emoji will be a no-op. If the emoji does not yet | 
					
						
							|  |  |  | // exist it will be newly inserted into the database | 
					
						
							|  |  |  | // followed by dereferencing the actual media file. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Please note that even if an error is returned, | 
					
						
							|  |  |  | // an emoji model may still be returned if the error | 
					
						
							|  |  |  | // was only encountered during actual dereferencing. | 
					
						
							|  |  |  | // In this case, it will act as a placeholder. | 
					
						
							|  |  |  | func (d *Dereferencer) GetEmoji( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	shortcode string, | 
					
						
							|  |  |  | 	domain string, | 
					
						
							|  |  |  | 	remoteURL string, | 
					
						
							|  |  |  | 	info media.AdditionalEmojiInfo, | 
					
						
							|  |  |  | 	refresh bool, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*gtsmodel.Emoji, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	// Look for an existing emoji with shortcode domain. | 
					
						
							|  |  |  | 	emoji, err := d.state.DB.GetEmojiByShortcodeDomain(ctx, | 
					
						
							|  |  |  | 		shortcode, | 
					
						
							|  |  |  | 		domain, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		return nil, gtserror.Newf("error fetching emoji from db: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if emoji != nil { | 
					
						
							|  |  |  | 		// This was an existing emoji, pass to refresh func. | 
					
						
							|  |  |  | 		return d.RefreshEmoji(ctx, emoji, info, refresh) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if domain == "" { | 
					
						
							|  |  |  | 		// failed local lookup, will be db.ErrNoEntries. | 
					
						
							|  |  |  | 		return nil, gtserror.SetUnretrievable(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Generate shortcode domain for locks + logging. | 
					
						
							|  |  |  | 	shortcodeDomain := shortcode + "@" + domain | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Pass along for safe processing. | 
					
						
							|  |  |  | 	return d.processEmojiSafely(ctx, | 
					
						
							|  |  |  | 		shortcodeDomain, | 
					
						
							|  |  |  | 		func() (*media.ProcessingEmoji, error) { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Ensure we have a valid remote URL. | 
					
						
							|  |  |  | 			url, err := url.Parse(remoteURL) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				err := gtserror.Newf("invalid image remote url %s for emoji %s: %w", remoteURL, shortcodeDomain, err) | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Acquire new instance account transport for emoji dereferencing. | 
					
						
							|  |  |  | 			tsport, err := d.transportController.NewTransportForUsername(ctx, "") | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				err := gtserror.Newf("error getting instance transport: %w", err) | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Get maximum supported remote emoji size. | 
					
						
							| 
									
										
										
										
											2024-10-16 14:13:58 +02:00
										 |  |  | 			maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated. | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Prepare data function to dereference remote emoji media. | 
					
						
							|  |  |  | 			data := func(context.Context) (io.ReadCloser, error) { | 
					
						
							| 
									
										
										
										
											2024-10-16 14:13:58 +02:00
										 |  |  | 				return tsport.DereferenceMedia(ctx, url, maxsz) | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Create new emoji with prepared info. | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			return d.mediaManager.CreateEmoji(ctx, | 
					
						
							|  |  |  | 				shortcode, | 
					
						
							|  |  |  | 				domain, | 
					
						
							|  |  |  | 				data, | 
					
						
							|  |  |  | 				info, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RefreshEmoji ensures that the given emoji is | 
					
						
							|  |  |  | // up-to-date, both in terms of being cached in | 
					
						
							|  |  |  | // in local instance storage, and compared to extra | 
					
						
							|  |  |  | // information provided in media.AdditionEmojiInfo{}. | 
					
						
							|  |  |  | // (note that is a no-op to pass in a local emoji). | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Please note that even if an error is returned, | 
					
						
							|  |  |  | // an emoji model may still be returned if the error | 
					
						
							|  |  |  | // was only encountered during actual dereferencing. | 
					
						
							|  |  |  | // In this case, it will act as a placeholder. | 
					
						
							|  |  |  | func (d *Dereferencer) RefreshEmoji( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	emoji *gtsmodel.Emoji, | 
					
						
							|  |  |  | 	info media.AdditionalEmojiInfo, | 
					
						
							|  |  |  | 	force bool, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*gtsmodel.Emoji, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2025-03-03 16:14:27 +00:00
										 |  |  | 	// Check uri up-to-date. | 
					
						
							|  |  |  | 	if info.URI != nil && | 
					
						
							|  |  |  | 		*info.URI != emoji.URI { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		emoji.URI = *info.URI | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		force = true | 
					
						
							| 
									
										
										
										
											2025-03-03 16:14:27 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check image remote URL up-to-date. | 
					
						
							|  |  |  | 	if info.ImageRemoteURL != nil && | 
					
						
							|  |  |  | 		*info.ImageRemoteURL != emoji.ImageRemoteURL { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		emoji.ImageRemoteURL = *info.ImageRemoteURL | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		force = true | 
					
						
							| 
									
										
										
										
											2025-03-03 16:14:27 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check image static remote URL up-to-date. | 
					
						
							|  |  |  | 	if info.ImageStaticRemoteURL != nil && | 
					
						
							|  |  |  | 		*info.ImageStaticRemoteURL != emoji.ImageStaticRemoteURL { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		emoji.ImageStaticRemoteURL = *info.ImageStaticRemoteURL | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		force = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 	// Check if needs | 
					
						
							|  |  |  | 	// force refresh. | 
					
						
							|  |  |  | 	if !force { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// We still want to make sure | 
					
						
							|  |  |  | 		// the emoji is cached. Simply | 
					
						
							|  |  |  | 		// check whether emoji is cached. | 
					
						
							|  |  |  | 		return d.RecacheEmoji(ctx, emoji) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Can't refresh local. | 
					
						
							|  |  |  | 	if emoji.IsLocal() { | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		return emoji, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// Get shortcode domain for locks + logging. | 
					
						
							|  |  |  | 	shortcodeDomain := emoji.ShortcodeDomain() | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure we have a valid image remote URL. | 
					
						
							|  |  |  | 	url, err := url.Parse(emoji.ImageRemoteURL) | 
					
						
							| 
									
										
										
										
											2023-10-31 11:12:22 +00:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		err := gtserror.Newf("invalid image remote url %s for emoji %s: %w", emoji.ImageRemoteURL, shortcodeDomain, err) | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2023-10-31 11:12:22 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Pass along for safe processing. | 
					
						
							|  |  |  | 	return d.processEmojiSafely(ctx, | 
					
						
							|  |  |  | 		shortcodeDomain, | 
					
						
							|  |  |  | 		func() (*media.ProcessingEmoji, error) { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Acquire new instance account transport for emoji dereferencing. | 
					
						
							|  |  |  | 			tsport, err := d.transportController.NewTransportForUsername(ctx, "") | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				err := gtserror.Newf("error getting instance transport: %w", err) | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Get maximum supported remote emoji size. | 
					
						
							| 
									
										
										
										
											2024-10-16 14:13:58 +02:00
										 |  |  | 			maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated. | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Prepare data function to dereference remote emoji media. | 
					
						
							|  |  |  | 			data := func(context.Context) (io.ReadCloser, error) { | 
					
						
							| 
									
										
										
										
											2024-10-16 14:13:58 +02:00
										 |  |  | 				return tsport.DereferenceMedia(ctx, url, maxsz) | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 			// Update emoji with prepared info. | 
					
						
							|  |  |  | 			return d.mediaManager.UpdateEmoji(ctx, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 				emoji, | 
					
						
							|  |  |  | 				data, | 
					
						
							|  |  |  | 				info, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | // RecacheEmoji handles the simplest case which is that | 
					
						
							|  |  |  | // of an existing emoji that only needs to be recached. | 
					
						
							|  |  |  | // It handles the case of both local emojis, and those | 
					
						
							|  |  |  | // already cached as no-ops. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Please note that even if an error is returned, | 
					
						
							|  |  |  | // an emoji model may still be returned if the error | 
					
						
							|  |  |  | // was only encountered during actual dereferencing. | 
					
						
							|  |  |  | // In this case, it will act as a placeholder. | 
					
						
							|  |  |  | func (d *Dereferencer) RecacheEmoji( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	emoji *gtsmodel.Emoji, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*gtsmodel.Emoji, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	// Can't recache local. | 
					
						
							|  |  |  | 	if emoji.IsLocal() { | 
					
						
							|  |  |  | 		return emoji, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if *emoji.Cached { | 
					
						
							|  |  |  | 		// Already cached. | 
					
						
							|  |  |  | 		return emoji, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get shortcode domain for locks + logging. | 
					
						
							|  |  |  | 	shortcodeDomain := emoji.ShortcodeDomain() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure we have a valid image remote URL. | 
					
						
							|  |  |  | 	url, err := url.Parse(emoji.ImageRemoteURL) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("invalid image remote url %s for emoji %s: %w", emoji.ImageRemoteURL, shortcodeDomain, err) | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Pass along for safe processing. | 
					
						
							|  |  |  | 	return d.processEmojiSafely(ctx, | 
					
						
							|  |  |  | 		shortcodeDomain, | 
					
						
							|  |  |  | 		func() (*media.ProcessingEmoji, error) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Acquire new instance account transport for emoji dereferencing. | 
					
						
							|  |  |  | 			tsport, err := d.transportController.NewTransportForUsername(ctx, "") | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				err := gtserror.Newf("error getting instance transport: %w", err) | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Get maximum supported remote emoji size. | 
					
						
							| 
									
										
										
										
											2024-10-16 14:13:58 +02:00
										 |  |  | 			maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated. | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Prepare data function to dereference remote emoji media. | 
					
						
							|  |  |  | 			data := func(context.Context) (io.ReadCloser, error) { | 
					
						
							| 
									
										
										
										
											2024-10-16 14:13:58 +02:00
										 |  |  | 				return tsport.DereferenceMedia(ctx, url, maxsz) | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Recache emoji with prepared info. | 
					
						
							|  |  |  | 			return d.mediaManager.CacheEmoji(ctx, | 
					
						
							|  |  |  | 				emoji, | 
					
						
							|  |  |  | 				data, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | // processingEmojiSafely provides concurrency-safe processing of | 
					
						
							|  |  |  | // an emoji with given shortcode+domain. if a copy of the emoji is | 
					
						
							|  |  |  | // not already being processed, the given 'process' callback will | 
					
						
							|  |  |  | // be used to generate new *media.ProcessingEmoji{} instance. | 
					
						
							|  |  |  | func (d *Dereferencer) processEmojiSafely( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	shortcodeDomain string, | 
					
						
							|  |  |  | 	process func() (*media.ProcessingEmoji, error), | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	emoji *gtsmodel.Emoji, | 
					
						
							|  |  |  | 	err error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Acquire map lock. | 
					
						
							| 
									
										
										
										
											2024-06-06 08:50:14 +00:00
										 |  |  | 	d.derefEmojisMu.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure unlock only done once. | 
					
						
							|  |  |  | 	unlock := d.derefEmojisMu.Unlock | 
					
						
							| 
									
										
										
										
											2024-02-09 12:38:51 +01:00
										 |  |  | 	unlock = util.DoOnce(unlock) | 
					
						
							| 
									
										
										
										
											2023-02-10 20:15:23 +00:00
										 |  |  | 	defer unlock() | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 08:50:14 +00:00
										 |  |  | 	// Look for an existing dereference in progress. | 
					
						
							|  |  |  | 	processing, ok := d.derefEmojis[shortcodeDomain] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		// Start new processing emoji. | 
					
						
							|  |  |  | 		processing, err = process() | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			return nil, err | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-03 13:30:41 +00:00
										 |  |  | 		// Add processing emoji media to hash map. | 
					
						
							|  |  |  | 		d.derefEmojis[shortcodeDomain] = processing | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		defer func() { | 
					
						
							|  |  |  | 			// Remove on finish. | 
					
						
							|  |  |  | 			d.derefEmojisMu.Lock() | 
					
						
							|  |  |  | 			delete(d.derefEmojis, shortcodeDomain) | 
					
						
							|  |  |  | 			d.derefEmojisMu.Unlock() | 
					
						
							|  |  |  | 		}() | 
					
						
							| 
									
										
										
										
											2022-09-12 13:03:23 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-10 20:15:23 +00:00
										 |  |  | 	// Unlock map. | 
					
						
							|  |  |  | 	unlock() | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Perform emoji load operation. | 
					
						
							|  |  |  | 	emoji, err = processing.Load(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err = gtserror.Newf("error loading emoji %s: %w", shortcodeDomain, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// TODO: in time we should return checkable flags by gtserror.Is___() | 
					
						
							|  |  |  | 		// which can determine if loading error should allow remaining placeholder. | 
					
						
							| 
									
										
										
										
											2022-09-12 13:03:23 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	return | 
					
						
							| 
									
										
										
										
											2022-09-12 13:03:23 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | func (d *Dereferencer) fetchEmojis( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	existing []*gtsmodel.Emoji, | 
					
						
							|  |  |  | 	emojis []*gtsmodel.Emoji, // newly dereferenced | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	[]*gtsmodel.Emoji, | 
					
						
							|  |  |  | 	bool, // any changes? | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	// Track any changes. | 
					
						
							|  |  |  | 	changed := false | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	for i, placeholder := range emojis { | 
					
						
							|  |  |  | 		// Look for an existing emoji with shortcode + domain. | 
					
						
							|  |  |  | 		existing, ok := getEmojiByShortcodeDomain(existing, | 
					
						
							|  |  |  | 			placeholder.Shortcode, | 
					
						
							|  |  |  | 			placeholder.Domain, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if ok && existing.ID != "" { | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			// Check for any emoji changes that | 
					
						
							|  |  |  | 			// indicate we should force a refresh. | 
					
						
							|  |  |  | 			force := emojiChanged(existing, placeholder) | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			// Ensure that the existing emoji model is up-to-date and cached. | 
					
						
							|  |  |  | 			existing, err := d.RefreshEmoji(ctx, existing, media.AdditionalEmojiInfo{ | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 				// Set latest values from placeholder. | 
					
						
							|  |  |  | 				URI:                  &placeholder.URI, | 
					
						
							|  |  |  | 				ImageRemoteURL:       &placeholder.ImageRemoteURL, | 
					
						
							|  |  |  | 				ImageStaticRemoteURL: &placeholder.ImageStaticRemoteURL, | 
					
						
							|  |  |  | 			}, force) | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 				log.Errorf(ctx, "error refreshing emoji: %v", err) | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 				// specifically do NOT continue here, | 
					
						
							|  |  |  | 				// we already have a model, we don't | 
					
						
							|  |  |  | 				// want to drop it from the slice, just | 
					
						
							|  |  |  | 				// log that an update for it failed. | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			// Set existing emoji. | 
					
						
							|  |  |  | 			emojis[i] = existing | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Emojis changed! | 
					
						
							|  |  |  | 		changed = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Fetch this newly added emoji, | 
					
						
							|  |  |  | 		// this function handles the case | 
					
						
							|  |  |  | 		// of existing cached emojis and | 
					
						
							|  |  |  | 		// new ones requiring dereference. | 
					
						
							|  |  |  | 		emoji, err := d.GetEmoji(ctx, | 
					
						
							|  |  |  | 			placeholder.Shortcode, | 
					
						
							|  |  |  | 			placeholder.Domain, | 
					
						
							|  |  |  | 			placeholder.ImageRemoteURL, | 
					
						
							|  |  |  | 			media.AdditionalEmojiInfo{ | 
					
						
							|  |  |  | 				URI:                  &placeholder.URI, | 
					
						
							|  |  |  | 				ImageRemoteURL:       &placeholder.ImageRemoteURL, | 
					
						
							|  |  |  | 				ImageStaticRemoteURL: &placeholder.ImageStaticRemoteURL, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			if emoji == nil { | 
					
						
							|  |  |  | 				log.Errorf(ctx, "error loading emoji %s: %v", placeholder.ImageRemoteURL, err) | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// non-fatal error occurred during loading, still use it. | 
					
						
							|  |  |  | 			log.Warnf(ctx, "partially loaded emoji: %v", err) | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		// Set updated emoji. | 
					
						
							|  |  |  | 		emojis[i] = emoji | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < len(emojis); { | 
					
						
							|  |  |  | 		if emojis[i].ID == "" { | 
					
						
							|  |  |  | 			// Remove failed emoji populations. | 
					
						
							|  |  |  | 			copy(emojis[i:], emojis[i+1:]) | 
					
						
							|  |  |  | 			emojis = emojis[:len(emojis)-1] | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		i++ | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	return emojis, changed, nil | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | } |