Downloading prototype works
This commit is contained in:
		
							parent
							
								
									b29caa58b4
								
							
						
					
					
						commit
						fda77f6981
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,2 +1,4 @@
 | 
			
		|||
/target/
 | 
			
		||||
/cache/
 | 
			
		||||
settings.toml
 | 
			
		||||
valgrind.log
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										42
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										42
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
# This file is automatically @generated by Cargo.
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
version = 4
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ab_glyph"
 | 
			
		||||
| 
						 | 
				
			
			@ -729,6 +729,9 @@ name = "camino"
 | 
			
		|||
version = "1.1.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cc"
 | 
			
		||||
| 
						 | 
				
			
			@ -4376,8 +4379,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "xmpd-cli"
 | 
			
		||||
name = "xmpd-cache"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "camino",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "uuid",
 | 
			
		||||
 "xmpd-cliargs",
 | 
			
		||||
 "xmpd-manifest",
 | 
			
		||||
 "xmpd-settings",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "xmpd-cliargs"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "camino",
 | 
			
		||||
 "clap",
 | 
			
		||||
 "dirs",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "xmpd-core"
 | 
			
		||||
| 
						 | 
				
			
			@ -4386,24 +4409,20 @@ dependencies = [
 | 
			
		|||
 "anyhow",
 | 
			
		||||
 "camino",
 | 
			
		||||
 "clap",
 | 
			
		||||
 "dirs",
 | 
			
		||||
 "env_logger",
 | 
			
		||||
 "log",
 | 
			
		||||
 "xmpd-cli",
 | 
			
		||||
 "xmpd-cliargs",
 | 
			
		||||
 "xmpd-gui",
 | 
			
		||||
 "xmpd-manifest",
 | 
			
		||||
 "xmpd-settings",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "xmpd-dl"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "xmpd-gui"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "camino",
 | 
			
		||||
 "eframe",
 | 
			
		||||
 "egui 0.27.2",
 | 
			
		||||
 "egui-aesthetix",
 | 
			
		||||
| 
						 | 
				
			
			@ -4412,6 +4431,8 @@ dependencies = [
 | 
			
		|||
 "log",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "uuid",
 | 
			
		||||
 "xmpd-cache",
 | 
			
		||||
 "xmpd-cliargs",
 | 
			
		||||
 "xmpd-manifest",
 | 
			
		||||
 "xmpd-settings",
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -4432,12 +4453,17 @@ name = "xmpd-settings"
 | 
			
		|||
version = "2.0.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "camino",
 | 
			
		||||
 "egui 0.27.2",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "toml",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "xmpd-tooling"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zbus"
 | 
			
		||||
version = "3.15.2"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,9 +4,10 @@ members=[
 | 
			
		|||
    "xmpd-core",
 | 
			
		||||
    "xmpd-manifest",
 | 
			
		||||
    "xmpd-gui",
 | 
			
		||||
    #"xmpd-cli",
 | 
			
		||||
    "xmpd-dl",
 | 
			
		||||
    "xmpd-cliargs",
 | 
			
		||||
    "xmpd-cache",
 | 
			
		||||
    "xmpd-settings", 
 | 
			
		||||
    "xmpd-tooling",
 | 
			
		||||
#   "xmpd-tui"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +25,7 @@ authors=[
 | 
			
		|||
anstyle = "1.0.6"
 | 
			
		||||
anyhow = "1.0.81"
 | 
			
		||||
bitflags = { version = "2.6.0", features = ["serde"] }
 | 
			
		||||
camino = "1.1.6"
 | 
			
		||||
camino = { version="1.1.6", features = ["serde1"] }
 | 
			
		||||
clap = { version = "4.5.4", features = ["derive"] }
 | 
			
		||||
eframe = "0.27.2"
 | 
			
		||||
egui = { version = "0.27.2", features = ["color-hex", "serde"] }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								assets/check.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/check.svg
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 179 B  | 
							
								
								
									
										1
									
								
								assets/cross.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/cross.svg
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M256-213.85 213.85-256l224-224-224-224L256-746.15l224 224 224-224L746.15-704l-224 224 224 224L704-213.85l-224-224-224 224Z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 247 B  | 
							
								
								
									
										1
									
								
								assets/download.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/download.svg
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 279 B  | 
							
								
								
									
										1
									
								
								assets/warning.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/warning.svg
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="m40-120 440-760 440 760H40Zm138-80h604L480-720 178-200Zm302-40q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm-40-120h80v-200h-80v200Zm40-100Z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 315 B  | 
							
								
								
									
										2356
									
								
								manifest.json
									
									
									
									
									
								
							
							
						
						
									
										2356
									
								
								manifest.json
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "xmpd-dl"
 | 
			
		||||
name = "xmpd-cache"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
readme="README.md"
 | 
			
		||||
authors.workspace = true
 | 
			
		||||
| 
						 | 
				
			
			@ -18,3 +18,11 @@ crate-type = ["rlib"]
 | 
			
		|||
bench = false
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
xmpd-settings.path = "../xmpd-settings"
 | 
			
		||||
xmpd-manifest.path = "../xmpd-manifest"
 | 
			
		||||
xmpd-cliargs.path = "../xmpd-cliargs"
 | 
			
		||||
anyhow.workspace = true
 | 
			
		||||
camino.workspace = true
 | 
			
		||||
lazy_static.workspace = true
 | 
			
		||||
log.workspace = true
 | 
			
		||||
uuid.workspace = true
 | 
			
		||||
							
								
								
									
										4
									
								
								xmpd-cache/src/downloader/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								xmpd-cache/src/downloader/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
 | 
			
		||||
pub mod song;
 | 
			
		||||
pub mod icon;
 | 
			
		||||
pub mod metadata;
 | 
			
		||||
							
								
								
									
										127
									
								
								xmpd-cache/src/downloader/song.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								xmpd-cache/src/downloader/song.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
use std::{collections::HashMap, ffi::OsStr, process::{Command, Stdio}, sync::{Arc, Mutex, MutexGuard}};
 | 
			
		||||
use xmpd_manifest::song::{Song, SourceType};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lazy_static::lazy_static!(
 | 
			
		||||
    static ref SONG_CACHE_DL: Arc<Mutex<SongCacheDl>> = Arc::default();
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)]
 | 
			
		||||
pub enum SongStatus {
 | 
			
		||||
    Downloading,
 | 
			
		||||
    Converting,
 | 
			
		||||
    Done
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Clone)]
 | 
			
		||||
pub struct SongCacheDl {
 | 
			
		||||
    pub jobs: HashMap<uuid::Uuid, SongStatus>,
 | 
			
		||||
    pub current_jobs: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SongCacheDl {
 | 
			
		||||
    pub fn get() -> crate::Result<MutexGuard<'static, Self>> {
 | 
			
		||||
        match SONG_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<()> {
 | 
			
		||||
        self.current_jobs += 1;
 | 
			
		||||
        let song_format = xmpd_settings::Settings::get().unwrap().tooling.song_format.clone();
 | 
			
		||||
        let tooling = xmpd_settings::Settings::get()?.tooling.clone();
 | 
			
		||||
        let mut song_cache_d = xmpd_cliargs::CLIARGS.cache_path();
 | 
			
		||||
        song_cache_d.push("songs");
 | 
			
		||||
        match song.source_type() {
 | 
			
		||||
            SourceType::Youtube |
 | 
			
		||||
            SourceType::Soundcloud => {
 | 
			
		||||
                let mut song_p = song_cache_d.clone();
 | 
			
		||||
                song_p.push(sid.to_string());
 | 
			
		||||
                let song_p = song_p.with_extension(&song_format);
 | 
			
		||||
 | 
			
		||||
                let mut dl_cmd = Command::new(&tooling.ytdlp_path);
 | 
			
		||||
                dl_cmd.arg(song.url().as_str());
 | 
			
		||||
                dl_cmd.args(["-x", "--audio-format", &song_format]);
 | 
			
		||||
                dl_cmd.arg("-o");
 | 
			
		||||
                dl_cmd.arg(&song_p);
 | 
			
		||||
                
 | 
			
		||||
                if xmpd_cliargs::CLIARGS.debug {
 | 
			
		||||
                    dl_cmd.stdout(Stdio::piped());
 | 
			
		||||
                    dl_cmd.stderr(Stdio::piped());
 | 
			
		||||
                } else {
 | 
			
		||||
                    dl_cmd.stdout(Stdio::null());
 | 
			
		||||
                    dl_cmd.stderr(Stdio::null());
 | 
			
		||||
                }
 | 
			
		||||
                let dl_child = dl_cmd.spawn()?;
 | 
			
		||||
                self.jobs.insert(sid, SongStatus::Downloading);
 | 
			
		||||
                std::thread::spawn(move || {
 | 
			
		||||
                    if let Ok(output) = dl_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 mut cache = SONG_CACHE_DL.lock().unwrap();
 | 
			
		||||
                    cache.jobs.insert(sid, SongStatus::Done);
 | 
			
		||||
                    cache.current_jobs -= 1;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SourceType::Spotify => {
 | 
			
		||||
                // Spotdl doesnt have webm as a format so its fucking annoying, oh well
 | 
			
		||||
                let mut song_p = song_cache_d.clone();
 | 
			
		||||
                song_p.push(sid.to_string());
 | 
			
		||||
                song_p.push("{output-ext}");
 | 
			
		||||
                let mut dl_cmd = Command::new(&tooling.spotdl_path);
 | 
			
		||||
                dl_cmd.arg(song.url().as_str());
 | 
			
		||||
                dl_cmd.arg("--ffmpeg");
 | 
			
		||||
                dl_cmd.arg(&tooling.ffmpeg_path);
 | 
			
		||||
                dl_cmd.args(["--format", &song_format, "--output"]);
 | 
			
		||||
                dl_cmd.arg(&song_p);
 | 
			
		||||
                let arg_str = dl_cmd.get_args();
 | 
			
		||||
                let arg_str: Vec<_> = arg_str.collect();
 | 
			
		||||
                let arg_str = arg_str.join(OsStr::new(" ")).to_string_lossy().to_string();
 | 
			
		||||
                log::debug!("spotify cli: {} {}", tooling.spotdl_path, arg_str);
 | 
			
		||||
                if xmpd_cliargs::CLIARGS.debug {
 | 
			
		||||
                    dl_cmd.stdout(Stdio::piped());
 | 
			
		||||
                    dl_cmd.stderr(Stdio::piped());
 | 
			
		||||
                } else {
 | 
			
		||||
                    dl_cmd.stdout(Stdio::null());
 | 
			
		||||
                    dl_cmd.stderr(Stdio::null());
 | 
			
		||||
                }
 | 
			
		||||
                let child = dl_cmd.spawn()?;
 | 
			
		||||
                self.jobs.insert(sid, SongStatus::Downloading);
 | 
			
		||||
                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 mut from = song_p.clone();
 | 
			
		||||
                    from.pop();
 | 
			
		||||
                    from.push("{song_format}.{song_format}");
 | 
			
		||||
 | 
			
		||||
                    let mut to = song_p.clone();
 | 
			
		||||
                    to.pop();
 | 
			
		||||
                    to.set_extension(&song_format);
 | 
			
		||||
                    std::fs::copy(&from, &to).unwrap();
 | 
			
		||||
                    from.pop();
 | 
			
		||||
                    std::fs::remove_dir_all(from).unwrap();
 | 
			
		||||
                    let mut cache = SONG_CACHE_DL.lock().unwrap();
 | 
			
		||||
                    cache.jobs.insert(sid, SongStatus::Done);
 | 
			
		||||
                    cache.current_jobs -= 1;
 | 
			
		||||
                });
 | 
			
		||||
            } 
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								xmpd-cache/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								xmpd-cache/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,122 @@
 | 
			
		|||
use std::{collections::HashMap, str::FromStr, sync::{Arc, Mutex, MutexGuard}, time::Duration};
 | 
			
		||||
use downloader::song::SongStatus;
 | 
			
		||||
use xmpd_manifest::song::Song;
 | 
			
		||||
 | 
			
		||||
pub mod downloader;
 | 
			
		||||
 | 
			
		||||
type Result<T> = anyhow::Result<T>;
 | 
			
		||||
 | 
			
		||||
lazy_static::lazy_static!(
 | 
			
		||||
    static ref CACHE: Arc<Mutex<Cache>> = Arc::new(Mutex::new(Cache::default()));
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub struct Cache {
 | 
			
		||||
    cache_dir: camino::Utf8PathBuf,
 | 
			
		||||
    song_cache: HashMap<uuid::Uuid, DlStatus>,
 | 
			
		||||
    queue: Vec<(uuid::Uuid, Song)>
 | 
			
		||||
    // TODO: Add Icon, metadata cache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum DlStatus {
 | 
			
		||||
    Done(camino::Utf8PathBuf),
 | 
			
		||||
    Downloading,
 | 
			
		||||
    Error(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cache {
 | 
			
		||||
    pub fn get() -> crate::Result<MutexGuard<'static, Self>> {
 | 
			
		||||
        match CACHE.lock() {
 | 
			
		||||
            Ok(l) => Ok(l),
 | 
			
		||||
            Err(e) => Err(anyhow::anyhow!(format!("{e:?}"))),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn init(&mut self) -> Result<()> {
 | 
			
		||||
        start_cache_mv_thread();
 | 
			
		||||
        self.cache_dir = xmpd_cliargs::CLIARGS.cache_path();
 | 
			
		||||
        
 | 
			
		||||
        { // Get cached songs
 | 
			
		||||
            let mut song_cache_dir = self.cache_dir.clone();
 | 
			
		||||
            song_cache_dir.push("songs");
 | 
			
		||||
            for file in song_cache_dir.read_dir_utf8()? {
 | 
			
		||||
                if let Ok(file) = file {
 | 
			
		||||
                    if !file.file_type()?.is_file() {
 | 
			
		||||
                        log::warn!("Non song file in: {}", file.path());
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    let file_path = file.path();
 | 
			
		||||
                    let file2 = file_path.with_extension("");
 | 
			
		||||
                    if let Some(file_name) = file2.file_name() {
 | 
			
		||||
                        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()));
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        { // Get cached icons
 | 
			
		||||
        }
 | 
			
		||||
        { // Get Cached meta
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn download_to_cache(&mut self, sid: uuid::Uuid, song: Song) {
 | 
			
		||||
        self.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 get_cached_song_status(&mut self, sid: &uuid::Uuid) -> Option<DlStatus> {
 | 
			
		||||
        Some(self.song_cache.get(sid)?.clone())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn start_cache_mv_thread() {
 | 
			
		||||
    std::thread::spawn(|| {
 | 
			
		||||
        loop {
 | 
			
		||||
            {
 | 
			
		||||
                std::thread::sleep(Duration::from_millis(500));
 | 
			
		||||
                let song_format = xmpd_settings::Settings::get().unwrap().tooling.song_format.clone();
 | 
			
		||||
                let mut done_jobs = Vec::new();
 | 
			
		||||
                let mut dlc = downloader::song::SongCacheDl::get().unwrap();
 | 
			
		||||
                for (sid, status) in &dlc.jobs {
 | 
			
		||||
                    if *status == SongStatus::Done {
 | 
			
		||||
                        let mut cache = CACHE.lock().unwrap();
 | 
			
		||||
                        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);
 | 
			
		||||
                        log::debug!("Found done: {:?}: {}", song_p, song_p.exists());
 | 
			
		||||
                        if song_p.exists() {
 | 
			
		||||
                            cache.song_cache.insert(sid.clone(), DlStatus::Done(song_p)); 
 | 
			
		||||
                            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();
 | 
			
		||||
                if !dlc.is_job_list_full() {
 | 
			
		||||
                    if let Some((sid, song)) = cache.queue.pop() {
 | 
			
		||||
                        dlc.download(sid, song).unwrap();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +0,0 @@
 | 
			
		|||
pub fn test() {
 | 
			
		||||
    println!("Hello, world!");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "xmpd-cli"
 | 
			
		||||
name = "xmpd-cliargs"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
readme="README.md"
 | 
			
		||||
version.workspace = true
 | 
			
		||||
| 
						 | 
				
			
			@ -18,3 +18,7 @@ crate-type = ["rlib"]
 | 
			
		|||
bench = false
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
camino.workspace = true
 | 
			
		||||
clap.workspace = true
 | 
			
		||||
dirs.workspace = true
 | 
			
		||||
lazy_static.workspace = true
 | 
			
		||||
							
								
								
									
										0
									
								
								xmpd-cliargs/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								xmpd-cliargs/README.md
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -1,5 +1,11 @@
 | 
			
		|||
use std::{path::PathBuf, str::FromStr};
 | 
			
		||||
use std::{path::PathBuf, str::FromStr, sync::Arc};
 | 
			
		||||
 | 
			
		||||
use camino::Utf8PathBuf;
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
 | 
			
		||||
lazy_static::lazy_static!(
 | 
			
		||||
    pub static ref CLIARGS: Arc<CliArgs> = Arc::new(CliArgs::parse());
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, clap::Parser)]
 | 
			
		||||
pub struct CliArgs {
 | 
			
		||||
| 
						 | 
				
			
			@ -24,8 +30,8 @@ impl CliArgs {
 | 
			
		|||
    pub fn settings_path(&self) -> PathBuf {
 | 
			
		||||
        self.settings.clone().into_std_path_buf()
 | 
			
		||||
    }
 | 
			
		||||
    pub fn cache_path(&self) -> PathBuf {
 | 
			
		||||
        self.cache.clone().into_std_path_buf()
 | 
			
		||||
    pub fn cache_path(&self) -> Utf8PathBuf {
 | 
			
		||||
        self.cache.clone()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ name="xmpd"
 | 
			
		|||
path="src/main.rs"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
xmpd-cli.path="../xmpd-cli"
 | 
			
		||||
xmpd-cliargs.path="../xmpd-cliargs"
 | 
			
		||||
xmpd-gui.path="../xmpd-gui"
 | 
			
		||||
xmpd-manifest.path="../xmpd-manifest"
 | 
			
		||||
xmpd-settings.path = "../xmpd-settings"
 | 
			
		||||
| 
						 | 
				
			
			@ -30,4 +30,3 @@ camino.workspace = true
 | 
			
		|||
anyhow.workspace = true
 | 
			
		||||
log.workspace = true
 | 
			
		||||
env_logger.workspace = true
 | 
			
		||||
dirs.workspace = true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,5 @@
 | 
			
		|||
use log::LevelFilter;
 | 
			
		||||
 | 
			
		||||
use crate::cli::CliArgs;
 | 
			
		||||
 | 
			
		||||
use xmpd_cliargs::CliArgs;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pub fn init(cliargs: &CliArgs) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +1,18 @@
 | 
			
		|||
use std::borrow::BorrowMut;
 | 
			
		||||
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
 | 
			
		||||
mod cli;
 | 
			
		||||
mod logger;
 | 
			
		||||
 | 
			
		||||
type Result<T> = anyhow::Result<T>;
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<()> {
 | 
			
		||||
    let cliargs = cli::CliArgs::parse();
 | 
			
		||||
    // NOTE: Parses on first load
 | 
			
		||||
    let cliargs = &xmpd_cliargs::CLIARGS;
 | 
			
		||||
    logger::init(&cliargs);
 | 
			
		||||
    log::debug!("Cli: {cliargs:?}");
 | 
			
		||||
    log::debug!("Initialising settings");
 | 
			
		||||
    xmpd_settings::Settings::get()?.load(Some(cliargs.settings_path()))?;
 | 
			
		||||
    xmpd_gui::start(cliargs.manifest_path())?;
 | 
			
		||||
    log::debug!("Starting gui");
 | 
			
		||||
    xmpd_gui::start()?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,8 @@ bench = false
 | 
			
		|||
[dependencies]
 | 
			
		||||
xmpd-manifest.path = "../xmpd-manifest"
 | 
			
		||||
xmpd-settings.path = "../xmpd-settings"
 | 
			
		||||
xmpd-cliargs.path = "../xmpd-cliargs"
 | 
			
		||||
xmpd-cache.path = "../xmpd-cache"
 | 
			
		||||
egui.workspace = true
 | 
			
		||||
eframe.workspace = true
 | 
			
		||||
tokio.workspace = true
 | 
			
		||||
| 
						 | 
				
			
			@ -29,3 +31,4 @@ log.workspace = true
 | 
			
		|||
egui_extras.workspace = true
 | 
			
		||||
egui-aesthetix = "0.2.4"
 | 
			
		||||
uuid.workspace = true
 | 
			
		||||
camino.workspace = true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
use egui::{RichText, Vec2};
 | 
			
		||||
use std::fmt::write;
 | 
			
		||||
 | 
			
		||||
use egui::{Color32, RichText, Sense, Vec2};
 | 
			
		||||
use song_list_nav::SearchType;
 | 
			
		||||
use xmpd_cache::DlStatus;
 | 
			
		||||
use xmpd_manifest::{song::Song, store::BaseStore};
 | 
			
		||||
use super::{CompGetter, CompUi};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,34 +83,92 @@ impl CompUi for SongList {
 | 
			
		|||
 | 
			
		||||
fn display_song_tab(ui: &mut egui::Ui, sid: &uuid::Uuid, song: &Song) {
 | 
			
		||||
    let theme = handle_error_ui!(xmpd_settings::Settings::get()).theme.clone();
 | 
			
		||||
    let rct = ui.horizontal(|ui| {
 | 
			
		||||
        ui.add(
 | 
			
		||||
    ui.horizontal(|ui| {
 | 
			
		||||
        let mut clicked = ui.add(
 | 
			
		||||
            egui::Image::new(crate::data::NOTE_ICON)
 | 
			
		||||
                .tint(theme.accent_color)
 | 
			
		||||
                .sense(Sense::click())
 | 
			
		||||
                .fit_to_exact_size(Vec2::new(32.0, 32.0))
 | 
			
		||||
        );
 | 
			
		||||
        ).clicked();
 | 
			
		||||
 | 
			
		||||
        ui.vertical(|ui| {
 | 
			
		||||
            let selected_song_id = {handle_error_ui!(SongList::get()).selected_song_id};
 | 
			
		||||
            if selected_song_id == *sid {
 | 
			
		||||
            let label = if selected_song_id == *sid {
 | 
			
		||||
                ui.label(
 | 
			
		||||
                    RichText::new(song.name())
 | 
			
		||||
                        .color(theme.accent_color)
 | 
			
		||||
                );
 | 
			
		||||
                )
 | 
			
		||||
            } else {
 | 
			
		||||
                ui.label(
 | 
			
		||||
                    RichText::new(song.name())
 | 
			
		||||
                        .color(theme.text_color)
 | 
			
		||||
                );
 | 
			
		||||
                )
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if label.clicked() {
 | 
			
		||||
                clicked = true;
 | 
			
		||||
            }
 | 
			
		||||
            ui.monospace(
 | 
			
		||||
                RichText::new(format!("By {}", song.author()))
 | 
			
		||||
                    .color(theme.dim_text_color)
 | 
			
		||||
                    .size(10.0)
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }).response.rect;
 | 
			
		||||
    if ui.interact(rct, format!("song_list_{sid:?}").into(), egui::Sense::click()).clicked() {
 | 
			
		||||
        
 | 
			
		||||
        ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
 | 
			
		||||
            ui.add_space(3.0);
 | 
			
		||||
            let status = {
 | 
			
		||||
                handle_error_ui!(xmpd_cache::Cache::get()).get_cached_song_status(&sid).clone()
 | 
			
		||||
            };
 | 
			
		||||
            match status {
 | 
			
		||||
                Some(DlStatus::Done(p)) => {
 | 
			
		||||
                    let img = ui.add(
 | 
			
		||||
                        egui::Image::new(crate::data::CHECK_ICON)
 | 
			
		||||
                            .tint(Color32::LIGHT_GREEN)
 | 
			
		||||
                            .sense(Sense::hover())
 | 
			
		||||
                            .fit_to_exact_size(Vec2::new(16.0, 16.0))
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    img.on_hover_ui(|ui| {
 | 
			
		||||
                        ui.label(format!("Path: {p}"));
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                Some(DlStatus::Downloading) => {
 | 
			
		||||
                    ui.add(
 | 
			
		||||
                        egui::Spinner::new()
 | 
			
		||||
                            .color(theme.accent_color)
 | 
			
		||||
                            .size(16.0)
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                Some(DlStatus::Error(e)) => {
 | 
			
		||||
                    let img = ui.add(
 | 
			
		||||
                        egui::Image::new(crate::data::WARN_ICON)
 | 
			
		||||
                            .tint(Color32::LIGHT_YELLOW)
 | 
			
		||||
                            .sense(Sense::hover())
 | 
			
		||||
                            .fit_to_exact_size(Vec2::new(16.0, 16.0))
 | 
			
		||||
                    );
 | 
			
		||||
                    img.on_hover_ui(|ui| {
 | 
			
		||||
                        ui.label(e);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                None => {
 | 
			
		||||
                    let img = ui.add(
 | 
			
		||||
                        egui::Image::new(crate::data::DL_ICON)
 | 
			
		||||
                            .tint(theme.accent_color)
 | 
			
		||||
                            .sense(Sense::click())
 | 
			
		||||
                            .fit_to_exact_size(Vec2::new(16.0, 16.0))
 | 
			
		||||
                    );
 | 
			
		||||
                    if img.clicked() {
 | 
			
		||||
                        handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone())
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        if clicked {
 | 
			
		||||
            handle_error_ui!(SongList::get()).selected_song_id = sid.clone();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    ui.separator();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,7 @@
 | 
			
		|||
use crate::components::{CompGetter, CompUi};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
use xmpd_manifest::store::BaseStore;
 | 
			
		||||
 | 
			
		||||
use crate::components::{left_nav::LeftNav, CompGetter, CompUi};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum SearchType {
 | 
			
		||||
| 
						 | 
				
			
			@ -9,14 +12,16 @@ pub enum SearchType {
 | 
			
		|||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub struct SongListNav {
 | 
			
		||||
    text: String
 | 
			
		||||
    text: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
component_register!(SongListNav);
 | 
			
		||||
 | 
			
		||||
impl CompUi for SongListNav {
 | 
			
		||||
    fn draw(ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
 | 
			
		||||
    fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
 | 
			
		||||
        let theme = xmpd_settings::Settings::get()?.theme.clone();
 | 
			
		||||
        let pid = {LeftNav::get()?.selected_playlist_id.clone()};
 | 
			
		||||
 | 
			
		||||
        ui.horizontal(|ui| {
 | 
			
		||||
            let search_icon = egui::Image::new(crate::data::SEARCH_ICON)
 | 
			
		||||
                .fit_to_exact_size(egui::Vec2::new(16.0, 16.0))
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +30,31 @@ impl CompUi for SongListNav {
 | 
			
		|||
            {
 | 
			
		||||
                ui.text_edit_singleline(&mut handle_error_ui!(SongListNav::get()).text);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
 | 
			
		||||
                let img = ui.add(
 | 
			
		||||
                    egui::Image::new(crate::data::DL_ICON)
 | 
			
		||||
                        .tint(theme.accent_color)
 | 
			
		||||
                        .sense(egui::Sense::click())
 | 
			
		||||
                        .fit_to_exact_size(egui::Vec2::new(16.0, 16.0))
 | 
			
		||||
                );
 | 
			
		||||
                if img.clicked() {
 | 
			
		||||
                    let songs: Vec<_>;
 | 
			
		||||
                    match pid {
 | 
			
		||||
                        Some(pid) => {
 | 
			
		||||
                            songs = state.manifest.store().get_playlist(&pid).unwrap().songs().to_vec();
 | 
			
		||||
                        }
 | 
			
		||||
                        None => {
 | 
			
		||||
                            songs = state.manifest.store().get_songs().keys().cloned().collect();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    for sid in &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())
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        Ok(()) 
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,16 +14,19 @@ impl CompUi for TopNav {
 | 
			
		|||
                ui.menu_button("File", |ui| {
 | 
			
		||||
                    if ui.button("Settings").clicked() {
 | 
			
		||||
                        state.windows.toggle(&WindowId::Settings, true);
 | 
			
		||||
                        ui.close_menu();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                ui.menu_button("Manifest", |ui| {
 | 
			
		||||
                    if ui.button("Save").clicked() {
 | 
			
		||||
                        handle_error_ui!(state.manifest.save());
 | 
			
		||||
                        ui.close_menu();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                ui.menu_button("Help", |ui| {
 | 
			
		||||
                    if ui.button("Source").clicked() {
 | 
			
		||||
                        ui.ctx().open_url(egui::OpenUrl::new_tab("https://git.mcorangehq.xyz/XOR64/music"));
 | 
			
		||||
                        ui.close_menu();
 | 
			
		||||
                    }
 | 
			
		||||
        
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,3 +6,6 @@ pub const PREV_ICON: egui::ImageSource  = egui::include_image!("../../assets/pre
 | 
			
		|||
pub const NEXT_ICON: egui::ImageSource  = egui::include_image!("../../assets/next.svg");
 | 
			
		||||
pub const PLAY_ICON: egui::ImageSource  = egui::include_image!("../../assets/play.svg");
 | 
			
		||||
pub const PAUSE_ICON: egui::ImageSource  = egui::include_image!("../../assets/pause.svg");
 | 
			
		||||
pub const CHECK_ICON: egui::ImageSource  = egui::include_image!("../../assets/check.svg");
 | 
			
		||||
pub const DL_ICON: egui::ImageSource  = egui::include_image!("../../assets/download.svg");
 | 
			
		||||
pub const WARN_ICON: egui::ImageSource  = egui::include_image!("../../assets/warning.svg");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
#![feature(async_closure)]
 | 
			
		||||
 | 
			
		||||
use std::{path::{Path, PathBuf}, time::Duration};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
use xmpd_manifest::{store::JsonStore, Manifest};
 | 
			
		||||
 | 
			
		||||
#[macro_use]
 | 
			
		||||
| 
						 | 
				
			
			@ -15,10 +15,10 @@ const W_NAME: &str = "xmpd v2.0.0a";
 | 
			
		|||
 | 
			
		||||
type Result<T> = anyhow::Result<T>;
 | 
			
		||||
 | 
			
		||||
pub fn start(manifest_path: PathBuf) -> Result<()> {
 | 
			
		||||
pub fn start() -> Result<()> {
 | 
			
		||||
    xmpd_cache::Cache::get()?.init();
 | 
			
		||||
    let options = eframe::NativeOptions::default();
 | 
			
		||||
    let mut state = GuiState::new(&manifest_path)?;
 | 
			
		||||
    let theme = xmpd_settings::Settings::get()?.theme.clone(); 
 | 
			
		||||
    let mut state = GuiState::new()?;
 | 
			
		||||
    let res = eframe::run_simple_native(W_NAME, options, move |ctx, _frame| {
 | 
			
		||||
        egui_extras::install_image_loaders(ctx);
 | 
			
		||||
        state.windows.clone().draw_all(ctx, &mut state); 
 | 
			
		||||
| 
						 | 
				
			
			@ -31,20 +31,15 @@ pub fn start(manifest_path: PathBuf) -> Result<()> {
 | 
			
		|||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pub enum Message {
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct GuiState {
 | 
			
		||||
    pub manifest: Manifest<JsonStore>,
 | 
			
		||||
    pub windows: windows::Windows
 | 
			
		||||
    pub windows: windows::Windows,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GuiState {
 | 
			
		||||
    pub fn new(manifest_path: &Path) -> Result<Self> {
 | 
			
		||||
    pub fn new() -> Result<Self> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            manifest: Manifest::new(manifest_path)?,
 | 
			
		||||
            manifest: Manifest::new(&xmpd_cliargs::CLIARGS.manifest_path())?,
 | 
			
		||||
            windows: windows::Windows::new(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
use xmpd_settings::theme::Theme;
 | 
			
		||||
 | 
			
		||||
use crate::{components::CompUi, GuiState};
 | 
			
		||||
use crate::{components::{song_list, CompUi}, GuiState};
 | 
			
		||||
 | 
			
		||||
pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
 | 
			
		||||
    // The central panel the region left after adding TopPanel's and SidePanel's
 | 
			
		||||
| 
						 | 
				
			
			@ -22,19 +22,22 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
 | 
			
		|||
                let avail = ui.available_size();
 | 
			
		||||
                let main_height = avail.y * 0.91;
 | 
			
		||||
 | 
			
		||||
                let left_nav_width = (avail.x * 0.25).clamp(0.0, 200.0);
 | 
			
		||||
                let song_list_width = (avail.x - left_nav_width - 35.0);
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.set_height(main_height);  
 | 
			
		||||
                    ui.group(|ui| {
 | 
			
		||||
                        ui.set_height(main_height);
 | 
			
		||||
                        ui.set_max_width((avail.x * 0.25).clamp(0.0, 200.0));
 | 
			
		||||
                        ui.set_max_width(left_nav_width);
 | 
			
		||||
                        handle_error_ui!(crate::components::left_nav::LeftNav::draw(ui, state));
 | 
			
		||||
                    });
 | 
			
		||||
                    ui.vertical(|ui| {
 | 
			
		||||
                        ui.group(|ui| {
 | 
			
		||||
                            ui.set_width(avail.x * 0.75);
 | 
			
		||||
                            ui.set_width(song_list_width);
 | 
			
		||||
                            handle_error_ui!(crate::components::song_list::song_list_nav::SongListNav::draw(ui, state));
 | 
			
		||||
                        });
 | 
			
		||||
                        ui.group(|ui| {
 | 
			
		||||
                            ui.set_width(song_list_width);
 | 
			
		||||
                            handle_error_ui!(crate::components::song_list::SongList::draw(ui, state));
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +61,7 @@ fn get_themed_frame(theme: &Theme) -> egui::Frame {
 | 
			
		|||
    egui::Frame::none()
 | 
			
		||||
        .fill(theme.primary_bg_color)
 | 
			
		||||
        .stroke(egui::Stroke::new(
 | 
			
		||||
            1.0,
 | 
			
		||||
            5.0,
 | 
			
		||||
            theme.secondary_bg_color,
 | 
			
		||||
        ))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,32 +1,37 @@
 | 
			
		|||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
use super::Window;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct SettingsW {
 | 
			
		||||
    accent_color: egui::Color32,
 | 
			
		||||
    primary_bg_color: egui::Color32,
 | 
			
		||||
    secondary_bg_color: egui::Color32,
 | 
			
		||||
    text_color: egui::Color32,
 | 
			
		||||
    dim_text_color: egui::Color32,
 | 
			
		||||
    ytdlp_p: String,
 | 
			
		||||
    spotdl_p: String,
 | 
			
		||||
    ffmpeg_p: String,
 | 
			
		||||
    song_fmt: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for SettingsW {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        let def = xmpd_settings::theme::Theme::default();  
 | 
			
		||||
        let tooling = xmpd_settings::Settings::get().unwrap().tooling.clone();
 | 
			
		||||
        Self {
 | 
			
		||||
            accent_color: def.accent_color,
 | 
			
		||||
            primary_bg_color: def.primary_bg_color, 
 | 
			
		||||
            secondary_bg_color: def.secondary_bg_color, 
 | 
			
		||||
            text_color: def.text_color, 
 | 
			
		||||
            dim_text_color: def.dim_text_color 
 | 
			
		||||
            ytdlp_p: tooling.ytdlp_path.to_string(),
 | 
			
		||||
            spotdl_p: tooling.spotdl_path.to_string(),
 | 
			
		||||
            ffmpeg_p: tooling.ffmpeg_path.to_string(),
 | 
			
		||||
            song_fmt: tooling.song_format
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Window for SettingsW {
 | 
			
		||||
    #[allow(irrefutable_let_patterns)]
 | 
			
		||||
    fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
 | 
			
		||||
        let theme = &mut xmpd_settings::Settings::get()?.theme;
 | 
			
		||||
        ui.group(|ui| {
 | 
			
		||||
            ui.vertical(|ui| {
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    {
 | 
			
		||||
                        let theme = &mut handle_error_ui!(xmpd_settings::Settings::get()).theme;
 | 
			
		||||
                        ui.group(|ui| {
 | 
			
		||||
                            ui.vertical(|ui| {
 | 
			
		||||
                                ui.heading("Theme");
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +40,41 @@ impl Window for SettingsW {
 | 
			
		|||
                                Self::add_theme_button(&mut theme.secondary_bg_color, ui, "Secondary BG");
 | 
			
		||||
                                Self::add_theme_button(&mut theme.text_color, ui, "Text");
 | 
			
		||||
                                Self::add_theme_button(&mut theme.dim_text_color, ui, "Dim Text");
 | 
			
		||||
                                if ui.button("Reset").clicked() {
 | 
			
		||||
                                    *theme = xmpd_settings::theme::Theme::default();
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                        
 | 
			
		||||
                    }
 | 
			
		||||
                    {   
 | 
			
		||||
                        ui.group(|ui| {
 | 
			
		||||
                            ui.vertical(|ui| {
 | 
			
		||||
                                ui.heading("Tooling paths");
 | 
			
		||||
                                Self::add_tooling_input(&mut self.ytdlp_p, ui, "stdlp");
 | 
			
		||||
                                Self::add_tooling_input(&mut self.spotdl_p, ui, "spotdl");
 | 
			
		||||
                                Self::add_tooling_input(&mut self.ffmpeg_p, ui, "ffmpeg");
 | 
			
		||||
                                Self::add_tooling_input(&mut self.song_fmt, ui, "Format");
 | 
			
		||||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                ui.with_layout(egui::Layout::bottom_up(egui::Align::RIGHT), |ui| {
 | 
			
		||||
                    if ui.button("Save").clicked() {
 | 
			
		||||
                        let mut settings = handle_error_ui!(xmpd_settings::Settings::get());
 | 
			
		||||
                        if let Ok(p) = camino::Utf8PathBuf::from_str(&self.ytdlp_p) {
 | 
			
		||||
                            settings.tooling.ytdlp_path = p;
 | 
			
		||||
                        }
 | 
			
		||||
                        if let Ok(p) = camino::Utf8PathBuf::from_str(&self.spotdl_p) {
 | 
			
		||||
                            settings.tooling.spotdl_path = p;
 | 
			
		||||
                        }
 | 
			
		||||
                        if let Ok(p) = camino::Utf8PathBuf::from_str(&self.ffmpeg_p) {
 | 
			
		||||
                            settings.tooling.ffmpeg_path = p;
 | 
			
		||||
                        }
 | 
			
		||||
                        settings.tooling.song_format.clone_from(&self.song_fmt);
 | 
			
		||||
                        handle_error_ui!(settings.save(None));
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        Ok(())
 | 
			
		||||
| 
						 | 
				
			
			@ -48,4 +88,10 @@ impl SettingsW {
 | 
			
		|||
            ui.color_edit_button_srgba(rf);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    fn add_tooling_input(inp: &mut String, ui: &mut egui::Ui, name: &str) {
 | 
			
		||||
        ui.horizontal(|ui|{
 | 
			
		||||
            ui.label(format!("{name}: "));
 | 
			
		||||
            ui.text_edit_singleline(inp);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ authors.workspace = true
 | 
			
		|||
 | 
			
		||||
[dependencies]
 | 
			
		||||
anyhow.workspace = true
 | 
			
		||||
camino.workspace = true
 | 
			
		||||
egui.workspace = true
 | 
			
		||||
lazy_static.workspace = true
 | 
			
		||||
serde.workspace = true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,10 @@
 | 
			
		|||
use std::{path::PathBuf, sync::{Arc, Mutex, MutexGuard}};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use theme::Theme;
 | 
			
		||||
use tooling::Tooling;
 | 
			
		||||
 | 
			
		||||
pub mod theme;
 | 
			
		||||
pub mod tooling;
 | 
			
		||||
 | 
			
		||||
lazy_static::lazy_static!(
 | 
			
		||||
    static ref SETTINGS: Arc<Mutex<Settings>> = Arc::new(Mutex::new(Settings::default()));
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +16,10 @@ pub type Result<T> = anyhow::Result<T>;
 | 
			
		|||
pub struct Settings {
 | 
			
		||||
    #[serde(skip)]
 | 
			
		||||
    settings_path: PathBuf,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub theme: Theme,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub tooling: Tooling,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Settings {
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +32,11 @@ impl Settings {
 | 
			
		|||
    pub fn load(&mut self, path: Option<PathBuf>) -> Result<()> {
 | 
			
		||||
        let path = path.unwrap_or(self.settings_path.clone());
 | 
			
		||||
        if !path.exists() {
 | 
			
		||||
            let mut dir = path.clone();
 | 
			
		||||
            dir.pop();
 | 
			
		||||
            if !dir.exists() {
 | 
			
		||||
                std::fs::create_dir_all(dir)?;
 | 
			
		||||
            }
 | 
			
		||||
            std::fs::write(&path, "[theme]")?;
 | 
			
		||||
            self.save(Some(path.clone()))?;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										42
									
								
								xmpd-settings/src/tooling.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								xmpd-settings/src/tooling.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Tooling {
 | 
			
		||||
    #[serde(default="Tooling::default_ytdlp_path")]
 | 
			
		||||
    pub ytdlp_path: camino::Utf8PathBuf,
 | 
			
		||||
    #[serde(default="Tooling::default_spotdl_path")]
 | 
			
		||||
    pub spotdl_path: camino::Utf8PathBuf,
 | 
			
		||||
    #[serde(default="Tooling::default_ffmpeg_path")]
 | 
			
		||||
    pub ffmpeg_path: camino::Utf8PathBuf,
 | 
			
		||||
    #[serde(default="Tooling::default_song_format")]
 | 
			
		||||
    pub song_format: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Tooling {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            ytdlp_path: Self::default_ytdlp_path(),
 | 
			
		||||
            spotdl_path: Self::default_spotdl_path(),
 | 
			
		||||
            ffmpeg_path: Self::default_ffmpeg_path(),
 | 
			
		||||
            song_format: Self::default_song_format(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Tooling {
 | 
			
		||||
    fn default_ytdlp_path() -> camino::Utf8PathBuf {
 | 
			
		||||
        camino::Utf8PathBuf::from_str("yt-dlp").unwrap()
 | 
			
		||||
    }
 | 
			
		||||
    fn default_spotdl_path() -> camino::Utf8PathBuf {
 | 
			
		||||
        camino::Utf8PathBuf::from_str("spotdl").unwrap()
 | 
			
		||||
    }
 | 
			
		||||
    fn default_ffmpeg_path() -> camino::Utf8PathBuf {
 | 
			
		||||
        camino::Utf8PathBuf::from_str("ffmpeg").unwrap()
 | 
			
		||||
    }
 | 
			
		||||
    fn default_song_format() -> String {
 | 
			
		||||
        String::from("flac")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								xmpd-tooling/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								xmpd-tooling/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "xmpd-tooling"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
version.workspace = true
 | 
			
		||||
repository.workspace = true
 | 
			
		||||
license.workspace = true
 | 
			
		||||
authors.workspace = true
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
							
								
								
									
										0
									
								
								xmpd-tooling/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								xmpd-tooling/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user