20220214175650_media_cleanup.go (5609B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package migrations 19 20 import ( 21 "context" 22 "database/sql" 23 "time" 24 25 previousgtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20211113114307_init" 26 newgtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20220214175650_media_cleanup" 27 "github.com/uptrace/bun" 28 ) 29 30 func init() { 31 const batchSize = 100 32 up := func(ctx context.Context, db *bun.DB) error { 33 // we need to migrate media attachments into a new table 34 // see section 6 here: https://www.sqlite.org/lang_altertable.html 35 36 return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { 37 // create the new media attachments table 38 if _, err := tx. 39 NewCreateTable(). 40 ModelTableExpr("new_media_attachments"). 41 Model(&newgtsmodel.MediaAttachment{}). 42 IfNotExists(). 43 Exec(ctx); err != nil { 44 return err 45 } 46 47 offset := time.Now() 48 // migrate existing media attachments into new table 49 migrateLoop: 50 for { 51 oldAttachments := []*previousgtsmodel.MediaAttachment{} 52 err := tx. 53 NewSelect(). 54 Model(&oldAttachments). 55 // subtract a millisecond from the offset just to make sure we're not getting double entries (this happens sometimes) 56 Where("media_attachment.created_at < ?", offset.Add(-1*time.Millisecond)). 57 Order("media_attachment.created_at DESC"). 58 Limit(batchSize). 59 Scan(ctx) 60 if err != nil && err != sql.ErrNoRows { 61 // there's been a real error 62 return err 63 } 64 65 if err == sql.ErrNoRows || len(oldAttachments) == 0 { 66 // we're finished migrating 67 break migrateLoop 68 } 69 70 // update the offset to the createdAt time of the oldest media attachment in the slice 71 offset = oldAttachments[len(oldAttachments)-1].CreatedAt 72 73 // for every old attachment, we need to make a new attachment out of it by taking the same values 74 newAttachments := []*newgtsmodel.MediaAttachment{} 75 for _, old := range oldAttachments { 76 new := &newgtsmodel.MediaAttachment{ 77 ID: old.ID, 78 CreatedAt: old.CreatedAt, 79 UpdatedAt: old.UpdatedAt, 80 StatusID: old.StatusID, 81 URL: old.URL, 82 RemoteURL: old.RemoteURL, 83 Type: newgtsmodel.FileType(old.Type), 84 FileMeta: newgtsmodel.FileMeta{ 85 Original: newgtsmodel.Original{ 86 Width: old.FileMeta.Original.Width, 87 Height: old.FileMeta.Original.Height, 88 Size: old.FileMeta.Original.Size, 89 Aspect: old.FileMeta.Original.Aspect, 90 }, 91 Small: newgtsmodel.Small{ 92 Width: old.FileMeta.Small.Width, 93 Height: old.FileMeta.Small.Height, 94 Size: old.FileMeta.Small.Size, 95 Aspect: old.FileMeta.Small.Aspect, 96 }, 97 Focus: newgtsmodel.Focus{ 98 X: old.FileMeta.Focus.X, 99 Y: old.FileMeta.Focus.Y, 100 }, 101 }, 102 AccountID: old.AccountID, 103 Description: old.Description, 104 ScheduledStatusID: old.ScheduledStatusID, 105 Blurhash: old.Blurhash, 106 Processing: newgtsmodel.ProcessingStatus(old.Processing), 107 File: newgtsmodel.File{ 108 Path: old.File.Path, 109 ContentType: old.File.ContentType, 110 FileSize: old.File.FileSize, 111 UpdatedAt: old.File.UpdatedAt, 112 }, 113 Thumbnail: newgtsmodel.Thumbnail{ 114 Path: old.Thumbnail.Path, 115 ContentType: old.Thumbnail.ContentType, 116 FileSize: old.Thumbnail.FileSize, 117 UpdatedAt: old.Thumbnail.UpdatedAt, 118 URL: old.Thumbnail.URL, 119 RemoteURL: old.Thumbnail.RemoteURL, 120 }, 121 Avatar: old.Avatar, 122 Header: old.Header, 123 Cached: true, 124 } 125 newAttachments = append(newAttachments, new) 126 } 127 128 // insert this batch of new attachments, and then continue the loop 129 if _, err := tx. 130 NewInsert(). 131 Model(&newAttachments). 132 ModelTableExpr("new_media_attachments"). 133 Exec(ctx); err != nil { 134 return err 135 } 136 } 137 138 // we have all the data we need from the old table, so we can safely drop it now 139 if _, err := tx.NewDropTable().Model(&previousgtsmodel.MediaAttachment{}).Exec(ctx); err != nil { 140 return err 141 } 142 143 // rename the new table to the same name as the old table was 144 if _, err := tx.ExecContext(ctx, "ALTER TABLE new_media_attachments RENAME TO media_attachments;"); err != nil { 145 return err 146 } 147 148 // add an index to the new table 149 if _, err := tx. 150 NewCreateIndex(). 151 Model(&newgtsmodel.MediaAttachment{}). 152 Index("media_attachments_id_idx"). 153 Column("id"). 154 Exec(ctx); err != nil { 155 return err 156 } 157 158 return nil 159 }) 160 } 161 162 down := func(ctx context.Context, db *bun.DB) error { 163 return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { 164 return nil 165 }) 166 } 167 168 if err := Migrations.Register(up, down); err != nil { 169 panic(err) 170 } 171 }