refractor
This commit is contained in:
		
							parent
							
								
									b05a20d724
								
							
						
					
					
						commit
						3cad0b0651
					
				
							
								
								
									
										3629
									
								
								music_mgr/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3629
									
								
								music_mgr/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -1,22 +1,24 @@
 | 
				
			||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "music_mgr"
 | 
					name = "music_mgr"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
edition = "2021"
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
anstyle = "1.0.6"
 | 
					anstyle = "1.0.6"
 | 
				
			||||||
anyhow = "1.0.81"
 | 
					anyhow = "1.0.81"
 | 
				
			||||||
camino = "1.1.6"
 | 
					camino = "1.1.6"
 | 
				
			||||||
clap = { version = "4.5.4", features = ["derive"] }
 | 
					clap = { version = "4.5.4", features = ["derive"] }
 | 
				
			||||||
env_logger = "0.11.3"
 | 
					env_logger = "0.11.3"
 | 
				
			||||||
lazy_static = "1.4.0"
 | 
					lazy_static = "1.4.0"
 | 
				
			||||||
libc = "0.2.153"
 | 
					libc = "0.2.153"
 | 
				
			||||||
log = "0.4.21"
 | 
					log = "0.4.21"
 | 
				
			||||||
reqwest = "0.12.3"
 | 
					reqwest = "0.12.3"
 | 
				
			||||||
serde = { version = "1.0.197", features = ["derive"] }
 | 
					serde = { version = "1.0.197", features = ["derive"] }
 | 
				
			||||||
serde_json = "1.0.115"
 | 
					serde_json = "1.0.115"
 | 
				
			||||||
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "process", "sync"] }
 | 
					# serde_traitobject = "0.2.8"
 | 
				
			||||||
windows = { version = "0.56.0", features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_Console"] }
 | 
					tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "process", "sync"] }
 | 
				
			||||||
