ci / module boundaries / boundaries (push) Successful in 6s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 2m1s
ci / ts / ts (packages + web) (push) Failing after 5m13s
ci / api / api (push) Successful in 41s
ci / db / db (push) Failing after 1m33s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 2m25s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 2m19s
75 lines
2.1 KiB
Go
75 lines
2.1 KiB
Go
package library
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"os"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type artistImageStore interface {
|
|
ArtistWithoutImageForFile(context.Context, uuid.UUID) (uuid.UUID, error)
|
|
SetArtistImageIfMissing(context.Context, uuid.UUID, string) error
|
|
}
|
|
|
|
type imageRenderer interface {
|
|
StoreImage(context.Context, []byte) (string, error)
|
|
}
|
|
|
|
// ArtistImages applies approved portraits to the album artist, never guests.
|
|
type ArtistImages struct {
|
|
store artistImageStore
|
|
images imageRenderer
|
|
}
|
|
|
|
func NewArtistImages(store *Store, covers *Covers) *ArtistImages {
|
|
return &ArtistImages{store: store, images: covers}
|
|
}
|
|
|
|
func (a *ArtistImages) ApplyArtistImage(ctx context.Context, file uuid.UUID, data []byte) error {
|
|
id, err := a.store.ArtistWithoutImageForFile(ctx, file)
|
|
if errors.Is(err, ErrNotFound) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
key, err := a.images.StoreImage(ctx, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return a.store.SetArtistImageIfMissing(ctx, id, key)
|
|
}
|
|
|
|
func (c *Covers) StoreImage(ctx context.Context, data []byte) (string, error) {
|
|
if err := os.MkdirAll(c.Dir, 0o755); err != nil {
|
|
return "", err
|
|
}
|
|
sum := sha256.Sum256(data)
|
|
key := hex.EncodeToString(sum[:8])
|
|
_, err := c.render(ctx, data, key)
|
|
return key, err
|
|
}
|
|
|
|
func (st *Store) ArtistWithoutImageForFile(ctx context.Context, file uuid.UUID) (uuid.UUID, error) {
|
|
var id uuid.UUID
|
|
err := st.pool.QueryRow(ctx, `SELECT a.id FROM media_files f JOIN tracks t ON t.id=f.track_id
|
|
JOIN albums al ON al.id=t.album_id JOIN artists a ON a.id=al.album_artist_id
|
|
WHERE f.id=$1 AND a.image_path IS NULL AND a.genius_image_url IS NULL`, file).Scan(&id)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return id, ErrNotFound
|
|
}
|
|
return id, err
|
|
}
|
|
|
|
func (st *Store) SetArtistImageIfMissing(ctx context.Context, id uuid.UUID, key string) error {
|
|
// Another import or Genius may have supplied a portrait during rendering.
|
|
_, err := st.pool.Exec(ctx, `UPDATE artists SET image_path=$2 WHERE id=$1
|
|
AND image_path IS NULL AND genius_image_url IS NULL`, id, key)
|
|
return err
|
|
}
|