Added downloading capabilities in the gui
This commit is contained in:
		
							parent
							
								
									266b580df7
								
							
						
					
					
						commit
						52a55d8be2
					
				
							
								
								
									
										1549
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1549
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -12,7 +12,9 @@ camino = "1.1.6"
 | 
				
			||||||
clap = { version = "4.5.4", features = ["derive"] }
 | 
					clap = { version = "4.5.4", features = ["derive"] }
 | 
				
			||||||
eframe = "0.27.2"
 | 
					eframe = "0.27.2"
 | 
				
			||||||
egui = "0.27.2"
 | 
					egui = "0.27.2"
 | 
				
			||||||
 | 
					egui_extras = "0.27.2"
 | 
				
			||||||
env_logger = "0.11.3"
 | 
					env_logger = "0.11.3"
 | 
				
			||||||
 | 
					futures = "0.3.30"
 | 
				
			||||||
lazy_static = "1.4.0"
 | 
					lazy_static = "1.4.0"
 | 
				
			||||||
libc = "0.2.153"
 | 
					libc = "0.2.153"
 | 
				
			||||||
log = "0.4.21"
 | 
					log = "0.4.21"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1284
									
								
								manifest.json
									
									
									
									
									
								
							
							
						
						
									
										1284
									
								
								manifest.json
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -4,19 +4,19 @@ use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{song::Song
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &Option<String>, name: &Option<String>, genre: &Option<String>) -> anyhow::Result<()> {
 | 
					pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &Option<String>, name: &Option<String>, playlist: &Option<String>) -> anyhow::Result<()> {
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    log::debug!("Genre: {genre:?}");
 | 
					    log::debug!("Playlist: {playlist:?}");
 | 
				
			||||||
    log::debug!("url: {url:?}");
 | 
					    log::debug!("url: {url:?}");
 | 
				
			||||||
    log::debug!("name: {name:?}");
 | 
					    log::debug!("name: {name:?}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut genres = manifest.get_playlists().keys().map(|f| f.clone()).collect::<Vec<String>>();
 | 
					    let mut playlists = manifest.get_playlists().keys().map(|f| f.clone()).collect::<Vec<String>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    genres.sort();
 | 
					    playlists.sort();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let genre = genre.clone().unwrap_or_else( || {
 | 
					    let playlist = playlist.clone().unwrap_or_else( || {
 | 
				
			||||||
        let g = crate::prompt::prompt_with_list_or_str("Enter song genre", &genres);
 | 
					        let g = crate::prompt::prompt_with_list_or_str("Enter song playlist", &playlists);
 | 
				
			||||||
        log::info!("Genre: {g}");
 | 
					        log::info!("Playlist: {g}");
 | 
				
			||||||
        g
 | 
					        g
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,14 +36,14 @@ pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let song = Song::from_url_str(url)?;
 | 
					    let song = Song::from_url_str(url)?;
 | 
				
			||||||
    manifest.add_song(genre.clone(), name.clone(), song.clone());
 | 
					    manifest.add_song(playlist.clone(), 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::prompt_bool("Download song now?", Some(false));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if should_download {
 | 
					    if should_download {
 | 
				
			||||||
        downloader.download_song(cfg, &name, &song, &genre, manifest.get_format()).await?;
 | 
					        downloader.download_song(cfg, &name, &song, &playlist, manifest.get_format())?;
 | 
				
			||||||
        crate::process_manager::wait_for_procs_untill(0).await?;
 | 
					        crate::process_manager::wait_for_procs_untill(0)?;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,33 +1,44 @@
 | 
				
			||||||
mod nav_bar;
 | 
					mod nav_bar;
 | 
				
			||||||
mod song_edit_window;
 | 
					mod song_edit_window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use egui::{Color32, Label, Sense};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::manifest::Manifest;
 | 
					use egui::{Color32, Label, Sense};
 | 
				
			||||||
 | 
					use egui_extras::{Column, TableBuilder};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{config::{Config, ConfigWrapper}, downloader::Downloader, manifest::{song::SongType, Manifest}};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use self::song_edit_window::GuiSongEditor;
 | 
					use self::song_edit_window::GuiSongEditor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct Gui {
 | 
					pub struct Gui {
 | 
				
			||||||
    manifest: Manifest,
 | 
					    manifest: Manifest,
 | 
				
			||||||
    song_editor: GuiSongEditor
 | 
					    song_editor: GuiSongEditor,
 | 
				
			||||||
 | 
					    filter: String,
 | 
				
			||||||
 | 
					    downloader: Downloader,
 | 
				
			||||||
 | 
					    cfg: ConfigWrapper
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Gui {
 | 
					impl Gui {
 | 
				
			||||||
    fn new(_: &eframe::CreationContext<'_>, manifest: Manifest) -> Self {
 | 
					    fn new(_: &eframe::CreationContext<'_>, manifest: Manifest, downloader: Downloader, cfg: ConfigWrapper) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            manifest,
 | 
					            manifest,
 | 
				
			||||||
            song_editor: GuiSongEditor {
 | 
					            song_editor: GuiSongEditor {
 | 
				
			||||||
 | 
					                is_new_open: false,
 | 
				
			||||||
                is_open: false,
 | 
					                is_open: false,
 | 
				
			||||||
                song: Default::default(),
 | 
					                song: Default::default(),
 | 
				
			||||||
                ed_url: String::new(),
 | 
					                ed_url: String::new(),
 | 
				
			||||||
                ed_name: String::new(),
 | 
					                ed_name: String::new(),
 | 
				
			||||||
 | 
					                ed_playlist: Some(String::new()),
 | 
				
			||||||
 | 
					                ed_type: SongType::Youtube
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            filter: String::new(),
 | 
				
			||||||
 | 
					            downloader,
 | 
				
			||||||
 | 
					            cfg,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn start(manifest: Manifest) -> anyhow::Result<()> {
 | 
					    pub fn start(manifest: Manifest, downloader: Downloader, cfg: ConfigWrapper) -> anyhow::Result<()> {
 | 
				
			||||||
        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])
 | 
				
			||||||
| 
						 | 
					@ -43,7 +54,7 @@ impl Gui {
 | 
				
			||||||
        if let Err(e) = eframe::run_native(
 | 
					        if let Err(e) = eframe::run_native(
 | 
				
			||||||
            "eframe template",
 | 
					            "eframe template",
 | 
				
			||||||
            native_options,
 | 
					            native_options,
 | 
				
			||||||
            Box::new(|cc| Box::new(Gui::new(cc, manifest))),
 | 
					            Box::new(|cc| Box::new(Gui::new(cc, manifest, downloader, cfg))),
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            log::error!("Failed to create window: {e}");
 | 
					            log::error!("Failed to create window: {e}");
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					@ -56,46 +67,129 @@ impl eframe::App for Gui {
 | 
				
			||||||
    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
 | 
					    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
 | 
				
			||||||
        self.draw_nav(ctx, frame);
 | 
					        self.draw_nav(ctx, frame);
 | 
				
			||||||
        self.draw_song_edit_window(ctx, frame);
 | 
					        self.draw_song_edit_window(ctx, frame);
 | 
				
			||||||
 | 
					        self.draw_new_song_window(ctx, frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        egui::CentralPanel::default().show(ctx, |ui| {
 | 
					        egui::CentralPanel::default().show(ctx, |ui| {
 | 
				
			||||||
            // The central panel the region left after adding TopPanel's and SidePanel's
 | 
					            // The central panel the region left after adding TopPanel's and SidePanel's
 | 
				
			||||||
            ui.heading(format!("Songs ({})", self.manifest.get_song_count()));
 | 
					            //ui.heading(format!("Songs ({})", self.manifest.get_song_count()));
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            egui::ScrollArea::vertical()
 | 
					            let fltr_by;
 | 
				
			||||||
                .max_width(f32::INFINITY)
 | 
					            let filter_clean;
 | 
				
			||||||
                .auto_shrink(false)
 | 
					            if self.filter.starts_with("playlist:") {
 | 
				
			||||||
                .show(ui, |ui| {
 | 
					                fltr_by = "playlist";
 | 
				
			||||||
                for (genre, songs) in  self.manifest.get_playlists() {
 | 
					                filter_clean = self.filter.strip_prefix("playlist:").unwrap_or("").to_string().to_lowercase();
 | 
				
			||||||
                    for (song_name, song) in songs {
 | 
					            } else if self.filter.starts_with("source:") {
 | 
				
			||||||
                        ui.horizontal(|ui| {
 | 
					                fltr_by = "source";
 | 
				
			||||||
                            ui.spacing_mut().item_spacing.x = 0.0;
 | 
					                filter_clean = self.filter.strip_prefix("source:").unwrap_or("").to_string().to_lowercase();
 | 
				
			||||||
                            ui.label("[");
 | 
					            } else if self.filter.starts_with("url:") {
 | 
				
			||||||
                            ui.hyperlink_to("link", song.get_url().unwrap());
 | 
					                fltr_by = "url";
 | 
				
			||||||
                            ui.label("] ");
 | 
					                filter_clean = self.filter.strip_prefix("url:").unwrap_or("").to_string();
 | 
				
			||||||
                            ui.colored_label(Color32::LIGHT_BLUE, genre);
 | 
					            } else {
 | 
				
			||||||
                            ui.label(": ");
 | 
					                fltr_by = "";
 | 
				
			||||||
                            if ui.add(Label::new(song_name).sense(Sense::click())).clicked() {
 | 
					                filter_clean = self.filter.clone();
 | 
				
			||||||
                                self.song_editor.song = (
 | 
					            }
 | 
				
			||||||
                                    genre.clone(),
 | 
					
 | 
				
			||||||
                                    song_name.clone(),
 | 
					            ui.vertical(|ui| {
 | 
				
			||||||
                                );
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
                                log::debug!("Label pressed");
 | 
					                    ui.colored_label(Color32::BLUE, "Filter: ");
 | 
				
			||||||
                                self.song_editor.is_open = true;
 | 
					                    ui.text_edit_singleline(&mut self.filter);
 | 
				
			||||||
                                self.song_editor.ed_name = song_name.clone();
 | 
					                });  
 | 
				
			||||||
                                self.song_editor.ed_url = song.get_url_str().clone();
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
                        // ui.label(RichText::new(""))
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
 | 
					            ui.vertical(|ui| {
 | 
				
			||||||
 | 
					                let available_height = ui.available_height();
 | 
				
			||||||
 | 
					                let table = TableBuilder::new(ui)
 | 
				
			||||||
 | 
					                    .striped(true)
 | 
				
			||||||
 | 
					                    .cell_layout(egui::Layout::left_to_right(egui::Align::Center))
 | 
				
			||||||
 | 
					                    .resizable(true)
 | 
				
			||||||
 | 
					                    .column(Column::auto())
 | 
				
			||||||
 | 
					                    .column(Column::auto())
 | 
				
			||||||
 | 
					                    //.column(
 | 
				
			||||||
 | 
					                    //    Column::remainder()
 | 
				
			||||||
 | 
					                    //        .at_least(40.0)
 | 
				
			||||||
 | 
					                    //        .clip(true)
 | 
				
			||||||
 | 
					                    //        .resizable(true),
 | 
				
			||||||
 | 
					                    //)
 | 
				
			||||||
 | 
					                    .column(Column::auto())
 | 
				
			||||||
 | 
					                    .column(Column::remainder())
 | 
				
			||||||
 | 
					                    //.column(Column::remainder())
 | 
				
			||||||
 | 
					                    .min_scrolled_height(0.0)
 | 
				
			||||||
 | 
					                    .max_scroll_height(available_height)
 | 
				
			||||||
 | 
					                    .sense(egui::Sense::click());
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                let playlists = self.manifest.get_playlists().clone();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let songs = {
 | 
				
			||||||
 | 
					                    let mut songs = Vec::new();
 | 
				
			||||||
 | 
					                    for (pname, p) in playlists {
 | 
				
			||||||
 | 
					                        for (sname, s) in p {
 | 
				
			||||||
 | 
					                            songs.push((pname.clone(), sname, s))
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    songs
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                table.header(20.0, |mut header| {
 | 
				
			||||||
 | 
					                    header.col(|_|{});
 | 
				
			||||||
 | 
					                    header.col(|ui| {
 | 
				
			||||||
 | 
					                        ui.strong("Playlist");
 | 
				
			||||||
 | 
					                    }); 
 | 
				
			||||||
 | 
					                    header.col(|ui| { 
 | 
				
			||||||
 | 
					                        ui.strong("Source");
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    header.col(|ui| {
 | 
				
			||||||
 | 
					                        ui.strong("Name");
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }).body(|mut body| {
 | 
				
			||||||
 | 
					                    for (pname, sname, s) in songs {    
 | 
				
			||||||
 | 
					                        if fltr_by == "playlist" && !filter_clean.is_empty() {
 | 
				
			||||||
 | 
					                            if !pname.to_lowercase().contains(&filter_clean) {
 | 
				
			||||||
 | 
					                                continue;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        } else if fltr_by == "type" && !filter_clean.is_empty(){
 | 
				
			||||||
 | 
					                            if !s.get_type().to_string().to_lowercase().contains(&filter_clean) {
 | 
				
			||||||
 | 
					                                continue;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        } else if fltr_by == "url" && !filter_clean.is_empty(){
 | 
				
			||||||
 | 
					                            if !s.get_url_str().contains(&filter_clean) {
 | 
				
			||||||
 | 
					                                continue;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        } else if !filter_clean.is_empty() {
 | 
				
			||||||
 | 
					                            if !sname.to_lowercase().contains(&filter_clean) {
 | 
				
			||||||
 | 
					                                continue;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        body.row(18.0, |mut row| {
 | 
				
			||||||
 | 
					                            row.col(|ui| {
 | 
				
			||||||
 | 
					                                if ui.add(Label::new("[edit]").sense(Sense::click())).clicked() {
 | 
				
			||||||
 | 
					                                    self.song_editor.song = (
 | 
				
			||||||
 | 
					                                        pname.clone(),
 | 
				
			||||||
 | 
					                                        sname.clone(),
 | 
				
			||||||
 | 
					                                    );
 | 
				
			||||||
 | 
					                                    log::debug!("Label pressed");
 | 
				
			||||||
 | 
					                                    self.song_editor.is_open = true;
 | 
				
			||||||
 | 
					                                    self.song_editor.ed_name = sname.clone();
 | 
				
			||||||
 | 
					                                    self.song_editor.ed_url = s.get_url_str().clone();
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            row.col(|ui| {
 | 
				
			||||||
 | 
					                                ui.label(pname.clone());
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                            row.col(|ui| {
 | 
				
			||||||
 | 
					                                ui.label(s.get_type().to_string());
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                            row.col(|ui| {
 | 
				
			||||||
 | 
					                                ui.hyperlink_to(sname.clone(), s.get_url_str());
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
            ui.separator();
 | 
					            ui.separator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ui.add(egui::github_link_file!(
 | 
					 | 
				
			||||||
                "https://github.com/emilk/eframe_template/blob/main/",
 | 
					 | 
				
			||||||
                "Source code."
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
 | 
					            ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
 | 
				
			||||||
                egui::warn_if_debug_build(ui);
 | 
					                egui::warn_if_debug_build(ui);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					use egui::Hyperlink;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::Gui;
 | 
					use super::Gui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,18 +10,43 @@ impl Gui {
 | 
				
			||||||
            // The top panel is often a good place for a menu bar:
 | 
					            // The top panel is often a good place for a menu bar:
 | 
				
			||||||
            egui::menu::bar(ui, |ui| {
 | 
					            egui::menu::bar(ui, |ui| {
 | 
				
			||||||
                ui.menu_button("File", |ui| {
 | 
					                ui.menu_button("File", |ui| {
 | 
				
			||||||
                    if ui.button("Quit").clicked() {
 | 
					                    if ui.button("Source").clicked() {
 | 
				
			||||||
                        ctx.send_viewport_cmd(egui::ViewportCommand::Close);
 | 
					                        ctx.open_url(egui::OpenUrl::new_tab("https://git.mcorangehq.xyz/XOR64/music"));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    if ui.button("Save").clicked() {
 | 
					                    if ui.button("Save").clicked() {
 | 
				
			||||||
                        if let Err(e) =  self.manifest.save(None) {
 | 
					                        if let Err(e) =  self.manifest.save(None) {
 | 
				
			||||||
                            log::error!("Failed to save manifest: {e}");
 | 
					                            log::error!("Failed to save manifest: {e}");
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    if ui.button("Quit").clicked() {
 | 
				
			||||||
 | 
					                        ctx.send_viewport_cmd(egui::ViewportCommand::Close);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ui.menu_button("Song", |ui| {
 | 
				
			||||||
 | 
					                    if ui.button("Add New").clicked() {
 | 
				
			||||||
 | 
					                        log::debug!("NEW SONG");
 | 
				
			||||||
 | 
					                        self.song_editor.is_new_open = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ui.menu_button("Downloader", |ui| {
 | 
				
			||||||
 | 
					                    if ui.button("Download All").clicked() {
 | 
				
			||||||
 | 
					                        if let Err(e) = self.downloader.download_all_nb(&self.manifest, &self.cfg) {
 | 
				
			||||||
 | 
					                            log::error!("Err: {e}");
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
                ui.add_space(16.0);
 | 
					                ui.add_space(16.0);
 | 
				
			||||||
                ui.with_layout(egui::Layout::bottom_up(egui::Align::RIGHT), |ui| {
 | 
					                ui.with_layout(egui::Layout::bottom_up(egui::Align::RIGHT), |ui| {
 | 
				
			||||||
                    egui::widgets::global_dark_light_mode_buttons(ui);
 | 
					                    ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                        if self.downloader.get_songs_left_nb() > 0 {
 | 
				
			||||||
 | 
					                            ui.label(format!("Downloading: {}/{}", self.downloader.get_songs_left_nb(), self.downloader.get_initial_song_count_nb()));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        let _ = self.downloader.download_all_nb_poll(&self.cfg);
 | 
				
			||||||
 | 
					                        egui::widgets::global_dark_light_mode_buttons(ui);
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,30 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use egui::Color32;
 | 
					use egui::{Color32, TextBuffer};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::manifest::{GenreName, SongName};
 | 
					
 | 
				
			||||||
 | 
					use crate::manifest::{playlist::Playlist, song::{Song, SongType}};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::Gui;
 | 
					use super::Gui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct GuiSongEditor {
 | 
					pub struct GuiSongEditor {
 | 
				
			||||||
    pub is_open: bool,
 | 
					    pub is_open: bool,
 | 
				
			||||||
    pub song: (GenreName, SongName),
 | 
					    pub is_new_open: bool,
 | 
				
			||||||
 | 
					    pub song: (String, String),
 | 
				
			||||||
    pub ed_url: String,
 | 
					    pub ed_url: String,
 | 
				
			||||||
    pub ed_name: String,
 | 
					    pub ed_name: String,
 | 
				
			||||||
 | 
					    pub ed_playlist: Option<String>,
 | 
				
			||||||
 | 
					    pub ed_type: SongType,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Gui {
 | 
					impl Gui {
 | 
				
			||||||
    pub fn draw_song_edit_window(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
 | 
					    pub fn draw_song_edit_window(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
 | 
				
			||||||
        let mut save = false;
 | 
					        let mut save = false;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let (genre, song_name) = self.song_editor.song.clone();
 | 
					        let (playlist, song_name) = self.song_editor.song.clone();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let Some(song) = self.manifest.get_song(genre.clone(), &song_name) else {
 | 
					        let Some(song) = self.manifest.get_song(playlist.clone(), &song_name) else {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let song = song.clone();
 | 
					        let song = song.clone();
 | 
				
			||||||
| 
						 | 
					@ -33,7 +39,7 @@ impl Gui {
 | 
				
			||||||
                ui.label("[");
 | 
					                ui.label("[");
 | 
				
			||||||
                ui.hyperlink_to("link", song.get_url().unwrap());
 | 
					                ui.hyperlink_to("link", song.get_url().unwrap());
 | 
				
			||||||
                ui.label("] ");
 | 
					                ui.label("] ");
 | 
				
			||||||
                ui.colored_label(Color32::LIGHT_BLUE, &genre);
 | 
					                ui.colored_label(Color32::LIGHT_BLUE, &playlist);
 | 
				
			||||||
                ui.label(": ");
 | 
					                ui.label(": ");
 | 
				
			||||||
                ui.label(&song_name)
 | 
					                ui.label(&song_name)
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
| 
						 | 
					@ -59,21 +65,81 @@ impl Gui {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if save {
 | 
					        if save {
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                let Some(song) = self.manifest.get_song_mut(genre.clone(), &song_name) else {
 | 
					                let Some(song) = self.manifest.get_song_mut(playlist.clone(), &song_name) else {
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                *song.get_url_str_mut() = self.song_editor.ed_url.clone();
 | 
					                *song.get_url_str_mut() = self.song_editor.ed_url.clone();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let Some(genre) = self.manifest.get_playlist_mut(genre.clone()) else {
 | 
					            let Some(playlist) = self.manifest.get_playlist_mut(playlist.clone()) else {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            genre.remove(&song_name);
 | 
					            playlist.remove_song(&song_name);
 | 
				
			||||||
            genre.insert(self.song_editor.ed_name.clone(), song);
 | 
					            playlist.add_song(self.song_editor.ed_name.clone(), song);
 | 
				
			||||||
            let _ = self.manifest.save(None);
 | 
					            let _ = self.manifest.save(None);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    pub fn draw_new_song_window(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
 | 
				
			||||||
 | 
					        let mut save = false;
 | 
				
			||||||
 | 
					        egui::Window::new("New song")
 | 
				
			||||||
 | 
					            .open(&mut self.song_editor.is_new_open)
 | 
				
			||||||
 | 
					            .show(ctx, |ui| {
 | 
				
			||||||
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                    ui.label("Type: ");
 | 
				
			||||||
 | 
					                    egui::ComboBox::from_id_source("new_song_window_type")
 | 
				
			||||||
 | 
					                        .selected_text(format!("{:?}", self.song_editor.ed_type))
 | 
				
			||||||
 | 
					                        .show_ui(ui, |ui| {
 | 
				
			||||||
 | 
					                            ui.selectable_value(&mut self.song_editor.ed_type, SongType::Youtube, "Youtube");
 | 
				
			||||||
 | 
					                            ui.selectable_value(&mut self.song_editor.ed_type, SongType::Spotify, "Spotify");
 | 
				
			||||||
 | 
					                            ui.selectable_value(&mut self.song_editor.ed_type, SongType::Soundcloud, "Soundcloud");
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    );    
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                    ui.label("Name: ");
 | 
				
			||||||
 | 
					                    ui.text_edit_singleline(&mut self.song_editor.ed_name);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                    ui.label("Playlist: ");
 | 
				
			||||||
 | 
					                    egui::ComboBox::from_id_source("new_song_window_playlist")
 | 
				
			||||||
 | 
					                        .selected_text(format!("{}", self.song_editor.ed_playlist.clone().unwrap()))
 | 
				
			||||||
 | 
					                        .show_ui(ui, |ui| {
 | 
				
			||||||
 | 
					                            for p in self.manifest.get_playlists().keys() {
 | 
				
			||||||
 | 
					                                ui.selectable_value(&mut self.song_editor.ed_playlist, Option::Some(p.clone()), p.as_str());
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    );    
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                    ui.label("Url: ");
 | 
				
			||||||
 | 
					                    ui.text_edit_singleline(&mut self.song_editor.ed_url);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ui.button("Save").clicked() {
 | 
				
			||||||
 | 
					                    save = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if save {
 | 
				
			||||||
 | 
					            let Some(playlist) = self.manifest.get_playlist_mut(self.song_editor.ed_playlist.clone().unwrap())  else {
 | 
				
			||||||
 | 
					                panic!("couldnt find playlist from a preset playlist list????????????");
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playlist.add_song(
 | 
				
			||||||
 | 
					                self.song_editor.ed_name.clone(),
 | 
				
			||||||
 | 
					                Song::from_url_str(self.song_editor.ed_url.clone()).unwrap().set_type(self.song_editor.ed_type.clone()).clone()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let _ = self.manifest.save(None);
 | 
				
			||||||
 | 
					            save = false;
 | 
				
			||||||
 | 
					            self.song_editor.is_new_open = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,18 +23,18 @@ pub async fn command_run(cfg: &ConfigWrapper, manifest: &mut Manifest) -> anyhow
 | 
				
			||||||
        (Some(c), _) => {
 | 
					        (Some(c), _) => {
 | 
				
			||||||
            match c {
 | 
					            match c {
 | 
				
			||||||
                CliCommand::Download => unreachable!(),
 | 
					                CliCommand::Download => unreachable!(),
 | 
				
			||||||
                CliCommand::Add { url, name, genre  } => {
 | 
					                CliCommand::Add { url, name, playlist  } => {
 | 
				
			||||||
                    if let Err(e) = add::add(cfg, manifest, &mut downloader, url, name, genre).await {
 | 
					                    if let Err(e) = add::add(cfg, manifest, &mut downloader, url, name, playlist).await {
 | 
				
			||||||
                        log::error!("Failed to run 'add' command: {e}");
 | 
					                        log::error!("Failed to run 'add' command: {e}");
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                CliCommand::Gui => {
 | 
					                CliCommand::Gui => {
 | 
				
			||||||
                    gui::Gui::start(manifest.clone())?;
 | 
					                    gui::Gui::start(manifest.clone(), downloader, cfg.clone())?;
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        (None, false) => {
 | 
					        (None, false) => {
 | 
				
			||||||
            gui::Gui::start(manifest.clone())?;
 | 
					            gui::Gui::start(manifest.clone(), downloader, cfg.clone())?;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ use camino::Utf8PathBuf;
 | 
				
			||||||
use clap::{Parser, Subcommand};
 | 
					use clap::{Parser, Subcommand};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Parser, Default)]
 | 
					#[derive(Debug, Parser, Default, Clone)]
 | 
				
			||||||
pub struct CliArgs {
 | 
					pub struct CliArgs {
 | 
				
			||||||
    /// Show more info
 | 
					    /// Show more info
 | 
				
			||||||
    #[arg(long, short)]
 | 
					    #[arg(long, short)]
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,7 @@ pub struct CliArgs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Subcommand)]
 | 
					#[derive(Debug, Subcommand, Clone)]
 | 
				
			||||||
pub enum CliCommand {
 | 
					pub enum CliCommand {
 | 
				
			||||||
    Download,
 | 
					    Download,
 | 
				
			||||||
    Add {
 | 
					    Add {
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@ pub enum CliCommand {
 | 
				
			||||||
        #[arg(long, short)]
 | 
					        #[arg(long, short)]
 | 
				
			||||||
        name: Option<String>,
 | 
					        name: Option<String>,
 | 
				
			||||||
        #[arg(long, short)]
 | 
					        #[arg(long, short)]
 | 
				
			||||||
        genre: Option<String>
 | 
					        playlist: Option<String>
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    Gui
 | 
					    Gui
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,25 +12,25 @@ 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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default)]
 | 
					#[derive(Debug, Default, Clone)]
 | 
				
			||||||
pub struct ConfigWrapper {
 | 
					pub struct ConfigWrapper {
 | 
				
			||||||
    pub cfg: Config,
 | 
					    pub cfg: Config,
 | 
				
			||||||
    pub cli: cli::CliArgs,
 | 
					    pub cli: cli::CliArgs,
 | 
				
			||||||
    pub isatty: bool
 | 
					    pub isatty: bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize, Default)]
 | 
					#[derive(Debug, Serialize, Deserialize, Default, Clone)]
 | 
				
			||||||
pub struct Config {
 | 
					pub struct Config {
 | 
				
			||||||
    pub ytdlp: ConfigYtdlp,
 | 
					    pub ytdlp: ConfigYtdlp,
 | 
				
			||||||
    pub spotdl: ConfigSpotdl,
 | 
					    pub spotdl: ConfigSpotdl,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize, Default)]
 | 
					#[derive(Debug, Serialize, Deserialize, Default, Clone)]
 | 
				
			||||||
pub struct ConfigYtdlp {
 | 
					pub struct ConfigYtdlp {
 | 
				
			||||||
    pub path: PathBuf,
 | 
					    pub path: PathBuf,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize, Default)]
 | 
					#[derive(Debug, Serialize, Deserialize, Default, Clone)]
 | 
				
			||||||
pub struct ConfigSpotdl {
 | 
					pub struct ConfigSpotdl {
 | 
				
			||||||
    pub path: PathBuf,
 | 
					    pub path: PathBuf,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,32 +18,66 @@ lazy_static!(
 | 
				
			||||||
    static ref PROCESSES: Mutex<RwLock<HashMap<usize, Proc>>> = Mutex::new(RwLock::new(HashMap::new()));
 | 
					    static ref PROCESSES: Mutex<RwLock<HashMap<usize, Proc>>> = Mutex::new(RwLock::new(HashMap::new()));
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Default)]
 | 
				
			||||||
pub struct Downloader {
 | 
					pub struct Downloader {
 | 
				
			||||||
    count: usize,
 | 
					    count: usize,
 | 
				
			||||||
 | 
					    nb_initial_song_count: usize,
 | 
				
			||||||
 | 
					    nb_cache: Vec<(String, String, Song, Format)>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Downloader {
 | 
					impl Downloader {
 | 
				
			||||||
    pub fn new() -> Self {
 | 
					    pub fn new() -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            count: 0,
 | 
					            ..Default::default()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_initial_song_count_nb(&self) -> usize {
 | 
				
			||||||
 | 
					        self.nb_initial_song_count
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_songs_left_nb(&self) -> usize {
 | 
				
			||||||
 | 
					        self.nb_cache.len()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn download_all_nb(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
 | 
				
			||||||
 | 
					        for (pname, playlist) in manifest.get_playlists() {
 | 
				
			||||||
 | 
					            for (sname, song) in playlist.get_songs() {
 | 
				
			||||||
 | 
					                self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), manifest.get_format().clone()));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self.nb_initial_song_count = self.nb_cache.len();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.download_all_nb_poll(cfg)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn download_all_nb_poll(&mut self, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
 | 
				
			||||||
 | 
					        if !crate::process_manager::is_proc_queue_full(10)  {
 | 
				
			||||||
 | 
					            if let Some((pname, sname, song, format)) = self.nb_cache.pop() {
 | 
				
			||||||
 | 
					                self.download_song(cfg, &sname, &song, &pname, &format)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(crate::process_manager::purge_done_procs())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn download_all(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
 | 
					    pub async fn download_all(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
 | 
				
			||||||
        let format = manifest.get_format();
 | 
					        let format = manifest.get_format();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (genre, songs) in manifest.get_playlists() {
 | 
					        for (name, playlist) in manifest.get_playlists() {
 | 
				
			||||||
            for (song_name, song) in songs {
 | 
					            for (song_name, song) in playlist.get_songs() {
 | 
				
			||||||
                self.download_song(cfg, song_name, song, &genre, format).await?;
 | 
					                self.download_song(cfg, song_name, song, &name, format)?;
 | 
				
			||||||
                self.count += crate::process_manager::wait_for_procs_untill(10).await?;
 | 
					                self.count += crate::process_manager::wait_for_procs_untill(10)?;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        self.count += crate::process_manager::wait_for_procs_untill(0).await?;
 | 
					        self.count += crate::process_manager::wait_for_procs_untill(0)?;
 | 
				
			||||||
        Ok(self.count)
 | 
					        Ok(self.count)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    pub async fn download_song(&mut self, cfg: &ConfigWrapper, name: &String, song: &Song, genre: &String, format: &Format) -> anyhow::Result<()> {
 | 
					    pub fn download_song(&mut self, cfg: &ConfigWrapper, name: &String, song: &Song, playlist: &String, format: &Format) -> anyhow::Result<()> {
 | 
				
			||||||
        let dl_dir = format!("{}/{genre}", 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if PathBuf::from(&dl_file).exists() {
 | 
					        if PathBuf::from(&dl_file).exists() {
 | 
				
			||||||
| 
						 | 
					@ -89,7 +123,7 @@ impl Downloader {
 | 
				
			||||||
            cmd.stdout(Stdio::null()).stderr(Stdio::null());
 | 
					            cmd.stdout(Stdio::null()).stderr(Stdio::null());
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        crate::process_manager::add_proc(cmd, format!("Downloaded {dl_file}")).await?;
 | 
					        crate::process_manager::add_proc(cmd, format!("Downloaded {dl_file}"))?;
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
// pub mod v1;
 | 
					// pub mod v1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod song;
 | 
					pub mod song;
 | 
				
			||||||
 | 
					pub mod 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};
 | 
				
			||||||
| 
						 | 
					@ -11,7 +12,7 @@ use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_MANIFEST: &'static str = include_str!("../../manifest.default.json");
 | 
					const DEFAULT_MANIFEST: &'static str = include_str!("../../manifest.default.json");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type GenreName = String;
 | 
					
 | 
				
			||||||
pub type SongName = String;
 | 
					pub type SongName = String;
 | 
				
			||||||
pub type Genre = HashMap<SongName, song::Song>;
 | 
					pub type Genre = HashMap<SongName, song::Song>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,36 +32,39 @@ pub struct Manifest {
 | 
				
			||||||
    #[serde(skip)]
 | 
					    #[serde(skip)]
 | 
				
			||||||
    path: PathBuf,
 | 
					    path: PathBuf,
 | 
				
			||||||
    format: Format,
 | 
					    format: Format,
 | 
				
			||||||
    playlists: HashMap<GenreName, Genre>
 | 
					    playlists: HashMap<String, playlist::Playlist>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[allow(dead_code)]
 | 
					#[allow(dead_code)]
 | 
				
			||||||
impl Manifest {
 | 
					impl Manifest {
 | 
				
			||||||
    pub fn get_format(&self) -> &Format {
 | 
					    pub fn get_format(&self) -> &Format {
 | 
				
			||||||
        &self.format
 | 
					        &self.format
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn add_song(&mut self, genre: GenreName, name: SongName, song: Song) -> Option<Song> {
 | 
					    pub fn add_song(&mut self, playlist_name: String, name: SongName, song: Song) -> Option<Song> {
 | 
				
			||||||
        self.get_playlist_mut(genre)?.insert(name, song)
 | 
					        self.get_playlist_mut(playlist_name)?.add_song(name, song)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn get_song(&self, genre: GenreName, name: &SongName) -> Option<&Song> {
 | 
					    pub fn get_song(&self, playlist_name: String, name: &String) -> Option<&Song> {
 | 
				
			||||||
        self.get_playlist(genre)?.get(name)
 | 
					        self.get_playlist(playlist_name)?.get_song(name)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn get_song_mut(&mut self, genre: GenreName, name: &SongName) -> Option<&mut Song> {
 | 
					    pub fn get_song_mut(&mut self, playlist_name: String, name: &String) -> Option<&mut Song> {
 | 
				
			||||||
        self.get_playlist_mut(genre)?.get_mut(name)
 | 
					        self.get_playlist_mut(playlist_name)?.get_song_mut(name)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn add_playlist(&mut self, name: GenreName) {
 | 
					    pub fn add_playlist(&mut self, playlist_name: String) {
 | 
				
			||||||
        self.playlists.insert(name, Default::default());
 | 
					        self.playlists.insert(playlist_name, Default::default());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn get_playlist(&self, name: GenreName) -> Option<&Genre> {
 | 
					    pub fn get_playlist(&self, playlist_name: String) -> Option<&playlist::Playlist> {
 | 
				
			||||||
        self.playlists.get(&name)
 | 
					        self.playlists.get(&playlist_name)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn get_playlist_mut(&mut self, name: GenreName) -> Option<&mut Genre> {
 | 
					    pub fn get_playlist_mut(&mut self, playlist_name: String) -> Option<&mut playlist::Playlist> {
 | 
				
			||||||
        self.playlists.get_mut(&name)
 | 
					        self.playlists.get_mut(&playlist_name)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn get_playlists(&self) -> &HashMap<GenreName, Genre> {
 | 
					    pub fn get_playlists(&self) -> &HashMap<String, playlist::Playlist> {
 | 
				
			||||||
        &self.playlists
 | 
					        &self.playlists
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn get_playlists_mut(&mut self) -> &mut HashMap<GenreName, Genre> {
 | 
					    pub fn get_playlists_mut(&mut self) -> &mut HashMap<String, playlist::Playlist> {
 | 
				
			||||||
        &mut self.playlists
 | 
					        &mut self.playlists
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn get_song_count(&self) -> usize {
 | 
					    pub fn get_song_count(&self) -> usize {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										56
									
								
								src/manifest/playlist.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/manifest/playlist.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,56 @@
 | 
				
			||||||
 | 
					use egui::ahash::HashMap;
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::song::Song;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Serialize, Deserialize, Clone, Default)]
 | 
				
			||||||
 | 
					pub struct Playlist {
 | 
				
			||||||
 | 
					    songs: HashMap<String, Song>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Playlist {
 | 
				
			||||||
 | 
					    pub fn new() -> Self {
 | 
				
			||||||
 | 
					        Self { ..Default::default() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn add_song(&mut self, name: String, song: Song) -> Option<Song> {
 | 
				
			||||||
 | 
					        self.songs.insert(name, song)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn remove_song(&mut self, name: &String) -> Option<Song> {
 | 
				
			||||||
 | 
					        self.songs.remove(name)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_song(&self, name: &String) -> Option<&Song> {
 | 
				
			||||||
 | 
					        self.songs.get(name)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_songs(&self) -> &HashMap<String, Song> {
 | 
				
			||||||
 | 
					        &self.songs
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_songs_mut(&mut self) -> &mut HashMap<String, Song> {
 | 
				
			||||||
 | 
					        &mut self.songs
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    pub fn get_song_mut(&mut self, name: &String) -> Option<&mut Song> {
 | 
				
			||||||
 | 
					        self.songs.get_mut(name)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    pub fn len(&self) -> usize {
 | 
				
			||||||
 | 
					        self.songs.len()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl IntoIterator for Playlist {
 | 
				
			||||||
 | 
					    type Item = (String, Song);
 | 
				
			||||||
 | 
					    type IntoIter = std::collections::hash_map::IntoIter<String, Song>;
 | 
				
			||||||
 | 
					    fn into_iter(self) -> Self::IntoIter {
 | 
				
			||||||
 | 
					        self.songs.into_iter()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ use anyhow::{bail, Result};
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
 | 
					#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
 | 
				
			||||||
pub enum SongType {
 | 
					pub enum SongType {
 | 
				
			||||||
    Youtube,
 | 
					    Youtube,
 | 
				
			||||||
    Spotify,
 | 
					    Spotify,
 | 
				
			||||||
| 
						 | 
					@ -33,13 +33,17 @@ impl Song {
 | 
				
			||||||
    pub fn from_url_str(url: String) -> Result<Self> {
 | 
					    pub fn from_url_str(url: String) -> Result<Self> {
 | 
				
			||||||
        Self::from_url(url::Url::from_str(url.as_str())?)
 | 
					        Self::from_url(url::Url::from_str(url.as_str())?)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn from_url(url: url::Url) -> Result<Self> {
 | 
					    pub fn from_url(url: url::Url) -> Result<Self> {
 | 
				
			||||||
        Ok(Self {
 | 
					        Ok(Self {
 | 
				
			||||||
            url: url.to_string(),
 | 
					            url: url.to_string(),
 | 
				
			||||||
            typ: url.try_into()?,
 | 
					            typ: url.try_into()?,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn set_type(&mut self, typ: SongType) -> &mut Self {
 | 
				
			||||||
 | 
					        self.typ = typ;
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    pub fn get_url(&self) -> Result<url::Url> {
 | 
					    pub fn get_url(&self) -> Result<url::Url> {
 | 
				
			||||||
        Ok(url::Url::from_str(&self.url)?)
 | 
					        Ok(url::Url::from_str(&self.url)?)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
use std::{collections::HashMap, sync::atomic::{AtomicUsize, Ordering}};
 | 
					use std::{collections::HashMap, sync::{atomic::{AtomicUsize, Ordering}, Mutex, RwLock}};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use tokio::{process::Command, sync::{Mutex, RwLock}};
 | 
					use tokio::process::Command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ static PROC_INC: AtomicUsize = AtomicUsize::new(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn add_proc(mut cmd: Command, msg: String) -> anyhow::Result<()> {
 | 
					pub fn add_proc(mut cmd: Command, msg: String) -> anyhow::Result<()> {
 | 
				
			||||||
    let mut proc = cmd.spawn()?;
 | 
					    let mut proc = cmd.spawn()?;
 | 
				
			||||||
    let id = PROC_INC.fetch_add(1, Ordering::AcqRel);
 | 
					    let id = PROC_INC.fetch_add(1, Ordering::AcqRel);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
| 
						 | 
					@ -27,10 +27,10 @@ pub async fn add_proc(mut cmd: Command, msg: String) -> anyhow::Result<()> {
 | 
				
			||||||
        let id = id;
 | 
					        let id = id;
 | 
				
			||||||
        proc.wait().await
 | 
					        proc.wait().await
 | 
				
			||||||
            .expect("child process encountered an error");
 | 
					            .expect("child process encountered an error");
 | 
				
			||||||
        PROCESSES.lock().await.write().await.get_mut(&id).unwrap().finished = true;
 | 
					        PROCESSES.lock().unwrap().write().unwrap().get_mut(&id).unwrap().finished = true;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    PROCESSES.lock().await.write().await.insert(id, Proc {
 | 
					    PROCESSES.lock().unwrap().write().unwrap().insert(id, Proc {
 | 
				
			||||||
        finished: false,
 | 
					        finished: false,
 | 
				
			||||||
        msg,
 | 
					        msg,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					@ -38,30 +38,39 @@ pub async fn add_proc(mut cmd: Command, msg: String) -> anyhow::Result<()> {
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn is_proc_queue_full(max: usize) -> bool {
 | 
				
			||||||
 | 
					    let proc_cnt = PROCESSES.lock().unwrap().read().unwrap().len();
 | 
				
			||||||
 | 
					    proc_cnt >= max
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn purge_done_procs() -> usize {
 | 
				
			||||||
 | 
					    let mut finish_count = 0;
 | 
				
			||||||
 | 
					    let procs = {
 | 
				
			||||||
 | 
					        PROCESSES.lock().unwrap().read().unwrap().clone()
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (idx, proc) in procs {
 | 
				
			||||||
 | 
					        if proc.finished {
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                PROCESSES.lock().unwrap().write().unwrap().remove(&idx);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            log::info!("{}", proc.msg);
 | 
				
			||||||
 | 
					            finish_count += 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    finish_count
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Waits for processes to finish untill the proc count is lower or equal to `max`
 | 
					/// Waits for processes to finish untill the proc count is lower or equal to `max`
 | 
				
			||||||
pub async fn wait_for_procs_untill(max: usize) -> anyhow::Result<usize> {
 | 
					pub fn wait_for_procs_untill(max: usize) -> anyhow::Result<usize> {
 | 
				
			||||||
    // NOTE: This looks really fucked because i dont want to deadlock the processes so i lock PROCESSES for as little as possible
 | 
					    // NOTE: This looks really fucked because i dont want to deadlock the processes so i lock PROCESSES for as little as possible
 | 
				
			||||||
    // NOTE: So its also kinda really slow
 | 
					    // NOTE: So its also kinda really slow
 | 
				
			||||||
    let mut finish_count = 0;
 | 
					    let mut finish_count = 0;
 | 
				
			||||||
    loop {
 | 
					    loop {
 | 
				
			||||||
        {
 | 
					        if !is_proc_queue_full(max) {
 | 
				
			||||||
            if PROCESSES.lock().await.read().await.len() <= max {
 | 
					            return Ok(finish_count);
 | 
				
			||||||
                return Ok(finish_count);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let procs = {
 | 
					 | 
				
			||||||
            PROCESSES.lock().await.read().await.clone()
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (idx, proc) in procs {
 | 
					 | 
				
			||||||
            if proc.finished {
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    PROCESSES.lock().await.write().await.remove(&idx);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                log::info!("{}", proc.msg);
 | 
					 | 
				
			||||||
                finish_count += 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        finish_count += purge_done_procs();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user