use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use inquire::{Confirm, MultiSelect, Select, Text};
use std::fmt;
use std::fs;
use std::fs::create_dir_all;
use std::sync::atomic::Ordering;
use std::thread;
use std::{sync::Arc, time::Duration};
use structopt::StructOpt;
mod file_path;
use libprotonup::{constants, file, github, utils};
#[derive(Debug, StructOpt)]
struct Opt {
#[structopt(short, long)]
quick_download: bool,
#[structopt(short = "f", long)]
quick_download_flatpak: bool,
}
#[derive(Debug, Copy, Clone)]
#[allow(clippy::upper_case_acronyms)]
enum Menu {
QuickUpdate,
QuickUpdateFlatpak,
ChoseReleases,
ChoseReleasesFlatpak,
ChoseReleasesCustomDir,
}
impl Menu {
const VARIANTS: &'static [Menu] = &[
Self::QuickUpdate,
Self::QuickUpdateFlatpak,
Self::ChoseReleases,
Self::ChoseReleasesFlatpak,
Self::ChoseReleasesCustomDir,
];
}
impl fmt::Display for Menu {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::QuickUpdate => write!(f, "Quick Update (Download latest GE Proton)"),
Self::QuickUpdateFlatpak => write!(
f,
"Quick Update (Download latest GE Proton) for Flatpak Steam"
),
Self::ChoseReleases => write!(f, "Chose GE Proton Releases from list"),
Self::ChoseReleasesFlatpak => {
write!(f, "Chose GE Proton Releases from list for Flatpak Steam")
}
Self::ChoseReleasesCustomDir => write!(
f,
"Chose GE Proton Releases and install to custom directory"
),
}
}
}
fn tag_menu(options: Vec<String>) -> Vec<String> {
let answer = MultiSelect::new("Select the Versions you want to download :", options)
.with_default(&vec![0 as usize])
.prompt();
match answer {
Ok(list) => return list,
Err(_) => {
println!("The tag list could not be processed");
return vec![];
}
}
}
fn confirm_menu(text: String) -> bool {
let answer = Confirm::new(&text)
.with_default(false)
.with_help_message("If you chose yes, we will re install it.")
.prompt();
match answer {
Ok(choice) => choice,
Err(_) => false,
}
}
#[tokio::main]
async fn main() {
let Opt {
quick_download,
quick_download_flatpak,
} = Opt::from_args();
if quick_download {
download_file("latest", constants::DEFAULT_INSTALL_DIR.to_string())
.await
.unwrap();
return;
}
if quick_download_flatpak {
download_file("latest", constants::DEFAULT_INSTALL_DIR_FLATPAK.to_string())
.await
.unwrap();
return;
}
let answer: Menu = Select::new("ProtonUp Menu: Chose your action:", Menu::VARIANTS.to_vec())
.prompt()
.unwrap();
match answer {
Menu::QuickUpdate => {
let tag = github::fetch_data_from_tag("latest").await.unwrap();
if file::check_if_exists(
constants::DEFAULT_INSTALL_DIR.to_owned(),
tag.version.clone(),
) {
if !confirm_menu(format!(
"Version {} exists in installation path. Overwrite ?",
tag.version
)) {
return;
}
}
download_file("latest", constants::DEFAULT_INSTALL_DIR.to_string())
.await
.unwrap();
return;
}
Menu::QuickUpdateFlatpak => {
let tag = github::fetch_data_from_tag("latest").await.unwrap();
if file::check_if_exists(
constants::DEFAULT_INSTALL_DIR_FLATPAK.to_owned(),
tag.version.clone(),
) {
if !confirm_menu(format!(
"Version {} exists in installation path. Overwrite ?",
tag.version
)) {
return;
}
}
download_file("latest", constants::DEFAULT_INSTALL_DIR.to_string())
.await
.unwrap();
return;
}
Menu::ChoseReleases => {
let release_list = libprotonup::github::list_releases().await.unwrap();
let tag_list: Vec<String> = release_list
.into_iter()
.map(|r| (r.tag_name.clone()))
.collect();
let list = tag_menu(tag_list);
for tag in list.iter() {
if file::check_if_exists(constants::DEFAULT_INSTALL_DIR.to_owned(), tag.to_owned())
{
if !confirm_menu(format!(
"Version {} exists in installation path. Overwrite ?",
tag
)) {
return;
}
}
download_file(tag, constants::DEFAULT_INSTALL_DIR.to_string())
.await
.unwrap();
}
return;
}
Menu::ChoseReleasesFlatpak => {
let release_list = libprotonup::github::list_releases().await.unwrap();
let tag_list: Vec<String> = release_list
.into_iter()
.map(|r| (r.tag_name.clone()))
.collect();
let list = tag_menu(tag_list);
for tag in list.iter() {
if file::check_if_exists(
constants::DEFAULT_INSTALL_DIR_FLATPAK.to_owned(),
tag.to_owned(),
) {
if !confirm_menu(format!(
"Version {} exists in installation path. Overwrite ?",
tag
)) {
return;
}
}
download_file(tag, constants::DEFAULT_INSTALL_DIR_FLATPAK.to_string())
.await
.unwrap();
}
return;
}
Menu::ChoseReleasesCustomDir => {
let current_dir = std::env::current_dir().unwrap();
let help_message = format!("Current directory: {}", current_dir.to_string_lossy());
let answer = Text::new("Installation Path:")
.with_autocomplete(file_path::FilePathCompleter::default())
.with_help_message(&help_message)
.prompt();
let chosen_path = match answer {
Ok(path) => path,
Err(error) => {
println!(
"Error choosing custom path. Using the default. Error: {:?}",
error
);
constants::DEFAULT_INSTALL_DIR.to_string()
}
};
let release_list = libprotonup::github::list_releases().await.unwrap();
let tag_list: Vec<String> = release_list
.into_iter()
.map(|r| (r.tag_name.clone()))
.collect();
let list = tag_menu(tag_list);
for tag in list.iter() {
if file::check_if_exists(constants::DEFAULT_INSTALL_DIR.to_owned(), tag.to_owned())
{
if !confirm_menu(format!(
"Version {} exists in installation path. Overwrite ?",
tag
)) {
return;
}
}
download_file(tag, chosen_path.clone()).await.unwrap();
}
return;
}
}
}
pub async fn download_file(tag: &str, install_path: String) -> Result<(), String> {
let install_dir = utils::expand_tilde(install_path).unwrap();
let mut temp_dir = utils::expand_tilde(constants::TEMP_DIR).unwrap();
let download = github::fetch_data_from_tag(tag).await.unwrap();
temp_dir.push(format!("{}.tar.gz", &download.version));
create_dir_all(&install_dir).unwrap();
let git_hash = file::download_file_into_memory(&download.sha512sum)
.await
.unwrap();
if temp_dir.exists() {
fs::remove_file(&temp_dir).unwrap();
}
let (progress, done) = file::create_progress_trackers();
let progress_read = Arc::clone(&progress);
let done_read = Arc::clone(&done);
let url = String::from(&download.download);
let i_dir = String::from(install_dir.to_str().unwrap());
thread::spawn(move || {
let pb = ProgressBar::with_draw_target(
Some(download.size),
ProgressDrawTarget::stderr_with_hz(20),
);
pb.set_style(ProgressStyle::default_bar()
.template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec})").unwrap()
.progress_chars("#>-"));
pb.set_message(format!("Downloading {}", url.split('/').last().unwrap()));
let wait_time = Duration::from_millis(50); loop {
let newpos = progress_read.load(Ordering::Relaxed);
pb.set_position(newpos as u64);
if done_read.load(Ordering::Relaxed) {
break;
}
thread::sleep(wait_time);
}
pb.set_message(format!("Downloaded {} to {}", url, i_dir));
pb.abandon();
println!("Checking file integrity"); });
file::download_file_progress(
download.download,
download.size,
temp_dir.clone(),
progress,
done,
)
.await
.unwrap();
if !file::hash_check_file(temp_dir.to_str().unwrap().to_string(), git_hash).unwrap() {
return Err("Failed checking file hash".to_string());
}
println!("Unpacking files into install location. Please wait");
file::decompress(temp_dir, install_dir.clone()).unwrap();
println!(
"Done! Restart Steam. Proton GE installed in {}",
install_dir.to_string_lossy()
);
return Ok(());
}