| 
									
										
										
										
											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-01-11 17:49:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | package media | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-01-16 18:52:55 +01:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2022-01-16 18:52:55 +01:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2024-01-05 13:39:53 +01:00
										 |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	"codeberg.org/gruf/go-bytesize" | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	"codeberg.org/gruf/go-errors/v2" | 
					
						
							|  |  |  | 	"codeberg.org/gruf/go-runners" | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	"github.com/h2non/filetype" | 
					
						
							| 
									
										
										
										
											2022-09-12 13:03:23 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/regexes" | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/uris" | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ProcessingEmoji represents an emoji currently processing. It exposes | 
					
						
							|  |  |  | // various functions for retrieving data from the process. | 
					
						
							|  |  |  | type ProcessingEmoji struct { | 
					
						
							| 
									
										
										
										
											2023-05-28 13:08:35 +01:00
										 |  |  | 	emoji     *gtsmodel.Emoji   // processing emoji details | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	existing  bool              // indicates whether this is an existing emoji ID being refreshed / recached | 
					
						
							|  |  |  | 	newPathID string            // new emoji path ID to use when being refreshed | 
					
						
							| 
									
										
										
										
											2023-05-28 13:08:35 +01:00
										 |  |  | 	dataFn    DataFunc          // load-data function, returns media stream | 
					
						
							|  |  |  | 	done      bool              // done is set when process finishes with non ctx canceled type error | 
					
						
							|  |  |  | 	proc      runners.Processor // proc helps synchronize only a singular running processing instance | 
					
						
							|  |  |  | 	err       error             // error stores permanent error value when done | 
					
						
							|  |  |  | 	mgr       *Manager          // mgr instance (access to db / storage) | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // EmojiID returns the ID of the underlying emoji without blocking processing. | 
					
						
							|  |  |  | func (p *ProcessingEmoji) EmojiID() string { | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	return p.emoji.ID // immutable, safe outside mutex. | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | // LoadEmoji blocks until the static and fullsize image has been processed, and then returns the completed emoji. | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | func (p *ProcessingEmoji) LoadEmoji(ctx context.Context) (*gtsmodel.Emoji, error) { | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	// Attempt to load synchronously. | 
					
						
							|  |  |  | 	emoji, done, err := p.load(ctx) | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		// No issue, return media. | 
					
						
							|  |  |  | 		return emoji, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !done { | 
					
						
							|  |  |  | 		// Provided context was cancelled, e.g. request cancelled | 
					
						
							|  |  |  | 		// early. Queue this item for asynchronous processing. | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 		log.Warnf(ctx, "reprocessing emoji %s after canceled ctx", p.emoji.ID) | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 		go p.mgr.state.Workers.Media.Enqueue(p.Process) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Process allows the receiving object to fit the runners.WorkerFunc signature. It performs a (blocking) load and logs on error. | 
					
						
							|  |  |  | func (p *ProcessingEmoji) Process(ctx context.Context) { | 
					
						
							|  |  |  | 	if _, _, err := p.load(ctx); err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 		log.Errorf(ctx, "error processing emoji: %v", err) | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // load performs a concurrency-safe load of ProcessingEmoji, only marking itself as complete when returned error is NOT a context cancel. | 
					
						
							|  |  |  | func (p *ProcessingEmoji) load(ctx context.Context) (*gtsmodel.Emoji, bool, error) { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		done bool | 
					
						
							|  |  |  | 		err  error | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = p.proc.Process(func() error { | 
					
						
							|  |  |  | 		if p.done { | 
					
						
							|  |  |  | 			// Already proc'd. | 
					
						
							|  |  |  | 			return p.err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		defer func() { | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 			// This is only done when ctx NOT cancelled. | 
					
						
							| 
									
										
										
										
											2023-11-30 16:22:34 +00:00
										 |  |  | 			done = err == nil || !errors.IsV2(err, | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 				context.Canceled, | 
					
						
							|  |  |  | 				context.DeadlineExceeded, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !done { | 
					
						
							|  |  |  | 				return | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 			// Store final values. | 
					
						
							|  |  |  | 			p.done = true | 
					
						
							|  |  |  | 			p.err = err | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Attempt to store media and calculate | 
					
						
							|  |  |  | 		// full-size media attachment details. | 
					
						
							|  |  |  | 		if err = p.store(ctx); err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Finish processing by reloading media into | 
					
						
							|  |  |  | 		// memory to get dimension and generate a thumb. | 
					
						
							|  |  |  | 		if err = p.finish(ctx); err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 		if p.existing { | 
					
						
							|  |  |  | 			// Existing emoji we're updating, so only update. | 
					
						
							|  |  |  | 			err = p.mgr.state.DB.UpdateEmoji(ctx, p.emoji) | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-19 13:43:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		// New emoji media, first time caching. | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 		err = p.mgr.state.DB.PutEmoji(ctx, p.emoji) | 
					
						
							|  |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, done, err | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	return p.emoji, done, nil | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-16 18:52:55 +01:00
										 |  |  | // store calls the data function attached to p if it hasn't been called yet, | 
					
						
							|  |  |  | // and updates the underlying attachment fields as necessary. It will then stream | 
					
						
							|  |  |  | // bytes from p's reader directly into storage so that it can be retrieved later. | 
					
						
							|  |  |  | func (p *ProcessingEmoji) store(ctx context.Context) error { | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Load media from provided data fn. | 
					
						
							|  |  |  | 	rc, sz, err := p.dataFn(ctx) | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("error executing data function: %w", err) | 
					
						
							| 
									
										
										
										
											2022-01-16 18:52:55 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-21 13:41:44 +01:00
										 |  |  | 	defer func() { | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		// Ensure data reader gets closed on return. | 
					
						
							| 
									
										
										
										
											2022-11-03 15:03:12 +01:00
										 |  |  | 		if err := rc.Close(); err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 			log.Errorf(ctx, "error closing data reader: %v", err) | 
					
						
							| 
									
										
										
										
											2022-03-21 13:41:44 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	var maxSize bytesize.Size | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if p.emoji.Domain == "" { | 
					
						
							|  |  |  | 		// this is a local emoji upload | 
					
						
							|  |  |  | 		maxSize = config.GetMediaEmojiLocalMaxSize() | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// this is a remote incoming emoji | 
					
						
							|  |  |  | 		maxSize = config.GetMediaEmojiRemoteMaxSize() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check that provided size isn't beyond max. We check beforehand | 
					
						
							|  |  |  | 	// so that we don't attempt to stream the emoji into storage if not needed. | 
					
						
							|  |  |  | 	if size := bytesize.Size(sz); sz > 0 && size > maxSize { | 
					
						
							|  |  |  | 		return gtserror.Newf("given emoji size %s greater than max allowed %s", size, maxSize) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	// Prepare to read bytes from | 
					
						
							|  |  |  | 	// file header or magic number. | 
					
						
							|  |  |  | 	fileSize := int(sz) | 
					
						
							|  |  |  | 	hdrBuf := newHdrBuf(fileSize) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Read into buffer as much as possible. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// UnexpectedEOF means we couldn't read up to the | 
					
						
							|  |  |  | 	// given size, but we may still have read something. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// EOF means we couldn't read anything at all. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// Any other error likely means the connection messed up. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// In other words, rather counterintuitively, we | 
					
						
							|  |  |  | 	// can only proceed on no error or unexpected error! | 
					
						
							|  |  |  | 	n, err := io.ReadFull(rc, hdrBuf) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if err != io.ErrUnexpectedEOF { | 
					
						
							|  |  |  | 			return gtserror.Newf("error reading first bytes of incoming media: %w", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Initial file size was misreported, so we didn't read | 
					
						
							|  |  |  | 		// fully into hdrBuf. Reslice it to the size we did read. | 
					
						
							|  |  |  | 		log.Warnf(ctx, | 
					
						
							|  |  |  | 			"recovered from misreported file size; reported %d; read %d", | 
					
						
							|  |  |  | 			fileSize, n, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		hdrBuf = hdrBuf[:n] | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Parse file type info from header buffer. | 
					
						
							|  |  |  | 	info, err := filetype.Match(hdrBuf) | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("error parsing file type: %w", err) | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-05 13:39:53 +01:00
										 |  |  | 	// Ensure supported emoji img type. | 
					
						
							|  |  |  | 	if !slices.Contains(SupportedEmojiMIMETypes, info.MIME.Value) { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("unsupported emoji filetype: %s", info.Extension) | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Recombine header bytes with remaining stream | 
					
						
							|  |  |  | 	r := io.MultiReader(bytes.NewReader(hdrBuf), rc) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-13 15:16:24 +02:00
										 |  |  | 	var pathID string | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	if p.newPathID != "" { | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		// This is a refreshed emoji with a new | 
					
						
							|  |  |  | 		// path ID that this will be stored under. | 
					
						
							| 
									
										
										
										
											2022-10-13 15:16:24 +02:00
										 |  |  | 		pathID = p.newPathID | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		// This is a new emoji, simply use provided ID. | 
					
						
							| 
									
										
										
										
											2022-10-13 15:16:24 +02:00
										 |  |  | 		pathID = p.emoji.ID | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	// Determine instance account ID from already generated image static path. | 
					
						
							|  |  |  | 	instanceAccID := regexes.FilePath.FindStringSubmatch(p.emoji.ImageStaticPath)[1] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Calculate emoji file path. | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	p.emoji.ImagePath = uris.StoragePathForAttachment( | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 		instanceAccID, | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 		string(TypeEmoji), | 
					
						
							|  |  |  | 		string(SizeOriginal), | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		pathID, | 
					
						
							|  |  |  | 		info.Extension, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// This shouldn't already exist, but we do a check as it's worth logging. | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	if have, _ := p.mgr.state.Storage.Has(ctx, p.emoji.ImagePath); have { | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 		log.Warnf(ctx, "emoji already exists at storage path: %s", p.emoji.ImagePath) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Attempt to remove existing emoji at storage path (might be broken / out-of-date) | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 		if err := p.mgr.state.Storage.Delete(ctx, p.emoji.ImagePath); err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 			return gtserror.Newf("error removing emoji from storage: %v", err) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-24 11:11:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Write the final image reader stream to our storage. | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	wroteSize, err := p.mgr.state.Storage.PutStream(ctx, p.emoji.ImagePath, r) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("error writing emoji to storage: %w", err) | 
					
						
							| 
									
										
										
										
											2022-09-24 11:11:47 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Once again check size in case none was provided previously. | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	if size := bytesize.Size(wroteSize); size > maxSize { | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 		if err := p.mgr.state.Storage.Delete(ctx, p.emoji.ImagePath); err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 			log.Errorf(ctx, "error removing too-large-emoji from storage: %v", err) | 
					
						
							| 
									
										
										
										
											2022-09-24 11:11:47 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("calculated emoji size %s greater than max allowed %s", size, maxSize) | 
					
						
							| 
									
										
										
										
											2022-09-24 11:11:47 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-01-16 18:52:55 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Fill in remaining attachment data now it's stored. | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	p.emoji.ImageURL = uris.URIForAttachment( | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 		instanceAccID, | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		string(TypeEmoji), | 
					
						
							|  |  |  | 		string(SizeOriginal), | 
					
						
							|  |  |  | 		pathID, | 
					
						
							|  |  |  | 		info.Extension, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	p.emoji.ImageContentType = info.MIME.Value | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	p.emoji.ImageFileSize = int(wroteSize) | 
					
						
							|  |  |  | 	p.emoji.Cached = util.Ptr(true) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *ProcessingEmoji) finish(ctx context.Context) error { | 
					
						
							|  |  |  | 	// Fetch a stream to the original file in storage. | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	rc, err := p.mgr.state.Storage.GetStream(ctx, p.emoji.ImagePath) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("error loading file from storage: %w", err) | 
					
						
							| 
									
										
										
										
											2022-01-16 18:52:55 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	defer rc.Close() | 
					
						
							| 
									
										
										
										
											2022-01-16 18:52:55 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Decode the image from storage. | 
					
						
							|  |  |  | 	staticImg, err := decodeImage(rc) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("error decoding image: %w", err) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The image should be in-memory by now. | 
					
						
							|  |  |  | 	if err := rc.Close(); err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("error closing file: %w", err) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// This shouldn't already exist, but we do a check as it's worth logging. | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	if have, _ := p.mgr.state.Storage.Has(ctx, p.emoji.ImageStaticPath); have { | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 		log.Warnf(ctx, "static emoji already exists at storage path: %s", p.emoji.ImagePath) | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 		// Attempt to remove static existing emoji at storage path (might be broken / out-of-date) | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 		if err := p.mgr.state.Storage.Delete(ctx, p.emoji.ImageStaticPath); err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 			return gtserror.Newf("error removing static emoji from storage: %v", err) | 
					
						
							| 
									
										
										
										
											2022-11-11 20:27:37 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-24 11:11:47 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	// Create an emoji PNG encoder stream. | 
					
						
							|  |  |  | 	enc := staticImg.ToPNG() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Stream-encode the PNG static image into storage. | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:48 +00:00
										 |  |  | 	sz, err := p.mgr.state.Storage.PutStream(ctx, p.emoji.ImageStaticPath, enc) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		return gtserror.Newf("error stream-encoding static emoji to storage: %w", err) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Set written image size. | 
					
						
							|  |  |  | 	p.emoji.ImageStaticFileSize = int(sz) | 
					
						
							| 
									
										
										
										
											2022-02-22 13:50:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 17:49:14 +01:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } |