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