From 1788303bf3f40cb1d9561b5938917566862f5313 Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Mon, 10 Jun 2024 20:53:15 +0200 Subject: [PATCH] Implement program parameters --- Cargo.lock | 37 ------------ Cargo.toml | 1 - data/de.johrpan.musicus.gschema.xml | 4 +- src/library.rs | 93 ++++++++++++++++++++++++++++- src/player.rs | 10 ++++ src/playlist_item.rs | 5 ++ 6 files changed, 108 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 556f69c..4fe3cf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1049,7 +1049,6 @@ dependencies = [ "log", "mpris-player", "once_cell", - "rand", "serde", "serde_json", "tracing-subscriber", @@ -1206,12 +1205,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1273,36 +1266,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - [[package]] name = "regex" version = "1.10.2" diff --git a/Cargo.toml b/Cargo.toml index 82d1d28..674249e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ lazy_static = "1" log = "0.4" mpris-player = "0.6" once_cell = "1" -rand = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" tracing-subscriber = "0.3" diff --git a/data/de.johrpan.musicus.gschema.xml b/data/de.johrpan.musicus.gschema.xml index 97e8741..ea75434 100644 --- a/data/de.johrpan.musicus.gschema.xml +++ b/data/de.johrpan.musicus.gschema.xml @@ -19,7 +19,7 @@ - '{"title":"Just play some music","description":"Randomly select some music. Customize programs using the button in the top right.","design":"Program1","prefer_recently_added":0.0,"prefer_least_recently_played":0.0,"play_full_recordings":true}' + '{"title":"Just play some music","description":"Randomly select some music. Customize programs using the button in the top right.","design":"Program1","prefer_recently_added":0.0,"prefer_least_recently_played":0.1,"play_full_recordings":true}' Default settings for program 1 @@ -29,7 +29,7 @@ - '{"title":"A long time ago","description":"Works that you haven\'t listend to for a long time.","design":"Program3","prefer_recently_added":-1.0,"prefer_least_recently_played":1.0,"play_full_recordings":false}' + '{"title":"A long time ago","description":"Works that you haven\'t listend to for a long time.","design":"Program3","prefer_recently_added":0.0,"prefer_least_recently_played":1.0,"play_full_recordings":false}' Default settings for program 3 diff --git a/src/library.rs b/src/library.rs index b752721..ffad06c 100644 --- a/src/library.rs +++ b/src/library.rs @@ -5,7 +5,13 @@ use std::{ use anyhow::Result; use chrono::prelude::*; -use diesel::{dsl::exists, prelude::*, QueryDsl, SqliteConnection}; +use diesel::{ + dsl::{exists, sql}, + prelude::*, + sql_query, + sql_types::BigInt, + QueryDsl, SqliteConnection, +}; use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*}; use crate::{ @@ -414,7 +420,59 @@ impl MusicusLibrary { query = query.filter(album_recordings::album_id.eq(album_id)); } - // TODO: Implement prefer_recently_added and prefer_least_recently_played. + if program.prefer_recently_added() > 0.0 { + let oldest_timestamp = sql_query( + "SELECT CAST(STRFTIME('%s', MIN(created_at)) AS INTEGER) AS value FROM recordings", + ) + .get_result::(connection)? + .value; + + let newest_timestamp = sql_query( + "SELECT CAST(STRFTIME('%s', MAX(created_at)) AS INTEGER) AS value FROM recordings", + ) + .get_result::(connection)? + .value; + + let range = newest_timestamp - oldest_timestamp; + + if range >= 60 { + let proportion = program.prefer_recently_added().max(1.0) * 0.9; + let cutoff_timestamp = + oldest_timestamp + (proportion * range as f64).floor() as i64; + + query = query.filter( + sql::("CAST(STRFTIME('%s', recordings.created_at) AS INTEGER)") + .ge(cutoff_timestamp) + .or(recordings::last_played_at.is_null()), + ); + } + } + + if program.prefer_least_recently_played() > 0.0 { + let oldest_timestamp = + sql_query("SELECT CAST(STRFTIME('%s', MIN(last_played_at)) AS INTEGER) AS value FROM recordings") + .get_result::(connection)? + .value; + + let newest_timestamp = + sql_query("SELECT CAST(STRFTIME('%s', MAX(last_played_at)) AS INTEGER) AS value FROM recordings") + .get_result::(connection)? + .value; + + let range = newest_timestamp - oldest_timestamp; + + if range >= 60 { + let proportion = 1.0 - program.prefer_least_recently_played().max(1.0) * 0.9; + let cutoff_timestamp = + oldest_timestamp + (proportion * range as f64).floor() as i64; + + query = query.filter( + sql::("CAST(STRFTIME('%s', recordings.last_played_at) AS INTEGER)") + .le(cutoff_timestamp) + .or(recordings::last_played_at.is_null()), + ); + } + } let row = query .order(random()) @@ -424,6 +482,31 @@ impl MusicusLibrary { Recording::from_table(row, &self.folder(), connection) } + pub fn track_played(&self, track_id: &str) -> Result<()> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let now = Local::now().naive_local(); + + diesel::update(recordings::table) + .filter(exists( + tracks::table.filter( + tracks::track_id + .eq(track_id) + .and(tracks::recording_id.eq(recordings::recording_id)), + ), + )) + .set(recordings::last_played_at.eq(now)) + .execute(connection)?; + + diesel::update(tracks::table) + .filter(tracks::track_id.eq(track_id)) + .set(tracks::last_played_at.eq(now)) + .execute(connection)?; + + Ok(()) + } + pub fn search_persons(&self, search: &str) -> Result> { let search = format!("%{}%", search); let mut binding = self.imp().connection.borrow_mut(); @@ -555,3 +638,9 @@ impl LibraryResults { && self.albums.is_empty() } } + +#[derive(QueryableByName)] +pub struct IntegerValue { + #[diesel(sql_type = diesel::sql_types::BigInt)] + pub value: i64, +} diff --git a/src/player.rs b/src/player.rs index 46134eb..d047db0 100644 --- a/src/player.rs +++ b/src/player.rs @@ -90,6 +90,13 @@ mod imp { self.current_index.set(index); item.set_is_playing(true); + + self.library + .borrow() + .as_ref() + .unwrap() + .track_played(&item.track_id()) + .unwrap(); } } } @@ -228,6 +235,7 @@ impl MusicusPlayer { Some(&performances), None, &tracks[0].path, + &tracks[0].track_id, )); } else { let mut tracks = tracks.into_iter(); @@ -254,6 +262,7 @@ impl MusicusPlayer { Some(&performances), Some(&track_title(&first_track, 1)), &first_track.path, + &first_track.track_id, )); for (index, track) in tracks.enumerate() { @@ -264,6 +273,7 @@ impl MusicusPlayer { // track number = track index + 1 (first track) + 1 (zero based) Some(&track_title(&track, index + 2)), &track.path, + &track.track_id, )); } } diff --git a/src/playlist_item.rs b/src/playlist_item.rs index 9f33432..279688d 100644 --- a/src/playlist_item.rs +++ b/src/playlist_item.rs @@ -27,6 +27,9 @@ mod imp { #[property(get, construct_only)] pub path: OnceCell, + + #[property(get, construct_only)] + pub track_id: OnceCell, } #[glib::object_subclass] @@ -50,6 +53,7 @@ impl PlaylistItem { performers: Option<&str>, part_title: Option<&str>, path: impl AsRef, + track_id: &str, ) -> Self { glib::Object::builder() .property("is-title", is_title) @@ -57,6 +61,7 @@ impl PlaylistItem { .property("performers", performers) .property("part-title", part_title) .property("path", path.as_ref()) + .property("track-id", track_id) .build() }