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]
 | 
			
		||||
name = "music_mgr"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
anstyle = "1.0.6"
 | 
			
		||||
anyhow = "1.0.81"
 | 
			
		||||
camino = "1.1.6"
 | 
			
		||||
clap = { version = "4.5.4", features = ["derive"] }
 | 
			
		||||
env_logger = "0.11.3"
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
libc = "0.2.153"
 | 
			
		||||
log = "0.4.21"
 | 
			
		||||
reqwest = "0.12.3"
 | 
			
		||||
serde = { version = "1.0.197", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.115"
 | 
			
		||||
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "process", "sync"] }
 | 
			
		||||
windows = { version = "0.56.0", features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_Console"] }
 | 
			
		||||
zip-extensions = "0.6.2"
 | 
			
		||||
[package]
 | 
			
		||||
name = "music_mgr"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
anstyle = "1.0.6"
 | 
			
		||||
anyhow = "1.0.81"
 | 
			
		||||
camino = "1.1.6"
 | 
			
		||||
clap = { version = "4.5.4", features = ["derive"] }
 | 
			
		||||
env_logger = "0.11.3"
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
libc = "0.2.153"
 | 
			
		||||
log = "0.4.21"
 | 
			
		||||
reqwest = "0.12.3"
 | 
			
		||||
serde = { version = "1.0.197", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.115"
 | 
			
		||||
# serde_traitobject = "0.2.8"
 | 
			
		||||
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "process", "sync"] }
 | 
			
		||||
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)")
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    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( ||
 | 
			
		||||
        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 log::Level;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ impl Downloader {
 | 
			
		|||
 | 
			
		||||
        for (genre, songs) in &manifest.genres {
 | 
			
		||||
            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?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -45,39 +45,54 @@ impl Downloader {
 | 
			
		|||
    }
 | 
			
		||||
    
 | 
			
		||||
    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() {
 | 
			
		||||
            log::debug!("File {path} exists, skipping");
 | 
			
		||||
        if PathBuf::from(&dl_file).exists() {
 | 
			
		||||
            log::debug!("File {dl_file} exists, skipping");
 | 
			
		||||
            return Ok(())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        log::debug!("File {path} doesnt exist, downloading");
 | 
			
		||||
        let mut cmd = if song.url.contains("youtube.com") || song.url.contains("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.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
 | 
			
		||||
        let url = url::Url::from_str(&song.url)?;
 | 
			
		||||
        let Some(url_host) = url.host() else {
 | 
			
		||||
            log::error!("Url {} doesnt have a valid host name", &song.url);
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        };
 | 
			
		||||
        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 {
 | 
			
		||||
            cmd.stdout(Stdio::null()).stderr(Stdio::null());
 | 
			
		||||
        };
 | 
			
		||||
| 
						 | 
				
			
			@ -92,10 +107,10 @@ impl Downloader {
 | 
			
		|||
            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 {
 | 
			
		||||
            url: song.url.clone(),
 | 
			
		||||
            path,
 | 
			
		||||
            path: dl_file,
 | 
			
		||||
            finished: false,
 | 
			
		||||
        });
 | 
			
		||||
        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} > ",
 | 
			
		||||
        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ pub fn simple_prompt(p: &str) -> String {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[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}",
 | 
			
		||||
        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
			
		||||
        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)",
 | 
			
		||||
        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
			
		||||
        r=anstyle::Reset.render()
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +77,7 @@ pub fn prompt_with_list_or_str(p: &str, options: &[String]) -> String {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
#[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}",
 | 
			
		||||
        c=anstyle::AnsiColor::Cyan.render_fg(),
 | 
			
		||||
        r=anstyle::Reset.render()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,10 +2,25 @@ use std::path::PathBuf;
 | 
			
		|||
 | 
			
		||||
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 fn is_program_in_path(program: &str) -> Option<PathBuf> {
 | 
			
		||||
pub(crate) fn is_program_in_path(program: &str) -> Option<PathBuf> {
 | 
			
		||||
    if let Ok(path) = std::env::var("PATH") {
 | 
			
		||||
        for p in path.split(constants::PATH_VAR_SEP) {
 | 
			
		||||
            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")]
 | 
			
		||||
pub fn isatty() -> bool {
 | 
			
		||||
pub(crate) fn isatty() -> bool {
 | 
			
		||||
    use std::{ffi::c_int, os::fd::AsRawFd};
 | 
			
		||||
    unsafe {
 | 
			
		||||
        let fd = std::io::stdin().as_raw_fd();
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +42,7 @@ pub fn isatty() -> bool {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_family="windows")]
 | 
			
		||||
pub fn isatty() -> bool {
 | 
			
		||||
pub(crate) fn isatty() -> bool {
 | 
			
		||||
    unsafe {
 | 
			
		||||
        use windows::Win32::System::Console;
 | 
			
		||||
        use Console::{CONSOLE_MODE, STD_OUTPUT_HANDLE};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user