zip-extensions = "0.6.2"
 | 
					url = "2.5.0"
 | 
				
			||||||
 | 
					windows = { version = "0.56.0", features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_Console"] }
 | 
				
			||||||
 | 
					zip-extensions = "0.6.2"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,6 @@
 | 
				
			||||||
use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{Manifest, ManifestSong}};
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{Manifest, ManifestSong}, util::is_supported_host};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +25,12 @@ pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut
 | 
				
			||||||
        crate::prompt::simple_prompt("Enter song youtube url, make sure its not a playlist, (yt only for now)")
 | 
					        crate::prompt::simple_prompt("Enter song youtube url, make sure its not a playlist, (yt only for now)")
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !is_supported_host(url::Url::from_str(&url)?) {
 | 
				
			||||||
 | 
					        log::error!("Invalid or unsupported host name");
 | 
				
			||||||
 | 
					        return Ok(());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let name = name.clone().unwrap_or_else( ||
 | 
					    let name = name.clone().unwrap_or_else( ||
 | 
				
			||||||
        crate::prompt::simple_prompt("Enter song name with like this: {Author} - {Song name}")
 | 
					        crate::prompt::simple_prompt("Enter song name with like this: {Author} - {Song name}")
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
use std::{collections::HashMap, path::PathBuf, process::Stdio};
 | 
					use std::{collections::HashMap, path::PathBuf, process::Stdio, str::FromStr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use lazy_static::lazy_static;
 | 
					use lazy_static::lazy_static;
 | 
				
			||||||
use log::Level;
 | 
					use log::Level;
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@ impl Downloader {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (genre, songs) in &manifest.genres {
 | 
					        for (genre, songs) in &manifest.genres {
 | 
				
			||||||
            for song in songs {
 | 
					            for song in songs {
 | 
				
			||||||
                self.download_song(cfg, &song, &genre, &format).await?;
 | 
					                self.download_song(cfg, song, &genre, &format).await?;
 | 
				
			||||||
                self.wait_for_procs(10).await?;
 | 
					                self.wait_for_procs(10).await?;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -45,39 +45,54 @@ impl Downloader {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    pub async fn download_song(&mut self, cfg: &ConfigWrapper, song: &ManifestSong, genre: &String, format: &String) -> anyhow::Result<()> {
 | 
					    pub async fn download_song(&mut self, cfg: &ConfigWrapper, song: &ManifestSong, genre: &String, format: &String) -> anyhow::Result<()> {
 | 
				
			||||||
        let path = format!("{}/{genre}/{}.{}", cfg.cli.output, song.name, &format);
 | 
					        let dl_dir = format!("{}/{genre}/", cfg.cli.output);
 | 
				
			||||||
 | 
					        let dl_file = format!("{dl_dir}/{}.{}", song.name, &format);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if PathBuf::from(&path).exists() {
 | 
					        if PathBuf::from(&dl_file).exists() {
 | 
				
			||||||
            log::debug!("File {path} exists, skipping");
 | 
					            log::debug!("File {dl_file} exists, skipping");
 | 
				
			||||||
            return Ok(())
 | 
					            return Ok(())
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log::debug!("File {path} doesnt exist, downloading");
 | 
					        let url = url::Url::from_str(&song.url)?;
 | 
				
			||||||
        let mut cmd = if song.url.contains("youtube.com") || song.url.contains("youtu.be") {
 | 
					        let Some(url_host) = url.host() else {
 | 
				
			||||||
            log::debug!("Song {} is from yotube", song.url);
 | 
					            log::error!("Url {} doesnt have a valid host name", &song.url);
 | 
				
			||||||
            let mut cmd = tokio::process::Command::new(&cfg.cfg.ytdlp.path);
 | 
					            return Ok(());
 | 
				
			||||||
            cmd.args([
 | 
					 | 
				
			||||||
                    "-x",
 | 
					 | 
				
			||||||
                    "--audio-format",
 | 
					 | 
				
			||||||
                    format.as_str(),
 | 
					 | 
				
			||||||
                    "-o",
 | 
					 | 
				
			||||||
                    path.as_str(),
 | 
					 | 
				
			||||||
                    song.url.as_str()
 | 
					 | 
				
			||||||
                ]);
 | 
					 | 
				
			||||||
            cmd
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            let mut cmd = tokio::process::Command::new(&cfg.cfg.spotdl.path);
 | 
					 | 
				
			||||||
            cmd.args([
 | 
					 | 
				
			||||||
                    "-x",
 | 
					 | 
				
			||||||
                    "--audio-format",
 | 
					 | 
				
			||||||
                    format.as_str(),
 | 
					 | 
				
			||||||
                    "-o",
 | 
					 | 
				
			||||||
                    path.as_str(),
 | 
					 | 
				
			||||||
                    song.url.as_str()
 | 
					 | 
				
			||||||
                ]);
 | 
					 | 
				
			||||||
            cmd
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					        log::debug!("File {dl_file} doesnt exist, downloading");
 | 
				
			||||||
 | 
					        let mut cmd = match url_host.to_string().as_str() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            "youtube.com" |
 | 
				
			||||||
 | 
					            "youtu.be" => {
 | 
				
			||||||
 | 
					                log::debug!("Song {} is from yotube", song.url);
 | 
				
			||||||
 | 
					                let mut cmd = tokio::process::Command::new(&cfg.cfg.ytdlp.path);
 | 
				
			||||||
 | 
					                cmd.args([
 | 
				
			||||||
 | 
					                        "-x",
 | 
				
			||||||
 | 
					                        "--audio-format",
 | 
				
			||||||
 | 
					                        &format.to_string(),
 | 
				
			||||||
 | 
					                        "-o",
 | 
				
			||||||
 | 
					                        dl_file.as_str(),
 | 
				
			||||||
 | 
					                        song.url.as_str()
 | 
				
			||||||
 | 
					                    ]);
 | 
				
			||||||
 | 
					                cmd
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            "open.spotify.com" => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let mut cmd = tokio::process::Command::new(&cfg.cfg.spotdl.path);
 | 
				
			||||||
 | 
					                cmd.args([
 | 
				
			||||||
 | 
					                    "--format",
 | 
				
			||||||
 | 
					                    &format.to_string(),
 | 
				
			||||||
 | 
					                    "--output",
 | 
				
			||||||
 | 
					                    dl_dir.as_str(),
 | 
				
			||||||
 | 
					                    song.url.as_str()
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					                cmd
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            url => {
 | 
				
			||||||
 | 
					                log::error!("Unknown or unsupported hostname '{}'", url);
 | 
				
			||||||
 | 
					                return Ok(());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
        if log::max_level() < Level::Debug {
 | 
					        if log::max_level() < Level::Debug {
 | 
				
			||||||
            cmd.stdout(Stdio::null()).stderr(Stdio::null());
 | 
					            cmd.stdout(Stdio::null()).stderr(Stdio::null());
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					@ -92,10 +107,10 @@ impl Downloader {
 | 
				
			||||||
            PROCESSES.lock().await.write().await.get_mut(&id).unwrap().finished = true;
 | 
					            PROCESSES.lock().await.write().await.get_mut(&id).unwrap().finished = true;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        log::info!("Downloading {path}");
 | 
					        log::info!("Downloading {dl_file}");
 | 
				
			||||||
        PROCESSES.lock().await.write().await.insert(id, Proc {
 | 
					        PROCESSES.lock().await.write().await.insert(id, Proc {
 | 
				
			||||||
            url: song.url.clone(),
 | 
					            url: song.url.clone(),
 | 
				
			||||||
            path,
 | 
					            path: dl_file,
 | 
				
			||||||
            finished: false,
 | 
					            finished: false,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        self.id_itr += 1;
 | 
					        self.id_itr += 1;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,76 +0,0 @@
 | 
				
			||||||
use std::{collections::HashMap, fs::read_to_string, path::{Path, PathBuf}};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use anyhow::bail;
 | 
					 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ALLOWED_FORMATS: &[&'static str] = &["m4a", "aac", "flac", "mp3", "vaw"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Genre = String;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					 | 
				
			||||||
pub struct Manifest {
 | 
					 | 
				
			||||||
    #[serde(skip)]
 | 
					 | 
				
			||||||
    path: PathBuf,
 | 
					 | 
				
			||||||
    format: String,
 | 
					 | 
				
			||||||
    pub genres: HashMap<Genre, Vec<ManifestSong>>
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Manifest {
 | 
					 | 
				
			||||||
    pub fn format(&self) -> anyhow::Result<String> {
 | 
					 | 
				
			||||||
        if !ALLOWED_FORMATS.contains(&self.format.as_str()) {
 | 
					 | 
				
			||||||
            log::error!("Unknown format, allowed formats: {}", ALLOWED_FORMATS.join(", "));
 | 
					 | 
				
			||||||
            bail!("")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Ok(self.format.clone())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					 | 
				
			||||||
pub struct ManifestSong {
 | 
					 | 
				
			||||||
    pub name: String,
 | 
					 | 
				
			||||||
    pub url: String
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Manifest {
 | 
					 | 
				
			||||||
    fn from_string(s: String) -> anyhow::Result<Self> {
 | 
					 | 
				
			||||||
        let s = serde_json::from_str(&s)?;
 | 
					 | 
				
			||||||
        Ok(s)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn from_path(p: &Path) -> anyhow::Result<Self> {
 | 
					 | 
				
			||||||
        let data = read_to_string(p)?;
 | 
					 | 
				
			||||||
        let mut s = Self::from_string(data)?;
 | 
					 | 
				
			||||||
        s.path = p.to_path_buf();
 | 
					 | 
				
			||||||
        Ok(s)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn add_song(&mut self, genre: String, name: String, url: String) -> anyhow::Result<()> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !self.genres.contains_key(&genre) {
 | 
					 | 
				
			||||||
            self.genres.insert(genre.clone(), Vec::new());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let Some(genre_ref) = self.genres.get_mut(&genre) else {
 | 
					 | 
				
			||||||
            log::error!("Invalid genre '{}'", genre);
 | 
					 | 
				
			||||||
            bail!("Invalid genre")
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        genre_ref.push(ManifestSong {
 | 
					 | 
				
			||||||
            name,
 | 
					 | 
				
			||||||
            url,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn save(&self) -> anyhow::Result<()> {
 | 
					 | 
				
			||||||
        let data = serde_json::to_string_pretty(self)?;
 | 
					 | 
				
			||||||
        std::fs::write(&self.path, data)?;
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										165
									
								
								music_mgr/src/manifest/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								music_mgr/src/manifest/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,165 @@
 | 
				
			||||||
 | 
					// pub mod v1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::{collections::HashMap, fmt::{Debug, Display}, path::{Path, PathBuf}, str::FromStr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use anyhow::{bail, Result};
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ALLOWED_FORMATS: &[&'static str] = &["m4a", "aac", "flac", "mp3", "vaw"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Genre = String;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					pub struct Manifest {
 | 
				
			||||||
 | 
					    #[serde(skip)]
 | 
				
			||||||
 | 
					    path: PathBuf,
 | 
				
			||||||
 | 
					    format: String,
 | 
				
			||||||
 | 
					    pub genres: HashMap<Genre, Vec<ManifestSong>>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Manifest {
 | 
				
			||||||
 | 
					    pub fn format(&self) -> anyhow::Result<String> {
 | 
				
			||||||
 | 
					        if !ALLOWED_FORMATS.contains(&self.format.as_str()) {
 | 
				
			||||||
 | 
					            log::error!("Unknown format, allowed formats: {}", ALLOWED_FORMATS.join(", "));
 | 
				
			||||||
 | 
					            bail!("")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(self.format.clone())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					pub struct ManifestSong {
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub url: String
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Manifest {
 | 
				
			||||||
 | 
					    fn from_string(s: String) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let s = serde_json::from_str(&s)?;
 | 
				
			||||||
 | 
					        Ok(s)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn from_path(p: &Path) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let data = std::fs::read_to_string(p)?;
 | 
				
			||||||
 | 
					        let mut s = Self::from_string(data)?;
 | 
				
			||||||
 | 
					        s.path = p.to_path_buf();
 | 
				
			||||||
 | 
					        Ok(s)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn add_song(&mut self, genre: String, name: String, url: String) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if !self.genres.contains_key(&genre) {
 | 
				
			||||||
 | 
					            self.genres.insert(genre.clone(), Vec::new());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let Some(genre_ref) = self.genres.get_mut(&genre) else {
 | 
				
			||||||
 | 
					            log::error!("Invalid genre '{}'", genre);
 | 
				
			||||||
 | 
					            bail!("Invalid genre")
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        genre_ref.push(ManifestSong {
 | 
				
			||||||
 | 
					            name,
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save(&self) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					        let data = serde_json::to_string_pretty(self)?;
 | 
				
			||||||
 | 
					        std::fs::write(&self.path, data)?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// pub type GenreName = String;
 | 
				
			||||||
 | 
					// pub type SongName = String;
 | 
				
			||||||
 | 
					// pub type Genre = HashMap<SongName, Box<dyn Song>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #[derive(Debug, Serialize, Deserialize, Clone)]
 | 
				
			||||||
 | 
					// pub enum SongType {
 | 
				
			||||||
 | 
					//     Youtube,
 | 
				
			||||||
 | 
					//     Spotify,
 | 
				
			||||||
 | 
					//     Soundcloud,
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// impl TryFrom<url::Url> for SongType {
 | 
				
			||||||
 | 
					//     type Error = anyhow::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//     fn try_from(url: url::Url) -> std::prelude::v1::Result<Self, Self::Error> {
 | 
				
			||||||
 | 
					//         let Some(host) = url.host_str() else {
 | 
				
			||||||
 | 
					//             bail!("{url} does not have a host");
 | 
				
			||||||
 | 
					//         };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//         match host {
 | 
				
			||||||
 | 
					//             "youtube.com" | "youtu.be"  => Ok(Self::Youtube),
 | 
				
			||||||
 | 
					//             "open.spotify.com"  => Ok(Self::Spotify),
 | 
				
			||||||
 | 
					//             "SOUNDCLOUD" => Ok(Self::Soundcloud), // TODO: Fix this
 | 
				
			||||||
 | 
					//             _ => bail!("Unknwon host {url}")
 | 
				
			||||||
 | 
					//         }
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #[allow(non_camel_case_types)]
 | 
				
			||||||
 | 
					// #[derive(Debug, Serialize, Deserialize, Clone)]
 | 
				
			||||||
 | 
					// pub enum Format {
 | 
				
			||||||
 | 
					//     m4a,
 | 
				
			||||||
 | 
					//     aac,
 | 
				
			||||||
 | 
					//     flac,
 | 
				
			||||||
 | 
					//     mp3,
 | 
				
			||||||
 | 
					//     vaw,
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// impl TryFrom<String> for Format {
 | 
				
			||||||
 | 
					//     type Error = anyhow::Error;
 | 
				
			||||||
 | 
					//     fn try_from(value: String) -> std::prelude::v1::Result<Self, Self::Error> {
 | 
				
			||||||
 | 
					//         match value.as_str() {
 | 
				
			||||||
 | 
					//             "m4a" => Ok(Self::m4a),
 | 
				
			||||||
 | 
					//             "aac" => Ok(Self::aac),
 | 
				
			||||||
 | 
					//             "flac" => Ok(Self::flac),
 | 
				
			||||||
 | 
					//             "mp3" => Ok(Self::mp3),
 | 
				
			||||||
 | 
					//             "vaw" => Ok(Self::vaw),
 | 
				
			||||||
 | 
					//             v => bail!("Unknown format {v}")
 | 
				
			||||||
 | 
					//         }
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// impl Display for Format {
 | 
				
			||||||
 | 
					//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					//         match self {
 | 
				
			||||||
 | 
					//             Format::m4a => write!(f, "m4a")?,
 | 
				
			||||||
 | 
					//             Format::aac => write!(f, "aac")?,
 | 
				
			||||||
 | 
					//             Format::flac => write!(f, "flac")?,
 | 
				
			||||||
 | 
					//             Format::mp3 => write!(f, "mp3")?,
 | 
				
			||||||
 | 
					//             Format::vaw => write!(f, "vaw")?,
 | 
				
			||||||
 | 
					//         }
 | 
				
			||||||
 | 
					//         Ok(())
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					// pub trait Song: Debug +  serde_traitobject::Serialize + serde_traitobject::Deserialize{
 | 
				
			||||||
 | 
					//     fn get_url(&self) -> Result<url::Url>;
 | 
				
			||||||
 | 
					//     fn get_url_str(&self) -> &String;
 | 
				
			||||||
 | 
					//     fn get_url_str_mut(&mut self) -> &mut String;
 | 
				
			||||||
 | 
					//     fn get_type(&self) -> &SongType;
 | 
				
			||||||
 | 
					//     fn get_type_mut(&mut self) -> &mut SongType;
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// pub trait Manifest: Debug + Clone + serde_traitobject::Serialize + serde_traitobject::Deserialize{
 | 
				
			||||||
 | 
					//     fn get_format(&self) -> Result<Format>;
 | 
				
			||||||
 | 
					//     fn add_song(&mut self, genre: GenreName, name: SongName, song: &dyn Song) -> Option<Box<dyn Song>>;
 | 
				
			||||||
 | 
					//     fn get_song(&self, genre: GenreName, name: SongName) -> Option<&Box<dyn Song>>;
 | 
				
			||||||
 | 
					//     fn get_song_mut(&mut self, genre: GenreName, name: SongName) -> Option<&mut Box<dyn Song>>;
 | 
				
			||||||
 | 
					//     fn add_genre(&mut self, genre: GenreName);
 | 
				
			||||||
 | 
					//     fn get_genre(&self, genre: GenreName) -> Option<&Genre>;
 | 
				
			||||||
 | 
					//     fn get_genre_mut(&mut self, genre: GenreName) -> Option<&mut Genre>;
 | 
				
			||||||
 | 
					//     fn get_genres(&self) -> &HashMap<GenreName, Genre>;
 | 
				
			||||||
 | 
					//     fn get_genres_mut(&mut self) -> &mut HashMap<GenreName, Genre>;
 | 
				
			||||||
 | 
					//     fn load(&mut self, p: PathBuf);
 | 
				
			||||||
 | 
					//     fn save(&self, p: Option<&PathBuf>);
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
							
								
								
									
										72
									
								
								music_mgr/src/manifest/v1/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								music_mgr/src/manifest/v1/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					pub mod song;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::{collections::HashMap, path::PathBuf, str::FromStr};
 | 
				
			||||||
 | 
					use anyhow::Result;
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use self::song::SongV1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{Format, Genre, GenreName, Manifest, Song, SongName};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					pub struct ManifestV1 {
 | 
				
			||||||
 | 
					    version: usize,
 | 
				
			||||||
 | 
					    format: String,
 | 
				
			||||||
 | 
					    genres: HashMap<GenreName, HashMap<SongName, Box<SongV1>>>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Manifest for ManifestV1 {
 | 
				
			||||||
 | 
					    fn get_format(&self) -> Result<super::Format> {
 | 
				
			||||||
 | 
					        Ok(Format::try_from(self.format.clone())?)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn add_song(&mut self, genre: GenreName, name: SongName, song: &dyn Song) -> Option<Box<dyn Song>> {
 | 
				
			||||||
 | 
					        let song: SongV1 = song.into();
 | 
				
			||||||
 | 
					        self.get_genre_mut(genre)?
 | 
				
			||||||
 | 
					            .insert(name, Box::new(song))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_song(&self, genre: GenreName, name: SongName) -> Option<&Box<dyn Song>> {
 | 
				
			||||||
 | 
					        self.get_genre(genre)?.get(&name)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_song_mut(&mut self, genre: GenreName, name: SongName) -> Option<&mut Box<dyn Song>> {
 | 
				
			||||||
 | 
					        self.get_genre_mut(genre)?.get_mut(&name)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn add_genre(&mut self, name: GenreName) {
 | 
				
			||||||
 | 
					        self.genres.insert(name, Default::default());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_genre(&self, genre: GenreName) -> Option<&super::Genre> {
 | 
				
			||||||
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            std::mem::transmute(self.genres.get(&genre))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_genre_mut(&mut self, genre: GenreName) -> Option<&mut super::Genre> {
 | 
				
			||||||
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            std::mem::transmute(self.genres.get_mut(&genre))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_genres(&self) -> &HashMap<GenreName, super::Genre> {
 | 
				
			||||||
 | 
					        &self.genres
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_genres_mut(&mut self) -> &mut HashMap<GenreName, super::Genre> {
 | 
				
			||||||
 | 
					        todo!()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn load(&mut self, p: PathBuf) {
 | 
				
			||||||
 | 
					        todo!()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn save(&self, p: Option<&PathBuf>) {
 | 
				
			||||||
 | 
					        todo!()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										57
									
								
								music_mgr/src/manifest/v1/song.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								music_mgr/src/manifest/v1/song.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					use anyhow::Result;
 | 
				
			||||||
 | 
					use crate::manifest::{Song, SongType};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					pub struct SongV1 {
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    typ: SongType
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl SongV1 {
 | 
				
			||||||
 | 
					    pub fn from_str(url: String) -> Result<Self> {
 | 
				
			||||||
 | 
					        Self::from_url(url::Url::from_str(&url)?)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn from_url(url: url::Url) -> Result<Self> {
 | 
				
			||||||
 | 
					        let typ = SongType::try_from(url.clone())?;
 | 
				
			||||||
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            url: url.to_string(),
 | 
				
			||||||
 | 
					            typ,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<&dyn Song> for SongV1 {
 | 
				
			||||||
 | 
					    fn from(value: &dyn Song) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            url: value.get_url_str().clone(),
 | 
				
			||||||
 | 
					            typ: value.get_type().clone(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Song for SongV1 {
 | 
				
			||||||
 | 
					    fn get_url(&self) -> anyhow::Result<url::Url> {
 | 
				
			||||||
 | 
					        Ok(url::Url::from_str(&self.url)?)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_url_str(&self) -> &String {
 | 
				
			||||||
 | 
					        &self.url
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_url_str_mut(&mut self) -> &mut String {
 | 
				
			||||||
 | 
					        &mut self.url
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_type(&self) -> &crate::manifest::SongType {
 | 
				
			||||||
 | 
					        &self.typ
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_type_mut(&mut self) -> &mut crate::manifest::SongType {
 | 
				
			||||||
 | 
					        &mut self.typ
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ use std::{collections::HashMap, io::Write};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn simple_prompt(p: &str) -> String {
 | 
					pub(crate) fn simple_prompt(p: &str) -> String {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print!("{c}prompt{r}: {p} > ",
 | 
					    print!("{c}prompt{r}: {p} > ",
 | 
				
			||||||
        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
					        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ pub fn simple_prompt(p: &str) -> String {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[allow(dead_code)]
 | 
					#[allow(dead_code)]
 | 
				
			||||||
pub fn prompt_with_list(p: &str, options: &[&str]) -> usize {
 | 
					pub(crate) fn prompt_with_list(p: &str, options: &[&str]) -> usize {
 | 
				
			||||||
    println!("{c}prompt{r}: {p}",
 | 
					    println!("{c}prompt{r}: {p}",
 | 
				
			||||||
        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
					        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
				
			||||||
        r=anstyle::Reset.render()
 | 
					        r=anstyle::Reset.render()
 | 
				
			||||||
| 
						 | 
					@ -47,7 +47,7 @@ pub fn prompt_with_list(p: &str, options: &[&str]) -> usize {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn prompt_with_list_or_str(p: &str, options: &[String]) -> String {
 | 
					pub(crate) fn prompt_with_list_or_str(p: &str, options: &[String]) -> String {
 | 
				
			||||||
    println!("{c}prompt{r}: {p} (select with number or input text)",
 | 
					    println!("{c}prompt{r}: {p} (select with number or input text)",
 | 
				
			||||||
        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
					        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
				
			||||||
        r=anstyle::Reset.render()
 | 
					        r=anstyle::Reset.render()
 | 
				
			||||||
| 
						 | 
					@ -77,7 +77,7 @@ pub fn prompt_with_list_or_str(p: &str, options: &[String]) -> String {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[allow(dead_code)]
 | 
					#[allow(dead_code)]
 | 
				
			||||||
pub fn prompt_with_map(p: &str, options: HashMap<&str, &str>) -> String {
 | 
					pub(crate) fn prompt_with_map(p: &str, options: HashMap<&str, &str>) -> String {
 | 
				
			||||||
    println!("{c}prompt{r}: {p}",
 | 
					    println!("{c}prompt{r}: {p}",
 | 
				
			||||||
        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
					        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
				
			||||||
        r=anstyle::Reset.render()
 | 
					        r=anstyle::Reset.render()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,25 @@ use std::path::PathBuf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) fn escape_song_name(s: String) -> String {
 | 
				
			||||||
 | 
					    s
 | 
				
			||||||
 | 
					        .replace(".", "")
 | 
				
			||||||
 | 
					        .replace("'", "")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) fn is_supported_host(url: url::Url) -> bool {
 | 
				
			||||||
 | 
					    let host = url.host_str();
 | 
				
			||||||
 | 
					    if host.is_none() {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    match host.unwrap() {
 | 
				
			||||||
 | 
					        "youtube.com" | "youtu.be" |
 | 
				
			||||||
 | 
					        "open.spotify.com"  => true,
 | 
				
			||||||
 | 
					        _ => false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) fn is_program_in_path(program: &str) -> Option<PathBuf> {
 | 
				
			||||||
pub fn is_program_in_path(program: &str) -> Option<PathBuf> {
 | 
					 | 
				
			||||||
    if let Ok(path) = std::env::var("PATH") {
 | 
					    if let Ok(path) = std::env::var("PATH") {
 | 
				
			||||||
        for p in path.split(constants::PATH_VAR_SEP) {
 | 
					        for p in path.split(constants::PATH_VAR_SEP) {
 | 
				
			||||||
            let exec_path = PathBuf::from(p).join(program).with_extension(constants::EXEC_EXT);
 | 
					            let exec_path = PathBuf::from(p).join(program).with_extension(constants::EXEC_EXT);
 | 
				
			||||||
| 
						 | 
					@ -18,7 +33,7 @@ pub fn is_program_in_path(program: &str) -> Option<PathBuf> {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(target_family="unix")]
 | 
					#[cfg(target_family="unix")]
 | 
				
			||||||
pub fn isatty() -> bool {
 | 
					pub(crate) fn isatty() -> bool {
 | 
				
			||||||
    use std::{ffi::c_int, os::fd::AsRawFd};
 | 
					    use std::{ffi::c_int, os::fd::AsRawFd};
 | 
				
			||||||
    unsafe {
 | 
					    unsafe {
 | 
				
			||||||
        let fd = std::io::stdin().as_raw_fd();
 | 
					        let fd = std::io::stdin().as_raw_fd();
 | 
				
			||||||
| 
						 | 
					@ -27,7 +42,7 @@ pub fn isatty() -> bool {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(target_family="windows")]
 | 
					#[cfg(target_family="windows")]
 | 
				
			||||||
pub fn isatty() -> bool {
 | 
					pub(crate) fn isatty() -> bool {
 | 
				
			||||||
    unsafe {
 | 
					    unsafe {
 | 
				
			||||||
        use windows::Win32::System::Console;
 | 
					        use windows::Win32::System::Console;
 | 
				
			||||||
        use Console::{CONSOLE_MODE, STD_OUTPUT_HANDLE};
 | 
					        use Console::{CONSOLE_MODE, STD_OUTPUT_HANDLE};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user