Migration 10: - play_history table (audio_file_id FK, played_at DATETIME, CASCADE delete) - play_count + last_played columns on audio_files (denormalized) - Recreated track_metadata VIEW with play_count and last_played columns Play recording: - queue.recordPlay() inserts play_history row + updates denormalized columns - Called from OnPlaybackFinished after queue advance completes - Mutex released before DB write to avoid MaxOpenConns(1) deadlock - Natural finish only — skip/stop does not count Tests: - TestMigration10PlayHistory: schema, columns, VIEW, round-trip verification - All 49 smart playlist + 15 service + existing DB tests still pass
58 lines
1.2 KiB
Go
58 lines
1.2 KiB
Go
package queue
|
|
|
|
import "time"
|
|
|
|
// recordPlay inserts a play_history row and updates the denormalized
|
|
// play_count / last_played columns on audio_files. Called from
|
|
// OnPlaybackFinished for the track that just finished.
|
|
//
|
|
// SAFETY: Uses ExecContext with parameterized queries only.
|
|
// Must be called without q.mu held — it acquires the DB connection
|
|
// which is single-writer (MaxOpenConns 1).
|
|
func (q *Queue) recordPlay(audioFileID int64) {
|
|
if audioFileID <= 0 {
|
|
return
|
|
}
|
|
|
|
now := time.Now().UTC().Format(time.DateTime)
|
|
|
|
// Insert play_history row.
|
|
_, err := q.db.ExecContext(
|
|
`INSERT INTO play_history (audio_file_id, played_at)
|
|
VALUES (?, ?)`,
|
|
audioFileID, now,
|
|
)
|
|
if err != nil {
|
|
q.logger.Error(
|
|
"failed to insert play history",
|
|
"audioFileId", audioFileID,
|
|
"error", err,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
// Update denormalized columns on audio_files.
|
|
_, err = q.db.ExecContext(
|
|
`UPDATE audio_files
|
|
SET play_count = play_count + 1,
|
|
last_played = ?
|
|
WHERE id = ?`,
|
|
now, audioFileID,
|
|
)
|
|
if err != nil {
|
|
q.logger.Error(
|
|
"failed to update play count",
|
|
"audioFileId", audioFileID,
|
|
"error", err,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
q.logger.Info(
|
|
"Play recorded",
|
|
"audioFileId", audioFileID,
|
|
)
|
|
}
|