// Gupax - GUI Uniting P2Pool And XMRig // // Copyright (c) 2022 hinto-janaiyo // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. //// You should have received a copy of the GNU General Public License // along with this program. If not, see . // This file contains all (most) of the code for updating. // The main [Update] struct contains meta update information. // It is held by the top [App] struct. Each package also gets // a [Pkg] struct that only lasts as long as the download. // // An update is triggered by either: // a. user clicks update on [Gupax] tab // b. auto-update at startup //---------------------------------------------------------------------------------------------------- Imports use serde_derive::{Serialize,Deserialize}; use tokio::task::JoinHandle; use std::time::Duration; use std::sync::{Arc,Mutex}; use std::io::{Read,Write}; //use crate::{Name::*,State}; use rand::{thread_rng, Rng}; use rand::distributions::Alphanumeric; use anyhow::{anyhow,Error}; use arti_hyper::*; use arti_client::{TorClient,TorClientConfig}; use tokio::io::{AsyncReadExt,AsyncWriteExt}; use tls_api::{TlsConnector, TlsConnectorBuilder}; use hyper::header::HeaderValue; use hyper::{Client,Body,Request}; use hyper_tls::HttpsConnector; use arti_hyper::*; use log::*; use crate::update::Name::*; use std::path::PathBuf; use crate::state::*; use crate::constants::GUPAX_VERSION; use walkdir::WalkDir; // use tls_api_native_tls::{TlsConnector,TlsConnectorBuilder}; //---------------------------------------------------------------------------------------------------- Constants // Package naming schemes: // gupax | gupax-vX.X.X-(windows|macos|linux)-x64.(zip|tar.gz) // p2pool | p2pool-vX.X.X-(windows|macos|linux)-x64.(zip|tar.gz) // xmrig | xmrig-X.X.X-(msvc-win64|macos-x64|linux-static-x64).(zip|tar.gz) // // Download link = PREFIX + Version (found at runtime) + SUFFIX + Version + EXT // Example: https://github.com/hinto-janaiyo/gupax/releases/download/v0.0.1/gupax-v0.0.1-linux-standalone-x64 // // Exceptions (there are always exceptions...): // - XMRig doesn't have a [v], so it is [xmrig-6.18.0-...] // - XMRig separates the hash and signature // - P2Pool hashes are in UPPERCASE // - Gupax will be downloaded as a standalone binary (no decompression/extraction needed) const GUPAX_METADATA: &'static str = "https://api.github.com/repos/hinto-janaiyo/gupax/releases/latest"; const P2POOL_METADATA: &'static str = "https://api.github.com/repos/SChernykh/p2pool/releases/latest"; const XMRIG_METADATA: &'static str = "https://api.github.com/repos/xmrig/xmrig/releases/latest"; const GUPAX_PREFIX: &'static str = "https://github.com/hinto-janaiyo/gupax/releases/download/"; const P2POOL_PREFIX: &'static str = "https://github.com/SChernykh/p2pool/releases/download/"; const XMRIG_PREFIX: &'static str = "https://github.com/xmrig/xmrig/releases/download/"; const GUPAX_SUFFIX: &'static str = "/gupax-"; const P2POOL_SUFFIX: &'static str = "/p2pool-"; const XMRIG_SUFFIX: &'static str = "/xmrig-"; const GUPAX_HASH: &'static str = "SHA256SUMS"; const P2POOL_HASH: &'static str = "sha256sums.txt.asc"; const XMRIG_HASH: &'static str = "SHA256SUMS"; #[cfg(target_os = "windows")] const GUPAX_EXTENSION: &'static str = "-windows-standalone-x64.exe"; #[cfg(target_os = "windows")] const P2POOL_EXTENSION: &'static str = "-windows-x64.zip"; #[cfg(target_os = "windows")] const XMRIG_EXTENSION: &'static str = "-msvc-win64.zip"; #[cfg(target_os = "macos")] const GUPAX_EXTENSION: &'static str = "-macos-standalone-x64"; #[cfg(target_os = "macos")] const P2POOL_EXTENSION: &'static str = "-macos-x64.tar.gz"; #[cfg(target_os = "macos")] const XMRIG_EXTENSION: &'static str = "-macos-x64.tar.gz"; #[cfg(target_os = "linux")] const GUPAX_EXTENSION: &'static str = "-linux-standalone-x64"; #[cfg(target_os = "linux")] const P2POOL_EXTENSION: &'static str = "-linux-x64.tar.gz"; #[cfg(target_os = "linux")] const XMRIG_EXTENSION: &'static str = "-linux-static-x64.tar.gz"; // Some fake Curl/Wget user-agents because GitHub API requires one and a Tor browser // user-agent might be fingerprintable without all the associated headers. const FAKE_USER_AGENT: [&'static str; 50] = [ "Wget/1.16.3", "Wget/1.17", "Wget/1.17.1", "Wget/1.18", "Wget/1.18", "Wget/1.19", "Wget/1.19.1", "Wget/1.19.2", "Wget/1.19.3", "Wget/1.19.4", "Wget/1.19.5", "Wget/1.20", "Wget/1.20.1", "Wget/1.20.2", "Wget/1.20.3", "Wget/1.21", "Wget/1.21.1", "Wget/1.21.2", "Wget/1.21.3", "curl/7.64.1", "curl/7.65.0", "curl/7.65.1", "curl/7.65.2", "curl/7.65.3", "curl/7.66.0", "curl/7.67.0", "curl/7.68.0", "curl/7.69.0", "curl/7.69.1", "curl/7.70.0", "curl/7.70.1", "curl/7.71.0", "curl/7.71.1", "curl/7.72.0", "curl/7.73.0", "curl/7.74.0", "curl/7.75.0", "curl/7.76.0", "curl/7.76.1", "curl/7.77.0", "curl/7.78.0", "curl/7.79.0", "curl/7.79.1", "curl/7.80.0", "curl/7.81.0", "curl/7.82.0", "curl/7.83.0", "curl/7.83.1", "curl/7.84.0", "curl/7.85.0", ]; const MSG_NONE: &'static str = "No update in progress"; const MSG_START: &'static str = "Starting update"; const MSG_TMP: &'static str = "Creating temporary directory"; const MSG_TOR: &'static str = "Creating Tor+HTTPS client"; const MSG_HTTPS: &'static str = "Creating HTTPS client"; const MSG_METADATA: &'static str = "Fetching package metadata"; const MSG_METADATA_RETRY: &'static str = "Fetching package metadata failed, attempt"; const MSG_COMPARE: &'static str = "Compare package versions"; const MSG_UP_TO_DATE: &'static str = "All packages already up-to-date"; const MSG_DOWNLOAD: &'static str = "Downloading packages"; const MSG_DOWNLOAD_RETRY: &'static str = "Downloading packages failed, attempt"; const MSG_EXTRACT: &'static str = "Extracting packages"; const MSG_UPGRADE: &'static str = "Upgrading packages"; pub const MSG_SUCCESS: &'static str = "Update successful"; pub const MSG_FAILED: &'static str = "Update failed"; const INIT: &'static str = "------------------- Init -------------------"; const METADATA: &'static str = "----------------- Metadata -----------------"; const COMPARE: &'static str = "----------------- Compare ------------------"; const DOWNLOAD: &'static str = "----------------- Download -----------------"; const EXTRACT: &'static str = "----------------- Extract ------------------"; const UPGRADE: &'static str = "----------------- Upgrade ------------------"; // These two are sequential and not async so no need for a constant message. // The package in question will be known at runtime, so that will be printed. //const MSG_EXTRACT: &'static str = "Extracting packages"; //const MSG_UPGRADE: &'static str = "Upgrading packages"; //---------------------------------------------------------------------------------------------------- Update struct/impl // Contains values needed during update // Progress bar structure: // 0% | Start // 5% | Create tmp directory, pkg list, fake user-agent // 5% | Create Tor/HTTPS client // 30% | Download Metadata (x3) // 5% | Compare Versions (x3) // 30% | Download Archive (x3) // 5% | Extract (x3) // 5% | Upgrade (x3) #[derive(Clone)] pub struct Update { pub path_gupax: String, // Full path to current gupax pub path_p2pool: String, // Full path to current p2pool pub path_xmrig: String, // Full path to current xmrig pub tmp_dir: String, // Full path to temporary directory pub updating: Arc>, // Is an update in progress? pub prog: Arc>, // Holds the 0-100% progress bar number pub msg: Arc>, // Message to display on [Gupax] tab while updating pub tor: bool, // Is Tor enabled or not? } impl Update { // Takes in current paths from [State] pub fn new(path_p2pool: PathBuf, path_xmrig: PathBuf, tor: bool) -> Self { Self { path_gupax: crate::get_exe().unwrap(), path_p2pool: path_p2pool.display().to_string(), path_xmrig: path_xmrig.display().to_string(), tmp_dir: "".to_string(), updating: Arc::new(Mutex::new(false)), prog: Arc::new(Mutex::new(0.0)), msg: Arc::new(Mutex::new(MSG_NONE.to_string())), tor, } } // Get a temporary random folder for package download contents // This used to use [std::env::temp_dir()] but there were issues // using [std::fs::rename()] on tmpfs -> disk (Invalid cross-device link (os error 18)). // So, uses the [Gupax] binary directory as a base, something like [/home/hinto/gupax/gupax_SG4xsDdVmr] pub fn get_tmp_dir() -> Result { let rand_string: String = thread_rng() .sample_iter(&Alphanumeric) .take(10) .map(char::from) .collect(); let base = crate::get_exe_dir()?; #[cfg(target_os = "windows")] let tmp_dir = format!("{}{}{}{}", base, r"\gupax_", rand_string, r"\"); #[cfg(target_family = "unix")] let tmp_dir = format!("{}{}{}{}", base, "/gupax_", rand_string, "/"); info!("Update | Temporary directory ... {}", tmp_dir); Ok(tmp_dir) } // Get a HTTPS client. Uses [Arti] if Tor is enabled. // The base type looks something like [hyper::Client<...>]. // This is then wrapped with the custom [ClientEnum] type to implement // returning either a [Tor+TLS|TLS-only] client AT RUNTIME BASED ON USER SETTINGS // tor == true? => return Tor client // tor == false? => return normal TLS client // // Since functions that take generic INPUT are much easier to implement, // [get_response()] just takes a [hyper::Client], which is passed to // it via deconstructing this [ClientEnum] with a match, like so: // ClientEnum::Tor(T) => get_reponse(... T ...) // ClientEnum::Https(H) => get_reponse(... H ...) // pub async fn get_client(tor: bool) -> Result { if tor { // This one below is non-async, but it doesn't bootstrap immediately. // let tor = TorClient::builder().bootstrap_behavior(arti_client::BootstrapBehavior::OnDemand).create_unbootstrapped()?; let tor = TorClient::create_bootstrapped(TorClientConfig::default()).await?; let tls = tls_api_native_tls::TlsConnector::builder()?.build()?; let connector = ArtiHttpConnector::new(tor, tls); let client = ClientEnum::Tor(Client::builder().build(connector)); return Ok(client) } else { let mut connector = hyper_tls::HttpsConnector::new(); connector.https_only(true); let client = ClientEnum::Https(Client::builder().build(connector)); return Ok(client) } } // Download process: // 0. setup tor, client, http, etc // 1. fill vector with all enums // 2. loop over vec, download metadata // 3. if current == version, remove from vec // 4. loop over vec, download links // 5. extract, upgrade #[tokio::main] pub async fn start(update: Arc>, mut version: Version) -> Result<(), anyhow::Error> { //---------------------------------------------------------------------------------------------------- Init *update.lock().unwrap().updating.lock().unwrap() = true; // Set timer let now = std::time::Instant::now(); // Set progress bar *update.lock().unwrap().msg.lock().unwrap() = MSG_START.to_string(); *update.lock().unwrap().prog.lock().unwrap() = 0.0; info!("Update | {}", INIT); // Get temporary directory *update.lock().unwrap().msg.lock().unwrap() = MSG_TMP.to_string(); // Cannot lock Arc twice in same line // so there will be some intermediate variables. info!("Update | {}", MSG_TMP.to_string()); let tmp_dir = Self::get_tmp_dir()?; std::fs::create_dir(&tmp_dir)?; // Make Pkg vector let prog = update.lock().unwrap().prog.clone(); let msg = update.lock().unwrap().msg.clone(); let mut vec = vec![ Pkg::new(Gupax, &tmp_dir, prog.clone(), msg.clone()), Pkg::new(P2pool, &tmp_dir, prog.clone(), msg.clone()), Pkg::new(Xmrig, &tmp_dir, prog.clone(), msg.clone()), ]; // Generate fake user-agent let user_agent = Pkg::get_user_agent(); *update.lock().unwrap().prog.lock().unwrap() = 5.0; // Create Tor/HTTPS client if update.lock().unwrap().tor { *update.lock().unwrap().msg.lock().unwrap() = MSG_TOR.to_string() } else { *update.lock().unwrap().msg.lock().unwrap() = MSG_HTTPS.to_string() } let prog = *update.lock().unwrap().prog.lock().unwrap(); info!("Update | {}", update.lock().unwrap().msg.lock().unwrap()); let client = Self::get_client(update.lock().unwrap().tor).await?; *update.lock().unwrap().prog.lock().unwrap() += 5.0; info!("Update | Init ... OK ... {}%", prog); //---------------------------------------------------------------------------------------------------- Metadata *update.lock().unwrap().msg.lock().unwrap() = MSG_METADATA.to_string(); info!("Update | {}", METADATA); let mut vec2 = vec![]; // Loop process: // 1. Start all async metadata fetches // 2. Wait for all to finish // 3. Iterate over all [pkg.new_ver], check if empty // 4. If not empty, move [pkg] to different vec // 5. At end, if original vec isn't empty, that means something failed // 6. Redo loop [3] times, with the original vec (that now only has the failed pkgs) // // This logic was originally in the [Pkg::get_metadata()] // function itself but for some reason, it was getting skipped over, // so the [new_ver] check is now here, in the outer scope. for i in 1..=3 { if i > 1 { *update.lock().unwrap().msg.lock().unwrap() = format!("{} [{}/3]", MSG_METADATA_RETRY.to_string(), i); } let mut handles: Vec>> = vec![]; for pkg in vec.iter() { // Clone data before sending to async let name = pkg.name.clone(); let new_ver = Arc::clone(&pkg.new_ver); let client = client.clone(); let link = pkg.link_metadata.to_string(); // Send to async let handle: JoinHandle> = tokio::spawn(async move { match client { ClientEnum::Tor(t) => Pkg::get_metadata(name, new_ver, t, link, user_agent).await, ClientEnum::Https(h) => Pkg::get_metadata(name, new_ver, h, link, user_agent).await, } }); handles.push(handle); } // Handle await for handle in handles { // Two [??] will send the error. // We don't actually want to return the error here since we // prefer looping and retrying over immediately erroring out. match handle.await? { Err(e) => warn!("Update | {}", e), _ => (), } } // Check for empty version let mut indexes = vec![]; for (index, pkg) in vec.iter().enumerate() { if pkg.new_ver.lock().unwrap().is_empty() { warn!("Update | {} failed, attempt [{}/3]...", pkg.name, i); } else { indexes.push(index); vec2.push(pkg.clone()); *update.lock().unwrap().prog.lock().unwrap() += 10.0; info!("Update | {} {} ... OK", pkg.name, pkg.new_ver.lock().unwrap()); } } // Order indexes from biggest to smallest // This prevents shifting the whole vector and causing panics. indexes.sort(); indexes.reverse(); for index in indexes { vec.remove(index); } if vec.is_empty() { break } } if vec.is_empty() { info!("Update | Metadata ... OK ... {}%", update.lock().unwrap().prog.lock().unwrap()); } else { error!("Update | Metadata ... FAIL"); return Err(anyhow!("Metadata fetch failed", )) } //---------------------------------------------------------------------------------------------------- Compare *update.lock().unwrap().msg.lock().unwrap() = MSG_COMPARE.to_string(); info!("Update | {}", COMPARE); let prog = update.lock().unwrap().prog.clone(); let msg = update.lock().unwrap().msg.clone(); let mut vec3 = vec![]; let mut new_pkgs = vec![]; for pkg in vec2.iter() { let new_ver = pkg.new_ver.lock().unwrap().to_owned(); match pkg.name { Gupax => { if new_ver == GUPAX_VERSION { info!("Update | {} {} == {} ... SKIPPING", pkg.name, pkg.new_ver.lock().unwrap(), GUPAX_VERSION); } else { info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), GUPAX_VERSION); new_pkgs.push(format!("\nGupax {} ➡ {}", GUPAX_VERSION, pkg.new_ver.lock().unwrap())); vec3.push(pkg); } } P2pool => { if new_ver == version.p2pool { info!("Update | {} {} == {} ... SKIPPING", pkg.name, pkg.new_ver.lock().unwrap(), version.p2pool); } else { info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), version.p2pool); new_pkgs.push(format!("\nP2Pool {} ➡ {}", version.p2pool, pkg.new_ver.lock().unwrap())); vec3.push(pkg); } } Xmrig => { if new_ver == GUPAX_VERSION { info!("Update | {} {} == {} ... SKIPPING", pkg.name, pkg.new_ver.lock().unwrap(), version.xmrig); } else { info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), version.xmrig); new_pkgs.push(format!("\nXMRig {} ➡ {}", version.xmrig, pkg.new_ver.lock().unwrap())); vec3.push(pkg); } } } } *update.lock().unwrap().prog.lock().unwrap() += 5.0; info!("Update | Compare ... OK ... {}%", update.lock().unwrap().prog.lock().unwrap()); // Return if 0 (all packages up-to-date) // Get amount of packages to divide up the percentage increases let pkg_amount = vec3.len() as f32; if pkg_amount == 0.0 { info!("Update | All packages up-to-date ... RETURNING"); *update.lock().unwrap().prog.lock().unwrap() = 100.0; *update.lock().unwrap().msg.lock().unwrap() = MSG_UP_TO_DATE.to_string(); return Ok(()) } let new_pkgs: String = new_pkgs.concat(); //---------------------------------------------------------------------------------------------------- Download *update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_DOWNLOAD, new_pkgs); info!("Update | {}", DOWNLOAD); let mut vec4 = vec![]; for i in 1..=3 { if i > 1 { *update.lock().unwrap().msg.lock().unwrap() = format!("{} [{}/3]{}", MSG_DOWNLOAD_RETRY.to_string(), i, new_pkgs); } let mut handles: Vec>> = vec![]; for pkg in vec3.iter() { // Clone data before async let name = pkg.name.clone(); let bytes = Arc::clone(&pkg.bytes); let client = client.clone(); let version = pkg.new_ver.lock().unwrap(); let link; // Download link = PREFIX + Version (found at runtime) + SUFFIX + Version + EXT // Example: https://github.com/hinto-janaiyo/gupax/releases/download/v0.0.1/gupax-v0.0.1-linux-standalone-x64 // XMRig doesn't have a [v], so slice it out if pkg.name == Name::Xmrig { link = pkg.link_prefix.to_string() + &version + &pkg.link_suffix + &version[1..] + &pkg.link_extension; // TODO FIX ME // This is temp link for v0.0.1 of [Gupax] } else if pkg.name == Name::Gupax { link = "https://github.com/hinto-janaiyo/gupax/releases/download/v0.0.1/gupax-v0.0.1-linux-x64".to_string() } else { link = pkg.link_prefix.to_string() + &version + &pkg.link_suffix + &version + &pkg.link_extension; } info!("Update | {} ... {}", pkg.name, link); let handle: JoinHandle> = tokio::spawn(async move { match client { ClientEnum::Tor(t) => Pkg::get_bytes(name, bytes, t, link, user_agent).await, ClientEnum::Https(h) => Pkg::get_bytes(name, bytes, h, link, user_agent).await, } }); handles.push(handle); } // Handle await for handle in handles { match handle.await? { Err(e) => warn!("Update | {}", e), _ => (), } } // Check for empty bytes let mut indexes = vec![]; for (index, pkg) in vec3.iter().enumerate() { if pkg.bytes.lock().unwrap().is_empty() { warn!("Update | {} failed, attempt [{}/3]...", pkg.name, i); } else { indexes.push(index); vec4.push(pkg.clone()); *update.lock().unwrap().prog.lock().unwrap() += (30.0 / pkg_amount).round(); info!("Update | {} ... OK", pkg.name); } } // Order indexes from biggest to smallest // This prevents shifting the whole vector and causing panics. indexes.sort(); indexes.reverse(); for index in indexes { vec3.remove(index); } if vec3.is_empty() { break } } if vec3.is_empty() { info!("Update | Download ... OK ... {}%", *update.lock().unwrap().prog.lock().unwrap()); } else { error!("Update | Download ... FAIL"); return Err(anyhow!("Download failed", )) } //---------------------------------------------------------------------------------------------------- Extract *update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_EXTRACT, new_pkgs); info!("Update | {}", EXTRACT); for pkg in vec4.iter() { let tmp = tmp_dir.to_owned() + &pkg.name.to_string(); if pkg.name == Name::Gupax { #[cfg(target_family = "unix")] use std::os::unix::fs::OpenOptionsExt; #[cfg(target_family = "unix")] std::fs::OpenOptions::new().create(true).write(true).mode(0o770).open(&tmp)?; std::fs::write(tmp, pkg.bytes.lock().unwrap().as_ref())?; } else { #[cfg(target_family = "unix")] tar::Archive::new(flate2::read::GzDecoder::new(pkg.bytes.lock().unwrap().as_ref())).unpack(tmp)?; } *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round(); info!("Update | {} ... OK", pkg.name); } info!("Update | Extract ... OK ... {}%", *update.lock().unwrap().prog.lock().unwrap()); //---------------------------------------------------------------------------------------------------- Upgrade // 0. Walk directories // 1. If basename matches known binary name, start // 2. Rename tmp path into current path // 3a. Update [State/Version] // 3b. Gupax version is builtin to binary, so no state change needed *update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_UPGRADE, new_pkgs); info!("Update | {}", UPGRADE); // Just in case, create all folders for entry in WalkDir::new(tmp_dir.clone()) { let entry = entry?.clone(); let basename = entry.file_name().to_str().ok_or(anyhow::Error::msg("WalkDir basename failed"))?; match basename { "Gupax" => { let path = update.lock().unwrap().path_gupax.clone(); info!("Update | Moving [{}] -> [{}]", entry.path().display(), path); std::fs::rename(entry.path(), path)?; *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round(); }, "p2pool" => { let path = update.lock().unwrap().path_p2pool.clone(); let path = std::path::Path::new(&path); info!("Update | Moving [{}] -> [{}]", entry.path().display(), path.display()); std::fs::create_dir_all(path.parent().ok_or(anyhow::Error::msg("P2Pool path failed"))?)?; std::fs::rename(entry.path(), path)?; version.p2pool = Pkg::get_new_pkg_version(P2pool, &vec4)?; *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round(); }, "xmrig" => { let path = update.lock().unwrap().path_xmrig.clone(); let path = std::path::Path::new(&path); info!("Update | Moving [{}] -> [{}]", entry.path().display(), path.display()); std::fs::create_dir_all(path.parent().ok_or(anyhow::Error::msg("XMRig path failed"))?)?; std::fs::rename(entry.path(), path)?; version.xmrig = Pkg::get_new_pkg_version(Xmrig, &vec4)?; *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round(); }, _ => (), } } info!("Update | Removing temporary directory ... {}", tmp_dir); std::fs::remove_dir_all(&tmp_dir)?; let seconds = now.elapsed().as_secs(); info!("Update ... Seconds elapsed: [{}s]", seconds); info!("Update ... OK ... 100%"); match seconds { 0 => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took 0 seconds... Do you have 10Gbit internet or something...?!{}", MSG_SUCCESS, new_pkgs), 1 => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took 1 second... Wow!{}", MSG_SUCCESS, new_pkgs), _ => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took {} seconds.{}", MSG_SUCCESS, seconds, new_pkgs), } *update.lock().unwrap().prog.lock().unwrap() = 100.0; *update.lock().unwrap().updating.lock().unwrap() = false; Ok(()) } } #[derive(Debug,Clone)] pub enum ClientEnum { Tor(Client>), Https(Client>), } //---------------------------------------------------------------------------------------------------- Pkg struct/impl #[derive(Debug,Clone)] pub struct Pkg { name: Name, link_metadata: &'static str, link_prefix: &'static str, link_suffix: &'static str, link_extension: &'static str, tmp_dir: String, prog: Arc>, msg: Arc>, bytes: Arc>, old_ver: String, new_ver: Arc>, } impl Pkg { pub fn new(name: Name, tmp_dir: &String, prog: Arc>, msg: Arc>) -> Self { let link_metadata = match name { Gupax => GUPAX_METADATA, P2pool => P2POOL_METADATA, Xmrig => XMRIG_METADATA, }; let link_prefix = match name { Gupax => GUPAX_PREFIX, P2pool => P2POOL_PREFIX, Xmrig => XMRIG_PREFIX, }; let link_suffix = match name { Gupax => GUPAX_SUFFIX, P2pool => P2POOL_SUFFIX, Xmrig => XMRIG_SUFFIX, }; let link_extension = match name { Gupax => GUPAX_EXTENSION, P2pool => P2POOL_EXTENSION, Xmrig => XMRIG_EXTENSION, }; Self { name, link_metadata, link_prefix, link_suffix, link_extension, tmp_dir: tmp_dir.to_string(), prog, msg, bytes: Arc::new(Mutex::new(bytes::Bytes::new())), old_ver: String::new(), new_ver: Arc::new(Mutex::new(String::new())), } } //---------------------------------------------------------------------------------------------------- Pkg functions // Generate fake [User-Agent] HTTP header pub fn get_user_agent() -> &'static str { let rand = thread_rng().gen_range(0..50); let user_agent = FAKE_USER_AGENT[rand]; info!("Update | Randomly selecting User-Agent ({}/50) ... {}", rand, user_agent); user_agent } // Generate GET request based off input URI + fake user agent pub fn get_request(link: String, user_agent: &'static str) -> Result, anyhow::Error> { let request = Request::builder() .method("GET") .uri(link) .header(hyper::header::USER_AGENT, HeaderValue::from_static(user_agent)) .body(Body::empty())?; Ok(request) } // Get metadata using [Generic hyper::client] & [Request] // and change [version, prog] under an Arc pub async fn get_metadata(name: Name, new_ver: Arc>, client: Client, link: String, user_agent: &'static str) -> Result<(), Error> where C: hyper::client::connect::Connect + Clone + Send + Sync + 'static, { let request = Pkg::get_request(link.clone(), user_agent)?; let mut response = client.request(request).await?; let body = hyper::body::to_bytes(response.body_mut()).await?; let body: TagName = serde_json::from_slice(&body)?; *new_ver.lock().unwrap() = body.tag_name.clone(); Ok(()) } // Takes a [Request], fills the appropriate [Pkg] // [bytes] field with the [Archive/Standalone] pub async fn get_bytes(name: Name, bytes: Arc>, client: Client, link: String, user_agent: &'static str) -> Result<(), anyhow::Error> where C: hyper::client::connect::Connect + Clone + Send + Sync + 'static, { // GitHub sends a 302 redirect, so we must follow // the [Location] header... only if Reqwest had custom // connectors so I didn't have to manually do this... let request = Self::get_request(link.clone(), user_agent)?; let response = client.request(request).await?; let request = Self::get_request(response.headers().get(hyper::header::LOCATION).unwrap().to_str()?.to_string(), user_agent)?; let response = client.request(request).await?; let body = hyper::body::to_bytes(response.into_body()).await?; *bytes.lock().unwrap() = body; Ok(()) } // Take in a [Name] and [Vec] of [Pkg]s, find // that [Name]'s corresponding new version. fn get_new_pkg_version(name: Name, vec: &Vec<&Pkg>) -> Result { for pkg in vec.iter() { if pkg.name == name { return Ok(pkg.new_ver.lock().unwrap().to_string()) } } Err(anyhow::Error::msg("Couldn't find new_pkg_version")) } } // This inherits the value of [tag_name] from GitHub's JSON API #[derive(Debug, Serialize, Deserialize)] struct TagName { tag_name: String, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Name { Gupax, P2pool, Xmrig, } impl std::fmt::Display for Name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:?}", self) } }