diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..a0a26e7 Binary files /dev/null and b/assets/icon.png differ diff --git a/src/config/cli.rs b/src/config/cli.rs index 4dd884e..ae84d89 100644 --- a/src/config/cli.rs +++ b/src/config/cli.rs @@ -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, diff --git a/src/config/mod.rs b/src/config/mod.rs index 869fee5..3aa0cef 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -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 { + #[allow(clippy::field_reassign_with_default)] + pub fn parse() -> Result { 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 { + pub fn parse(cli: &CliArgs) -> Result { 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 { + fn setup_config(cli: &CliArgs) -> Result { 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 { diff --git a/src/constants.rs b/src/constants.rs index 6b0f8de..c9e0964 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -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::*; \ No newline at end of file +pub use _m::*; diff --git a/src/downloader.rs b/src/downloader.rs index 30dfd6a..c514250 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -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 { + pub fn download_all(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result { 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 { + pub fn download_playlist(&mut self, cfg: &ConfigWrapper, url: &str, pname: &str, format: &Format) -> anyhow::Result { 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> { + pub fn download_playlist_nb(&mut self, cfg: &ConfigWrapper, url: &str, pname: &str, format: &Format) -> anyhow::Result> { 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::>(); + let mut split_text = line.split('|').collect::>(); 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(()); } diff --git a/src/logger.rs b/src/logger.rs index 82f7ebb..f89207a 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -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 { @@ -11,4 +11,4 @@ pub fn init_logger(debug: bool) { .format_timestamp(None) .filter_level(level) .init(); -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index e2f71d0..f66d75c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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); } diff --git a/src/manifest/mod.rs b/src/manifest/mod.rs index dbe0d99..1c9e015 100644 --- a/src/manifest/mod.rs +++ b/src/manifest/mod.rs @@ -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) } diff --git a/src/manifest/song.rs b/src/manifest/song.rs index eba75ff..7d7960a 100644 --- a/src/manifest/song.rs +++ b/src/manifest/song.rs @@ -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::from_url(url::Url::from_str(url.as_str())?) + #[allow(clippy::needless_pass_by_value)] + pub fn from_url_str(url: S) -> Result { + Self::from_url(url::Url::from_str(&url.to_string())?) } pub fn from_url(url: url::Url) -> Result { Ok(Self { diff --git a/src/prompt.rs b/src/prompt.rs index de9d891..daa059c 100644 --- a/src/prompt.rs +++ b/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::() { - 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::() { -// 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 { +pub fn yes_no(p: &str, default: Option) -> 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 { 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 { "n" => false, c => { log::error!("'{c}' is invalid, type y (yes) or n (no)"); - return prompt_bool(p, default); + yes_no(p, default) } } } diff --git a/src/ui/cli/add.rs b/src/ui/cli/add.rs index 4ee9fb5..4250b99 100644 --- a/src/ui/cli/add.rs +++ b/src/ui/cli/add.rs @@ -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::>(); + let mut playlists = manifest.get_playlists().keys().cloned().collect::>(); 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() { diff --git a/src/ui/cli/mod.rs b/src/ui/cli/mod.rs index 6c975ad..cf8f53f 100644 --- a/src/ui/cli/mod.rs +++ b/src/ui/cli/mod.rs @@ -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}"); } } diff --git a/src/ui/gui/components/context_menu.rs b/src/ui/gui/components/context_menu.rs index abea285..27deec1 100644 --- a/src/ui/gui/components/context_menu.rs +++ b/src/ui/gui/components/context_menu.rs @@ -11,7 +11,7 @@ impl /* ComponentUi for */ ContextMenu { let w = gui.windows.get_window::(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(); } } } diff --git a/src/ui/gui/components/nav.rs b/src/ui/gui/components/nav.rs index 88d46a6..4480437 100644 --- a/src/ui/gui/components/nav.rs +++ b/src/ui/gui/components/nav.rs @@ -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) { diff --git a/src/ui/gui/components/song_list.rs b/src/ui/gui/components/song_list.rs index 5b62b29..98c3a61 100644 --- a/src/ui/gui/components/song_list.rs +++ b/src/ui/gui/components/song_list.rs @@ -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)); - }) + }); } }); }); diff --git a/src/ui/gui/mod.rs b/src/ui/gui/mod.rs index 1ef225c..5ac0e28 100644 --- a/src/ui/gui/mod.rs +++ b/src/ui/gui/mod.rs @@ -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(&mut self, text: S) { let w = self.windows.get_window::(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; diff --git a/src/ui/gui/windows/error.rs b/src/ui/gui/windows/error.rs index 5c7d166..a10dd1c 100644 --- a/src/ui/gui/windows/error.rs +++ b/src/ui/gui/windows/error.rs @@ -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(&mut self, text: S) { + pub fn set_error_message(&mut self, text: &S) { self.text = text.to_string(); } } diff --git a/src/ui/gui/windows/import_playlist.rs b/src/ui/gui/windows/import_playlist.rs index 7170b4e..a7a373f 100644 --- a/src/ui/gui/windows/import_playlist.rs +++ b/src/ui/gui/windows/import_playlist.rs @@ -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"); diff --git a/src/ui/gui/windows/mod.rs b/src/ui/gui/windows/mod.rs index a6cbcd3..5eac44c 100644 --- a/src/ui/gui/windows/mod.rs +++ b/src/ui/gui/windows/mod.rs @@ -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(&mut self, id: WindowIndex) -> &mut Box { diff --git a/src/ui/gui/windows/song_edit.rs b/src/ui/gui/windows/song_edit.rs index 9fa6a48..39e0182 100644 --- a/src/ui/gui/windows/song_edit.rs +++ b/src/ui/gui/windows/song_edit.rs @@ -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(); } } diff --git a/src/ui/gui/windows/song_new.rs b/src/ui/gui/windows/song_new.rs index 8728bfc..8c3ddb6 100644 --- a/src/ui/gui/windows/song_new.rs +++ b/src/ui/gui/windows/song_new.rs @@ -3,10 +3,10 @@ use super::{State, Window}; #[derive(Debug, Default)] pub struct GuiNewSong { - ed_type: SongType, - ed_name: String, - ed_playlist: Option, - ed_url: String, + typ: SongType, + name: String, + playlist: Option, + 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() ); diff --git a/src/util.rs b/src/util.rs index 40c246b..d54884d 100644 --- a/src/util.rs +++ b/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 { @@ -66,7 +62,7 @@ pub fn get_song_path/*>*/(/*basepath: Option

,*/ 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");