Added playback history window
This commit is contained in:
@@ -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
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user