media.go (7911B)
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 bundb 19 20 import ( 21 "context" 22 "errors" 23 "time" 24 25 "github.com/superseriousbusiness/gotosocial/internal/db" 26 "github.com/superseriousbusiness/gotosocial/internal/gtscontext" 27 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 28 "github.com/superseriousbusiness/gotosocial/internal/log" 29 "github.com/superseriousbusiness/gotosocial/internal/state" 30 "github.com/uptrace/bun" 31 ) 32 33 type mediaDB struct { 34 conn *DBConn 35 state *state.State 36 } 37 38 func (m *mediaDB) GetAttachmentByID(ctx context.Context, id string) (*gtsmodel.MediaAttachment, db.Error) { 39 return m.getAttachment( 40 ctx, 41 "ID", 42 func(attachment *gtsmodel.MediaAttachment) error { 43 return m.conn.NewSelect(). 44 Model(attachment). 45 Where("? = ?", bun.Ident("media_attachment.id"), id). 46 Scan(ctx) 47 }, 48 id, 49 ) 50 } 51 52 func (m *mediaDB) GetAttachmentsByIDs(ctx context.Context, ids []string) ([]*gtsmodel.MediaAttachment, error) { 53 attachments := make([]*gtsmodel.MediaAttachment, 0, len(ids)) 54 55 for _, id := range ids { 56 // Attempt fetch from DB 57 attachment, err := m.GetAttachmentByID(ctx, id) 58 if err != nil { 59 log.Errorf(ctx, "error getting attachment %q: %v", id, err) 60 continue 61 } 62 63 // Append attachment 64 attachments = append(attachments, attachment) 65 } 66 67 return attachments, nil 68 } 69 70 func (m *mediaDB) getAttachment(ctx context.Context, lookup string, dbQuery func(*gtsmodel.MediaAttachment) error, keyParts ...any) (*gtsmodel.MediaAttachment, db.Error) { 71 return m.state.Caches.GTS.Media().Load(lookup, func() (*gtsmodel.MediaAttachment, error) { 72 var attachment gtsmodel.MediaAttachment 73 74 // Not cached! Perform database query 75 if err := dbQuery(&attachment); err != nil { 76 return nil, m.conn.ProcessError(err) 77 } 78 79 return &attachment, nil 80 }, keyParts...) 81 } 82 83 func (m *mediaDB) PutAttachment(ctx context.Context, media *gtsmodel.MediaAttachment) error { 84 return m.state.Caches.GTS.Media().Store(media, func() error { 85 _, err := m.conn.NewInsert().Model(media).Exec(ctx) 86 return m.conn.ProcessError(err) 87 }) 88 } 89 90 func (m *mediaDB) UpdateAttachment(ctx context.Context, media *gtsmodel.MediaAttachment, columns ...string) error { 91 media.UpdatedAt = time.Now() 92 if len(columns) > 0 { 93 // If we're updating by column, ensure "updated_at" is included. 94 columns = append(columns, "updated_at") 95 } 96 97 return m.state.Caches.GTS.Media().Store(media, func() error { 98 _, err := m.conn.NewUpdate(). 99 Model(media). 100 Where("? = ?", bun.Ident("media_attachment.id"), media.ID). 101 Column(columns...). 102 Exec(ctx) 103 return m.conn.ProcessError(err) 104 }) 105 } 106 107 func (m *mediaDB) DeleteAttachment(ctx context.Context, id string) error { 108 defer m.state.Caches.GTS.Media().Invalidate("ID", id) 109 110 // Load media into cache before attempting a delete, 111 // as we need it cached in order to trigger the invalidate 112 // callback. This in turn invalidates others. 113 _, err := m.GetAttachmentByID(gtscontext.SetBarebones(ctx), id) 114 if err != nil { 115 if errors.Is(err, db.ErrNoEntries) { 116 // not an issue. 117 err = nil 118 } 119 return err 120 } 121 122 // Finally delete media from DB. 123 _, err = m.conn.NewDelete(). 124 TableExpr("? AS ?", bun.Ident("media_attachments"), bun.Ident("media_attachment")). 125 Where("? = ?", bun.Ident("media_attachment.id"), id). 126 Exec(ctx) 127 return m.conn.ProcessError(err) 128 } 129 130 func (m *mediaDB) GetRemoteOlderThan(ctx context.Context, olderThan time.Time, limit int) ([]*gtsmodel.MediaAttachment, db.Error) { 131 attachmentIDs := []string{} 132 133 q := m.conn. 134 NewSelect(). 135 TableExpr("? AS ?", bun.Ident("media_attachments"), bun.Ident("media_attachment")). 136 Column("media_attachment.id"). 137 Where("? = ?", bun.Ident("media_attachment.cached"), true). 138 Where("? < ?", bun.Ident("media_attachment.created_at"), olderThan). 139 WhereGroup(" AND ", whereNotEmptyAndNotNull("media_attachment.remote_url")). 140 Order("media_attachment.created_at DESC") 141 142 if limit != 0 { 143 q = q.Limit(limit) 144 } 145 146 if err := q.Scan(ctx, &attachmentIDs); err != nil { 147 return nil, m.conn.ProcessError(err) 148 } 149 150 return m.GetAttachmentsByIDs(ctx, attachmentIDs) 151 } 152 153 func (m *mediaDB) CountRemoteOlderThan(ctx context.Context, olderThan time.Time) (int, db.Error) { 154 q := m.conn. 155 NewSelect(). 156 TableExpr("? AS ?", bun.Ident("media_attachments"), bun.Ident("media_attachment")). 157 Column("media_attachment.id"). 158 Where("? = ?", bun.Ident("media_attachment.cached"), true). 159 Where("? < ?", bun.Ident("media_attachment.created_at"), olderThan). 160 WhereGroup(" AND ", whereNotEmptyAndNotNull("media_attachment.remote_url")) 161 162 count, err := q.Count(ctx) 163 if err != nil { 164 return 0, m.conn.ProcessError(err) 165 } 166 167 return count, nil 168 } 169 170 func (m *mediaDB) GetAvatarsAndHeaders(ctx context.Context, maxID string, limit int) ([]*gtsmodel.MediaAttachment, db.Error) { 171 attachmentIDs := []string{} 172 173 q := m.conn.NewSelect(). 174 TableExpr("? AS ?", bun.Ident("media_attachments"), bun.Ident("media_attachment")). 175 Column("media_attachment.id"). 176 WhereGroup(" AND ", func(innerQ *bun.SelectQuery) *bun.SelectQuery { 177 return innerQ. 178 WhereOr("? = ?", bun.Ident("media_attachment.avatar"), true). 179 WhereOr("? = ?", bun.Ident("media_attachment.header"), true) 180 }). 181 Order("media_attachment.id DESC") 182 183 if maxID != "" { 184 q = q.Where("? < ?", bun.Ident("media_attachment.id"), maxID) 185 } 186 187 if limit != 0 { 188 q = q.Limit(limit) 189 } 190 191 if err := q.Scan(ctx, &attachmentIDs); err != nil { 192 return nil, m.conn.ProcessError(err) 193 } 194 195 return m.GetAttachmentsByIDs(ctx, attachmentIDs) 196 } 197 198 func (m *mediaDB) GetLocalUnattachedOlderThan(ctx context.Context, olderThan time.Time, limit int) ([]*gtsmodel.MediaAttachment, db.Error) { 199 attachmentIDs := []string{} 200 201 q := m.conn. 202 NewSelect(). 203 TableExpr("? AS ?", bun.Ident("media_attachments"), bun.Ident("media_attachment")). 204 Column("media_attachment.id"). 205 Where("? = ?", bun.Ident("media_attachment.cached"), true). 206 Where("? = ?", bun.Ident("media_attachment.avatar"), false). 207 Where("? = ?", bun.Ident("media_attachment.header"), false). 208 Where("? < ?", bun.Ident("media_attachment.created_at"), olderThan). 209 Where("? IS NULL", bun.Ident("media_attachment.remote_url")). 210 Where("? IS NULL", bun.Ident("media_attachment.status_id")). 211 Order("media_attachment.created_at DESC") 212 213 if limit != 0 { 214 q = q.Limit(limit) 215 } 216 217 if err := q.Scan(ctx, &attachmentIDs); err != nil { 218 return nil, m.conn.ProcessError(err) 219 } 220 221 return m.GetAttachmentsByIDs(ctx, attachmentIDs) 222 } 223 224 func (m *mediaDB) CountLocalUnattachedOlderThan(ctx context.Context, olderThan time.Time) (int, db.Error) { 225 q := m.conn. 226 NewSelect(). 227 TableExpr("? AS ?", bun.Ident("media_attachments"), bun.Ident("media_attachment")). 228 Column("media_attachment.id"). 229 Where("? = ?", bun.Ident("media_attachment.cached"), true). 230 Where("? = ?", bun.Ident("media_attachment.avatar"), false). 231 Where("? = ?", bun.Ident("media_attachment.header"), false). 232 Where("? < ?", bun.Ident("media_attachment.created_at"), olderThan). 233 Where("? IS NULL", bun.Ident("media_attachment.remote_url")). 234 Where("? IS NULL", bun.Ident("media_attachment.status_id")) 235 236 count, err := q.Count(ctx) 237 if err != nil { 238 return 0, m.conn.ProcessError(err) 239 } 240 241 return count, nil 242 }