diff options
author | 2022-01-08 17:17:01 +0100 | |
---|---|---|
committer | 2022-01-08 17:17:01 +0100 | |
commit | f61c3ddcf72ff689b9d253546c58d499b6fe6ac8 (patch) | |
tree | b418d5a833f0e3b92b255e73efa98007eb8127ac /internal/media | |
parent | further refinements (diff) | |
download | gotosocial-f61c3ddcf72ff689b9d253546c58d499b6fe6ac8.tar.xz |
compiling now
Diffstat (limited to 'internal/media')
-rw-r--r-- | internal/media/manager.go | 25 | ||||
-rw-r--r-- | internal/media/manager_test.go | 4 | ||||
-rw-r--r-- | internal/media/media.go | 113 | ||||
-rw-r--r-- | internal/media/media_test.go | 65 |
4 files changed, 187 insertions, 20 deletions
diff --git a/internal/media/manager.go b/internal/media/manager.go index 8032ab42d..9ca450141 100644 --- a/internal/media/manager.go +++ b/internal/media/manager.go @@ -37,7 +37,17 @@ import ( // Manager provides an interface for managing media: parsing, storing, and retrieving media objects like photos, videos, and gifs. type Manager interface { - ProcessMedia(ctx context.Context, data []byte, accountID string) (*Media, error) + // ProcessMedia begins the process of decoding and storing the given data as a piece of media (aka an attachment). + // It will return a pointer to a Media struct upon which further actions can be performed, such as getting + // the finished media, thumbnail, decoded bytes, attachment, and setting additional fields. + // + // accountID should be the account that the media belongs to. + // + // RemoteURL is optional, and can be an empty string. Setting this to a non-empty string indicates that + // the piece of media originated on a remote instance and has been dereferenced to be cached locally. + ProcessMedia(ctx context.Context, data []byte, accountID string, remoteURL string) (*Media, error) + + ProcessEmoji(ctx context.Context, data []byte, accountID string, remoteURL string) (*Media, error) } type manager struct { @@ -70,7 +80,7 @@ func New(database db.DB, storage *kv.KVStore) (Manager, error) { INTERFACE FUNCTIONS */ -func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID string) (*Media, error) { +func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID string, remoteURL string) (*Media, error) { contentType, err := parseContentType(data) if err != nil { return nil, err @@ -85,7 +95,7 @@ func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID strin switch mainType { case mimeImage: - media, err := m.preProcessImage(ctx, data, contentType, accountID) + media, err := m.preProcessImage(ctx, data, contentType, accountID, remoteURL) if err != nil { return nil, err } @@ -97,7 +107,7 @@ func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID strin return default: // start preloading the media for the caller's convenience - media.PreLoad(innerCtx) + media.preLoad(innerCtx) } }) @@ -107,8 +117,12 @@ func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID strin } } +func (m *manager) ProcessEmoji(ctx context.Context, data []byte, accountID string, remoteURL string) (*Media, error) { + return nil, nil +} + // preProcessImage initializes processing -func (m *manager) preProcessImage(ctx context.Context, data []byte, contentType string, accountID string) (*Media, error) { +func (m *manager) preProcessImage(ctx context.Context, data []byte, contentType string, accountID string, remoteURL string) (*Media, error) { if !supportedImage(contentType) { return nil, fmt.Errorf("image type %s not supported", contentType) } @@ -128,6 +142,7 @@ func (m *manager) preProcessImage(ctx context.Context, data []byte, contentType ID: id, UpdatedAt: time.Now(), URL: uris.GenerateURIForAttachment(accountID, string(TypeAttachment), string(SizeOriginal), id, extension), + RemoteURL: remoteURL, Type: gtsmodel.FileTypeImage, AccountID: accountID, Processing: 0, diff --git a/internal/media/manager_test.go b/internal/media/manager_test.go new file mode 100644 index 000000000..45428fbba --- /dev/null +++ b/internal/media/manager_test.go @@ -0,0 +1,4 @@ +package media_test + + + diff --git a/internal/media/media.go b/internal/media/media.go index 022de063e..e19997391 100644 --- a/internal/media/media.go +++ b/internal/media/media.go @@ -1,9 +1,28 @@ +/* + 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 ( "context" "fmt" "sync" + "time" "codeberg.org/gruf/go-store/kv" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -26,7 +45,8 @@ type Media struct { attachment will be updated incrementally as media goes through processing */ - attachment *gtsmodel.MediaAttachment + attachment *gtsmodel.MediaAttachment // will only be set if the media is an attachment + emoji *gtsmodel.Emoji // will only be set if the media is an emoji rawData []byte /* @@ -86,17 +106,10 @@ func (m *Media) Thumb(ctx context.Context) (*ImageMeta, error) { m.attachment.Thumbnail.FileSize = thumb.size // put or update the attachment in the database - if err := m.database.Put(ctx, m.attachment); err != nil { - if err != db.ErrAlreadyExists { - m.err = fmt.Errorf("error putting attachment: %s", err) - m.thumbstate = errored - return nil, m.err - } - if err := m.database.UpdateByPrimaryKey(ctx, m.attachment); err != nil { - m.err = fmt.Errorf("error updating attachment: %s", err) - m.thumbstate = errored - return nil, m.err - } + if err := putOrUpdateAttachment(ctx, m.database, m.attachment); err != nil { + m.err = err + m.thumbstate = errored + return nil, err } // set the thumbnail of this media @@ -148,6 +161,30 @@ func (m *Media) FullSize(ctx context.Context) (*ImageMeta, error) { return nil, err } + // put the full size in storage + if err := m.storage.Put(m.attachment.File.Path, decoded.image); err != nil { + m.err = fmt.Errorf("error storing full size image: %s", err) + m.fullSizeState = errored + return nil, m.err + } + + // set appropriate fields on the attachment based on the image we derived + m.attachment.FileMeta.Original = gtsmodel.Original{ + Width: decoded.width, + Height: decoded.height, + Size: decoded.size, + Aspect: decoded.aspect, + } + m.attachment.File.FileSize = decoded.size + m.attachment.File.UpdatedAt = time.Now() + + // put or update the attachment in the database + if err := putOrUpdateAttachment(ctx, m.database, m.attachment); err != nil { + m.err = err + m.fullSizeState = errored + return nil, err + } + // set the fullsize of this media m.fullSize = decoded @@ -163,17 +200,46 @@ func (m *Media) FullSize(ctx context.Context) (*ImageMeta, error) { return nil, fmt.Errorf("full size processing status %d unknown", m.fullSizeState) } -// PreLoad begins the process of deriving the thumbnail and encoding the full-size image. +func (m *Media) SetAsAvatar(ctx context.Context) error { + m.mu.Lock() + defer m.mu.Unlock() + + m.attachment.Avatar = true + return putOrUpdateAttachment(ctx, m.database, m.attachment) +} + +func (m *Media) SetAsHeader(ctx context.Context) error { + m.mu.Lock() + defer m.mu.Unlock() + + m.attachment.Header = true + return putOrUpdateAttachment(ctx, m.database, m.attachment) +} + +func (m *Media) SetStatusID(ctx context.Context, statusID string) error { + m.mu.Lock() + defer m.mu.Unlock() + + m.attachment.StatusID = statusID + return putOrUpdateAttachment(ctx, m.database, m.attachment) +} + +// AttachmentID returns the ID of the underlying media attachment without blocking processing. +func (m *Media) AttachmentID() string { + return m.attachment.ID +} + +// preLoad begins the process of deriving the thumbnail and encoding the full-size image. // It does this in a non-blocking way, so you can call it and then come back later and check // if it's finished. -func (m *Media) PreLoad(ctx context.Context) { +func (m *Media) preLoad(ctx context.Context) { go m.Thumb(ctx) go m.FullSize(ctx) } // Load is the blocking equivalent of pre-load. It makes sure the thumbnail and full-size image // have been processed, then it returns the full-size image. -func (m *Media) Load(ctx context.Context) (*gtsmodel.MediaAttachment, error) { +func (m *Media) LoadAttachment(ctx context.Context) (*gtsmodel.MediaAttachment, error) { if _, err := m.Thumb(ctx); err != nil { return nil, err } @@ -184,3 +250,20 @@ func (m *Media) Load(ctx context.Context) (*gtsmodel.MediaAttachment, error) { return m.attachment, nil } + +func (m *Media) LoadEmoji(ctx context.Context) (*gtsmodel.Emoji, error) { + return nil, nil +} + +func putOrUpdateAttachment(ctx context.Context, database db.DB, attachment *gtsmodel.MediaAttachment) error { + if err := database.Put(ctx, attachment); err != nil { + if err != db.ErrAlreadyExists { + return fmt.Errorf("putOrUpdateAttachment: proper error while putting attachment: %s", err) + } + if err := database.UpdateByPrimaryKey(ctx, attachment); err != nil { + return fmt.Errorf("putOrUpdateAttachment: error while updating attachment: %s", err) + } + } + + return nil +} diff --git a/internal/media/media_test.go b/internal/media/media_test.go new file mode 100644 index 000000000..7e820c9ea --- /dev/null +++ b/internal/media/media_test.go @@ -0,0 +1,65 @@ +/* + 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_test + +import ( + "testing" + + "codeberg.org/gruf/go-store/kv" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/media" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type MediaStandardTestSuite struct { + suite.Suite + + db db.DB + storage *kv.KVStore + manager media.Manager +} + +func (suite *MediaStandardTestSuite) SetupSuite() { + testrig.InitTestLog() + testrig.InitTestConfig() + + suite.db = testrig.NewTestDB() + suite.storage = testrig.NewTestStorage() +} + +func (suite *MediaStandardTestSuite) SetupTest() { + testrig.StandardStorageSetup(suite.storage, "../../testrig/media") + testrig.StandardDBSetup(suite.db, nil) + + m, err := media.New(suite.db, suite.storage) + if err != nil { + panic(err) + } + suite.manager = m +} + +func (suite *MediaStandardTestSuite) TearDownTest() { + testrig.StandardDBTeardown(suite.db) + testrig.StandardStorageTeardown(suite.storage) +} + +func TestMediaStandardTestSuite(t *testing.T) { + suite.Run(t, &MediaStandardTestSuite{}) +} |