summaryrefslogtreecommitdiff
path: root/internal/media
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2023-05-28 13:08:35 +0100
committerLibravatar GitHub <noreply@github.com>2023-05-28 14:08:35 +0200
commit5faeb4de2032e112ab49751eeeb906ac43826f3d (patch)
treeea94b86f27384954ff93aec864b13b83c7f46db0 /internal/media
parent[docs] Update + simplify roadmap, revise beta estimate (#1826) (diff)
downloadgotosocial-5faeb4de2032e112ab49751eeeb906ac43826f3d.tar.xz
[chore] tidy up media manager, add calling func to errors, build-script improvements (#1835)
* media manager tidy-up: de-interface and remove unused PostDataFunc Signed-off-by: kim <grufwub@gmail.com> * remove last traces of media.Manager being an interface Signed-off-by: kim <grufwub@gmail.com> * update error to provide caller, allow tuneable via build tags Signed-off-by: kim <grufwub@gmail.com> * remove kim-specific build script changes Signed-off-by: kim <grufwub@gmail.com> * fix merge conflicts Signed-off-by: kim <grufwub@gmail.com> * update build-script to support externally setting build variables Signed-off-by: kim <grufwub@gmail.com> --------- Signed-off-by: kim <grufwub@gmail.com>
Diffstat (limited to 'internal/media')
-rw-r--r--internal/media/manager.go201
-rw-r--r--internal/media/manager_test.go47
-rw-r--r--internal/media/media_test.go2
-rw-r--r--internal/media/processingemoji.go30
-rw-r--r--internal/media/processingmedia.go14
-rw-r--r--internal/media/prune.go46
-rw-r--r--internal/media/prune_test.go2
-rw-r--r--internal/media/refetch.go12
-rw-r--r--internal/media/types.go6
9 files changed, 140 insertions, 220 deletions
diff --git a/internal/media/manager.go b/internal/media/manager.go
index b12b6ace2..ec95b67e9 100644
--- a/internal/media/manager.go
+++ b/internal/media/manager.go
@@ -21,8 +21,10 @@ import (
"context"
"errors"
"fmt"
+ "io"
"time"
+ "codeberg.org/gruf/go-iotools"
"codeberg.org/gruf/go-runners"
"codeberg.org/gruf/go-sched"
"codeberg.org/gruf/go-store/v2/storage"
@@ -47,112 +49,7 @@ var SupportedEmojiMIMETypes = []string{
mimeImagePng,
}
-// Manager provides an interface for managing media: parsing, storing, and retrieving media objects like photos, videos, and gifs.
-type Manager interface {
- /*
- PROCESSING FUNCTIONS
- */
-
- // PreProcessMedia begins the process of decoding and storing the given data as an attachment.
- // It will return a pointer to a ProcessingMedia struct upon which further actions can be performed, such as getting
- // the finished media, thumbnail, attachment, etc.
- //
- // data should be a function that the media manager can call to return a reader containing the media data.
- //
- // postData will be called after data has been called; it can be used to clean up any remaining resources.
- // The provided function can be nil, in which case it will not be executed.
- //
- // accountID should be the account that the media belongs to.
- //
- // ai is optional and can be nil. Any additional information about the attachment provided will be put in the database.
- //
- // Note: unlike ProcessMedia, this will NOT queue the media to be asynchronously processed.
- PreProcessMedia(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, accountID string, ai *AdditionalMediaInfo) (*ProcessingMedia, error)
-
- // PreProcessMediaRecache refetches, reprocesses, and recaches an existing attachment that has been uncached via pruneRemote.
- //
- // Note: unlike ProcessMedia, this will NOT queue the media to be asychronously processed.
- PreProcessMediaRecache(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, attachmentID string) (*ProcessingMedia, error)
-
- // ProcessMedia will call PreProcessMedia, followed by queuing the media to be processing in the media worker queue.
- ProcessMedia(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, accountID string, ai *AdditionalMediaInfo) (*ProcessingMedia, error)
-
- // PreProcessEmoji begins the process of decoding and storing the given data as an emoji.
- // It will return a pointer to a ProcessingEmoji struct upon which further actions can be performed, such as getting
- // the finished media, thumbnail, attachment, etc.
- //
- // data should be a function that the media manager can call to return a reader containing the emoji data.
- //
- // postData will be called after data has been called; it can be used to clean up any remaining resources.
- // The provided function can be nil, in which case it will not be executed.
- //
- // shortcode should be the emoji shortcode without the ':'s around it.
- //
- // id is the database ID that should be used to store the emoji.
- //
- // uri is the ActivityPub URI/ID of the emoji.
- //
- // ai is optional and can be nil. Any additional information about the emoji provided will be put in the database.
- //
- // Note: unlike ProcessEmoji, this will NOT queue the emoji to be asynchronously processed.
- PreProcessEmoji(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, shortcode string, id string, uri string, ai *AdditionalEmojiInfo, refresh bool) (*ProcessingEmoji, error)
-
- // ProcessEmoji will call PreProcessEmoji, followed by queuing the emoji to be processing in the emoji worker queue.
- ProcessEmoji(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, shortcode string, id string, uri string, ai *AdditionalEmojiInfo, refresh bool) (*ProcessingEmoji, error)
-
- /*
- PRUNING/UNCACHING FUNCTIONS
- */
-
- // PruneAll runs all of the below pruning/uncacheing functions, and then cleans up any resulting
- // empty directories from the storage driver. It can be called as a shortcut for calling the below
- // pruning functions one by one.
- //
- // If blocking is true, then any errors encountered during the prune will be combined + returned to
- // the caller. If blocking is false, the prune is run in the background and errors are just logged
- // instead.
- PruneAll(ctx context.Context, mediaCacheRemoteDays int, blocking bool) error
- // UncacheRemote uncaches all remote media attachments older than the given amount of days.
- //
- // In this context, uncacheing means deleting media files from storage and marking the attachment
- // as cached=false in the database.
- //
- // If 'dry' is true, then only a dry run will be performed: nothing will actually be changed.
- //
- // The returned int is the amount of media that was/would be uncached by this function.
- UncacheRemote(ctx context.Context, olderThanDays int, dry bool) (int, error)
- // PruneUnusedRemote prunes unused/out of date headers and avatars cached on this instance.
- //
- // The returned int is the amount of media that was pruned by this function.
- PruneUnusedRemote(ctx context.Context, dry bool) (int, error)
- // PruneUnusedLocal prunes unused media attachments that were uploaded by
- // a user on this instance, but never actually attached to a status, or attached but
- // later detached.
- //
- // The returned int is the amount of media that was pruned by this function.
- PruneUnusedLocal(ctx context.Context, dry bool) (int, error)
- // PruneOrphaned prunes files that exist in storage but which do not have a corresponding
- // entry in the database.
- //
- // If dry is true, then nothing will be changed, only the amount that *would* be removed
- // is returned to the caller.
- PruneOrphaned(ctx context.Context, dry bool) (int, error)
-
- /*
- REFETCHING FUNCTIONS
- Useful when data loss has occurred.
- */
-
- // RefetchEmojis iterates through remote emojis (for the given domain, or all if domain is empty string).
- //
- // For each emoji, the manager will check whether both the full size and static images are present in storage.
- // If not, the manager will refetch and reprocess full size and static images for the emoji.
- //
- // The provided DereferenceMedia function will be used when it's necessary to refetch something this way.
- RefetchEmojis(ctx context.Context, domain string, dereferenceMedia DereferenceMedia) (int, error)
-}
-
-type manager struct {
+type Manager struct {
state *state.State
}
@@ -162,13 +59,24 @@ type manager struct {
// a limited number of media will be processed in parallel. The numbers of workers
// is determined from the $GOMAXPROCS environment variable (usually no. CPU cores).
// See internal/concurrency.NewWorkerPool() documentation for further information.
-func NewManager(state *state.State) Manager {
- m := &manager{state: state}
+func NewManager(state *state.State) *Manager {
+ m := &Manager{state: state}
scheduleCleanupJobs(m)
return m
}
-func (m *manager) PreProcessMedia(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, accountID string, ai *AdditionalMediaInfo) (*ProcessingMedia, error) {
+// PreProcessMedia begins the process of decoding and storing the given data as an attachment.
+// It will return a pointer to a ProcessingMedia struct upon which further actions can be performed, such as getting
+// the finished media, thumbnail, attachment, etc.
+//
+// data should be a function that the media manager can call to return a reader containing the media data.
+//
+// accountID should be the account that the media belongs to.
+//
+// ai is optional and can be nil. Any additional information about the attachment provided will be put in the database.
+//
+// Note: unlike ProcessMedia, this will NOT queue the media to be asynchronously processed.
+func (m *Manager) PreProcessMedia(ctx context.Context, data DataFunc, accountID string, ai *AdditionalMediaInfo) (*ProcessingMedia, error) {
id, err := id.NewRandomULID()
if err != nil {
return nil, err
@@ -248,14 +156,16 @@ func (m *manager) PreProcessMedia(ctx context.Context, data DataFunc, postData P
processingMedia := &ProcessingMedia{
media: attachment,
dataFn: data,
- postFn: postData,
mgr: m,
}
return processingMedia, nil
}
-func (m *manager) PreProcessMediaRecache(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, attachmentID string) (*ProcessingMedia, error) {
+// PreProcessMediaRecache refetches, reprocesses, and recaches an existing attachment that has been uncached via pruneRemote.
+//
+// Note: unlike ProcessMedia, this will NOT queue the media to be asychronously processed.
+func (m *Manager) PreProcessMediaRecache(ctx context.Context, data DataFunc, attachmentID string) (*ProcessingMedia, error) {
// get the existing attachment from database.
attachment, err := m.state.DB.GetAttachmentByID(ctx, attachmentID)
if err != nil {
@@ -265,7 +175,6 @@ func (m *manager) PreProcessMediaRecache(ctx context.Context, data DataFunc, pos
processingMedia := &ProcessingMedia{
media: attachment,
dataFn: data,
- postFn: postData,
recache: true, // indicate it's a recache
mgr: m,
}
@@ -273,9 +182,10 @@ func (m *manager) PreProcessMediaRecache(ctx context.Context, data DataFunc, pos
return processingMedia, nil
}
-func (m *manager) ProcessMedia(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, accountID string, ai *AdditionalMediaInfo) (*ProcessingMedia, error) {
+// ProcessMedia will call PreProcessMedia, followed by queuing the media to be processing in the media worker queue.
+func (m *Manager) ProcessMedia(ctx context.Context, data DataFunc, accountID string, ai *AdditionalMediaInfo) (*ProcessingMedia, error) {
// Create a new processing media object for this media request.
- media, err := m.PreProcessMedia(ctx, data, postData, accountID, ai)
+ media, err := m.PreProcessMedia(ctx, data, accountID, ai)
if err != nil {
return nil, err
}
@@ -286,7 +196,22 @@ func (m *manager) ProcessMedia(ctx context.Context, data DataFunc, postData Post
return media, nil
}
-func (m *manager) PreProcessEmoji(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, shortcode string, emojiID string, uri string, ai *AdditionalEmojiInfo, refresh bool) (*ProcessingEmoji, error) {
+// PreProcessEmoji begins the process of decoding and storing the given data as an emoji.
+// It will return a pointer to a ProcessingEmoji struct upon which further actions can be performed, such as getting
+// the finished media, thumbnail, attachment, etc.
+//
+// data should be a function that the media manager can call to return a reader containing the emoji data.
+//
+// shortcode should be the emoji shortcode without the ':'s around it.
+//
+// id is the database ID that should be used to store the emoji.
+//
+// uri is the ActivityPub URI/ID of the emoji.
+//
+// ai is optional and can be nil. Any additional information about the emoji provided will be put in the database.
+//
+// Note: unlike ProcessEmoji, this will NOT queue the emoji to be asynchronously processed.
+func (m *Manager) PreProcessEmoji(ctx context.Context, data DataFunc, shortcode string, emojiID string, uri string, ai *AdditionalEmojiInfo, refresh bool) (*ProcessingEmoji, error) {
instanceAccount, err := m.state.DB.GetInstanceAccount(ctx, "")
if err != nil {
return nil, fmt.Errorf("preProcessEmoji: error fetching this instance account from the db: %s", err)
@@ -299,36 +224,38 @@ func (m *manager) PreProcessEmoji(ctx context.Context, data DataFunc, postData P
)
if refresh {
+ // Look for existing emoji by given ID.
emoji, err = m.state.DB.GetEmojiByID(ctx, emojiID)
if err != nil {
return nil, fmt.Errorf("preProcessEmoji: error fetching emoji to refresh from the db: %s", err)
}
// if this is a refresh, we will end up with new images
- // stored for this emoji, so we can use the postData function
+ // stored for this emoji, so we can use an io.Closer callback
// to perform clean up of the old images from storage
- originalPostData := postData
+ originalData := data
originalImagePath := emoji.ImagePath
originalImageStaticPath := emoji.ImageStaticPath
- postData = func(innerCtx context.Context) error {
- // trigger the original postData function if it was provided
- if originalPostData != nil {
- if err := originalPostData(innerCtx); err != nil {
- return err
- }
+ data = func(ctx context.Context) (io.ReadCloser, int64, error) {
+ // Call original data func.
+ rc, sz, err := originalData(ctx)
+ if err != nil {
+ return nil, 0, err
}
- l := log.WithContext(ctx).
- WithField("shortcode@domain", emoji.Shortcode+"@"+emoji.Domain)
- l.Debug("postData: cleaning up old emoji files for refreshed emoji")
- if err := m.state.Storage.Delete(innerCtx, originalImagePath); err != nil && !errors.Is(err, storage.ErrNotFound) {
- l.Errorf("postData: error cleaning up old emoji image at %s for refreshed emoji: %s", originalImagePath, err)
- }
- if err := m.state.Storage.Delete(innerCtx, originalImageStaticPath); err != nil && !errors.Is(err, storage.ErrNotFound) {
- l.Errorf("postData: error cleaning up old emoji static image at %s for refreshed emoji: %s", originalImageStaticPath, err)
- }
+ // Wrap closer to cleanup old data.
+ c := iotools.CloserCallback(rc, func() {
+ if err := m.state.Storage.Delete(ctx, originalImagePath); err != nil && !errors.Is(err, storage.ErrNotFound) {
+ log.Errorf(ctx, "error removing old emoji %s@%s from storage: %v", emoji.Shortcode, emoji.Domain, err)
+ }
+
+ if err := m.state.Storage.Delete(ctx, originalImageStaticPath); err != nil && !errors.Is(err, storage.ErrNotFound) {
+ log.Errorf(ctx, "error removing old static emoji %s@%s from storage: %v", emoji.Shortcode, emoji.Domain, err)
+ }
+ })
- return nil
+ // Return newly wrapped readcloser and size.
+ return iotools.ReadCloser(rc, c), sz, nil
}
newPathID, err = id.NewRandomULID()
@@ -410,16 +337,16 @@ func (m *manager) PreProcessEmoji(ctx context.Context, data DataFunc, postData P
refresh: refresh,
newPathID: newPathID,
dataFn: data,
- postFn: postData,
mgr: m,
}
return processingEmoji, nil
}
-func (m *manager) ProcessEmoji(ctx context.Context, data DataFunc, postData PostDataCallbackFunc, shortcode string, id string, uri string, ai *AdditionalEmojiInfo, refresh bool) (*ProcessingEmoji, error) {
+// ProcessEmoji will call PreProcessEmoji, followed by queuing the emoji to be processing in the emoji worker queue.
+func (m *Manager) ProcessEmoji(ctx context.Context, data DataFunc, shortcode string, id string, uri string, ai *AdditionalEmojiInfo, refresh bool) (*ProcessingEmoji, error) {
// Create a new processing emoji object for this emoji request.
- emoji, err := m.PreProcessEmoji(ctx, data, postData, shortcode, id, uri, ai, refresh)
+ emoji, err := m.PreProcessEmoji(ctx, data, shortcode, id, uri, ai, refresh)
if err != nil {
return nil, err
}
@@ -430,7 +357,7 @@ func (m *manager) ProcessEmoji(ctx context.Context, data DataFunc, postData Post
return emoji, nil
}
-func scheduleCleanupJobs(m *manager) {
+func scheduleCleanupJobs(m *Manager) {
const day = time.Hour * 24
// Calculate closest midnight.
diff --git a/internal/media/manager_test.go b/internal/media/manager_test.go
index fb0784034..2bee1091d 100644
--- a/internal/media/manager_test.go
+++ b/internal/media/manager_test.go
@@ -55,7 +55,7 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlocking() {
emojiID := "01GDQ9G782X42BAMFASKP64343"
emojiURI := "http://localhost:8080/emoji/01GDQ9G782X42BAMFASKP64343"
- processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, nil, "rainbow_test", emojiID, emojiURI, nil, false)
+ processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, "rainbow_test", emojiID, emojiURI, nil, false)
suite.NoError(err)
// do a blocking call to fetch the emoji
@@ -125,7 +125,7 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlockingRefresh() {
emojiID := emojiToUpdate.ID
emojiURI := emojiToUpdate.URI
- processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, nil, "yell", emojiID, emojiURI, &media.AdditionalEmojiInfo{
+ processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, "yell", emojiID, emojiURI, &media.AdditionalEmojiInfo{
CreatedAt: &emojiToUpdate.CreatedAt,
Domain: &emojiToUpdate.Domain,
ImageRemoteURL: &newImageRemoteURL,
@@ -209,7 +209,7 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlockingTooLarge() {
emojiID := "01GDQ9G782X42BAMFASKP64343"
emojiURI := "http://localhost:8080/emoji/01GDQ9G782X42BAMFASKP64343"
- processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, nil, "big_panda", emojiID, emojiURI, nil, false)
+ processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, "big_panda", emojiID, emojiURI, nil, false)
suite.NoError(err)
// do a blocking call to fetch the emoji
@@ -233,7 +233,7 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlockingTooLargeNoSizeGiven() {
emojiID := "01GDQ9G782X42BAMFASKP64343"
emojiURI := "http://localhost:8080/emoji/01GDQ9G782X42BAMFASKP64343"
- processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, nil, "big_panda", emojiID, emojiURI, nil, false)
+ processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, "big_panda", emojiID, emojiURI, nil, false)
suite.NoError(err)
// do a blocking call to fetch the emoji
@@ -258,7 +258,7 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlockingNoFileSizeGiven() {
emojiURI := "http://localhost:8080/emoji/01GDQ9G782X42BAMFASKP64343"
// process the media with no additional info provided
- processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, nil, "rainbow_test", emojiID, emojiURI, nil, false)
+ processingEmoji, err := suite.manager.ProcessEmoji(ctx, data, "rainbow_test", emojiID, emojiURI, nil, false)
suite.NoError(err)
// do a blocking call to fetch the emoji
@@ -319,7 +319,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlocking() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -391,7 +391,7 @@ func (suite *ManagerTestSuite) TestSlothVineProcessBlocking() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -467,7 +467,7 @@ func (suite *ManagerTestSuite) TestLongerMp4ProcessBlocking() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -543,7 +543,7 @@ func (suite *ManagerTestSuite) TestBirdnestMp4ProcessBlocking() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -621,7 +621,7 @@ func (suite *ManagerTestSuite) TestNotAnMp4ProcessBlocking() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// pre processing should go fine but...
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// we should get an error while loading
@@ -646,7 +646,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingNoContentLengthGiven
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -719,7 +719,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingReadCloser() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -791,7 +791,7 @@ func (suite *ManagerTestSuite) TestPngNoAlphaChannelProcessBlocking() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -863,7 +863,7 @@ func (suite *ManagerTestSuite) TestPngAlphaChannelProcessBlocking() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -932,18 +932,10 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingWithCallback() {
return io.NopCloser(bytes.NewBuffer(b)), int64(len(b)), nil
}
- // test the callback function by setting a simple boolean
- var calledPostData bool
- postData := func(_ context.Context) error {
- calledPostData = true
- return nil
- }
- suite.False(calledPostData) // not called yet (obvs)
-
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, postData, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
@@ -953,9 +945,6 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingWithCallback() {
suite.NoError(err)
suite.NotNil(attachment)
- // the post data callback should have been called
- suite.True(calledPostData)
-
// make sure it's got the stuff set on it that we expect
// the attachment ID and accountID we expect
suite.Equal(attachmentID, attachment.ID)
@@ -1019,7 +1008,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessAsync() {
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
@@ -1101,7 +1090,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegQueueSpamming() {
inProcess := []*media.ProcessingMedia{}
for i := 0; i < spam; i++ {
// process the media with no additional info provided
- processingMedia, err := suite.manager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := suite.manager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
inProcess = append(inProcess, processingMedia)
}
@@ -1202,7 +1191,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingWithDiskStorage() {
suite.manager = diskManager
// process the media with no additional info provided
- processingMedia, err := diskManager.ProcessMedia(ctx, data, nil, accountID, nil)
+ processingMedia, err := diskManager.ProcessMedia(ctx, data, accountID, nil)
suite.NoError(err)
// fetch the attachment id from the processing media
attachmentID := processingMedia.AttachmentID()
diff --git a/internal/media/media_test.go b/internal/media/media_test.go
index 323f87bf4..c7c73bae6 100644
--- a/internal/media/media_test.go
+++ b/internal/media/media_test.go
@@ -35,7 +35,7 @@ type MediaStandardTestSuite struct {
db db.DB
storage *storage.Driver
state state.State
- manager media.Manager
+ manager *media.Manager
transportController transport.Controller
testAttachments map[string]*gtsmodel.MediaAttachment
testAccounts map[string]*gtsmodel.Account
diff --git a/internal/media/processingemoji.go b/internal/media/processingemoji.go
index b2da54289..7c3db8196 100644
--- a/internal/media/processingemoji.go
+++ b/internal/media/processingemoji.go
@@ -36,16 +36,15 @@ import (
// ProcessingEmoji represents an emoji currently processing. It exposes
// various functions for retrieving data from the process.
type ProcessingEmoji struct {
- instAccID string // instance account ID
- emoji *gtsmodel.Emoji // processing emoji details
- refresh bool // whether this is an existing emoji being refreshed
- newPathID string // new emoji path ID to use if refreshed
- dataFn DataFunc // load-data function, returns media stream
- postFn PostDataCallbackFunc // post data callback function
- 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)
+ instAccID string // instance account ID
+ emoji *gtsmodel.Emoji // processing emoji details
+ refresh bool // whether this is an existing emoji being refreshed
+ newPathID string // new emoji path ID to use if refreshed
+ 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)
}
// EmojiID returns the ID of the underlying emoji without blocking processing.
@@ -158,17 +157,6 @@ func (p *ProcessingEmoji) load(ctx context.Context) (*gtsmodel.Emoji, bool, erro
// 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 {
- defer func() {
- if p.postFn == nil {
- return
- }
-
- // Ensure post callback gets called.
- if err := p.postFn(ctx); err != nil {
- log.Errorf(ctx, "error executing postdata function: %v", err)
- }
- }()
-
// Load media from provided data fn.
rc, sz, err := p.dataFn(ctx)
if err != nil {
diff --git a/internal/media/processingmedia.go b/internal/media/processingmedia.go
index fc75c3136..5c66f561d 100644
--- a/internal/media/processingmedia.go
+++ b/internal/media/processingmedia.go
@@ -40,12 +40,11 @@ import (
type ProcessingMedia struct {
media *gtsmodel.MediaAttachment // processing media attachment details
dataFn DataFunc // load-data function, returns media stream
- postFn PostDataCallbackFunc // post data callback function
recache bool // recaching existing (uncached) media
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)
+ mgr *Manager // mgr instance (access to db / storage)
}
// AttachmentID returns the ID of the underlying media attachment without blocking processing.
@@ -143,17 +142,6 @@ func (p *ProcessingMedia) load(ctx context.Context) (*gtsmodel.MediaAttachment,
// 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 *ProcessingMedia) store(ctx context.Context) error {
- defer func() {
- if p.postFn == nil {
- return
- }
-
- // ensure post callback gets called.
- if err := p.postFn(ctx); err != nil {
- log.Errorf(ctx, "error executing postdata function: %v", err)
- }
- }()
-
// Load media from provided data fun
rc, sz, err := p.dataFn(ctx)
if err != nil {
diff --git a/internal/media/prune.go b/internal/media/prune.go
index f0d0516e1..71c8e00ce 100644
--- a/internal/media/prune.go
+++ b/internal/media/prune.go
@@ -37,7 +37,14 @@ const (
unusedLocalAttachmentDays = 3 // Number of days to keep local media in storage if not attached to a status.
)
-func (m *manager) PruneAll(ctx context.Context, mediaCacheRemoteDays int, blocking bool) error {
+// PruneAll runs all of the below pruning/uncacheing functions, and then cleans up any resulting
+// empty directories from the storage driver. It can be called as a shortcut for calling the below
+// pruning functions one by one.
+//
+// If blocking is true, then any errors encountered during the prune will be combined + returned to
+// the caller. If blocking is false, the prune is run in the background and errors are just logged
+// instead.
+func (m *Manager) PruneAll(ctx context.Context, mediaCacheRemoteDays int, blocking bool) error {
const dry = false
f := func(innerCtx context.Context) error {
@@ -93,7 +100,10 @@ func (m *manager) PruneAll(ctx context.Context, mediaCacheRemoteDays int, blocki
return nil
}
-func (m *manager) PruneUnusedRemote(ctx context.Context, dry bool) (int, error) {
+// PruneUnusedRemote prunes unused/out of date headers and avatars cached on this instance.
+//
+// The returned int is the amount of media that was pruned by this function.
+func (m *Manager) PruneUnusedRemote(ctx context.Context, dry bool) (int, error) {
var (
totalPruned int
maxID string
@@ -152,7 +162,12 @@ func (m *manager) PruneUnusedRemote(ctx context.Context, dry bool) (int, error)
return totalPruned, nil
}
-func (m *manager) PruneOrphaned(ctx context.Context, dry bool) (int, error) {
+// PruneOrphaned prunes files that exist in storage but which do not have a corresponding
+// entry in the database.
+//
+// If dry is true, then nothing will be changed, only the amount that *would* be removed
+// is returned to the caller.
+func (m *Manager) PruneOrphaned(ctx context.Context, dry bool) (int, error) {
// Emojis are stored under the instance account, so we
// need the ID of the instance account for the next part.
instanceAccount, err := m.state.DB.GetInstanceAccount(ctx, "")
@@ -200,7 +215,7 @@ func (m *manager) PruneOrphaned(ctx context.Context, dry bool) (int, error) {
return m.removeFiles(ctx, orphanedKeys...)
}
-func (m *manager) orphaned(ctx context.Context, key string, instanceAccountID string) (bool, error) {
+func (m *Manager) orphaned(ctx context.Context, key string, instanceAccountID string) (bool, error) {
pathParts := regexes.FilePath.FindStringSubmatch(key)
if len(pathParts) != 6 {
// This doesn't match our expectations so
@@ -239,7 +254,15 @@ func (m *manager) orphaned(ctx context.Context, key string, instanceAccountID st
return orphaned, nil
}
-func (m *manager) UncacheRemote(ctx context.Context, olderThanDays int, dry bool) (int, error) {
+// UncacheRemote uncaches all remote media attachments older than the given amount of days.
+//
+// In this context, uncacheing means deleting media files from storage and marking the attachment
+// as cached=false in the database.
+//
+// If 'dry' is true, then only a dry run will be performed: nothing will actually be changed.
+//
+// The returned int is the amount of media that was/would be uncached by this function.
+func (m *Manager) UncacheRemote(ctx context.Context, olderThanDays int, dry bool) (int, error) {
if olderThanDays < 0 {
return 0, nil
}
@@ -276,7 +299,12 @@ func (m *manager) UncacheRemote(ctx context.Context, olderThanDays int, dry bool
return totalPruned, nil
}
-func (m *manager) PruneUnusedLocal(ctx context.Context, dry bool) (int, error) {
+// PruneUnusedLocal prunes unused media attachments that were uploaded by
+// a user on this instance, but never actually attached to a status, or attached but
+// later detached.
+//
+// The returned int is the amount of media that was pruned by this function.
+func (m *Manager) PruneUnusedLocal(ctx context.Context, dry bool) (int, error) {
olderThan := time.Now().Add(-time.Hour * 24 * time.Duration(unusedLocalAttachmentDays))
if dry {
@@ -313,7 +341,7 @@ func (m *manager) PruneUnusedLocal(ctx context.Context, dry bool) (int, error) {
Handy little helpers
*/
-func (m *manager) deleteAttachment(ctx context.Context, attachment *gtsmodel.MediaAttachment) error {
+func (m *Manager) deleteAttachment(ctx context.Context, attachment *gtsmodel.MediaAttachment) error {
if _, err := m.removeFiles(ctx, attachment.File.Path, attachment.Thumbnail.Path); err != nil {
return err
}
@@ -322,7 +350,7 @@ func (m *manager) deleteAttachment(ctx context.Context, attachment *gtsmodel.Med
return m.state.DB.DeleteAttachment(ctx, attachment.ID)
}
-func (m *manager) uncacheAttachment(ctx context.Context, attachment *gtsmodel.MediaAttachment) error {
+func (m *Manager) uncacheAttachment(ctx context.Context, attachment *gtsmodel.MediaAttachment) error {
if _, err := m.removeFiles(ctx, attachment.File.Path, attachment.Thumbnail.Path); err != nil {
return err
}
@@ -332,7 +360,7 @@ func (m *manager) uncacheAttachment(ctx context.Context, attachment *gtsmodel.Me
return m.state.DB.UpdateAttachment(ctx, attachment, "cached")
}
-func (m *manager) removeFiles(ctx context.Context, keys ...string) (int, error) {
+func (m *Manager) removeFiles(ctx context.Context, keys ...string) (int, error) {
errs := make(gtserror.MultiError, 0, len(keys))
for _, key := range keys {
diff --git a/internal/media/prune_test.go b/internal/media/prune_test.go
index 64823e640..375ce0c06 100644
--- a/internal/media/prune_test.go
+++ b/internal/media/prune_test.go
@@ -312,7 +312,7 @@ func (suite *PruneTestSuite) TestUncacheAndRecache() {
testStatusAttachment,
testHeader,
} {
- processingRecache, err := suite.manager.PreProcessMediaRecache(ctx, data, nil, original.ID)
+ processingRecache, err := suite.manager.PreProcessMediaRecache(ctx, data, original.ID)
suite.NoError(err)
// synchronously load the recached attachment
diff --git a/internal/media/refetch.go b/internal/media/refetch.go
index 8fb08f88b..80dfe4f60 100644
--- a/internal/media/refetch.go
+++ b/internal/media/refetch.go
@@ -32,7 +32,13 @@ import (
type DereferenceMedia func(ctx context.Context, iri *url.URL) (io.ReadCloser, int64, error)
-func (m *manager) RefetchEmojis(ctx context.Context, domain string, dereferenceMedia DereferenceMedia) (int, error) {
+// RefetchEmojis iterates through remote emojis (for the given domain, or all if domain is empty string).
+//
+// For each emoji, the manager will check whether both the full size and static images are present in storage.
+// If not, the manager will refetch and reprocess full size and static images for the emoji.
+//
+// The provided DereferenceMedia function will be used when it's necessary to refetch something this way.
+func (m *Manager) RefetchEmojis(ctx context.Context, domain string, dereferenceMedia DereferenceMedia) (int, error) {
// normalize domain
if domain == "" {
domain = db.EmojiAllDomains
@@ -107,7 +113,7 @@ func (m *manager) RefetchEmojis(ctx context.Context, domain string, dereferenceM
return dereferenceMedia(ctx, emojiImageIRI)
}
- processingEmoji, err := m.PreProcessEmoji(ctx, dataFunc, nil, emoji.Shortcode, emoji.ID, emoji.URI, &AdditionalEmojiInfo{
+ processingEmoji, err := m.PreProcessEmoji(ctx, dataFunc, emoji.Shortcode, emoji.ID, emoji.URI, &AdditionalEmojiInfo{
Domain: &emoji.Domain,
ImageRemoteURL: &emoji.ImageRemoteURL,
ImageStaticRemoteURL: &emoji.ImageStaticRemoteURL,
@@ -131,7 +137,7 @@ func (m *manager) RefetchEmojis(ctx context.Context, domain string, dereferenceM
return totalRefetched, nil
}
-func (m *manager) emojiRequiresRefetch(ctx context.Context, emoji *gtsmodel.Emoji) (bool, error) {
+func (m *Manager) emojiRequiresRefetch(ctx context.Context, emoji *gtsmodel.Emoji) (bool, error) {
if has, err := m.state.Storage.Has(ctx, emoji.ImagePath); err != nil {
return false, err
} else if !has {
diff --git a/internal/media/types.go b/internal/media/types.go
index 1619029c6..35c62a947 100644
--- a/internal/media/types.go
+++ b/internal/media/types.go
@@ -110,9 +110,3 @@ type AdditionalEmojiInfo struct {
// DataFunc represents a function used to retrieve the raw bytes of a piece of media.
type DataFunc func(ctx context.Context) (reader io.ReadCloser, fileSize int64, err error)
-
-// PostDataCallbackFunc represents a function executed after the DataFunc has been executed,
-// and the returned reader has been read. It can be used to clean up any remaining resources.
-//
-// This can be set to nil, and will then not be executed.
-type PostDataCallbackFunc func(ctx context.Context) error