Clippy pedantic fixes

This commit is contained in:
Gvidas Juknevičius 2024-09-22 02:16:18 +03:00
parent 387a5eaf4a
commit f7008e882c
Signed by: MCorange
GPG Key ID: 12B1346D720B7FBB
22 changed files with 162 additions and 275 deletions

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -2,6 +2,7 @@ use camino::Utf8PathBuf;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
#[allow(clippy::pedantic)]
#[derive(Debug, Parser, Default, Clone)] #[derive(Debug, Parser, Default, Clone)]
pub struct CliArgs { pub struct CliArgs {
/// Show more info /// Show more info
@ -25,6 +26,7 @@ pub struct CliArgs {
} }
#[allow(clippy::pedantic)]
#[derive(Debug, Subcommand, Clone)] #[derive(Debug, Subcommand, Clone)]
pub enum CliCommand { pub enum CliCommand {
Download, Download,

View File

@ -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 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"; // const SPOTDL_DL_URL: &'static str = "https://github.com/spotDL/spotify-downloader/archive/refs/heads/master.zip";
#[allow(clippy::pedantic)]
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ConfigWrapper { pub struct ConfigWrapper {
pub cfg: Config, pub cfg: Config,
@ -19,17 +19,20 @@ pub struct ConfigWrapper {
pub isatty: bool pub isatty: bool
} }
#[allow(clippy::pedantic)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)] #[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct Config { pub struct Config {
pub ytdlp: ConfigYtdlp, pub ytdlp: ConfigYtdlp,
pub spotdl: ConfigSpotdl, pub spotdl: ConfigSpotdl,
} }
#[allow(clippy::pedantic)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)] #[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct ConfigYtdlp { pub struct ConfigYtdlp {
pub path: PathBuf, pub path: PathBuf,
} }
#[allow(clippy::pedantic)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)] #[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct ConfigSpotdl { pub struct ConfigSpotdl {
pub path: PathBuf, pub path: PathBuf,
@ -37,21 +40,22 @@ pub struct ConfigSpotdl {
impl ConfigWrapper { impl ConfigWrapper {
pub async fn parse() -> Result<Self> { #[allow(clippy::field_reassign_with_default)]
pub fn parse() -> Result<Self> {
let mut s = Self::default(); let mut s = Self::default();
s.cli = cli::CliArgs::parse(); s.cli = cli::CliArgs::parse();
crate::logger::init_logger(s.cli.debug); crate::logger::init(s.cli.debug);
s.cfg = Config::parse(&s.cli).await?; s.cfg = Config::parse(&s.cli)?;
s.isatty = isatty(); s.isatty = isatty();
Ok(s) Ok(s)
} }
} }
impl Config { impl Config {
pub async fn parse(cli: &CliArgs) -> Result<Self> { pub fn parse(cli: &CliArgs) -> Result<Self> {
if !cli.config.exists() { if !cli.config.exists() {
log::info!("Config doesnt exist"); 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)?; let data = std::fs::read_to_string(&cli.config)?;
@ -59,63 +63,51 @@ impl Config {
Ok(data) Ok(data)
} }
async fn setup_config(cli: &CliArgs) -> Result<Self> { fn setup_config(cli: &CliArgs) -> Result<Self> {
let mut s = Self::default(); let mut s = Self::default();
let mut error = false; let mut error = false;
match util::is_program_in_path("yt-dlp") { if let Some(p) = util::is_program_in_path("yt-dlp") {
Some(p) => { s.ytdlp.path = p;
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; error = true;
log::error!("could not find yt-dlp, please install it."); log::error!("could not find spotdl, please install it. ");
log::info!(" - With winget (Windows only) (recommended):"); log::info!(" - With pip (from python) (Cross platform) (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!(" - Make sure you have python installed - https://www.python.org/downloads/");
log::info!(" - run `winget install yt-dlp`"); log::info!(" - pip install spotdl");
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)")
} }
} }
match util::is_program_in_path("spotdl") { if util::is_program_in_path("ffmpeg").is_none() {
Some(p) => { error = true;
s.spotdl.path = p; 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");
None => { log::info!(" - run `winget install --id=Gyan.FFmpeg -e`");
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); log::info!(" - With chocolatey (Windows only):");
if res { log::info!(" - Make sure you have chocolatey installed - https://chocolatey.org/install");
s.spotdl.path = PathBuf::from("UNUSED"); log::info!(" - run `choco install ffmpeg` as Admin");
} 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 !error { if !error {

View File

@ -1,15 +1,15 @@
#[cfg(target_family="windows")] #[cfg(target_family="windows")]
mod constants { mod _m {
pub const PATH_VAR_SEP: &'static str = ";"; pub const PATH_VAR_SEP: &str = ";";
pub const EXEC_EXT: &'static str = "exe"; pub const EXEC_EXT: &str = "exe";
} }
#[cfg(target_family="unix")] #[cfg(target_family="unix")]
mod constants { mod _m {
pub const PATH_VAR_SEP: &'static str = ":"; pub const PATH_VAR_SEP: &str = ":";
pub const EXEC_EXT: &'static str = ""; pub const EXEC_EXT: &str = "";
} }
pub use constants::*; pub use _m::*;

View File

@ -40,8 +40,8 @@ impl Downloader {
self.nb_cache.len() + crate::process_manager::proc_count() 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<()> { pub fn download_song_nb(&mut self, cfg: &ConfigWrapper, pname: &str, sname: &str, song: &Song, format: &Format) -> anyhow::Result<()> {
self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), format.clone())); self.nb_cache.push((pname.to_string(), sname.to_string(), song.clone(), format.clone()));
self.nb_initial_song_count += 1; self.nb_initial_song_count += 1;
self.download_all_nb_poll(cfg)?; self.download_all_nb_poll(cfg)?;
Ok(()) 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(); let format = manifest.get_format();
for (name, playlist) in manifest.get_playlists() { for (name, playlist) in manifest.get_playlists() {
for (song_name, song) in playlist.get_songs() { 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)?; self.count += crate::process_manager::wait_for_procs_untill(10)?;
} }
} }
@ -90,7 +90,7 @@ impl Downloader {
} }
#[allow(dead_code)] #[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)?; self.download_playlist_nb(cfg, url, pname, format)?;
let mut count = 0; let mut count = 0;
while let Some(c) = self.download_all_nb_poll(cfg)? { while let Some(c) = self.download_all_nb_poll(cfg)? {
@ -99,14 +99,14 @@ impl Downloader {
Ok(count) 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"); 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); let mut cmd = tokio::process::Command::new(&cfg.cfg.ytdlp.path);
cmd.args([ cmd.args([
"--flat-playlist", "--flat-playlist",
"--simulate", "--simulate",
"-O", "%(url)s|%(title)s", "-O", "%(url)s|%(title)s",
url.as_str() url
]); ]);
cmd cmd
.stderr(Stdio::null()) .stderr(Stdio::null())
@ -119,11 +119,11 @@ impl Downloader {
let out = futures::executor::block_on(ftr)?.stdout; let out = futures::executor::block_on(ftr)?.stdout;
let out = String::from_utf8(out)?; let out = String::from_utf8(out)?;
for line in out.lines() { 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 url = split_text.swap_remove(0).to_string();
let sname = split_text.join("|"); let sname = split_text.join("|");
let song = Song::from_url_str(url)?.set_type(SongType::Youtube).clone(); 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()); ret.insert(sname, song.clone());
} }
self.nb_initial_song_count += out.lines().count(); self.nb_initial_song_count += out.lines().count();
@ -131,7 +131,7 @@ impl Downloader {
Ok(ret) 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_dir = format!("{}/{playlist}", cfg.cli.output);
let dl_file = format!("{dl_dir}/{}.{}", name, &format); let dl_file = format!("{dl_dir}/{}.{}", name, &format);
log::debug!("Checking: {dl_file}"); log::debug!("Checking: {dl_file}");
@ -168,7 +168,7 @@ impl Downloader {
]); ]);
cmd cmd
} }
url => { url @ SongType::Soundcloud => {
log::error!("Unknown or unsupported hostname '{:?}'", url); log::error!("Unknown or unsupported hostname '{:?}'", url);
return Ok(()); return Ok(());
} }

View File

@ -1,7 +1,7 @@
use log::LevelFilter; use log::LevelFilter;
pub fn init_logger(debug: bool) { pub fn init(debug: bool) {
let level = if debug { let level = if debug {
LevelFilter::Debug LevelFilter::Debug
} else { } else {

View File

@ -8,15 +8,14 @@ mod manifest;
mod logger; mod logger;
mod downloader; mod downloader;
mod util; mod util;
mod prompt;
mod config; mod config;
mod constants; mod constants;
mod process_manager; mod process_manager;
mod ui; mod ui;
mod prompt;
#[tokio::main] fn main() {
async fn main() { let Ok(cfg) = ConfigWrapper::parse() else {
let Ok(cfg) = ConfigWrapper::parse().await else {
return; 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);
} }

View File

@ -2,6 +2,7 @@
pub mod song; pub mod song;
pub mod playlist; pub mod playlist;
use playlist::Playlist;
use song::Song; use song::Song;
use std::{collections::HashMap, fmt::{Debug, Display}, path::PathBuf}; use std::{collections::HashMap, fmt::{Debug, Display}, path::PathBuf};
@ -10,7 +11,7 @@ use anyhow::{bail, Result};
use serde::{Deserialize, Serialize}; 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) self.get_playlist_mut(playlist_name)?.get_song_mut(name)
} }
pub fn add_playlist(&mut self, playlist_name: String) { 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> { pub fn get_playlist(&self, playlist_name: &String) -> Option<&playlist::Playlist> {
self.playlists.get(playlist_name) self.playlists.get(playlist_name)
@ -67,7 +68,7 @@ impl Manifest {
} }
pub fn get_song_count(&self) -> usize { pub fn get_song_count(&self) -> usize {
let mut count = 0; let mut count = 0;
for (_, v) in &self.playlists { for v in self.playlists.values() {
count += v.len(); count += v.len();
} }
count count
@ -98,7 +99,7 @@ impl Manifest {
let mut s = Self::default(); let mut s = Self::default();
log::debug!("Path: {p:?}"); log::debug!("Path: {p:?}");
s.path = p.clone(); s.path.clone_from(p);
s.load(Some(p))?; s.load(Some(p))?;
Ok(s) Ok(s)
} }

View File

@ -1,9 +1,9 @@
use std::str::FromStr; use std::{fmt::Display, str::FromStr};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[allow(clippy::pedantic)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
pub enum SongType { pub enum SongType {
#[default] #[default]
@ -12,14 +12,14 @@ pub enum SongType {
Soundcloud, Soundcloud,
} }
impl ToString for SongType { impl Display for SongType {
fn to_string(&self) -> String { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self { match self {
SongType::Youtube => "Youtube", Self::Soundcloud => write!(f, "SoundCloud")?,
SongType::Spotify => "Spotify", Self::Spotify => write!(f, "Spotify")?,
SongType::Soundcloud => "Soundcloud", Self::Youtube => write!(f, "YouTube")?,
}; }
String::from(s) Ok(())
} }
} }
@ -31,8 +31,9 @@ pub struct Song {
#[allow(dead_code)] #[allow(dead_code)]
impl Song { impl Song {
pub fn from_url_str(url: String) -> Result<Self> { #[allow(clippy::needless_pass_by_value)]
Self::from_url(url::Url::from_str(url.as_str())?) 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> { pub fn from_url(url: url::Url) -> Result<Self> {
Ok(Self { Ok(Self {

View File

@ -1,108 +1,7 @@
use std::{collections::HashMap, io::Write}; use std::io::Write;
//pub(crate) fn simple_prompt(p: &str) -> String { pub fn yes_no(p: &str, default: Option<bool>) -> bool {
//
// 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 {
if default == Some(true) { if default == Some(true) {
println!("{c}prompt{r}: {p} (Y/n)", println!("{c}prompt{r}: {p} (Y/n)",
c=anstyle::AnsiColor::Cyan.render_fg(), 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(true) => return true,
Some(false) => return false, Some(false) => return false,
None => { 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, "n" => false,
c => { c => {
log::error!("'{c}' is invalid, type y (yes) or n (no)"); log::error!("'{c}' is invalid, type y (yes) or n (no)");
return prompt_bool(p, default); yes_no(p, default)
} }
} }
} }

View File

@ -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(); 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"); log::error!("Invalid or unsupported host name");
return Ok(()); 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.add_song(playlist, name.clone(), song.clone());
manifest.save(None)?; 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 { 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)?; crate::process_manager::wait_for_procs_untill(0)?;
} }
Ok(()) 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())?; let songs = downloader.download_playlist_nb(cfg, url, name, manifest.get_format())?;
if manifest.get_playlist(name).is_some() { if manifest.get_playlist(name).is_some() {

View File

@ -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); log::info!("Is in term: {}", cfg.isatty);
//std::fs::write("./isatty", format!("{}\n", cfg.isatty))?; //std::fs::write("./isatty", format!("{}\n", cfg.isatty))?;
let mut downloader = Downloader::new(); let mut downloader = Downloader::new();
match (&cfg.cli.command, cfg.isatty) { match (&cfg.cli.command, cfg.isatty) {
(None | Some(CliCommand::Download), true) => { (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"), Ok(count) => log::info!("Downloaded {count} songs"),
Err(e) => { Err(e) => {
log::error!("Failed to download songs: {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 { match c {
CliCommand::Download => unreachable!(), CliCommand::Download => unreachable!(),
CliCommand::AddPlaylist { url, name } => { 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}"); log::error!("Failed to run 'add-playlist' commmand: {e}");
} }
} }
CliCommand::Add { url, name, playlist } => { 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}"); log::error!("Failed to run 'add' command: {e}");
} }
} }

View File

@ -11,7 +11,7 @@ impl /* ComponentUi for */ ContextMenu {
let w = gui.windows.get_window::<GuiSongEditor>(WindowIndex::SongEdit); let w = gui.windows.get_window::<GuiSongEditor>(WindowIndex::SongEdit);
w.set_active_song(pname, sname, song.get_url_str()); w.set_active_song(pname, sname, song.get_url_str());
gui.windows.open(WindowIndex::SongEdit, true); gui.windows.open(WindowIndex::SongEdit, true);
ui.close_menu() ui.close_menu();
} }
if ui.button("Download").clicked() { if ui.button("Download").clicked() {
@ -19,7 +19,7 @@ impl /* ComponentUi for */ ContextMenu {
log::error!("{e}"); log::error!("{e}");
gui.throw_error(format!("Failed to download song {sname}: {e}")); gui.throw_error(format!("Failed to download song {sname}: {e}"));
} }
ui.close_menu() ui.close_menu();
} }
if ui.button("Open Source").clicked() { if ui.button("Open Source").clicked() {
@ -27,18 +27,18 @@ impl /* ComponentUi for */ ContextMenu {
log::error!("{e}"); log::error!("{e}");
gui.throw_error(format!("Failed to open song source: {e}")); gui.throw_error(format!("Failed to open song source: {e}"));
} }
ui.close_menu() ui.close_menu();
} }
if ui.button("Play").clicked() { if ui.button("Play").clicked() {
let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format()); let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format());
if !p.exists() { 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) { } else if let Err(e) = open::that(p) {
log::error!("{e}"); log::error!("{e}");
gui.throw_error(format!("Failed to play song: {e}")); gui.throw_error(format!("Failed to play song: {e}"));
} }
ui.close_menu() ui.close_menu();
} }
if ui.button("Delete from disk").clicked() { if ui.button("Delete from disk").clicked() {
let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format()); 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() { if ui.button(RichText::new("Delete").color(Color32::RED)).clicked() {
gui.throw_error("TODO"); gui.throw_error("TODO");
ui.close_menu() ui.close_menu();
} }
} }
} }

View File

@ -3,8 +3,9 @@ use crate::ui::gui::{windows::WindowIndex, Gui};
use super::Component; use super::Component;
#[allow(clippy::pedantic)]
pub struct NavBar; pub struct NavBar;
#[warn(clippy::pedantic)]
impl Component for NavBar { impl Component for NavBar {
fn ui(gui: &mut Gui, ctx: &egui::Context) { fn ui(gui: &mut Gui, ctx: &egui::Context) {

View File

@ -1,4 +1,4 @@
use egui::{Color32, RichText}; use egui::Color32;
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder};
use crate::manifest::song::SongType; use crate::manifest::song::SongType;
@ -60,7 +60,7 @@ impl ComponentUi for SongList {
let mut songs = Vec::new(); let mut songs = Vec::new();
for (pname, p) in playlists { for (pname, p) in playlists {
for (sname, s) in p { for (sname, s) in p {
songs.push((pname.clone(), sname, s)) songs.push((pname.clone(), sname, s));
} }
} }
songs songs
@ -91,10 +91,8 @@ impl ComponentUi for SongList {
if !s.get_url_str().contains(&filter_clean) { if !s.get_url_str().contains(&filter_clean) {
continue; continue;
} }
} else if !filter_clean.is_empty() { } else if !filter_clean.is_empty() && !sname.to_lowercase().contains(&filter_clean) {
if !sname.to_lowercase().contains(&filter_clean) { continue;
continue;
}
} }
body.row(18.0, |mut row| { body.row(18.0, |mut row| {
@ -121,7 +119,7 @@ impl ComponentUi for SongList {
row.response() row.response()
.context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s)); .context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s));
}) });
} }
}); });
}); });

