diff --git a/.github/workflows/enforce_label.yaml b/.github/workflows/enforce_label.yaml index 585ddaa..d4d36ee 100644 --- a/.github/workflows/enforce_label.yaml +++ b/.github/workflows/enforce_label.yaml @@ -11,4 +11,4 @@ jobs: - uses: yogevbd/enforce-label-action@2.2.2 with: REQUIRED_LABELS_ANY: "๐Ÿš€ Feature,๐Ÿ’… Improvement,๐Ÿ› Bug,๐Ÿ“š Docs,๐Ÿงช Tests,โ˜๏ธ CI,๐Ÿšจ Security,๐Ÿค– Dependencies" - REQUIRED_LABELS_ANY_DESCRIPTION: "It is necessary to add a label to your PR. This will help to categorize it and add a note on the release. Chose one of [๐Ÿš€ Feature,๐Ÿ’… Improvement,๐Ÿ› Bug,๐Ÿ“š Docs,๐Ÿงช Tests,โ˜๏ธ CI,๐Ÿšจ Security,๐Ÿค– Dependencies]" + REQUIRED_LABELS_ANY_DESCRIPTION: "It is necessary to add a label to your PR. This will help to categorize it and add a note on the release. Choose one of [๐Ÿš€ Feature,๐Ÿ’… Improvement,๐Ÿ› Bug,๐Ÿ“š Docs,๐Ÿงช Tests,โ˜๏ธ CI,๐Ÿšจ Security,๐Ÿค– Dependencies]" diff --git a/libprotonup/src/apps.rs b/libprotonup/src/apps.rs index 0f9e19a..f97896e 100644 --- a/libprotonup/src/apps.rs +++ b/libprotonup/src/apps.rs @@ -24,14 +24,7 @@ impl fmt::Display for App { } impl App { - pub fn app_available_variants(&self) -> Vec { - match *self { - Self::Steam => vec![Variant::GEProton], - Self::Lutris => vec![Variant::WineGE, Variant::GEProton], - } - } - - pub fn app_default_variant(&self) -> Variant { + pub fn app_wine_version(&self) -> Variant { match *self { Self::Steam => Variant::GEProton, Self::Lutris => Variant::WineGE, diff --git a/libprotonup/src/github.rs b/libprotonup/src/github.rs index 152d195..7eed1db 100644 --- a/libprotonup/src/github.rs +++ b/libprotonup/src/github.rs @@ -1,5 +1,5 @@ use crate::constants; -use crate::variants::VariantParameters; +use crate::variants::VariantGithubParameters; use anyhow::Result; use serde::{Deserialize, Serialize}; @@ -7,25 +7,53 @@ pub type ReleaseList = Vec; #[derive(Serialize, Deserialize, Debug)] pub struct Release { - url: String, + /// API URL of the Release + url: Option, + /// Tag name of the Release, examples "8.7-GE-1-Lol" "GE-Proton8-5" pub tag_name: String, name: String, - published_at: String, + /// Asset list for each Release, usually the tar.gz/tar.xz file and a sha512sum file for integrity checking assets: Vec, } +impl std::fmt::Display for Release { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", self.tag_name) + } +} + +impl Release { + /// Returns a Download struct corresponding to the Release + pub fn get_download_info(&self) -> Download { + let mut download: Download = Download::default(); + download.version = self.tag_name.clone(); + for asset in &self.assets { + if asset.name.ends_with("sha512sum") { + download.sha512sum_url = asset.browser_download_url.clone(); + } else if asset.name.ends_with("tar.gz") || asset.name.ends_with("tar.xz") { + download.download_url = asset.browser_download_url.clone(); + download.size = asset.size as u64; + } + } + + download + } +} + #[derive(Serialize, Deserialize, Debug)] pub struct Asset { url: String, id: i64, name: String, size: i64, - created_at: String, updated_at: String, browser_download_url: String, } -pub async fn list_releases(source: &VariantParameters) -> Result { +/// Returns a Vec of Releases from a GitHub repository, the URL used for the request is built from the passed in VariantParameters +pub async fn list_releases( + source: &VariantGithubParameters, +) -> Result { let agent = format!("{}/v{}", constants::USER_AGENT, constants::VERSION,); let url = format!( @@ -43,57 +71,9 @@ pub async fn list_releases(source: &VariantParameters) -> Result Result { - let agent = format!("{}/v{}", constants::USER_AGENT, constants::VERSION,); - - let client = reqwest::Client::builder().user_agent(agent).build()?; - - let mut download = Download::default(); - let release = match tag { - "latest" => { - let url = format!( - "{}/{}/{}/releases/latest", - source.repository_url, source.repository_account, source.repository_name, - ); - let rel: Release = client.get(url).send().await?.json().await?; - rel - } - _ => { - let url = format!( - "{}/{}/{}/releases/tags/{}", - source.repository_url, source.repository_account, source.repository_name, &tag - ); - let rel: Release = client.get(url).send().await?.json().await?; - rel - } - }; - - download.version = release.tag_name; - for asset in &release.assets { - if asset.name.ends_with("sha512sum") { - download.sha512sum = asset.browser_download_url.as_str().to_string(); - } - if asset.name.ends_with("tar.gz") { - download.created_at = asset.created_at.clone(); - download.download = asset.browser_download_url.as_str().to_string(); - download.size = asset.size as u64; - } - if asset.name.ends_with("tar.xz") { - download.created_at = asset.created_at.clone(); - download.download = asset.browser_download_url.as_str().to_string(); - download.size = asset.size as u64; - } - } - Ok(download) } #[cfg(test)] mod tests { @@ -102,66 +82,24 @@ mod tests { use super::*; #[tokio::test] - async fn test_fetch_data_from_tag() { + async fn test_list_releases() { let conditions = &[ ( - variants::Variant::WineGE.parameters(), - "latest", - "Get Steam", + variants::Variant::WineGE.get_github_parameters(), + "List WineGE", ), ( - variants::Variant::GEProton.parameters(), - "latest", - "Download Lutris", + variants::Variant::GEProton.get_github_parameters(), + "List GEProton", ), ]; - for (source_parameters, tag, desc) in conditions { - let result = fetch_data_from_tag(tag, source_parameters).await; - - assert!( - result.is_ok(), - "case :{} test: fetch_data_from_tag returned error", - desc - ); - - let result = result.unwrap(); - - assert!( - result.download.len() > 5, - "case : '{}' test: fetch_data_from_tag returned an wrong download link", - desc - ); - assert!( - result.sha512sum.len() > 5, - "case : '{}' test: fetch_data_from_tag returned an wrong sha512sum", - desc - ); - assert!( - result.size > 100, - "case : '{}' test: fetch_data_from_tag returned an wrong sha512sum", - desc - ); - assert!( - result.version.len() > 2, - "case : '{}' test: fetch_data_from_tag returned an wrong version", - desc - ); - } - } - - #[tokio::test] - async fn test_list_releases() { - let conditions = &[ - (variants::Variant::WineGE.parameters(), "List WineGE"), - (variants::Variant::GEProton.parameters(), "List GEProton"), - ]; for (source_parameters, desc) in conditions { let result = list_releases(source_parameters).await; assert!( result.is_ok(), - "case : '{}' test: fetch_data_from_tag returned error", + "case : '{}' test: list_releases returned error", desc ); @@ -188,8 +126,14 @@ mod tests { }; let conditions = &[ - (variants::Variant::WineGE.parameters(), "Get WineGE"), - (variants::Variant::GEProton.parameters(), "Get GEProton"), + ( + variants::Variant::WineGE.get_github_parameters(), + "Get WineGE", + ), + ( + variants::Variant::GEProton.get_github_parameters(), + "Get GEProton", + ), ]; for (source_parameters, desc) in conditions { let url = format!( diff --git a/libprotonup/src/variants.rs b/libprotonup/src/variants.rs index d6fcf77..6602482 100644 --- a/libprotonup/src/variants.rs +++ b/libprotonup/src/variants.rs @@ -1,7 +1,9 @@ use super::constants::*; use std::{fmt, str::FromStr}; -// VariantParameters stores the parameters for a variant of Proton -pub struct VariantParameters { + +/// Struct used to build GitHub api request URLs. +/// Contains the GitHub URL, username for GE, the repository name for either Wine GE or Proton GE, and a Variant Enum for identifying the parameters type +pub struct VariantGithubParameters { /// this is a link back to the enum variant variant_ref: Variant, /// URL of the repository server (GitHub compatible URL only at the moment) @@ -12,15 +14,15 @@ pub struct VariantParameters { pub repository_name: String, } -impl VariantParameters { +impl VariantGithubParameters { /// new_custom is a generator for custom VariantParameters pub fn new_custom( variant: Variant, repository_url: String, repository_account: String, repository_name: String, - ) -> VariantParameters { - VariantParameters { + ) -> VariantGithubParameters { + VariantGithubParameters { variant_ref: variant, repository_url, repository_account, @@ -71,16 +73,16 @@ impl Variant { } } - /// returns the default parameters for this Variant. - pub fn parameters(&self) -> VariantParameters { + /// Returns the default parameters for this Variant, used to build the GitHub URL + pub fn get_github_parameters(&self) -> VariantGithubParameters { match self { - Variant::GEProton => VariantParameters { + Variant::GEProton => VariantGithubParameters { variant_ref: Variant::GEProton, repository_url: GITHUB_URL.to_owned(), repository_name: GEPROTON_GITHUB_REPO.to_owned(), repository_account: GE_GITHUB_ACCOUNT.to_owned(), }, - Variant::WineGE => VariantParameters { + Variant::WineGE => VariantGithubParameters { variant_ref: Variant::WineGE, repository_url: GITHUB_URL.to_owned(), repository_name: WINEGE_GITHUB_REPO.to_owned(), diff --git a/protonup-rs/src/download.rs b/protonup-rs/src/download.rs index f67b23b..7cd3487 100644 --- a/protonup-rs/src/download.rs +++ b/protonup-rs/src/download.rs @@ -1,5 +1,7 @@ use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; +use inquire::{Select, Text}; + use std::fs; use std::path::{Path, PathBuf}; use std::{ @@ -8,32 +10,28 @@ use std::{ time::Duration, }; -use libprotonup::{constants, files, github, utils, variants}; +use crate::{file_path, helper_menus}; -pub(crate) async fn download_file( - tag: &str, - source: &variants::VariantParameters, -) -> Result { - let mut temp_dir = utils::expand_tilde(constants::TEMP_DIR).unwrap(); +use libprotonup::{ + apps, constants, files, + github::{self, Download, Release}, + utils, + variants::{self, Variant}, +}; - let download = match github::fetch_data_from_tag(tag, source).await { - Ok(data) => data, - Err(e) => { - eprintln!("Failed to fetch GitHub data, make sure you're connected to the internet\nError: {}", e); - std::process::exit(1) - } - }; +pub(crate) async fn download_file(download: Download) -> Result { + let mut temp_dir = utils::expand_tilde(constants::TEMP_DIR).unwrap(); - temp_dir.push(if download.download.ends_with("tar.gz") { + temp_dir.push(if download.download_url.ends_with("tar.gz") { format!("{}.tar.gz", &download.version) - } else if download.download.ends_with("tar.xz") { + } else if download.download_url.ends_with("tar.xz") { format!("{}.tar.xz", &download.version) } else { eprintln!("Downloaded file wasn't of the expected type. (tar.(gz/xz)"); std::process::exit(1) }); - let git_hash = files::download_file_into_memory(&download.sha512sum) + let git_hash = files::download_file_into_memory(&download.sha512sum_url) .await .unwrap(); @@ -44,7 +42,7 @@ pub(crate) async fn download_file( let (progress, done) = files::create_progress_trackers(); let progress_read = Arc::clone(&progress); let done_read = Arc::clone(&done); - let url = String::from(&download.download); + let url = String::from(&download.download_url); let tmp_dir = String::from(temp_dir.to_str().unwrap()); // start ProgressBar in another thread @@ -73,7 +71,7 @@ pub(crate) async fn download_file( }); files::download_file_progress( - download.download, + download.download_url, download.size, temp_dir.clone().as_path(), progress, @@ -92,7 +90,7 @@ pub(crate) async fn download_file( pub(crate) async fn unpack_file( dowaload_path: &Path, install_path: &str, - source: &variants::VariantParameters, + wine_version: &Variant, ) -> Result<(), String> { let install_dir = utils::expand_tilde(install_path).unwrap(); @@ -100,12 +98,186 @@ pub(crate) async fn unpack_file( println!("Unpacking files into install location. Please wait"); files::decompress(dowaload_path, install_dir.as_path()).unwrap(); - let source_type = source.variant_type(); println!( "Done! Restart {}. {} installed in {}", - source_type.intended_application(), - source_type, + wine_version.intended_application(), + wine_version, install_dir.to_string_lossy(), ); Ok(()) } + +pub async fn run_quick_downloads() { + let found_apps = apps::list_installed_apps(); + if found_apps.is_empty() { + println!("No apps found. Please install at least one app before using this feature."); + return; + } + println!( + "Found the following apps: {}", + found_apps + .iter() + .map(|app| app.to_string()) + .collect::>() + .join(", ") + ); + + for app_inst in &found_apps { + let wine_version = app_inst.into_app().app_wine_version(); + let destination = app_inst.default_install_dir().to_string(); + println!( + "\nQuick Download: {} for {} into -> {}", + wine_version, + app_inst.into_app(), + destination + ); + + // Get the latest Download info for the wine_version + let download = match github::list_releases(&wine_version.get_github_parameters()).await { + // Get the Download info from the first item on the list, the latest version + Ok(release_list) => release_list[0].get_download_info(), + Err(e) => { + eprintln!("Failed to fetch Github data, make sure you're connected to the internet.\nError: {}", e); + std::process::exit(1) + } + }; + + let file = download_file(download).await.unwrap(); + unpack_file(&file, &destination, &wine_version) + .await + .unwrap_or_else(|e| { + eprintln!( + "Failed unpacking file {} into {}. Error: {}", + file.to_string_lossy(), + destination, + e + ); + }); + } +} + +/// Start the Download for the selected app +/// If no app is provided, the user is prompted for which version of Wine/Proton to use and what directory to extract to +pub async fn download_to_selected_app(app: Option) { + // Get the version of Wine/Proton to install + let wine_version = match app { + // Use the default for the app + Some(app) => app.app_wine_version(), + // Or have the user select which one + None => Select::new( + "Choose the variant you want to install:", + variants::ALL_VARIANTS.to_vec(), + ) + .prompt() + .unwrap_or_else(|_| std::process::exit(0)), + }; + + // Get the folder to install Wine/Proton into + let install_dir: String = match app { + // If the user selected an app (Steam/Lutris)... + Some(app) => match app.detect_installation_method() { + installed_apps if installed_apps.len() == 0 => { + println!("Install location for selected app(s) not found. Exiting."); + std::process::exit(0); + } + + + // Figure out which versions of the App the user has (Native/Flatpak) + installed_apps if installed_apps.len() == 1 => { + println!( + "Detected {}. Installing to {}", + installed_apps[0], + installed_apps[0].default_install_dir() + ); + installed_apps[0].default_install_dir().to_string() + } + // If the user has more than one installation method, ask them which one they would like to use + installed_apps => Select::new( + "Detected several app versions, which would you like to use?", + installed_apps, + ) + .prompt() + .unwrap_or_else(|_| std::process::exit(0)) + .default_install_dir() + .to_string(), + }, + // If the user didn't select an app, ask them what directory they want to install to + None => Text::new("Installation path:") + .with_autocomplete(file_path::FilePathCompleter::default()) + .with_help_message(&format!( + "Current directory: {}", + &std::env::current_dir() + .unwrap_or_else(|_| std::process::exit(0)) + .to_string_lossy() + )) + .with_default( + &std::env::current_dir() + .unwrap_or_else(|_| std::process::exit(0)) + .to_string_lossy(), + ) + .prompt() + .unwrap_or_else(|_| std::process::exit(0)), + }; + + let release_list = match github::list_releases(&wine_version.get_github_parameters()).await { + Ok(data) => data, + Err(e) => { + eprintln!("Failed to fetch Github data, make sure you're connected to the internet.\nError: {}", e); + std::process::exit(1) + } + }; + + // versions_to_install = vec![]; + + // Let the user choose which releases they want to use + let mut release_list = match helper_menus::multiple_select_menu( + "Select the versions you want to download :", + release_list, + ) { + Ok(release_list) => release_list, + Err(e) => { + eprintln!("The tag list could not be processed.\nError: {}", e); + vec![] + } + }; + + // Check if the versions the user selected are already on the disk + check_if_already_downloaded(&mut release_list, &install_dir).await; + + // Prepare the download for the user's chosen releases/versions + // TODO Look into using async in a way to download multiple files at once, would need to .join all the download_file() 'Futures' + for release in &release_list { + match download_file(release.get_download_info()).await { + Ok(file) => { + // TODO: should just upack once and copy to all folders + unpack_file(&file, &install_dir, &wine_version) + .await + .unwrap(); + } + Err(e) => { + eprintln!( + "Error downloading {}, make sure you're connected to the internet\nError: {}", + release.tag_name, e + ) + } + } + } +} + +/// Checks if the selected Release/version is already installed. +/// Will prompt the user to overwrite existing files +async fn check_if_already_downloaded(release_list: &mut Vec, install_dir: &str) { + release_list.retain(|release| { + // Check if versions exist in disk. + // If they do, ask the user if it should be overwritten + !(files::check_if_exists(&install_dir, &release.tag_name) + && !helper_menus::confirm_menu( + format!( + "Version {} exists in the installation path. Overwrite?", + &release.tag_name + ), + String::from("If you choose yes, you will re-install it."), + false, + )) + }); +} diff --git a/protonup-rs/src/helper_menus.rs b/protonup-rs/src/helper_menus.rs index d6721fe..db8c9dd 100644 --- a/protonup-rs/src/helper_menus.rs +++ b/protonup-rs/src/helper_menus.rs @@ -1,18 +1,15 @@ use inquire::{Confirm, InquireError, MultiSelect}; -use libprotonup::apps::AppInstallations; -pub(crate) fn tag_menu(message: &str, options: Vec) -> Result, InquireError> { - MultiSelect::new(message, options) - .with_default(&[0_usize]) - .prompt() -} - -pub(crate) fn variants_menu( +/// Creates a inquire::MultiSelect menu with the first option selected +pub(crate) fn multiple_select_menu( message: &str, - options: Vec, -) -> Result, InquireError> { + options: Vec, +) -> Result, InquireError> +where + T: std::fmt::Display, +{ MultiSelect::new(message, options) - .with_default(&[0_usize]) + .with_default(&[0]) .prompt() } diff --git a/protonup-rs/src/main.rs b/protonup-rs/src/main.rs index 325fcb3..c12f8cb 100644 --- a/protonup-rs/src/main.rs +++ b/protonup-rs/src/main.rs @@ -1,18 +1,16 @@ use clap::Parser; -use inquire::{Select, Text}; +use inquire::Select; use std::fmt; -use libprotonup::{apps, files, github, variants}; +use libprotonup::apps::App; mod download; mod file_path; mod helper_menus; mod manage_apps; -use download::{download_file, unpack_file}; -use helper_menus::{confirm_menu, tag_menu, variants_menu}; use manage_apps::manage_apps_routine; #[derive(Debug, Parser)] @@ -26,8 +24,8 @@ struct Opt { #[allow(clippy::upper_case_acronyms)] enum InitialMenu { QuickUpdate, - DoanloadForSteam, - DoanloadForLutris, + DownloadForSteam, + DownloadForLutris, DownloadIntoCustomLocation, ManageExistingInstallations, } @@ -36,8 +34,8 @@ impl InitialMenu { // could be generated by macro const VARIANTS: &'static [InitialMenu] = &[ Self::QuickUpdate, - Self::DoanloadForSteam, - Self::DoanloadForLutris, + Self::DownloadForSteam, + Self::DownloadForLutris, Self::DownloadIntoCustomLocation, Self::ManageExistingInstallations, ]; @@ -47,8 +45,8 @@ impl fmt::Display for InitialMenu { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Self::QuickUpdate => write!(f, "Quick Update (detect apps and auto download)"), - Self::DoanloadForSteam => write!(f, "Download GE-Proton for Steam"), - Self::DoanloadForLutris => write!(f, "Download GE-Proton/Wine-GE for Lutris"), + Self::DownloadForSteam => write!(f, "Download GE-Proton for Steam"), + Self::DownloadForLutris => write!(f, "Download GE-Proton/Wine-GE for Lutris"), Self::DownloadIntoCustomLocation => { write!(f, "Download GE-Proton/Wine-GE into custom location") } @@ -60,280 +58,25 @@ impl fmt::Display for InitialMenu { #[tokio::main] async fn main() { // run quick downloads and skip InitialMenu - if run_quick_downloads().await { - return; - } - - // Default Parameters - let source: variants::VariantParameters; - let mut install_dirs: Vec = vec![]; - let mut tags: Vec = vec![]; - - let mut should_open_tag_selector = false; - let mut should_open_dir_selector = false; - let mut manage_existing_versions = false; - let mut should_detect_apps = false; - let mut apps_to_use: Vec = vec![]; - let mut selected_app: Option = None; - - let answer: InitialMenu = Select::new( - "ProtonUp Menu: Chose your action:", - InitialMenu::VARIANTS.to_vec(), - ) - .with_page_size(10) - .prompt() - .unwrap_or_else(|_| std::process::exit(0)); - - // Set parameters based on users choice - match answer { - InitialMenu::QuickUpdate => { - should_detect_apps = true; - } - InitialMenu::DoanloadForSteam => { - selected_app = Some(apps::App::Steam); - should_open_tag_selector = true; - } - InitialMenu::DoanloadForLutris => { - selected_app = Some(apps::App::Lutris); - should_open_tag_selector = true; - } - InitialMenu::DownloadIntoCustomLocation => { - should_open_dir_selector = true; - should_open_tag_selector = true; - } - InitialMenu::ManageExistingInstallations => manage_existing_versions = true, - } - - // This is where the execution happens - - // If the user wants to manage existing installations, we skip the rest of the menu - if manage_existing_versions { - manage_apps_routine(); - return; - } - - if should_detect_apps { - apps_to_use = apps::list_installed_apps(); - if apps_to_use.is_empty() { - println!("Could not find any version of Steam or Lutris. Please install at least one app before using this feature."); - return; - } - println!( - "Found the following apps: {}", - apps_to_use - .iter() - .map(|app| app.to_string()) - .collect::>() - .join(", ") - ); - - // TODO: this should be done in a single place. But here we use the default for every app, and in the other part we install all selected tags for all selected apps - for app_inst in &apps_to_use { - let variant = app_inst.into_app().app_default_variant(); - let destination = app_inst.default_install_dir().to_string(); - println!( - "\nQuick Download: {} for {} into -> {}\n", - variant, - app_inst.into_app(), - destination - ); - let file = download_file("latest", &variant.parameters()) - .await - .unwrap(); - unpack_file(&file, &destination, &variant.parameters()) - .await - .unwrap_or_else(|_| std::process::exit(0)); - } - return; - } - - // the rest of th execution is for updating/installing new versions - if should_open_dir_selector { - let current_dir = std::env::current_dir().unwrap_or_else(|_| std::process::exit(0)); - let help_message = format!("Current directory: {}", ¤t_dir.to_string_lossy()); - let answer = Text::new("Installation path:") - .with_autocomplete(file_path::FilePathCompleter::default()) - .with_help_message(&help_message) - .with_default(¤t_dir.to_string_lossy()) - .prompt(); - - match answer { - Ok(path) => { - println!("Will use the custom path: {}", path); - install_dirs.push(path) - } - Err(error) => { - println!("Error choosing custom path. Using the default. Error: {error:?}"); - } - }; - } - - let source_options = match selected_app { - Some(app) => app.app_available_variants(), - None => variants::ALL_VARIANTS.to_vec(), - }; - - if source_options.len() == 1 { - source = source_options[0].parameters(); - } else { - source = Select::new("Chose the variant you want to install:", source_options) - .prompt() - .map(|variant| variant.parameters()) - .unwrap_or_else(|_| std::process::exit(0)); - }; - - // if empty, no apps were detected, so we ask the user to chose one - if apps_to_use.is_empty() && selected_app.is_some() { - apps_to_use = apps::list_installed_apps(); - if !apps_to_use.is_empty() { - // filter detected app installations that match selected app - apps_to_use.retain(|app_inst| app_inst.into_app() == selected_app.unwrap()); - apps_to_use.retain(|app_inst| { - confirm_menu( - format!("Detected {app_inst} installation. Use it?"), - String::from("If you have multiple install options, you can chose all of them"), - true, - ) - }) - } - - // if apps_to_use is empty, list default options - if apps_to_use.is_empty() { - apps_to_use = variants_menu( - "Chose the app you want to install for:", - selected_app.unwrap().app_installations(), - ) - .unwrap_or_else(|_| std::process::exit(0)); - } - } - - // if still no apps were selected - if apps_to_use.is_empty() && selected_app.is_some() { - return; - } - - // past this point, either a path was manually selected or selected_app is not empty - if install_dirs.is_empty() { - install_dirs = apps_to_use - .iter() - .map(|app_inst| app_inst.default_install_dir().to_string()) - .collect(); - } - - if should_open_tag_selector { - tags = vec![]; - let release_list = match github::list_releases(&source).await { - Ok(data) => data, - Err(e) => { - eprintln!("Failed to fetch Github data, make sure you're connected to the internet.\nError: {}", e); - std::process::exit(1) - } - }; - let tag_list: Vec = release_list.into_iter().map(|r| (r.tag_name)).collect(); - let list = match tag_menu("Select the versions you want to download :", tag_list) { - Ok(tags) => tags, - Err(e) => { - eprintln!("The tag list could not be processed.\nError: {}", e); - vec![] - } - }; - for tag_iter in list.iter() { - let tag = String::from(tag_iter); - tags.push(tag); - } - } else { - let tag = match github::fetch_data_from_tag("latest", &source).await { - Ok(data) => data, - Err(e) => { - eprintln!("Failed to fetch Github data, make sure you're connected to the internet.\nError: {}", e); - std::process::exit(1) - } - }; - tags.push(tag.version) - } - - tags.retain(|tag_name| { - // Check if versions exist in disk. - // If they do, ask the user if it should be overwritten - !(install_dirs - .iter() - .any(|install_dir| files::check_if_exists(install_dir, tag_name)) - && !confirm_menu( - format!("Version {tag_name} exists in the installation path. Overwrite?"), - String::from("If you choose yes, you will re-install it."), - false, - )) - }); - - // install the versions that are in the tags array, into the locations that are in the install_dirs array - for tag_name in tags.clone() { - let tag = match github::fetch_data_from_tag(&tag_name, &source).await { - Ok(data) => data, - Err(e) => { - eprintln!("Failed to fetch Github data, make sure you're connected to the internet.\nError: {}", e); - std::process::exit(1) - } - }; - - match download_file(&tag_name, &source).await { - Ok(file) => { - // TODO: should just upack once and copy to all folders - for install_path in &install_dirs { - unpack_file(&file, install_path, &source).await.unwrap(); - } - } - Err(e) => { - eprintln!( - "Error downloading {}, make sure you're connected to the internet\nError: {}", - tag.version, e - ) - } - } - } -} - -async fn run_quick_downloads() -> bool { let Opt { quick_download } = Opt::parse(); - if quick_download { - let found_apps = apps::list_installed_apps(); - if found_apps.is_empty() { - println!("No apps found. Please install at least one app before using this feature."); - return false; - } - println!( - "Found the following apps: {}", - found_apps - .iter() - .map(|app| app.to_string()) - .collect::>() - .join(", ") - ); - - for app_inst in &found_apps { - let variant = app_inst.into_app().app_default_variant(); - let destination = app_inst.default_install_dir().to_string(); - println!( - "\nQuick Download: {} for {} into -> {}", - variant, - app_inst.into_app(), - destination - ); - let file = download_file("latest", &variant.parameters()) - .await - .unwrap(); - unpack_file(&file, &destination, &variant.parameters()) - .await - .unwrap_or_else(|e| { - eprintln!( - "Failed unpacking file {} into {}. Error: {}", - file.to_string_lossy(), - destination, - e - ); - }); + download::run_quick_downloads().await + } else { + let answer: InitialMenu = Select::new( + "ProtonUp Menu: Choose your action:", + InitialMenu::VARIANTS.to_vec(), + ) + .with_page_size(10) + .prompt() + .unwrap_or_else(|_| std::process::exit(0)); + + // Set parameters based on users choice + match answer { + InitialMenu::QuickUpdate => download::run_quick_downloads().await, + InitialMenu::DownloadForSteam => download::download_to_selected_app(Some(App::Steam)).await, + InitialMenu::DownloadForLutris => download::download_to_selected_app(Some(App::Lutris)).await, + InitialMenu::DownloadIntoCustomLocation => download::download_to_selected_app(None).await, + InitialMenu::ManageExistingInstallations => manage_apps_routine(), } } - - quick_download } diff --git a/protonup-rs/src/manage_apps.rs b/protonup-rs/src/manage_apps.rs index 7a5d04f..d419c56 100644 --- a/protonup-rs/src/manage_apps.rs +++ b/protonup-rs/src/manage_apps.rs @@ -2,7 +2,7 @@ use inquire::MultiSelect; use libprotonup::{apps, files}; use std::fmt; -use super::helper_menus::{confirm_menu, tag_menu}; +use super::helper_menus::{confirm_menu, multiple_select_menu}; #[derive(Debug, PartialEq, Eq, Clone)] pub(crate) enum ManageAppsMenuOptions { @@ -66,7 +66,7 @@ pub(crate) fn manage_apps_routine() { println!("No versions found for {}, skipping... ", app); continue; } - let delete_versions = match tag_menu( + let delete_versions = match multiple_select_menu( &format!("Select the versions you want to DELETE from {}", app), versions, ) {