Added, but disabled icon dl impl. Player improvements
This commit is contained in:
		
							parent
							
								
									4ee4ca1add
								
							
						
					
					
						commit
						86cf5542be
					
				
							
								
								
									
										982
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										982
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -35,7 +35,7 @@ lazy_static = "1.4.0"
 | 
			
		|||
log = "0.4.21"
 | 
			
		||||
# notify-rust = "4.11.3"
 | 
			
		||||
# open = "5.3.0"
 | 
			
		||||
# reqwest = { version = "0.12.3", features = ["blocking", "h2", "http2", "rustls-tls"], default-features = false }
 | 
			
		||||
reqwest = { version = "0.12.3", features = ["blocking", "h2", "http2", "rustls-tls"], default-features = false }
 | 
			
		||||
serde = { version = "1.0.197", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.115"
 | 
			
		||||
url = { version = "2.5.0", features = ["serde"] }
 | 
			
		||||
| 
						 | 
				
			
			@ -47,3 +47,4 @@ winresource = "0.1.17"
 | 
			
		|||
toml = "0.8.19"
 | 
			
		||||
rfd = "0.15.1"
 | 
			
		||||
rodio = { version = "0.20.1", features = ["symphonia-all"] }
 | 
			
		||||
image = "0.25.5"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2426
									
								
								manifest.json
									
									
									
									
									
								
							
							
						
						
									
										2426
									
								
								manifest.json
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -26,3 +26,6 @@ camino.workspace = true
 | 
			
		|||
lazy_static.workspace = true
 | 
			
		||||
log.workspace = true
 | 
			
		||||
uuid.workspace = true
 | 
			
		||||
reqwest.workspace = true
 | 
			
		||||
url.workspace = true
 | 
			
		||||
image.workspace = true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,133 @@
 | 
			
		|||
use std::{collections::HashMap, ffi::OsStr, io::{BufReader, Cursor}, path::PathBuf, process::{Command, Stdio}, str::FromStr, sync::{Arc, Mutex, MutexGuard}};
 | 
			
		||||
 | 
			
		||||
use anyhow::anyhow;
 | 
			
		||||
use image::ImageReader;
 | 
			
		||||
use xmpd_manifest::song::{IconType, Song, SourceType};
 | 
			
		||||
 | 
			
		||||
use crate::{downloader::song::SongStatus, DlStatus};
 | 
			
		||||
 | 
			
		||||
lazy_static::lazy_static!(
 | 
			
		||||
    static ref ICON_CACHE_DL: Arc<Mutex<IconCacheDl>> = Arc::default();
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Clone)]
 | 
			
		||||
pub struct IconCacheDl {
 | 
			
		||||
    pub jobs: HashMap<uuid::Uuid, DlStatus>,
 | 
			
