diff options
| author | 2021-12-28 16:36:00 +0100 | |
|---|---|---|
| committer | 2021-12-28 16:36:00 +0100 | |
| commit | c4d63d125b5a44c150a00b0b20b3638cad9221f8 (patch) | |
| tree | 03230f6e34068fb30a8db9dd9a4c3db1f4e04f9b | |
| parent | start refactor of media package (diff) | |
| download | gotosocial-c4d63d125b5a44c150a00b0b20b3638cad9221f8.tar.xz | |
more refactoring, media handler => manager
29 files changed, 321 insertions, 479 deletions
diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go index d55f38fc5..05c2e8974 100644 --- a/cmd/gotosocial/action/server/server.go +++ b/cmd/gotosocial/action/server/server.go @@ -105,10 +105,10 @@ var Start action.GTSAction = func(ctx context.Context) error {  	}  	// build backend handlers -	mediaHandler := media.New(dbService, storage) +	mediaManager := media.New(dbService, storage)  	oauthServer := oauth.New(ctx, dbService)  	transportController := transport.NewController(dbService, &federation.Clock{}, http.DefaultClient) -	federator := federation.NewFederator(dbService, federatingDB, transportController, typeConverter, mediaHandler) +	federator := federation.NewFederator(dbService, federatingDB, transportController, typeConverter, mediaManager)  	// decide whether to create a noop email sender (won't send emails) or a real one  	var emailSender email.Sender @@ -128,7 +128,7 @@ var Start action.GTSAction = func(ctx context.Context) error {  	}  	// create and start the message processor using the other services we've created so far -	processor := processing.NewProcessor(typeConverter, federator, oauthServer, mediaHandler, storage, timelineManager, dbService, emailSender) +	processor := processing.NewProcessor(typeConverter, federator, oauthServer, mediaManager, storage, timelineManager, dbService, emailSender)  	if err := processor.Start(ctx); err != nil {  		return fmt.Errorf("error starting processor: %s", err)  	} diff --git a/internal/api/client/fileserver/servefile_test.go b/internal/api/client/fileserver/servefile_test.go index 18f54542a..109ed4eba 100644 --- a/internal/api/client/fileserver/servefile_test.go +++ b/internal/api/client/fileserver/servefile_test.go @@ -51,7 +51,7 @@ type ServeFileTestSuite struct {  	federator    federation.Federator  	tc           typeutils.TypeConverter  	processor    processing.Processor -	mediaHandler media.Handler +	mediaManager media.Manager  	oauthServer  oauth.Server  	emailSender  email.Sender @@ -82,7 +82,7 @@ func (suite *ServeFileTestSuite) SetupSuite() {  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender)  	suite.tc = testrig.NewTestTypeConverter(suite.db) -	suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) +	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage)  	suite.oauthServer = testrig.NewTestOauthServer(suite.db)  	// setup module being tested diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 2897d786b..72a377c25 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -53,7 +53,7 @@ type MediaCreateTestSuite struct {  	storage      *kv.KVStore  	federator    federation.Federator  	tc           typeutils.TypeConverter -	mediaHandler media.Handler +	mediaManager media.Manager  	oauthServer  oauth.Server  	emailSender  email.Sender  	processor    processing.Processor @@ -81,7 +81,7 @@ func (suite *MediaCreateTestSuite) SetupSuite() {  	suite.db = testrig.NewTestDB()  	suite.storage = testrig.NewTestStorage()  	suite.tc = testrig.NewTestTypeConverter(suite.db) -	suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) +	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage)  	suite.oauthServer = testrig.NewTestOauthServer(suite.db)  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) diff --git a/internal/api/s2s/webfinger/webfingerget_test.go b/internal/api/s2s/webfinger/webfingerget_test.go index 4b27ada42..c5df1f7e5 100644 --- a/internal/api/s2s/webfinger/webfingerget_test.go +++ b/internal/api/s2s/webfinger/webfingerget_test.go @@ -69,7 +69,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUser() {  func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHost() {  	viper.Set(config.Keys.Host, "gts.example.org")  	viper.Set(config.Keys.AccountDomain, "example.org") -	suite.processor = processing.NewProcessor(suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaHandler(suite.db, suite.storage), suite.storage, testrig.NewTestTimelineManager(suite.db), suite.db, suite.emailSender) +	suite.processor = processing.NewProcessor(suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaManager(suite.db, suite.storage), suite.storage, testrig.NewTestTimelineManager(suite.db), suite.db, suite.emailSender)  	suite.webfingerModule = webfinger.New(suite.processor).(*webfinger.Module)  	targetAccount := accountDomainAccount() @@ -103,7 +103,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHo  func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByAccountDomain() {  	viper.Set(config.Keys.Host, "gts.example.org")  	viper.Set(config.Keys.AccountDomain, "example.org") -	suite.processor = processing.NewProcessor(suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaHandler(suite.db, suite.storage), suite.storage, testrig.NewTestTimelineManager(suite.db), suite.db, suite.emailSender) +	suite.processor = processing.NewProcessor(suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaManager(suite.db, suite.storage), suite.storage, testrig.NewTestTimelineManager(suite.db), suite.db, suite.emailSender)  	suite.webfingerModule = webfinger.New(suite.processor).(*webfinger.Module)  	targetAccount := accountDomainAccount() diff --git a/internal/federation/dereferencing/account.go b/internal/federation/dereferencing/account.go index d06ad21c1..19c98e203 100644 --- a/internal/federation/dereferencing/account.go +++ b/internal/federation/dereferencing/account.go @@ -246,7 +246,7 @@ func (d *deref) fetchHeaderAndAviForAccount(ctx context.Context, targetAccount *  	}  	if targetAccount.AvatarRemoteURL != "" && (targetAccount.AvatarMediaAttachmentID == "" || refresh) { -		a, err := d.mediaHandler.ProcessRemoteHeaderOrAvatar(ctx, t, >smodel.MediaAttachment{ +		a, err := d.mediaManager.ProcessRemoteHeaderOrAvatar(ctx, t, >smodel.MediaAttachment{  			RemoteURL: targetAccount.AvatarRemoteURL,  			Avatar:    true,  		}, targetAccount.ID) @@ -257,7 +257,7 @@ func (d *deref) fetchHeaderAndAviForAccount(ctx context.Context, targetAccount *  	}  	if targetAccount.HeaderRemoteURL != "" && (targetAccount.HeaderMediaAttachmentID == "" || refresh) { -		a, err := d.mediaHandler.ProcessRemoteHeaderOrAvatar(ctx, t, >smodel.MediaAttachment{ +		a, err := d.mediaManager.ProcessRemoteHeaderOrAvatar(ctx, t, >smodel.MediaAttachment{  			RemoteURL: targetAccount.HeaderRemoteURL,  			Header:    true,  		}, targetAccount.ID) diff --git a/internal/federation/dereferencing/attachment.go b/internal/federation/dereferencing/attachment.go index 0c7005e23..30ab6da10 100644 --- a/internal/federation/dereferencing/attachment.go +++ b/internal/federation/dereferencing/attachment.go @@ -93,7 +93,7 @@ func (d *deref) RefreshAttachment(ctx context.Context, requestingUsername string  		return nil, fmt.Errorf("RefreshAttachment: error dereferencing media: %s", err)  	} -	a, err := d.mediaHandler.ProcessAttachment(ctx, attachmentBytes, minAttachment) +	a, err := d.mediaManager.ProcessAttachment(ctx, attachmentBytes, minAttachment)  	if err != nil {  		return nil, fmt.Errorf("RefreshAttachment: error processing attachment: %s", err)  	} diff --git a/internal/federation/dereferencing/dereferencer.go b/internal/federation/dereferencing/dereferencer.go index d0b653920..4f977b8c8 100644 --- a/internal/federation/dereferencing/dereferencer.go +++ b/internal/federation/dereferencing/dereferencer.go @@ -80,18 +80,18 @@ type deref struct {  	db                  db.DB  	typeConverter       typeutils.TypeConverter  	transportController transport.Controller -	mediaHandler        media.Handler +	mediaManager        media.Manager  	handshakes          map[string][]*url.URL  	handshakeSync       *sync.Mutex // mutex to lock/unlock when checking or updating the handshakes map  }  // NewDereferencer returns a Dereferencer initialized with the given parameters. -func NewDereferencer(db db.DB, typeConverter typeutils.TypeConverter, transportController transport.Controller, mediaHandler media.Handler) Dereferencer { +func NewDereferencer(db db.DB, typeConverter typeutils.TypeConverter, transportController transport.Controller, mediaManager media.Manager) Dereferencer {  	return &deref{  		db:                  db,  		typeConverter:       typeConverter,  		transportController: transportController, -		mediaHandler:        mediaHandler, +		mediaManager:        mediaManager,  		handshakeSync:       &sync.Mutex{},  	}  } diff --git a/internal/federation/dereferencing/dereferencer_test.go b/internal/federation/dereferencing/dereferencer_test.go index 569e8e93b..fe66abce4 100644 --- a/internal/federation/dereferencing/dereferencer_test.go +++ b/internal/federation/dereferencing/dereferencer_test.go @@ -64,7 +64,7 @@ func (suite *DereferencerStandardTestSuite) SetupTest() {  	suite.db = testrig.NewTestDB()  	suite.storage = testrig.NewTestStorage() -	suite.dereferencer = dereferencing.NewDereferencer(suite.db, testrig.NewTestTypeConverter(suite.db), suite.mockTransportController(), testrig.NewTestMediaHandler(suite.db, suite.storage)) +	suite.dereferencer = dereferencing.NewDereferencer(suite.db, testrig.NewTestTypeConverter(suite.db), suite.mockTransportController(), testrig.NewTestMediaManager(suite.db, suite.storage))  	testrig.StandardDBSetup(suite.db, nil)  } diff --git a/internal/federation/federator.go b/internal/federation/federator.go index 0a82f12bc..7edff1118 100644 --- a/internal/federation/federator.go +++ b/internal/federation/federator.go @@ -78,13 +78,13 @@ type federator struct {  	typeConverter       typeutils.TypeConverter  	transportController transport.Controller  	dereferencer        dereferencing.Dereferencer -	mediaHandler        media.Handler +	mediaManager        media.Manager  	actor               pub.FederatingActor  }  // NewFederator returns a new federator -func NewFederator(db db.DB, federatingDB federatingdb.DB, transportController transport.Controller, typeConverter typeutils.TypeConverter, mediaHandler media.Handler) Federator { -	dereferencer := dereferencing.NewDereferencer(db, typeConverter, transportController, mediaHandler) +func NewFederator(db db.DB, federatingDB federatingdb.DB, transportController transport.Controller, typeConverter typeutils.TypeConverter, mediaManager media.Manager) Federator { +	dereferencer := dereferencing.NewDereferencer(db, typeConverter, transportController, mediaManager)  	clock := &Clock{}  	f := &federator{ @@ -94,7 +94,7 @@ func NewFederator(db db.DB, federatingDB federatingdb.DB, transportController tr  		typeConverter:       typeConverter,  		transportController: transportController,  		dereferencer:        dereferencer, -		mediaHandler:        mediaHandler, +		mediaManager:        mediaManager,  	}  	actor := newFederatingActor(f, f, federatingDB, clock)  	f.actor = actor diff --git a/internal/federation/federator_test.go b/internal/federation/federator_test.go index 43f4904a5..6dac76c05 100644 --- a/internal/federation/federator_test.go +++ b/internal/federation/federator_test.go @@ -78,7 +78,7 @@ func (suite *ProtocolTestSuite) TestPostInboxRequestBodyHook() {  		return nil, nil  	}), suite.db)  	// setup module being tested -	federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db), tc, suite.typeConverter, testrig.NewTestMediaHandler(suite.db, suite.storage)) +	federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db), tc, suite.typeConverter, testrig.NewTestMediaManager(suite.db, suite.storage))  	// setup request  	ctx := context.Background() @@ -107,7 +107,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() {  	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db)  	// now setup module being tested, with the mock transport controller -	federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db), tc, suite.typeConverter, testrig.NewTestMediaHandler(suite.db, suite.storage)) +	federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db), tc, suite.typeConverter, testrig.NewTestMediaManager(suite.db, suite.storage))  	request := httptest.NewRequest(http.MethodPost, "http://localhost:8080/users/the_mighty_zork/inbox", nil)  	// we need these headers for the request to be validated diff --git a/internal/media/image.go b/internal/media/image.go index f1cc03bb6..87b5d70b7 100644 --- a/internal/media/image.go +++ b/internal/media/image.go @@ -19,67 +19,88 @@  package media  import ( +	"bytes"  	"errors"  	"fmt" +	"image" +	"image/gif" +	"image/jpeg" +	"image/png"  	"strings"  	"time" +	"github.com/buckket/go-blurhash" +	"github.com/nfnt/resize" +	"github.com/superseriousbusiness/exifremove/pkg/exifremove"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/id"  	"github.com/superseriousbusiness/gotosocial/internal/uris"  ) -func (mh *mediaHandler) processImage(data []byte, minAttachment *gtsmodel.MediaAttachment) (*gtsmodel.MediaAttachment, error) { +const ( +	thumbnailMaxWidth  = 512 +	thumbnailMaxHeight = 512 +) + +type imageAndMeta struct { +	image    []byte +	width    int +	height   int +	size     int +	aspect   float64 +	blurhash string +} + +func (m *manager) processImage(data []byte, contentType string) (*gtsmodel.MediaAttachment, error) {  	var clean []byte  	var err error  	var original *imageAndMeta  	var small *imageAndMeta -	contentType := minAttachment.File.ContentType -  	switch contentType { -	case mimeJpeg, mimePng: -		if clean, err = purgeExif(data); err != nil { -			return nil, fmt.Errorf("error cleaning exif data: %s", err) +	case mimeImageJpeg, mimeImagePng: +		// first 'clean' image by purging exif data from it +		var exifErr error +		if clean, exifErr = purgeExif(data); exifErr != nil { +			return nil, fmt.Errorf("error cleaning exif data: %s", exifErr)  		} -		original, err = deriveImage(clean, contentType) -		if err != nil { -			return nil, fmt.Errorf("error parsing image: %s", err) -		} -	case mimeGif: +		original, err = decodeImage(clean, contentType) +	case mimeImageGif: +		// gifs are already clean - no exif data to remove  		clean = data -		original, err = deriveGif(clean, contentType) -		if err != nil { -			return nil, fmt.Errorf("error parsing gif: %s", err) -		} +		original, err = decodeGif(clean, contentType)  	default: -		return nil, errors.New("media type unrecognized") +		err = fmt.Errorf("content type %s not a recognized image type", contentType) +	} + +	if err != nil { +		return nil, err  	} -	small, err = deriveThumbnail(clean, contentType, 512, 512) +	small, err = deriveThumbnail(clean, contentType, thumbnailMaxWidth, thumbnailMaxHeight)  	if err != nil {  		return nil, fmt.Errorf("error deriving thumbnail: %s", err)  	}  	// now put it in storage, take a new id for the name of the file so we don't store any unnecessary info about it  	extension := strings.Split(contentType, "/")[1] -	newMediaID, err := id.NewRandomULID() +	attachmentID, err := id.NewRandomULID()  	if err != nil {  		return nil, err  	} -	originalURL := uris.GenerateURIForAttachment(minAttachment.AccountID, string(TypeAttachment), string(SizeOriginal), newMediaID, extension) -	smallURL := uris.GenerateURIForAttachment(minAttachment.AccountID, string(TypeAttachment), string(SizeSmall), newMediaID, "jpeg") // all thumbnails/smalls are encoded as jpeg +	originalURL := uris.GenerateURIForAttachment(minAttachment.AccountID, string(TypeAttachment), string(SizeOriginal), attachmentID, extension) +	smallURL := uris.GenerateURIForAttachment(minAttachment.AccountID, string(TypeAttachment), string(SizeSmall), attachmentID, "jpeg") // all thumbnails/smalls are encoded as jpeg  	// we store the original... -	originalPath := fmt.Sprintf("%s/%s/%s/%s.%s", minAttachment.AccountID, TypeAttachment, SizeOriginal, newMediaID, extension) -	if err := mh.storage.Put(originalPath, original.image); err != nil { +	originalPath := fmt.Sprintf("%s/%s/%s/%s.%s", minAttachment.AccountID, TypeAttachment, SizeOriginal, attachmentID, extension) +	if err := m.storage.Put(originalPath, original.image); err != nil {  		return nil, fmt.Errorf("storage error: %s", err)  	}  	// and a thumbnail... -	smallPath := fmt.Sprintf("%s/%s/%s/%s.jpeg", minAttachment.AccountID, TypeAttachment, SizeSmall, newMediaID) // all thumbnails/smalls are encoded as jpeg -	if err := mh.storage.Put(smallPath, small.image); err != nil { +	smallPath := fmt.Sprintf("%s/%s/%s/%s.jpeg", minAttachment.AccountID, TypeAttachment, SizeSmall, attachmentID) // all thumbnails/smalls are encoded as jpeg +	if err := m.storage.Put(smallPath, small.image); err != nil {  		return nil, fmt.Errorf("storage error: %s", err)  	} @@ -98,7 +119,7 @@ func (mh *mediaHandler) processImage(data []byte, minAttachment *gtsmodel.MediaA  	}  	attachment := >smodel.MediaAttachment{ -		ID:                newMediaID, +		ID:                attachmentID,  		StatusID:          minAttachment.StatusID,  		URL:               originalURL,  		RemoteURL:         minAttachment.RemoteURL, @@ -131,3 +152,173 @@ func (mh *mediaHandler) processImage(data []byte, minAttachment *gtsmodel.MediaA  	return attachment, nil  } + +func decodeGif(b []byte, extension string) (*imageAndMeta, error) { +	var g *gif.GIF +	var err error + +	switch extension { +	case mimeGif: +		g, err = gif.DecodeAll(bytes.NewReader(b)) +	default: +		err = fmt.Errorf("extension %s not recognised", extension) +	} + +	if err != nil { +		return nil, err +	} + +	// use the first frame to get the static characteristics +	width := g.Config.Width +	height := g.Config.Height +	size := width * height +	aspect := float64(width) / float64(height) + +	return &imageAndMeta{ +		image:  b, +		width:  width, +		height: height, +		size:   size, +		aspect: aspect, +	}, nil +} + +func decodeImage(b []byte, contentType string) (*imageAndMeta, error) { +	var i image.Image +	var err error + +	switch contentType { +	case mimeImageJpeg: +		i, err = jpeg.Decode(bytes.NewReader(b)) +	case mimeImagePng: +		i, err = png.Decode(bytes.NewReader(b)) +	default: +		err = fmt.Errorf("content type %s not recognised", contentType) +	} + +	if err != nil { +		return nil, err +	} + +	if i == nil { +		return nil, errors.New("processed image was nil") +	} + +	width := i.Bounds().Size().X +	height := i.Bounds().Size().Y +	size := width * height +	aspect := float64(width) / float64(height) + +	return &imageAndMeta{ +		image:  b, +		width:  width, +		height: height, +		size:   size, +		aspect: aspect, +	}, nil +} + +// deriveThumbnail returns a byte slice and metadata for a thumbnail of width x and height y, +// of a given jpeg, png, or gif, or an error if something goes wrong. +// +// Note that the aspect ratio of the image will be retained, +// so it will not necessarily be a square, even if x and y are set as the same value. +func deriveThumbnail(b []byte, contentType string, x uint, y uint) (*imageAndMeta, error) { +	var i image.Image +	var err error + +	switch contentType { +	case mimeImageJpeg: +		i, err = jpeg.Decode(bytes.NewReader(b)) +		if err != nil { +			return nil, err +		} +	case mimeImagePng: +		i, err = png.Decode(bytes.NewReader(b)) +		if err != nil { +			return nil, err +		} +	case mimeImageGif: +		i, err = gif.Decode(bytes.NewReader(b)) +		if err != nil { +			return nil, err +		} +	default: +		return nil, fmt.Errorf("content type %s not recognised", contentType) +	} + +	thumb := resize.Thumbnail(x, y, i, resize.NearestNeighbor) +	width := thumb.Bounds().Size().X +	height := thumb.Bounds().Size().Y +	size := width * height +	aspect := float64(width) / float64(height) + +	tiny := resize.Thumbnail(32, 32, thumb, resize.NearestNeighbor) +	bh, err := blurhash.Encode(4, 3, tiny) +	if err != nil { +		return nil, err +	} + +	out := &bytes.Buffer{} +	if err := jpeg.Encode(out, thumb, &jpeg.Options{ +		Quality: 75, +	}); err != nil { +		return nil, err +	} +	return &imageAndMeta{ +		image:    out.Bytes(), +		width:    width, +		height:   height, +		size:     size, +		aspect:   aspect, +		blurhash: bh, +	}, nil +} + +// deriveStaticEmojji takes a given gif or png of an emoji, decodes it, and re-encodes it as a static png. +func deriveStaticEmoji(b []byte, contentType string) (*imageAndMeta, error) { +	var i image.Image +	var err error + +	switch contentType { +	case mimeImagePng: +		i, err = png.Decode(bytes.NewReader(b)) +		if err != nil { +			return nil, err +		} +	case mimeImageGif: +		i, err = gif.Decode(bytes.NewReader(b)) +		if err != nil { +			return nil, err +		} +	default: +		return nil, fmt.Errorf("content type %s not allowed for emoji", contentType) +	} + +	out := &bytes.Buffer{} +	if err := png.Encode(out, i); err != nil { +		return nil, err +	} +	return &imageAndMeta{ +		image: out.Bytes(), +	}, nil +} + +// purgeExif is a little wrapper for the action of removing exif data from an image. +// Only pass pngs or jpegs to this function. +func purgeExif(data []byte) ([]byte, error) { +	if len(data) == 0 { +		return nil, errors.New("passed image was not valid") +	} + +	clean, err := exifremove.Remove(data) +	if err != nil { +		return nil, fmt.Errorf("could not purge exif from image: %s", err) +	} + +	if len(clean) == 0 { +		return nil, errors.New("purged image was not valid") +	} + +	return clean, nil +} diff --git a/internal/media/handler.go b/internal/media/manager.go index b64e583b3..782542ca9 100644 --- a/internal/media/handler.go +++ b/internal/media/manager.go @@ -27,7 +27,6 @@ import (  	"time"  	"codeberg.org/gruf/go-store/kv" -	"github.com/sirupsen/logrus"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/id" @@ -35,26 +34,31 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/uris"  ) +// ProcessCallback is triggered by the media manager when an attachment has finished undergoing +// image processing (generation of a blurhash, thumbnail etc) but hasn't yet been inserted into +// the database. It is provided to allow callers to a) access the processed media attachment and b) +// make any last-minute changes to the media attachment before it enters the database. +type ProcessCallback func(*gtsmodel.MediaAttachment) *gtsmodel.MediaAttachment +// defaultCB will be used when a nil ProcessCallback is passed to one of the manager's interface functions. +// It just returns the processed media attachment with no additional changes. +var defaultCB ProcessCallback = func(a *gtsmodel.MediaAttachment) *gtsmodel.MediaAttachment { +	return a +} -type ProcessedCallback func(*gtsmodel.MediaAttachment) error - -// Handler provides an interface for parsing, storing, and retrieving media objects like photos, videos, and gifs. -type Handler interface { -	ProcessHeader(ctx context.Context, data []byte, accountID string, cb ProcessedCallback) (*gtsmodel.MediaAttachment, error) -	ProcessAvatar(ctx context.Context, data []byte, accountID string, cb ProcessedCallback) (*gtsmodel.MediaAttachment, error) -	ProcessAttachment(ctx context.Context, data []byte, accountID string, cb ProcessedCallback) (*gtsmodel.MediaAttachment, error) -	ProcessEmoji(ctx context.Context, data []byte, shortcode string) (*gtsmodel.Emoji, error) +// Manager provides an interface for managing media: parsing, storing, and retrieving media objects like photos, videos, and gifs. +type Manager interface { +	ProcessAttachment(ctx context.Context, data []byte, accountID string, cb ProcessCallback) (*gtsmodel.MediaAttachment, error)  } -type mediaHandler struct { +type manager struct {  	db      db.DB  	storage *kv.KVStore  } -// New returns a new handler with the given db and storage -func New(database db.DB, storage *kv.KVStore) Handler { -	return &mediaHandler{ +// New returns a media manager with the given db and underlying storage. +func New(database db.DB, storage *kv.KVStore) Manager { +	return &manager{  		db:      database,  		storage: storage,  	} @@ -64,83 +68,64 @@ func New(database db.DB, storage *kv.KVStore) Handler {  	INTERFACE FUNCTIONS  */ +func (m *manager) ProcessAttachment(ctx context.Context, data []byte, accountID string, cb ProcessCallback) (*gtsmodel.MediaAttachment, error) { +	contentType, err := parseContentType(data) +	if err != nil { +		return nil, err +	} + +	mainType := strings.Split(contentType, "/")[0] +	switch mainType { +	case mimeImage: +		if !supportedImage(contentType) { +			return nil, fmt.Errorf("image type %s not supported", contentType) +		} +		if len(data) == 0 { +			return nil, errors.New("image was of size 0") +		} +		return m.processImage(attachmentBytes, minAttachment) +	default: +		return nil, fmt.Errorf("content type %s not (yet) supported", contentType) +	} +} +  // ProcessHeaderOrAvatar takes a new header image for an account, checks it out, removes exif data from it,  // puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image,  // and then returns information to the caller about the new header. -func (mh *mediaHandler) ProcessHeaderOrAvatar(ctx context.Context, attachment []byte, accountID string, mediaType Type, remoteURL string) (*gtsmodel.MediaAttachment, error) { -	l := logrus.WithField("func", "SetHeaderForAccountID") - -	if mediaType != TypeHeader && mediaType != TypeAvatar { -		return nil, errors.New("header or avatar not selected") -	} +func (m *manager) ProcessHeader(ctx context.Context, data []byte, accountID string, cb ProcessCallback) (*gtsmodel.MediaAttachment, error) {  	// make sure we have a type we can handle -	contentType, err := parseContentType(attachment) +	contentType, err := parseContentType(data)  	if err != nil {  		return nil, err  	} +  	if !supportedImage(contentType) {  		return nil, fmt.Errorf("%s is not an accepted image type", contentType)  	} -	if len(attachment) == 0 { +	if len(data) == 0 {  		return nil, fmt.Errorf("passed reader was of size 0")  	} -	l.Tracef("read %d bytes of file", len(attachment))  	// process it -	ma, err := mh.processHeaderOrAvi(attachment, contentType, mediaType, accountID, remoteURL) +	ma, err := m.processHeaderOrAvi(attachment, contentType, mediaType, accountID, remoteURL)  	if err != nil {  		return nil, fmt.Errorf("error processing %s: %s", mediaType, err)  	}  	// set it in the database -	if err := mh.db.SetAccountHeaderOrAvatar(ctx, ma, accountID); err != nil { +	if err := m.db.SetAccountHeaderOrAvatar(ctx, ma, accountID); err != nil {  		return nil, fmt.Errorf("error putting %s in database: %s", mediaType, err)  	}  	return ma, nil  } -// ProcessAttachment takes a new attachment and the owning account, checks it out, removes exif data from it, -// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new media, -// and then returns information to the caller about the attachment. -func (mh *mediaHandler) ProcessAttachment(ctx context.Context, attachmentBytes []byte, minAttachment *gtsmodel.MediaAttachment) (*gtsmodel.MediaAttachment, error) { -	contentType, err := parseContentType(attachmentBytes) -	if err != nil { -		return nil, err -	} - -	minAttachment.File.ContentType = contentType - -	mainType := strings.Split(contentType, "/")[0] -	switch mainType { -	// case MIMEVideo: -	// 	if !SupportedVideoType(contentType) { -	// 		return nil, fmt.Errorf("video type %s not supported", contentType) -	// 	} -	// 	if len(attachment) == 0 { -	// 		return nil, errors.New("video was of size 0") -	// 	} -	// 	return mh.processVideoAttachment(attachment, accountID, contentType, remoteURL) -	case mimeImage: -		if !supportedImage(contentType) { -			return nil, fmt.Errorf("image type %s not supported", contentType) -		} -		if len(attachmentBytes) == 0 { -			return nil, errors.New("image was of size 0") -		} -		return mh.processImageAttachment(attachmentBytes, minAttachment) -	default: -		break -	} -	return nil, fmt.Errorf("content type %s not (yet) supported", contentType) -} -  // ProcessLocalEmoji takes a new emoji and a shortcode, cleans it up, puts it in storage, and creates a new  // *gts.Emoji for it, then returns it to the caller. It's the caller's responsibility to put the returned struct  // in the database. -func (mh *mediaHandler) ProcessLocalEmoji(ctx context.Context, emojiBytes []byte, shortcode string) (*gtsmodel.Emoji, error) { +func (m *manager) ProcessLocalEmoji(ctx context.Context, emojiBytes []byte, shortcode string) (*gtsmodel.Emoji, error) {  	var clean []byte  	var err error  	var original *imageAndMeta @@ -187,7 +172,7 @@ func (mh *mediaHandler) ProcessLocalEmoji(ctx context.Context, emojiBytes []byte  	// since emoji aren't 'owned' by an account, but we still want to use the same pattern for serving them through the filserver,  	// (ie., fileserver/ACCOUNT_ID/etc etc) we need to fetch the INSTANCE ACCOUNT from the database. That is, the account that's created  	// with the same username as the instance hostname, which doesn't belong to any particular user. -	instanceAccount, err := mh.db.GetInstanceAccount(ctx, "") +	instanceAccount, err := m.db.GetInstanceAccount(ctx, "")  	if err != nil {  		return nil, fmt.Errorf("error fetching instance account: %s", err)  	} @@ -214,12 +199,12 @@ func (mh *mediaHandler) ProcessLocalEmoji(ctx context.Context, emojiBytes []byte  	emojiStaticPath := fmt.Sprintf("%s/%s/%s/%s.png", instanceAccount.ID, TypeEmoji, SizeStatic, newEmojiID)  	// Store the original emoji -	if err := mh.storage.Put(emojiPath, original.image); err != nil { +	if err := m.storage.Put(emojiPath, original.image); err != nil {  		return nil, fmt.Errorf("storage error: %s", err)  	}  	// Store the static emoji -	if err := mh.storage.Put(emojiStaticPath, static.image); err != nil { +	if err := m.storage.Put(emojiStaticPath, static.image); err != nil {  		return nil, fmt.Errorf("storage error: %s", err)  	} @@ -249,7 +234,7 @@ func (mh *mediaHandler) ProcessLocalEmoji(ctx context.Context, emojiBytes []byte  	return e, nil  } -func (mh *mediaHandler) ProcessRemoteHeaderOrAvatar(ctx context.Context, t transport.Transport, currentAttachment *gtsmodel.MediaAttachment, accountID string) (*gtsmodel.MediaAttachment, error) { +func (m *manager) ProcessRemoteHeaderOrAvatar(ctx context.Context, t transport.Transport, currentAttachment *gtsmodel.MediaAttachment, accountID string) (*gtsmodel.MediaAttachment, error) {  	if !currentAttachment.Header && !currentAttachment.Avatar {  		return nil, errors.New("provided attachment was set to neither header nor avatar")  	} @@ -285,5 +270,5 @@ func (mh *mediaHandler) ProcessRemoteHeaderOrAvatar(ctx context.Context, t trans  		return nil, fmt.Errorf("dereferencing remote media with url %s: %s", remoteIRI.String(), err)  	} -	return mh.ProcessHeaderOrAvatar(ctx, attachmentBytes, accountID, headerOrAvi, currentAttachment.RemoteURL) +	return m.ProcessHeaderOrAvatar(ctx, attachmentBytes, accountID, headerOrAvi, currentAttachment.RemoteURL)  } diff --git a/internal/media/processicon.go b/internal/media/processicon.go deleted file mode 100644 index faeae0ee6..000000000 --- a/internal/media/processicon.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - -   This program is free software: you can redistribute it and/or modify -   it under the terms of the GNU Affero General Public License as published by -   the Free Software Foundation, either version 3 of the License, or -   (at your option) any later version. - -   This program is distributed in the hope that it will be useful, -   but WITHOUT ANY WARRANTY; without even the implied warranty of -   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -   GNU Affero General Public License for more details. - -   You should have received a copy of the GNU Affero General Public License -   along with this program.  If not, see <http://www.gnu.org/licenses/>. -*/ - -package media - -import ( -	"errors" -	"fmt" -	"strings" -	"time" - -	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -	"github.com/superseriousbusiness/gotosocial/internal/id" -	"github.com/superseriousbusiness/gotosocial/internal/uris" -) - -func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, mediaType Type, accountID string, remoteURL string) (*gtsmodel.MediaAttachment, error) { -	var isHeader bool -	var isAvatar bool - -	switch mediaType { -	case TypeHeader: -		isHeader = true -	case TypeAvatar: -		isAvatar = true -	default: -		return nil, errors.New("header or avatar not selected") -	} - -	var clean []byte -	var err error - -	var original *imageAndMeta -	switch contentType { -	case mimeJpeg: -		if clean, err = purgeExif(imageBytes); err != nil { -			return nil, fmt.Errorf("error cleaning exif data: %s", err) -		} -		original, err = deriveImage(clean, contentType) -	case mimePng: -		if clean, err = purgeExif(imageBytes); err != nil { -			return nil, fmt.Errorf("error cleaning exif data: %s", err) -		} -		original, err = deriveImage(clean, contentType) -	case mimeGif: -		clean = imageBytes -		original, err = deriveGif(clean, contentType) -	default: -		return nil, errors.New("media type unrecognized") -	} - -	if err != nil { -		return nil, fmt.Errorf("error parsing image: %s", err) -	} - -	small, err := deriveThumbnail(clean, contentType, 256, 256) -	if err != nil { -		return nil, fmt.Errorf("error deriving thumbnail: %s", err) -	} - -	// now put it in storage, take a new id for the name of the file so we don't store any unnecessary info about it -	extension := strings.Split(contentType, "/")[1] -	newMediaID, err := id.NewRandomULID() -	if err != nil { -		return nil, err -	} - -	originalURL := uris.GenerateURIForAttachment(accountID, string(mediaType), string(SizeOriginal), newMediaID, extension) -	smallURL := uris.GenerateURIForAttachment(accountID, string(mediaType), string(SizeSmall), newMediaID, extension) -	// we store the original... -	originalPath := fmt.Sprintf("%s/%s/%s/%s.%s", accountID, mediaType, SizeOriginal, newMediaID, extension) -	if err := mh.storage.Put(originalPath, original.image); err != nil { -		return nil, fmt.Errorf("storage error: %s", err) -	} - -	// and a thumbnail... -	smallPath := fmt.Sprintf("%s/%s/%s/%s.%s", accountID, mediaType, SizeSmall, newMediaID, extension) -	if err := mh.storage.Put(smallPath, small.image); err != nil { -		return nil, fmt.Errorf("storage error: %s", err) -	} - -	ma := >smodel.MediaAttachment{ -		ID:        newMediaID, -		StatusID:  "", -		URL:       originalURL, -		RemoteURL: remoteURL, -		CreatedAt: time.Now(), -		UpdatedAt: time.Now(), -		Type:      gtsmodel.FileTypeImage, -		FileMeta: gtsmodel.FileMeta{ -			Original: gtsmodel.Original{ -				Width:  original.width, -				Height: original.height, -				Size:   original.size, -				Aspect: original.aspect, -			}, -			Small: gtsmodel.Small{ -				Width:  small.width, -				Height: small.height, -				Size:   small.size, -				Aspect: small.aspect, -			}, -		}, -		AccountID:         accountID, -		Description:       "", -		ScheduledStatusID: "", -		Blurhash:          small.blurhash, -		Processing:        2, -		File: gtsmodel.File{ -			Path:        originalPath, -			ContentType: contentType, -			FileSize:    len(original.image), -			UpdatedAt:   time.Now(), -		}, -		Thumbnail: gtsmodel.Thumbnail{ -			Path:        smallPath, -			ContentType: contentType, -			FileSize:    len(small.image), -			UpdatedAt:   time.Now(), -			URL:         smallURL, -			RemoteURL:   "", -		}, -		Avatar: isAvatar, -		Header: isHeader, -	} - -	return ma, nil -} diff --git a/internal/media/processing.go b/internal/media/processing.go deleted file mode 100644 index ccd9ebfdb..000000000 --- a/internal/media/processing.go +++ /dev/null @@ -1,190 +0,0 @@ -package media - -import ( -	"bytes" -	"errors" -	"fmt" -	"image" -	"image/gif" -	"image/jpeg" -	"image/png" - -	"github.com/buckket/go-blurhash" -	"github.com/nfnt/resize" -	"github.com/superseriousbusiness/exifremove/pkg/exifremove" -) - -// purgeExif is a little wrapper for the action of removing exif data from an image. -// Only pass pngs or jpegs to this function. -func purgeExif(data []byte) ([]byte, error) { -	if len(data) == 0 { -		return nil, errors.New("passed image was not valid") -	} - -	clean, err := exifremove.Remove(data) -	if err != nil { -		return nil, fmt.Errorf("could not purge exif from image: %s", err) -	} - -	if len(clean) == 0 { -		return nil, errors.New("purged image was not valid") -	} -	 -	return clean, nil -} - -func deriveGif(b []byte, extension string) (*imageAndMeta, error) { -	var g *gif.GIF -	var err error -	switch extension { -	case mimeGif: -		g, err = gif.DecodeAll(bytes.NewReader(b)) -		if err != nil { -			return nil, err -		} -	default: -		return nil, fmt.Errorf("extension %s not recognised", extension) -	} - -	// use the first frame to get the static characteristics -	width := g.Config.Width -	height := g.Config.Height -	size := width * height -	aspect := float64(width) / float64(height) - -	return &imageAndMeta{ -		image:  b, -		width:  width, -		height: height, -		size:   size, -		aspect: aspect, -	}, nil -} - -func deriveImage(b []byte, contentType string) (*imageAndMeta, error) { -	var i image.Image -	var err error - -	switch contentType { -	case mimeImageJpeg: -		i, err = jpeg.Decode(bytes.NewReader(b)) -		if err != nil { -			return nil, err -		} -	case mimeImagePng: -		i, err = png.Decode(bytes.NewReader(b)) -		if err != nil { -			return nil, err -		} -	default: -		return nil, fmt.Errorf("content type %s not recognised", contentType) -	} - -	width := i.Bounds().Size().X -	height := i.Bounds().Size().Y -	size := width * height -	aspect := float64(width) / float64(height) - -	return &imageAndMeta{ -		image:  b, -		width:  width, -		height: height, -		size:   size, -		aspect: aspect, -	}, nil -} - -// deriveThumbnail returns a byte slice and metadata for a thumbnail of width x and height y, -// of a given jpeg, png, or gif, or an error if something goes wrong. -// -// Note that the aspect ratio of the image will be retained, -// so it will not necessarily be a square, even if x and y are set as the same value. -func deriveThumbnail(b []byte, contentType string, x uint, y uint) (*imageAndMeta, error) { -	var i image.Image -	var err error - -	switch contentType { -	case mimeImageJpeg: -		i, err = jpeg.Decode(bytes.NewReader(b)) -		if err != nil { -			return nil, err -		} -	case mimeImagePng: -		i, err = png.Decode(bytes.NewReader(b)) -		if err != nil { -			return nil, err -		} -	case mimeImageGif: -		i, err = gif.Decode(bytes.NewReader(b)) -		if err != nil { -			return nil, err -		} -	default: -		return nil, fmt.Errorf("content type %s not recognised", contentType) -	} - -	thumb := resize.Thumbnail(x, y, i, resize.NearestNeighbor) -	width := thumb.Bounds().Size().X -	height := thumb.Bounds().Size().Y -	size := width * height -	aspect := float64(width) / float64(height) - -	tiny := resize.Thumbnail(32, 32, thumb, resize.NearestNeighbor) -	bh, err := blurhash.Encode(4, 3, tiny) -	if err != nil { -		return nil, err -	} - -	out := &bytes.Buffer{} -	if err := jpeg.Encode(out, thumb, &jpeg.Options{ -		Quality: 75, -	}); err != nil { -		return nil, err -	} -	return &imageAndMeta{ -		image:    out.Bytes(), -		width:    width, -		height:   height, -		size:     size, -		aspect:   aspect, -		blurhash: bh, -	}, nil -} - -// deriveStaticEmojji takes a given gif or png of an emoji, decodes it, and re-encodes it as a static png. -func deriveStaticEmoji(b []byte, contentType string) (*imageAndMeta, error) { -	var i image.Image -	var err error - -	switch contentType { -	case mimeImagePng: -		i, err = png.Decode(bytes.NewReader(b)) -		if err != nil { -			return nil, err -		} -	case mimeImageGif: -		i, err = gif.Decode(bytes.NewReader(b)) -		if err != nil { -			return nil, err -		} -	default: -		return nil, fmt.Errorf("content type %s not allowed for emoji", contentType) -	} - -	out := &bytes.Buffer{} -	if err := png.Encode(out, i); err != nil { -		return nil, err -	} -	return &imageAndMeta{ -		image: out.Bytes(), -	}, nil -} - -type imageAndMeta struct { -	image    []byte -	width    int -	height   int -	size     int -	aspect   float64 -	blurhash string -} diff --git a/internal/media/processvideo.go b/internal/media/processvideo.go index d0d11f779..e829c68c0 100644 --- a/internal/media/processvideo.go +++ b/internal/media/processvideo.go @@ -18,6 +18,6 @@  package media -// func (mh *mediaHandler) processVideoAttachment(data []byte, accountID string, contentType string, remoteURL string) (*gtsmodel.MediaAttachment, error) { +// func (mh *mediaManager) processVideoAttachment(data []byte, accountID string, contentType string, remoteURL string) (*gtsmodel.MediaAttachment, error) {  // 	return nil, nil  // } diff --git a/internal/media/types.go b/internal/media/types.go index f1608f880..d40f402d2 100644 --- a/internal/media/types.go +++ b/internal/media/types.go @@ -40,7 +40,6 @@ const (  	mimeImagePng = mimeImage + "/" + mimePng  ) -  // EmojiMaxBytes is the maximum permitted bytes of an emoji upload (50kb)  // const EmojiMaxBytes = 51200 diff --git a/internal/media/util_test.go b/internal/media/util_test.go index 817b597cb..1180bf2aa 100644 --- a/internal/media/util_test.go +++ b/internal/media/util_test.go @@ -103,7 +103,7 @@ func (suite *MediaUtilTestSuite) TestDeriveImageFromJPEG() {  	suite.NoError(err)  	// clean it up and validate the clean version -	imageAndMeta, err := deriveImage(b, "image/jpeg") +	imageAndMeta, err := decodeImage(b, "image/jpeg")  	suite.NoError(err)  	suite.Equal(1920, imageAndMeta.width) diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go index ae005f4f6..b2321f414 100644 --- a/internal/processing/account/account.go +++ b/internal/processing/account/account.go @@ -77,7 +77,7 @@ type Processor interface {  type processor struct {  	tc            typeutils.TypeConverter -	mediaHandler  media.Handler +	mediaManager  media.Manager  	fromClientAPI chan messages.FromClientAPI  	oauthServer   oauth.Server  	filter        visibility.Filter @@ -87,10 +87,10 @@ type processor struct {  }  // New returns a new account processor. -func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauthServer oauth.Server, fromClientAPI chan messages.FromClientAPI, federator federation.Federator) Processor { +func New(db db.DB, tc typeutils.TypeConverter, mediaManager media.Manager, oauthServer oauth.Server, fromClientAPI chan messages.FromClientAPI, federator federation.Federator) Processor {  	return &processor{  		tc:            tc, -		mediaHandler:  mediaHandler, +		mediaManager:  mediaManager,  		fromClientAPI: fromClientAPI,  		oauthServer:   oauthServer,  		filter:        visibility.NewFilter(db), diff --git a/internal/processing/account/account_test.go b/internal/processing/account/account_test.go index e4611ba23..9c7f0fe67 100644 --- a/internal/processing/account/account_test.go +++ b/internal/processing/account/account_test.go @@ -41,7 +41,7 @@ type AccountStandardTestSuite struct {  	db                  db.DB  	tc                  typeutils.TypeConverter  	storage             *kv.KVStore -	mediaHandler        media.Handler +	mediaManager        media.Manager  	oauthServer         oauth.Server  	fromClientAPIChan   chan messages.FromClientAPI  	httpClient          pub.HttpClient @@ -80,7 +80,7 @@ func (suite *AccountStandardTestSuite) SetupTest() {  	suite.db = testrig.NewTestDB()  	suite.tc = testrig.NewTestTypeConverter(suite.db)  	suite.storage = testrig.NewTestStorage() -	suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) +	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage)  	suite.oauthServer = testrig.NewTestOauthServer(suite.db)  	suite.fromClientAPIChan = make(chan messages.FromClientAPI, 100)  	suite.httpClient = testrig.NewMockHTTPClient(nil) @@ -88,7 +88,7 @@ func (suite *AccountStandardTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage)  	suite.sentEmails = make(map[string]string)  	suite.emailSender = testrig.NewEmailSender("../../../web/template/", suite.sentEmails) -	suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaHandler, suite.oauthServer, suite.fromClientAPIChan, suite.federator) +	suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaManager, suite.oauthServer, suite.fromClientAPIChan, suite.federator)  	testrig.StandardDBSetup(suite.db, nil)  	testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")  } diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go index a32dd9ac0..8de6c83f0 100644 --- a/internal/processing/account/update.go +++ b/internal/processing/account/update.go @@ -159,7 +159,7 @@ func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHead  	}  	// do the setting -	avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(ctx, buf.Bytes(), accountID, media.TypeAvatar, "") +	avatarInfo, err := p.mediaManager.ProcessHeaderOrAvatar(ctx, buf.Bytes(), accountID, media.TypeAvatar, "")  	if err != nil {  		return nil, fmt.Errorf("error processing avatar: %s", err)  	} @@ -193,7 +193,7 @@ func (p *processor) UpdateHeader(ctx context.Context, header *multipart.FileHead  	}  	// do the setting -	headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(ctx, buf.Bytes(), accountID, media.TypeHeader, "") +	headerInfo, err := p.mediaManager.ProcessHeaderOrAvatar(ctx, buf.Bytes(), accountID, media.TypeHeader, "")  	if err != nil {  		return nil, fmt.Errorf("error processing header: %s", err)  	} diff --git a/internal/processing/admin/admin.go b/internal/processing/admin/admin.go index 217d10dfe..27a7da47a 100644 --- a/internal/processing/admin/admin.go +++ b/internal/processing/admin/admin.go @@ -43,16 +43,16 @@ type Processor interface {  type processor struct {  	tc            typeutils.TypeConverter -	mediaHandler  media.Handler +	mediaManager  media.Manager  	fromClientAPI chan messages.FromClientAPI  	db            db.DB  }  // New returns a new admin processor. -func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, fromClientAPI chan messages.FromClientAPI) Processor { +func New(db db.DB, tc typeutils.TypeConverter, mediaManager media.Manager, fromClientAPI chan messages.FromClientAPI) Processor {  	return &processor{  		tc:            tc, -		mediaHandler:  mediaHandler, +		mediaManager:  mediaManager,  		fromClientAPI: fromClientAPI,  		db:            db,  	} diff --git a/internal/processing/admin/emoji.go b/internal/processing/admin/emoji.go index 4989d8e8d..5620374b8 100644 --- a/internal/processing/admin/emoji.go +++ b/internal/processing/admin/emoji.go @@ -49,8 +49,8 @@ func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account,  		return nil, errors.New("could not read provided emoji: size 0 bytes")  	} -	// allow the mediaHandler to work its magic of processing the emoji bytes, and putting them in whatever storage backend we're using -	emoji, err := p.mediaHandler.ProcessLocalEmoji(ctx, buf.Bytes(), form.Shortcode) +	// allow the mediaManager to work its magic of processing the emoji bytes, and putting them in whatever storage backend we're using +	emoji, err := p.mediaManager.ProcessLocalEmoji(ctx, buf.Bytes(), form.Shortcode)  	if err != nil {  		return nil, fmt.Errorf("error reading emoji: %s", err)  	} diff --git a/internal/processing/media/create.go b/internal/processing/media/create.go index de15d3162..68a011683 100644 --- a/internal/processing/media/create.go +++ b/internal/processing/media/create.go @@ -65,8 +65,8 @@ func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, form  		},  	} -	// allow the mediaHandler to work its magic of processing the attachment bytes, and putting them in whatever storage backend we're using -	attachment, err := p.mediaHandler.ProcessAttachment(ctx, buf.Bytes(), minAttachment) +	// allow the mediaManager to work its magic of processing the attachment bytes, and putting them in whatever storage backend we're using +	attachment, err := p.mediaManager.ProcessAttachment(ctx, buf.Bytes(), minAttachment)  	if err != nil {  		return nil, fmt.Errorf("error reading attachment: %s", err)  	} diff --git a/internal/processing/media/media.go b/internal/processing/media/media.go index 9e050fe84..3d4ae5009 100644 --- a/internal/processing/media/media.go +++ b/internal/processing/media/media.go @@ -43,16 +43,16 @@ type Processor interface {  type processor struct {  	tc           typeutils.TypeConverter -	mediaHandler media.Handler +	mediaManager media.Manager  	storage      *kv.KVStore  	db           db.DB  }  // New returns a new media processor. -func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, storage *kv.KVStore) Processor { +func New(db db.DB, tc typeutils.TypeConverter, mediaManager media.Manager, storage *kv.KVStore) Processor {  	return &processor{  		tc:           tc, -		mediaHandler: mediaHandler, +		mediaManager: mediaManager,  		storage:      storage,  		db:           db,  	} diff --git a/internal/processing/processor.go b/internal/processing/processor.go index f5334a1ef..2626c1fea 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -235,7 +235,7 @@ type processor struct {  	stop            chan interface{}  	tc              typeutils.TypeConverter  	oauthServer     oauth.Server -	mediaHandler    media.Handler +	mediaManager    media.Manager  	storage         *kv.KVStore  	timelineManager timeline.Manager  	db              db.DB @@ -259,7 +259,7 @@ func NewProcessor(  	tc typeutils.TypeConverter,  	federator federation.Federator,  	oauthServer oauth.Server, -	mediaHandler media.Handler, +	mediaManager media.Manager,  	storage *kv.KVStore,  	timelineManager timeline.Manager,  	db db.DB, @@ -269,9 +269,9 @@ func NewProcessor(  	statusProcessor := status.New(db, tc, fromClientAPI)  	streamingProcessor := streaming.New(db, oauthServer) -	accountProcessor := account.New(db, tc, mediaHandler, oauthServer, fromClientAPI, federator) -	adminProcessor := admin.New(db, tc, mediaHandler, fromClientAPI) -	mediaProcessor := mediaProcessor.New(db, tc, mediaHandler, storage) +	accountProcessor := account.New(db, tc, mediaManager, oauthServer, fromClientAPI, federator) +	adminProcessor := admin.New(db, tc, mediaManager, fromClientAPI) +	mediaProcessor := mediaProcessor.New(db, tc, mediaManager, storage)  	userProcessor := user.New(db, emailSender)  	federationProcessor := federationProcessor.New(db, tc, federator, fromFederator) @@ -282,7 +282,7 @@ func NewProcessor(  		stop:            make(chan interface{}),  		tc:              tc,  		oauthServer:     oauthServer, -		mediaHandler:    mediaHandler, +		mediaManager:    mediaManager,  		storage:         storage,  		timelineManager: timelineManager,  		db:              db, diff --git a/internal/processing/processor_test.go b/internal/processing/processor_test.go index dc7562a2e..04793898f 100644 --- a/internal/processing/processor_test.go +++ b/internal/processing/processor_test.go @@ -51,7 +51,7 @@ type ProcessingStandardTestSuite struct {  	transportController transport.Controller  	federator           federation.Federator  	oauthServer         oauth.Server -	mediaHandler        media.Handler +	mediaManager        media.Manager  	timelineManager     timeline.Manager  	emailSender         email.Sender @@ -218,11 +218,11 @@ func (suite *ProcessingStandardTestSuite) SetupTest() {  	suite.transportController = testrig.NewTestTransportController(httpClient, suite.db)  	suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage)  	suite.oauthServer = testrig.NewTestOauthServer(suite.db) -	suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage) +	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage)  	suite.timelineManager = testrig.NewTestTimelineManager(suite.db)  	suite.emailSender = testrig.NewEmailSender("../../web/template/", nil) -	suite.processor = processing.NewProcessor(suite.typeconverter, suite.federator, suite.oauthServer, suite.mediaHandler, suite.storage, suite.timelineManager, suite.db, suite.emailSender) +	suite.processor = processing.NewProcessor(suite.typeconverter, suite.federator, suite.oauthServer, suite.mediaManager, suite.storage, suite.timelineManager, suite.db, suite.emailSender)  	testrig.StandardDBSetup(suite.db, suite.testAccounts)  	testrig.StandardStorageSetup(suite.storage, "../../testrig/media") diff --git a/testrig/federator.go b/testrig/federator.go index b04c01d63..b9c742832 100644 --- a/testrig/federator.go +++ b/testrig/federator.go @@ -27,5 +27,5 @@ import (  // NewTestFederator returns a federator with the given database and (mock!!) transport controller.  func NewTestFederator(db db.DB, tc transport.Controller, storage *kv.KVStore) federation.Federator { -	return federation.NewFederator(db, NewTestFederatingDB(db), tc, NewTestTypeConverter(db), NewTestMediaHandler(db, storage)) +	return federation.NewFederator(db, NewTestFederatingDB(db), tc, NewTestTypeConverter(db), NewTestMediaManager(db, storage))  } diff --git a/testrig/mediahandler.go b/testrig/mediahandler.go index ab7fee621..ba2148655 100644 --- a/testrig/mediahandler.go +++ b/testrig/mediahandler.go @@ -24,7 +24,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/media"  ) -// NewTestMediaHandler returns a media handler with the default test config, and the given db and storage. -func NewTestMediaHandler(db db.DB, storage *kv.KVStore) media.Handler { +// NewTestMediaManager returns a media handler with the default test config, and the given db and storage. +func NewTestMediaManager(db db.DB, storage *kv.KVStore) media.Manager {  	return media.New(db, storage)  } diff --git a/testrig/processor.go b/testrig/processor.go index 0baffd35b..d86dd8411 100644 --- a/testrig/processor.go +++ b/testrig/processor.go @@ -28,5 +28,5 @@ import (  // NewTestProcessor returns a Processor suitable for testing purposes  func NewTestProcessor(db db.DB, storage *kv.KVStore, federator federation.Federator, emailSender email.Sender) processing.Processor { -	return processing.NewProcessor(NewTestTypeConverter(db), federator, NewTestOauthServer(db), NewTestMediaHandler(db, storage), storage, NewTestTimelineManager(db), db, emailSender) +	return processing.NewProcessor(NewTestTypeConverter(db), federator, NewTestOauthServer(db), NewTestMediaManager(db, storage), storage, NewTestTimelineManager(db), db, emailSender)  }  | 
