| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2021-02-28 15:17:18 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-09 17:03:40 +01:00
										 |  |  | package media | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-05-17 19:06:58 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2023-05-28 13:08:35 +01:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-26 15:34:10 +02:00
										 |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/gtserror" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/gtsmodel" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/id" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/log" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/state" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/storage" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/uris" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/util" | 
					
						
							| 
									
										
										
										
											2023-05-28 13:08:35 +01:00
										 |  |  | 	"codeberg.org/gruf/go-iotools" | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-11 12:48:38 +01:00
										 |  |  | var SupportedMIMETypes = []string{ | 
					
						
							| 
									
										
										
										
											2024-07-15 14:24:53 +00:00
										 |  |  | 	"image/jpeg", // .jpeg | 
					
						
							|  |  |  | 	"image/gif",  // .gif | 
					
						
							|  |  |  | 	"image/webp", // .webp | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-28 11:02:12 +01:00
										 |  |  | 	"audio/mp2",  // .mp2 | 
					
						
							|  |  |  | 	"audio/mp3",  // .mp3 | 
					
						
							|  |  |  | 	"audio/mpeg", // .mp1, .mp2, .mp3 | 
					
						
							| 
									
										
										
										
											2024-07-15 14:24:53 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"video/x-msvideo", // .avi | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-21 13:42:51 +01:00
										 |  |  | 	"audio/flac",   // .flac | 
					
						
							|  |  |  | 	"audio/x-flac", // .flac | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-15 14:24:53 +00:00
										 |  |  | 	// png types | 
					
						
							|  |  |  | 	"image/png",  // .png | 
					
						
							|  |  |  | 	"image/apng", // .apng | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ogg types | 
					
						
							|  |  |  | 	"audio/ogg", // .ogg | 
					
						
							|  |  |  | 	"video/ogg", // .ogv | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// mpeg4 types | 
					
						
							| 
									
										
										
										
											2024-11-04 13:58:15 +00:00
										 |  |  | 	"audio/mp4",       // .m4a | 
					
						
							| 
									
										
										
										
											2024-07-15 14:24:53 +00:00
										 |  |  | 	"video/mp4",       // .mp4 | 
					
						
							|  |  |  | 	"video/quicktime", // .mov | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// asf types | 
					
						
							|  |  |  | 	"audio/x-ms-wma", // .wma | 
					
						
							|  |  |  | 	"video/x-ms-wmv", // .wmv | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// matroska types | 
					
						
							|  |  |  | 	"video/webm",       // .webm | 
					
						
							|  |  |  | 	"audio/x-matroska", // .mka | 
					
						
							|  |  |  | 	"video/x-matroska", // .mkv | 
					
						
							| 
									
										
										
										
											2023-02-11 12:48:38 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-15 16:45:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-11 12:48:38 +01:00
										 |  |  | var SupportedEmojiMIMETypes = []string{ | 
					
						
							| 
									
										
										
										
											2024-07-15 14:24:53 +00:00
										 |  |  | 	"image/jpeg", // .jpeg | 
					
						
							|  |  |  | 	"image/gif",  // .gif | 
					
						
							|  |  |  | 	"image/webp", // .webp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// png types | 
					
						
							|  |  |  | 	"image/png",  // .png | 
					
						
							|  |  |  | 	"image/apng", // .apng | 
					
						
							| 
									
										
										
										
											2023-02-11 12:48:38 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-06-30 12:22:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-28 13:08:35 +01:00
										 |  |  | type Manager struct { | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	state *state.State | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | // NewManager returns a media manager with given state. | 
					
						
							| 
									
										
										
										
											2023-05-28 13:08:35 +01:00
										 |  |  | func NewManager(state *state.State) *Manager { | 
					
						
							| 
									
										
										
										
											2023-11-30 10:50:28 +01:00
										 |  |  | 	return &Manager{state: state} | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | // CreateMedia creates a new media attachment entry | 
					
						
							|  |  |  | // in the database for given owning account ID and | 
					
						
							|  |  |  | // extra information, and prepares a new processing | 
					
						
							|  |  |  | // media entry to dereference it using the given | 
					
						
							|  |  |  | // data function, decode the media and finish filling | 
					
						
							|  |  |  | // out remaining media fields (e.g. type, path, etc). | 
					
						
							|  |  |  | func (m *Manager) CreateMedia( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	accountID string, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	data DataFunc, | 
					
						
							|  |  |  | 	info AdditionalMediaInfo, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*ProcessingMedia, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	now := time.Now() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Populate initial fields on the new media, | 
					
						
							|  |  |  | 	// leaving out fields with values we don't know | 
					
						
							|  |  |  | 	// yet. These will be overwritten as we go. | 
					
						
							|  |  |  | 	attachment := >smodel.MediaAttachment{ | 
					
						
							| 
									
										
										
										
											2024-07-17 15:26:33 +00:00
										 |  |  | 		ID:         id.NewULID(), | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		AccountID:  accountID, | 
					
						
							| 
									
										
										
										
											2024-07-17 15:26:33 +00:00
										 |  |  | 		Type:       gtsmodel.FileTypeUnknown, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		Processing: gtsmodel.ProcessingStatusReceived, | 
					
						
							| 
									
										
										
										
											2024-07-17 15:26:33 +00:00
										 |  |  | 		Avatar:     util.Ptr(false), | 
					
						
							|  |  |  | 		Header:     util.Ptr(false), | 
					
						
							|  |  |  | 		Cached:     util.Ptr(false), | 
					
						
							|  |  |  | 		CreatedAt:  now, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Check if we were provided additional info | 
					
						
							|  |  |  | 	// to add to the attachment, and overwrite | 
					
						
							|  |  |  | 	// some of the attachment fields if so. | 
					
						
							|  |  |  | 	if info.StatusID != nil { | 
					
						
							|  |  |  | 		attachment.StatusID = *info.StatusID | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.RemoteURL != nil { | 
					
						
							|  |  |  | 		attachment.RemoteURL = *info.RemoteURL | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.Description != nil { | 
					
						
							|  |  |  | 		attachment.Description = *info.Description | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.ScheduledStatusID != nil { | 
					
						
							|  |  |  | 		attachment.ScheduledStatusID = *info.ScheduledStatusID | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.Blurhash != nil { | 
					
						
							|  |  |  | 		attachment.Blurhash = *info.Blurhash | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.Avatar != nil { | 
					
						
							|  |  |  | 		attachment.Avatar = info.Avatar | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.Header != nil { | 
					
						
							|  |  |  | 		attachment.Header = info.Header | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.FocusX != nil { | 
					
						
							|  |  |  | 		attachment.FileMeta.Focus.X = *info.FocusX | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.FocusY != nil { | 
					
						
							|  |  |  | 		attachment.FileMeta.Focus.Y = *info.FocusY | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Store attachment in database in initial form. | 
					
						
							|  |  |  | 	err := m.state.DB.PutAttachment(ctx, attachment) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2022-05-07 16:36:01 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Pass prepared media as ready to be cached. | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 	return m.CacheMedia(attachment, data), nil | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | // CacheMedia wraps a media model (assumed already | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | // inserted in the database!) with given data function | 
					
						
							|  |  |  | // to perform a blocking dereference / decode operation | 
					
						
							|  |  |  | // from the data stream returned. | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | func (m *Manager) CacheMedia( | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	media *gtsmodel.MediaAttachment, | 
					
						
							|  |  |  | 	data DataFunc, | 
					
						
							|  |  |  | ) *ProcessingMedia { | 
					
						
							|  |  |  | 	return &ProcessingMedia{ | 
					
						
							|  |  |  | 		media:  media, | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 		dataFn: data, | 
					
						
							|  |  |  | 		mgr:    m, | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | // CreateEmoji creates a new emoji entry in the | 
					
						
							|  |  |  | // database for given shortcode, domain and extra | 
					
						
							|  |  |  | // information, and prepares a new processing emoji | 
					
						
							|  |  |  | // entry to dereference it using the given data | 
					
						
							|  |  |  | // function, decode the media and finish filling | 
					
						
							|  |  |  | // out remaining fields (e.g. type, path, etc). | 
					
						
							|  |  |  | func (m *Manager) CreateEmoji( | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	shortcode string, | 
					
						
							|  |  |  | 	domain string, | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	data DataFunc, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	info AdditionalEmojiInfo, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*ProcessingEmoji, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	now := time.Now() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Generate new ID. | 
					
						
							|  |  |  | 	id := id.NewULID() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if domain == "" && info.URI == nil { | 
					
						
							|  |  |  | 		// Generate URI for local emoji. | 
					
						
							|  |  |  | 		uri := uris.URIForEmoji(id) | 
					
						
							|  |  |  | 		info.URI = &uri | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Populate initial fields on the new emoji, | 
					
						
							|  |  |  | 	// leaving out fields with values we don't know | 
					
						
							|  |  |  | 	// yet. These will be overwritten as we go. | 
					
						
							|  |  |  | 	emoji := >smodel.Emoji{ | 
					
						
							| 
									
										
										
										
											2024-07-17 15:26:33 +00:00
										 |  |  | 		ID:              id, | 
					
						
							|  |  |  | 		Shortcode:       shortcode, | 
					
						
							|  |  |  | 		Domain:          domain, | 
					
						
							|  |  |  | 		Disabled:        util.Ptr(false), | 
					
						
							|  |  |  | 		VisibleInPicker: util.Ptr(true), | 
					
						
							|  |  |  | 		CreatedAt:       now, | 
					
						
							|  |  |  | 		UpdatedAt:       now, | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Finally, create new emoji. | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 	return m.createOrUpdateEmoji(ctx, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		m.state.DB.PutEmoji, | 
					
						
							|  |  |  | 		data, | 
					
						
							|  |  |  | 		emoji, | 
					
						
							|  |  |  | 		info, | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | // UpdateEmoji prepares an update operation for the given emoji, | 
					
						
							|  |  |  | // which is assumed to already exist in the database. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Calling load on the returned *ProcessingEmoji will update the | 
					
						
							|  |  |  | // db entry with provided extra information, ensure emoji images | 
					
						
							|  |  |  | // are cached, and use new storage paths for the dereferenced media | 
					
						
							|  |  |  | // files to skirt around browser caching of the old files. | 
					
						
							|  |  |  | func (m *Manager) UpdateEmoji( | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	emoji *gtsmodel.Emoji, | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	data DataFunc, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	info AdditionalEmojiInfo, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*ProcessingEmoji, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	// Create references to old emoji image | 
					
						
							|  |  |  | 	// paths before they get updated with new | 
					
						
							|  |  |  | 	// path ID. These are required for later | 
					
						
							|  |  |  | 	// deleting the old image files on refresh. | 
					
						
							| 
									
										
										
										
											2024-07-03 15:53:54 -07:00
										 |  |  | 	shortcodeDomain := emoji.ShortcodeDomain() | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	oldStaticPath := emoji.ImageStaticPath | 
					
						
							|  |  |  | 	oldPath := emoji.ImagePath | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Since this is a refresh we will end up storing new images at new | 
					
						
							|  |  |  | 	// paths, so we should wrap closer to delete old paths at completion. | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 	wrapped := func(ctx context.Context) (io.ReadCloser, error) { | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 		// Call original func. | 
					
						
							|  |  |  | 		rc, err := data(ctx) | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 			return nil, err | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 		// Cast as separated reader / closer types. | 
					
						
							|  |  |  | 		rct, ok := rc.(*iotools.ReadCloserType) | 
					
						
							| 
									
										
										
										
											2023-05-28 13:08:35 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 		if !ok { | 
					
						
							|  |  |  | 			// Allocate new read closer type. | 
					
						
							|  |  |  | 			rct = new(iotools.ReadCloserType) | 
					
						
							|  |  |  | 			rct.Reader = rc | 
					
						
							|  |  |  | 			rct.Closer = rc | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Wrap underlying io.Closer type to cleanup old data. | 
					
						
							|  |  |  | 		rct.Closer = iotools.CloserCallback(rct.Closer, func() { | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			// Remove any *old* emoji image file path now stream is closed. | 
					
						
							|  |  |  | 			if err := m.state.Storage.Delete(ctx, oldPath); err != nil && | 
					
						
							|  |  |  | 				!storage.IsNotFound(err) { | 
					
						
							|  |  |  | 				log.Errorf(ctx, "error deleting old emoji %s from storage: %v", shortcodeDomain, err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			// Remove any *old* emoji static image file path now stream is closed. | 
					
						
							|  |  |  | 			if err := m.state.Storage.Delete(ctx, oldStaticPath); err != nil && | 
					
						
							|  |  |  | 				!storage.IsNotFound(err) { | 
					
						
							|  |  |  | 				log.Errorf(ctx, "error deleting old static emoji %s from storage: %v", shortcodeDomain, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 		return rct, nil | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 	// Update existing emoji in database. | 
					
						
							|  |  |  | 	processingEmoji, err := m.createOrUpdateEmoji(ctx, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		func(ctx context.Context, emoji *gtsmodel.Emoji) error { | 
					
						
							|  |  |  | 			return m.state.DB.UpdateEmoji(ctx, emoji) | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		wrapped, | 
					
						
							|  |  |  | 		emoji, | 
					
						
							|  |  |  | 		info, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2021-05-21 15:48:26 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-17 15:26:33 +00:00
										 |  |  | 	// Generate a new path ID to use instead. | 
					
						
							|  |  |  | 	processingEmoji.newPathID = id.NewULID() | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	return processingEmoji, nil | 
					
						
							| 
									
										
										
										
											2022-01-08 17:17:01 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | // CacheEmoji wraps an emoji model (assumed already | 
					
						
							|  |  |  | // inserted in the database!) with given data function | 
					
						
							|  |  |  | // to perform a blocking dereference / decode operation | 
					
						
							|  |  |  | // from the data stream returned. | 
					
						
							|  |  |  | func (m *Manager) CacheEmoji( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	emoji *gtsmodel.Emoji, | 
					
						
							|  |  |  | 	data DataFunc, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*ProcessingEmoji, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	// Fetch the local instance account for emoji path generation. | 
					
						
							|  |  |  | 	instanceAcc, err := m.state.DB.GetInstanceAccount(ctx, "") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, gtserror.Newf("error fetching instance account: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var pathID string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Look for an emoji path ID that differs from its actual ID, this indicates | 
					
						
							|  |  |  | 	// a previous 'refresh'. We need to be sure to set this on the ProcessingEmoji{} | 
					
						
							|  |  |  | 	// so it knows to store the emoji under this path, and not default to emoji.ID. | 
					
						
							|  |  |  | 	if id := extractEmojiPathID(emoji.ImagePath); id != emoji.ID { | 
					
						
							|  |  |  | 		pathID = id | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &ProcessingEmoji{ | 
					
						
							|  |  |  | 		newPathID: pathID, | 
					
						
							|  |  |  | 		instAccID: instanceAcc.ID, | 
					
						
							|  |  |  | 		emoji:     emoji, | 
					
						
							|  |  |  | 		dataFn:    data, | 
					
						
							|  |  |  | 		mgr:       m, | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // createOrUpdateEmoji updates the emoji according to | 
					
						
							|  |  |  | // provided additional data, and performs the actual | 
					
						
							|  |  |  | // database write, finally returning an emoji ready | 
					
						
							|  |  |  | // for processing (i.e. caching to local storage). | 
					
						
							|  |  |  | func (m *Manager) createOrUpdateEmoji( | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 	storeDB func(context.Context, *gtsmodel.Emoji) error, | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	data DataFunc, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	emoji *gtsmodel.Emoji, | 
					
						
							|  |  |  | 	info AdditionalEmojiInfo, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*ProcessingEmoji, | 
					
						
							|  |  |  | 	error, | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2024-07-17 15:26:33 +00:00
										 |  |  | 	// Fetch the local instance account for emoji path generation. | 
					
						
							|  |  |  | 	instanceAcc, err := m.state.DB.GetInstanceAccount(ctx, "") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, gtserror.Newf("error fetching instance account: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Check if we have additional info to add to the emoji, | 
					
						
							|  |  |  | 	// and overwrite some of the emoji fields if so. | 
					
						
							|  |  |  | 	if info.URI != nil { | 
					
						
							|  |  |  | 		emoji.URI = *info.URI | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.Domain != nil { | 
					
						
							|  |  |  | 		emoji.Domain = *info.Domain | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.ImageRemoteURL != nil { | 
					
						
							|  |  |  | 		emoji.ImageRemoteURL = *info.ImageRemoteURL | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.ImageStaticRemoteURL != nil { | 
					
						
							|  |  |  | 		emoji.ImageStaticRemoteURL = *info.ImageStaticRemoteURL | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.Disabled != nil { | 
					
						
							|  |  |  | 		emoji.Disabled = info.Disabled | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.VisibleInPicker != nil { | 
					
						
							|  |  |  | 		emoji.VisibleInPicker = info.VisibleInPicker | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if info.CategoryID != nil { | 
					
						
							|  |  |  | 		emoji.CategoryID = *info.CategoryID | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 	// Put or update emoji in database. | 
					
						
							|  |  |  | 	if err := storeDB(ctx, emoji); err != nil { | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Return wrapped emoji for later processing. | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	processingEmoji := &ProcessingEmoji{ | 
					
						
							| 
									
										
										
										
											2024-07-17 15:26:33 +00:00
										 |  |  | 		instAccID: instanceAcc.ID, | 
					
						
							|  |  |  | 		emoji:     emoji, | 
					
						
							|  |  |  | 		dataFn:    data, | 
					
						
							|  |  |  | 		mgr:       m, | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return processingEmoji, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | // extractEmojiPathID pulls the ID used in the final path segment of an emoji path (can be URL). | 
					
						
							|  |  |  | func extractEmojiPathID(path string) string { | 
					
						
							|  |  |  | 	// Look for '.' indicating file ext. | 
					
						
							|  |  |  | 	i := strings.LastIndexByte(path, '.') | 
					
						
							|  |  |  | 	if i == -1 { | 
					
						
							|  |  |  | 		return "" | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Strip ext. | 
					
						
							|  |  |  | 	path = path[:i] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Look for '/' of final path sep. | 
					
						
							|  |  |  | 	i = strings.LastIndexByte(path, '/') | 
					
						
							|  |  |  | 	if i == -1 { | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Strip up to | 
					
						
							|  |  |  | 	// final segment. | 
					
						
							|  |  |  | 	path = path[i+1:] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return path | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | } |