gtsocial-umbx

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

emoji.go (6654B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package dereferencing
     19 
     20 import (
     21 	"context"
     22 	"fmt"
     23 	"io"
     24 	"net/url"
     25 
     26 	"github.com/superseriousbusiness/gotosocial/internal/db"
     27 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     28 	"github.com/superseriousbusiness/gotosocial/internal/id"
     29 	"github.com/superseriousbusiness/gotosocial/internal/log"
     30 	"github.com/superseriousbusiness/gotosocial/internal/media"
     31 )
     32 
     33 func (d *deref) GetRemoteEmoji(ctx context.Context, requestingUsername string, remoteURL string, shortcode string, domain string, id string, emojiURI string, ai *media.AdditionalEmojiInfo, refresh bool) (*media.ProcessingEmoji, error) {
     34 	var (
     35 		shortcodeDomain = shortcode + "@" + domain
     36 		processingEmoji *media.ProcessingEmoji
     37 	)
     38 
     39 	// Acquire lock for derefs map.
     40 	unlock := d.derefEmojisMu.Lock()
     41 	defer unlock()
     42 
     43 	// first check if we're already processing this emoji
     44 	if alreadyProcessing, ok := d.derefEmojis[shortcodeDomain]; ok {
     45 		// we're already on it, no worries
     46 		processingEmoji = alreadyProcessing
     47 	} else {
     48 		// not processing it yet, let's start
     49 		t, err := d.transportController.NewTransportForUsername(ctx, requestingUsername)
     50 		if err != nil {
     51 			return nil, fmt.Errorf("GetRemoteEmoji: error creating transport to fetch emoji %s: %s", shortcodeDomain, err)
     52 		}
     53 
     54 		derefURI, err := url.Parse(remoteURL)
     55 		if err != nil {
     56 			return nil, fmt.Errorf("GetRemoteEmoji: error parsing url for emoji %s: %s", shortcodeDomain, err)
     57 		}
     58 
     59 		dataFunc := func(innerCtx context.Context) (io.ReadCloser, int64, error) {
     60 			return t.DereferenceMedia(innerCtx, derefURI)
     61 		}
     62 
     63 		newProcessing, err := d.mediaManager.PreProcessEmoji(ctx, dataFunc, shortcode, id, emojiURI, ai, refresh)
     64 		if err != nil {
     65 			return nil, fmt.Errorf("GetRemoteEmoji: error processing emoji %s: %s", shortcodeDomain, err)
     66 		}
     67 
     68 		// store it in our map to indicate it's in process
     69 		d.derefEmojis[shortcodeDomain] = newProcessing
     70 		processingEmoji = newProcessing
     71 	}
     72 
     73 	// Unlock map.
     74 	unlock()
     75 
     76 	defer func() {
     77 		// On exit safely remove emoji from map.
     78 		unlock := d.derefEmojisMu.Lock()
     79 		delete(d.derefEmojis, shortcodeDomain)
     80 		unlock()
     81 	}()
     82 
     83 	// Start emoji attachment loading (blocking call).
     84 	if _, err := processingEmoji.LoadEmoji(ctx); err != nil {
     85 		return nil, err
     86 	}
     87 
     88 	return processingEmoji, nil
     89 }
     90 
     91 func (d *deref) populateEmojis(ctx context.Context, rawEmojis []*gtsmodel.Emoji, requestingUsername string) ([]*gtsmodel.Emoji, error) {
     92 	// At this point we should know:
     93 	// * the AP uri of the emoji
     94 	// * the domain of the emoji
     95 	// * the shortcode of the emoji
     96 	// * the remote URL of the image
     97 	// This should be enough to dereference the emoji
     98 
     99 	gotEmojis := make([]*gtsmodel.Emoji, 0, len(rawEmojis))
    100 
    101 	for _, e := range rawEmojis {
    102 		var gotEmoji *gtsmodel.Emoji
    103 		var err error
    104 		shortcodeDomain := e.Shortcode + "@" + e.Domain
    105 
    106 		// check if we already know this emoji
    107 		if e.ID != "" {
    108 			// we had an ID for this emoji already, which means
    109 			// it should be fleshed out already and we won't
    110 			// have to get it from the database again
    111 			gotEmoji = e
    112 		} else if gotEmoji, err = d.state.DB.GetEmojiByShortcodeDomain(ctx, e.Shortcode, e.Domain); err != nil && err != db.ErrNoEntries {
    113 			log.Errorf(ctx, "error checking database for emoji %s: %s", shortcodeDomain, err)
    114 			continue
    115 		}
    116 
    117 		var refresh bool
    118 
    119 		if gotEmoji != nil {
    120 			// we had the emoji already, but refresh it if necessary
    121 			if e.UpdatedAt.Unix() > gotEmoji.ImageUpdatedAt.Unix() {
    122 				log.Tracef(ctx, "emoji %s was updated since we last saw it, will refresh", shortcodeDomain)
    123 				refresh = true
    124 			}
    125 
    126 			if !refresh && (e.URI != gotEmoji.URI) {
    127 				log.Tracef(ctx, "emoji %s changed URI since we last saw it, will refresh", shortcodeDomain)
    128 				refresh = true
    129 			}
    130 
    131 			if !refresh && (e.ImageRemoteURL != gotEmoji.ImageRemoteURL) {
    132 				log.Tracef(ctx, "emoji %s changed image URL since we last saw it, will refresh", shortcodeDomain)
    133 				refresh = true
    134 			}
    135 
    136 			if !refresh {
    137 				log.Tracef(ctx, "emoji %s is up to date, will not refresh", shortcodeDomain)
    138 			} else {
    139 				log.Tracef(ctx, "refreshing emoji %s", shortcodeDomain)
    140 				emojiID := gotEmoji.ID // use existing ID
    141 				processingEmoji, err := d.GetRemoteEmoji(ctx, requestingUsername, e.ImageRemoteURL, e.Shortcode, e.Domain, emojiID, e.URI, &media.AdditionalEmojiInfo{
    142 					Domain:               &e.Domain,
    143 					ImageRemoteURL:       &e.ImageRemoteURL,
    144 					ImageStaticRemoteURL: &e.ImageStaticRemoteURL,
    145 					Disabled:             gotEmoji.Disabled,
    146 					VisibleInPicker:      gotEmoji.VisibleInPicker,
    147 				}, refresh)
    148 				if err != nil {
    149 					log.Errorf(ctx, "couldn't refresh remote emoji %s: %s", shortcodeDomain, err)
    150 					continue
    151 				}
    152 
    153 				if gotEmoji, err = processingEmoji.LoadEmoji(ctx); err != nil {
    154 					log.Errorf(ctx, "couldn't load refreshed remote emoji %s: %s", shortcodeDomain, err)
    155 					continue
    156 				}
    157 			}
    158 		} else {
    159 			// it's new! go get it!
    160 			newEmojiID, err := id.NewRandomULID()
    161 			if err != nil {
    162 				log.Errorf(ctx, "error generating id for remote emoji %s: %s", shortcodeDomain, err)
    163 				continue
    164 			}
    165 
    166 			processingEmoji, err := d.GetRemoteEmoji(ctx, requestingUsername, e.ImageRemoteURL, e.Shortcode, e.Domain, newEmojiID, e.URI, &media.AdditionalEmojiInfo{
    167 				Domain:               &e.Domain,
    168 				ImageRemoteURL:       &e.ImageRemoteURL,
    169 				ImageStaticRemoteURL: &e.ImageStaticRemoteURL,
    170 				Disabled:             e.Disabled,
    171 				VisibleInPicker:      e.VisibleInPicker,
    172 			}, refresh)
    173 			if err != nil {
    174 				log.Errorf(ctx, "couldn't get remote emoji %s: %s", shortcodeDomain, err)
    175 				continue
    176 			}
    177 
    178 			if gotEmoji, err = processingEmoji.LoadEmoji(ctx); err != nil {
    179 				log.Errorf(ctx, "couldn't load remote emoji %s: %s", shortcodeDomain, err)
    180 				continue
    181 			}
    182 		}
    183 
    184 		// if we get here, we either had the emoji already or we successfully fetched it
    185 		gotEmojis = append(gotEmojis, gotEmoji)
    186 	}
    187 
    188 	return gotEmojis, nil
    189 }