View File

@ -31,12 +31,10 @@ impl Gui {
let native_options = eframe::NativeOptions { let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default() viewport: egui::ViewportBuilder::default()
.with_inner_size([400.0, 300.0]) .with_inner_size([400.0, 300.0])
.with_min_inner_size([300.0, 220.0]), .with_min_inner_size([300.0, 220.0])
// .with_icon( .with_icon(
// // NOTE: Adding an icon is optional eframe::icon_data::from_png_bytes(&include_bytes!("../../../assets/icon.png")[..])?,
// eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..]) ),
// .expect("Failed to load icon"),
// ),
..Default::default() ..Default::default()
}; };
@ -50,9 +48,11 @@ impl Gui {
Ok(()) 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); 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); self.windows.open(WindowIndex::Error, true);
} }
} }
@ -66,7 +66,7 @@ impl eframe::App for Gui {
downloader: self.downloader.clone(), downloader: self.downloader.clone(),
manifest: self.manifest.clone(), manifest: self.manifest.clone(),
}; };
self.windows.ui(&mut state, ctx).unwrap(); self.windows.ui(&mut state, ctx);
self.cfg = state.cfg; self.cfg = state.cfg;
self.downloader = state.downloader; self.downloader = state.downloader;
self.manifest = state.manifest; self.manifest = state.manifest;

