Skip to content

Commit 487799b

Browse files
author
Ross
committed
fix caching file names and add favorites
1 parent 429b842 commit 487799b

4 files changed

Lines changed: 406 additions & 100 deletions

File tree

src/app.rs

Lines changed: 202 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub struct App {
5353
pub subsonic_client: Arc<SubsonicClient>,
5454
pub player: Rc<Mutex<Player>>,
5555
pub queue: Vec<Track>,
56+
pub favorites: Vec<Track>,
5657
pub tracks: Vec<Track>,
5758
pub albums: Vec<Album>,
5859
pub artists: Vec<Artist>,
@@ -62,6 +63,7 @@ pub struct App {
6263
pub selected_artist_index: usize,
6364
pub selected_album_index: usize,
6465
pub selected_playlist_index: usize,
66+
pub selected_favorite_index: usize,
6567
pub is_playing: bool,
6668
pub current_track: Option<Track>,
6769
pub current_volume: f64,
@@ -77,6 +79,7 @@ pub struct App {
7779
pub album_state: ListState,
7880
pub playlist_state: ListState,
7981
pub search_state: ListState,
82+
pub favorite_state: ListState,
8083
pub active_tab: ActiveTab,
8184
pub active_section: ActiveSection,
8285
// Search fields
@@ -97,6 +100,7 @@ pub struct App {
97100
pub enum InputMode {
98101
Normal,
99102
Search,
103+
InlineSearch, // search in current tab
100104
}
101105
#[derive(Clone, Debug)]
102106
pub struct Track {
@@ -143,6 +147,7 @@ pub enum ActiveTab {
143147
Artists,
144148
Songs,
145149
Search,
150+
Favorites,
146151
}
147152
impl App {
148153
pub async fn new() -> Result<Self> {
@@ -201,12 +206,14 @@ impl App {
201206
artists: Vec::new(),
202207
albums: Vec::new(),
203208
playlists: Vec::new(),
209+
favorites: Vec::new(),
204210
metadata: Metadata::default(),
205211
selected_queue_index: 0,
206212
selected_index: 0,
207213
selected_artist_index: 0,
208214
selected_album_index: 0,
209215
selected_playlist_index: 0,
216+
selected_favorite_index: 0,
210217
playing_index: 0,
211218
is_playing: false,
212219
current_track: None,
@@ -218,6 +225,7 @@ impl App {
218225
album_state: ListState::default(),
219226
search_state: ListState::default(),
220227
playlist_state: ListState::default(),
228+
favorite_state: ListState::default(),
221229
mpris: mprisserver,
222230
command_receiver: rx,
223231
active_tab: ActiveTab::Songs,
@@ -229,7 +237,7 @@ impl App {
229237
search_engine,
230238
is_searching: false,
231239
on_repeat: false,
232-
_on_shuffle: true,
240+
_on_shuffle: false,
233241
cover_art_protocol: None,
234242
};
235243

@@ -267,6 +275,109 @@ impl App {
267275
])
268276
.await;
269277
}
278+
pub fn start_inline_search(&mut self) {
279+
self.input_mode = InputMode::InlineSearch;
280+
self.search_query.clear();
281+
}
282+
pub fn exit_inline_search(&mut self) {
283+
self.input_mode = InputMode::Normal;
284+
self.search_query.clear();
285+
}
286+
pub fn inline_search_input(&mut self, c: char) {
287+
if self.input_mode == InputMode::InlineSearch {
288+
self.search_query.push(c);
289+
self.jump_to_inline_match();
290+
}
291+
}
292+
pub fn inline_search_backspace(&mut self) {
293+
if self.input_mode == InputMode::InlineSearch {
294+
self.search_query.pop();
295+
self.jump_to_inline_match();
296+
}
297+
}
298+
299+
pub fn jump_to_inline_match(&mut self) {
300+
if self.search_query.is_empty() {
301+
return;
302+
}
303+
let query = self.search_query.to_lowercase();
304+
match self.active_section {
305+
ActiveSection::Queue => {
306+
if let Some(idx) = self.queue.iter().position(|t| {
307+
t.title.to_lowercase().contains(&query)
308+
|| t.artist.to_lowercase().contains(&query)
309+
}) {
310+
self.selected_queue_index = idx;
311+
self.queue_state.select(Some(idx));
312+
}
313+
}
314+
ActiveSection::Others => match self.active_tab {
315+
ActiveTab::Songs => {
316+
if let Some(idx) = self.tracks.iter().position(|t| {
317+
t.title.to_lowercase().contains(&query)
318+
|| t.artist.to_lowercase().contains(&query)
319+
}) {
320+
self.selected_index = idx;
321+
self.list_state.select(Some(idx));
322+
}
323+
}
324+
ActiveTab::Favorites => {
325+
if let Some(idx) = self.favorites.iter().position(|a| {
326+
a.title.to_lowercase().contains(&query)
327+
|| a.artist.to_lowercase().contains(&query)
328+
}) {
329+
self.selected_favorite_index = idx;
330+
self.favorite_state.select(Some(idx));
331+
}
332+
}
333+
ActiveTab::Artists => {
334+
if let Some(idx) = self
335+
.artists
336+
.iter()
337+
.position(|a| a.name.to_lowercase().contains(&query))
338+
{
339+
self.selected_artist_index = idx;
340+
self.artist_state.select(Some(idx));
341+
}
342+
}
343+
ActiveTab::Albums => {
344+
if let Some(idx) = self.albums.iter().position(|a| {
345+
a.name.to_lowercase().contains(&query)
346+
|| a.artist.to_lowercase().contains(&query)
347+
}) {
348+
self.selected_album_index = idx;
349+
self.album_state.select(Some(idx));
350+
}
351+
}
352+
ActiveTab::Playlist => {
353+
if let Some(idx) = self
354+
.playlists
355+
.iter()
356+
.position(|p| p.name.to_lowercase().contains(&query))
357+
{
358+
self.selected_playlist_index = idx;
359+
self.playlist_state.select(Some(idx));
360+
}
361+
}
362+
ActiveTab::Search => {}
363+
},
364+
}
365+
}
366+
pub async fn handle_inline_search_input(&mut self, key: KeyEvent) -> Result<bool> {
367+
match key.code {
368+
KeyCode::Esc | KeyCode::Enter => {
369+
self.exit_inline_search();
370+
}
371+
KeyCode::Backspace => {
372+
self.inline_search_backspace();
373+
}
374+
KeyCode::Char(c) => {
375+
self.inline_search_input(c);
376+
}
377+
_ => {}
378+
}
379+
Ok(false)
380+
}
270381
pub fn enter_search_mode(&mut self) {
271382
self.input_mode = InputMode::Search;
272383
self.active_tab = ActiveTab::Search;
@@ -322,20 +433,29 @@ impl App {
322433
}
323434
pub async fn load_cover_art_for_track(&mut self, track: &Track) {
324435
self.cover_art_protocol = None;
436+
let album = track
437+
.album
438+
.replace(|c: char| !c.is_alphabetic() && c != '-', "_")
439+
.to_lowercase()
440+
.chars()
441+
.fold(String::new(), |mut acc, c| {
442+
if c == '_' && acc.ends_with('_') {
443+
acc
444+
} else {
445+
acc.push(c);
446+
acc
447+
}
448+
});
325449

326450
let url = match &track.cover_art {
327451
Some(url) if !url.is_empty() => url,
328452
_ => return,
329453
};
330454
let mut cache_path = std::env::temp_dir();
331455
cache_path.push("sonicrust");
332-
cache_path.push(format!("cover_{}_{}.jpg", track.album, track.artist));
456+
cache_path.push(format!("cover_{}.jpg", album));
333457
let img_result = if cache_path.exists() {
334-
log::debug!(
335-
"Using cached cover_art for {}_{}",
336-
track.album,
337-
track.artist
338-
);
458+
log::debug!("Using cached cover_art for {}", album);
339459
image::open(&cache_path)
340460
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
341461
} else {
@@ -354,30 +474,17 @@ impl App {
354474
Err(e) => eprintln!("failed to load cover art: {}", e),
355475
}
356476
}
357-
async fn fetch_and_cache_image(
358-
&self,
359-
url: &str,
360-
track_album: &str,
361-
track_artist: &str,
362-
) -> Result<String> {
363-
log::debug!(
364-
"Currently fetching cover are for track_id: {}_{}",
365-
track_album,
366-
track_artist
367-
);
477+
async fn fetch_and_cache_image(&self, url: &str, track_album: &str) -> Result<String> {
478+
log::debug!("Currently fetching cover are for album: {}", track_album,);
368479
let mut path = std::env::temp_dir();
369480
path.push("sonicrust");
370481
std::fs::create_dir_all(&path).map_err(|e| anyhow::anyhow!(e))?;
371-
path.push(format!("cover_{}_{}.jpg", track_album, track_artist));
482+
path.push(format!("cover_{}.jpg", track_album));
372483
if path.exists() {
373-
log::debug!(
374-
"Using cached cover art for {}_{}",
375-
track_album,
376-
track_artist
377-
);
484+
log::debug!("Using cached cover art for {}", track_album,);
378485
return Ok(path.to_string_lossy().to_string());
379486
}
380-
log::debug!("fetching cover art for {}_{}", track_album, track_artist);
487+
log::debug!("fetching cover art for {}", track_album);
381488
let img = self
382489
.fetch_cover_art(url)
383490
.await
@@ -462,6 +569,7 @@ impl App {
462569
self.artists = self.subsonic_client.get_all_artists().await?;
463570
self.albums = self.subsonic_client.get_all_albums().await?;
464571
self.playlists = self.subsonic_client.get_playlists().await?;
572+
self.favorites = self.subsonic_client.get_all_favorites().await?;
465573
Ok(())
466574
}
467575
pub fn find_selected(&self) -> usize {
@@ -482,6 +590,7 @@ impl App {
482590
}
483591
}
484592
ActiveTab::Artists => self.selected_queue_index,
593+
ActiveTab::Favorites => self.selected_queue_index,
485594
ActiveTab::Playlist => self.selected_queue_index,
486595
ActiveTab::Songs => {
487596
if !self.queue.is_empty() {
@@ -546,6 +655,16 @@ impl App {
546655
track_to_play = Some(track);
547656
}
548657
}
658+
ActiveTab::Favorites => {
659+
if let Some(track) =
660+
self.favorites.get(self.selected_favorite_index).cloned()
661+
{
662+
self.queue = vec![track.clone()];
663+
self.selected_queue_index = 0;
664+
track_to_play = Some(track);
665+
self.playing_index = self.selected_queue_index;
666+
}
667+
}
549668
ActiveTab::Songs => {
550669
if let Some(track) = self.tracks.get(self.selected_index).cloned() {
551670
self.queue = vec![track.clone()];
@@ -757,10 +876,21 @@ impl App {
757876
if let Some(url) = &track.cover_art
758877
&& !url.is_empty()
759878
{
760-
match self
761-
.fetch_and_cache_image(url, &track.album, &track.artist)
762-
.await
763-
{
879+
let album = &track
880+
.album
881+
.replace(|c: char| !c.is_alphabetic() && c != '-', "_")
882+
.to_lowercase()
883+
.chars()
884+
.fold(String::new(), |mut acc, c| {
885+
if c == '_' && acc.ends_with('_') {
886+
acc
887+
} else {
888+
acc.push(c);
889+
acc
890+
}
891+
});
892+
893+
match self.fetch_and_cache_image(url, album).await {
764894
Ok(path) => {
765895
notif.hint(Hint::ImagePath(path));
766896
}
@@ -883,6 +1013,7 @@ impl App {
8831013
ActiveTab::Playlist => self.playlist_state.select(None),
8841014
ActiveTab::Artists => self.artist_state.select(None),
8851015
ActiveTab::Albums => self.album_state.select(None),
1016+
ActiveTab::Favorites => self.favorite_state.select(None),
8861017
ActiveTab::Search => {
8871018
self.search_state.select(None);
8881019
self.input_mode = InputMode::Normal;
@@ -903,6 +1034,10 @@ impl App {
9031034
ActiveTab::Artists if !self.artists.is_empty() => {
9041035
self.artist_state.select(Some(self.selected_artist_index));
9051036
}
1037+
ActiveTab::Favorites if !self.favorites.is_empty() => {
1038+
self.favorite_state
1039+
.select(Some(self.selected_favorite_index));
1040+
}
9061041
ActiveTab::Albums if !self.albums.is_empty() => {
9071042
self.album_state.select(Some(self.selected_album_index));
9081043
}
@@ -932,8 +1067,13 @@ impl App {
9321067
ActiveTab::Songs => {
9331068
self.artist_state.select(Some(self.selected_index));
9341069
}
1070+
ActiveTab::Favorites => {
1071+
self.favorite_state
1072+
.select(Some(self.selected_favorite_index));
1073+
}
9351074
ActiveTab::Playlist => {
936-
self.playlist_state.select(Some(self.selected_index));
1075+
self.playlist_state
1076+
.select(Some(self.selected_playlist_index));
9371077
}
9381078
ActiveTab::Artists => {
9391079
self.album_state.select(Some(self.selected_artist_index));
@@ -1007,6 +1147,20 @@ impl App {
10071147
self.list_state.select(None);
10081148
}
10091149
}
1150+
ActiveTab::Favorites => {
1151+
if !self.favorites.is_empty() {
1152+
let i = if let Some(selected) = self.favorite_state.selected() {
1153+
(selected + 1) % self.favorites.len()
1154+
} else {
1155+
0
1156+
};
1157+
self.selected_favorite_index = i;
1158+
self.favorite_state
1159+
.select(Some(self.selected_favorite_index));
1160+
} else {
1161+
self.favorite_state.select(None);
1162+
}
1163+
}
10101164
ActiveTab::Artists => {
10111165
if !self.artists.is_empty() {
10121166
let i = if let Some(selected) = self.artist_state.selected() {
@@ -1057,6 +1211,24 @@ impl App {
10571211
}
10581212
ActiveSection::Others => {
10591213
match self.active_tab {
1214+
ActiveTab::Favorites => {
1215+
if !self.favorites.is_empty() {
1216+
let i = if let Some(selected) = self.favorite_state.selected() {
1217+
if selected == 0 {
1218+
self.favorites.len() - 1
1219+
} else {
1220+
selected - 1
1221+
}
1222+
} else {
1223+
self.favorites.len().saturating_sub(1)
1224+
};
1225+
self.selected_favorite_index = i;
1226+
self.favorite_state
1227+
.select(Some(self.selected_favorite_index));
1228+
} else {
1229+
self.favorite_state.select(None);
1230+
}
1231+
}
10601232
ActiveTab::Playlist => {
10611233
if !self.playlists.is_empty() {
10621234
let i = if let Some(selected) = self.playlist_state.selected() {

0 commit comments

Comments
 (0)