gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }