Added playback history window

This commit is contained in:
Ultradesu
2026-07-27 23:37:46 +01:00
parent a7f41ff205
commit 28189bae95
14 changed files with 281 additions and 5 deletions
+53
View File
@@ -26,6 +26,16 @@ use models::{
pub const LIKES_PLAYLIST_ID: i64 = -1;
const NETWORK_ARTIST_CACHE_TTL_MS: i64 = 7 * 24 * 60 * 60 * 1000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListenHistoryEntry {
pub listen_id: String,
pub content_id: String,
pub title: String,
pub artist: String,
pub origin_device_id: String,
pub started_at_ms: i64,
}
const SCHEMA: &str = "
CREATE TABLE IF NOT EXISTS artists (
id INTEGER PRIMARY KEY,
@@ -2277,6 +2287,49 @@ impl Library {
Ok(inserted > 0)
}
/// Most recent qualified listens, including tracks that are not present
/// in this device's local library.
pub fn listen_history(&self, limit: usize) -> Result<Vec<ListenHistoryEntry>> {
let conn = self.lock();
let mut stmt = conn.prepare(
"SELECT listen_id, content_id, origin_device_id, started_at_ms, metadata_json
FROM listen_events
WHERE qualified = 1
ORDER BY started_at_ms DESC, listen_id DESC
LIMIT ?1",
)?;
let rows = stmt
.query_map([limit.min(i64::MAX as usize) as i64], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, i64>(3)?,
row.get::<_, String>(4)?,
))
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
rows.into_iter()
.map(
|(listen_id, content_id, origin_device_id, started_at_ms, metadata_json)| {
let metadata: music_dht::device_sync::ListenTrackMetadata =
serde_json::from_str(&metadata_json)
.context("invalid listen history metadata")?;
let mut artists = metadata.artist_names;
artists.extend(metadata.featured_artist_names);
Ok(ListenHistoryEntry {
listen_id,
content_id,
title: metadata.title,
artist: artists.join(", "),
origin_device_id,
started_at_ms,
})
},
)
.collect()
}
// -----------------------------------------------------------------
// Editing & deleting
// -----------------------------------------------------------------
+27
View File
@@ -526,4 +526,31 @@ fn history_counts_completed_plays() {
assert!(!lib.apply_listen_event(&event, "device-a").unwrap());
let track = lib.tracks_by_ids(&[track_id]).unwrap().remove(0);
assert_eq!(track.play_count, 1);
let history = lib.listen_history(20).unwrap();
assert_eq!(history.len(), 1);
assert_eq!(history[0].listen_id, "listen-1");
assert_eq!(history[0].title, "Song");
assert_eq!(history[0].artist, "Artist");
assert_eq!(history[0].origin_device_id, "device-a");
}
#[test]
fn listen_history_hides_unqualified_events_and_keeps_remote_metadata() {
let lib = test_library();
let event = music_dht::device_sync::ListenEvent {
listen_id: "remote-listen".to_string(),
content_id: format!("b3:{}", "a".repeat(64)),
started_at_ms: 1_700_000_000_000,
listened_ms: 10_000,
track_duration_ms: Some(120_000),
ended_reason: music_dht::device_sync::ListenEndReason::Skipped,
track: music_dht::device_sync::ListenTrackMetadata {
title: "Remote song".to_string(),
artist_names: vec!["Remote artist".to_string()],
featured_artist_names: vec!["Guest".to_string()],
release_title: None,
},
};
assert!(lib.apply_listen_event(&event, "remote-device").unwrap());
assert!(lib.listen_history(20).unwrap().is_empty());
}