Clippy pedantic fixes
This commit is contained in:
parent
387a5eaf4a
commit
f7008e882c
BIN
assets/icon.png
Normal file
BIN
assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
|
@ -2,6 +2,7 @@ use camino::Utf8PathBuf;
|
|||
use clap::{Parser, Subcommand};
|
||||
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Parser, Default, Clone)]
|
||||
pub struct CliArgs {
|
||||
/// Show more info
|
||||
|
@ -25,6 +26,7 @@ pub struct CliArgs {
|
|||
|
||||
}
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Subcommand, Clone)]
|
||||
pub enum CliCommand {
|
||||
Download,
|
||||
|
|
|
@ -11,7 +11,7 @@ use self::cli::CliArgs;
|
|||
|
||||
// const YTDLP_DL_URL: &'static str = "https://github.com/yt-dlp/yt-dlp/archive/refs/heads/master.zip";
|
||||
// const SPOTDL_DL_URL: &'static str = "https://github.com/spotDL/spotify-downloader/archive/refs/heads/master.zip";
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ConfigWrapper {
|
||||
pub cfg: Config,
|
||||
|
@ -19,17 +19,20 @@ pub struct ConfigWrapper {
|
|||
pub isatty: bool
|
||||
}
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct Config {
|
||||
pub ytdlp: ConfigYtdlp,
|
||||
pub spotdl: ConfigSpotdl,
|
||||
}
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct ConfigYtdlp {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct ConfigSpotdl {
|
||||
pub path: PathBuf,
|
||||
|
@ -37,21 +40,22 @@ pub struct ConfigSpotdl {
|
|||
|
||||
|
||||
impl ConfigWrapper {
|
||||
pub async fn parse() -> Result<Self> {
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
pub fn parse() -> Result<Self> {
|
||||
let mut s = Self::default();
|
||||
s.cli = cli::CliArgs::parse();
|
||||
crate::logger::init_logger(s.cli.debug);
|
||||
s.cfg = Config::parse(&s.cli).await?;
|
||||
crate::logger::init(s.cli.debug);
|
||||
s.cfg = Config::parse(&s.cli)?;
|
||||
s.isatty = isatty();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub async fn parse(cli: &CliArgs) -> Result<Self> {
|
||||
pub fn parse(cli: &CliArgs) -> Result<Self> {
|
||||
if !cli.config.exists() {
|
||||
log::info!("Config doesnt exist");
|
||||
return Self::setup_config(&cli).await;
|
||||
return Self::setup_config(cli);
|
||||
}
|
||||
|
||||
let data = std::fs::read_to_string(&cli.config)?;
|
||||
|
@ -59,63 +63,51 @@ impl Config {
|
|||
Ok(data)
|
||||
}
|
||||
|
||||
async fn setup_config(cli: &CliArgs) -> Result<Self> {
|
||||
fn setup_config(cli: &CliArgs) -> Result<Self> {
|
||||
let mut s = Self::default();
|
||||
let mut error = false;
|
||||
|
||||
match util::is_program_in_path("yt-dlp") {
|
||||
Some(p) => {
|
||||
s.ytdlp.path = p;
|
||||
},
|
||||
if let Some(p) = util::is_program_in_path("yt-dlp") {
|
||||
s.ytdlp.path = p;
|
||||
} else {
|
||||
error = true;
|
||||
log::error!("could not find yt-dlp, please install it.");
|
||||
log::info!(" - With winget (Windows only) (recommended):");
|
||||
log::info!(" - Most new windows versions have winget installed, if not, instructions here: https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget");
|
||||
log::info!(" - run `winget install yt-dlp`");
|
||||
log::info!(" - With chocolatey (Windows only):");
|
||||
log::info!(" - Make sure you have chocolatey installed - https://chocolatey.org/install");
|
||||
log::info!(" - run `choco install yt-dlp` as Admin");
|
||||
log::info!(" - With pip (from python) (Cross platform)");
|
||||
log::info!(" - Make sure you have python installed");
|
||||
log::info!(" - pip install yt-dlp");
|
||||
log::info!(" - Using your distro's package manager (Unix/BSD only) (Not recommended)");
|
||||
}
|
||||
|
||||
None => {
|
||||
if let Some(p) = util::is_program_in_path("spotdl") {
|
||||
s.spotdl.path = p;
|
||||
} else {
|
||||
let res = crate::prompt::yes_no("Spotdl is not installed but if you dont need to download music from spotify you dont need it, skip it?", None);
|
||||
if res {
|
||||
s.spotdl.path = PathBuf::from("UNUSED");
|
||||
} else {
|
||||
error = true;
|
||||
log::error!("could not find yt-dlp, please install it.");
|
||||
log::info!(" - With winget (Windows only) (recommended):");
|
||||
log::info!(" - Most new windows versions have winget installed, if not, instructions here: https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget");
|
||||
log::info!(" - run `winget install yt-dlp`");
|
||||
log::info!(" - With chocolatey (Windows only):");
|
||||
log::info!(" - Make sure you have chocolatey installed - https://chocolatey.org/install");
|
||||
log::info!(" - run `choco install yt-dlp` as Admin");
|
||||
log::info!(" - With pip (from python) (Cross platform)");
|
||||
log::info!(" - Make sure you have python installed");
|
||||
log::info!(" - pip install yt-dlp");
|
||||
log::info!(" - Using your distro's package manager (Unix/BSD only) (Not recommended)")
|
||||
log::error!("could not find spotdl, please install it. ");
|
||||
log::info!(" - With pip (from python) (Cross platform) (recommended)");
|
||||
log::info!(" - Make sure you have python installed - https://www.python.org/downloads/");
|
||||
log::info!(" - pip install spotdl");
|
||||
}
|
||||
}
|
||||
|
||||
match util::is_program_in_path("spotdl") {
|
||||
Some(p) => {
|
||||
s.spotdl.path = p;
|
||||
},
|
||||
|
||||
None => {
|
||||
let res = crate::prompt::prompt_bool("Spotdl is not installed but if you dont need to download music from spotify you dont need it, skip it?", None);
|
||||
if res {
|
||||
s.spotdl.path = PathBuf::from("UNUSED");
|
||||
} else {
|
||||
error = true;
|
||||
log::error!("could not find spotdl, please install it. ");
|
||||
log::info!(" - With pip (from python) (Cross platform) (recommended)");
|
||||
log::info!(" - Make sure you have python installed - https://www.python.org/downloads/");
|
||||
log::info!(" - pip install spotdl");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match util::is_program_in_path("ffmpeg") {
|
||||
Some(_) => (),
|
||||
|
||||
None => {
|
||||
error = true;
|
||||
log::error!("could not find ffmpeg, please install it.");
|
||||
log::info!(" - With winget (Windows only) (recommended):");
|
||||
log::info!(" - Most new windows versions have winget installed, if not, instructions here: https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget");
|
||||
log::info!(" - run `winget install --id=Gyan.FFmpeg -e`");
|
||||
log::info!(" - With chocolatey (Windows only):");
|
||||
log::info!(" - Make sure you have chocolatey installed - https://chocolatey.org/install");
|
||||
log::info!(" - run `choco install ffmpeg` as Admin");
|
||||
}
|
||||
if util::is_program_in_path("ffmpeg").is_none() {
|
||||
error = true;
|
||||
log::error!("could not find ffmpeg, please install it.");
|
||||
log::info!(" - With winget (Windows only) (recommended):");
|
||||
log::info!(" - Most new windows versions have winget installed, if not, instructions here: https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget");
|
||||
log::info!(" - run `winget install --id=Gyan.FFmpeg -e`");
|
||||
log::info!(" - With chocolatey (Windows only):");
|
||||
log::info!(" - Make sure you have chocolatey installed - https://chocolatey.org/install");
|
||||
log::info!(" - run `choco install ffmpeg` as Admin");
|
||||
}
|
||||
|
||||
if !error {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
|
||||
#[cfg(target_family="windows")]
|
||||
mod constants {
|
||||
pub const PATH_VAR_SEP: &'static str = ";";
|
||||
pub const EXEC_EXT: &'static str = "exe";
|
||||
mod _m {
|
||||
pub const PATH_VAR_SEP: &str = ";";
|
||||
pub const EXEC_EXT: &str = "exe";
|
||||
}
|
||||
|
||||
#[cfg(target_family="unix")]
|
||||
mod constants {
|
||||
pub const PATH_VAR_SEP: &'static str = ":";
|
||||
pub const EXEC_EXT: &'static str = "";
|
||||
mod _m {
|
||||
pub const PATH_VAR_SEP: &str = ":";
|
||||
pub const EXEC_EXT: &str = "";
|
||||
}
|
||||
|
||||
|
||||
pub use constants::*;
|
||||
pub use _m::*;
|
||||
|
|
|
@ -40,8 +40,8 @@ impl Downloader {
|
|||
self.nb_cache.len() + crate::process_manager::proc_count()
|
||||
}
|
||||
|
||||
pub fn download_song_nb(&mut self, cfg: &ConfigWrapper, pname: &String, sname: &String, song: &Song, format: &Format) -> anyhow::Result<()> {
|
||||
self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), format.clone()));
|
||||
pub fn download_song_nb(&mut self, cfg: &ConfigWrapper, pname: &str, sname: &str, song: &Song, format: &Format) -> anyhow::Result<()> {
|
||||
self.nb_cache.push((pname.to_string(), sname.to_string(), song.clone(), format.clone()));
|
||||
self.nb_initial_song_count += 1;
|
||||
self.download_all_nb_poll(cfg)?;
|
||||
Ok(())
|
||||
|
@ -76,12 +76,12 @@ impl Downloader {
|
|||
|
||||
|
||||
|
||||
pub async fn download_all(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
|
||||
pub fn download_all(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
|
||||
let format = manifest.get_format();
|
||||
|
||||
for (name, playlist) in manifest.get_playlists() {
|
||||
for (song_name, song) in playlist.get_songs() {
|
||||
self.download_song(cfg, song_name, song, &name, format)?;
|
||||
self.download_song(cfg, song_name, song, name, format)?;
|
||||
self.count += crate::process_manager::wait_for_procs_untill(10)?;
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ impl Downloader {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn download_playlist(&mut self, cfg: &ConfigWrapper, url: &String, pname: &String, format: &Format) -> anyhow::Result<usize> {
|
||||
pub fn download_playlist(&mut self, cfg: &ConfigWrapper, url: &str, pname: &str, format: &Format) -> anyhow::Result<usize> {
|
||||
self.download_playlist_nb(cfg, url, pname, format)?;
|
||||
let mut count = 0;
|
||||
while let Some(c) = self.download_all_nb_poll(cfg)? {
|
||||
|
@ -99,14 +99,14 @@ impl Downloader {
|
|||
Ok(count)
|
||||
}
|
||||
|
||||
pub fn download_playlist_nb(&mut self, cfg: &ConfigWrapper, url: &String, pname: &String, format: &Format) -> anyhow::Result<HashMap<String, Song>> {
|
||||
pub fn download_playlist_nb(&mut self, cfg: &ConfigWrapper, url: &str, pname: &str, format: &Format) -> anyhow::Result<HashMap<String, Song>> {
|
||||
log::warn!("This automatically assumes its a youtube link as it is currently the only supported playlist source");
|
||||
let mut cmd = tokio::process::Command::new(&cfg.cfg.ytdlp.path);
|
||||
cmd.args([
|
||||
"--flat-playlist",
|
||||
"--simulate",
|
||||
"-O", "%(url)s|%(title)s",
|
||||
url.as_str()
|
||||
url
|
||||
]);
|
||||
cmd
|
||||
.stderr(Stdio::null())
|
||||
|
@ -119,11 +119,11 @@ impl Downloader {
|
|||
let out = futures::executor::block_on(ftr)?.stdout;
|
||||
let out = String::from_utf8(out)?;
|
||||
for line in out.lines() {
|
||||
let mut split_text = line.split("|").collect::<Vec<&str>>();
|
||||
let mut split_text = line.split('|').collect::<Vec<&str>>();
|
||||
let url = split_text.swap_remove(0).to_string();
|
||||
let sname = split_text.join("|");
|
||||
let song = Song::from_url_str(url)?.set_type(SongType::Youtube).clone();
|
||||
self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), format.clone()));
|
||||
self.nb_cache.push((pname.to_string(), sname.clone(), song.clone(), format.clone()));
|
||||
ret.insert(sname, song.clone());
|
||||
}
|
||||
self.nb_initial_song_count += out.lines().count();
|
||||
|
@ -131,7 +131,7 @@ impl Downloader {
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn download_song(&mut self, cfg: &ConfigWrapper, name: &String, song: &Song, playlist: &String, format: &Format) -> anyhow::Result<()> {
|
||||
pub fn download_song(&self, cfg: &ConfigWrapper, name: &String, song: &Song, playlist: &String, format: &Format) -> anyhow::Result<()> {
|
||||
let dl_dir = format!("{}/{playlist}", cfg.cli.output);
|
||||
let dl_file = format!("{dl_dir}/{}.{}", name, &format);
|
||||
log::debug!("Checking: {dl_file}");
|
||||
|
@ -168,7 +168,7 @@ impl Downloader {
|
|||
]);
|
||||
cmd
|
||||
}
|
||||
url => {
|
||||
url @ SongType::Soundcloud => {
|
||||
log::error!("Unknown or unsupported hostname '{:?}'", url);
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use log::LevelFilter;
|
||||
|
||||
|
||||
pub fn init_logger(debug: bool) {
|
||||
pub fn init(debug: bool) {
|
||||
let level = if debug {
|
||||
LevelFilter::Debug
|
||||
} else {
|
||||
|
|
|
@ -8,15 +8,14 @@ mod manifest;
|
|||
mod logger;
|
||||
mod downloader;
|
||||
mod util;
|
||||
mod prompt;
|
||||
mod config;
|
||||
mod constants;
|
||||
mod process_manager;
|
||||
mod ui;
|
||||
mod prompt;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let Ok(cfg) = ConfigWrapper::parse().await else {
|
||||
fn main() {
|
||||
let Ok(cfg) = ConfigWrapper::parse() else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -29,5 +28,5 @@ async fn main() {
|
|||
};
|
||||
|
||||
|
||||
let _ = ui::cli::command_run(&cfg, &mut manifest).await;
|
||||
let _ = ui::cli::command_run(&cfg, &mut manifest);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub mod song;
|
||||
pub mod playlist;
|
||||
use playlist::Playlist;
|
||||
use song::Song;
|
||||
|
||||
use std::{collections::HashMap, fmt::{Debug, Display}, path::PathBuf};
|
||||
|
@ -10,7 +11,7 @@ use anyhow::{bail, Result};
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
const DEFAULT_MANIFEST: &'static str = include_str!("../../manifest.default.json");
|
||||
const DEFAULT_MANIFEST: &str = include_str!("../../manifest.default.json");
|
||||
|
||||
|
||||
|
||||
|
@ -51,7 +52,7 @@ impl Manifest {
|
|||
self.get_playlist_mut(playlist_name)?.get_song_mut(name)
|
||||
}
|
||||
pub fn add_playlist(&mut self, playlist_name: String) {
|
||||
self.playlists.insert(playlist_name, Default::default());
|
||||
self.playlists.insert(playlist_name, Playlist::default());
|
||||
}
|
||||
pub fn get_playlist(&self, playlist_name: &String) -> Option<&playlist::Playlist> {
|
||||
self.playlists.get(playlist_name)
|
||||
|
@ -67,7 +68,7 @@ impl Manifest {
|
|||
}
|
||||
pub fn get_song_count(&self) -> usize {
|
||||
let mut count = 0;
|
||||
for (_, v) in &self.playlists {
|
||||
for v in self.playlists.values() {
|
||||
count += v.len();
|
||||
}
|
||||
count
|
||||
|
@ -98,7 +99,7 @@ impl Manifest {
|
|||
|
||||
let mut s = Self::default();
|
||||
log::debug!("Path: {p:?}");
|
||||
s.path = p.clone();
|
||||
s.path.clone_from(p);
|
||||
s.load(Some(p))?;
|
||||
Ok(s)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::str::FromStr;
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
|
||||
pub enum SongType {
|
||||
#[default]
|
||||
|
@ -12,14 +12,14 @@ pub enum SongType {
|
|||
Soundcloud,
|
||||
}
|
||||
|
||||
impl ToString for SongType {
|
||||
fn to_string(&self) -> String {
|
||||
let s = match self {
|
||||
SongType::Youtube => "Youtube",
|
||||
SongType::Spotify => "Spotify",
|
||||
SongType::Soundcloud => "Soundcloud",
|
||||
};
|
||||
String::from(s)
|
||||
impl Display for SongType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Soundcloud => write!(f, "SoundCloud")?,
|
||||
Self::Spotify => write!(f, "Spotify")?,
|
||||
Self::Youtube => write!(f, "YouTube")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,9 @@ pub struct Song {
|
|||
|
||||
#[allow(dead_code)]
|
||||
impl Song {
|
||||
pub fn from_url_str(url: String) -> Result<Self> {
|
||||
Self::from_url(url::Url::from_str(url.as_str())?)
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_url_str<S: ToString>(url: S) -> Result<Self> {
|
||||
Self::from_url(url::Url::from_str(&url.to_string())?)
|
||||
}
|
||||
pub fn from_url(url: url::Url) -> Result<Self> {
|
||||
Ok(Self {
|
||||
|
|
109
src/prompt.rs
109
src/prompt.rs
|
@ -1,108 +1,7 @@
|
|||
use std::{collections::HashMap, io::Write};
|
||||
use std::io::Write;
|
||||
|
||||
|
||||
//pub(crate) fn simple_prompt(p: &str) -> String {
|
||||
//
|
||||
// print!("{c}prompt{r}: {p} > ",
|
||||
// c=anstyle::AnsiColor::Cyan.render_fg(),
|
||||
// r=anstyle::Reset.render()
|
||||
// );
|
||||
//
|
||||
// // I dont care if it fails
|
||||
// let _ = std::io::stdout().flush();
|
||||
//
|
||||
// let mut buf = String::new();
|
||||
// let _ = std::io::stdin().read_line(&mut buf);
|
||||
//
|
||||
// buf.trim().to_string()
|
||||
//}
|
||||
|
||||
#[allow(dead_code)]
|
||||
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()
|
||||
);
|
||||
|
||||
for (i, op) in options.iter().enumerate() {
|
||||
println!(" - {}: {}", i, op);
|
||||
}
|
||||
|
||||
print!("> ");
|
||||
// I dont care if it fails
|
||||
let _ = std::io::stdout().flush();
|
||||
|
||||
let mut buf = String::new();
|
||||
let _ = std::io::stdin().read_line(&mut buf);
|
||||
|
||||
if let Ok(num) = buf.parse::<usize>() {
|
||||
if num <= options.len() {
|
||||
return num;
|
||||
} else {
|
||||
return prompt_with_list(p, options);
|
||||
}
|
||||
} else {
|
||||
return prompt_with_list(p, options);
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// );
|
||||
//
|
||||
// for (i, op) in options.iter().enumerate() {
|
||||
// println!(" - {}: {}", i, op);
|
||||
// }
|
||||
//
|
||||
// print!("> ");
|
||||
// // I dont care if it fails
|
||||
// let _ = std::io::stdout().flush();
|
||||
//
|
||||
// let mut buf = String::new();
|
||||
// let _ = std::io::stdin().read_line(&mut buf);
|
||||
//
|
||||
// if let Ok(num) = buf.trim().parse::<usize>() {
|
||||
// if let Some(g) = options.get(num) {
|
||||
// return g.clone();
|
||||
// } else {
|
||||
// return prompt_with_list_or_str(p, options);
|
||||
// }
|
||||
// } else {
|
||||
// return buf.trim().to_string();
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
#[allow(dead_code)]
|
||||
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()
|
||||
);
|
||||
|
||||
let mut keys = Vec::new();
|
||||
|
||||
for (k, v) in &options {
|
||||
println!(" - {}: {}", k, v);
|
||||
keys.push(k.trim().to_lowercase())
|
||||
}
|
||||
|
||||
print!("> ");
|
||||
|
||||
// I dont care if it fails
|
||||
let _ = std::io::stdout().flush();
|
||||
|
||||
let mut buf = String::new();
|
||||
let _ = std::io::stdin().read_line(&mut buf);
|
||||
if !keys.contains(&buf.trim().to_lowercase()) {
|
||||
return prompt_with_map(p, options);
|
||||
}
|
||||
buf.trim().to_string()
|
||||
}
|
||||
|
||||
pub fn prompt_bool(p: &str, default: Option<bool>) -> bool {
|
||||
pub fn yes_no(p: &str, default: Option<bool>) -> bool {
|
||||
if default == Some(true) {
|
||||
println!("{c}prompt{r}: {p} (Y/n)",
|
||||
c=anstyle::AnsiColor::Cyan.render_fg(),
|
||||
|
@ -132,7 +31,7 @@ pub fn prompt_bool(p: &str, default: Option<bool>) -> bool {
|
|||
Some(true) => return true,
|
||||
Some(false) => return false,
|
||||
None => {
|
||||
return prompt_bool(p, default);
|
||||
return yes_no(p, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +41,7 @@ pub fn prompt_bool(p: &str, default: Option<bool>) -> bool {
|
|||
"n" => false,
|
||||
c => {
|
||||
log::error!("'{c}' is invalid, type y (yes) or n (no)");
|
||||
return prompt_bool(p, default);
|
||||
yes_no(p, default)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,34 +6,34 @@ use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{song::Song
|
|||
|
||||
|
||||
|
||||
pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &String, name: &String, playlist: &String) -> anyhow::Result<()> {
|
||||
pub fn song(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &str, name: &String, playlist: &String) -> anyhow::Result<()> {
|
||||
|
||||
let mut playlists = manifest.get_playlists().keys().map(|f| f.clone()).collect::<Vec<String>>();
|
||||
let mut playlists = manifest.get_playlists().keys().cloned().collect::<Vec<String>>();
|
||||
|
||||
playlists.sort();
|
||||
|
||||
if !is_supported_host(url::Url::from_str(&url)?) {
|
||||
if !is_supported_host(&url::Url::from_str(url)?) {
|
||||
log::error!("Invalid or unsupported host name");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
|
||||
let song = Song::from_url_str(url.clone())?;
|
||||
let song = Song::from_url_str(url.to_string())?;
|
||||
manifest.add_song(playlist, name.clone(), song.clone());
|
||||
manifest.save(None)?;
|
||||
|
||||
let should_download = crate::prompt::prompt_bool("Download song now?", Some(false));
|
||||
let should_download = crate::prompt::yes_no("Download song now?", Some(false));
|
||||
|
||||
if should_download {
|
||||
downloader.download_song(cfg, &name, &song, &playlist, manifest.get_format())?;
|
||||
downloader.download_song(cfg, name, &song, playlist, manifest.get_format())?;
|
||||
crate::process_manager::wait_for_procs_untill(0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_playlist(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &String, name: &String) -> anyhow::Result<()> {
|
||||
pub fn playlist(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &str, name: &String) -> anyhow::Result<()> {
|
||||
let songs = downloader.download_playlist_nb(cfg, url, name, manifest.get_format())?;
|
||||
|
||||
if manifest.get_playlist(name).is_some() {
|
||||
|
|
|
@ -4,14 +4,14 @@ use crate::{config::{cli::CliCommand, ConfigWrapper}, downloader::Downloader, ma
|
|||
|
||||
|
||||
|
||||
pub async fn command_run(cfg: &ConfigWrapper, manifest: &mut Manifest) -> anyhow::Result<()> {
|
||||
pub fn command_run(cfg: &ConfigWrapper, manifest: &mut Manifest) -> anyhow::Result<()> {
|
||||
log::info!("Is in term: {}", cfg.isatty);
|
||||
//std::fs::write("./isatty", format!("{}\n", cfg.isatty))?;
|
||||
|
||||
let mut downloader = Downloader::new();
|
||||
match (&cfg.cli.command, cfg.isatty) {
|
||||
(None | Some(CliCommand::Download), true) => {
|
||||
match downloader.download_all(manifest, &cfg).await {
|
||||
match downloader.download_all(manifest, cfg) {
|
||||
Ok(count) => log::info!("Downloaded {count} songs"),
|
||||
Err(e) => {
|
||||
log::error!("Failed to download songs: {e}");
|
||||
|
@ -23,12 +23,12 @@ pub async fn command_run(cfg: &ConfigWrapper, manifest: &mut Manifest) -> anyhow
|
|||
match c {
|
||||
CliCommand::Download => unreachable!(),
|
||||
CliCommand::AddPlaylist { url, name } => {
|
||||
if let Err(e) = add::add_playlist(cfg, manifest, &mut downloader, url, name).await {
|
||||
if let Err(e) = add::playlist(cfg, manifest, &mut downloader, url, name) {
|
||||
log::error!("Failed to run 'add-playlist' commmand: {e}");
|
||||
}
|
||||
}
|
||||
CliCommand::Add { url, name, playlist } => {
|
||||
if let Err(e) = add::add(cfg, manifest, &mut downloader, url, name, playlist).await {
|
||||
if let Err(e) = add::song(cfg, manifest, &mut downloader, url, name, playlist) {
|
||||
log::error!("Failed to run 'add' command: {e}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ impl /* ComponentUi for */ ContextMenu {
|
|||
let w = gui.windows.get_window::<GuiSongEditor>(WindowIndex::SongEdit);
|
||||
w.set_active_song(pname, sname, song.get_url_str());
|
||||
gui.windows.open(WindowIndex::SongEdit, true);
|
||||
ui.close_menu()
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if ui.button("Download").clicked() {
|
||||
|
@ -19,7 +19,7 @@ impl /* ComponentUi for */ ContextMenu {
|
|||
log::error!("{e}");
|
||||
gui.throw_error(format!("Failed to download song {sname}: {e}"));
|
||||
}
|
||||
ui.close_menu()
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if ui.button("Open Source").clicked() {
|
||||
|
@ -27,18 +27,18 @@ impl /* ComponentUi for */ ContextMenu {
|
|||
log::error!("{e}");
|
||||
gui.throw_error(format!("Failed to open song source: {e}"));
|
||||
}
|
||||
ui.close_menu()
|
||||
ui.close_menu();
|
||||
}
|
||||
if ui.button("Play").clicked() {
|
||||
let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format());
|
||||
|
||||
if !p.exists() {
|
||||
gui.throw_error(format!("Song does not exist on disk"));
|
||||
gui.throw_error("Song does not exist on disk".to_string());
|
||||
} else if let Err(e) = open::that(p) {
|
||||
log::error!("{e}");
|
||||
gui.throw_error(format!("Failed to play song: {e}"));
|
||||
}
|
||||
ui.close_menu()
|
||||
ui.close_menu();
|
||||
}
|
||||
if ui.button("Delete from disk").clicked() {
|
||||
let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format());
|
||||
|
@ -51,7 +51,7 @@ impl /* ComponentUi for */ ContextMenu {
|
|||
}
|
||||
if ui.button(RichText::new("Delete").color(Color32::RED)).clicked() {
|
||||
gui.throw_error("TODO");
|
||||
ui.close_menu()
|
||||
ui.close_menu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ use crate::ui::gui::{windows::WindowIndex, Gui};
|
|||
use super::Component;
|
||||
|
||||
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
pub struct NavBar;
|
||||
#[warn(clippy::pedantic)]
|
||||
|
||||
impl Component for NavBar {
|
||||
fn ui(gui: &mut Gui, ctx: &egui::Context) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use egui::{Color32, RichText};
|
||||
use egui::Color32;
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
|
||||
use crate::manifest::song::SongType;
|
||||
|
@ -60,7 +60,7 @@ impl ComponentUi for SongList {
|
|||
let mut songs = Vec::new();
|
||||
for (pname, p) in playlists {
|
||||
for (sname, s) in p {
|
||||
songs.push((pname.clone(), sname, s))
|
||||
songs.push((pname.clone(), sname, s));
|
||||
}
|
||||
}
|
||||
songs
|
||||
|
@ -91,10 +91,8 @@ impl ComponentUi for SongList {
|
|||
if !s.get_url_str().contains(&filter_clean) {
|
||||
continue;
|
||||
}
|
||||
} else if !filter_clean.is_empty() {
|
||||
if !sname.to_lowercase().contains(&filter_clean) {
|
||||
continue;
|
||||
}
|
||||
} else if !filter_clean.is_empty() && !sname.to_lowercase().contains(&filter_clean) {
|
||||
continue;
|
||||
}
|
||||
body.row(18.0, |mut row| {
|
||||
|
||||
|
@ -121,7 +119,7 @@ impl ComponentUi for SongList {
|
|||
row.response()
|
||||
.context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s));
|
||||
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,12 +31,10 @@ impl Gui {
|
|||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([400.0, 300.0])
|
||||
.with_min_inner_size([300.0, 220.0]),
|
||||
// .with_icon(
|
||||
// // NOTE: Adding an icon is optional
|
||||
// eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..])
|
||||
// .expect("Failed to load icon"),
|
||||
// ),
|
||||
.with_min_inner_size([300.0, 220.0])
|
||||
.with_icon(
|
||||
eframe::icon_data::from_png_bytes(&include_bytes!("../../../assets/icon.png")[..])?,
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -50,9 +48,11 @@ impl Gui {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
pub fn throw_error(&mut self, text: impl ToString) {
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
pub fn throw_error<S: ToString>(&mut self, text: S) {
|
||||
let w = self.windows.get_window::<windows::error::GuiError>(WindowIndex::Error);
|
||||
w.set_error_message(text);
|
||||
w.set_error_message(&text);
|
||||
self.windows.open(WindowIndex::Error, true);
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ impl eframe::App for Gui {
|
|||
downloader: self.downloader.clone(),
|
||||
manifest: self.manifest.clone(),
|
||||
};
|
||||
self.windows.ui(&mut state, ctx).unwrap();
|
||||
self.windows.ui(&mut state, ctx);
|
||||
self.cfg = state.cfg;
|
||||
self.downloader = state.downloader;
|
||||
self.manifest = state.manifest;
|
||||
|
|
|
@ -3,6 +3,7 @@ use egui::{Color32, Label, RichText};
|
|||
use super::{State, Window};
|
||||
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GuiError {
|
||||
text: String,
|
||||
|
@ -26,7 +27,7 @@ impl Window for GuiError {
|
|||
}
|
||||
|
||||
impl GuiError {
|
||||
pub fn set_error_message<S: ToString>(&mut self, text: S) {
|
||||
pub fn set_error_message<S: ToString>(&mut self, text: &S) {
|
||||
self.text = text.to_string();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::{State, Window};
|
||||
|
||||
|
||||
|
||||
#[allow(clippy::pedantic)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GuiImportPlaylist {
|
||||
ed_name: String,
|
||||
|
@ -41,7 +41,7 @@ impl Window for GuiImportPlaylist {
|
|||
log::error!("Playlist {name} already exists");
|
||||
}
|
||||
|
||||
let songs = state.downloader.download_playlist_nb(&state.cfg, &url, &name, &state.manifest.get_format()).unwrap();
|
||||
let songs = state.downloader.download_playlist_nb(&state.cfg, &url, &name, state.manifest.get_format()).unwrap();
|
||||
state.manifest.add_playlist(name.clone());
|
||||
|
||||
let playlist = state.manifest.get_playlist_mut(&name).expect("Unreachable");
|
||||
|
|
|
@ -45,17 +45,17 @@ impl WindowManager {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_open(&self, id: &WindowIndex) -> bool {
|
||||
*self.opened.get(id).unwrap()
|
||||
pub fn is_open(&self, id: WindowIndex) -> bool {
|
||||
*self.opened.get(&id).unwrap()
|
||||
}
|
||||
|
||||
pub fn open(&mut self, id: WindowIndex, open: bool) {
|
||||
self.opened.insert(id, open);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, state: &mut State, ctx: &egui::Context) -> anyhow::Result<()> {
|
||||
pub fn ui(&mut self, state: &mut State, ctx: &egui::Context) {
|
||||
for (id, window) in &mut self.windows {
|
||||
if !self.opened.contains_key(&id) {
|
||||
if !self.opened.contains_key(id) {
|
||||
self.opened.insert(*id, false);
|
||||
}
|
||||
let open = self.opened.get_mut(id).unwrap();
|
||||
|
@ -63,8 +63,6 @@ impl WindowManager {
|
|||
log::error!("Window {id:?} errored: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_window<T: Window + 'static>(&mut self, id: WindowIndex) -> &mut Box<T> {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use anyhow::{Result, bail};
|
||||
use anyhow::bail;
|
||||
use egui::Color32;
|
||||
use crate::ui::gui::Gui;
|
||||
|
||||
use super::{State, Window};
|
||||
|
||||
|
@ -44,7 +43,7 @@ impl Window for GuiSongEditor {
|
|||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Type: ");
|
||||
ui.label(&song.get_type().to_string());
|
||||
ui.label(song.get_type().to_string());
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -67,7 +66,7 @@ impl Window for GuiSongEditor {
|
|||
bail!("Failed to get song (2)");
|
||||
};
|
||||
|
||||
*song.get_url_str_mut() = self.ed_url.clone();
|
||||
song.get_url_str_mut().clone_from(&self.ed_url);
|
||||
}
|
||||
|
||||
let Some(playlist) = state.manifest.get_playlist_mut(&playlist) else {
|
||||
|
@ -86,10 +85,10 @@ impl Window for GuiSongEditor {
|
|||
}
|
||||
|
||||
impl GuiSongEditor {
|
||||
pub fn set_active_song(&mut self, pname: &String, sname: &String, url: &String) {
|
||||
self.song.0 = pname.clone();
|
||||
self.song.1 = sname.clone();
|
||||
self.ed_name = sname.clone();
|
||||
self.ed_url = url.clone();
|
||||
pub fn set_active_song(&mut self, pname: &str, sname: &str, url: &str) {
|
||||
self.song.0 = pname.to_string();
|
||||
self.song.1 = sname.to_string();
|
||||
self.ed_name = sname.to_string();
|
||||
self.ed_url = url.to_string();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ use super::{State, Window};
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GuiNewSong {
|
||||
ed_type: SongType,
|
||||
ed_name: String,
|
||||
ed_playlist: Option<String>,
|
||||
ed_url: String,
|
||||
typ: SongType,
|
||||
name: String,
|
||||
playlist: Option<String>,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl Window for GuiNewSong {
|
||||
|
@ -18,33 +18,33 @@ impl Window for GuiNewSong {
|
|||
ui.horizontal(|ui| {
|
||||
ui.label("Type: ");
|
||||
egui::ComboBox::from_id_source("new_song_window_type")
|
||||
.selected_text(format!("{:?}", self.ed_type))
|
||||
.selected_text(format!("{:?}", self.typ))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut self.ed_type, SongType::Youtube, "Youtube");
|
||||
ui.selectable_value(&mut self.ed_type, SongType::Spotify, "Spotify");
|
||||
ui.selectable_value(&mut self.ed_type, SongType::Soundcloud, "Soundcloud");
|
||||
ui.selectable_value(&mut self.typ, SongType::Youtube, "Youtube");
|
||||
ui.selectable_value(&mut self.typ, SongType::Spotify, "Spotify");
|
||||
ui.selectable_value(&mut self.typ, SongType::Soundcloud, "Soundcloud");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Name: ");
|
||||
ui.text_edit_singleline(&mut self.ed_name);
|
||||
ui.text_edit_singleline(&mut self.name);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Playlist: ");
|
||||
egui::ComboBox::from_id_source("new_song_window_playlist")
|
||||
.selected_text(format!("{}", self.ed_playlist.clone().unwrap_or("".to_string())))
|
||||
.selected_text(self.playlist.clone().unwrap_or_default())
|
||||
.show_ui(ui, |ui| {
|
||||
for p in state.manifest.get_playlists().keys() {
|
||||
ui.selectable_value(&mut self.ed_playlist, Option::Some(p.clone()), p.as_str());
|
||||
ui.selectable_value(&mut self.playlist, Option::Some(p.clone()), p.as_str());
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Url: ");
|
||||
ui.text_edit_singleline(&mut self.ed_url);
|
||||
ui.text_edit_singleline(&mut self.url);
|
||||
});
|
||||
|
||||
if ui.button("Save").clicked() {
|
||||
|
@ -53,13 +53,13 @@ impl Window for GuiNewSong {
|
|||
});
|
||||
|
||||
if save {
|
||||
let Some(playlist) = state.manifest.get_playlist_mut(&self.ed_playlist.clone().unwrap()) else {
|
||||
let Some(playlist) = state.manifest.get_playlist_mut(&self.playlist.clone().unwrap()) else {
|
||||
panic!("couldnt find playlist from a preset playlist list????????????");
|
||||
};
|
||||
|
||||
playlist.add_song(
|
||||
self.ed_name.clone(),
|
||||
Song::from_url_str(self.ed_url.clone()).unwrap().set_type(self.ed_type.clone()).clone()
|
||||
self.name.clone(),
|
||||
Song::from_url_str(self.url.clone()).unwrap().set_type(self.typ.clone()).clone()
|
||||
);
|
||||
|
||||
|
||||
|
|
10
src/util.rs
10
src/util.rs
|
@ -2,16 +2,12 @@ use std::{any::Any, path::PathBuf};
|
|||
|
||||
use crate::{constants, manifest::Format};
|
||||
|
||||
pub(crate) fn is_supported_host(url: url::Url) -> bool {
|
||||
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
|
||||
}
|
||||
matches!(host.unwrap(), "youtube.com" | "youtu.be" | "open.spotify.com")
|
||||
}
|
||||
|
||||
pub(crate) fn is_program_in_path(program: &str) -> Option<PathBuf> {
|
||||
|
@ -66,7 +62,7 @@ pub fn get_song_path/*<P: TryInto<PathBuf>>*/(/*basepath: Option<P>,*/ pname: &S
|
|||
path = std::env::current_dir().unwrap_or(PathBuf::new());
|
||||
}
|
||||
} else {*/
|
||||
let mut path = std::env::current_dir().unwrap_or(PathBuf::new());
|
||||
let mut path = std::env::current_dir().unwrap_or_default();
|
||||
//}
|
||||
// TODO: Get this from cfg
|
||||
path.push("out");
|
||||
|
|
Loading…
Reference in New Issue
Block a user