gtsocial-umbx

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

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 }