Added async logic and switched some functions (Alot faster) #2

Merged
MojaveMF merged 1 commits from main into main 2023-11-04 05:29:20 -05:00
6 changed files with 536 additions and 293 deletions

94
Cargo.lock generated
View File

@ -812,6 +812,16 @@ dependencies = [
"tempfile", "tempfile",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.16" version = "0.2.16"
@ -902,6 +912,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -1198,6 +1214,15 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"
@ -1261,7 +1286,7 @@ dependencies = [
[[package]] [[package]]
name = "syntax_bootstrapper" name = "syntax_bootstrapper"
version = "1.2.0" version = "1.2.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"colored", "colored",
@ -1273,6 +1298,8 @@ dependencies = [
"reqwest", "reqwest",
"term_size", "term_size",
"tokio", "tokio",
"tracing",
"tracing-subscriber",
"winreg 0.51.0", "winreg 0.51.0",
"winres", "winres",
"zip-extract", "zip-extract",
@ -1321,6 +1348,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.45" version = "0.1.45"
@ -1435,22 +1472,59 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.37" version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [ dependencies = [
"cfg-if",
"pin-project-lite", "pin-project-lite",
"tracing-attributes",
"tracing-core", "tracing-core",
] ]
[[package]] [[package]]
name = "tracing-core" name = "tracing-attributes"
version = "0.1.31" version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@ -1503,6 +1577,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

View File

@ -17,6 +17,8 @@ tokio = { version = "1.32.0", features=["full"]}
futures-util = "0.3.28" futures-util = "0.3.28"
md5 = "0.7.0" md5 = "0.7.0"
zip-extract = "0.1.2" zip-extract = "0.1.2"
tracing = "0.1.40"
tracing-subscriber = "0.3.17"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.51.0" winreg = "0.51.0"

8
src/ascii.txt Normal file
View File

@ -0,0 +1,8 @@
.d8888b. Y88b d88P 888b 888 88888888888 d8888 Y88b d88P
d88P Y88b Y88b d88P 8888b 888 888 d88888 Y88b d88P
Y88b. Y88o88P 88888b 888 888 d88P888 Y88o88P
\"Y888b. Y888P 888Y88b 888 888 d88P 888 Y888P
\"Y88b. 888 888 Y88b888 888 d88P 888 d888b
\"888 888 888 Y88888 888 d88P 888 d88888b
Y88b d88P 888 888 Y8888 888 d8888888888 d88P Y88b
\"Y8888P\" 888 888 Y888 888 d88P 888 d88P Y88b

19
src/constants/mod.rs Normal file
View File

@ -0,0 +1,19 @@
pub const FILES_TO_DOWNLOAD: [[&str; 2]; 17] = [
["SyntaxApp.zip", "./"],
["NPSyntaxProxy.zip", "./"],
["SyntaxProxy.zip", "./"],
["Libraries.zip", "./"],
["redist.zip", "./"],
["content-textures.zip", "./content/textures"],
["content-textures2.zip", "./content/textures"],
["content-textures3.zip", "./content/textures"],
["content-terrain.zip", "./content/terrain"],
["content-fonts.zip", "./content/fonts"],
["content-sounds.zip", "./content/sounds"],
["content-scripts.zip", "./content/scripts"],
["content-sky.zip", "./content/sky"],
["content-music.zip", "./content/music"],
["content-particles.zip", "./content/particles"],
["2018client.zip", "./Client2018"],
["2020client.zip", "./Client2020"],
];

View File

@ -1,159 +1,83 @@
use colored::*; use colored::*;
use std::path::PathBuf;
use reqwest::Client;
use dirs::data_local_dir; use dirs::data_local_dir;
use futures_util::StreamExt; use futures_util::StreamExt;
use md5; use md5;
use metadata::LevelFilter;
use reqwest::Client;
use std::path::PathBuf;
use tokio::task::JoinSet;
use zip_extract; use zip_extract;
mod constants;
mod util;
use constants::*;
use tracing::*;
use util::*;
#[cfg(not(target_os = "windows"))]
use std::io::prelude::*;
#[cfg(not(target_os = "windows"))]
use std::os::unix::fs::FileExt;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use std::os::windows::prelude::FileExt; use std::os::windows::prelude::FileExt;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use winreg::enums::*; use winreg::enums::*;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use winreg::RegKey; use winreg::RegKey;
#[cfg(not(target_os = "windows"))]
use std::io::prelude::*;
#[cfg(not(target_os = "windows"))]
use std::os::unix::fs::FileExt;
fn info( message : &str ) {
let time = chrono::Local::now().format("%H:%M:%S").to_string();
println!("[{}] [{}] {}", time.bold().blue(), "INFO".bold().green(), message);
}
fn error( message : &str ) {
let time = chrono::Local::now().format("%H:%M:%S").to_string();
println!("[{}] [{}] {}", time.bold().blue(), "ERROR".bold().red(), message);
}
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
fn debug( message : &str ) { const DEBUG: bool = true;
let time = chrono::Local::now().format("%H:%M:%S").to_string(); #[cfg(debug_assertions)]
println!("[{}] [{}] {}", time.bold().blue(), "DEBUG".bold().yellow(), message); const MAX_TRACING_LEVEL: LevelFilter = LevelFilter::DEBUG;
}
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
fn debug( message : &str ) {} const DEBUG: bool = false;
#[cfg(not(debug_assertions))]
const MAX_TRACING_LEVEL: LevelFilter = LevelFilter::ERROR;
pub async fn http_get( client: &Client ,url: &str ) -> Result<String, reqwest::Error> { const ASCII_ART: &str = include_str!("./ascii.txt");
debug(&format!("{} {}", "GET".green(), url.bright_blue()));
let response = client.get(url).send().await;
if (response.is_err()) {
debug(&format!("Failed to fetch {}", url.bright_blue()));
return Err(response.err().unwrap());
}
let response_body = response.unwrap().text().await.unwrap();
Ok(response_body)
}
pub async fn download_file( client: &Client, url: &str, path: &PathBuf ) {
debug(&format!("{} {}", "GET".green(), url.bright_blue()));
let response = client.get(url).send().await.unwrap();
let content_length = response.content_length().unwrap();
debug(&format!("Content Length: {}", content_length));
let time = chrono::Local::now().format("%H:%M:%S").to_string();
let pg_bar_str = " {spinner:.green} [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})";
let progress_bar = indicatif::ProgressBar::new(content_length);
let progress_style = indicatif::ProgressStyle::default_bar()
.template(
format!("{}\n{}",
format!(
"[{}] [{}] {}",
time.bold().blue(),
"INFO".bold().green(),
&format!("Downloading {}", &url.bright_blue())
),
pg_bar_str).as_str()
)
.unwrap().progress_chars("#>-");
progress_bar.set_style(progress_style);
progress_bar.set_message("Downloading File");
let file = std::fs::File::create(path).unwrap();
let mut downloaded: u64 = 0;
let mut stream = response.bytes_stream();
while let Some(item) = stream.next().await {
let chunk = item.or(Err(format!("Error while downloading file"))).unwrap();
#[cfg(target_os = "windows")]
{
file.seek_write(chunk.as_ref(), downloaded).unwrap();
}
#[cfg(not(target_os = "windows"))]
{
file.write_at(chunk.as_ref(), downloaded).unwrap();
}
let new = std::cmp::min(downloaded + (chunk.len() as u64), content_length);
downloaded = new;
progress_bar.set_position(new);
}
progress_bar.finish();
info(format!("Finished downloading {}", url.green()).as_str());
}
pub async fn download_file_prefix( client: &Client, url: &str, path_prefix : &PathBuf ) -> PathBuf {
let path = path_prefix.join(generate_md5(url).await);
download_file(client, url, &path).await;
return path;
}
pub async fn generate_md5( input : &str ) -> String {
let hashed_input = md5::compute(input.as_bytes());
return format!("{:x}", hashed_input);
}
pub async fn create_folder_if_not_exists( path: &PathBuf ) {
if !path.exists() {
info(&format!("Creating folder {}", path.to_str().unwrap().bright_blue()));
std::fs::create_dir_all(path).unwrap();
}
}
fn get_installation_directory() -> PathBuf {
return PathBuf::from(data_local_dir().unwrap().to_str().unwrap()).join("Syntax");
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// Clear the terminal before printing the startup text // Clear the terminal before printing the startup text
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
std::process::Command::new("cmd") std::process::Command::new("cmd")
.args(&["/c", "cls"]) .args(&["/c", "cls"])
.spawn() .spawn()
.expect("cls command failed to start") .expect("cls command failed to start")
.wait() .wait()
.expect("failed to wait"); .expect("failed to wait");
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
std::process::Command::new("clear").spawn().unwrap(); std::process::Command::new("clear").spawn().unwrap();
} }
tracing_subscriber::fmt()
.with_max_level(MAX_TRACING_LEVEL)
.pretty()
.init();
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let base_url : &str = "www.syntax.eco"; let base_url: &str = "www.syntax.eco";
let mut setup_url : &str = "setup.syntax.eco"; let mut setup_url: &str = "setup.syntax.eco";
let fallback_setup_url : &str = "d2f3pa9j0u8v6f.cloudfront.net"; let fallback_setup_url: &str = "d2f3pa9j0u8v6f.cloudfront.net";
let mut bootstrapper_filename :&str = "SyntaxPlayerLauncher.exe"; let mut bootstrapper_filename: &str = "SyntaxPlayerLauncher.exe";
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
bootstrapper_filename = "SyntaxPlayerLinuxLauncher"; bootstrapper_filename = "SyntaxPlayerLinuxLauncher";
} }
let build_date = include_str!(concat!(env!("OUT_DIR"), "/build_date.txt")); let build_date = include_str!(concat!(env!("OUT_DIR"), "/build_date.txt"));
let startup_text = format!(" let startup_text = ASCII_ART.to_owned();
.d8888b. Y88b d88P 888b 888 88888888888 d8888 Y88b d88P
d88P Y88b Y88b d88P 8888b 888 888 d88888 Y88b d88P let bootstrapper_info = format!(
Y88b. Y88o88P 88888b 888 888 d88P888 Y88o88P "{} | Build Date: {} | Version: {}",
\"Y888b. Y888P 888Y88b 888 888 d88P 888 Y888P base_url,
\"Y88b. 888 888 Y88b888 888 d88P 888 d888b build_date,
\"888 888 888 Y88888 888 d88P 888 d88888b env!("CARGO_PKG_VERSION"),
Y88b d88P 888 888 Y8888 888 d8888888888 d88P Y88b );
\"Y8888P\" 888 888 Y888 888 d88P 888 d88P Y88b
{} | Build Date: {} | Version: {}", base_url ,build_date, env!("CARGO_PKG_VERSION"));
// Format the startup text to be centered // Format the startup text to be centered
let mut terminal_width = 80; let mut terminal_width = 80;
@ -161,51 +85,80 @@ async fn main() {
terminal_width = w; terminal_width = w;
} }
if terminal_width < 80 { if terminal_width < 80 {
print!("{}\n", format!("SYNTAX Bootstrapper | {} | Build Date: {} | Version: {}", base_url, build_date, env!("CARGO_PKG_VERSION")).to_string().magenta().cyan().italic().on_black()); // Fallback message print!(
"{}\n",
format!(
"SYNTAX Bootstrapper | {} | Build Date: {} | Version: {}",
base_url,
build_date,
env!("CARGO_PKG_VERSION")
)
.to_string()
.magenta()
.cyan()
.italic()
.on_black()
); // Fallback message
} else { } else {
let startup_text_lines = startup_text.lines().collect::<Vec<&str>>(); let startup_text_lines = startup_text.lines().collect::<Vec<&str>>();
//println!("{}", startup_text.bold().blue().on_black()); //println!("{}", startup_text.bold().blue().on_black());
// print all lines except the last one // print all lines except the last one
for line in &startup_text_lines[0..startup_text_lines.len() - 1] { for line in startup_text_lines {
let spaces = (terminal_width - line.len()) / 2; let spaces = (terminal_width - line.len()) / 2;
let formatted_line = format!("{}{}", " ".repeat(spaces), line); let formatted_line = format!("{}{}", " ".repeat(spaces), line);
println!("{}", formatted_line.bright_magenta().italic().on_black()); println!("{}", formatted_line.bright_magenta().italic().on_black());
} }
// print last line as a different color // print last line as a different color
let last_line = startup_text_lines[startup_text_lines.len() - 1]; println!(
let spaces = (terminal_width - last_line.len()) / 2; "{}\n",
let last_line = format!("{}{}", " ".repeat(spaces), last_line); bootstrapper_info.magenta().cyan().italic().on_black()
println!("{}\n", last_line.magenta().cyan().italic().on_black()); );
} }
let http_client: Client = reqwest::Client::builder() let http_client: Client = reqwest::Client::builder().no_gzip().build().unwrap();
.no_gzip() debug!(
.build() "Setup Server: {} | Base Server: {}",
.unwrap(); setup_url.bright_blue(),
debug(format!("Setup Server: {} | Base Server: {}", setup_url.bright_blue(), base_url.bright_blue()).as_str()); base_url.bright_blue()
debug("Fetching latest client version from setup server"); );
let latest_client_version : String; debug!("Fetching latest client version from setup server");
let latest_client_version_response = http_get(&http_client ,&format!("https://{}/version", setup_url)).await;
let latest_client_version: String;
let latest_client_version_response =
http_get(&http_client, &format!("https://{}/version", setup_url)).await;
match latest_client_version_response { match latest_client_version_response {
Ok(latest_client_version_result) => { Ok(latest_client_version_result) => {
debug(&format!("Latest Client Version: {}", latest_client_version_result.bright_blue())); debug!(
"Latest Client Version: {}",
latest_client_version_result.bright_blue()
);
latest_client_version = latest_client_version_result; latest_client_version = latest_client_version_result;
}, }
Err(e) => { Err(e) => {
error(&format!("Failed to fetch latest client version from setup server: [{}], attempting to fallback to {}", e.to_string().bright_red(), fallback_setup_url.bright_blue())); error!("Failed to fetch latest client version from setup server: [{}], attempting to fallback to {}", e.to_string().bright_red(), fallback_setup_url.bright_blue());
let fallback_client_version_response = http_get(&http_client ,&format!("https://{}/version", fallback_setup_url)).await; let fallback_client_version_response = http_get(
&http_client,
&format!("https://{}/version", fallback_setup_url),
)
.await;
match fallback_client_version_response { match fallback_client_version_response {
Ok(fallback_client_version_result) => { Ok(fallback_client_version_result) => {
info(&format!("Successfully fetched latest client version from fallback setup server: {}", fallback_setup_url.bright_blue())); info!(
debug(&format!("Latest Client Version: {}", fallback_client_version_result.bright_blue())); "Successfully fetched latest client version from fallback setup server: {}",
fallback_setup_url.bright_blue()
);
debug!(
"Latest Client Version: {}",
fallback_client_version_result.bright_blue()
);
latest_client_version = fallback_client_version_result; latest_client_version = fallback_client_version_result;
setup_url = fallback_setup_url; setup_url = fallback_setup_url;
}, }
Err(e) => { Err(e) => {
error(&format!("Failed to fetch latest client version from fallback setup server: {}, are you connected to the internet?", e)); error!("Failed to fetch latest client version from fallback setup server: {}, are you connected to the internet?", e);
std::thread::sleep(std::time::Duration::from_secs(10)); std::thread::sleep(std::time::Duration::from_secs(10));
std::process::exit(0); std::process::exit(0);
} }
@ -214,35 +167,58 @@ async fn main() {
} }
// Wait for the latest client version to be fetched // Wait for the latest client version to be fetched
info(&format!("Latest Client Version: {}", latest_client_version.cyan().underline())); info!(
debug(&format!("Setup Server: {}", setup_url.cyan().underline())); "Latest Client Version: {}",
latest_client_version.cyan().underline()
);
debug!("Setup Server: {}", setup_url.cyan().underline());
let installation_directory = get_installation_directory(); let installation_directory = get_installation_directory();
debug(&format!("Installation Directory: {}", installation_directory.to_str().unwrap().bright_blue())); debug!(
"Installation Directory: {}",
installation_directory.to_str().unwrap().bright_blue()
);
create_folder_if_not_exists(&installation_directory).await; create_folder_if_not_exists(&installation_directory).await;
let versions_directory = installation_directory.join("Versions"); let versions_directory = installation_directory.join("Versions");
debug(&format!("Versions Directory: {}", versions_directory.to_str().unwrap().bright_blue())); debug!(
"Versions Directory: {}",
versions_directory.to_str().unwrap().bright_blue()
);
create_folder_if_not_exists(&versions_directory).await; create_folder_if_not_exists(&versions_directory).await;
let temp_downloads_directory = installation_directory.join("Downloads"); let temp_downloads_directory = installation_directory.join("Downloads");
debug(&format!("Temp Downloads Directory: {}", temp_downloads_directory.to_str().unwrap().bright_blue())); debug!(
"Temp Downloads Directory: {}",
temp_downloads_directory.to_str().unwrap().bright_blue()
);
create_folder_if_not_exists(&temp_downloads_directory).await; create_folder_if_not_exists(&temp_downloads_directory).await;
let current_version_directory = versions_directory.join(format!("{}", latest_client_version)); let current_version_directory = versions_directory.join(format!("{}", latest_client_version));
debug(&format!("Current Version Directory: {}", current_version_directory.to_str().unwrap().bright_blue())); debug!(
"Current Version Directory: {}",
current_version_directory.to_str().unwrap().bright_blue()
);
create_folder_if_not_exists(&current_version_directory).await; create_folder_if_not_exists(&current_version_directory).await;
let latest_bootstrapper_path = current_version_directory.join(bootstrapper_filename); let latest_bootstrapper_path = current_version_directory.join(bootstrapper_filename);
// Is the program currently running from the latest version directory? // Is the program currently running from the latest version directory?
let current_exe_path = std::env::current_exe().unwrap(); let current_exe_path = std::env::current_exe().unwrap();
// If the current exe path is not in the current version directory, then we need to run the latest bootstrapper ( download if needed ) // If the current exe path is not in the current version directory, then we need to run the latest bootstrapper ( download if needed )
if !current_exe_path.starts_with(&current_version_directory) { if !current_exe_path.starts_with(&current_version_directory) && !DEBUG {
// Check if the latest bootstrapper is downloaded // Check if the latest bootstrapper is downloaded
if !latest_bootstrapper_path.exists() { if !latest_bootstrapper_path.exists() {
info("Downloading the latest bootstrapper and restarting"); info!("Downloading the latest bootstrapper and restarting");
// Download the latest bootstrapper // Download the latest bootstrapper
download_file(&http_client, &format!("https://{}/{}-{}", setup_url, latest_client_version, bootstrapper_filename), &latest_bootstrapper_path).await; download_to_file(
&http_client,
&format!(
"https://{}/{}-{}",
setup_url, latest_client_version, bootstrapper_filename
),
&latest_bootstrapper_path,
)
.await;
} }
// Run the latest bootstrapper ( with the same arguments passed to us ) and exit // Run the latest bootstrapper ( with the same arguments passed to us ) and exit
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -250,12 +226,20 @@ async fn main() {
let mut command = std::process::Command::new(latest_bootstrapper_path.clone()); let mut command = std::process::Command::new(latest_bootstrapper_path.clone());
command.args(&args[1..]); command.args(&args[1..]);
match command.spawn() { match command.spawn() {
Ok(_) => {}, Ok(_) => {}
Err(e) => { Err(e) => {
debug(&format!("Bootstrapper errored with error {}", e)); debug!(&format!("Bootstrapper errored with error {}", e));
info("Found bootstrapper was corrupted! Downloading..."); info("Found bootstrapper was corrupted! Downloading...");
std::fs::remove_file(latest_bootstrapper_path.clone()).unwrap(); std::fs::remove_file(latest_bootstrapper_path.clone()).unwrap();
download_file(&http_client, &format!("https://{}/{}-{}", setup_url, latest_client_version, bootstrapper_filename), &latest_bootstrapper_path).await; download_file(
&http_client,
&format!(
"https://{}/{}-{}",
setup_url, latest_client_version, bootstrapper_filename
),
&latest_bootstrapper_path,
)
.await;
command.spawn().expect("Bootstrapper is still corrupted."); command.spawn().expect("Bootstrapper is still corrupted.");
std::thread::sleep(std::time::Duration::from_secs(20)); std::thread::sleep(std::time::Duration::from_secs(20));
} }
@ -264,9 +248,13 @@ async fn main() {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
// Make sure the latest bootstrapper is executable // Make sure the latest bootstrapper is executable
std::process::Command::new("chmod").arg("+x").arg(latest_bootstrapper_path.to_str().unwrap()).spawn().unwrap(); std::process::Command::new("chmod")
.arg("+x")
.arg(latest_bootstrapper_path.to_str().unwrap())
.spawn()
.unwrap();
info("We need permission to run the latest bootstrapper"); info!("We need permission to run the latest bootstrapper");
let mut command = std::process::Command::new(latest_bootstrapper_path); let mut command = std::process::Command::new(latest_bootstrapper_path);
command.args(&args[1..]); command.args(&args[1..]);
command.spawn().unwrap(); command.spawn().unwrap();
@ -275,13 +263,13 @@ async fn main() {
} }
// Looks like we are running from the latest version directory, so we can continue with the update process // Looks like we are running from the latest version directory, so we can continue with the update process
// Check for "AppSettings.xml" in the current version directory // Check for "AppSettings.xml" in the current version directory
// If it doesent exist, then we got either a fresh directory or a corrupted installation // If it doesent exist, then we got either a fresh directory or a corrupted installation
// So delete the every file in the current version directory except for the Bootstrapper itself // So delete the every file in the current version directory except for the Bootstrapper itself
let app_settings_path = current_version_directory.join("AppSettings.xml"); let app_settings_path = current_version_directory.join("AppSettings.xml");
let client_executable_path = current_version_directory.join("SyntaxPlayerBeta.exe"); let client_executable_path = current_version_directory.join("SyntaxPlayerBeta.exe");
if !app_settings_path.exists() || !client_executable_path.exists() { if !app_settings_path.exists() || !client_executable_path.exists() {
info("Downloading the latest client files, this may take a while."); info!("Downloading the latest client files, this may take a while.");
for entry in std::fs::read_dir(&current_version_directory).unwrap() { for entry in std::fs::read_dir(&current_version_directory).unwrap() {
let entry = entry.unwrap(); let entry = entry.unwrap();
let path = entry.path(); let path = entry.path();
@ -294,117 +282,70 @@ async fn main() {
} }
} }
let VersionURLPrefix = format!("https://{}/{}-", setup_url, latest_client_version); let version_url_prefix = format!("https://{}/{}-", setup_url, latest_client_version);
let SyntaxAppZip : PathBuf = download_file_prefix(&http_client, format!("{}SyntaxApp.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let NPSyntaxProxyZip : PathBuf = download_file_prefix(&http_client, format!("{}NPSyntaxProxy.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let SyntaxProxyZip : PathBuf = download_file_prefix(&http_client, format!("{}SyntaxProxy.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let LibrariesZip : PathBuf = download_file_prefix(&http_client, format!("{}Libraries.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let RedistZip : PathBuf = download_file_prefix(&http_client, format!("{}redist.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentTexturesZip : PathBuf = download_file_prefix(&http_client, format!("{}content-textures.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; /* Use a joisnet to run multiple async functions at once */
let ContentTextures2Zip : PathBuf = download_file_prefix(&http_client, format!("{}content-textures2.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentTextures3Zip : PathBuf = download_file_prefix(&http_client, format!("{}content-textures3.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentTerrainZip : PathBuf = download_file_prefix(&http_client, format!("{}content-terrain.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentFontsZip : PathBuf = download_file_prefix(&http_client, format!("{}content-fonts.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentSoundsZip : PathBuf = download_file_prefix(&http_client, format!("{}content-sounds.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentScriptsZip : PathBuf = download_file_prefix(&http_client, format!("{}content-scripts.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentSkyZip : PathBuf = download_file_prefix(&http_client, format!("{}content-sky.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentMusicZip : PathBuf = download_file_prefix(&http_client, format!("{}content-music.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ContentParticles : PathBuf = download_file_prefix(&http_client, format!("{}content-particles.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let ShadersZip : PathBuf = download_file_prefix(&http_client, format!("{}shaders.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; let mut set = JoinSet::new();
let Client2018Zip : PathBuf = download_file_prefix(&http_client, format!("{}2018client.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; for [value, _] in FILES_TO_DOWNLOAD {
let Client2020Zip : PathBuf = download_file_prefix(&http_client, format!("{}2020client.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; set.spawn(download_and_extract(
info("Download finished, extracting files."); value.to_string(),
version_url_prefix.clone(),
fn extract_to_dir( zip_file : &PathBuf, target_dir : &PathBuf ) { current_version_directory.clone(),
let zip_file_cursor = std::fs::File::open(zip_file).unwrap(); ));
zip_extract::extract(zip_file_cursor, target_dir, false).unwrap();
} }
extract_to_dir(&SyntaxAppZip, &current_version_directory);
extract_to_dir(&NPSyntaxProxyZip, &current_version_directory);
extract_to_dir(&SyntaxProxyZip, &current_version_directory);
extract_to_dir(&LibrariesZip, &current_version_directory);
extract_to_dir(&RedistZip, &current_version_directory);
let content_directory = current_version_directory.join("content"); while let Some(value) = set.join_next().await {
let platform_content_directory = current_version_directory.join("PlatformContent"); value.unwrap()
let shaders_directory = current_version_directory.join("shaders"); }
create_folder_if_not_exists(&content_directory).await; info!("Binary installed");
create_folder_if_not_exists(&platform_content_directory).await;
create_folder_if_not_exists(&shaders_directory).await;
let fonts_directory = content_directory.join("fonts"); /* Convert to async due to this being a slow function */
let music_directory = content_directory.join("music");
let particles_directory = content_directory.join("particles");
let sky_directory = content_directory.join("sky");
let sounds_directory = content_directory.join("sounds");
let textures_directory = content_directory.join("textures");
let scripts_directory = content_directory.join("scripts");
create_folder_if_not_exists(&fonts_directory).await; // Redacted for lagging vscode
create_folder_if_not_exists(&music_directory).await;
create_folder_if_not_exists(&particles_directory).await;
create_folder_if_not_exists(&sky_directory).await;
create_folder_if_not_exists(&sounds_directory).await;
create_folder_if_not_exists(&textures_directory).await;
extract_to_dir(&ContentTexturesZip, &textures_directory);
extract_to_dir(&ContentTextures2Zip, &textures_directory);
extract_to_dir(&ContentFontsZip, &fonts_directory);
extract_to_dir(&ContentSoundsZip, &sounds_directory);
extract_to_dir(&ContentSkyZip, &sky_directory);
extract_to_dir(&ContentMusicZip, &music_directory);
extract_to_dir(&ContentParticles, &particles_directory);
extract_to_dir(&ContentScriptsZip, &scripts_directory);
let platform_pc_directory = platform_content_directory.join("pc");
create_folder_if_not_exists(&platform_pc_directory).await;
let terrain_directory = platform_pc_directory.join("terrain");
let textures_directory = platform_pc_directory.join("textures");
create_folder_if_not_exists(&terrain_directory).await;
create_folder_if_not_exists(&textures_directory).await;
extract_to_dir(&ContentTerrainZip, &terrain_directory);
extract_to_dir(&ContentTextures3Zip, &textures_directory);
extract_to_dir(&ShadersZip, &shaders_directory);
let client_2018_directory = current_version_directory.join("Client2018");
create_folder_if_not_exists(&client_2018_directory).await;
extract_to_dir(&Client2018Zip, &client_2018_directory);
let client_2020_directory = current_version_directory.join("Client2020");
create_folder_if_not_exists(&client_2020_directory).await;
extract_to_dir(&Client2020Zip, &client_2020_directory);
info("Finished extracting files, cleaning up.");
std::fs::remove_dir_all(&temp_downloads_directory).unwrap();
// Install the syntax-player scheme in the registry // Install the syntax-player scheme in the registry
info("Installing syntax-player scheme"); info!("Installing syntax-player scheme");
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
let hkey_current_user = RegKey::predef(HKEY_CURRENT_USER); let hkey_current_user = RegKey::predef(HKEY_CURRENT_USER);
let hkey_classes_root : RegKey = hkey_current_user.open_subkey("Software\\Classes").unwrap(); let hkey_classes_root: RegKey =
hkey_current_user.open_subkey("Software\\Classes").unwrap();
let hkey_syntax_player = hkey_classes_root.create_subkey("syntax-player").unwrap().0; let hkey_syntax_player = hkey_classes_root.create_subkey("syntax-player").unwrap().0;
let hkey_syntax_player_shell = hkey_syntax_player.create_subkey("shell").unwrap().0; let hkey_syntax_player_shell = hkey_syntax_player.create_subkey("shell").unwrap().0;
let hkey_syntax_player_shell_open = hkey_syntax_player_shell.create_subkey("open").unwrap().0; let hkey_syntax_player_shell_open =
let hkey_syntax_player_shell_open_command = hkey_syntax_player_shell_open.create_subkey("command").unwrap().0; hkey_syntax_player_shell.create_subkey("open").unwrap().0;
let hkey_syntax_player_shell_open_command = hkey_syntax_player_shell_open
.create_subkey("command")
.unwrap()
.0;
let defaulticon = hkey_syntax_player.create_subkey("DefaultIcon").unwrap().0; let defaulticon = hkey_syntax_player.create_subkey("DefaultIcon").unwrap().0;
hkey_syntax_player_shell_open_command.set_value("", &format!("\"{}\" \"%1\"", current_exe_path.to_str().unwrap())).unwrap(); hkey_syntax_player_shell_open_command
defaulticon.set_value("", &format!("\"{}\",0", current_exe_path.to_str().unwrap())).unwrap(); .set_value(
hkey_syntax_player.set_value("", &format!("URL: Syntax Protocol")).unwrap(); "",
&format!("\"{}\" \"%1\"", current_exe_path.to_str().unwrap()),
)
.unwrap();
defaulticon
.set_value("", &format!("\"{}\",0", current_exe_path.to_str().unwrap()))
.unwrap();
hkey_syntax_player
.set_value("", &format!("URL: Syntax Protocol"))
.unwrap();
hkey_syntax_player.set_value("URL Protocol", &"").unwrap(); hkey_syntax_player.set_value("URL Protocol", &"").unwrap();
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
// Linux support // Linux support
// We have to write a .desktop file to ~/.local/share/applications // We have to write a .desktop file to ~/.local/share/applications
let desktop_file_path = dirs::data_local_dir().unwrap().join("applications").join("syntax-player.desktop"); let desktop_file_path = dirs::data_local_dir()
.unwrap()
.join("applications")
.join("syntax-player.desktop");
let desktop_file = format!( let desktop_file = format!(
"[Desktop Entry] "[Desktop Entry]
Name=Syntax Launcher Name=Syntax Launcher
Exec={} %u Exec={} %u
Terminal=true Terminal=true
@ -414,31 +355,37 @@ Icon={}
StartupWMClass=SyntaxLauncher StartupWMClass=SyntaxLauncher
Categories=Game; Categories=Game;
Comment=Syntax Launcher Comment=Syntax Launcher
", current_exe_path.to_str().unwrap(), current_exe_path.to_str().unwrap()); ",
current_exe_path.to_str().unwrap(),
current_exe_path.to_str().unwrap()
);
std::fs::write(desktop_file_path, desktop_file).unwrap(); std::fs::write(desktop_file_path, desktop_file).unwrap();
// We also have to write a mimeapps.list file to ~/.config // We also have to write a mimeapps.list file to ~/.config
let mimeapps_list_path = dirs::config_dir().unwrap().join("mimeapps.list"); let mimeapps_list_path = dirs::config_dir().unwrap().join("mimeapps.list");
let mimeapps_list = format!( let mimeapps_list = format!(
"[Default Applications] "[Default Applications]
x-scheme-handler/syntax-player=syntax-player.desktop x-scheme-handler/syntax-player=syntax-player.desktop
"); "
);
std::fs::write(mimeapps_list_path, mimeapps_list).unwrap(); std::fs::write(mimeapps_list_path, mimeapps_list).unwrap();
// We also have to write a mimeapps.list file to ~/.local/share // We also have to write a mimeapps.list file to ~/.local/share
let mimeapps_list_path = dirs::data_local_dir().unwrap().join("mimeapps.list"); let mimeapps_list_path = dirs::data_local_dir().unwrap().join("mimeapps.list");
let mimeapps_list = format!( let mimeapps_list = format!(
"[Default Applications] "[Default Applications]
x-scheme-handler/syntax-player=syntax-player.desktop x-scheme-handler/syntax-player=syntax-player.desktop
"); "
);
std::fs::write(mimeapps_list_path, mimeapps_list).unwrap(); std::fs::write(mimeapps_list_path, mimeapps_list).unwrap();
} }
// Write the AppSettings.xml file // Write the AppSettings.xml file
let app_settings_xml = format!( let app_settings_xml = format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<Settings> <Settings>
<ContentFolder>content</ContentFolder> <ContentFolder>content</ContentFolder>
<BaseUrl>https://{}</BaseUrl> <BaseUrl>https://{}</BaseUrl>
</Settings>", base_url </Settings>",
base_url
); );
std::fs::write(app_settings_path, app_settings_xml).unwrap(); std::fs::write(app_settings_path, app_settings_xml).unwrap();
@ -456,17 +403,25 @@ x-scheme-handler/syntax-player=syntax-player.desktop
// Parse the arguments passed to the bootstrapper // Parse the arguments passed to the bootstrapper
// Looks something like "syntax-player://1+launchmode:play+gameinfo:TICKET+placelauncherurl:https://www.syntax.eco/Game/placelauncher.ashx?placeId=660&t=TICKET+k:l" // Looks something like "syntax-player://1+launchmode:play+gameinfo:TICKET+placelauncherurl:https://www.syntax.eco/Game/placelauncher.ashx?placeId=660&t=TICKET+k:l"
debug(&format!("Arguments Passed: {}", args.join(" ").bright_blue())); debug!("Arguments Passed: {}", args.join(" ").bright_blue());
if args.len() == 1 { if args.len() == 1 {
// Just open the website // Just open the website
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
std::process::Command::new("cmd").arg("/c").arg("start").arg("https://www.syntax.eco/games").spawn().unwrap(); std::process::Command::new("cmd")
.arg("/c")
.arg("start")
.arg("https://www.syntax.eco/games")
.spawn()
.unwrap();
std::process::exit(0); std::process::exit(0);
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
std::process::Command::new("xdg-open").arg("https://www.syntax.eco/games").spawn().unwrap(); std::process::Command::new("xdg-open")
.arg("https://www.syntax.eco/games")
.spawn()
.unwrap();
std::process::exit(0); std::process::exit(0);
} }
} }
@ -479,30 +434,29 @@ x-scheme-handler/syntax-player=syntax-player.desktop
let mut authentication_ticket = String::new(); let mut authentication_ticket = String::new();
let mut join_script = String::new(); let mut join_script = String::new();
let mut client_year = String::new(); let mut client_year = String::new();
for arg in main_args { for arg in main_args {
let mut arg_split = arg.split(":"); let mut arg_split = arg.split(":");
let key = arg_split.next().unwrap(); let key = arg_split.next().unwrap();
let value = let value = if arg_split.clone().count() > 0 {
if arg_split.clone().count() > 0 { arg_split.collect::<Vec<&str>>().join(":")
arg_split.collect::<Vec<&str>>().join(":") } else {
} else { String::new()
String::new() };
}; debug!("{}: {}", key.bright_blue(), value.bright_blue());
debug(&format!("{}: {}", key.bright_blue(), value.bright_blue()));
match key { match key {
"launchmode" => { "launchmode" => {
launch_mode = value.to_string(); launch_mode = value.to_string();
}, }
"gameinfo" => { "gameinfo" => {
authentication_ticket = value.to_string(); authentication_ticket = value.to_string();
}, }
"placelauncherurl" => { "placelauncherurl" => {
join_script = value.to_string(); join_script = value.to_string();
}, }
"clientyear" => { "clientyear" => {
client_year = value.to_string(); client_year = value.to_string();
}, }
_ => {} _ => {}
} }
} }
@ -514,18 +468,22 @@ x-scheme-handler/syntax-player=syntax-player.desktop
let wine_path_file = installation_directory.join("winepath.txt"); let wine_path_file = installation_directory.join("winepath.txt");
if wine_path_file.exists() { if wine_path_file.exists() {
let custom_wine = std::fs::read_to_string(wine_path_file).unwrap(); let custom_wine = std::fs::read_to_string(wine_path_file).unwrap();
info(&format!("Using custom wine binary: {}", custom_wine.bright_blue())); info!("Using custom wine binary: {}", custom_wine.bright_blue());
} else { } else {
info("No custom wine binary specified, using default wine command"); info!("No custom wine binary specified, using default wine command");
info(format!("If you want to use a custom wine binary, please create a file at {} with the path to the wine binary", wine_path_file.to_str().unwrap()).as_str()); info!("If you want to use a custom wine binary, please create a file at {} with the path to the wine binary", wine_path_file.to_str().unwrap());
} }
} }
let client_executable_path : PathBuf; let client_executable_path: PathBuf;
debug(&client_year.to_string()); debug!("{}", &client_year.to_string());
if client_year == "2018" { if client_year == "2018" {
client_executable_path = current_version_directory.join("Client2018").join("SyntaxPlayerBeta.exe"); client_executable_path = current_version_directory
.join("Client2018")
.join("SyntaxPlayerBeta.exe");
} else if client_year == "2020" { } else if client_year == "2020" {
client_executable_path = current_version_directory.join("Client2020").join("SyntaxPlayerBeta.exe"); client_executable_path = current_version_directory
.join("Client2020")
.join("SyntaxPlayerBeta.exe");
} else { } else {
client_executable_path = current_version_directory.join("SyntaxPlayerBeta.exe"); client_executable_path = current_version_directory.join("SyntaxPlayerBeta.exe");
} }
@ -534,17 +492,25 @@ x-scheme-handler/syntax-player=syntax-player.desktop
let app_settings_path = current_version_directory.join("AppSettings.xml"); let app_settings_path = current_version_directory.join("AppSettings.xml");
std::fs::remove_file(app_settings_path).unwrap(); std::fs::remove_file(app_settings_path).unwrap();
error("Failed to run SyntaxPlayerBeta.exe, is your antivirus removing it? The bootstrapper will attempt to redownload the client on next launch."); error!("Failed to run SyntaxPlayerBeta.exe, is your antivirus removing it? The bootstrapper will attempt to redownload the client on next launch.");
std::thread::sleep(std::time::Duration::from_secs(20)); std::thread::sleep(std::time::Duration::from_secs(20));
std::process::exit(0); std::process::exit(0);
} }
match launch_mode.as_str() { match launch_mode.as_str() {
"play" => { "play" => {
info("Launching SYNTAX"); info!("Launching SYNTAX");
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
let mut command = std::process::Command::new(client_executable_path); let mut command = std::process::Command::new(client_executable_path);
command.args(&["--play","--authenticationUrl", format!("https://{}/Login/Negotiate.ashx", base_url).as_str(), "--authenticationTicket", authentication_ticket.as_str(), "--joinScriptUrl", format!("{}",join_script.as_str()).as_str()]); command.args(&[
"--play",
"--authenticationUrl",
format!("https://{}/Login/Negotiate.ashx", base_url).as_str(),
"--authenticationTicket",
authentication_ticket.as_str(),
"--joinScriptUrl",
format!("{}", join_script.as_str()).as_str(),
]);
command.spawn().unwrap(); command.spawn().unwrap();
std::thread::sleep(std::time::Duration::from_secs(5)); std::thread::sleep(std::time::Duration::from_secs(5));
std::process::exit(0); std::process::exit(0);
@ -553,18 +519,27 @@ x-scheme-handler/syntax-player=syntax-player.desktop
{ {
// We have to launch the game through wine // We have to launch the game through wine
let mut command = std::process::Command::new(custom_wine); let mut command = std::process::Command::new(custom_wine);
command.args(&[client_executable_path.to_str().unwrap(), "--play","--authenticationUrl", format!("https://{}/Login/Negotiate.ashx", base_url).as_str(), "--authenticationTicket", authentication_ticket.as_str(), "--joinScriptUrl", format!("{}",join_script.as_str()).as_str()]); command.args(&[
client_executable_path.to_str().unwrap(),
"--play",
"--authenticationUrl",
format!("https://{}/Login/Negotiate.ashx", base_url).as_str(),
"--authenticationTicket",
authentication_ticket.as_str(),
"--joinScriptUrl",
format!("{}", join_script.as_str()).as_str(),
]);
// We must wait for the game to exit before exiting the bootstrapper // We must wait for the game to exit before exiting the bootstrapper
let mut child = command.spawn().unwrap(); let mut child = command.spawn().unwrap();
child.wait().unwrap(); child.wait().unwrap();
std::thread::sleep(std::time::Duration::from_secs(1)); std::thread::sleep(std::time::Duration::from_secs(1));
std::process::exit(0); std::process::exit(0);
} }
}, }
_ => { _ => {
error("Unknown launch mode, exiting."); error!("Unknown launch mode, exiting.");
std::thread::sleep(std::time::Duration::from_secs(10)); std::thread::sleep(std::time::Duration::from_secs(10));
std::process::exit(0); std::process::exit(0);
} }
} }
} }

159
src/util/mod.rs Normal file
View File

@ -0,0 +1,159 @@
/*
Move all generic functions into this area
*/
use colored::*;
use dirs::data_local_dir;
use futures_util::StreamExt;
use md5;
use reqwest::Client;
use reqwest::ClientBuilder;
use std::io::Cursor;
use std::path::PathBuf;
use tokio::fs;
use tokio::fs::create_dir_all;
use zip_extract;
use crate::constants::*;
use tracing::*;
#[cfg(not(target_os = "windows"))]
use std::io::prelude::*;
/*
#[cfg(not(target_os = "windows"))]
use std::os::unix::fs::FileExt;
#[cfg(target_os = "windows")]
use std::os::windows::prelude::FileExt;
*/
#[cfg(target_os = "windows")]
use winreg::enums::*;
#[cfg(target_os = "windows")]
use winreg::RegKey;
pub async fn http_get(client: &Client, url: &str) -> Result<String, reqwest::Error> {
debug!("{} {}", "GET".green(), url.bright_blue());
let response = client.get(url).send().await;
if response.is_err() {
debug!("Failed to fetch {}", url.bright_blue());
return Err(response.err().unwrap());
}
let response_body = response.unwrap().text().await.unwrap();
Ok(response_body)
}
pub async fn download_file(client: &Client, url: &str) -> Vec<u8> {
debug!("{} {}", "GET".green(), url.bright_blue());
let response = client.get(url).send().await.unwrap();
/* Why through over a visual bug? */
let content_length = response.content_length().or(Some(0)).unwrap();
debug!("Content Length: {}", content_length);
let time = chrono::Local::now().format("%H:%M:%S").to_string();
let pg_bar_str =
" {spinner:.green} [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})";
let progress_bar = indicatif::ProgressBar::new(content_length);
let progress_style = indicatif::ProgressStyle::default_bar()
.template(
format!(
"{}\n{}",
format!(
"[{}] [{}] {}",
time.bold().blue(),
"INFO".bold().green(),
&format!("Downloading {}", &url.bright_blue())
),
pg_bar_str
)
.as_str(),
)
.unwrap()
.progress_chars("#>-");
progress_bar.set_style(progress_style);
progress_bar.set_message("Downloading File");
let mut buffer: Vec<u8> = vec![];
let mut downloaded: u64 = 0;
let mut stream = response.bytes_stream();
while let Some(item) = stream.next().await {
let chunk = item
.or(Err(format!("Error while downloading file")))
.unwrap();
buffer.write_all(chunk.as_ref()).unwrap();
let new = std::cmp::min(downloaded + (chunk.len() as u64), content_length);
downloaded = new;
progress_bar.set_position(new);
}
progress_bar.finish();
info!("Finished downloading {}", url.green());
return buffer;
}
pub async fn download_to_file(client: &Client, url: &str, path: &PathBuf) {
let bytes = download_file(client, url).await;
fs::write(path, bytes).await.unwrap();
}
pub async fn download_file_prefix<T: Into<String>>(client: &Client, url: T) -> Vec<u8> {
let string: String = url.into();
let buffer = download_file(client, &string).await;
return buffer;
}
/*
pub async fn generate_md5(input: &str) -> String {
let hashed_input = md5::compute(input.as_bytes());
return format!("{:x}", hashed_input);
}*/
pub async fn create_folder_if_not_exists(path: &PathBuf) {
if !path.exists() {
info!("Creating folder {}", path.to_str().unwrap().bright_blue());
fs::create_dir_all(path).await.unwrap();
}
}
pub fn get_installation_directory() -> PathBuf {
return PathBuf::from(data_local_dir().unwrap().to_str().unwrap()).join("Syntax");
}
/* Why was this in the main function i will never know */
pub async fn extract_to_dir(zip_file: &Vec<u8>, target_dir: &PathBuf) {
let zip_file_cursor = Cursor::new(zip_file);
zip_extract::extract(zip_file_cursor, target_dir, false).unwrap();
}
fn get_location_from_file_name<T: AsRef<str>>(file_name: T) -> String {
let file_name = file_name.as_ref();
for [first, last] in FILES_TO_DOWNLOAD {
if first == file_name {
return last.to_owned();
}
}
let formated = format!("Is not a valid file {}", file_name);
error!("{}", formated);
panic!("{}", formated)
}
pub async fn download_and_extract<T: Into<String>, T2: Into<String>, P: Into<PathBuf>>(
file_name: T,
url_prefix: T2,
extract_location: P,
) {
let http_client = ClientBuilder::default().build().unwrap();
let file_name: String = file_name.into();
let url_prefix: String = url_prefix.into();
let extract_location: PathBuf = extract_location.into();
let buffer = download_file_prefix(&http_client, format!("{}{file_name}", url_prefix)).await;
drop(http_client);
drop(url_prefix);
let dir = extract_location.join(get_location_from_file_name(&file_name));
create_dir_all(&dir).await.unwrap();
info!("Extracting file {}", file_name);
extract_to_dir(&buffer, &dir).await;
drop(buffer);
info!("File {} installed to {:?}", file_name, dir.display());
}