View File

@ -3,6 +3,7 @@ use egui::{Color32, Label, RichText};
use super::{State, Window}; use super::{State, Window};
#[allow(clippy::pedantic)]
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct GuiError { pub struct GuiError {
text: String, text: String,
@ -26,7 +27,7 @@ impl Window for GuiError {
} }
impl 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(); self.text = text.to_string();
} }
} }

View File

@ -1,7 +1,7 @@
use super::{State, Window}; use super::{State, Window};
#[allow(clippy::pedantic)]
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct GuiImportPlaylist { pub struct GuiImportPlaylist {
ed_name: String, ed_name: String,
@ -41,7 +41,7 @@ impl Window for GuiImportPlaylist {
log::error!("Playlist {name} already exists"); 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()); state.manifest.add_playlist(name.clone());
let playlist = state.manifest.get_playlist_mut(&name).expect("Unreachable"); let playlist = state.manifest.get_playlist_mut(&name).expect("Unreachable");

View File

@ -45,17 +45,17 @@ impl WindowManager {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn is_open(&self, id: &WindowIndex) -> bool { pub fn is_open(&self, id: WindowIndex) -> bool {
*self.opened.get(id).unwrap() *self.opened.get(&id).unwrap()
} }
pub fn open(&mut self, id: WindowIndex, open: bool) { pub fn open(&mut self, id: WindowIndex, open: bool) {
self.opened.insert(id, open); 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 { for (id, window) in &mut self.windows {
if !self.opened.contains_key(&id) { if !self.opened.contains_key(id) {
self.opened.insert(*id, false); self.opened.insert(*id, false);
} }
let open = self.opened.get_mut(id).unwrap(); let open = self.opened.get_mut(id).unwrap();
@ -63,8 +63,6 @@ impl WindowManager {
log::error!("Window {id:?} errored: {e}"); log::error!("Window {id:?} errored: {e}");
} }
} }
Ok(())
} }
pub fn get_window<T: Window + 'static>(&mut self, id: WindowIndex) -> &mut Box<T> { pub fn get_window<T: Window + 'static>(&mut self, id: WindowIndex) -> &mut Box<T> {

View File

@ -1,6 +1,5 @@
use anyhow::{Result, bail}; use anyhow::bail;
use egui::Color32; use egui::Color32;
use crate::ui::gui::Gui;
use super::{State, Window}; use super::{State, Window};
@ -44,7 +43,7 @@ impl Window for GuiSongEditor {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Type: "); ui.label("Type: ");
ui.label(&song.get_type().to_string()); ui.label(song.get_type().to_string());
}); });
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -67,7 +66,7 @@ impl Window for GuiSongEditor {
bail!("Failed to get song (2)"); 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 { let Some(playlist) = state.manifest.get_playlist_mut(&playlist) else {
@ -86,10 +85,10 @@ impl Window for GuiSongEditor {
} }
impl GuiSongEditor { impl GuiSongEditor {
pub fn set_active_song(&mut self, pname: &String, sname: &String, url: &String) { pub fn set_active_song(&mut self, pname: &str, sname: &str, url: &str) {
self.song.0 = pname.clone(); self.song.0 = pname.to_string();
self.song.1 = sname.clone(); self.song.1 = sname.to_string();
self.ed_name = sname.clone(); self.ed_name = sname.to_string();
self.ed_url = url.clone(); self.ed_url = url.to_string();
} }
} }

View File

@ -3,10 +3,10 @@ use super::{State, Window};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct GuiNewSong { pub struct GuiNewSong {
ed_type: SongType, typ: SongType,
ed_name: String, name: String,
ed_playlist: Option<String>, playlist: Option<String>,
ed_url: String, url: String,
} }
impl Window for GuiNewSong { impl Window for GuiNewSong {
@ -18,33 +18,33 @@ impl Window for GuiNewSong {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Type: "); ui.label("Type: ");
egui::ComboBox::from_id_source("new_song_window_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| { .show_ui(ui, |ui| {
ui.selectable_value(&mut self.ed_type, SongType::Youtube, "Youtube"); ui.selectable_value(&mut self.typ, SongType::Youtube, "Youtube");
ui.selectable_value(&mut self.ed_type, SongType::Spotify, "Spotify"); ui.selectable_value(&mut self.typ, SongType::Spotify, "Spotify");
ui.selectable_value(&mut self.ed_type, SongType::Soundcloud, "Soundcloud"); ui.selectable_value(&mut self.typ, SongType::Soundcloud, "Soundcloud");
} }
); );
}); });
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Name: "); ui.label("Name: ");
ui.text_edit_singleline(&mut self.ed_name); ui.text_edit_singleline(&mut self.name);
}); });
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Playlist: "); ui.label("Playlist: ");
egui::ComboBox::from_id_source("new_song_window_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| { .show_ui(ui, |ui| {
for p in state.manifest.get_playlists().keys() { 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.horizontal(|ui| {
ui.label("Url: "); ui.label("Url: ");
ui.text_edit_singleline(&mut self.ed_url); ui.text_edit_singleline(&mut self.url);
}); });
if ui.button("Save").clicked() { if ui.button("Save").clicked() {
@ -53,13 +53,13 @@ impl Window for GuiNewSong {
}); });
if save { 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????????????"); panic!("couldnt find playlist from a preset playlist list????????????");
}; };
playlist.add_song( playlist.add_song(
self.ed_name.clone(), self.name.clone(),
Song::from_url_str(self.ed_url.clone()).unwrap().set_type(self.ed_type.clone()).clone() Song::from_url_str(self.url.clone()).unwrap().set_type(self.typ.clone()).clone()
); );

View File

@ -2,16 +2,12 @@ use std::{any::Any, path::PathBuf};
use crate::{constants, manifest::Format}; 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(); let host = url.host_str();
if host.is_none() { if host.is_none() {
return false; return false;
} }
match host.unwrap() { matches!(host.unwrap(), "youtube.com" | "youtu.be" | "open.spotify.com")
"youtube.com" | "youtu.be" |
"open.spotify.com" => true,
_ => false
}
} }
pub(crate) fn is_program_in_path(program: &str) -> Option<PathBuf> { 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()); path = std::env::current_dir().unwrap_or(PathBuf::new());
} }
} else {*/ } 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 // TODO: Get this from cfg
path.push("out"); path.push("out");