aboutsummaryrefslogtreecommitdiffstats
path: root/src/daemon.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-06-06 19:48:22 +0300
committersalmonik <raisersoft@proton.me>2026-06-06 19:48:22 +0300
commitd347d89dfeef54d648ffdea054897617c0016509 (patch)
tree0ea0e25e2a8e9bcdd8c06ffadbe29721a7f07f77 /src/daemon.rs
parentdirty, broken commit (diff)
downloadrsplayer-d347d89dfeef54d648ffdea054897617c0016509.tar.gz
rsplayer-d347d89dfeef54d648ffdea054897617c0016509.zip
correctly working auto completion for db
Diffstat (limited to '')
-rw-r--r--src/daemon.rs113
1 files changed, 31 insertions, 82 deletions
diff --git a/src/daemon.rs b/src/daemon.rs
index d34305a..5ad6654 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -18,14 +18,20 @@ use std::sync::{Arc, Mutex, RwLock};
18use std::thread; 18use std::thread;
19use std::time::{Duration, Instant, SystemTime}; 19use std::time::{Duration, Instant, SystemTime};
20 20
21struct ScrobbleState { 21pub struct ScrobbleState {
22 track: Option<Track>, 22 pub track: Option<Track>,
23 start_time: Option<Instant>, 23 pub start_time: Option<Instant>,
24 timestamp: Option<u64>, 24 pub timestamp: Option<u64>,
25}
26
27impl Default for ScrobbleState {
28 fn default() -> Self {
29 Self::new()
30 }
25} 31}
26 32
27impl ScrobbleState { 33impl ScrobbleState {
28 fn new() -> Self { 34 pub fn new() -> Self {
29 ScrobbleState { 35 ScrobbleState {
30 track: None, 36 track: None,
31 start_time: None, 37 start_time: None,
@@ -33,7 +39,7 @@ impl ScrobbleState {
33 } 39 }
34 } 40 }
35 41
36 fn start(&mut self, track: Track) { 42 pub fn start(&mut self, track: Track) {
37 if track.is_stream { 43 if track.is_stream {
38 crate::debug_eprintln(format_args!( 44 crate::debug_eprintln(format_args!(
39 "scrobble: not tracking stream '{}'", 45 "scrobble: not tracking stream '{}'",
@@ -57,7 +63,7 @@ impl ScrobbleState {
57 self.timestamp = Some(ts); 63 self.timestamp = Some(ts);
58 } 64 }
59 65
60 fn should_scrobble(&self) -> bool { 66 pub fn should_scrobble(&self) -> bool {
61 let (Some(track), Some(start)) = (&self.track, self.start_time) else { 67 let (Some(track), Some(start)) = (&self.track, self.start_time) else {
62 crate::debug_eprintln(format_args!( 68 crate::debug_eprintln(format_args!(
63 "scrobble: should_scrobble false — no track or start_time" 69 "scrobble: should_scrobble false — no track or start_time"
@@ -78,7 +84,7 @@ impl ScrobbleState {
78 result 84 result
79 } 85 }
80 86
81 fn should_scrobble_with_params(elapsed: u64, duration_secs: Option<f64>) -> bool { 87 pub fn should_scrobble_with_params(elapsed: u64, duration_secs: Option<f64>) -> bool {
82 if elapsed < 30 { 88 if elapsed < 30 {
83 return false; 89 return false;
84 } 90 }
@@ -89,86 +95,13 @@ impl ScrobbleState {
89 elapsed >= threshold 95 elapsed >= threshold
90 } 96 }
91 97
92 fn clear(&mut self) { 98 pub fn clear(&mut self) {
93 self.track = None; 99 self.track = None;
94 self.start_time = None; 100 self.start_time = None;
95 self.timestamp = None; 101 self.timestamp = None;
96 } 102 }
97} 103}
98 104
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_scrobble_state_new() {
105 let s = ScrobbleState::new();
106 assert!(s.track.is_none());
107 assert!(s.start_time.is_none());
108 assert!(s.timestamp.is_none());
109 }
110
111 #[test]
112 fn test_scrobble_state_clear() {
113 let mut s = ScrobbleState::new();
114 let t = Track {
115 path: PathBuf::from("/test/song.mp3"),
116 title: Some("Test".into()),
117 artist: Some("Artist".into()),
118 album: None,
119 duration_secs: Some(200.0),
120 is_stream: false,
121 };
122 s.start(t);
123 assert!(s.track.is_some());
124 s.clear();
125 assert!(s.track.is_none());
126 assert!(s.start_time.is_none());
127 assert!(s.timestamp.is_none());
128 }
129
130 #[test]
131 fn test_should_scrobble_without_track_returns_false() {
132 assert!(!ScrobbleState::new().should_scrobble());
133 }
134
135 #[test]
136 fn test_should_scrobble_threshold_below_30_seconds() {
137 assert!(!ScrobbleState::should_scrobble_with_params(29, Some(200.0)));
138 }
139
140 #[test]
141 fn test_should_scrobble_threshold_half_duration() {
142 assert!(ScrobbleState::should_scrobble_with_params(100, Some(200.0)));
143 }
144
145 #[test]
146 fn test_should_scrobble_threshold_below_half_duration() {
147 assert!(!ScrobbleState::should_scrobble_with_params(80, Some(200.0)));
148 }
149
150 #[test]
151 fn test_should_scrobble_threshold_capped_at_4min() {
152 assert!(ScrobbleState::should_scrobble_with_params(240, Some(600.0)));
153 assert!(!ScrobbleState::should_scrobble_with_params(
154 239,
155 Some(600.0)
156 ));
157 }
158
159 #[test]
160 fn test_should_scrobble_no_duration_requires_4min() {
161 assert!(ScrobbleState::should_scrobble_with_params(240, None));
162 assert!(!ScrobbleState::should_scrobble_with_params(239, None));
163 }
164
165 #[test]
166 fn test_should_scrobble_short_song() {
167 assert!(ScrobbleState::should_scrobble_with_params(30, Some(60.0)));
168 assert!(!ScrobbleState::should_scrobble_with_params(15, Some(60.0)));
169 }
170}
171
172fn try_scrobble_current(scrobbler: &RwLock<Scrobbler>, state: &mut ScrobbleState) { 105fn try_scrobble_current(scrobbler: &RwLock<Scrobbler>, state: &mut ScrobbleState) {
173 if let Some(ref track) = state.track 106 if let Some(ref track) = state.track
174 && track.is_stream 107 && track.is_stream
@@ -875,6 +808,22 @@ fn process_command(
875 // Strip trailing slash so autocompleted Artist/Album/ resolves cleanly 808 // Strip trailing slash so autocompleted Artist/Album/ resolves cleanly
876 let path_str = path_str.trim_end_matches('/').to_string(); 809 let path_str = path_str.trim_end_matches('/').to_string();
877 810
811 // Unescape backslash sequences (e.g., \! → !) so shell escaping works
812 let path_str = {
813 let mut unescaped = String::with_capacity(path_str.len());
814 let mut chars = path_str.chars();
815 while let Some(c) = chars.next() {
816 if c == '\\' {
817 if let Some(next) = chars.next() {
818 unescaped.push(next);
819 }
820 } else {
821 unescaped.push(c);
822 }
823 }
824 unescaped
825 };
826
878 if path_str.is_empty() { 827 if path_str.is_empty() {
879 return "Usage: play [now|next|last] <path>\n".to_string(); 828 return "Usage: play [now|next|last] <path>\n".to_string();
880 } 829 }