		||||
    pub current_jobs: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IconCacheDl {
 | 
			
		||||
    pub fn get() -> crate::Result<MutexGuard<'static, Self>> {
 | 
			
		||||
        match ICON_CACHE_DL.lock() {
 | 
			
		||||
            Ok(v) => Ok(v),
 | 
			
		||||
            Err(e) => anyhow::bail!(format!("{e:?}"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn is_job_list_full(&self) -> bool {
 | 
			
		||||
        self.current_jobs >= 5
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn download(&mut self, sid: uuid::Uuid, song: Song) -> crate::Result<()> {
 | 
			
		||||
        match song.icon_type().clone() {
 | 
			
		||||
            IconType::FromSource => {
 | 
			
		||||
                let tooling = xmpd_settings::Settings::get()?.tooling.clone();
 | 
			
		||||
                match song.source_type() {
 | 
			
		||||
                    SourceType::Youtube => {
 | 
			
		||||
                        self.jobs.insert(sid.clone(), DlStatus::Downloading);
 | 
			
		||||
                        let mut path = xmpd_cliargs::CLIARGS.cache_path();
 | 
			
		||||
                        path.push("icons");
 | 
			
		||||
                        path.push(sid.to_string());
 | 
			
		||||
 | 
			
		||||
                        let mut cmd = Command::new(tooling.ytdlp_path);
 | 
			
		||||
                        cmd.arg(song.url().to_string());
 | 
			
		||||
                        cmd.arg("-o");
 | 
			
		||||
                        cmd.arg(&path);
 | 
			
		||||
                        cmd.args(["--write-thumbnail", "--skip-download"]);
 | 
			
		||||
                        if xmpd_cliargs::CLIARGS.debug {
 | 
			
		||||
                            cmd.stdout(Stdio::piped());
 | 
			
		||||
                            cmd.stderr(Stdio::piped());
 | 
			
		||||
                        } else {
 | 
			
		||||
                            cmd.stdout(Stdio::null());
 | 
			
		||||
                            cmd.stderr(Stdio::null());
 | 
			
		||||
                        }
 | 
			
		||||
                        let child = cmd.spawn()?;
 | 
			
		||||
                        std::thread::spawn(move || {
 | 
			
		||||
                            if let Ok(output) = child.wait_with_output() {
 | 
			
		||||
                                for line in String::from_utf8(output.stdout).unwrap().lines() {
 | 
			
		||||
                                    log::info!("CMD: {}", line);
 | 
			
		||||
                                }
 | 
			
		||||
                                for line in String::from_utf8(output.stderr).unwrap().lines() {
 | 
			
		||||
                                    log::error!("CMD: {}", line);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            let old_p = path.with_extension("webp"); // Default for yt-dlp
 | 
			
		||||
                            let new_p = path.with_extension("png");  // Default for all
 | 
			
		||||
                            let old_img = ImageReader::open(&old_p).unwrap().decode().unwrap();
 | 
			
		||||
                            old_img.save(&new_p).unwrap();
 | 
			
		||||
                            std::fs::remove_file(old_p).unwrap();
 | 
			
		||||
                            let mut cache = IconCacheDl::get().unwrap();
 | 
			
		||||
                            cache.jobs.insert(sid, DlStatus::Done(Some(new_p.into())));
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    SourceType::Spotify => {
 | 
			
		||||
                        todo!()
 | 
			
		||||
                    }
 | 
			
		||||
                    SourceType::Soundcloud => {
 | 
			
		||||
                        todo!()
 | 
			
		||||
                    }
 | 
			
		||||
                    _ => (),
 | 
			
		||||
                } 
 | 
			
		||||
            }
 | 
			
		||||
            IconType::CustomUrl(url) => self.download_custom_url_icon(&sid, &url)?,
 | 
			
		||||
            IconType::None => ()
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn download_custom_url_icon(&mut self, sid: &uuid::Uuid, url: &url::Url) -> crate::Result<()> {
 | 
			
		||||
        self.jobs.insert(sid.clone(), DlStatus::Downloading);
 | 
			
		||||
        let url_p = PathBuf::from_str(url.path())?;
 | 
			
		||||
        let Some(ext) = url_p.extension() else {
 | 
			
		||||
            anyhow::bail!("Url without extension, cant continue");
 | 
			
		||||
        };
 | 
			
		||||
        let ext = ext.to_string_lossy().to_string();
 | 
			
		||||
        let mut path = xmpd_cliargs::CLIARGS.cache_path();
 | 
			
		||||
        path.push("icons");
 | 
			
		||||
        path.push(sid.to_string());
 | 
			
		||||
        path.set_extension(ext);
 | 
			
		||||
        let sid = sid.clone();
 | 
			
		||||
        let url = url.clone();
 | 
			
		||||
        std::thread::spawn(move || {
 | 
			
		||||
            match reqwest::blocking::get(url.clone()) {
 | 
			
		||||
                Ok(v) => {
 | 
			
		||||
                   match v.bytes() {
 | 
			
		||||
                        Ok(bytes) => {
 | 
			
		||||
                            if let Err(e) = std::fs::write(path, bytes) {
 | 
			
		||||
                                if let Ok(mut cache) = IconCacheDl::get() {
 | 
			
		||||
                                    if let Some(job) = cache.jobs.get_mut(&sid) {
 | 
			
		||||
                                        *job = DlStatus::Error(file!(), line!() as usize, format!("{e:?}"));
 | 
			
		||||
                                    } 
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        Err(e) => {
 | 
			
		||||
                            if let Ok(mut cache) = IconCacheDl::get() {
 | 
			
		||||
                                if let Some(job) = cache.jobs.get_mut(&sid) {
 | 
			
		||||
                                    *job = DlStatus::Error(file!(), line!() as usize, format!("{e:?}"));
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                   }
 | 
			
		||||
                }
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    if let Ok(mut cache) = IconCacheDl::get() {
 | 
			
		||||
                        if let Some(job) = cache.jobs.get_mut(&sid) {
 | 
			
		||||
                            *job = DlStatus::Error(file!(), line!() as usize, format!("{e:?}"));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use std::{collections::HashMap, str::FromStr, sync::{mpsc::{self, Receiver, Sender}, Arc, Mutex, MutexGuard}, time::Duration};
 | 
			
		||||
use std::{collections::HashMap, path::PathBuf, str::FromStr, sync::{mpsc::{self, Receiver, Sender}, Arc, Mutex, MutexGuard}, time::Duration};
 | 
			
		||||
use downloader::song::SongStatus;
 | 
			
		||||
use xmpd_manifest::song::Song;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -14,20 +14,24 @@ lazy_static::lazy_static!(
 | 
			
		|||
pub struct Cache {
 | 
			
		||||
    cache_dir: camino::Utf8PathBuf,
 | 
			
		||||
    song_cache: HashMap<uuid::Uuid, DlStatus>,
 | 
			
		||||
    queue: Vec<(uuid::Uuid, Song)>
 | 
			
		||||
    icon_cache: HashMap<uuid::Uuid, DlStatus>,
 | 
			
		||||
    song_queue: Vec<(uuid::Uuid, Song)>,
 | 
			
		||||
    icon_queue: Vec<(uuid::Uuid, Song)>,
 | 
			
		||||
    //meta_queue: Vec<(uuid::Uuid, Song)>
 | 
			
		||||
    // TODO: Add Icon, metadata cache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum DlStatus {
 | 
			
		||||
    Done(camino::Utf8PathBuf),
 | 
			
		||||
    Done(Option<PathBuf>),
 | 
			
		||||
    Downloading,
 | 
			
		||||
    Error(String),
 | 
			
		||||
    Error(&'static str, usize, String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum Message {
 | 
			
		||||
    DownloadDone(uuid::Uuid),
 | 
			
		||||
    Error(&'static str, usize, String)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cache {
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +64,7 @@ impl Cache {
 | 
			
		|||
                        let id = uuid::Uuid::from_str(file_name)?;
 | 
			
		||||
                        log::debug!("Found song {id}");
 | 
			
		||||
                        // TODO: Check if id is in manifest
 | 
			
		||||
                        self.song_cache.insert(id, DlStatus::Done(file_path.to_path_buf()));
 | 
			
		||||
                        self.song_cache.insert(id, DlStatus::Done(Some(file_path.into())));
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -73,21 +77,35 @@ impl Cache {
 | 
			
		|||
        Ok(cache_rx)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn download_to_cache(&mut self, sid: uuid::Uuid, song: Song) {
 | 
			
		||||
        self.queue.push((sid, song));
 | 
			
		||||
    pub fn download_song_to_cache(&mut self, sid: uuid::Uuid, song: Song) {
 | 
			
		||||
        self.song_queue.push((sid, song));
 | 
			
		||||
        self.song_cache.insert(sid, DlStatus::Downloading);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn download_to_cache_multiple(&mut self, mut songs: Vec<(uuid::Uuid, Song)>) {
 | 
			
		||||
        while let Some((sid, song)) = songs.pop() {
 | 
			
		||||
            self.download_to_cache(sid, song);
 | 
			
		||||
        }
 | 
			
		||||
    pub fn download_icon_to_cache(&mut self, sid: uuid::Uuid, song: Song) {
 | 
			
		||||
        self.icon_queue.push((sid, song));
 | 
			
		||||
        self.icon_cache.insert(sid, DlStatus::Downloading);
 | 
			
		||||
    }        
 | 
			
		||||
 | 
			
		||||
    pub fn get_cached_song_status(&mut self, sid: &uuid::Uuid) -> Option<DlStatus> {
 | 
			
		||||
        let original = self.song_cache.get(sid)?.clone();
 | 
			
		||||
        Some(original)
 | 
			
		||||
    }
 | 
			
		||||
    pub fn get_cached_icon_status(&mut self, sid: &uuid::Uuid) -> Option<DlStatus> {
 | 
			
		||||
        let original = self.icon_cache.get(sid)?.clone();
 | 
			
		||||
        Some(original)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
macro_rules! he {
 | 
			
		||||
    ($tx:expr, $val:expr) => {
 | 
			
		||||
        match $val {
 | 
			
		||||
            Ok(v) => v,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                let _ = $tx.send(Message::Error(std::file!(), std::line!() as usize, format!("{e:?}")));
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn start_cache_mv_thread(tx: Sender<Message>) {
 | 
			
		||||
| 
						 | 
				
			
			@ -95,19 +113,19 @@ fn start_cache_mv_thread(tx: Sender<Message>) {
 | 
			
		|||
        loop {
 | 
			
		||||
            {
 | 
			
		||||
                std::thread::sleep(Duration::from_millis(500));
 | 
			
		||||
                let song_format = xmpd_settings::Settings::get().unwrap().tooling.song_format.clone();
 | 
			
		||||
                let song_format = he!(tx, xmpd_settings::Settings::get()).tooling.song_format.clone();
 | 
			
		||||
                let mut done_jobs = Vec::new();
 | 
			
		||||
                let mut dlc = downloader::song::SongCacheDl::get().unwrap();
 | 
			
		||||
                let mut dlc = he!(tx, downloader::song::SongCacheDl::get());
 | 
			
		||||
                for (sid, status) in &dlc.jobs {
 | 
			
		||||
                    if *status == SongStatus::Done {
 | 
			
		||||
                        let mut cache = CACHE.lock().unwrap();
 | 
			
		||||
                        let mut cache = he!(tx, CACHE.lock());
 | 
			
		||||
                        let mut song_p = xmpd_cliargs::CLIARGS.cache_path().clone();
 | 
			
		||||
                        song_p.push("songs");
 | 
			
		||||
                        song_p.push(sid.clone().to_string());
 | 
			
		||||
                        let song_p = song_p.with_extension(&song_format);
 | 
			
		||||
                        if song_p.exists() {
 | 
			
		||||
                            let _ = tx.send(Message::DownloadDone(sid.clone()));
 | 
			
		||||
                            cache.song_cache.insert(sid.clone(), DlStatus::Done(song_p)); 
 | 
			
		||||
                            cache.song_cache.insert(sid.clone(), DlStatus::Done(Some(song_p.into()))); 
 | 
			
		||||
                            done_jobs.push(sid.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -115,17 +133,44 @@ fn start_cache_mv_thread(tx: Sender<Message>) {
 | 
			
		|||
                for sid in done_jobs {
 | 
			
		||||
                    dlc.jobs.remove(&sid);
 | 
			
		||||
                }
 | 
			
		||||
                {
 | 
			
		||||
                    let mut done_jobs = Vec::new();
 | 
			
		||||
                    let mut dlc = he!(tx, downloader::icon::IconCacheDl::get());
 | 
			
		||||
                    for (sid, status) in &dlc.jobs {
 | 
			
		||||
                        if let DlStatus::Done(path) = status {
 | 
			
		||||
                            let mut cache = he!(tx, CACHE.lock());
 | 
			
		||||
                            cache.icon_cache.insert(sid.clone(), DlStatus::Done(path.clone())); 
 | 
			
		||||
                            done_jobs.push(sid.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    for sid in done_jobs {
 | 
			
		||||
                        dlc.jobs.remove(&sid);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            {
 | 
			
		||||
                let mut cache = Cache::get().unwrap();
 | 
			
		||||
                let mut dlc = downloader::song::SongCacheDl::get().unwrap();
 | 
			
		||||
                let mut cache = he!(tx, Cache::get());
 | 
			
		||||
                {
 | 
			
		||||
                    let mut dlc = he!(tx, downloader::song::SongCacheDl::get());
 | 
			
		||||
                    if !dlc.is_job_list_full() {
 | 
			
		||||
                    if let Some((sid, song)) = cache.queue.pop() {
 | 
			
		||||
                        dlc.download(sid, song).unwrap();
 | 
			
		||||
                        if let Some((sid, song)) = cache.song_queue.pop() {
 | 
			
		||||
                            he!(tx, dlc.download(sid, song));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                {
 | 
			
		||||
                    let mut icnc = he!(tx, downloader::icon::IconCacheDl::get());
 | 
			
		||||
                    if !icnc.is_job_list_full() {
 | 
			
		||||
                        if let Some((sid, song)) = cache.icon_queue.pop() {
 | 
			
		||||
                            log::debug!("Downloading {sid:?}");
 | 
			
		||||
                            he!(tx, icnc.download(sid, song));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
use egui::{Sense, Stroke, Vec2};
 | 
			
		||||
use egui::{RichText, Sense, Stroke, Vec2};
 | 
			
		||||
use xmpd_manifest::store::BaseStore;
 | 
			
		||||
 | 
			
		||||
use super::{song_list::SongList, CompGetter, CompUi};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -26,13 +27,45 @@ component_register!(Player);
 | 
			
		|||
impl CompUi for Player {
 | 
			
		||||
    fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
 | 
			
		||||
        let theme = xmpd_settings::Settings::get()?.theme.clone();
 | 
			
		||||
        let avail = ui.available_size();
 | 
			
		||||
        let full_avail = ui.available_size();
 | 
			
		||||
        ui.horizontal_centered(|ui| {
 | 
			
		||||
            ui.add_space(10.0);
 | 
			
		||||
            let icon = egui::Image::new(crate::data::NOTE_ICON)
 | 
			
		||||
                .tint(theme.accent_color)
 | 
			
		||||
                .sense(Sense::click())
 | 
			
		||||
                .fit_to_exact_size(Vec2::new(32.0, 32.0));
 | 
			
		||||
            ui.add(icon);
 | 
			
		||||
            ui.vertical(|ui| {
 | 
			
		||||
                
 | 
			
		||||
                ui.add_space(5.0);
 | 
			
		||||
                let sid = &handle_error_ui!(SongList::get()).selected_sid;
 | 
			
		||||
                if let Some(song) = state.manifest.store().get_song(sid) {
 | 
			
		||||
                    let mut name = song.name().to_string();
 | 
			
		||||
                    if name.len() > 16 {
 | 
			
		||||
                        name = (&name)[..16].to_string();
 | 
			
		||||
                        name.push_str("...");
 | 
			
		||||
                    }
 | 
			
		||||
                    ui.label(
 | 
			
		||||
                        RichText::new(name)
 | 
			
		||||
                            .size(12.0)
 | 
			
		||||
                    );
 | 
			
		||||
                    ui.label(
 | 
			
		||||
                        RichText::new(song.author())
 | 
			
		||||
                            .size(8.0)
 | 
			
		||||
                            .monospace()
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            ui.vertical_centered_justified(|ui| {
 | 
			
		||||
                let avail = ui.available_size();
 | 
			
		||||
                let song_info_w = full_avail.x - avail.x;
 | 
			
		||||
                ui.add_space(3.0);
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    
 | 
			
		||||
                    {
 | 
			
		||||
                    ui.add_space(avail.x * 0.05 / 2.0);
 | 
			
		||||
                    ui.style_mut().spacing.slider_width = avail.x * 0.87;
 | 
			
		||||
                        let slider_width = full_avail.x * 0.60;
 | 
			
		||||
                        ui.add_space((((full_avail.x / 2.0) - song_info_w) - slider_width / 2.0).clamp(0.0, f32::MAX));
 | 
			
		||||
                        ui.style_mut().spacing.slider_width = avail.x * 0.75;
 | 
			
		||||
                        let s = Stroke {
 | 
			
		||||
                            color: theme.accent_color,
 | 
			
		||||
                            width: 2.0
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +95,8 @@ impl CompUi for Player {
 | 
			
		|||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                ui.add_space((avail.x / 2.0) - 16.0 - 8.0 - ui.spacing().item_spacing.x);
 | 
			
		||||
                    let icon_size = 16.0;
 | 
			
		||||
                    ui.add_space(((full_avail.x / 2.0) - song_info_w) - icon_size * 1.5 - ui.spacing().item_spacing.x);
 | 
			
		||||
                    let pp = if state.player.is_paused() {
 | 
			
		||||
                        crate::data::PLAY_ICON
 | 
			
		||||
                    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -72,15 +106,15 @@ impl CompUi for Player {
 | 
			
		|||
                    let prev = egui::Image::new(crate::data::PREV_ICON)
 | 
			
		||||
                        .tint(theme.accent_color)
 | 
			
		||||
                        .sense(Sense::click())
 | 
			
		||||
                    .max_size(Vec2::new(16.0, 16.0));
 | 
			
		||||
                        .max_size(Vec2::new(icon_size, icon_size));
 | 
			
		||||
                    let pp = egui::Image::new(pp)
 | 
			
		||||
                        .tint(theme.accent_color)
 | 
			
		||||
                        .sense(Sense::click())
 | 
			
		||||
                    .max_size(Vec2::new(16.0, 16.0));
 | 
			
		||||
                        .max_size(Vec2::new(icon_size, icon_size));
 | 
			
		||||
                    let next = egui::Image::new(crate::data::NEXT_ICON)
 | 
			
		||||
                        .tint(theme.accent_color)
 | 
			
		||||
                        .sense(Sense::click())
 | 
			
		||||
                    .max_size(Vec2::new(16.0, 16.0));
 | 
			
		||||
                        .max_size(Vec2::new(icon_size, icon_size));
 | 
			
		||||
                    if ui.add(prev).clicked() {     
 | 
			
		||||
                        handle_error_ui!(handle_error_ui!(SongList::get()).play_prev(state));
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +131,6 @@ impl CompUi for Player {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
 | 
			
		||||
                    ui.add_space(15.0);
 | 
			
		||||
                    ui.style_mut().spacing.slider_width = avail.x * 0.15;
 | 
			
		||||
                    let s = Stroke {
 | 
			
		||||
| 
						 | 
				
			
			@ -118,9 +151,9 @@ impl CompUi for Player {
 | 
			
		|||
                        state.player.set_volume(slf.volume_slider); 
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
                ui.add_space(3.0);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,6 @@
 | 
			
		|||
use egui::{Color32, CursorIcon, RichText, Sense, Vec2};
 | 
			
		||||
use std::borrow::Cow;
 | 
			
		||||
 | 
			
		||||
use egui::{Color32, CursorIcon, ImageSource, RichText, Sense, Vec2};
 | 
			
		||||
use song_list_nav::SearchType;
 | 
			
		||||
use xmpd_cache::DlStatus;
 | 
			
		||||
use xmpd_manifest::{song::Song, store::BaseStore};
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +10,7 @@ pub mod song_list_nav;
 | 
			
		|||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub struct SongList {
 | 
			
		||||
    selected_sid: uuid::Uuid,
 | 
			
		||||
    pub selected_sid: uuid::Uuid,
 | 
			
		||||
    playable_songs: Vec<uuid::Uuid>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -113,11 +115,21 @@ impl SongList {
 | 
			
		|||
        let mut clicked = false;
 | 
			
		||||
        ui.horizontal(|ui| {
 | 
			
		||||
            let theme = handle_error_ui!(xmpd_settings::Settings::get()).theme.clone();
 | 
			
		||||
            // let icon_status = handle_error_ui!(xmpd_cache::Cache::get()).get_cached_icon_status(&sid).clone();
 | 
			
		||||
            let img = ui.add(
 | 
			
		||||
                //if let Some(DlStatus::Done(Some(p))) = icon_status {
 | 
			
		||||
                //    let uri: Cow<str> = Cow::Owned(p.to_string_lossy().to_string());
 | 
			
		||||
                //    let bytes = handle_error_ui!(std::fs::read(p));
 | 
			
		||||
                //    ui.ctx().include_bytes(uri.clone(), bytes);
 | 
			
		||||
                //    egui::Image::new(ImageSource::Uri(uri))
 | 
			
		||||
                //        .sense(Sense::click())
 | 
			
		||||
                //        .fit_to_exact_size(Vec2::new(32.0, 32.0))
 | 
			
		||||
                // } else {
 | 
			
		||||
                    egui::Image::new(crate::data::NOTE_ICON)
 | 
			
		||||
                        .tint(theme.accent_color)
 | 
			
		||||
                        .sense(Sense::click())
 | 
			
		||||
                        .fit_to_exact_size(Vec2::new(32.0, 32.0))
 | 
			
		||||
                //}
 | 
			
		||||
            );
 | 
			
		||||
            let status = {
 | 
			
		||||
                handle_error_ui!(xmpd_cache::Cache::get()).get_cached_song_status(&sid).clone()
 | 
			
		||||
| 
						 | 
				
			
			@ -132,6 +144,7 @@ impl SongList {
 | 
			
		|||
                    ui.output_mut(|o| o.cursor_icon = CursorIcon::Default);
 | 
			
		||||
                }
 | 
			
		||||
            } 
 | 
			
		||||
            // img.context_menu(|ui| handle_error_ui!(Self::show_context_menu(ui, sid, song)));
 | 
			
		||||
 | 
			
		||||
            ui.vertical(|ui| {
 | 
			
		||||
                let slf = handle_error_ui!(SongList::get());
 | 
			
		||||
| 
						 | 
				
			
			@ -157,6 +170,8 @@ impl SongList {
 | 
			
		|||
                        ui.output_mut(|o| o.cursor_icon = CursorIcon::Default);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // label.context_menu(|ui| handle_error_ui!(Self::show_context_menu(ui, sid, song)));
 | 
			
		||||
                ui.monospace(
 | 
			
		||||
                    RichText::new(format!("By {}", song.author()))
 | 
			
		||||
                        .color(theme.dim_text_color)
 | 
			
		||||
| 
						 | 
				
			
			@ -169,7 +184,7 @@ impl SongList {
 | 
			
		|||
                ui.add_space(3.0);
 | 
			
		||||
                
 | 
			
		||||
                match status {
 | 
			
		||||
                    Some(DlStatus::Done(_p)) => {
 | 
			
		||||
                    Some(DlStatus::Done(_)) => {
 | 
			
		||||
                        //let img = egui::Image::new(crate::data::CHECK_ICON)
 | 
			
		||||
                        //    .tint(Color32::LIGHT_GREEN)
 | 
			
		||||
                        //    .sense(Sense::hover())
 | 
			
		||||
| 
						 | 
				
			
			@ -185,7 +200,7 @@ impl SongList {
 | 
			
		|||
                                .size(16.0);
 | 
			
		||||
                        ui.add(spinner);
 | 
			
		||||
                    }
 | 
			
		||||
                    Some(DlStatus::Error(e)) => {
 | 
			
		||||
                    Some(DlStatus::Error(e, ..)) => {
 | 
			
		||||
                        let img = egui::Image::new(crate::data::WARN_ICON)
 | 
			
		||||
                                .tint(Color32::LIGHT_YELLOW)
 | 
			
		||||
                                .sense(Sense::hover())
 | 
			
		||||
| 
						 | 
				
			
			@ -202,7 +217,7 @@ impl SongList {
 | 
			
		|||
                                .fit_to_exact_size(Vec2::new(16.0, 16.0));
 | 
			
		||||
 | 
			
		||||
                        if ui.add(img).clicked() {
 | 
			
		||||
                            handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone());
 | 
			
		||||
                            handle_error_ui!(xmpd_cache::Cache::get()).download_song_to_cache(sid.clone(), song.clone());
 | 
			
		||||
                            let mut toast = handle_error_ui!(crate::components::toast::Toast::get());
 | 
			
		||||
                            toast.show_toast(
 | 
			
		||||
                                "Downloading Song",
 | 
			
		||||
| 
						 | 
				
			
			@ -214,6 +229,7 @@ impl SongList {
 | 
			
		|||
                }
 | 
			
		||||
            });    
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        ui.separator();
 | 
			
		||||
        if clicked {
 | 
			
		||||
            let mut sl = SongList::get()?;
 | 
			
		||||
| 
						 | 
				
			
			@ -268,4 +284,10 @@ impl SongList {
 | 
			
		|||
        }
 | 
			
		||||
        Ok(playable_songs)
 | 
			
		||||
    }
 | 
			
		||||
    //fn show_context_menu(ui: &mut egui::Ui, sid: &uuid::Uuid, song: &Song) -> crate::Result<()> {
 | 
			
		||||
    //    if ui.button("Download icon").clicked() {
 | 
			
		||||
    //        xmpd_cache::Cache::get()?.download_icon_to_cache(sid.clone(), song.clone());
 | 
			
		||||
    //    }
 | 
			
		||||
    //    Ok(())
 | 
			
		||||
    //}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,7 +60,7 @@ impl CompUi for SongListNav {
 | 
			
		|||
                    }
 | 
			
		||||
                    for sid in handle_error_ui!(Self::get_songs_to_download(&songs)) {
 | 
			
		||||
                        if let Some(song) = state.manifest.store().get_song(&sid) {
 | 
			
		||||
                            handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone())
 | 
			
		||||
                            handle_error_ui!(xmpd_cache::Cache::get()).download_song_to_cache(sid.clone(), song.clone())
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    let mut toast = handle_error_ui!(crate::components::toast::Toast::get());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,6 +69,15 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState, cache_rx: &Receiver<Messa
 | 
			
		|||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Message::Error(file, line, e) => {
 | 
			
		||||
                if let Ok(mut toast) = crate::components::toast::Toast::get() {
 | 
			
		||||
                    toast.show_toast(
 | 
			
		||||
                        &format!("Error in {file}:{line}"),
 | 
			
		||||
                        &format!("{e}"),
 | 
			
		||||
                        crate::components::toast::ToastType::Error,
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,12 @@
 | 
			
		|||
use std::{fmt::Display, path::PathBuf, str::FromStr};
 | 
			
		||||
use std::{fmt::Display, str::FromStr};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, PartialOrd, Default)]
 | 
			
		||||
pub enum IconType {
 | 
			
		||||
    CustomUrl(url::Url),
 | 
			
		||||
    FromSource,
 | 
			
		||||
    #[default]
 | 
			
		||||
    None
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, PartialOrd)]
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +15,13 @@ pub struct Song {
 | 
			
		|||
    author: String,
 | 
			
		||||
    url: url::Url,
 | 
			
		||||
    source_type: SourceType,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    icon_type: IconType,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    genres: Vec<String>,
 | 
			
		||||
    /// Seconds
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    length: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Song {
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +30,10 @@ impl Song {
 | 
			
		|||
            name: String::default(),
 | 
			
		||||
            author: String::default(),
 | 
			
		||||
            source_type,
 | 
			
		||||
            url: url.clone()
 | 
			
		||||
            icon_type: IconType::None,
 | 
			
		||||
            url: url.clone(),
 | 
			
		||||
            genres: Vec::new(),
 | 
			
		||||
            length: 0
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    pub fn new_from_str(url: &str, source_type: SourceType) -> crate::Result<Self> {
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +51,15 @@ impl Song {
 | 
			
		|||
    pub fn source_type(&self) -> &SourceType {
 | 
			
		||||
        &self.source_type
 | 
			
		||||
    }
 | 
			
		||||
    pub fn icon_type(&self) -> &IconType {
 | 
			
		||||
        &self.icon_type
 | 
			
		||||
    }
 | 
			
		||||
    pub fn genres(&self) -> &[String] {
 | 
			
		||||
        &self.genres
 | 
			
		||||
    }
 | 
			
		||||
    pub fn length(&self) -> usize {
 | 
			
		||||
        self.length
 | 
			
		||||
    }
 | 
			
		||||
    pub fn set_name(&mut self, v: &str) {
 | 
			
		||||
        self.name = v.to_string();
 | 
			
		||||
    } 
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +72,15 @@ impl Song {
 | 
			
		|||
    pub fn set_source_type(&mut self, v: &SourceType) {
 | 
			
		||||
        self.source_type.clone_from(v);
 | 
			
		||||
    }
 | 
			
		||||
    pub fn set_icon_type(&mut self, v: &IconType) {
 | 
			
		||||
        self.icon_type.clone_from(v);
 | 
			
		||||
    }
 | 
			
		||||
    pub fn genres_mut(&mut self) -> &mut Vec<String> {
 | 
			
		||||
        &mut self.genres
 | 
			
		||||
    }
 | 
			
		||||
    pub fn set_length(&mut self, l: usize) {
 | 
			
		||||
        self.length = l;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, PartialOrd)]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user