// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package database import ( "context" "database/sql" "net/http" "slices" "time" "go.mau.fi/util/dbutil" "go.mau.fi/util/jsontime" "maunium.net/go/mautrix" "maunium.net/go/mautrix/crypto/attachment" "maunium.net/go/mautrix/id" ) const ( insertCachedMediaQuery = ` INSERT INTO cached_media (mxc, event_rowid, enc_file, file_name, mime_type, size, hash, error) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (mxc) DO NOTHING ` upsertCachedMediaQuery = ` INSERT INTO cached_media (mxc, event_rowid, enc_file, file_name, mime_type, size, hash, error) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (mxc) DO UPDATE SET enc_file = excluded.enc_file, file_name = excluded.file_name, mime_type = excluded.mime_type, size = excluded.size, hash = excluded.hash, error = excluded.error WHERE excluded.error IS NULL OR cached_media.hash IS NULL ` getCachedMediaQuery = ` SELECT mxc, event_rowid, enc_file, file_name, mime_type, size, hash, error FROM cached_media WHERE mxc = $1 ` ) type CachedMediaQuery struct { *dbutil.QueryHelper[*CachedMedia] } func (cmq *CachedMediaQuery) Add(ctx context.Context, cm *CachedMedia) error { return cmq.Exec(ctx, insertCachedMediaQuery, cm.sqlVariables()...) } func (cmq *CachedMediaQuery) Put(ctx context.Context, cm *CachedMedia) error { return cmq.Exec(ctx, upsertCachedMediaQuery, cm.sqlVariables()...) } func (cmq *CachedMediaQuery) Get(ctx context.Context, mxc id.ContentURI) (*CachedMedia, error) { return cmq.QueryOne(ctx, getCachedMediaQuery, &mxc) } type MediaError struct { Matrix *mautrix.RespError `json:"data"` StatusCode int `json:"status_code"` ReceivedAt jsontime.UnixMilli `json:"received_at"` Attempts int `json:"attempts"` } const MaxMediaBackoff = 7 * 24 * time.Hour func (me *MediaError) backoff() time.Duration { return min(time.Duration(2<