Skip to content

Commit

Permalink
[chore] media pipeline improvements (#3110)
Browse files Browse the repository at this point in the history
* don't set emoji / media image paths on failed download, migrate FileType from string to integer

* fix incorrect uses of util.PtrOr, fix returned frontend media

* fix migration not setting arguments correctly in where clause

* fix not providing default with not null column

* whoops

* ensure a default gets set for media attachment file type

* remove the exclusive flag from writing files in disk storage

* rename PtrOr -> PtrOrZero, and rename PtrValueOr -> PtrOrValue to match

* slight wording changes

* use singular / plural word forms (no parentheses), is better for screen readers

* update testmodels with unknown media type to have unset file details, update attachment focus handling converting to frontend, update tests

* store first instance in ffmpeg wasm pool, fill remaining with closed instances
  • Loading branch information
NyaaaWhatsUpDoc authored Jul 17, 2024
1 parent 0aadc2d commit 72ba566
Show file tree
Hide file tree
Showing 29 changed files with 665 additions and 395 deletions.
3 changes: 3 additions & 0 deletions cmd/process-emoji/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os/signal"
"syscall"

"codeberg.org/gruf/go-logger/v2/level"
"codeberg.org/gruf/go-storage/memory"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
Expand All @@ -40,6 +41,8 @@ func main() {
ctx, cncl := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT)
defer cncl()

log.SetLevel(level.INFO)

if len(os.Args) != 3 {
log.Panic(ctx, "Usage: go run ./cmd/process-emoji <input-file> <output-static>")
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/process-media/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os/signal"
"syscall"

"codeberg.org/gruf/go-logger/v2/level"
"codeberg.org/gruf/go-storage/memory"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
Expand All @@ -39,6 +40,8 @@ func main() {
ctx, cncl := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT)
defer cncl()

log.SetLevel(level.INFO)

if len(os.Args) != 4 {
log.Panic(ctx, "Usage: go run ./cmd/process-media <input-file> <output-processed> <output-thumbnail>")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/accounts/mute.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (m *Module) AccountMutePOSTHandler(c *gin.Context) {

func normalizeCreateUpdateMute(form *apimodel.UserMuteCreateUpdateRequest) error {
// Apply defaults for missing fields.
form.Notifications = util.Ptr(util.PtrValueOr(form.Notifications, false))
form.Notifications = util.Ptr(util.PtrOrValue(form.Notifications, false))

// Normalize mute duration if necessary.
// If we parsed this as JSON, expires_in
Expand Down
4 changes: 2 additions & 2 deletions internal/api/client/filters/v1/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func validateNormalizeCreateUpdateFilter(form *model.FilterCreateUpdateRequestV1
}

// Apply defaults for missing fields.
form.WholeWord = util.Ptr(util.PtrValueOr(form.WholeWord, false))
form.Irreversible = util.Ptr(util.PtrValueOr(form.Irreversible, false))
form.WholeWord = util.Ptr(util.PtrOrValue(form.WholeWord, false))
form.Irreversible = util.Ptr(util.PtrOrValue(form.Irreversible, false))

if *form.Irreversible {
return errors.New("irreversible aka server-side drop filters are not supported yet")
Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/filters/v2/filterkeywordget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (suite *FiltersTestSuite) TestGetFilterKeyword() {
suite.NotEmpty(filterKeyword)
suite.Equal(expectedFilterKeyword.ID, filterKeyword.ID)
suite.Equal(expectedFilterKeyword.Keyword, filterKeyword.Keyword)
suite.Equal(util.PtrValueOr(expectedFilterKeyword.WholeWord, false), filterKeyword.WholeWord)
suite.Equal(util.PtrOrValue(expectedFilterKeyword.WholeWord, false), filterKeyword.WholeWord)
}

func (suite *FiltersTestSuite) TestGetAnotherAccountsFilterKeyword() {
Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/filters/v2/filterkeywordpost.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func validateNormalizeCreateUpdateFilterKeyword(form *apimodel.FilterKeywordCrea
return err
}

form.WholeWord = util.Ptr(util.PtrValueOr(form.WholeWord, false))
form.WholeWord = util.Ptr(util.PtrOrValue(form.WholeWord, false))

return nil
}
4 changes: 2 additions & 2 deletions internal/api/client/filters/v2/filterpost.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func validateNormalizeCreateFilter(form *apimodel.FilterCreateRequestV2) error {
if err := validate.FilterTitle(form.Title); err != nil {
return err
}
action := util.PtrValueOr(form.FilterAction, apimodel.FilterActionWarn)
action := util.PtrOrValue(form.FilterAction, apimodel.FilterActionWarn)
if err := validate.FilterAction(action); err != nil {
return err
}
Expand Down Expand Up @@ -253,7 +253,7 @@ func validateNormalizeCreateFilter(form *apimodel.FilterCreateRequestV2) error {
if err := validate.FilterKeyword(formKeyword.Keyword); err != nil {
return err
}
form.Keywords[i].WholeWord = util.Ptr(util.PtrValueOr(formKeyword.WholeWord, false))
form.Keywords[i].WholeWord = util.Ptr(util.PtrOrValue(formKeyword.WholeWord, false))
}
for _, formStatus := range form.Statuses {
if err := validate.ULID(formStatus.StatusID, "status_id"); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/api/client/filters/v2/filterput.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func validateNormalizeUpdateFilter(form *apimodel.FilterUpdateRequestV2) error {
}
}

destroy := util.PtrValueOr(formKeyword.Destroy, false)
destroy := util.PtrOrValue(formKeyword.Destroy, false)
form.Keywords[i].Destroy = &destroy

if destroy && formKeyword.ID == nil {
Expand All @@ -305,7 +305,7 @@ func validateNormalizeUpdateFilter(form *apimodel.FilterUpdateRequestV2) error {
}
}

destroy := util.PtrValueOr(formStatus.Destroy, false)
destroy := util.PtrOrValue(formStatus.Destroy, false)
form.Statuses[i].Destroy = &destroy

switch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ func init() {
CreatedAt: account.CreatedAt,
Reason: account.Reason,
Privacy: newgtsmodel.Visibility(account.Privacy),
Sensitive: util.Ptr(util.PtrValueOr(account.Sensitive, false)),
Sensitive: util.Ptr(util.PtrOrValue(account.Sensitive, false)),
Language: account.Language,
StatusContentType: account.StatusContentType,
CustomCSS: account.CustomCSS,
EnableRSS: util.Ptr(util.PtrValueOr(account.EnableRSS, false)),
HideCollections: util.Ptr(util.PtrValueOr(account.HideCollections, false)),
EnableRSS: util.Ptr(util.PtrOrValue(account.EnableRSS, false)),
HideCollections: util.Ptr(util.PtrOrValue(account.HideCollections, false)),
}

// Insert the settings model.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// GoToSocial
// Copyright (C) GoToSocial Authors [email protected]
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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 migrations

import (
"context"

old_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20240715204203_media_pipeline_improvements"
new_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"

"github.com/uptrace/bun"
)

func init() {
up := func(ctx context.Context, db *bun.DB) error {
if err := db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
if _, err := tx.NewAddColumn().
Table("media_attachments").
ColumnExpr("? INTEGER NOT NULL DEFAULT ?", bun.Ident("type_new"), 0).
Exec(ctx); err != nil {
return err
}

for old, new := range map[old_gtsmodel.FileType]new_gtsmodel.FileType{
old_gtsmodel.FileTypeAudio: new_gtsmodel.FileTypeAudio,
old_gtsmodel.FileTypeImage: new_gtsmodel.FileTypeImage,
old_gtsmodel.FileTypeGifv: new_gtsmodel.FileTypeImage,
old_gtsmodel.FileTypeVideo: new_gtsmodel.FileTypeVideo,
old_gtsmodel.FileTypeUnknown: new_gtsmodel.FileTypeUnknown,
} {
if _, err := tx.NewUpdate().
Table("media_attachments").
Where("? = ?", bun.Ident("type"), old).
Set("? = ?", bun.Ident("type_new"), new).
Exec(ctx); err != nil {
return err
}
}

if _, err := tx.NewDropColumn().
Table("media_attachments").
ColumnExpr("?", bun.Ident("type")).
Exec(ctx); err != nil {
return err
}

if _, err := tx.NewRaw(
"ALTER TABLE ? RENAME COLUMN ? TO ?",
bun.Ident("media_attachments"),
bun.Ident("type_new"),
bun.Ident("type"),
).Exec(ctx); err != nil {
return err
}

return nil
}); err != nil {
return err
}

// Zero-out attachment data
// for "unknown" non-locally
// stored media attachments.
if _, err := db.NewUpdate().
Table("media_attachments").
Where("? = ?", bun.Ident("type"), new_gtsmodel.FileTypeUnknown).
Set("? = ?", bun.Ident("url"), "").
Set("? = ?", bun.Ident("file_path"), "").
Set("? = ?", bun.Ident("file_content_type"), "").
Set("? = ?", bun.Ident("file_file_size"), 0).
Set("? = ?", bun.Ident("thumbnail_path"), "").
Set("? = ?", bun.Ident("thumbnail_content_type"), "").
Set("? = ?", bun.Ident("thumbnail_file_size"), 0).
Set("? = ?", bun.Ident("thumbnail_url"), "").
Exec(ctx); err != nil {
return err
}

// Zero-out emoji data for
// non-locally stored emoji.
if _, err := db.NewUpdate().
Table("emojis").
WhereOr("? = ?", bun.Ident("image_url"), "").
WhereOr("? = ?", bun.Ident("image_path"), "").
Set("? = ?", bun.Ident("image_path"), "").
Set("? = ?", bun.Ident("image_url"), "").
Set("? = ?", bun.Ident("image_file_size"), 0).
Set("? = ?", bun.Ident("image_content_type"), "").
Set("? = ?", bun.Ident("image_static_path"), "").
Set("? = ?", bun.Ident("image_static_url"), "").
Set("? = ?", bun.Ident("image_static_file_size"), 0).
Set("? = ?", bun.Ident("image_static_content_type"), "").
Exec(ctx); err != nil {
return err
}

return nil
}

down := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
return nil
})
}

if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// GoToSocial
// Copyright (C) GoToSocial Authors [email protected]
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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 gtsmodel

import "time"

// Emoji represents a custom emoji that's been uploaded through the admin UI or downloaded from a remote instance.
type Emoji struct {
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
Shortcode string `bun:",nullzero,notnull,unique:domainshortcode"` // String shortcode for this emoji -- the part that's between colons. This should be a-zA-Z_ eg., 'blob_hug' 'purple_heart' 'Gay_Otter' Must be unique with domain.
Domain string `bun:",nullzero,unique:domainshortcode"` // Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis.
ImageRemoteURL string `bun:",nullzero"` // Where can this emoji be retrieved remotely? Null for local emojis.
ImageStaticRemoteURL string `bun:",nullzero"` // Where can a static / non-animated version of this emoji be retrieved remotely? Null for local emojis.
ImageURL string `bun:",nullzero"` // Where can this emoji be retrieved from the local server? Null for remote emojis.
ImageStaticURL string `bun:",nullzero"` // Where can a static version of this emoji be retrieved from the local server? Null for remote emojis.
ImagePath string `bun:",notnull"` // Path of the emoji image in the server storage system.
ImageStaticPath string `bun:",notnull"` // Path of a static version of the emoji image in the server storage system
ImageContentType string `bun:",notnull"` // MIME content type of the emoji image
ImageStaticContentType string `bun:",notnull"` // MIME content type of the static version of the emoji image.
ImageFileSize int `bun:",notnull"` // Size of the emoji image file in bytes, for serving purposes.
ImageStaticFileSize int `bun:",notnull"` // Size of the static version of the emoji image file in bytes, for serving purposes.
Disabled *bool `bun:",nullzero,notnull,default:false"` // Has a moderation action disabled this emoji from being shown?
URI string `bun:",nullzero,notnull,unique"` // ActivityPub uri of this emoji. Something like 'https://example.org/emojis/1234'
VisibleInPicker *bool `bun:",nullzero,notnull,default:true"` // Is this emoji visible in the admin emoji picker?
Category *EmojiCategory `bun:"rel:belongs-to"` // In which emoji category is this emoji visible?
CategoryID string `bun:"type:CHAR(26),nullzero"` // ID of the category this emoji belongs to.
Cached *bool `bun:",nullzero,notnull,default:false"` // whether emoji is cached in locally in gotosocial storage.
}

// IsLocal returns true if the emoji is
// local to this instance., ie., it did
// not originate from a remote instance.
func (e *Emoji) IsLocal() bool {
return e.Domain == ""
}

// ShortcodeDomain returns the [shortcode]@[domain] for the given emoji.
func (e *Emoji) ShortcodeDomain() string {
return e.Shortcode + "@" + e.Domain
}

// EmojiCategory represents a grouping of custom emojis.
type EmojiCategory struct {
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
Name string `bun:",nullzero,notnull,unique"` // name of this category
}
Loading

0 comments on commit 72ba566

Please sign in to comment.