diff options
Diffstat (limited to 'internal/processing')
-rw-r--r-- | internal/processing/account/getrss_test.go | 4 | ||||
-rw-r--r-- | internal/processing/admin/updateemoji.go | 5 | ||||
-rw-r--r-- | internal/processing/media/getfile.go | 164 |
3 files changed, 53 insertions, 120 deletions
diff --git a/internal/processing/account/getrss_test.go b/internal/processing/account/getrss_test.go index f9fb1accb..6c699abae 100644 --- a/internal/processing/account/getrss_test.go +++ b/internal/processing/account/getrss_test.go @@ -40,7 +40,7 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSAdmin() { fmt.Println(feed) - suite.Equal("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>Posts from @admin@localhost:8080</title>\n <link>http://localhost:8080/@admin</link>\n <description>Posts from @admin@localhost:8080</description>\n <pubDate>Wed, 20 Oct 2021 12:36:45 +0000</pubDate>\n <lastBuildDate>Wed, 20 Oct 2021 12:36:45 +0000</lastBuildDate>\n <item>\n <title>open to see some puppies</title>\n <link>http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37</link>\n <description>@admin@localhost:8080 made a new post: "🐕🐕🐕🐕🐕"</description>\n <content:encoded><![CDATA[🐕🐕🐕🐕🐕]]></content:encoded>\n <author>@admin@localhost:8080</author>\n <guid>http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37</guid>\n <pubDate>Wed, 20 Oct 2021 12:36:45 +0000</pubDate>\n <source>http://localhost:8080/@admin/feed.rss</source>\n </item>\n <item>\n <title>hello world! #welcome ! first post on the instance :rainbow: !</title>\n <link>http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R</link>\n <description>@admin@localhost:8080 posted 1 attachment: "hello world! #welcome ! first post on the instance :rainbow: !"</description>\n <content:encoded><![CDATA[hello world! #welcome ! first post on the instance <img src=\"http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png\" title=\":rainbow:\" alt=\":rainbow:\" class=\"emoji\"/> !]]></content:encoded>\n <author>@admin@localhost:8080</author>\n <enclosure url=\"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg\" length=\"62529\" type=\"image/jpeg\"></enclosure>\n <guid>http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R</guid>\n <pubDate>Wed, 20 Oct 2021 11:36:45 +0000</pubDate>\n <source>http://localhost:8080/@admin/feed.rss</source>\n </item>\n </channel>\n</rss>", feed) + suite.Equal("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>Posts from @admin@localhost:8080</title>\n <link>http://localhost:8080/@admin</link>\n <description>Posts from @admin@localhost:8080</description>\n <pubDate>Wed, 20 Oct 2021 12:36:45 +0000</pubDate>\n <lastBuildDate>Wed, 20 Oct 2021 12:36:45 +0000</lastBuildDate>\n <item>\n <title>open to see some puppies</title>\n <link>http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37</link>\n <description>@admin@localhost:8080 made a new post: "🐕🐕🐕🐕🐕"</description>\n <content:encoded><![CDATA[🐕🐕🐕🐕🐕]]></content:encoded>\n <author>@admin@localhost:8080</author>\n <guid>http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37</guid>\n <pubDate>Wed, 20 Oct 2021 12:36:45 +0000</pubDate>\n <source>http://localhost:8080/@admin/feed.rss</source>\n </item>\n <item>\n <title>hello world! #welcome ! first post on the instance :rainbow: !</title>\n <link>http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R</link>\n <description>@admin@localhost:8080 posted 1 attachment: "hello world! #welcome ! first post on the instance :rainbow: !"</description>\n <content:encoded><![CDATA[hello world! #welcome ! first post on the instance <img src=\"http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png\" title=\":rainbow:\" alt=\":rainbow:\" class=\"emoji\"/> !]]></content:encoded>\n <author>@admin@localhost:8080</author>\n <enclosure url=\"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg\" length=\"62529\" type=\"image/jpeg\"></enclosure>\n <guid>http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R</guid>\n <pubDate>Wed, 20 Oct 2021 11:36:45 +0000</pubDate>\n <source>http://localhost:8080/@admin/feed.rss</source>\n </item>\n </channel>\n</rss>", feed) } func (suite *GetRSSTestSuite) TestGetAccountRSSZork() { @@ -53,7 +53,7 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSZork() { fmt.Println(feed) - suite.Equal("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>Posts from @the_mighty_zork@localhost:8080</title>\n <link>http://localhost:8080/@the_mighty_zork</link>\n <description>Posts from @the_mighty_zork@localhost:8080</description>\n <pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>\n <lastBuildDate>Wed, 20 Oct 2021 10:40:37 +0000</lastBuildDate>\n <image>\n <url>http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg</url>\n <title>Avatar for @the_mighty_zork@localhost:8080</title>\n <link>http://localhost:8080/@the_mighty_zork</link>\n </image>\n <item>\n <title>introduction post</title>\n <link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>\n <description>@the_mighty_zork@localhost:8080 made a new post: "hello everyone!"</description>\n <content:encoded><![CDATA[hello everyone!]]></content:encoded>\n <author>@the_mighty_zork@localhost:8080</author>\n <guid>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>\n <pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>\n <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n </item>\n </channel>\n</rss>", feed) + suite.Equal("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>Posts from @the_mighty_zork@localhost:8080</title>\n <link>http://localhost:8080/@the_mighty_zork</link>\n <description>Posts from @the_mighty_zork@localhost:8080</description>\n <pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>\n <lastBuildDate>Wed, 20 Oct 2021 10:40:37 +0000</lastBuildDate>\n <image>\n <url>http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg</url>\n <title>Avatar for @the_mighty_zork@localhost:8080</title>\n <link>http://localhost:8080/@the_mighty_zork</link>\n </image>\n <item>\n <title>introduction post</title>\n <link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>\n <description>@the_mighty_zork@localhost:8080 made a new post: "hello everyone!"</description>\n <content:encoded><![CDATA[hello everyone!]]></content:encoded>\n <author>@the_mighty_zork@localhost:8080</author>\n <guid>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>\n <pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>\n <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n </item>\n </channel>\n</rss>", feed) } func TestGetRSSTestSuite(t *testing.T) { diff --git a/internal/processing/admin/updateemoji.go b/internal/processing/admin/updateemoji.go index 25759ce1a..370e6e27f 100644 --- a/internal/processing/admin/updateemoji.go +++ b/internal/processing/admin/updateemoji.go @@ -90,9 +90,8 @@ func (p *processor) emojiUpdateCopy(ctx context.Context, emoji *gtsmodel.Emoji, newEmojiURI := uris.GenerateURIForEmoji(newEmojiID) data := func(ctx context.Context) (reader io.ReadCloser, fileSize int64, err error) { - // 'copy' the emoji by pulling the existing one out of storage - i, err := p.storage.GetStream(ctx, emoji.ImagePath) - return i, int64(emoji.ImageFileSize), err + rc, err := p.storage.GetStream(ctx, emoji.ImagePath) + return rc, int64(emoji.ImageFileSize), err } var ai *media.AdditionalEmojiInfo diff --git a/internal/processing/media/getfile.go b/internal/processing/media/getfile.go index 14e031e52..d5f74926a 100644 --- a/internal/processing/media/getfile.go +++ b/internal/processing/media/getfile.go @@ -28,7 +28,6 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/iotools" "github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/transport" "github.com/superseriousbusiness/gotosocial/internal/uris" @@ -99,135 +98,70 @@ func (p *processor) getAttachmentContent(ctx context.Context, requestingAccount return nil, gtserror.NewErrorNotFound(fmt.Errorf("attachment %s is not owned by %s", wantedMediaID, owningAccountID)) } - // get file information from the attachment depending on the requested media size - switch mediaSize { - case media.SizeOriginal: - attachmentContent.ContentType = a.File.ContentType - attachmentContent.ContentLength = int64(a.File.FileSize) - storagePath = a.File.Path - case media.SizeSmall: - attachmentContent.ContentType = a.Thumbnail.ContentType - attachmentContent.ContentLength = int64(a.Thumbnail.FileSize) - storagePath = a.Thumbnail.Path - default: - return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not recognized for attachment", mediaSize)) - } - - // if we have the media cached on our server already, we can now simply return it from storage - if *a.Cached { - return p.retrieveFromStorage(ctx, storagePath, attachmentContent) - } - - // if we don't have it cached, then we can assume two things: - // 1. this is remote media, since local media should never be uncached - // 2. we need to fetch it again using a transport and the media manager - remoteMediaIRI, err := url.Parse(a.RemoteURL) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error parsing remote media iri %s: %s", a.RemoteURL, err)) - } - - // use an empty string as requestingUsername to use the instance account, unless the request for this - // media has been http signed, then use the requesting account to make the request to remote server - var requestingUsername string - if requestingAccount != nil { - requestingUsername = requestingAccount.Username - } + if !*a.Cached { + // if we don't have it cached, then we can assume two things: + // 1. this is remote media, since local media should never be uncached + // 2. we need to fetch it again using a transport and the media manager + remoteMediaIRI, err := url.Parse(a.RemoteURL) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("error parsing remote media iri %s: %s", a.RemoteURL, err)) + } - var data media.DataFunc + // use an empty string as requestingUsername to use the instance account, unless the request for this + // media has been http signed, then use the requesting account to make the request to remote server + var requestingUsername string + if requestingAccount != nil { + requestingUsername = requestingAccount.Username + } - if mediaSize == media.SizeSmall { - // if it's the thumbnail that's requested then the user will have to wait a bit while we process the - // large version and derive a thumbnail from it, so use the normal recaching procedure: fetch the media, - // process it, then return the thumbnail data - data = func(innerCtx context.Context) (io.ReadCloser, int64, error) { + // Pour one out for tobi's original streamed recache + // (streaming data both to the client and storage). + // Gone and forever missed <3 + // + // [ + // the reason it was removed was because a slow + // client connection could hold open a storage + // recache operation, and so holding open a media + // worker worker. + // ] + + dataFn := func(innerCtx context.Context) (io.ReadCloser, int64, error) { t, err := p.transportController.NewTransportForUsername(innerCtx, requestingUsername) if err != nil { return nil, 0, err } return t.DereferenceMedia(transport.WithFastfail(innerCtx), remoteMediaIRI) } - } else { - // if it's the full-sized version being requested, we can cheat a bit by streaming data to the user as - // it's retrieved from the remote server, using tee; this saves the user from having to wait while - // we process the media on our side - // - // this looks a bit like this: - // - // http fetch pipe - // remote server ------------> data function ----------------> api caller - // | - // | tee - // | - // ▼ - // instance storage - - // This pipe will connect the caller to the in-process media retrieval... - pipeReader, pipeWriter := io.Pipe() - // Wrap the output pipe to silence any errors during the actual media - // streaming process. We catch the error later but they must be silenced - // during stream to prevent interruptions to storage of the actual media. - silencedWriter := iotools.SilenceWriter(pipeWriter) - - // Pass the reader side of the pipe to the caller to slurp from. - attachmentContent.Content = pipeReader - - // Create a data function which injects the writer end of the pipe - // into the data retrieval process. If something goes wrong while - // doing the data retrieval, we hang up the underlying pipeReader - // to indicate to the caller that no data is available. It's up to - // the caller of this processor function to handle that gracefully. - data = func(innerCtx context.Context) (io.ReadCloser, int64, error) { - t, err := p.transportController.NewTransportForUsername(innerCtx, requestingUsername) - if err != nil { - // propagate the transport error to read end of pipe. - _ = pipeWriter.CloseWithError(fmt.Errorf("error getting transport for user: %w", err)) - return nil, 0, err - } - - readCloser, fileSize, err := t.DereferenceMedia(transport.WithFastfail(innerCtx), remoteMediaIRI) - if err != nil { - // propagate the dereference error to read end of pipe. - _ = pipeWriter.CloseWithError(fmt.Errorf("error dereferencing media: %w", err)) - return nil, 0, err - } - - // Make a TeeReader so that everything read from the readCloser, - // aka the remote instance, will also be written into the pipe. - teeReader := io.TeeReader(readCloser, silencedWriter) - - // Wrap teereader to implement original readcloser's close, - // and also ensuring that we close the pipe from write end. - return iotools.ReadFnCloser(teeReader, func() error { - defer func() { - // We use the error (if any) encountered by the - // silenced writer to close connection to make sure it - // gets propagated to the attachment.Content reader. - _ = pipeWriter.CloseWithError(silencedWriter.Error()) - }() - - return readCloser.Close() - }), fileSize, nil + // Start recaching this media with the prepared data function. + processingMedia, err := p.mediaManager.RecacheMedia(ctx, dataFn, nil, wantedMediaID) + if err != nil { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("error recaching media: %s", err)) } - } - - // put the media recached in the queue - processingMedia, err := p.mediaManager.RecacheMedia(ctx, data, nil, wantedMediaID) - if err != nil { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("error recaching media: %s", err)) - } - // if it's the thumbnail, stream the processed thumbnail from storage, after waiting for processing to finish - if mediaSize == media.SizeSmall { - // below function call blocks until all processing on the attachment has finished... - if _, err := processingMedia.LoadAttachment(ctx); err != nil { + // Load attachment and block until complete + a, err = processingMedia.LoadAttachment(ctx) + if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error loading recached attachment: %s", err)) } - // ... so now we can safely return it - return p.retrieveFromStorage(ctx, storagePath, attachmentContent) } - return attachmentContent, nil + // get file information from the attachment depending on the requested media size + switch mediaSize { + case media.SizeOriginal: + attachmentContent.ContentType = a.File.ContentType + attachmentContent.ContentLength = int64(a.File.FileSize) + storagePath = a.File.Path + case media.SizeSmall: + attachmentContent.ContentType = a.Thumbnail.ContentType + attachmentContent.ContentLength = int64(a.Thumbnail.FileSize) + storagePath = a.Thumbnail.Path + default: + return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not recognized for attachment", mediaSize)) + } + + // ... so now we can safely return it + return p.retrieveFromStorage(ctx, storagePath, attachmentContent) } func (p *processor) getEmojiContent(ctx context.Context, fileName string, owningAccountID string, emojiSize media.Size) (*apimodel.Content, gtserror.WithCode) { |