dhowden/tag's readTFrame splits ID3v2 null-separated multi-value text frames and rejoins them with the EMPTY string, so a file tagged "Alternative Rock" + "Rock" was stored as "Alternative RockRock". It also leaves bare numeric ID3v1 references unresolved, which is why the library showed genres like "4017" and "526617". This corrupted more than the browse axis added in #367: taste_profile.sql reads tracks.genre directly, so the welded tokens were entering the taste profile's tag vocabulary, and recommendation.sql/discover.sql were comparing them as single opaque tags. Genre counts were wrong everywhere. ffprobe is not a fix — ffmpeg's read_ttag calls decode_str once with no loop, keeping only the first value. Truncating multi-genre tags would blunt the similarity signal genre mainly feeds. So the TCON frame is now parsed directly (ID3v2.2/2.3/2.4, all four text encodings, per-frame and tag-level unsynchronisation, numeric and parenthesised ID3v1 references); everything else still comes from dhowden/tag. Values are stored ";"-delimited, which the read side already splits on, so no query changes. Existing rows are repaired without an operator-run rebuild: migration 0054 adds tracks.tag_read_version DEFAULT 0, below the scanner's current tagReadVersion, so the next scan re-reads tags it would otherwise skip on mtime. Such a re-read reuses the stored duration instead of re-running ffprobe, keeping a repair pass tag-read-bound rather than one fork+exec per file. Bumping the constant is how a future extraction fix reaches an existing library. Only ID3v2 is in scope — dhowden welds nowhere else. The Vorbis/MP4 repeated-field question is #2500, unproven and deliberately not built.
355 lines
8.7 KiB
Go
355 lines
8.7 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: likes.sql
|
|
|
|
package dbq
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const countLikedAlbums = `-- name: CountLikedAlbums :one
|
|
SELECT count(*) FROM general_likes_albums WHERE user_id = $1
|
|
`
|
|
|
|
func (q *Queries) CountLikedAlbums(ctx context.Context, userID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countLikedAlbums, userID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const countLikedArtists = `-- name: CountLikedArtists :one
|
|
SELECT count(*) FROM general_likes_artists WHERE user_id = $1
|
|
`
|
|
|
|
func (q *Queries) CountLikedArtists(ctx context.Context, userID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countLikedArtists, userID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const countLikedTracks = `-- name: CountLikedTracks :one
|
|
SELECT count(*) FROM general_likes WHERE user_id = $1
|
|
`
|
|
|
|
func (q *Queries) CountLikedTracks(ctx context.Context, userID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countLikedTracks, userID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const likeAlbum = `-- name: LikeAlbum :exec
|
|
INSERT INTO general_likes_albums (user_id, album_id)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (user_id, album_id) DO NOTHING
|
|
`
|
|
|
|
type LikeAlbumParams struct {
|
|
UserID pgtype.UUID
|
|
AlbumID pgtype.UUID
|
|
}
|
|
|
|
func (q *Queries) LikeAlbum(ctx context.Context, arg LikeAlbumParams) error {
|
|
_, err := q.db.Exec(ctx, likeAlbum, arg.UserID, arg.AlbumID)
|
|
return err
|
|
}
|
|
|
|
const likeArtist = `-- name: LikeArtist :exec
|
|
INSERT INTO general_likes_artists (user_id, artist_id)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (user_id, artist_id) DO NOTHING
|
|
`
|
|
|
|
type LikeArtistParams struct {
|
|
UserID pgtype.UUID
|
|
ArtistID pgtype.UUID
|
|
}
|
|
|
|
func (q *Queries) LikeArtist(ctx context.Context, arg LikeArtistParams) error {
|
|
_, err := q.db.Exec(ctx, likeArtist, arg.UserID, arg.ArtistID)
|
|
return err
|
|
}
|
|
|
|
const likeTrack = `-- name: LikeTrack :execrows
|
|
INSERT INTO general_likes (user_id, track_id)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (user_id, track_id) DO NOTHING
|
|
`
|
|
|
|
type LikeTrackParams struct {
|
|
UserID pgtype.UUID
|
|
TrackID pgtype.UUID
|
|
}
|
|
|
|
func (q *Queries) LikeTrack(ctx context.Context, arg LikeTrackParams) (int64, error) {
|
|
result, err := q.db.Exec(ctx, likeTrack, arg.UserID, arg.TrackID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected(), nil
|
|
}
|
|
|
|
const listLikedAlbumIDs = `-- name: ListLikedAlbumIDs :many
|
|
SELECT album_id FROM general_likes_albums WHERE user_id = $1 ORDER BY liked_at DESC
|
|
`
|
|
|
|
func (q *Queries) ListLikedAlbumIDs(ctx context.Context, userID pgtype.UUID) ([]pgtype.UUID, error) {
|
|
rows, err := q.db.Query(ctx, listLikedAlbumIDs, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []pgtype.UUID
|
|
for rows.Next() {
|
|
var album_id pgtype.UUID
|
|
if err := rows.Scan(&album_id); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, album_id)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listLikedAlbumRows = `-- name: ListLikedAlbumRows :many
|
|
SELECT a.id, a.title, a.sort_title, a.artist_id, a.release_date, a.mbid, a.cover_art_path, a.created_at, a.updated_at, a.cover_art_source, a.cover_art_sources_version FROM albums a
|
|
JOIN general_likes_albums l ON l.album_id = a.id
|
|
WHERE l.user_id = $1
|
|
ORDER BY l.liked_at DESC
|
|
LIMIT $2 OFFSET $3
|
|
`
|
|
|
|
type ListLikedAlbumRowsParams struct {
|
|
UserID pgtype.UUID
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
func (q *Queries) ListLikedAlbumRows(ctx context.Context, arg ListLikedAlbumRowsParams) ([]Album, error) {
|
|
rows, err := q.db.Query(ctx, listLikedAlbumRows, arg.UserID, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Album
|
|
for rows.Next() {
|
|
var i Album
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.SortTitle,
|
|
&i.ArtistID,
|
|
&i.ReleaseDate,
|
|
&i.Mbid,
|
|
&i.CoverArtPath,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.CoverArtSource,
|
|
&i.CoverArtSourcesVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listLikedArtistIDs = `-- name: ListLikedArtistIDs :many
|
|
SELECT artist_id FROM general_likes_artists WHERE user_id = $1 ORDER BY liked_at DESC
|
|
`
|
|
|
|
func (q *Queries) ListLikedArtistIDs(ctx context.Context, userID pgtype.UUID) ([]pgtype.UUID, error) {
|
|
rows, err := q.db.Query(ctx, listLikedArtistIDs, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []pgtype.UUID
|
|
for rows.Next() {
|
|
var artist_id pgtype.UUID
|
|
if err := rows.Scan(&artist_id); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, artist_id)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listLikedArtistRows = `-- name: ListLikedArtistRows :many
|
|
SELECT a.id, a.name, a.sort_name, a.mbid, a.created_at, a.updated_at, a.artist_thumb_path, a.artist_fanart_path, a.artist_art_source, a.artist_art_sources_version FROM artists a
|
|
JOIN general_likes_artists l ON l.artist_id = a.id
|
|
WHERE l.user_id = $1
|
|
ORDER BY l.liked_at DESC
|
|
LIMIT $2 OFFSET $3
|
|
`
|
|
|
|
type ListLikedArtistRowsParams struct {
|
|
UserID pgtype.UUID
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
func (q *Queries) ListLikedArtistRows(ctx context.Context, arg ListLikedArtistRowsParams) ([]Artist, error) {
|
|
rows, err := q.db.Query(ctx, listLikedArtistRows, arg.UserID, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Artist
|
|
for rows.Next() {
|
|
var i Artist
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listLikedTrackIDs = `-- name: ListLikedTrackIDs :many
|
|
SELECT track_id FROM general_likes WHERE user_id = $1 ORDER BY liked_at DESC
|
|
`
|
|
|
|
func (q *Queries) ListLikedTrackIDs(ctx context.Context, userID pgtype.UUID) ([]pgtype.UUID, error) {
|
|
rows, err := q.db.Query(ctx, listLikedTrackIDs, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []pgtype.UUID
|
|
for rows.Next() {
|
|
var track_id pgtype.UUID
|
|
if err := rows.Scan(&track_id); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, track_id)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listLikedTrackRows = `-- name: ListLikedTrackRows :many
|
|
SELECT t.id, t.title, t.album_id, t.artist_id, t.track_number, t.disc_number, t.duration_ms, t.file_path, t.file_size, t.file_format, t.bitrate, t.mbid, t.genre, t.added_at, t.updated_at, t.tag_source, t.tag_sources_version, t.tag_read_version FROM tracks t
|
|
JOIN general_likes l ON l.track_id = t.id
|
|
WHERE l.user_id = $1
|
|
ORDER BY l.liked_at DESC
|
|
LIMIT $2 OFFSET $3
|
|
`
|
|
|
|
type ListLikedTrackRowsParams struct {
|
|
UserID pgtype.UUID
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
func (q *Queries) ListLikedTrackRows(ctx context.Context, arg ListLikedTrackRowsParams) ([]Track, error) {
|
|
rows, err := q.db.Query(ctx, listLikedTrackRows, arg.UserID, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Track
|
|
for rows.Next() {
|
|
var i Track
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Title,
|
|
&i.AlbumID,
|
|
&i.ArtistID,
|
|
&i.TrackNumber,
|
|
&i.DiscNumber,
|
|
&i.DurationMs,
|
|
&i.FilePath,
|
|
&i.FileSize,
|
|
&i.FileFormat,
|
|
&i.Bitrate,
|
|
&i.Mbid,
|
|
&i.Genre,
|
|
&i.AddedAt,
|
|
&i.UpdatedAt,
|
|
&i.TagSource,
|
|
&i.TagSourcesVersion,
|
|
&i.TagReadVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const unlikeAlbum = `-- name: UnlikeAlbum :exec
|
|
DELETE FROM general_likes_albums WHERE user_id = $1 AND album_id = $2
|
|
`
|
|
|
|
type UnlikeAlbumParams struct {
|
|
UserID pgtype.UUID
|
|
AlbumID pgtype.UUID
|
|
}
|
|
|
|
func (q *Queries) UnlikeAlbum(ctx context.Context, arg UnlikeAlbumParams) error {
|
|
_, err := q.db.Exec(ctx, unlikeAlbum, arg.UserID, arg.AlbumID)
|
|
return err
|
|
}
|
|
|
|
const unlikeArtist = `-- name: UnlikeArtist :exec
|
|
DELETE FROM general_likes_artists WHERE user_id = $1 AND artist_id = $2
|
|
`
|
|
|
|
type UnlikeArtistParams struct {
|
|
UserID pgtype.UUID
|
|
ArtistID pgtype.UUID
|
|
}
|
|
|
|
func (q *Queries) UnlikeArtist(ctx context.Context, arg UnlikeArtistParams) error {
|
|
_, err := q.db.Exec(ctx, unlikeArtist, arg.UserID, arg.ArtistID)
|
|
return err
|
|
}
|
|
|
|
const unlikeTrack = `-- name: UnlikeTrack :exec
|
|
DELETE FROM general_likes WHERE user_id = $1 AND track_id = $2
|
|
`
|
|
|
|
type UnlikeTrackParams struct {
|
|
UserID pgtype.UUID
|
|
TrackID pgtype.UUID
|
|
}
|
|
|
|
func (q *Queries) UnlikeTrack(ctx context.Context, arg UnlikeTrackParams) error {
|
|
_, err := q.db.Exec(ctx, unlikeTrack, arg.UserID, arg.TrackID)
|
|
return err
|
|
}
|