From 75491835d4ca1fba9f2d3c02788036baf07001e6 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Tue, 22 Oct 2024 10:00:37 +0200 Subject: [PATCH 01/21] working implementation --- package-lock.json | 4 +- src-tauri/Cargo.toml | 28 +++--- src-tauri/src/binaries/binaries_manager.rs | 87 +++++++++++++++++- src-tauri/src/binaries/binaries_resolver.rs | 25 ++++- src-tauri/src/main.rs | 11 ++- .../0.2.1657.32929/CodeChunks.db | Bin 0 -> 73728 bytes .../0.2.1657.32929/SemanticSymbols.db | Bin 0 -> 32768 bytes .../0.2.1657.32929/SemanticSymbols.db-shm | Bin 0 -> 32768 bytes .../0.2.1657.32929/SemanticSymbols.db-wal | Bin 0 -> 3106512 bytes ...27bb9716-bb51-4a01-86eb-af3758e05a42.vsidx | Bin 0 -> 107 bytes ...37762544-a741-4cc4-8cec-b7bd207a3ea2.vsidx | Bin 0 -> 107 bytes ...9acf0f2f-d3dc-486a-9893-c58763540b08.vsidx | Bin 0 -> 14076 bytes ...d3d18ab6-70b4-4e04-a309-23e79620d609.vsidx | Bin 0 -> 21214 bytes ...dfb9e6f5-7195-4f5a-9754-3b2231ce0bf9.vsidx | Bin 0 -> 107 bytes .../v17/DocumentLayout.backup.json | 41 +++++++++ .../tari-win-bundler/v17/DocumentLayout.json | 41 +++++++++ 16 files changed, 214 insertions(+), 23 deletions(-) create mode 100644 tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/CodeChunks.db create mode 100644 tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db create mode 100644 tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-shm create mode 100644 tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-wal create mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/27bb9716-bb51-4a01-86eb-af3758e05a42.vsidx create mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/37762544-a741-4cc4-8cec-b7bd207a3ea2.vsidx create mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/9acf0f2f-d3dc-486a-9893-c58763540b08.vsidx create mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/d3d18ab6-70b4-4e04-a309-23e79620d609.vsidx create mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/dfb9e6f5-7195-4f5a-9754-3b2231ce0bf9.vsidx create mode 100644 tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.backup.json create mode 100644 tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.json diff --git a/package-lock.json b/package-lock.json index 48e22897c..1f7c31ad1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tari-universe", - "version": "0.5.31", + "version": "0.5.41", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tari-universe", - "version": "0.5.31", + "version": "0.5.41", "dependencies": { "@floating-ui/react": "^0.26.25", "@tauri-apps/api": "^1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d8fe9dbc5..64eadf82d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -9,12 +9,12 @@ version = "0.5.41" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] -tauri-build = {version = "1.5.5", features = ["isolation"]} +tauri-build = {version = "1.5.5", features = ["isolation"] } [dependencies] anyhow = "1" async-trait = "0.1.81" -async_zip = {version = "0.0.17", features = ["full"]} +async_zip = {version = "0.0.17", features = ["full"] } auto-launch = "0.5.0" blake2 = "0.10" chrono = "0.4.38" @@ -29,26 +29,26 @@ keyring = {version = "3.0.5", features = [ "windows-native", "apple-native", "linux-native", -]} +] } libsqlite3-sys = {version = "0.25.1", features = [ "bundled", -]}# Required for tari_wallet +] }# Required for tari_wallet log = "0.4.22" log4rs = "1.3.0" minotari_node_grpc_client = {git = "https://github.com/tari-project/tari.git", branch = "development"} minotari_wallet_grpc_client = {git = "https://github.com/tari-project/tari.git", branch = "development"} -nix = {version = "0.29.0", features = ["signal"]} +nix = {version = "0.29.0", features = ["signal"] } nvml-wrapper = "0.10.0" open = "5" phraze = "0.3.15" rand = "0.8.5" regex = "1.10.5" -reqwest = {version = "0.12.5", features = ["stream", "json", "multipart"]} +reqwest = {version = "0.12.5", features = ["stream", "json", "multipart"] } sanitize-filename = "0.5" semver = "1.0.23" -sentry = {version = "0.34.0", features = ["anyhow"]} +sentry = {version = "0.34.0", features = ["anyhow"] } sentry-tauri = "0.3.0" -serde = {version = "1", features = ["derive"]} +serde = {version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.10.8" sys-locale = "0.3.1" @@ -58,7 +58,7 @@ tari_common = {git = "https://github.com/tari-project/tari.git", branch = "devel tari_common_types = {git = "https://github.com/tari-project/tari.git", branch = "development"} tari_core = {git = "https://github.com/tari-project/tari.git", branch = "development", features = [ "transactions", -]} +] } tari_crypto = "0.20.3" tari_key_manager = {git = "https://github.com/tari-project/tari.git", branch = "development"} tari_shutdown = {git = "https://github.com/tari-project/tari.git", branch = "development"} @@ -78,12 +78,12 @@ tauri = {version = "1.8.0", features = [ "isolation", "shell-open", "process-command-api", -]} +] } tauri-plugin-single-instance = {git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1"} thiserror = "1.0.26" -tokio = {version = "1", features = ["full"]} -tokio-util = {version = "0.7.11", features = ["compat"]} -xz2 = {version = "0.1.7", features = ["static"]}# static bind lzma +tokio = {version = "1", features = ["full"] } +tokio-util = {version = "0.7.11", features = ["compat"] } +xz2 = {version = "0.1.7", features = ["static"] }# static bind lzma zip = "2.2.0" [target.'cfg(windows)'.dependencies] @@ -92,7 +92,7 @@ winreg = "0.52.0" # needed for keymanager. TODO: Find a way of creating a keymanager without bundling sqlite chrono = "0.4.38" device_query = "2.1.0" -libsqlite3-sys = {version = "0.25.1", features = ["bundled"]} +libsqlite3-sys = {version = "0.25.1", features = ["bundled"] } log = "0.4.22" nvml-wrapper = "0.10.0" rand = "0.8.5" diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index 1172493a4..afa2b1ce9 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -32,6 +32,7 @@ pub(crate) struct BinaryManager { local_aviailable_versions_list: Vec, used_version: Option, adapter: Box, + releases_cache_id: Option, } impl BinaryManager { @@ -41,6 +42,7 @@ impl BinaryManager { adapter: Box, network_prerelease_prefix: Option, should_validate_checksum: bool, + releases_cache_id: Option, ) -> Self { let versions_requirements_data = match Network::get_current_or_user_setting_or_default() { Network::NextNet => include_str!("../../binaries_versions_nextnet.json"), @@ -62,6 +64,7 @@ impl BinaryManager { local_aviailable_versions_list: Vec::new(), used_version: None, adapter, + releases_cache_id, } } @@ -69,6 +72,69 @@ impl BinaryManager { self.binary_subfolder.as_ref() } + + fn create_file_with_cached_releases(&self, data: Vec) -> Result<(), Error> { + info!(target: LOG_TARGET, "Creating file with cached releases"); + if self.releases_cache_id.is_none() { + return Err(anyhow!("No cache id provided")); + } + + let binary_folder = self.adapter.get_binary_folder()?; + let cache_file = binary_folder.join(self.releases_cache_id.as_ref().unwrap()); + + let json_content = serde_json::to_string(&data)?; + std::fs::write(cache_file, json_content)?; + + Ok(()) + } + + fn check_if_cached_releases_exist(&self) -> bool { + info!(target: LOG_TARGET, "Checking if cached releases exist"); + if self.releases_cache_id.is_none() { + return false; + } + + let binary_folder = self.adapter.get_binary_folder().ok(); + let cache_file = binary_folder.map(|path| path.join(self.releases_cache_id.as_ref().unwrap())); + + cache_file.map_or(false, |path| path.exists()) + } + + fn read_cached_releases(&self) -> Result, Error> { + info!(target: LOG_TARGET, "Reading cached releases"); + if self.releases_cache_id.is_none() { + return Err(anyhow!("No cache id provided")); + } + + let binary_folder = self.adapter.get_binary_folder()?; + let cache_file = binary_folder.join(self.releases_cache_id.as_ref().unwrap()); + + let json_content = std::fs::read_to_string(cache_file)?; + let releases: Vec = serde_json::from_str(&json_content)?; + + for release in &releases { + info!(target: LOG_TARGET, "Cached release: {:?}", release.version); + } + + Ok(releases) + } + + pub fn remove_cached_releases(&self) -> Result<(), Error> { + info!(target: LOG_TARGET, "Removing cached releases"); + if self.releases_cache_id.is_none() { + return Err(anyhow!("No cache id provided")); + } + + let binary_folder = self.adapter.get_binary_folder()?; + let cache_file = binary_folder.join(self.releases_cache_id.as_ref().unwrap()); + + if cache_file.exists() { + std::fs::remove_file(cache_file)?; + } + + Ok(()) + } + fn read_version_requirements(binary_name: String, data_str: &str) -> VersionReq { let json_content: BinaryVersionsJsonContent = serde_json::from_str(data_str).unwrap_or_default(); @@ -329,7 +395,26 @@ impl BinaryManager { pub async fn check_for_updates(&mut self) { info!(target: LOG_TARGET,"Checking for updates for binary: {:?}", self.binary_name); - let versions_info = self.adapter.fetch_releases_list().await.unwrap_or_default(); + let versions_info = match self.releases_cache_id { + Some(_) => { + info!(target: LOG_TARGET, "Reading cached releases"); + if self.check_if_cached_releases_exist() { + self.read_cached_releases().unwrap_or_default() + } else { + let data = self.adapter.fetch_releases_list().await.unwrap_or_default(); + let _ = self.create_file_with_cached_releases(data.clone()); + + data + } + } + None => { + info!(target: LOG_TARGET, "Fetching releases list"); + let data = self.adapter.fetch_releases_list().await.unwrap_or_default(); + let _ = self.create_file_with_cached_releases(data.clone()); + + data + } + }; info!(target: LOG_TARGET, "Found {:?} versions for binary: {:?}", diff --git a/src-tauri/src/binaries/binaries_resolver.rs b/src-tauri/src/binaries/binaries_resolver.rs index 4502266c4..26b79349c 100644 --- a/src-tauri/src/binaries/binaries_resolver.rs +++ b/src-tauri/src/binaries/binaries_resolver.rs @@ -1,8 +1,10 @@ use crate::ProgressTracker; use anyhow::{anyhow, Error}; use async_trait::async_trait; +use log::info; use regex::Regex; use semver::Version; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; use std::sync::LazyLock; @@ -15,16 +17,18 @@ use super::adapter_xmrig::XmrigVersionApiAdapter; use super::binaries_manager::BinaryManager; use super::Binaries; +pub const LOG_TARGET: &str = "tari::universe::binary_resolver"; + static INSTANCE: LazyLock> = LazyLock::new(|| RwLock::new(BinaryResolver::new())); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionDownloadInfo { pub(crate) version: Version, pub(crate) assets: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionAsset { pub(crate) url: String, pub(crate) name: String, @@ -78,6 +82,7 @@ impl BinaryResolver { Box::new(XmrigVersionApiAdapter {}), None, true, + Some("xmrig".to_string()), ), ); @@ -93,6 +98,7 @@ impl BinaryResolver { }), None, true, + Some("tarigpuminer".to_string()), ), ); @@ -108,6 +114,7 @@ impl BinaryResolver { }), Some(tari_prerelease_prefix.to_string()), true, + Some("tari-suite".to_string()), ), ); @@ -123,6 +130,7 @@ impl BinaryResolver { }), Some(tari_prerelease_prefix.to_string()), true, + Some("tari-suite".to_string()), ), ); @@ -138,6 +146,7 @@ impl BinaryResolver { }), Some(tari_prerelease_prefix.to_string()), true, + Some("tari-suite".to_string()), ), ); @@ -153,6 +162,7 @@ impl BinaryResolver { }), None, true, + Some("sha-p2pool".to_string()), ), ); @@ -164,6 +174,7 @@ impl BinaryResolver { Box::new(TorReleaseAdapter {}), None, true, + Some("tor".to_string()) , ), ); @@ -208,6 +219,8 @@ impl BinaryResolver { progress_tracker: ProgressTracker, should_check_for_update: bool, ) -> Result<(), Error> { + info!(target: LOG_TARGET, "Initializing binary: {} | should check for update: {}", binary.name(), should_check_for_update); + let manager = self .managers .get_mut(&binary) @@ -256,6 +269,14 @@ impl BinaryResolver { Ok(()) } + + pub async fn remove_all_caches(&mut self) -> Result<(), Error> { + for manager in self.managers.values_mut() { + manager.remove_cached_releases()?; + } + Ok(()) + } + pub async fn update_binary( &mut self, binary: Binaries, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 03a00bcb0..a77460e85 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -570,10 +570,11 @@ async fn setup_inner( .await?; let mut binary_resolver = BinaryResolver::current().write().await; - let should_check_for_update = now - .duration_since(last_binaries_update_timestamp) - .unwrap_or(Duration::from_secs(0)) - > Duration::from_secs(60 * 60 * 6); + // let should_check_for_update = now + // .duration_since(last_binaries_update_timestamp) + // .unwrap_or(Duration::from_secs(0)) + // > Duration::from_secs(60 * 60 * 6); + let should_check_for_update = true; if use_tor && cfg!(target_os = "windows") { progress.set_max(5).await; @@ -661,6 +662,8 @@ async fn setup_inner( .await?; } + binary_resolver.remove_all_caches().await?; + //drop binary resolver to release the lock drop(binary_resolver); diff --git a/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/CodeChunks.db b/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/CodeChunks.db new file mode 100644 index 0000000000000000000000000000000000000000..6c320c3691c9e7bce42cfe692f50060f53deddb0 GIT binary patch literal 73728 zcmeI)&u`jh7{GBmq?jZSC%yO-$yJmAYMK>A)wETSMv2q164DZ~wHgy5!EIUj(SW1S z_E6DPlXlp3=lv0rcGz)~cIt83zcB5#9;WTK*9HfZnogyfRC!;k1pBq$*M2_F``S1T z-MyKsS*Cuc)oxTQeO+3RrlzD1bzPFAsCbR-}g z_-|?cH}&WFZ|2{LHDZ56zn)u*7H5B$`8@J#M2dW?Y$?BpO_&fs009IL_-_TSFNU?1 z75P)EvR^klhjnokEO)GmmGaJ?%@mAu(a?+OD>*~=u1Q$?_0?LnyK2`-_M)nyX`0+S zcA6`;?wd{L$HGVhai=VB4ROx^Y`i z?-h5ldC|tUkuR~Q?&JU`B}!1_SxL0C^{Qk-eI?duU>?)1+Q7wv2+2<P9Q?^}s@SxVbo5)s^HqWM!GsHm3q<1sv zO~a=lCpGrQo`#$<{}x0qKb%#x_gCcJ;z)`I`z-2SE` zDe(-lv59$aCahgw88>FVQ00_&X}0&7qG;#O%TEu7yt`JbZB{?(w3^PQ!^MaX@^I~` zVevAD_dM&;WLizjY@R-^`gB(3P)1z5ag2rO$!8Sp+&Q_o?Zh`c+TmPyG^*i>VL z9zF4UE*Y;5MU0yDa9F!~Zd`JP`zxo0^Gb7C(Jn2?Jtzqt22QT;8L2~=H33o3^k=1vn!dhl&Tvpw1 z$|-j)BtBe4>b009ILKmY**5I_I{1m13eKc7T0 zAC&ewX1i0`ue7aJsna@YADE@K`JmM)S(SF}!f~y6VgIOEt()yq!MantXEw~WZlf+D z`+lP88xN%AbEYD#fAOFGF(H5e0tg_000IagfB*srAb>!C1?=bl z-2VqS!GwkY0tg_000IagfB*srAb@}nu)qJ${Xbm+0R#|0009ILKmY**5I_KdU<+{n zAME@R9Rdg-fB*srAb&=>zPHMF^W@}*(Y-84dNoZ@J-Cfvj z_5yo{!QNx9GWGaHW=Lll^)EtC9>5p%QUPxXGTSBQMyd^{s zgbF*?*vXGoc2njFb`?|idAAkeMgLE2^RFOPe+nDFH-Bw>-*~?MWBnOR;(!1IAOHaf zKmY;|fIy)@C`t00ZE;#3jC%f^an^0$4==_+w==q)c)_4Q@Ozdw^pmG+xR31$Cs0#KMIXrQd`RF@v&gGvsBJ5QRrRB znrnyNO{hF1O@H($yi(M!pI0S$dt3Zwhu%dnn`F{)W!AB)9hfvVms*vuLos>^Q*h}a zb;yZh9Ba-wIilx8>$tXIvAW09a(8EiQzPP)cvfo?1ox+DX@$Jk9Q~cOY632Mgg*HU28{|`O5lu7~v)wd?00Izz00bZa0SG_<0uX>eu>k)67yBR=0uX=z1Rwwb2tWV= z5P$##Ag~Mq`2W8QQN$b|009U<00Izz00bZa0SG_<0>uLO|6lBbTnIn_0uX=z1Rwwb z2tWV=5P-lk2;l$!GDHz`fB*y_009U<00Izz00bZa0SFWe;QPPW2e}Y{00bZa0SG_< P0uX=z1Rwx`Wf1rWc+s9; literal 0 HcmV?d00001 diff --git a/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-shm b/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..c500b15117c306fc472a17b65e3fe21cc995647e GIT binary patch literal 32768 zcmeI)b#N3(6bA6``*3#>Jh&#fySux)yE_DTcM0we!QI{6-Q8Vo_qY@(j;n*Zf&%Wn zs;TYm>6z*I^_$t5Kek;jyTcj99Sebwj|BvBl)VOd^m+2|+~KpmJtJ0&QvY^A?;}0d z-VE;M`i?)OXRiA;wDq4n%ZfXyZkM=VfU85KX?0>e2b&w z-Qmyo2FHhm37CKhn1BhGfC-p@37CKhn1BhGfC-p@37CKhn1BhGfC-p@37CKhn1BhG zfC-p@37CKhn1BhGfC-p@37CKhn1BhGfC-p@37CKhn1BiVM}bGqzq(nN!2eQ!ju{66 zATWX>Btjz`A|NuNAtquWE)pUU5+gZMAT`n>12Q8!av~23pdgB%1WKX|Dxe~&payE9 z4jP~#nxF+*qAl8?Bf6j~dY}*bVgQCf~2XnCiOW=*=SdBGUkImSM z9oU1tIDjKKiW4}4v$%jOxQd&&g?qS<2Y8Ikk}8GLD6KLm zi?S+*@+hwgsECTHgvzL_DyWL8s)p*Qt{SL`nyQ7`sI5B4Q(e?uz12tkHAq7>Qlm6h z6Esm%G($5rM+>x2OSD`ov|8)6L7TN*JG5K-bwG!8Tqkr|=XF6>bX7NXOLyg*zvuS~ zAs~Vv48kJ{+z=b_;Ep6ni8RQFEXaktD1@RYg|euGs;Gs!XoRL{h4$!#Zs>`A7>HpQ ziE)^KX_$$5Scs)qfwkCxZP3R%yRbX^us?@zILB~2CvzHSa~>CS zDOYhVH*qU>aW4bmK^0R;l~YAkQ%%)VLp4)NwNpoRRS)&m01ehKjn+6#(p1gTTrHBfR%(qlYKwMi zj}GdHPU?&<>Y8rr-fx#bzxLJO2mTQhArJ~Kh=@ptj`&E5w8)Aa$cJJmhia&YW@wGh z=!HQTg^8GgSy+UX*oZCIg+n-nOSq0Zc!oFd!ypX9@N{7m#-=+{G9z=cAWO0$YqB9* zvLkzN07uY^(>b5ZxQ^SnkH>h9*LaW5_>TS*OyT6JsEQ*GrBWv4QX!R6CDl?RwNfYb z)Nqa0G|kget<_fT)lr?*C0*Bj%dcNMA%AG^{W|8V2!^n5MO4H<93+4Tk|7n+ArrD8 zH}a#fV@ABvD38jhj@qb?#%PW<=m1Z2M{o4UU<}7-jK?HQ$85~UVl2ZdtivX3$8PM$ zVI0S4oX2I{z+F7TQ@q4m#}s>h8IVC4l3^K8op%N>(QY*bOE4y+lzY43kN~^potGa5dz8b5! zTC2S}tDAbMp9X4(Mre$@G+EO%Tl2M8%d|@Cv`O2vOZ#+4$8<{PbXhlaM-S}U{Gr)x z2QvW^FaZ-V0TVC*6Yvpm&TegC0w!PrCSU?4U;-v!0w!PrCSU?4U;-v!0w!PrCSU?4 bU;-v!0w!PrCSU?4U;-v!0w!PrzlXpd#Hg!Y literal 0 HcmV?d00001 diff --git a/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-wal b/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..1767c6327525650ba6b587593abad8fc1218daa3 GIT binary patch literal 3106512 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 literal 0 HcmV?d00001 diff --git a/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/27bb9716-bb51-4a01-86eb-af3758e05a42.vsidx b/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/27bb9716-bb51-4a01-86eb-af3758e05a42.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..70aef67a00f5a48bfb0d87044cf8f151e2685b5d GIT binary patch literal 107 wcmZ>EaTnxZU~p%E02V0C38Z0cW+XOHDO{Wlsuo0n)bd05(ok9*N*hCI0FV0t;Q#;t literal 0 HcmV?d00001 diff --git a/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/37762544-a741-4cc4-8cec-b7bd207a3ea2.vsidx b/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/37762544-a741-4cc4-8cec-b7bd207a3ea2.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..70aef67a00f5a48bfb0d87044cf8f151e2685b5d GIT binary patch literal 107 wcmZ>EaTnxZU~p%E02V0C38Z0cW+XOHDO{Wlsuo0n)bd05(ok9*N*hCI0FV0t;Q#;t literal 0 HcmV?d00001 diff --git a/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/9acf0f2f-d3dc-486a-9893-c58763540b08.vsidx b/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/9acf0f2f-d3dc-486a-9893-c58763540b08.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..adb1a05badc98e17cc331a2b38209ed57e2b0836 GIT binary patch literal 14076 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 literal 0 HcmV?d00001 diff --git a/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.backup.json b/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.backup.json new file mode 100644 index 000000000..0b662f676 --- /dev/null +++ b/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.backup.json @@ -0,0 +1,41 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{4B155CEE-1847-47DC-94A3-8AE15BAFDA2C}|tari-win-bundler.wixproj|C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs||{FA3CD31E-987B-443A-9B81-186104E8DAC1}", + "RelativeMoniker": "D:0:0:{4B155CEE-1847-47DC-94A3-8AE15BAFDA2C}|tari-win-bundler.wixproj|solutionrelative:Bundle.wxs||{FA3CD31E-987B-443A-9B81-186104E8DAC1}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "Bundle.wxs", + "DocumentMoniker": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs", + "RelativeDocumentMoniker": "Bundle.wxs", + "ToolTip": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs", + "RelativeToolTip": "Bundle.wxs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAABIAAAAYAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|", + "WhenOpened": "2024-10-15T07:50:05.685Z", + "EditorCaption": "" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.json b/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.json new file mode 100644 index 000000000..235b8b9e2 --- /dev/null +++ b/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.json @@ -0,0 +1,41 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{4B155CEE-1847-47DC-94A3-8AE15BAFDA2C}|tari-win-bundler.wixproj|C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs||{FA3CD31E-987B-443A-9B81-186104E8DAC1}", + "RelativeMoniker": "D:0:0:{4B155CEE-1847-47DC-94A3-8AE15BAFDA2C}|tari-win-bundler.wixproj|solutionrelative:Bundle.wxs||{FA3CD31E-987B-443A-9B81-186104E8DAC1}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "Bundle.wxs", + "DocumentMoniker": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs", + "RelativeDocumentMoniker": "Bundle.wxs", + "ToolTip": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs", + "RelativeToolTip": "Bundle.wxs", + "ViewState": "AgIAAAQAAAAAAAAAAAAmwBIAAAAYAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|", + "WhenOpened": "2024-10-15T07:50:05.685Z", + "EditorCaption": "" + } + ] + } + ] + } + ] +} \ No newline at end of file From a6867cc1aa739f479b41a8e95122b807f256dbe3 Mon Sep 17 00:00:00 2001 From: Misieq01 <38589417+Misieq01@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:11:41 +0200 Subject: [PATCH 02/21] Delete tari-win-bundler directory --- .../0.2.1657.32929/CodeChunks.db | Bin 73728 -> 0 bytes .../0.2.1657.32929/SemanticSymbols.db | Bin 32768 -> 0 bytes .../0.2.1657.32929/SemanticSymbols.db-shm | Bin 32768 -> 0 bytes .../0.2.1657.32929/SemanticSymbols.db-wal | Bin 3106512 -> 0 bytes ...27bb9716-bb51-4a01-86eb-af3758e05a42.vsidx | Bin 107 -> 0 bytes ...37762544-a741-4cc4-8cec-b7bd207a3ea2.vsidx | Bin 107 -> 0 bytes ...9acf0f2f-d3dc-486a-9893-c58763540b08.vsidx | Bin 14076 -> 0 bytes ...d3d18ab6-70b4-4e04-a309-23e79620d609.vsidx | Bin 21214 -> 0 bytes ...dfb9e6f5-7195-4f5a-9754-3b2231ce0bf9.vsidx | Bin 107 -> 0 bytes .../v17/DocumentLayout.backup.json | 41 ------------------ .../tari-win-bundler/v17/DocumentLayout.json | 41 ------------------ 11 files changed, 82 deletions(-) delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/CodeChunks.db delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-shm delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-wal delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/27bb9716-bb51-4a01-86eb-af3758e05a42.vsidx delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/37762544-a741-4cc4-8cec-b7bd207a3ea2.vsidx delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/9acf0f2f-d3dc-486a-9893-c58763540b08.vsidx delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/d3d18ab6-70b4-4e04-a309-23e79620d609.vsidx delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/dfb9e6f5-7195-4f5a-9754-3b2231ce0bf9.vsidx delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.backup.json delete mode 100644 tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.json diff --git a/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/CodeChunks.db b/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/CodeChunks.db deleted file mode 100644 index 6c320c3691c9e7bce42cfe692f50060f53deddb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73728 zcmeI)&u`jh7{GBmq?jZSC%yO-$yJmAYMK>A)wETSMv2q164DZ~wHgy5!EIUj(SW1S z_E6DPlXlp3=lv0rcGz)~cIt83zcB5#9;WTK*9HfZnogyfRC!;k1pBq$*M2_F``S1T z-MyKsS*Cuc)oxTQeO+3RrlzD1bzPFAsCbR-}g z_-|?cH}&WFZ|2{LHDZ56zn)u*7H5B$`8@J#M2dW?Y$?BpO_&fs009IL_-_TSFNU?1 z75P)EvR^klhjnokEO)GmmGaJ?%@mAu(a?+OD>*~=u1Q$?_0?LnyK2`-_M)nyX`0+S zcA6`;?wd{L$HGVhai=VB4ROx^Y`i z?-h5ldC|tUkuR~Q?&JU`B}!1_SxL0C^{Qk-eI?duU>?)1+Q7wv2+2<P9Q?^}s@SxVbo5)s^HqWM!GsHm3q<1sv zO~a=lCpGrQo`#$<{}x0qKb%#x_gCcJ;z)`I`z-2SE` zDe(-lv59$aCahgw88>FVQ00_&X}0&7qG;#O%TEu7yt`JbZB{?(w3^PQ!^MaX@^I~` zVevAD_dM&;WLizjY@R-^`gB(3P)1z5ag2rO$!8Sp+&Q_o?Zh`c+TmPyG^*i>VL z9zF4UE*Y;5MU0yDa9F!~Zd`JP`zxo0^Gb7C(Jn2?Jtzqt22QT;8L2~=H33o3^k=1vn!dhl&Tvpw1 z$|-j)BtBe4>b009ILKmY**5I_I{1m13eKc7T0 zAC&ewX1i0`ue7aJsna@YADE@K`JmM)S(SF}!f~y6VgIOEt()yq!MantXEw~WZlf+D z`+lP88xN%AbEYD#fAOFGF(H5e0tg_000IagfB*srAb>!C1?=bl z-2VqS!GwkY0tg_000IagfB*srAb@}nu)qJ${Xbm+0R#|0009ILKmY**5I_KdU<+{n zAME@R9Rdg-fB*srAb&=>zPHMF^W@}*(Y-84dNoZ@J-Cfvj z_5yo{!QNx9GWGaHW=Lll^)EtC9>5p%QUPxXGTSBQMyd^{s zgbF*?*vXGoc2njFb`?|idAAkeMgLE2^RFOPe+nDFH-Bw>-*~?MWBnOR;(!1IAOHaf zKmY;|fIy)@C`t00ZE;#3jC%f^an^0$4==_+w==q)c)_4Q@Ozdw^pmG+xR31$Cs0#KMIXrQd`RF@v&gGvsBJ5QRrRB znrnyNO{hF1O@H($yi(M!pI0S$dt3Zwhu%dnn`F{)W!AB)9hfvVms*vuLos>^Q*h}a zb;yZh9Ba-wIilx8>$tXIvAW09a(8EiQzPP)cvfo?1ox+DX@$Jk9Q~cOY632Mgg*HU28{|`O5lu7~v)wd?00Izz00bZa0SG_<0uX>eu>k)67yBR=0uX=z1Rwwb2tWV= z5P$##Ag~Mq`2W8QQN$b|009U<00Izz00bZa0SG_<0>uLO|6lBbTnIn_0uX=z1Rwwb z2tWV=5P-lk2;l$!GDHz`fB*y_009U<00Izz00bZa0SFWe;QPPW2e}Y{00bZa0SG_< P0uX=z1Rwx`Wf1rWc+s9; diff --git a/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-shm b/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-shm deleted file mode 100644 index c500b15117c306fc472a17b65e3fe21cc995647e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI)b#N3(6bA6``*3#>Jh&#fySux)yE_DTcM0we!QI{6-Q8Vo_qY@(j;n*Zf&%Wn zs;TYm>6z*I^_$t5Kek;jyTcj99Sebwj|BvBl)VOd^m+2|+~KpmJtJ0&QvY^A?;}0d z-VE;M`i?)OXRiA;wDq4n%ZfXyZkM=VfU85KX?0>e2b&w z-Qmyo2FHhm37CKhn1BhGfC-p@37CKhn1BhGfC-p@37CKhn1BhGfC-p@37CKhn1BhG zfC-p@37CKhn1BhGfC-p@37CKhn1BhGfC-p@37CKhn1BiVM}bGqzq(nN!2eQ!ju{66 zATWX>Btjz`A|NuNAtquWE)pUU5+gZMAT`n>12Q8!av~23pdgB%1WKX|Dxe~&payE9 z4jP~#nxF+*qAl8?Bf6j~dY}*bVgQCf~2XnCiOW=*=SdBGUkImSM z9oU1tIDjKKiW4}4v$%jOxQd&&g?qS<2Y8Ikk}8GLD6KLm zi?S+*@+hwgsECTHgvzL_DyWL8s)p*Qt{SL`nyQ7`sI5B4Q(e?uz12tkHAq7>Qlm6h z6Esm%G($5rM+>x2OSD`ov|8)6L7TN*JG5K-bwG!8Tqkr|=XF6>bX7NXOLyg*zvuS~ zAs~Vv48kJ{+z=b_;Ep6ni8RQFEXaktD1@RYg|euGs;Gs!XoRL{h4$!#Zs>`A7>HpQ ziE)^KX_$$5Scs)qfwkCxZP3R%yRbX^us?@zILB~2CvzHSa~>CS zDOYhVH*qU>aW4bmK^0R;l~YAkQ%%)VLp4)NwNpoRRS)&m01ehKjn+6#(p1gTTrHBfR%(qlYKwMi zj}GdHPU?&<>Y8rr-fx#bzxLJO2mTQhArJ~Kh=@ptj`&E5w8)Aa$cJJmhia&YW@wGh z=!HQTg^8GgSy+UX*oZCIg+n-nOSq0Zc!oFd!ypX9@N{7m#-=+{G9z=cAWO0$YqB9* zvLkzN07uY^(>b5ZxQ^SnkH>h9*LaW5_>TS*OyT6JsEQ*GrBWv4QX!R6CDl?RwNfYb z)Nqa0G|kget<_fT)lr?*C0*Bj%dcNMA%AG^{W|8V2!^n5MO4H<93+4Tk|7n+ArrD8 zH}a#fV@ABvD38jhj@qb?#%PW<=m1Z2M{o4UU<}7-jK?HQ$85~UVl2ZdtivX3$8PM$ zVI0S4oX2I{z+F7TQ@q4m#}s>h8IVC4l3^K8op%N>(QY*bOE4y+lzY43kN~^potGa5dz8b5! zTC2S}tDAbMp9X4(Mre$@G+EO%Tl2M8%d|@Cv`O2vOZ#+4$8<{PbXhlaM-S}U{Gr)x z2QvW^FaZ-V0TVC*6Yvpm&TegC0w!PrCSU?4U;-v!0w!PrCSU?4U;-v!0w!PrCSU?4 bU;-v!0w!PrCSU?4U;-v!0w!PrzlXpd#Hg!Y diff --git a/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-wal b/tari-win-bundler/.vs/tari-win-bundler/CopilotIndices/0.2.1657.32929/SemanticSymbols.db-wal deleted file mode 100644 index 1767c6327525650ba6b587593abad8fc1218daa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3106512 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 diff --git a/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/27bb9716-bb51-4a01-86eb-af3758e05a42.vsidx b/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/27bb9716-bb51-4a01-86eb-af3758e05a42.vsidx deleted file mode 100644 index 70aef67a00f5a48bfb0d87044cf8f151e2685b5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 107 wcmZ>EaTnxZU~p%E02V0C38Z0cW+XOHDO{Wlsuo0n)bd05(ok9*N*hCI0FV0t;Q#;t diff --git a/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/37762544-a741-4cc4-8cec-b7bd207a3ea2.vsidx b/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/37762544-a741-4cc4-8cec-b7bd207a3ea2.vsidx deleted file mode 100644 index 70aef67a00f5a48bfb0d87044cf8f151e2685b5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 107 wcmZ>EaTnxZU~p%E02V0C38Z0cW+XOHDO{Wlsuo0n)bd05(ok9*N*hCI0FV0t;Q#;t diff --git a/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/9acf0f2f-d3dc-486a-9893-c58763540b08.vsidx b/tari-win-bundler/.vs/tari-win-bundler/FileContentIndex/9acf0f2f-d3dc-486a-9893-c58763540b08.vsidx deleted file mode 100644 index adb1a05badc98e17cc331a2b38209ed57e2b0836..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14076 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 diff --git a/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.backup.json b/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.backup.json deleted file mode 100644 index 0b662f676..000000000 --- a/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.backup.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "Version": 1, - "WorkspaceRootPath": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\", - "Documents": [ - { - "AbsoluteMoniker": "D:0:0:{4B155CEE-1847-47DC-94A3-8AE15BAFDA2C}|tari-win-bundler.wixproj|C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs||{FA3CD31E-987B-443A-9B81-186104E8DAC1}", - "RelativeMoniker": "D:0:0:{4B155CEE-1847-47DC-94A3-8AE15BAFDA2C}|tari-win-bundler.wixproj|solutionrelative:Bundle.wxs||{FA3CD31E-987B-443A-9B81-186104E8DAC1}" - } - ], - "DocumentGroupContainers": [ - { - "Orientation": 0, - "VerticalTabListWidth": 256, - "DocumentGroups": [ - { - "DockedWidth": 200, - "SelectedChildIndex": 1, - "Children": [ - { - "$type": "Bookmark", - "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" - }, - { - "$type": "Document", - "DocumentIndex": 0, - "Title": "Bundle.wxs", - "DocumentMoniker": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs", - "RelativeDocumentMoniker": "Bundle.wxs", - "ToolTip": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs", - "RelativeToolTip": "Bundle.wxs", - "ViewState": "AgIAAAAAAAAAAAAAAAAAABIAAAAYAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|", - "WhenOpened": "2024-10-15T07:50:05.685Z", - "EditorCaption": "" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.json b/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.json deleted file mode 100644 index 235b8b9e2..000000000 --- a/tari-win-bundler/.vs/tari-win-bundler/v17/DocumentLayout.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "Version": 1, - "WorkspaceRootPath": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\", - "Documents": [ - { - "AbsoluteMoniker": "D:0:0:{4B155CEE-1847-47DC-94A3-8AE15BAFDA2C}|tari-win-bundler.wixproj|C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs||{FA3CD31E-987B-443A-9B81-186104E8DAC1}", - "RelativeMoniker": "D:0:0:{4B155CEE-1847-47DC-94A3-8AE15BAFDA2C}|tari-win-bundler.wixproj|solutionrelative:Bundle.wxs||{FA3CD31E-987B-443A-9B81-186104E8DAC1}" - } - ], - "DocumentGroupContainers": [ - { - "Orientation": 0, - "VerticalTabListWidth": 256, - "DocumentGroups": [ - { - "DockedWidth": 200, - "SelectedChildIndex": 1, - "Children": [ - { - "$type": "Bookmark", - "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" - }, - { - "$type": "Document", - "DocumentIndex": 0, - "Title": "Bundle.wxs", - "DocumentMoniker": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs", - "RelativeDocumentMoniker": "Bundle.wxs", - "ToolTip": "C:\\Users\\barto\\source\\repos\\tari-win-bundler\\Bundle.wxs", - "RelativeToolTip": "Bundle.wxs", - "ViewState": "AgIAAAQAAAAAAAAAAAAmwBIAAAAYAAAAAAAAAA==", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|", - "WhenOpened": "2024-10-15T07:50:05.685Z", - "EditorCaption": "" - } - ] - } - ] - } - ] -} \ No newline at end of file From 2a5a42690fccc28291e7640ee893eed50eac7ba8 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Tue, 22 Oct 2024 10:19:30 +0200 Subject: [PATCH 03/21] cargo fmt, cleanup --- src-tauri/src/binaries/binaries_manager.rs | 9 ++++++--- src-tauri/src/binaries/binaries_resolver.rs | 3 +-- src-tauri/src/main.rs | 9 ++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index afa2b1ce9..5a7420d58 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -72,8 +72,10 @@ impl BinaryManager { self.binary_subfolder.as_ref() } - - fn create_file_with_cached_releases(&self, data: Vec) -> Result<(), Error> { + fn create_file_with_cached_releases( + &self, + data: Vec, + ) -> Result<(), Error> { info!(target: LOG_TARGET, "Creating file with cached releases"); if self.releases_cache_id.is_none() { return Err(anyhow!("No cache id provided")); @@ -95,7 +97,8 @@ impl BinaryManager { } let binary_folder = self.adapter.get_binary_folder().ok(); - let cache_file = binary_folder.map(|path| path.join(self.releases_cache_id.as_ref().unwrap())); + let cache_file = + binary_folder.map(|path| path.join(self.releases_cache_id.as_ref().unwrap())); cache_file.map_or(false, |path| path.exists()) } diff --git a/src-tauri/src/binaries/binaries_resolver.rs b/src-tauri/src/binaries/binaries_resolver.rs index 26b79349c..78503059c 100644 --- a/src-tauri/src/binaries/binaries_resolver.rs +++ b/src-tauri/src/binaries/binaries_resolver.rs @@ -174,7 +174,7 @@ impl BinaryResolver { Box::new(TorReleaseAdapter {}), None, true, - Some("tor".to_string()) , + Some("tor".to_string()), ), ); @@ -269,7 +269,6 @@ impl BinaryResolver { Ok(()) } - pub async fn remove_all_caches(&mut self) -> Result<(), Error> { for manager in self.managers.values_mut() { manager.remove_cached_releases()?; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a77460e85..f47418f6d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -570,11 +570,10 @@ async fn setup_inner( .await?; let mut binary_resolver = BinaryResolver::current().write().await; - // let should_check_for_update = now - // .duration_since(last_binaries_update_timestamp) - // .unwrap_or(Duration::from_secs(0)) - // > Duration::from_secs(60 * 60 * 6); - let should_check_for_update = true; + let should_check_for_update = now + .duration_since(last_binaries_update_timestamp) + .unwrap_or(Duration::from_secs(0)) + > Duration::from_secs(60 * 60 * 6); if use_tor && cfg!(target_os = "windows") { progress.set_max(5).await; From 7bbb8285c308019bdd6174e2ab5d41942004c5ac Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Wed, 23 Oct 2024 09:51:52 +0200 Subject: [PATCH 04/21] fix lint --- src-tauri/src/tor_adapter.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src-tauri/src/tor_adapter.rs b/src-tauri/src/tor_adapter.rs index ce3d76105..98fbf008b 100644 --- a/src-tauri/src/tor_adapter.rs +++ b/src-tauri/src/tor_adapter.rs @@ -6,7 +6,6 @@ use log::{debug, info}; use serde::{Deserialize, Serialize}; use tari_shutdown::Shutdown; use tokio::fs; -use tor_hash_passwd::EncryptedKey; use crate::{ process_adapter::{ @@ -19,7 +18,6 @@ const LOG_TARGET: &str = "tari::universe::tor_adapter"; pub(crate) struct TorAdapter { socks_port: u16, - password: String, config_file: Option, config: TorConfig, } @@ -27,11 +25,9 @@ pub(crate) struct TorAdapter { impl TorAdapter { pub fn new() -> Self { let socks_port = 9050; - let password = "tari is the best".to_string(); Self { socks_port, - password, config_file: None, config: TorConfig::default(), } From 27178a5b481d4290c6ad95951342391c7ff55193 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Thu, 24 Oct 2024 14:14:59 +0200 Subject: [PATCH 05/21] save --- src-tauri/src/github/cache.rs | 127 +++++++++++++++++++++++++ src-tauri/src/github/mod.rs | 120 +++++++++++++++++++---- src-tauri/src/github/request_client.rs | 111 +++++++++++++++++++++ 3 files changed, 341 insertions(+), 17 deletions(-) create mode 100644 src-tauri/src/github/cache.rs create mode 100644 src-tauri/src/github/request_client.rs diff --git a/src-tauri/src/github/cache.rs b/src-tauri/src/github/cache.rs new file mode 100644 index 000000000..0e8c2d3d4 --- /dev/null +++ b/src-tauri/src/github/cache.rs @@ -0,0 +1,127 @@ +use std::{collections::HashMap, path::PathBuf, sync::LazyLock}; + +use serde::{Deserialize, Serialize}; +use anyhow::{anyhow, Error, Ok}; +use tauri::api::path::cache_dir; +use tokio::sync::RwLock; +use crate::{binaries::binaries_resolver::VersionDownloadInfo, APPLICATION_FOLDER_ID}; + +const LOG_TARGET: &str = "tari::universe::github_cache"; + +static INSTANCE: LazyLock> = LazyLock::new(|| RwLock::new(CacheJsonFile::new())); + +#[derive(Debug, Serialize, Deserialize)] +pub struct CacheEntry { + pub repo_owner: String, + pub repo_name: String, + pub github_etag: Option, + pub mirror_etag: Option, + pub file_path: PathBuf, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CacheJsonFile { + pub cache_entries: HashMap, + pub cache_file_path: PathBuf, +} + +impl CacheJsonFile { + fn new() -> Self { + let cache_file_path = PathBuf::new().join(APPLICATION_FOLDER_ID).join("cache").join("binaries_versions").join("versions_releases_responses.json"); + + Self { + cache_entries: HashMap::new(), + cache_file_path, + } + } + + fn create_cache_entry_identifier(repo_owner: &str, repo_name: &str) -> String { + format!("{}-{}", repo_owner, repo_name) + } + + fn get_version_releases_responses_cache_file_path(&self) -> Result { + let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get cache directory"))?; + Ok(cache_path.join(self.cache_file_path.clone())) + } + + pub fn read_version_releases_responses_cache_file(&mut self) -> Result<(), Error> { + let cache_file_path = self.get_version_releases_responses_cache_file_path()?; + if !cache_file_path.exists() { + std::fs::write(&cache_file_path, "{}")?; + } + let json = std::fs::read_to_string(&cache_file_path)?; + self.cache_entries = serde_json::from_str(&json)?; + Ok(()) + } + + fn save_version_releases_responses_cache_file(&self) -> Result<(), Error> { + let cache_file_path = self.get_version_releases_responses_cache_file_path()?; + let json = serde_json::to_string_pretty(&self.cache_entries)?; + std::fs::write(&cache_file_path, json)?; + Ok(()) + } + + pub fn get_cache_entry(&self, repo_owner: &str, repo_name: &str) -> Option<&CacheEntry> { + self.cache_entries.get(&Self::create_cache_entry_identifier(repo_owner, repo_name)) + } + + + pub fn update_cache_entry(&mut self, repo_owner: &str, repo_name: &str, github_etag: Option, mirror_etag: Option, file_path: PathBuf) -> Result<(), Error> { + let cache_entry = self.cache_entries.get_mut(&Self::create_cache_entry_identifier(repo_owner, repo_name)).ok_or_else(|| anyhow!("Cache entry not found"))?; + cache_entry.github_etag = github_etag; + cache_entry.mirror_etag = mirror_etag; + cache_entry.file_path = file_path; + self.save_version_releases_responses_cache_file()?; + Ok(()) + } + + pub fn create_cache_entry(&mut self, repo_owner: &str, repo_name: &str, file_path: PathBuf, github_etag: Option, mirror_etag: Option) -> Result<(), Error> { + let is_cache_entry_exists = self.cache_entries.contains_key(&Self::create_cache_entry_identifier(repo_owner, repo_name)); + + if is_cache_entry_exists { + self.update_cache_entry(repo_owner, repo_name, github_etag, mirror_etag, file_path)?; + } else { + let cache_entry = CacheEntry { + repo_owner: repo_owner.to_string(), + repo_name: repo_name.to_string(), + github_etag, + mirror_etag, + file_path, + }; + self.cache_entries.insert(Self::create_cache_entry_identifier(repo_owner, repo_name), cache_entry); + self.save_version_releases_responses_cache_file()?; + }; + + Ok(()) + } + + pub fn chech_if_content_file_exist(&self, repo_owner: &str, repo_name: &str) -> bool{ + let cache_entry = self.get_cache_entry(repo_owner, repo_name); + match cache_entry { + Some(cache_entry) => cache_entry.file_path.exists(), + None => false, + } + } + + pub fn save_file_content(&self, repo_owner: &str, repo_name: &str, content: Vec) -> Result<(), Error> { + let cache_entry = self.get_cache_entry(repo_owner, repo_name).ok_or_else(|| anyhow!("Cache entry not found"))?; + let file_path = cache_entry.file_path.clone(); + let json = serde_json::to_string_pretty(&content)?; + std::fs::write(&file_path, json)?; + Ok(()) + } + + + pub fn get_file_content(&self, repo_owner: &str, repo_name: &str) -> Result, Error> { + let cache_entry = self.get_cache_entry(repo_owner, repo_name).ok_or_else(|| anyhow!("Cache entry not found"))?; + let file_path = cache_entry.file_path.clone(); + let json = std::fs::read_to_string(&file_path)?; + let content: Vec = serde_json::from_str(&json)?; + Ok(content) + } + + pub fn current() -> &'static RwLock { + &INSTANCE + } + +} \ No newline at end of file diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 9309ce779..71f63e268 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -1,9 +1,16 @@ +mod cache; +mod request_client; + +use std::path::{Path, PathBuf}; + +use request_client::{ CloudFlareCacheStatus, RequestClient }; +use cache::CacheJsonFile; use anyhow::anyhow; use log::{debug, info, warn}; use reqwest::Client; use serde::Deserialize; -use crate::binaries::binaries_resolver::{VersionAsset, VersionDownloadInfo}; +use crate::{binaries::binaries_resolver::{VersionAsset, VersionDownloadInfo}, APPLICATION_FOLDER_ID}; const LOG_TARGET: &str = "tari::universe::github"; @@ -21,7 +28,7 @@ struct Asset { browser_download_url: String, } -#[derive(Debug)] +#[derive(Debug,Clone)] enum ReleaseSource { Github, Mirror, @@ -59,6 +66,9 @@ pub async fn list_releases( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { + + CacheJsonFile::current().write().await.read_version_releases_responses_cache_file()?; + let mut attempts = 0; let mut releases = loop { match list_releases_from(ReleaseSource::Mirror, repo_owner, repo_name).await { @@ -101,27 +111,101 @@ async fn list_releases_from( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { + let cache_json_file_lock = CacheJsonFile::current().write().await; + let client = Client::new(); - let url = match source { + let url: String = match source { ReleaseSource::Github => get_gh_url(repo_owner, repo_name), ReleaseSource::Mirror => get_mirror_url(repo_owner, repo_name), }; - let response = client - .get(&url) - .header("User-Agent", "request") - .send() - .await?; - if response.status() != 200 { - return Err(anyhow!( - "Failed to fetch releases for {}:{}: {} - ", - repo_owner, - repo_name, - response.status() - )); + let cache_entry = cache_json_file_lock.get_cache_entry(repo_owner, repo_name); + let was_content_downloaded = false; + + let (releases, etag) = match cache_entry { + Some( cache_entry ) => { + let remote_etag = RequestClient::current().fetch_head_etag(&url).await?; + let local_etag = match source { + ReleaseSource::Mirror => cache_entry.mirror_etag.clone(), + ReleaseSource::Github => cache_entry.github_etag.clone(), + }; + + if remote_etag.eq(&local_etag.unwrap_or("".to_string())) { + match cache_json_file_lock.get_file_content(repo_owner, repo_name) { + Ok(content) => (content, remote_etag), + Err(e) => { + was_content_downloaded = true; + RequestClient::current().fetch_get_versions_download_info(&url).await? + } + } + } else { + was_content_downloaded = true; + RequestClient::current().fetch_get_versions_download_info(&url).await? + } + }, + None => { + was_content_downloaded = true; + RequestClient::current().fetch_get_versions_download_info(&url).await? + } + }; + + + if was_content_downloaded { + cache_json_file_lock.save_file_content(repo_owner, repo_name, releases.clone())?; } - let data = response.text().await?; - let releases: Vec = serde_json::from_str(&data)?; + + + // let head_response = client + // .head(&url) + // .header("User-Agent", "request") + // .send() + // .await?; + + // let response_etag = head_response + // .headers() + // .get("etag") + // .map(|v| v.to_str().unwrap_or_default()) + // .unwrap_or_default(); + + + // let is_cache_valid = entry.map_or(false, |e| e.e_tag == response_etag); + + // info!( + // target: LOG_TARGET, + // "Cache for {}/{} is valid: {}", + // repo_owner, + // repo_name, + // is_cache_valid + // ); + + // let releases = if is_cache_valid { + // // return stored content + // vec![] + // } else { + // // fetch new content + // let response = client + // .get(&url) + // .header("User-Agent", "request") + // .send() + // .await?; + + + // if response.status() != 200 { + // return Err(anyhow!( + // "Failed to fetch releases for {}:{}: {} - ", + // repo_owner, + // repo_name, + // response.status() + // )); + // } + // let data = response.text().await?; + // let releases: Vec = serde_json::from_str(&data)?; + + // releases + // }; + + + // CacheJsonFile::current().write().await.set_entry(repo_owner, repo_name, source.clone(), response_etag.to_string())?; debug!(target: LOG_TARGET, "Releases for {}/{}:", repo_owner, repo_name); let mut res = vec![]; @@ -162,5 +246,7 @@ async fn list_releases_from( } } + // save res to cache + Ok(res) } diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs new file mode 100644 index 000000000..eddf779b7 --- /dev/null +++ b/src-tauri/src/github/request_client.rs @@ -0,0 +1,111 @@ +use std::sync::LazyLock; + +use reqwest::{ Client, Response }; +use anyhow::anyhow; + +use crate::binaries::binaries_resolver::VersionDownloadInfo; +const LOG_TARGET: &str = "tari::universe::request_client"; + + +pub enum CloudFlareCacheStatus { + Hit, + Miss, + Unknown, + Expired, + Stale, + Bypass, + Revalidated, + Updating, + Dynamic +} + +impl CloudFlareCacheStatus { + pub fn from_str(s: &str) -> Self { + match s { + "HIT" => Self::Hit, + "MISS" => Self::Miss, + "EXPIRED" => Self::Expired, + "STALE" => Self::Stale, + "BYPASS" => Self::Bypass, + "REVALIDATED" => Self::Revalidated, + "UPDATING" => Self::Updating, + "DYNAMIC" => Self::Dynamic, + "UNKNOWN" => Self::Unknown, + "NONE" => Self::Unknown, + "NONE/UNKNOWN" => Self::Unknown, + _ => Self::Unknown + } + } + pub fn to_str(&self) -> &str { + match self { + Self::Hit => "HIT", + Self::Miss => "MISS", + Self::Unknown => "UNKNOWN", + Self::Expired => "EXPIRED", + Self::Stale => "STALE", + Self::Bypass => "BYPASS", + Self::Revalidated => "REVALIDATED", + Self::Updating => "UPDATING", + Self::Dynamic => "DYNAMIC", + } + } + + pub fn is_hit(&self) -> bool { + matches!(self, Self::Hit) + } + + pub fn is_miss(&self) -> bool { + matches!(self, Self::Miss) + } +} + +static INSTANCE: LazyLock = LazyLock::new(RequestClient::new); +pub struct RequestClient { + client: Client, + user_agent: String, +} + +impl RequestClient { + pub fn new() -> Self { + + let user_agent = format!( + "universe {}({})", + env!("CARGO_PKG_VERSION"), + std::env::consts::OS); + + Self { + client: Client::new(), + user_agent + } + } + + async fn send_head_request(&self, url: &str) -> Result { + self.client.head(url).header("User-Agent", self.user_agent.clone()).send().await + } + + async fn send_get_request(&self, url: &str) -> Result { + self.client.get(url).header("User-Agent", self.user_agent.clone()).send().await + } + + pub async fn fetch_head_etag(&self, url: &str) -> Result { + let head_response = self.send_head_request(url).await.map_err(|e| anyhow!(e))?; + Ok(head_response.headers().get("etag").map_or("", |v| v.to_str().unwrap_or_default()).to_string()) + } + + pub async fn fetch_head_cf_cache_status(&self, url: &str) -> Result { + let head_response = self.send_head_request(url).await.map_err(|e| anyhow!(e))?; + Ok(CloudFlareCacheStatus::from_str(head_response.headers().get("cf-cache-status").map_or("", |v| v.to_str().unwrap_or_default()))) + } + + pub async fn fetch_get_versions_download_info(&self, url: &str) -> Result<(Vec, String), anyhow::Error> { + let get_response = self.send_get_request(url).await.map_err(|e| anyhow!(e))?; + let etag = get_response.headers().get("etag").map_or("", |v| v.to_str().unwrap_or_default()).to_string(); + let body = get_response.text().await.map_err(|e| anyhow!(e))?; + + Ok((serde_json::from_str(&body)?, etag)) + } + + pub fn current() -> &'static LazyLock { + &INSTANCE + } +} \ No newline at end of file From 422df6a851a275cd62f3e48d150d37713c3e9b01 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Thu, 24 Oct 2024 21:16:48 +0200 Subject: [PATCH 06/21] save --- src-tauri/src/binaries/binaries_manager.rs | 5 +- src-tauri/src/binaries/binaries_resolver.rs | 4 +- src-tauri/src/github/cache.rs | 8 +- src-tauri/src/github/mod.rs | 283 ++++++++++---------- src-tauri/src/github/request_client.rs | 27 +- 5 files changed, 172 insertions(+), 155 deletions(-) diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index 5a7420d58..be4815645 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -5,8 +5,7 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use tari_common::configuration::Network; use crate::{ - download_utils::{download_file_with_retries, extract, validate_checksum}, - progress_tracker::ProgressTracker, + download_utils::{download_file_with_retries, extract, validate_checksum}, github::ReleaseSource, progress_tracker::ProgressTracker }; use super::{ @@ -273,7 +272,7 @@ impl BinaryManager { info!(target: LOG_TARGET, "Validating checksum for version: {:?}", version); let version_download_info = VersionDownloadInfo { version: version.clone(), - assets: vec![asset.clone()], + assets: vec![asset.clone()] }; let checksum_file = self .adapter diff --git a/src-tauri/src/binaries/binaries_resolver.rs b/src-tauri/src/binaries/binaries_resolver.rs index 78503059c..3cb25cc7c 100644 --- a/src-tauri/src/binaries/binaries_resolver.rs +++ b/src-tauri/src/binaries/binaries_resolver.rs @@ -1,3 +1,4 @@ +use crate::github::ReleaseSource; use crate::ProgressTracker; use anyhow::{anyhow, Error}; use async_trait::async_trait; @@ -25,13 +26,14 @@ static INSTANCE: LazyLock> = #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionDownloadInfo { pub(crate) version: Version, - pub(crate) assets: Vec, + pub(crate) assets: Vec } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionAsset { pub(crate) url: String, pub(crate) name: String, + pub(crate) source: ReleaseSource } #[async_trait] diff --git a/src-tauri/src/github/cache.rs b/src-tauri/src/github/cache.rs index 0e8c2d3d4..11bd4ee2d 100644 --- a/src-tauri/src/github/cache.rs +++ b/src-tauri/src/github/cache.rs @@ -66,7 +66,7 @@ impl CacheJsonFile { } - pub fn update_cache_entry(&mut self, repo_owner: &str, repo_name: &str, github_etag: Option, mirror_etag: Option, file_path: PathBuf) -> Result<(), Error> { + pub fn update_cache_entry(&mut self, repo_owner: &str, repo_name: &str,file_path: PathBuf, github_etag: Option, mirror_etag: Option ) -> Result<(), Error> { let cache_entry = self.cache_entries.get_mut(&Self::create_cache_entry_identifier(repo_owner, repo_name)).ok_or_else(|| anyhow!("Cache entry not found"))?; cache_entry.github_etag = github_etag; cache_entry.mirror_etag = mirror_etag; @@ -79,7 +79,7 @@ impl CacheJsonFile { let is_cache_entry_exists = self.cache_entries.contains_key(&Self::create_cache_entry_identifier(repo_owner, repo_name)); if is_cache_entry_exists { - self.update_cache_entry(repo_owner, repo_name, github_etag, mirror_etag, file_path)?; + self.update_cache_entry(repo_owner, repo_name, file_path,github_etag, mirror_etag)?; } else { let cache_entry = CacheEntry { repo_owner: repo_owner.to_string(), @@ -103,12 +103,12 @@ impl CacheJsonFile { } } - pub fn save_file_content(&self, repo_owner: &str, repo_name: &str, content: Vec) -> Result<(), Error> { + pub fn save_file_content(&self, repo_owner: &str, repo_name: &str, content: Vec) -> Result { let cache_entry = self.get_cache_entry(repo_owner, repo_name).ok_or_else(|| anyhow!("Cache entry not found"))?; let file_path = cache_entry.file_path.clone(); let json = serde_json::to_string_pretty(&content)?; std::fs::write(&file_path, json)?; - Ok(()) + Ok(file_path) } diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 71f63e268..5b4f52c16 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -1,14 +1,10 @@ mod cache; mod request_client; -use std::path::{Path, PathBuf}; - -use request_client::{ CloudFlareCacheStatus, RequestClient }; +use request_client::RequestClient; use cache::CacheJsonFile; -use anyhow::anyhow; use log::{debug, info, warn}; -use reqwest::Client; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::{binaries::binaries_resolver::{VersionAsset, VersionDownloadInfo}, APPLICATION_FOLDER_ID}; @@ -27,9 +23,8 @@ struct Asset { name: String, browser_download_url: String, } - -#[derive(Debug,Clone)] -enum ReleaseSource { +#[derive(Debug,Clone, Serialize, Deserialize)] +pub enum ReleaseSource { Github, Mirror, } @@ -69,23 +64,14 @@ pub async fn list_releases( CacheJsonFile::current().write().await.read_version_releases_responses_cache_file()?; - let mut attempts = 0; - let mut releases = loop { - match list_releases_from(ReleaseSource::Mirror, repo_owner, repo_name).await { - Ok(r) => break r, - Err(e) => { - warn!(target: LOG_TARGET, "Failed to fetch releases from mirror: {}", e); - } - }; - attempts += 1; - warn!( - target: LOG_TARGET, - "Failed to fetch releases from mirror, attempt {}", - attempts - ); - }; + let mut mirror_releases = list_mirror_releases(repo_owner, repo_name) + .await + .inspect_err(|e| { + warn!(target: LOG_TARGET, "Failed to fetch releases from Github: {}", e); + }) + .unwrap_or_default(); // Add any missing releases from github - let github_releases = list_releases_from(ReleaseSource::Github, repo_owner, repo_name) + let github_releases = list_github_releases(repo_owner, repo_name) .await .inspect_err(|e| { warn!(target: LOG_TARGET, "Failed to fetch releases from Github: {}", e); @@ -93,11 +79,11 @@ pub async fn list_releases( .unwrap_or_default(); for release in &github_releases { - if !releases.iter().any(|r| r.version == release.version) { - releases.push(release.clone()); + if !mirror_releases.iter().any(|r| r.version == release.version) { + mirror_releases.push(release.clone()); } } - Ok(releases) + Ok(mirror_releases) // if releases.as_ref().map_or(false, |r| !r.is_empty()) { // releases @@ -106,147 +92,154 @@ pub async fn list_releases( // } } -async fn list_releases_from( - source: ReleaseSource, +async fn list_mirror_releases( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { - let cache_json_file_lock = CacheJsonFile::current().write().await; + let mut cache_json_file_lock = CacheJsonFile::current().write().await; + let url = get_mirror_url(repo_owner, repo_name); - let client = Client::new(); - let url: String = match source { - ReleaseSource::Github => get_gh_url(repo_owner, repo_name), - ReleaseSource::Mirror => get_mirror_url(repo_owner, repo_name), - }; + let (need_to_download, cache_entry_present) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Mirror).await?; + + let mut versions_list: Vec = vec![]; + let mut does_hit = false; - let cache_entry = cache_json_file_lock.get_cache_entry(repo_owner, repo_name); - let was_content_downloaded = false; + if need_to_download { + does_hit = RequestClient::current().check_if_cache_hits(&url).await?; + } - let (releases, etag) = match cache_entry { - Some( cache_entry ) => { - let remote_etag = RequestClient::current().fetch_head_etag(&url).await?; - let local_etag = match source { - ReleaseSource::Mirror => cache_entry.mirror_etag.clone(), - ReleaseSource::Github => cache_entry.github_etag.clone(), - }; + if does_hit { + let (response, etag) = RequestClient::current().fetch_get_versions_download_info(&url).await?; + let remote_versions_list = extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Mirror).await?; + let content_path = cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; - if remote_etag.eq(&local_etag.unwrap_or("".to_string())) { - match cache_json_file_lock.get_file_content(repo_owner, repo_name) { - Ok(content) => (content, remote_etag), - Err(e) => { - was_content_downloaded = true; - RequestClient::current().fetch_get_versions_download_info(&url).await? - } - } + let args = (repo_owner, repo_name, content_path, Some(etag), None); + if cache_entry_present { + cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3, args.4)?; } else { - was_content_downloaded = true; - RequestClient::current().fetch_get_versions_download_info(&url).await? - } - }, - None => { - was_content_downloaded = true; - RequestClient::current().fetch_get_versions_download_info(&url).await? + cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + }; + + versions_list.extend(remote_versions_list); + }else { + let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; + versions_list.extend(content); } - }; + Ok(versions_list) +} - if was_content_downloaded { - cache_json_file_lock.save_file_content(repo_owner, repo_name, releases.clone())?; - } +async fn list_github_releases( + repo_owner: &str, + repo_name: &str, +) -> Result, anyhow::Error> { + let mut cache_json_file_lock = CacheJsonFile::current().write().await; + let url = get_gh_url(repo_owner, repo_name); + let (need_to_download, cache_entry_present) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Github).await?; - // let head_response = client - // .head(&url) - // .header("User-Agent", "request") - // .send() - // .await?; + let mut versions_list: Vec = vec![]; - // let response_etag = head_response - // .headers() - // .get("etag") - // .map(|v| v.to_str().unwrap_or_default()) - // .unwrap_or_default(); + if need_to_download { + let (response, etag) = RequestClient::current().fetch_get_versions_download_info(&url).await?; + let remote_versions_list = extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Github).await?; + let content_path = cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; - - // let is_cache_valid = entry.map_or(false, |e| e.e_tag == response_etag); - - // info!( - // target: LOG_TARGET, - // "Cache for {}/{} is valid: {}", - // repo_owner, - // repo_name, - // is_cache_valid - // ); - - // let releases = if is_cache_valid { - // // return stored content - // vec![] - // } else { - // // fetch new content - // let response = client - // .get(&url) - // .header("User-Agent", "request") - // .send() - // .await?; - - - // if response.status() != 200 { - // return Err(anyhow!( - // "Failed to fetch releases for {}:{}: {} - ", - // repo_owner, - // repo_name, - // response.status() - // )); - // } - // let data = response.text().await?; - // let releases: Vec = serde_json::from_str(&data)?; + let args = (repo_owner, repo_name, content_path, Some(etag), None); + if cache_entry_present { + cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + } else { + cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + }; - // releases - // }; + versions_list.extend(remote_versions_list); + }else { + let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; + versions_list.extend(content); + } + Ok(versions_list) +} - // CacheJsonFile::current().write().await.set_entry(repo_owner, repo_name, source.clone(), response_etag.to_string())?; +async fn check_if_need_download( + repo_owner: &str, + repo_name: &str, + url: &str, + source: ReleaseSource, +) -> Result<(bool, bool),anyhow::Error> { + let cache_json_file_lock = CacheJsonFile::current().write().await; + let cache_entry = cache_json_file_lock.get_cache_entry(repo_owner, repo_name); + let mut need_to_download = false; + let cache_entry_present = cache_entry.is_some(); + + match cache_entry { + Some( cache_entry ) => { + if !cache_json_file_lock.chech_if_content_file_exist(repo_owner, repo_name) { + need_to_download = true; + } - debug!(target: LOG_TARGET, "Releases for {}/{}:", repo_owner, repo_name); - let mut res = vec![]; - for release in releases { - if release.draft { - continue; - } + let remote_etag = RequestClient::current().fetch_head_etag(&url).await?; + let local_etag = match source { + ReleaseSource::Mirror => cache_entry.mirror_etag.clone(), + ReleaseSource::Github => cache_entry.github_etag.clone(), + }; - if release.name.contains(".old") { - continue; - } - // Remove any v prefix - let release_name = release.tag_name.trim_start_matches('v').to_string(); - debug!(target: LOG_TARGET, " - release: {}", release_name); - // res.push(semver::Version::parse(&tag_name)?); - let mut assets = vec![]; - for asset in release.assets { - let url = match source { - ReleaseSource::Mirror => asset.browser_download_url.replace( - &get_gh_download_url(repo_owner, repo_name), - &get_mirror_download_url(repo_owner, repo_name), - ), - ReleaseSource::Github => asset.browser_download_url, + if !remote_etag.eq(&local_etag.unwrap_or("".to_string())) { + need_to_download = true }; - assets.push(VersionAsset { - url, - name: asset.name, - }); } - match semver::Version::parse(&release_name) { - Ok(v) => { - res.push(VersionDownloadInfo { version: v, assets }); + None => { + need_to_download = true; + } + }; + + Ok((need_to_download,cache_entry_present)) +} + +async fn extract_versions_from_release( + repo_owner: &str, + repo_name: &str, + releases: Vec, + source: ReleaseSource, +) -> Result, anyhow::Error> { + let mut versions_list = vec![]; + for release in releases { + if release.draft { + continue; } - Err(e) => { - info!(target: LOG_TARGET, "Failed to parse {:?} version: {}", release_name, e); + + if release.name.contains(".old") { continue; } + // Remove any v prefix + let release_name = release.tag_name.trim_start_matches('v').to_string(); + debug!(target: LOG_TARGET, " - release: {}", release_name); + // res.push(semver::Version::parse(&tag_name)?); + let mut assets = vec![]; + for asset in release.assets { + let url = match source { + ReleaseSource::Mirror => asset.browser_download_url.replace( + &get_gh_download_url(repo_owner, repo_name), + &get_mirror_download_url(repo_owner, repo_name), + ), + ReleaseSource::Github => asset.browser_download_url, + }; + assets.push(VersionAsset { + url, + name: asset.name, + source: source.clone(), + }); + } + match semver::Version::parse(&release_name) { + Ok(v) => { + versions_list.push(VersionDownloadInfo { version: v, assets}); + } + Err(e) => { + info!(target: LOG_TARGET, "Failed to parse {:?} version: {}", release_name, e); + continue; + } + } } - } - // save res to cache - - Ok(res) -} + Ok(versions_list) +} \ No newline at end of file diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index eddf779b7..72d13ac2e 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -1,9 +1,10 @@ use std::sync::LazyLock; +use log::warn; use reqwest::{ Client, Response }; use anyhow::anyhow; -use crate::binaries::binaries_resolver::VersionDownloadInfo; +use super::Release; const LOG_TARGET: &str = "tari::universe::request_client"; @@ -97,7 +98,7 @@ impl RequestClient { Ok(CloudFlareCacheStatus::from_str(head_response.headers().get("cf-cache-status").map_or("", |v| v.to_str().unwrap_or_default()))) } - pub async fn fetch_get_versions_download_info(&self, url: &str) -> Result<(Vec, String), anyhow::Error> { + pub async fn fetch_get_versions_download_info(&self, url: &str) -> Result<(Vec, String), anyhow::Error> { let get_response = self.send_get_request(url).await.map_err(|e| anyhow!(e))?; let etag = get_response.headers().get("etag").map_or("", |v| v.to_str().unwrap_or_default()).to_string(); let body = get_response.text().await.map_err(|e| anyhow!(e))?; @@ -105,6 +106,28 @@ impl RequestClient { Ok((serde_json::from_str(&body)?, etag)) } + pub async fn check_if_cache_hits(&self, url: &str) -> Result { + const MAX_RETRIES: u8 = 5; + let mut retries = 0; + + loop { + if retries >= MAX_RETRIES { + return Ok(false); + } + + let head_response = self.fetch_head_cf_cache_status(&url).await?; + if head_response.is_hit() { + break; + } + + warn!(target: LOG_TARGET, "Cache miss. Retrying in 3 seconds. Try {}/{}", retries, MAX_RETRIES); + retries += 1; + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + } + + Ok(true) + } + pub fn current() -> &'static LazyLock { &INSTANCE } From def934a0b5cc4df6f465c6600198cad6659c0eb4 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 09:25:06 +0200 Subject: [PATCH 07/21] working implementation --- src-tauri/src/binaries/adapter_github.rs | 10 +- src-tauri/src/binaries/adapter_tor.rs | 22 +- src-tauri/src/binaries/binaries_manager.rs | 34 ++- src-tauri/src/binaries/binaries_resolver.rs | 4 +- src-tauri/src/download_utils.rs | 5 +- src-tauri/src/github/cache.rs | 78 +++++-- src-tauri/src/github/mod.rs | 236 ++++++++++++-------- src-tauri/src/github/request_client.rs | 62 +++-- 8 files changed, 287 insertions(+), 164 deletions(-) diff --git a/src-tauri/src/binaries/adapter_github.rs b/src-tauri/src/binaries/adapter_github.rs index bc7f09079..7020b3f65 100644 --- a/src-tauri/src/binaries/adapter_github.rs +++ b/src-tauri/src/binaries/adapter_github.rs @@ -8,7 +8,9 @@ use tari_common::configuration::Network; use tauri::api::path::cache_dir; use crate::{ - download_utils::download_file_with_retries, github, progress_tracker::ProgressTracker, + download_utils::download_file_with_retries, + github::{self, request_client::RequestClient}, + progress_tracker::ProgressTracker, APPLICATION_FOLDER_ID, }; @@ -41,6 +43,12 @@ impl LatestVersionApiAdapter for GithubReleasesAdapter { .join(format!("{}.sha256", asset.name)); let checksum_url = format!("{}.sha256", asset.url); + if asset.source.is_mirror() { + RequestClient::current() + .check_if_cache_hits(checksum_url.as_str()) + .await?; + } + match download_file_with_retries(&checksum_url, &checksum_path, progress_tracker).await { Ok(_) => Ok(checksum_path), Err(e) => { diff --git a/src-tauri/src/binaries/adapter_tor.rs b/src-tauri/src/binaries/adapter_tor.rs index 0cb8ae751..e248c7486 100644 --- a/src-tauri/src/binaries/adapter_tor.rs +++ b/src-tauri/src/binaries/adapter_tor.rs @@ -2,6 +2,8 @@ use crate::binaries::binaries_resolver::{ LatestVersionApiAdapter, VersionAsset, VersionDownloadInfo, }; use crate::download_utils::download_file_with_retries; +use crate::github::request_client::RequestClient; +use crate::github::ReleaseSource; use crate::progress_tracker::ProgressTracker; use crate::APPLICATION_FOLDER_ID; use anyhow::Error; @@ -22,20 +24,10 @@ impl LatestVersionApiAdapter for TorReleaseAdapter { "https://cdn-universe.tari.com/torbrowser/13.5.7/tor-expert-bundle-{}-13.5.7.tar.gz", platform ); - let mut cdn_responded = false; - - let client = reqwest::Client::new(); - for _ in 0..3 { - let cloned_cdn_tor_bundle_url = cdn_tor_bundle_url.clone(); - let response = client.head(cloned_cdn_tor_bundle_url).send().await; - - if let Ok(resp) = response { - if resp.status().is_success() { - cdn_responded = true; - break; - } - } - } + + let cdn_responded = RequestClient::current() + .check_if_cache_hits(cdn_tor_bundle_url.as_str()) + .await?; if cdn_responded { let version = VersionDownloadInfo { @@ -43,6 +35,7 @@ impl LatestVersionApiAdapter for TorReleaseAdapter { assets: vec![VersionAsset { url: cdn_tor_bundle_url.to_string(), name: format!("tor-expert-bundle-{}-13.5.7.tar.gz", platform), + source: ReleaseSource::Github, }], }; return Ok(vec![version]); @@ -54,6 +47,7 @@ impl LatestVersionApiAdapter for TorReleaseAdapter { assets: vec![VersionAsset { url: format!("https://dist.torproject.org/torbrowser/13.5.7/tor-expert-bundle-{}-13.5.7.tar.gz", platform), name: format!("tor-expert-bundle-{}-13.5.7.tar.gz", platform), + source: ReleaseSource::Github }] }; Ok(vec![version]) diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index be4815645..8902bc6e5 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -5,7 +5,9 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use tari_common::configuration::Network; use crate::{ - download_utils::{download_file_with_retries, extract, validate_checksum}, github::ReleaseSource, progress_tracker::ProgressTracker + download_utils::{download_file, download_file_with_retries, extract, validate_checksum}, + github::{request_client::RequestClient, ReleaseSource}, + progress_tracker::ProgressTracker, }; use super::{ @@ -272,7 +274,7 @@ impl BinaryManager { info!(target: LOG_TARGET, "Validating checksum for version: {:?}", version); let version_download_info = VersionDownloadInfo { version: version.clone(), - assets: vec![asset.clone()] + assets: vec![asset.clone()], }; let checksum_file = self .adapter @@ -488,13 +490,27 @@ impl BinaryManager { .map_err(|e| anyhow!("Error creating in progress folder. Error: {:?}", e))?; let in_progress_file_zip = in_progress_dir.join(asset.name.clone()); - download_file_with_retries( - asset.url.as_str(), - &in_progress_file_zip, - progress_tracker.clone(), - ) - .await - .map_err(|e| anyhow!("Error downloading version: {:?}. Error: {:?}", version, e))?; + if asset.source.is_github() { + download_file_with_retries( + asset.url.as_str(), + &in_progress_file_zip, + progress_tracker.clone(), + ) + .await + .map_err(|e| anyhow!("Error downloading version: {:?}. Error: {:?}", version, e))?; + } + + if asset.source.is_mirror() { + RequestClient::current() + .check_if_cache_hits(asset.url.as_str()) + .await?; + download_file( + asset.url.as_str(), + &in_progress_file_zip, + progress_tracker.clone(), + ) + .await?; + } info!(target: LOG_TARGET, "Downloaded version: {:?}", version); diff --git a/src-tauri/src/binaries/binaries_resolver.rs b/src-tauri/src/binaries/binaries_resolver.rs index 3cb25cc7c..8152ba621 100644 --- a/src-tauri/src/binaries/binaries_resolver.rs +++ b/src-tauri/src/binaries/binaries_resolver.rs @@ -26,14 +26,14 @@ static INSTANCE: LazyLock> = #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionDownloadInfo { pub(crate) version: Version, - pub(crate) assets: Vec + pub(crate) assets: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionAsset { pub(crate) url: String, pub(crate) name: String, - pub(crate) source: ReleaseSource + pub(crate) source: ReleaseSource, } #[async_trait] diff --git a/src-tauri/src/download_utils.rs b/src-tauri/src/download_utils.rs index 6ba21eb5d..bf3bd0a58 100644 --- a/src-tauri/src/download_utils.rs +++ b/src-tauri/src/download_utils.rs @@ -1,3 +1,4 @@ +use crate::github::request_client::RequestClient; use crate::ProgressTracker; use anyhow::{anyhow, Error}; use async_zip::base::read::seek::ZipFileReader; @@ -39,12 +40,12 @@ pub async fn download_file_with_retries( } } -async fn download_file( +pub async fn download_file( url: &str, destination: &Path, progress_tracker: ProgressTracker, ) -> Result<(), anyhow::Error> { - let response = reqwest::get(url).await?; + let response = RequestClient::current().send_get_request(url).await?; // Ensure the directory exists if let Some(parent) = destination.parent() { diff --git a/src-tauri/src/github/cache.rs b/src-tauri/src/github/cache.rs index 11bd4ee2d..acd443190 100644 --- a/src-tauri/src/github/cache.rs +++ b/src-tauri/src/github/cache.rs @@ -1,14 +1,15 @@ use std::{collections::HashMap, path::PathBuf, sync::LazyLock}; -use serde::{Deserialize, Serialize}; +use crate::{binaries::binaries_resolver::VersionDownloadInfo, APPLICATION_FOLDER_ID}; use anyhow::{anyhow, Error, Ok}; +use serde::{Deserialize, Serialize}; use tauri::api::path::cache_dir; use tokio::sync::RwLock; -use crate::{binaries::binaries_resolver::VersionDownloadInfo, APPLICATION_FOLDER_ID}; const LOG_TARGET: &str = "tari::universe::github_cache"; -static INSTANCE: LazyLock> = LazyLock::new(|| RwLock::new(CacheJsonFile::new())); +static INSTANCE: LazyLock> = + LazyLock::new(|| RwLock::new(CacheJsonFile::new())); #[derive(Debug, Serialize, Deserialize)] pub struct CacheEntry { @@ -27,7 +28,11 @@ pub struct CacheJsonFile { impl CacheJsonFile { fn new() -> Self { - let cache_file_path = PathBuf::new().join(APPLICATION_FOLDER_ID).join("cache").join("binaries_versions").join("versions_releases_responses.json"); + let cache_file_path = PathBuf::new() + .join(APPLICATION_FOLDER_ID) + .join("cache") + .join("binaries_versions") + .join("versions_releases_responses.json"); Self { cache_entries: HashMap::new(), @@ -62,12 +67,22 @@ impl CacheJsonFile { } pub fn get_cache_entry(&self, repo_owner: &str, repo_name: &str) -> Option<&CacheEntry> { - self.cache_entries.get(&Self::create_cache_entry_identifier(repo_owner, repo_name)) + self.cache_entries + .get(&Self::create_cache_entry_identifier(repo_owner, repo_name)) } - - pub fn update_cache_entry(&mut self, repo_owner: &str, repo_name: &str,file_path: PathBuf, github_etag: Option, mirror_etag: Option ) -> Result<(), Error> { - let cache_entry = self.cache_entries.get_mut(&Self::create_cache_entry_identifier(repo_owner, repo_name)).ok_or_else(|| anyhow!("Cache entry not found"))?; + pub fn update_cache_entry( + &mut self, + repo_owner: &str, + repo_name: &str, + file_path: PathBuf, + github_etag: Option, + mirror_etag: Option, + ) -> Result<(), Error> { + let cache_entry = self + .cache_entries + .get_mut(&Self::create_cache_entry_identifier(repo_owner, repo_name)) + .ok_or_else(|| anyhow!("Cache entry not found"))?; cache_entry.github_etag = github_etag; cache_entry.mirror_etag = mirror_etag; cache_entry.file_path = file_path; @@ -75,11 +90,20 @@ impl CacheJsonFile { Ok(()) } - pub fn create_cache_entry(&mut self, repo_owner: &str, repo_name: &str, file_path: PathBuf, github_etag: Option, mirror_etag: Option) -> Result<(), Error> { - let is_cache_entry_exists = self.cache_entries.contains_key(&Self::create_cache_entry_identifier(repo_owner, repo_name)); + pub fn create_cache_entry( + &mut self, + repo_owner: &str, + repo_name: &str, + file_path: PathBuf, + github_etag: Option, + mirror_etag: Option, + ) -> Result<(), Error> { + let is_cache_entry_exists = self + .cache_entries + .contains_key(&Self::create_cache_entry_identifier(repo_owner, repo_name)); if is_cache_entry_exists { - self.update_cache_entry(repo_owner, repo_name, file_path,github_etag, mirror_etag)?; + self.update_cache_entry(repo_owner, repo_name, file_path, github_etag, mirror_etag)?; } else { let cache_entry = CacheEntry { repo_owner: repo_owner.to_string(), @@ -88,14 +112,17 @@ impl CacheJsonFile { mirror_etag, file_path, }; - self.cache_entries.insert(Self::create_cache_entry_identifier(repo_owner, repo_name), cache_entry); + self.cache_entries.insert( + Self::create_cache_entry_identifier(repo_owner, repo_name), + cache_entry, + ); self.save_version_releases_responses_cache_file()?; }; Ok(()) } - pub fn chech_if_content_file_exist(&self, repo_owner: &str, repo_name: &str) -> bool{ + pub fn chech_if_content_file_exist(&self, repo_owner: &str, repo_name: &str) -> bool { let cache_entry = self.get_cache_entry(repo_owner, repo_name); match cache_entry { Some(cache_entry) => cache_entry.file_path.exists(), @@ -103,17 +130,29 @@ impl CacheJsonFile { } } - pub fn save_file_content(&self, repo_owner: &str, repo_name: &str, content: Vec) -> Result { - let cache_entry = self.get_cache_entry(repo_owner, repo_name).ok_or_else(|| anyhow!("Cache entry not found"))?; + pub fn save_file_content( + &self, + repo_owner: &str, + repo_name: &str, + content: Vec, + ) -> Result { + let cache_entry = self + .get_cache_entry(repo_owner, repo_name) + .ok_or_else(|| anyhow!("Cache entry not found"))?; let file_path = cache_entry.file_path.clone(); let json = serde_json::to_string_pretty(&content)?; std::fs::write(&file_path, json)?; Ok(file_path) } - - pub fn get_file_content(&self, repo_owner: &str, repo_name: &str) -> Result, Error> { - let cache_entry = self.get_cache_entry(repo_owner, repo_name).ok_or_else(|| anyhow!("Cache entry not found"))?; + pub fn get_file_content( + &self, + repo_owner: &str, + repo_name: &str, + ) -> Result, Error> { + let cache_entry = self + .get_cache_entry(repo_owner, repo_name) + .ok_or_else(|| anyhow!("Cache entry not found"))?; let file_path = cache_entry.file_path.clone(); let json = std::fs::read_to_string(&file_path)?; let content: Vec = serde_json::from_str(&json)?; @@ -123,5 +162,4 @@ impl CacheJsonFile { pub fn current() -> &'static RwLock { &INSTANCE } - -} \ No newline at end of file +} diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 5b4f52c16..2b7df69d0 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -1,12 +1,15 @@ mod cache; -mod request_client; +pub mod request_client; -use request_client::RequestClient; use cache::CacheJsonFile; use log::{debug, info, warn}; +use request_client::RequestClient; use serde::{Deserialize, Serialize}; -use crate::{binaries::binaries_resolver::{VersionAsset, VersionDownloadInfo}, APPLICATION_FOLDER_ID}; +use crate::{ + binaries::binaries_resolver::{VersionAsset, VersionDownloadInfo}, + APPLICATION_FOLDER_ID, +}; const LOG_TARGET: &str = "tari::universe::github"; @@ -23,12 +26,35 @@ struct Asset { name: String, browser_download_url: String, } -#[derive(Debug,Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum ReleaseSource { Github, Mirror, } +impl ReleaseSource { + pub fn to_string(&self) -> String { + match self { + ReleaseSource::Github => "Github".to_string(), + ReleaseSource::Mirror => "Mirror".to_string(), + } + } + + pub fn is_github(&self) -> bool { + match self { + ReleaseSource::Github => true, + _ => false, + } + } + + pub fn is_mirror(&self) -> bool { + match self { + ReleaseSource::Mirror => true, + _ => false, + } + } +} + pub fn get_gh_url(repo_owner: &str, repo_name: &str) -> String { format!( "https://api.github.com/repos/{}/{}/releases", @@ -61,8 +87,10 @@ pub async fn list_releases( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { - - CacheJsonFile::current().write().await.read_version_releases_responses_cache_file()?; + CacheJsonFile::current() + .write() + .await + .read_version_releases_responses_cache_file()?; let mut mirror_releases = list_mirror_releases(repo_owner, repo_name) .await @@ -96,69 +124,81 @@ async fn list_mirror_releases( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { - let mut cache_json_file_lock = CacheJsonFile::current().write().await; - let url = get_mirror_url(repo_owner, repo_name); + let mut cache_json_file_lock = CacheJsonFile::current().write().await; + let url = get_mirror_url(repo_owner, repo_name); - let (need_to_download, cache_entry_present) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Mirror).await?; - - let mut versions_list: Vec = vec![]; - let mut does_hit = false; - - if need_to_download { - does_hit = RequestClient::current().check_if_cache_hits(&url).await?; - } + let (need_to_download, cache_entry_present) = + check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Mirror).await?; - if does_hit { - let (response, etag) = RequestClient::current().fetch_get_versions_download_info(&url).await?; - let remote_versions_list = extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Mirror).await?; - let content_path = cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; + let mut versions_list: Vec = vec![]; + let mut does_hit = false; - let args = (repo_owner, repo_name, content_path, Some(etag), None); - if cache_entry_present { - cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3, args.4)?; - } else { - cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3, args.4)?; - }; + if need_to_download { + does_hit = RequestClient::current().check_if_cache_hits(&url).await?; + } - versions_list.extend(remote_versions_list); - }else { - let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; - versions_list.extend(content); - } + if does_hit { + let (response, etag) = RequestClient::current() + .fetch_get_versions_download_info(&url) + .await?; + let remote_versions_list = + extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Mirror) + .await?; + let content_path = + cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; + + let args = (repo_owner, repo_name, content_path, Some(etag), None); + if cache_entry_present { + cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + } else { + cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + }; + + versions_list.extend(remote_versions_list); + } else { + let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; + versions_list.extend(content); + } - Ok(versions_list) + Ok(versions_list) } async fn list_github_releases( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { - let mut cache_json_file_lock = CacheJsonFile::current().write().await; - let url = get_gh_url(repo_owner, repo_name); - - let (need_to_download, cache_entry_present) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Github).await?; - - let mut versions_list: Vec = vec![]; - - if need_to_download { - let (response, etag) = RequestClient::current().fetch_get_versions_download_info(&url).await?; - let remote_versions_list = extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Github).await?; - let content_path = cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; - - let args = (repo_owner, repo_name, content_path, Some(etag), None); - if cache_entry_present { - cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3, args.4)?; - } else { - cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3, args.4)?; - }; - - versions_list.extend(remote_versions_list); - }else { - let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; - versions_list.extend(content); - } + let mut cache_json_file_lock = CacheJsonFile::current().write().await; + let url = get_gh_url(repo_owner, repo_name); + + let (need_to_download, cache_entry_present) = + check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Github).await?; + + let mut versions_list: Vec = vec![]; + + if need_to_download { + let (response, etag) = RequestClient::current() + .fetch_get_versions_download_info(&url) + .await?; + let remote_versions_list = + extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Github) + .await?; + let content_path = + cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; + + let args = (repo_owner, repo_name, content_path, Some(etag), None); + if cache_entry_present { + cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + } else { + cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + }; + + versions_list.extend(remote_versions_list); + } else { + let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; + versions_list.extend(content); + } - Ok(versions_list) + Ok(versions_list) } async fn check_if_need_download( @@ -166,17 +206,17 @@ async fn check_if_need_download( repo_name: &str, url: &str, source: ReleaseSource, -) -> Result<(bool, bool),anyhow::Error> { +) -> Result<(bool, bool), anyhow::Error> { let cache_json_file_lock = CacheJsonFile::current().write().await; let cache_entry = cache_json_file_lock.get_cache_entry(repo_owner, repo_name); let mut need_to_download = false; let cache_entry_present = cache_entry.is_some(); - + match cache_entry { - Some( cache_entry ) => { + Some(cache_entry) => { if !cache_json_file_lock.chech_if_content_file_exist(repo_owner, repo_name) { need_to_download = true; - } + } let remote_etag = RequestClient::current().fetch_head_etag(&url).await?; let local_etag = match source { @@ -193,7 +233,7 @@ async fn check_if_need_download( } }; - Ok((need_to_download,cache_entry_present)) + Ok((need_to_download, cache_entry_present)) } async fn extract_versions_from_release( @@ -202,44 +242,44 @@ async fn extract_versions_from_release( releases: Vec, source: ReleaseSource, ) -> Result, anyhow::Error> { - let mut versions_list = vec![]; - for release in releases { - if release.draft { - continue; - } + let mut versions_list = vec![]; + for release in releases { + if release.draft { + continue; + } - if release.name.contains(".old") { - continue; - } - // Remove any v prefix - let release_name = release.tag_name.trim_start_matches('v').to_string(); - debug!(target: LOG_TARGET, " - release: {}", release_name); - // res.push(semver::Version::parse(&tag_name)?); - let mut assets = vec![]; - for asset in release.assets { - let url = match source { - ReleaseSource::Mirror => asset.browser_download_url.replace( - &get_gh_download_url(repo_owner, repo_name), - &get_mirror_download_url(repo_owner, repo_name), - ), - ReleaseSource::Github => asset.browser_download_url, - }; - assets.push(VersionAsset { - url, - name: asset.name, - source: source.clone(), - }); + if release.name.contains(".old") { + continue; + } + // Remove any v prefix + let release_name = release.tag_name.trim_start_matches('v').to_string(); + debug!(target: LOG_TARGET, " - release: {}", release_name); + // res.push(semver::Version::parse(&tag_name)?); + let mut assets = vec![]; + for asset in release.assets { + let url = match source { + ReleaseSource::Mirror => asset.browser_download_url.replace( + &get_gh_download_url(repo_owner, repo_name), + &get_mirror_download_url(repo_owner, repo_name), + ), + ReleaseSource::Github => asset.browser_download_url, + }; + assets.push(VersionAsset { + url, + name: asset.name, + source: source.clone(), + }); + } + match semver::Version::parse(&release_name) { + Ok(v) => { + versions_list.push(VersionDownloadInfo { version: v, assets }); } - match semver::Version::parse(&release_name) { - Ok(v) => { - versions_list.push(VersionDownloadInfo { version: v, assets}); - } - Err(e) => { - info!(target: LOG_TARGET, "Failed to parse {:?} version: {}", release_name, e); - continue; - } + Err(e) => { + info!(target: LOG_TARGET, "Failed to parse {:?} version: {}", release_name, e); + continue; } } + } - Ok(versions_list) -} \ No newline at end of file + Ok(versions_list) +} diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index 72d13ac2e..ba2a3e1f4 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -1,13 +1,12 @@ use std::sync::LazyLock; -use log::warn; -use reqwest::{ Client, Response }; use anyhow::anyhow; +use log::warn; +use reqwest::{Client, Response}; use super::Release; const LOG_TARGET: &str = "tari::universe::request_client"; - pub enum CloudFlareCacheStatus { Hit, Miss, @@ -17,7 +16,7 @@ pub enum CloudFlareCacheStatus { Bypass, Revalidated, Updating, - Dynamic + Dynamic, } impl CloudFlareCacheStatus { @@ -34,7 +33,7 @@ impl CloudFlareCacheStatus { "UNKNOWN" => Self::Unknown, "NONE" => Self::Unknown, "NONE/UNKNOWN" => Self::Unknown, - _ => Self::Unknown + _ => Self::Unknown, } } pub fn to_str(&self) -> &str { @@ -68,39 +67,66 @@ pub struct RequestClient { impl RequestClient { pub fn new() -> Self { - let user_agent = format!( "universe {}({})", env!("CARGO_PKG_VERSION"), - std::env::consts::OS); + std::env::consts::OS + ); Self { client: Client::new(), - user_agent + user_agent, } } - async fn send_head_request(&self, url: &str) -> Result { - self.client.head(url).header("User-Agent", self.user_agent.clone()).send().await + pub async fn send_head_request(&self, url: &str) -> Result { + self.client + .head(url) + .header("User-Agent", self.user_agent.clone()) + .send() + .await } - async fn send_get_request(&self, url: &str) -> Result { - self.client.get(url).header("User-Agent", self.user_agent.clone()).send().await + pub async fn send_get_request(&self, url: &str) -> Result { + self.client + .get(url) + .header("User-Agent", self.user_agent.clone()) + .send() + .await } pub async fn fetch_head_etag(&self, url: &str) -> Result { let head_response = self.send_head_request(url).await.map_err(|e| anyhow!(e))?; - Ok(head_response.headers().get("etag").map_or("", |v| v.to_str().unwrap_or_default()).to_string()) + Ok(head_response + .headers() + .get("etag") + .map_or("", |v| v.to_str().unwrap_or_default()) + .to_string()) } - pub async fn fetch_head_cf_cache_status(&self, url: &str) -> Result { + pub async fn fetch_head_cf_cache_status( + &self, + url: &str, + ) -> Result { let head_response = self.send_head_request(url).await.map_err(|e| anyhow!(e))?; - Ok(CloudFlareCacheStatus::from_str(head_response.headers().get("cf-cache-status").map_or("", |v| v.to_str().unwrap_or_default()))) + Ok(CloudFlareCacheStatus::from_str( + head_response + .headers() + .get("cf-cache-status") + .map_or("", |v| v.to_str().unwrap_or_default()), + )) } - pub async fn fetch_get_versions_download_info(&self, url: &str) -> Result<(Vec, String), anyhow::Error> { + pub async fn fetch_get_versions_download_info( + &self, + url: &str, + ) -> Result<(Vec, String), anyhow::Error> { let get_response = self.send_get_request(url).await.map_err(|e| anyhow!(e))?; - let etag = get_response.headers().get("etag").map_or("", |v| v.to_str().unwrap_or_default()).to_string(); + let etag = get_response + .headers() + .get("etag") + .map_or("", |v| v.to_str().unwrap_or_default()) + .to_string(); let body = get_response.text().await.map_err(|e| anyhow!(e))?; Ok((serde_json::from_str(&body)?, etag)) @@ -131,4 +157,4 @@ impl RequestClient { pub fn current() -> &'static LazyLock { &INSTANCE } -} \ No newline at end of file +} From c86bdd43938bc801a1c4f274d10a4cb4f9841a44 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 11:01:44 +0200 Subject: [PATCH 08/21] remove old cache system --- src-tauri/src/binaries/binaries_manager.rs | 114 ++------------------ src-tauri/src/binaries/binaries_resolver.rs | 28 ++--- src-tauri/src/main.rs | 2 - 3 files changed, 17 insertions(+), 127 deletions(-) diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index 8721961e7..299208051 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -5,9 +5,7 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use tari_common::configuration::Network; use crate::{ - download_utils::{download_file, download_file_with_retries, extract, validate_checksum}, - github::{request_client::RequestClient, ReleaseSource}, - progress_tracker::ProgressTracker, + download_utils::{download_file, download_file_with_retries, extract, validate_checksum}, github::{request_client::RequestClient}, progress_tracker::ProgressTracker }; use super::{ @@ -32,8 +30,7 @@ pub(crate) struct BinaryManager { online_versions_list: Vec, local_aviailable_versions_list: Vec, used_version: Option, - adapter: Box, - releases_cache_id: Option, + adapter: Box } impl BinaryManager { @@ -42,8 +39,7 @@ impl BinaryManager { binary_subfolder: Option, adapter: Box, network_prerelease_prefix: Option, - should_validate_checksum: bool, - releases_cache_id: Option, + should_validate_checksum: bool ) -> Self { let versions_requirements_data = match Network::get_current_or_user_setting_or_default() { Network::NextNet => include_str!("../../binaries_versions_nextnet.json"), @@ -64,8 +60,7 @@ impl BinaryManager { online_versions_list: Vec::new(), local_aviailable_versions_list: Vec::new(), used_version: None, - adapter, - releases_cache_id, + adapter } } @@ -73,71 +68,6 @@ impl BinaryManager { self.binary_subfolder.as_ref() } - fn create_file_with_cached_releases( - &self, - data: Vec, - ) -> Result<(), Error> { - info!(target: LOG_TARGET, "Creating file with cached releases"); - if self.releases_cache_id.is_none() { - return Err(anyhow!("No cache id provided")); - } - - let binary_folder = self.adapter.get_binary_folder()?; - let cache_file = binary_folder.join(self.releases_cache_id.as_ref().unwrap()); - - let json_content = serde_json::to_string(&data)?; - std::fs::write(cache_file, json_content)?; - - Ok(()) - } - - fn check_if_cached_releases_exist(&self) -> bool { - info!(target: LOG_TARGET, "Checking if cached releases exist"); - if self.releases_cache_id.is_none() { - return false; - } - - let binary_folder = self.adapter.get_binary_folder().ok(); - let cache_file = - binary_folder.map(|path| path.join(self.releases_cache_id.as_ref().unwrap())); - - cache_file.map_or(false, |path| path.exists()) - } - - fn read_cached_releases(&self) -> Result, Error> { - info!(target: LOG_TARGET, "Reading cached releases"); - if self.releases_cache_id.is_none() { - return Err(anyhow!("No cache id provided")); - } - - let binary_folder = self.adapter.get_binary_folder()?; - let cache_file = binary_folder.join(self.releases_cache_id.as_ref().unwrap()); - - let json_content = std::fs::read_to_string(cache_file)?; - let releases: Vec = serde_json::from_str(&json_content)?; - - for release in &releases { - info!(target: LOG_TARGET, "Cached release: {:?}", release.version); - } - - Ok(releases) - } - - pub fn remove_cached_releases(&self) -> Result<(), Error> { - info!(target: LOG_TARGET, "Removing cached releases"); - if self.releases_cache_id.is_none() { - return Err(anyhow!("No cache id provided")); - } - - let binary_folder = self.adapter.get_binary_folder()?; - let cache_file = binary_folder.join(self.releases_cache_id.as_ref().unwrap()); - - if cache_file.exists() { - std::fs::remove_file(cache_file)?; - } - - Ok(()) - } fn read_version_requirements(binary_name: String, data_str: &str) -> VersionReq { let json_content: BinaryVersionsJsonContent = @@ -299,7 +229,7 @@ impl BinaryManager { info!(target: LOG_TARGET, "Validating checksum for version: {:?}", version); let version_download_info = VersionDownloadInfo { version: version.clone(), - assets: vec![asset.clone()], + assets: vec![asset.clone()] }; let checksum_file = self .adapter @@ -424,26 +354,7 @@ impl BinaryManager { pub async fn check_for_updates(&mut self) { info!(target: LOG_TARGET,"Checking for updates for binary: {:?}", self.binary_name); - let versions_info = match self.releases_cache_id { - Some(_) => { - info!(target: LOG_TARGET, "Reading cached releases"); - if self.check_if_cached_releases_exist() { - self.read_cached_releases().unwrap_or_default() - } else { - let data = self.adapter.fetch_releases_list().await.unwrap_or_default(); - let _ = self.create_file_with_cached_releases(data.clone()); - - data - } - } - None => { - info!(target: LOG_TARGET, "Fetching releases list"); - let data = self.adapter.fetch_releases_list().await.unwrap_or_default(); - let _ = self.create_file_with_cached_releases(data.clone()); - - data - } - }; + let versions_info = self.adapter.fetch_releases_list().await.unwrap_or_default(); info!(target: LOG_TARGET, "Found {:?} versions for binary: {:?}", @@ -526,17 +437,12 @@ impl BinaryManager { } if asset.source.is_mirror() { - RequestClient::current() - .check_if_cache_hits(asset.url.as_str()) - .await?; - download_file( - asset.url.as_str(), - &in_progress_file_zip, - progress_tracker.clone(), - ) - .await?; + RequestClient::current().check_if_cache_hits(asset.url.as_str()).await?; + download_file(asset.url.as_str(), &in_progress_file_zip, progress_tracker.clone()).await?; } + + info!(target: LOG_TARGET, "Downloaded version: {:?}", version); extract(&in_progress_file_zip, &destination_dir) diff --git a/src-tauri/src/binaries/binaries_resolver.rs b/src-tauri/src/binaries/binaries_resolver.rs index 8152ba621..5ffac3790 100644 --- a/src-tauri/src/binaries/binaries_resolver.rs +++ b/src-tauri/src/binaries/binaries_resolver.rs @@ -83,8 +83,7 @@ impl BinaryResolver { None, Box::new(XmrigVersionApiAdapter {}), None, - true, - Some("xmrig".to_string()), + true ), ); @@ -99,8 +98,7 @@ impl BinaryResolver { specific_name: gpuminer_specific_nanme, }), None, - true, - Some("tarigpuminer".to_string()), + true ), ); @@ -115,8 +113,7 @@ impl BinaryResolver { specific_name: None, }), Some(tari_prerelease_prefix.to_string()), - true, - Some("tari-suite".to_string()), + true ), ); @@ -131,8 +128,7 @@ impl BinaryResolver { specific_name: None, }), Some(tari_prerelease_prefix.to_string()), - true, - Some("tari-suite".to_string()), + true ), ); @@ -147,8 +143,7 @@ impl BinaryResolver { specific_name: None, }), Some(tari_prerelease_prefix.to_string()), - true, - Some("tari-suite".to_string()), + true ), ); @@ -163,8 +158,7 @@ impl BinaryResolver { specific_name: None, }), None, - true, - Some("sha-p2pool".to_string()), + true ), ); @@ -175,8 +169,7 @@ impl BinaryResolver { Some("tor".to_string()), Box::new(TorReleaseAdapter {}), None, - true, - Some("tor".to_string()), + true ), ); @@ -271,13 +264,6 @@ impl BinaryResolver { Ok(()) } - pub async fn remove_all_caches(&mut self) -> Result<(), Error> { - for manager in self.managers.values_mut() { - manager.remove_cached_releases()?; - } - Ok(()) - } - pub async fn update_binary( &mut self, binary: Binaries, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 7947d8cbe..f3bd0c64a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -732,8 +732,6 @@ async fn setup_inner( .await?; } - binary_resolver.remove_all_caches().await?; - //drop binary resolver to release the lock drop(binary_resolver); From f018a2af5dab2d196d0caf2e1b26436b4b5918fb Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 11:40:48 +0200 Subject: [PATCH 09/21] Extend cache code behaviours and reduce requests send --- src-tauri/src/github/mod.rs | 24 ++++++++++------ src-tauri/src/github/request_client.rs | 40 +++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 2b7df69d0..fff546598 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -4,6 +4,7 @@ pub mod request_client; use cache::CacheJsonFile; use log::{debug, info, warn}; use request_client::RequestClient; +use reqwest::Response; use serde::{Deserialize, Serialize}; use crate::{ @@ -127,13 +128,17 @@ async fn list_mirror_releases( let mut cache_json_file_lock = CacheJsonFile::current().write().await; let url = get_mirror_url(repo_owner, repo_name); - let (need_to_download, cache_entry_present) = + let (need_to_download, cache_entry_present,response) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Mirror).await?; let mut versions_list: Vec = vec![]; - let mut does_hit = false; + let mut does_hit = response.and_then(|res| { + Some(RequestClient::current().get_cf_cache_status_from_head_response(&res).is_hit()) + }).unwrap_or(false); - if need_to_download { + + + if need_to_download && !does_hit { does_hit = RequestClient::current().check_if_cache_hits(&url).await?; } @@ -170,7 +175,7 @@ async fn list_github_releases( let mut cache_json_file_lock = CacheJsonFile::current().write().await; let url = get_gh_url(repo_owner, repo_name); - let (need_to_download, cache_entry_present) = + let (need_to_download, cache_entry_present,response) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Github).await?; let mut versions_list: Vec = vec![]; @@ -206,7 +211,7 @@ async fn check_if_need_download( repo_name: &str, url: &str, source: ReleaseSource, -) -> Result<(bool, bool), anyhow::Error> { +) -> Result<(bool, bool, Option), anyhow::Error> { let cache_json_file_lock = CacheJsonFile::current().write().await; let cache_entry = cache_json_file_lock.get_cache_entry(repo_owner, repo_name); let mut need_to_download = false; @@ -218,7 +223,8 @@ async fn check_if_need_download( need_to_download = true; } - let remote_etag = RequestClient::current().fetch_head_etag(&url).await?; + let response = RequestClient::current().send_head_request(&url).await?; + let remote_etag = RequestClient::current().get_etag_from_head_response(&response); let local_etag = match source { ReleaseSource::Mirror => cache_entry.mirror_etag.clone(), ReleaseSource::Github => cache_entry.github_etag.clone(), @@ -227,13 +233,15 @@ async fn check_if_need_download( if !remote_etag.eq(&local_etag.unwrap_or("".to_string())) { need_to_download = true }; + + Ok((need_to_download, cache_entry_present,Some(response))) } None => { need_to_download = true; + Ok((need_to_download, cache_entry_present,None)) } - }; + } - Ok((need_to_download, cache_entry_present)) } async fn extract_versions_from_release( diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index ba2a3e1f4..fca688dd1 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -17,6 +17,7 @@ pub enum CloudFlareCacheStatus { Revalidated, Updating, Dynamic, + NonExistent, } impl CloudFlareCacheStatus { @@ -33,6 +34,7 @@ impl CloudFlareCacheStatus { "UNKNOWN" => Self::Unknown, "NONE" => Self::Unknown, "NONE/UNKNOWN" => Self::Unknown, + "" => Self::NonExistent, _ => Self::Unknown, } } @@ -47,16 +49,31 @@ impl CloudFlareCacheStatus { Self::Revalidated => "REVALIDATED", Self::Updating => "UPDATING", Self::Dynamic => "DYNAMIC", + Self::NonExistent => "NONEXISTENT", } } + pub fn is_non_existent(&self) -> bool { + matches!(self, Self::NonExistent) + } + pub fn is_hit(&self) -> bool { - matches!(self, Self::Hit) + matches!(self, Self::Hit) || matches!(self, Self::Revalidated) } pub fn is_miss(&self) -> bool { matches!(self, Self::Miss) } + + pub fn should_log_warning(&self) -> bool { + matches!(self, Self::Unknown) || matches!(self, Self::NonExistent) || matches!(self, Self::Dynamic) || matches!(self, Self::Bypass) + } + + pub fn log_warning(&self) { + if self.should_log_warning() { + warn!(target: LOG_TARGET, "Cloudflare cache status: {}", self.to_str()); + } + } } static INSTANCE: LazyLock = LazyLock::new(RequestClient::new); @@ -95,6 +112,26 @@ impl RequestClient { .await } + pub fn get_etag_from_head_response(&self, response: &Response) -> String { + response + .headers() + .get("etag") + .map_or("", |v| v.to_str().unwrap_or_default()) + .to_string() + } + + pub fn get_cf_cache_status_from_head_response(&self, response: &Response) -> CloudFlareCacheStatus { + let cache_status = CloudFlareCacheStatus::from_str( + response + .headers() + .get("cf-cache-status") + .map_or("", |v| v.to_str().unwrap_or_default()), + ); + + cache_status.log_warning(); + cache_status + } + pub async fn fetch_head_etag(&self, url: &str) -> Result { let head_response = self.send_head_request(url).await.map_err(|e| anyhow!(e))?; Ok(head_response @@ -142,6 +179,7 @@ impl RequestClient { } let head_response = self.fetch_head_cf_cache_status(&url).await?; + head_response.log_warning(); if head_response.is_hit() { break; } From 1730e329b61beabc1c9ed175f4fc9970d975bc54 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 11:58:58 +0200 Subject: [PATCH 10/21] add sleep time based on content length --- src-tauri/src/github/request_client.rs | 38 ++++++++++++++++++++------ 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index fca688dd1..691e87901 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -1,4 +1,4 @@ -use std::sync::LazyLock; +use std::{ops::Div, sync::LazyLock}; use anyhow::anyhow; use log::warn; @@ -69,7 +69,7 @@ impl CloudFlareCacheStatus { matches!(self, Self::Unknown) || matches!(self, Self::NonExistent) || matches!(self, Self::Dynamic) || matches!(self, Self::Bypass) } - pub fn log_warning(&self) { + pub fn log_warning_if_present(&self) { if self.should_log_warning() { warn!(target: LOG_TARGET, "Cloudflare cache status: {}", self.to_str()); } @@ -120,6 +120,13 @@ impl RequestClient { .to_string() } + pub fn get_content_length_from_head_response(&self, response: &Response) -> u64 { + response + .headers() + .get("content-length") + .map_or(0, |v| v.to_str().unwrap_or_default().parse().unwrap_or(0)) + } + pub fn get_cf_cache_status_from_head_response(&self, response: &Response) -> CloudFlareCacheStatus { let cache_status = CloudFlareCacheStatus::from_str( response @@ -128,7 +135,7 @@ impl RequestClient { .map_or("", |v| v.to_str().unwrap_or_default()), ); - cache_status.log_warning(); + cache_status.log_warning_if_present(); cache_status } @@ -171,6 +178,7 @@ impl RequestClient { pub async fn check_if_cache_hits(&self, url: &str) -> Result { const MAX_RETRIES: u8 = 5; + const MAX_WAIT_TIME: u64 = 30; let mut retries = 0; loop { @@ -178,20 +186,34 @@ impl RequestClient { return Ok(false); } - let head_response = self.fetch_head_cf_cache_status(&url).await?; - head_response.log_warning(); - if head_response.is_hit() { + let head_response = self.send_head_request(&url).await?; + + let cf_cache_status = self.get_cf_cache_status_from_head_response(&head_response); + cf_cache_status.log_warning_if_present(); + + let content_length = self.get_content_length_from_head_response(&head_response); + + let sleep_time = std::time::Duration::from_secs((self.convert_content_length_to_mb(content_length).div(10.0) as u64).max(MAX_WAIT_TIME)); + + + + if cf_cache_status.is_hit() { break; } - warn!(target: LOG_TARGET, "Cache miss. Retrying in 3 seconds. Try {}/{}", retries, MAX_RETRIES); retries += 1; - tokio::time::sleep(std::time::Duration::from_secs(3)).await; + warn!(target: LOG_TARGET, "Cache miss. Retrying in {} seconds. Try {}/{}", sleep_time.as_secs().to_string() ,retries, MAX_RETRIES); + tokio::time::sleep(sleep_time).await; } Ok(true) } + + fn convert_content_length_to_mb(&self, content_length: u64) -> f64 { + (content_length as f64) / 1024.0 / 1024.0 + } + pub fn current() -> &'static LazyLock { &INSTANCE } From 2c74fdf59f18e130972ef93c2aec64c7cc760ddc Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 12:24:04 +0200 Subject: [PATCH 11/21] include http status handling --- src-tauri/src/github/mod.rs | 2 +- src-tauri/src/github/request_client.rs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index fff546598..438c5dd99 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -15,7 +15,7 @@ use crate::{ const LOG_TARGET: &str = "tari::universe::github"; #[derive(Deserialize)] -struct Release { +pub struct Release { name: String, tag_name: String, draft: bool, diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index 691e87901..3dd46f7a2 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -113,6 +113,9 @@ impl RequestClient { } pub fn get_etag_from_head_response(&self, response: &Response) -> String { + if response.status().is_server_error() || response.status().is_client_error() { + return "".to_string(); + }; response .headers() .get("etag") @@ -121,6 +124,9 @@ impl RequestClient { } pub fn get_content_length_from_head_response(&self, response: &Response) -> u64 { + if response.status().is_server_error() || response.status().is_client_error() { + return 0; + }; response .headers() .get("content-length") @@ -128,6 +134,9 @@ impl RequestClient { } pub fn get_cf_cache_status_from_head_response(&self, response: &Response) -> CloudFlareCacheStatus { + if response.status().is_server_error() || response.status().is_client_error() { + return CloudFlareCacheStatus::Unknown; + }; let cache_status = CloudFlareCacheStatus::from_str( response .headers() @@ -179,6 +188,7 @@ impl RequestClient { pub async fn check_if_cache_hits(&self, url: &str) -> Result { const MAX_RETRIES: u8 = 5; const MAX_WAIT_TIME: u64 = 30; + const DEFAULT_WAIT_TIME: u64 = 5; let mut retries = 0; loop { @@ -193,9 +203,11 @@ impl RequestClient { let content_length = self.get_content_length_from_head_response(&head_response); - let sleep_time = std::time::Duration::from_secs((self.convert_content_length_to_mb(content_length).div(10.0) as u64).max(MAX_WAIT_TIME)); + let mut sleep_time = std::time::Duration::from_secs(DEFAULT_WAIT_TIME); - + if !content_length.eq(&0) { + sleep_time = std::time::Duration::from_secs((self.convert_content_length_to_mb(content_length).div(10.0) as u64).max(MAX_WAIT_TIME)); + } if cf_cache_status.is_hit() { break; From d3e1c8c6eb30bd45a429464d7246607cfce46cdc Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 12:24:20 +0200 Subject: [PATCH 12/21] cargo fmt --- src-tauri/src/binaries/binaries_manager.rs | 26 +++++++++++++-------- src-tauri/src/binaries/binaries_resolver.rs | 14 +++++------ src-tauri/src/github/mod.rs | 23 ++++++++++-------- src-tauri/src/github/request_client.rs | 18 ++++++++++---- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index 299208051..9f7578d17 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -5,7 +5,9 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use tari_common::configuration::Network; use crate::{ - download_utils::{download_file, download_file_with_retries, extract, validate_checksum}, github::{request_client::RequestClient}, progress_tracker::ProgressTracker + download_utils::{download_file, download_file_with_retries, extract, validate_checksum}, + github::request_client::RequestClient, + progress_tracker::ProgressTracker, }; use super::{ @@ -30,7 +32,7 @@ pub(crate) struct BinaryManager { online_versions_list: Vec, local_aviailable_versions_list: Vec, used_version: Option, - adapter: Box + adapter: Box, } impl BinaryManager { @@ -39,7 +41,7 @@ impl BinaryManager { binary_subfolder: Option, adapter: Box, network_prerelease_prefix: Option, - should_validate_checksum: bool + should_validate_checksum: bool, ) -> Self { let versions_requirements_data = match Network::get_current_or_user_setting_or_default() { Network::NextNet => include_str!("../../binaries_versions_nextnet.json"), @@ -60,7 +62,7 @@ impl BinaryManager { online_versions_list: Vec::new(), local_aviailable_versions_list: Vec::new(), used_version: None, - adapter + adapter, } } @@ -68,7 +70,6 @@ impl BinaryManager { self.binary_subfolder.as_ref() } - fn read_version_requirements(binary_name: String, data_str: &str) -> VersionReq { let json_content: BinaryVersionsJsonContent = serde_json::from_str(data_str).unwrap_or_default(); @@ -229,7 +230,7 @@ impl BinaryManager { info!(target: LOG_TARGET, "Validating checksum for version: {:?}", version); let version_download_info = VersionDownloadInfo { version: version.clone(), - assets: vec![asset.clone()] + assets: vec![asset.clone()], }; let checksum_file = self .adapter @@ -437,12 +438,17 @@ impl BinaryManager { } if asset.source.is_mirror() { - RequestClient::current().check_if_cache_hits(asset.url.as_str()).await?; - download_file(asset.url.as_str(), &in_progress_file_zip, progress_tracker.clone()).await?; + RequestClient::current() + .check_if_cache_hits(asset.url.as_str()) + .await?; + download_file( + asset.url.as_str(), + &in_progress_file_zip, + progress_tracker.clone(), + ) + .await?; } - - info!(target: LOG_TARGET, "Downloaded version: {:?}", version); extract(&in_progress_file_zip, &destination_dir) diff --git a/src-tauri/src/binaries/binaries_resolver.rs b/src-tauri/src/binaries/binaries_resolver.rs index 5ffac3790..e43b8c0c6 100644 --- a/src-tauri/src/binaries/binaries_resolver.rs +++ b/src-tauri/src/binaries/binaries_resolver.rs @@ -83,7 +83,7 @@ impl BinaryResolver { None, Box::new(XmrigVersionApiAdapter {}), None, - true + true, ), ); @@ -98,7 +98,7 @@ impl BinaryResolver { specific_name: gpuminer_specific_nanme, }), None, - true + true, ), ); @@ -113,7 +113,7 @@ impl BinaryResolver { specific_name: None, }), Some(tari_prerelease_prefix.to_string()), - true + true, ), ); @@ -128,7 +128,7 @@ impl BinaryResolver { specific_name: None, }), Some(tari_prerelease_prefix.to_string()), - true + true, ), ); @@ -143,7 +143,7 @@ impl BinaryResolver { specific_name: None, }), Some(tari_prerelease_prefix.to_string()), - true + true, ), ); @@ -158,7 +158,7 @@ impl BinaryResolver { specific_name: None, }), None, - true + true, ), ); @@ -169,7 +169,7 @@ impl BinaryResolver { Some("tor".to_string()), Box::new(TorReleaseAdapter {}), None, - true + true, ), ); diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 438c5dd99..a221e8c3b 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -128,15 +128,19 @@ async fn list_mirror_releases( let mut cache_json_file_lock = CacheJsonFile::current().write().await; let url = get_mirror_url(repo_owner, repo_name); - let (need_to_download, cache_entry_present,response) = + let (need_to_download, cache_entry_present, response) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Mirror).await?; let mut versions_list: Vec = vec![]; - let mut does_hit = response.and_then(|res| { - Some(RequestClient::current().get_cf_cache_status_from_head_response(&res).is_hit()) - }).unwrap_or(false); - - + let mut does_hit = response + .and_then(|res| { + Some( + RequestClient::current() + .get_cf_cache_status_from_head_response(&res) + .is_hit(), + ) + }) + .unwrap_or(false); if need_to_download && !does_hit { does_hit = RequestClient::current().check_if_cache_hits(&url).await?; @@ -175,7 +179,7 @@ async fn list_github_releases( let mut cache_json_file_lock = CacheJsonFile::current().write().await; let url = get_gh_url(repo_owner, repo_name); - let (need_to_download, cache_entry_present,response) = + let (need_to_download, cache_entry_present, response) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Github).await?; let mut versions_list: Vec = vec![]; @@ -234,14 +238,13 @@ async fn check_if_need_download( need_to_download = true }; - Ok((need_to_download, cache_entry_present,Some(response))) + Ok((need_to_download, cache_entry_present, Some(response))) } None => { need_to_download = true; - Ok((need_to_download, cache_entry_present,None)) + Ok((need_to_download, cache_entry_present, None)) } } - } async fn extract_versions_from_release( diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index 3dd46f7a2..b5c5c698a 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -66,7 +66,10 @@ impl CloudFlareCacheStatus { } pub fn should_log_warning(&self) -> bool { - matches!(self, Self::Unknown) || matches!(self, Self::NonExistent) || matches!(self, Self::Dynamic) || matches!(self, Self::Bypass) + matches!(self, Self::Unknown) + || matches!(self, Self::NonExistent) + || matches!(self, Self::Dynamic) + || matches!(self, Self::Bypass) } pub fn log_warning_if_present(&self) { @@ -133,7 +136,10 @@ impl RequestClient { .map_or(0, |v| v.to_str().unwrap_or_default().parse().unwrap_or(0)) } - pub fn get_cf_cache_status_from_head_response(&self, response: &Response) -> CloudFlareCacheStatus { + pub fn get_cf_cache_status_from_head_response( + &self, + response: &Response, + ) -> CloudFlareCacheStatus { if response.status().is_server_error() || response.status().is_client_error() { return CloudFlareCacheStatus::Unknown; }; @@ -202,11 +208,14 @@ impl RequestClient { cf_cache_status.log_warning_if_present(); let content_length = self.get_content_length_from_head_response(&head_response); - + let mut sleep_time = std::time::Duration::from_secs(DEFAULT_WAIT_TIME); if !content_length.eq(&0) { - sleep_time = std::time::Duration::from_secs((self.convert_content_length_to_mb(content_length).div(10.0) as u64).max(MAX_WAIT_TIME)); + sleep_time = std::time::Duration::from_secs( + (self.convert_content_length_to_mb(content_length).div(10.0) as u64) + .max(MAX_WAIT_TIME), + ); } if cf_cache_status.is_hit() { @@ -221,7 +230,6 @@ impl RequestClient { Ok(true) } - fn convert_content_length_to_mb(&self, content_length: u64) -> f64 { (content_length as f64) / 1024.0 / 1024.0 } From 882400aad0c75699f3ee37670950c4a46d08e43e Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 12:57:28 +0200 Subject: [PATCH 13/21] fix lock's issues --- .../stringhandler-tarigpuminer.json | 1 + .../tari-project-sha-p2pool.json | 1 + .../binaries_versions/tari-project-tari.json | 1 + .../cache/binaries_versions/xmrig-xmrig.json | 1 + src-tauri/src/github/cache.rs | 38 +++++++++++----- src-tauri/src/github/mod.rs | 43 ++++++++++++------- src-tauri/src/github/request_client.rs | 3 ++ 7 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 src-tauri/com.tari.universe.alpha/cache/binaries_versions/stringhandler-tarigpuminer.json create mode 100644 src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-sha-p2pool.json create mode 100644 src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-tari.json create mode 100644 src-tauri/com.tari.universe.alpha/cache/binaries_versions/xmrig-xmrig.json diff --git a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/stringhandler-tarigpuminer.json b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/stringhandler-tarigpuminer.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/stringhandler-tarigpuminer.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-sha-p2pool.json b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-sha-p2pool.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-sha-p2pool.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-tari.json b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-tari.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-tari.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/xmrig-xmrig.json b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/xmrig-xmrig.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/xmrig-xmrig.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src-tauri/src/github/cache.rs b/src-tauri/src/github/cache.rs index acd443190..5a01156ef 100644 --- a/src-tauri/src/github/cache.rs +++ b/src-tauri/src/github/cache.rs @@ -5,6 +5,7 @@ use anyhow::{anyhow, Error, Ok}; use serde::{Deserialize, Serialize}; use tauri::api::path::cache_dir; use tokio::sync::RwLock; +use log::{info}; const LOG_TARGET: &str = "tari::universe::github_cache"; @@ -24,6 +25,7 @@ pub struct CacheEntry { pub struct CacheJsonFile { pub cache_entries: HashMap, pub cache_file_path: PathBuf, + pub versions_cache_folder_path: PathBuf, } impl CacheJsonFile { @@ -33,10 +35,15 @@ impl CacheJsonFile { .join("cache") .join("binaries_versions") .join("versions_releases_responses.json"); + let versions_cache_folder_path = PathBuf::new() + .join(APPLICATION_FOLDER_ID) + .join("cache") + .join("binaries_versions"); Self { cache_entries: HashMap::new(), cache_file_path, + versions_cache_folder_path } } @@ -51,16 +58,23 @@ impl CacheJsonFile { pub fn read_version_releases_responses_cache_file(&mut self) -> Result<(), Error> { let cache_file_path = self.get_version_releases_responses_cache_file_path()?; - if !cache_file_path.exists() { - std::fs::write(&cache_file_path, "{}")?; + info!(target: LOG_TARGET, "Reading cache file: {:?}", cache_file_path); + if cache_file_path.exists() { + let json = std::fs::read_to_string(&cache_file_path)?; + self.cache_entries = serde_json::from_str(&json)?; } - let json = std::fs::read_to_string(&cache_file_path)?; - self.cache_entries = serde_json::from_str(&json)?; + + info!(target: LOG_TARGET, "Cache file read successfully"); Ok(()) } fn save_version_releases_responses_cache_file(&self) -> Result<(), Error> { let cache_file_path = self.get_version_releases_responses_cache_file_path()?; + if !cache_file_path.exists() { + std::fs::create_dir_all(cache_file_path.parent(). + ok_or_else(|| anyhow!("Failed to create cache directory"))? + )?; + } let json = serde_json::to_string_pretty(&self.cache_entries)?; std::fs::write(&cache_file_path, json)?; Ok(()) @@ -75,7 +89,6 @@ impl CacheJsonFile { &mut self, repo_owner: &str, repo_name: &str, - file_path: PathBuf, github_etag: Option, mirror_etag: Option, ) -> Result<(), Error> { @@ -85,7 +98,6 @@ impl CacheJsonFile { .ok_or_else(|| anyhow!("Cache entry not found"))?; cache_entry.github_etag = github_etag; cache_entry.mirror_etag = mirror_etag; - cache_entry.file_path = file_path; self.save_version_releases_responses_cache_file()?; Ok(()) } @@ -94,7 +106,6 @@ impl CacheJsonFile { &mut self, repo_owner: &str, repo_name: &str, - file_path: PathBuf, github_etag: Option, mirror_etag: Option, ) -> Result<(), Error> { @@ -103,14 +114,14 @@ impl CacheJsonFile { .contains_key(&Self::create_cache_entry_identifier(repo_owner, repo_name)); if is_cache_entry_exists { - self.update_cache_entry(repo_owner, repo_name, file_path, github_etag, mirror_etag)?; + self.update_cache_entry(repo_owner, repo_name, github_etag, mirror_etag)?; } else { let cache_entry = CacheEntry { repo_owner: repo_owner.to_string(), repo_name: repo_name.to_string(), github_etag, mirror_etag, - file_path, + file_path: self.versions_cache_folder_path.join(format!("{}-{}.json", repo_owner,repo_name)), }; self.cache_entries.insert( Self::create_cache_entry_identifier(repo_owner, repo_name), @@ -135,14 +146,19 @@ impl CacheJsonFile { repo_owner: &str, repo_name: &str, content: Vec, - ) -> Result { + ) -> Result<(), Error> { let cache_entry = self .get_cache_entry(repo_owner, repo_name) .ok_or_else(|| anyhow!("Cache entry not found"))?; let file_path = cache_entry.file_path.clone(); + + if !file_path.exists() { + std::fs::create_dir_all(file_path.parent().ok_or_else(|| anyhow!("Failed to create cache directory"))?)?; + } + let json = serde_json::to_string_pretty(&content)?; std::fs::write(&file_path, json)?; - Ok(file_path) + Ok(()) } pub fn get_file_content( diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index a221e8c3b..70ced14f6 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -88,17 +88,22 @@ pub async fn list_releases( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { + info!(target: LOG_TARGET, "Reading cache releases for {}/{}", repo_owner, repo_name); CacheJsonFile::current() .write() .await .read_version_releases_responses_cache_file()?; + info!(target: LOG_TARGET, "Fetching releases for {}/{}", repo_owner, repo_name); + let mut mirror_releases = list_mirror_releases(repo_owner, repo_name) .await .inspect_err(|e| { - warn!(target: LOG_TARGET, "Failed to fetch releases from Github: {}", e); + warn!(target: LOG_TARGET, "Failed to fetch releases from Mirror: {}", e); }) .unwrap_or_default(); + + info!(target: LOG_TARGET, "Found {} releases from mirror", mirror_releases.len()); // Add any missing releases from github let github_releases = list_github_releases(repo_owner, repo_name) .await @@ -107,6 +112,8 @@ pub async fn list_releases( }) .unwrap_or_default(); + info!(target: LOG_TARGET, "Found {} releases from Github", github_releases.len()); + for release in &github_releases { if !mirror_releases.iter().any(|r| r.version == release.version) { mirror_releases.push(release.clone()); @@ -125,12 +132,16 @@ async fn list_mirror_releases( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { - let mut cache_json_file_lock = CacheJsonFile::current().write().await; let url = get_mirror_url(repo_owner, repo_name); + info!(target: LOG_TARGET, "i'm here"); + let (need_to_download, cache_entry_present, response) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Mirror).await?; + info!(target: LOG_TARGET, "Mirror releases need to download: {}", need_to_download); + info!(target: LOG_TARGET, "Mirror releases cache entry present: {}", cache_entry_present); + let mut versions_list: Vec = vec![]; let mut does_hit = response .and_then(|res| { @@ -142,10 +153,14 @@ async fn list_mirror_releases( }) .unwrap_or(false); + info!(target: LOG_TARGET, "Mirror releases cache hit: {}", does_hit); + if need_to_download && !does_hit { does_hit = RequestClient::current().check_if_cache_hits(&url).await?; } + let mut cache_json_file_lock = CacheJsonFile::current().write().await; + if does_hit { let (response, etag) = RequestClient::current() .fetch_get_versions_download_info(&url) @@ -153,16 +168,14 @@ async fn list_mirror_releases( let remote_versions_list = extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Mirror) .await?; - let content_path = - cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; - let args = (repo_owner, repo_name, content_path, Some(etag), None); + let args = (repo_owner, repo_name, Some(etag), None); if cache_entry_present { - cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3)?; } else { - cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3)?; }; - + cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; versions_list.extend(remote_versions_list); } else { let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; @@ -176,14 +189,15 @@ async fn list_github_releases( repo_owner: &str, repo_name: &str, ) -> Result, anyhow::Error> { - let mut cache_json_file_lock = CacheJsonFile::current().write().await; let url = get_gh_url(repo_owner, repo_name); - let (need_to_download, cache_entry_present, response) = + let (need_to_download, cache_entry_present, _) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Github).await?; let mut versions_list: Vec = vec![]; + let mut cache_json_file_lock = CacheJsonFile::current().write().await; + if need_to_download { let (response, etag) = RequestClient::current() .fetch_get_versions_download_info(&url) @@ -191,16 +205,15 @@ async fn list_github_releases( let remote_versions_list = extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Github) .await?; - let content_path = - cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; - let args = (repo_owner, repo_name, content_path, Some(etag), None); + let args = (repo_owner, repo_name, Some(etag), None); if cache_entry_present { - cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3)?; } else { - cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3, args.4)?; + cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3)?; }; + cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; versions_list.extend(remote_versions_list); } else { let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index b5c5c698a..801afb1eb 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -3,6 +3,7 @@ use std::{ops::Div, sync::LazyLock}; use anyhow::anyhow; use log::warn; use reqwest::{Client, Response}; +use log::info; use super::Release; const LOG_TARGET: &str = "tari::universe::request_client"; @@ -208,6 +209,8 @@ impl RequestClient { cf_cache_status.log_warning_if_present(); let content_length = self.get_content_length_from_head_response(&head_response); + info!(target: LOG_TARGET, "Content length: {}", content_length); + info!(target: LOG_TARGET, "Content length in MB: {}", self.convert_content_length_to_mb(content_length)); let mut sleep_time = std::time::Duration::from_secs(DEFAULT_WAIT_TIME); From 3a546b59d849ec3d0a71f3a0492d194f56a8be3c Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 14:04:09 +0200 Subject: [PATCH 14/21] mvp --- src-tauri/src/binaries/adapter_tor.rs | 2 +- src-tauri/src/binaries/binaries_manager.rs | 5 ++- src-tauri/src/download_utils.rs | 6 +-- src-tauri/src/github/cache.rs | 43 +++++++++++++++------- src-tauri/src/github/mod.rs | 8 ++-- src-tauri/src/github/request_client.rs | 16 +++++--- src-tauri/src/main.rs | 20 +++++----- 7 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src-tauri/src/binaries/adapter_tor.rs b/src-tauri/src/binaries/adapter_tor.rs index e248c7486..26f9a73b8 100644 --- a/src-tauri/src/binaries/adapter_tor.rs +++ b/src-tauri/src/binaries/adapter_tor.rs @@ -35,7 +35,7 @@ impl LatestVersionApiAdapter for TorReleaseAdapter { assets: vec![VersionAsset { url: cdn_tor_bundle_url.to_string(), name: format!("tor-expert-bundle-{}-13.5.7.tar.gz", platform), - source: ReleaseSource::Github, + source: ReleaseSource::Mirror, }], }; return Ok(vec![version]); diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index 9f7578d17..ac42a4375 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -441,12 +441,13 @@ impl BinaryManager { RequestClient::current() .check_if_cache_hits(asset.url.as_str()) .await?; - download_file( + download_file_with_retries( asset.url.as_str(), &in_progress_file_zip, progress_tracker.clone(), ) - .await?; + .await + .map_err(|e| anyhow!("Error downloading version: {:?}. Error: {:?}", version, e))?; } info!(target: LOG_TARGET, "Downloaded version: {:?}", version); diff --git a/src-tauri/src/download_utils.rs b/src-tauri/src/download_utils.rs index bf3bd0a58..8558a26e1 100644 --- a/src-tauri/src/download_utils.rs +++ b/src-tauri/src/download_utils.rs @@ -58,9 +58,9 @@ pub async fn download_file( // Stream the response body directly to the file let mut stream = response.bytes_stream(); while let Some(item) = stream.next().await { - let _ = progress_tracker - .update("downloading".to_string(), None, 10) - .await; + // let _ = progress_tracker + // .update("downloading".to_string(), None, 10) + // .await; dest.write_all(&item?).await?; } diff --git a/src-tauri/src/github/cache.rs b/src-tauri/src/github/cache.rs index 5a01156ef..63a41377f 100644 --- a/src-tauri/src/github/cache.rs +++ b/src-tauri/src/github/cache.rs @@ -2,10 +2,10 @@ use std::{collections::HashMap, path::PathBuf, sync::LazyLock}; use crate::{binaries::binaries_resolver::VersionDownloadInfo, APPLICATION_FOLDER_ID}; use anyhow::{anyhow, Error, Ok}; +use log::info; use serde::{Deserialize, Serialize}; use tauri::api::path::cache_dir; use tokio::sync::RwLock; -use log::{info}; const LOG_TARGET: &str = "tari::universe::github_cache"; @@ -43,7 +43,7 @@ impl CacheJsonFile { Self { cache_entries: HashMap::new(), cache_file_path, - versions_cache_folder_path + versions_cache_folder_path, } } @@ -71,9 +71,11 @@ impl CacheJsonFile { fn save_version_releases_responses_cache_file(&self) -> Result<(), Error> { let cache_file_path = self.get_version_releases_responses_cache_file_path()?; if !cache_file_path.exists() { - std::fs::create_dir_all(cache_file_path.parent(). - ok_or_else(|| anyhow!("Failed to create cache directory"))? - )?; + std::fs::create_dir_all( + cache_file_path + .parent() + .ok_or_else(|| anyhow!("Failed to create cache directory"))?, + )?; } let json = serde_json::to_string_pretty(&self.cache_entries)?; std::fs::write(&cache_file_path, json)?; @@ -96,8 +98,12 @@ impl CacheJsonFile { .cache_entries .get_mut(&Self::create_cache_entry_identifier(repo_owner, repo_name)) .ok_or_else(|| anyhow!("Cache entry not found"))?; - cache_entry.github_etag = github_etag; - cache_entry.mirror_etag = mirror_etag; + if github_etag.is_some() { + cache_entry.github_etag = github_etag; + } + if mirror_etag.is_some() { + cache_entry.mirror_etag = mirror_etag; + } self.save_version_releases_responses_cache_file()?; Ok(()) } @@ -114,14 +120,16 @@ impl CacheJsonFile { .contains_key(&Self::create_cache_entry_identifier(repo_owner, repo_name)); if is_cache_entry_exists { - self.update_cache_entry(repo_owner, repo_name, github_etag, mirror_etag)?; + self.update_cache_entry(repo_owner, repo_name, github_etag, mirror_etag)?; } else { let cache_entry = CacheEntry { repo_owner: repo_owner.to_string(), repo_name: repo_name.to_string(), github_etag, mirror_etag, - file_path: self.versions_cache_folder_path.join(format!("{}-{}.json", repo_owner,repo_name)), + file_path: self + .versions_cache_folder_path + .join(format!("{}-{}.json", repo_owner, repo_name)), }; self.cache_entries.insert( Self::create_cache_entry_identifier(repo_owner, repo_name), @@ -147,17 +155,25 @@ impl CacheJsonFile { repo_name: &str, content: Vec, ) -> Result<(), Error> { + let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get cache directory"))?; let cache_entry = self .get_cache_entry(repo_owner, repo_name) .ok_or_else(|| anyhow!("Cache entry not found"))?; - let file_path = cache_entry.file_path.clone(); + let file_path = cache_path.join(cache_entry.file_path.clone()); if !file_path.exists() { - std::fs::create_dir_all(file_path.parent().ok_or_else(|| anyhow!("Failed to create cache directory"))?)?; + std::fs::create_dir_all( + file_path + .parent() + .ok_or_else(|| anyhow!("Failed to create cache directory"))?, + )?; } - + let json = serde_json::to_string_pretty(&content)?; std::fs::write(&file_path, json)?; + + info!(target: LOG_TARGET, "File content saved successfully"); + Ok(()) } @@ -166,10 +182,11 @@ impl CacheJsonFile { repo_owner: &str, repo_name: &str, ) -> Result, Error> { + let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get cache directory"))?; let cache_entry = self .get_cache_entry(repo_owner, repo_name) .ok_or_else(|| anyhow!("Cache entry not found"))?; - let file_path = cache_entry.file_path.clone(); + let file_path = cache_path.join(cache_entry.file_path.clone()); let json = std::fs::read_to_string(&file_path)?; let content: Vec = serde_json::from_str(&json)?; Ok(content) diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 70ced14f6..df0a2ece3 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -169,19 +169,21 @@ async fn list_mirror_releases( extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Mirror) .await?; - let args = (repo_owner, repo_name, Some(etag), None); + let args = (repo_owner, repo_name, None, Some(etag)); if cache_entry_present { cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3)?; } else { cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3)?; }; - cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; versions_list.extend(remote_versions_list); + cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; } else { let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; versions_list.extend(content); } + drop(cache_json_file_lock); + Ok(versions_list) } @@ -213,8 +215,8 @@ async fn list_github_releases( cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3)?; }; - cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; versions_list.extend(remote_versions_list); + cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; } else { let content = cache_json_file_lock.get_file_content(repo_owner, repo_name)?; versions_list.extend(content); diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index 801afb1eb..8b0a0a350 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -1,9 +1,9 @@ use std::{ops::Div, sync::LazyLock}; use anyhow::anyhow; +use log::info; use log::warn; use reqwest::{Client, Response}; -use log::info; use super::Release; const LOG_TARGET: &str = "tari::universe::request_client"; @@ -141,7 +141,9 @@ impl RequestClient { &self, response: &Response, ) -> CloudFlareCacheStatus { + info!(target: LOG_TARGET, "get_cf_cache_status_from_head_response, response status: {}, url: {}", response.status(), response.url()); if response.status().is_server_error() || response.status().is_client_error() { + info!(target: LOG_TARGET, "get_cf_cache_status_from_head_response, error"); return CloudFlareCacheStatus::Unknown; }; let cache_status = CloudFlareCacheStatus::from_str( @@ -151,6 +153,9 @@ impl RequestClient { .map_or("", |v| v.to_str().unwrap_or_default()), ); + info!(target: LOG_TARGET, "get_cf_cache_status_from_head_response, cache status: {:?}", cache_status.to_str()); + info!(target: LOG_TARGET, "get_cf_cache_status_from_head_response_raw, cache status: {:?}", response.headers().get("cf-cache-status")); + cache_status.log_warning_if_present(); cache_status } @@ -193,9 +198,9 @@ impl RequestClient { } pub async fn check_if_cache_hits(&self, url: &str) -> Result { - const MAX_RETRIES: u8 = 5; + const MAX_RETRIES: u8 = 3; const MAX_WAIT_TIME: u64 = 30; - const DEFAULT_WAIT_TIME: u64 = 5; + const DEFAULT_WAIT_TIME: u64 = 2; let mut retries = 0; loop { @@ -210,14 +215,15 @@ impl RequestClient { let content_length = self.get_content_length_from_head_response(&head_response); info!(target: LOG_TARGET, "Content length: {}", content_length); - info!(target: LOG_TARGET, "Content length in MB: {}", self.convert_content_length_to_mb(content_length)); + info!(target: LOG_TARGET, "Content length in mb: {}", self.convert_content_length_to_mb(content_length)); let mut sleep_time = std::time::Duration::from_secs(DEFAULT_WAIT_TIME); if !content_length.eq(&0) { sleep_time = std::time::Duration::from_secs( (self.convert_content_length_to_mb(content_length).div(10.0) as u64) - .max(MAX_WAIT_TIME), + .min(MAX_WAIT_TIME) + .max(2), ); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f3bd0c64a..43b0fee69 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -646,16 +646,16 @@ async fn setup_inner( .unwrap_or(Duration::from_secs(0)) > Duration::from_secs(60 * 60 * 6); - if use_tor { - progress.set_max(5).await; - progress - .update("checking-latest-version-tor".to_string(), None, 0) - .await; - binary_resolver - .initalize_binary(Binaries::Tor, progress.clone(), should_check_for_update) - .await?; - sleep(Duration::from_secs(1)); - } + // if use_tor { + // progress.set_max(5).await; + // progress + // .update("checking-latest-version-tor".to_string(), None, 0) + // .await; + // binary_resolver + // .initalize_binary(Binaries::Tor, progress.clone(), should_check_for_update) + // .await?; + // sleep(Duration::from_secs(1)); + // } progress.set_max(10).await; progress .update("checking-latest-version-node".to_string(), None, 0) From 964edc286981ab7ae02db48db36747ffff5f96dd Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 14:19:00 +0200 Subject: [PATCH 15/21] bring back tor and shouldupdate check --- src-tauri/src/github/mod.rs | 2 +- src-tauri/src/main.rs | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index df0a2ece3..79a3e1ba2 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -161,7 +161,7 @@ async fn list_mirror_releases( let mut cache_json_file_lock = CacheJsonFile::current().write().await; - if does_hit { + if does_hit && need_to_download { let (response, etag) = RequestClient::current() .fetch_get_versions_download_info(&url) .await?; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 43b0fee69..f3bd0c64a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -646,16 +646,16 @@ async fn setup_inner( .unwrap_or(Duration::from_secs(0)) > Duration::from_secs(60 * 60 * 6); - // if use_tor { - // progress.set_max(5).await; - // progress - // .update("checking-latest-version-tor".to_string(), None, 0) - // .await; - // binary_resolver - // .initalize_binary(Binaries::Tor, progress.clone(), should_check_for_update) - // .await?; - // sleep(Duration::from_secs(1)); - // } + if use_tor { + progress.set_max(5).await; + progress + .update("checking-latest-version-tor".to_string(), None, 0) + .await; + binary_resolver + .initalize_binary(Binaries::Tor, progress.clone(), should_check_for_update) + .await?; + sleep(Duration::from_secs(1)); + } progress.set_max(10).await; progress .update("checking-latest-version-node".to_string(), None, 0) From 11b000b4ee9e25f4da40ee3b6ea07ac967d2a3e2 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 15:59:40 +0200 Subject: [PATCH 16/21] part of cleanup --- src-tauri/src/github/cache.rs | 65 ++++++++++++-------------- src-tauri/src/github/mod.rs | 36 +++++++------- src-tauri/src/github/request_client.rs | 30 ++---------- 3 files changed, 52 insertions(+), 79 deletions(-) diff --git a/src-tauri/src/github/cache.rs b/src-tauri/src/github/cache.rs index 63a41377f..6bf057cff 100644 --- a/src-tauri/src/github/cache.rs +++ b/src-tauri/src/github/cache.rs @@ -30,15 +30,7 @@ pub struct CacheJsonFile { impl CacheJsonFile { fn new() -> Self { - let cache_file_path = PathBuf::new() - .join(APPLICATION_FOLDER_ID) - .join("cache") - .join("binaries_versions") - .join("versions_releases_responses.json"); - let versions_cache_folder_path = PathBuf::new() - .join(APPLICATION_FOLDER_ID) - .join("cache") - .join("binaries_versions"); + let (cache_file_path, versions_cache_folder_path) = Self::initialize_paths(); Self { cache_entries: HashMap::new(), @@ -47,12 +39,18 @@ impl CacheJsonFile { } } + fn initialize_paths() -> (PathBuf, PathBuf) { + let base_path = PathBuf::from(APPLICATION_FOLDER_ID).join("cache").join("binaries_versions"); + let cache_file_path = base_path.join("versions_releases_responses.json"); + (cache_file_path, base_path) + } + fn create_cache_entry_identifier(repo_owner: &str, repo_name: &str) -> String { format!("{}-{}", repo_owner, repo_name) } fn get_version_releases_responses_cache_file_path(&self) -> Result { - let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get cache directory"))?; + let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get cache path"))?; Ok(cache_path.join(self.cache_file_path.clone())) } @@ -64,7 +62,7 @@ impl CacheJsonFile { self.cache_entries = serde_json::from_str(&json)?; } - info!(target: LOG_TARGET, "Cache file read successfully"); + info!(target: LOG_TARGET, "Version releases cache file read successfully"); Ok(()) } @@ -79,6 +77,8 @@ impl CacheJsonFile { } let json = serde_json::to_string_pretty(&self.cache_entries)?; std::fs::write(&cache_file_path, json)?; + + info!(target: LOG_TARGET, "Version releases cache file saved successfully"); Ok(()) } @@ -115,11 +115,9 @@ impl CacheJsonFile { github_etag: Option, mirror_etag: Option, ) -> Result<(), Error> { - let is_cache_entry_exists = self - .cache_entries - .contains_key(&Self::create_cache_entry_identifier(repo_owner, repo_name)); + let identifier = Self::create_cache_entry_identifier(repo_owner, repo_name); - if is_cache_entry_exists { + if self.cache_entries.contains_key(&identifier) { self.update_cache_entry(repo_owner, repo_name, github_etag, mirror_etag)?; } else { let cache_entry = CacheEntry { @@ -131,10 +129,7 @@ impl CacheJsonFile { .versions_cache_folder_path .join(format!("{}-{}.json", repo_owner, repo_name)), }; - self.cache_entries.insert( - Self::create_cache_entry_identifier(repo_owner, repo_name), - cache_entry, - ); + self.cache_entries.insert(identifier,cache_entry); self.save_version_releases_responses_cache_file()?; }; @@ -142,11 +137,16 @@ impl CacheJsonFile { } pub fn chech_if_content_file_exist(&self, repo_owner: &str, repo_name: &str) -> bool { - let cache_entry = self.get_cache_entry(repo_owner, repo_name); - match cache_entry { - Some(cache_entry) => cache_entry.file_path.exists(), - None => false, - } + self.get_cache_entry(repo_owner, repo_name) + .map_or(false, |cache_entry| cache_entry.file_path.exists()) + } + + fn get_file_content_path(&self, repo_owner: &str, repo_name: &str) -> Result { + let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get file content path"))?; + let cache_entry = self.get_cache_entry(repo_owner, repo_name).ok_or_else(|| { + anyhow!("File content not found for repo_owner: {}, repo_name: {}", repo_owner, repo_name) + })?; + Ok(cache_path.join(cache_entry.file_path.clone())) } pub fn save_file_content( @@ -155,11 +155,7 @@ impl CacheJsonFile { repo_name: &str, content: Vec, ) -> Result<(), Error> { - let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get cache directory"))?; - let cache_entry = self - .get_cache_entry(repo_owner, repo_name) - .ok_or_else(|| anyhow!("Cache entry not found"))?; - let file_path = cache_path.join(cache_entry.file_path.clone()); + let file_path = self.get_file_content_path(repo_owner, repo_name)?; if !file_path.exists() { std::fs::create_dir_all( @@ -171,9 +167,8 @@ impl CacheJsonFile { let json = serde_json::to_string_pretty(&content)?; std::fs::write(&file_path, json)?; - + info!(target: LOG_TARGET, "File content saved successfully"); - Ok(()) } @@ -182,13 +177,11 @@ impl CacheJsonFile { repo_owner: &str, repo_name: &str, ) -> Result, Error> { - let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get cache directory"))?; - let cache_entry = self - .get_cache_entry(repo_owner, repo_name) - .ok_or_else(|| anyhow!("Cache entry not found"))?; - let file_path = cache_path.join(cache_entry.file_path.clone()); + let file_path = self.get_file_content_path(repo_owner, repo_name)?; let json = std::fs::read_to_string(&file_path)?; let content: Vec = serde_json::from_str(&json)?; + + info!(target: LOG_TARGET, "File content read successfully"); Ok(content) } diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 79a3e1ba2..642b7c18f 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -7,10 +7,8 @@ use request_client::RequestClient; use reqwest::Response; use serde::{Deserialize, Serialize}; -use crate::{ - binaries::binaries_resolver::{VersionAsset, VersionDownloadInfo}, - APPLICATION_FOLDER_ID, -}; +use crate::binaries::binaries_resolver::{VersionAsset, VersionDownloadInfo}; + const LOG_TARGET: &str = "tari::universe::github"; @@ -94,7 +92,7 @@ pub async fn list_releases( .await .read_version_releases_responses_cache_file()?; - info!(target: LOG_TARGET, "Fetching releases for {}/{}", repo_owner, repo_name); + info!(target: LOG_TARGET, "Fetching mirror releases for {}/{}", repo_owner, repo_name); let mut mirror_releases = list_mirror_releases(repo_owner, repo_name) .await @@ -104,7 +102,9 @@ pub async fn list_releases( .unwrap_or_default(); info!(target: LOG_TARGET, "Found {} releases from mirror", mirror_releases.len()); - // Add any missing releases from github + + info!(target: LOG_TARGET, "Fetching github releases for {}/{}", repo_owner, repo_name); + let github_releases = list_github_releases(repo_owner, repo_name) .await .inspect_err(|e| { @@ -114,6 +114,7 @@ pub async fn list_releases( info!(target: LOG_TARGET, "Found {} releases from Github", github_releases.len()); + // Add any missing releases from github for release in &github_releases { if !mirror_releases.iter().any(|r| r.version == release.version) { mirror_releases.push(release.clone()); @@ -133,8 +134,7 @@ async fn list_mirror_releases( repo_name: &str, ) -> Result, anyhow::Error> { let url = get_mirror_url(repo_owner, repo_name); - - info!(target: LOG_TARGET, "i'm here"); + info!(target: LOG_TARGET, "Mirror releases url: {}", url); let (need_to_download, cache_entry_present, response) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Mirror).await?; @@ -169,11 +169,10 @@ async fn list_mirror_releases( extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Mirror) .await?; - let args = (repo_owner, repo_name, None, Some(etag)); if cache_entry_present { - cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3)?; + cache_json_file_lock.update_cache_entry(repo_owner, repo_name, None, Some(etag))?; } else { - cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3)?; + cache_json_file_lock.create_cache_entry(repo_owner, repo_name, None, Some(etag))?; }; versions_list.extend(remote_versions_list); cache_json_file_lock.save_file_content(repo_owner, repo_name, versions_list.clone())?; @@ -182,8 +181,6 @@ async fn list_mirror_releases( versions_list.extend(content); } - drop(cache_json_file_lock); - Ok(versions_list) } @@ -192,10 +189,14 @@ async fn list_github_releases( repo_name: &str, ) -> Result, anyhow::Error> { let url = get_gh_url(repo_owner, repo_name); + info!(target: LOG_TARGET, "Github releases url: {}", url); let (need_to_download, cache_entry_present, _) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Github).await?; + info!(target: LOG_TARGET, "Github releases need to download: {}", need_to_download); + info!(target: LOG_TARGET, "Github releases cache entry present: {}", cache_entry_present); + let mut versions_list: Vec = vec![]; let mut cache_json_file_lock = CacheJsonFile::current().write().await; @@ -208,11 +209,10 @@ async fn list_github_releases( extract_versions_from_release(repo_owner, repo_name, response, ReleaseSource::Github) .await?; - let args = (repo_owner, repo_name, Some(etag), None); if cache_entry_present { - cache_json_file_lock.update_cache_entry(args.0, args.1, args.2, args.3)?; + cache_json_file_lock.update_cache_entry(repo_owner, repo_name, Some(etag), None)?; } else { - cache_json_file_lock.create_cache_entry(args.0, args.1, args.2, args.3)?; + cache_json_file_lock.create_cache_entry(repo_owner, repo_name, Some(etag), None)?; }; versions_list.extend(remote_versions_list); @@ -249,6 +249,9 @@ async fn check_if_need_download( ReleaseSource::Github => cache_entry.github_etag.clone(), }; + info!(target: LOG_TARGET, "Remote etag: {}", remote_etag); + info!(target: LOG_TARGET, "Local etag: {:?}", cache_entry); + if !remote_etag.eq(&local_etag.unwrap_or("".to_string())) { need_to_download = true }; @@ -280,7 +283,6 @@ async fn extract_versions_from_release( // Remove any v prefix let release_name = release.tag_name.trim_start_matches('v').to_string(); debug!(target: LOG_TARGET, " - release: {}", release_name); - // res.push(semver::Version::parse(&tag_name)?); let mut assets = vec![]; for asset in release.assets { let url = match source { diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index 8b0a0a350..4d093ff20 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -100,6 +100,10 @@ impl RequestClient { } } + fn convert_content_length_to_mb(&self, content_length: u64) -> f64 { + (content_length as f64) / 1024.0 / 1024.0 + } + pub async fn send_head_request(&self, url: &str) -> Result { self.client .head(url) @@ -160,28 +164,6 @@ impl RequestClient { cache_status } - pub async fn fetch_head_etag(&self, url: &str) -> Result { - let head_response = self.send_head_request(url).await.map_err(|e| anyhow!(e))?; - Ok(head_response - .headers() - .get("etag") - .map_or("", |v| v.to_str().unwrap_or_default()) - .to_string()) - } - - pub async fn fetch_head_cf_cache_status( - &self, - url: &str, - ) -> Result { - let head_response = self.send_head_request(url).await.map_err(|e| anyhow!(e))?; - Ok(CloudFlareCacheStatus::from_str( - head_response - .headers() - .get("cf-cache-status") - .map_or("", |v| v.to_str().unwrap_or_default()), - )) - } - pub async fn fetch_get_versions_download_info( &self, url: &str, @@ -239,10 +221,6 @@ impl RequestClient { Ok(true) } - fn convert_content_length_to_mb(&self, content_length: u64) -> f64 { - (content_length as f64) / 1024.0 / 1024.0 - } - pub fn current() -> &'static LazyLock { &INSTANCE } From 7d7938865475089f469da27e62e2456d989f8646 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 16:00:51 +0200 Subject: [PATCH 17/21] Remove misplaced folder --- .../cache/binaries_versions/stringhandler-tarigpuminer.json | 1 - .../cache/binaries_versions/tari-project-sha-p2pool.json | 1 - .../cache/binaries_versions/tari-project-tari.json | 1 - .../cache/binaries_versions/xmrig-xmrig.json | 1 - 4 files changed, 4 deletions(-) delete mode 100644 src-tauri/com.tari.universe.alpha/cache/binaries_versions/stringhandler-tarigpuminer.json delete mode 100644 src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-sha-p2pool.json delete mode 100644 src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-tari.json delete mode 100644 src-tauri/com.tari.universe.alpha/cache/binaries_versions/xmrig-xmrig.json diff --git a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/stringhandler-tarigpuminer.json b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/stringhandler-tarigpuminer.json deleted file mode 100644 index 0637a088a..000000000 --- a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/stringhandler-tarigpuminer.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-sha-p2pool.json b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-sha-p2pool.json deleted file mode 100644 index 0637a088a..000000000 --- a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-sha-p2pool.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-tari.json b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-tari.json deleted file mode 100644 index 0637a088a..000000000 --- a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/tari-project-tari.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/xmrig-xmrig.json b/src-tauri/com.tari.universe.alpha/cache/binaries_versions/xmrig-xmrig.json deleted file mode 100644 index 0637a088a..000000000 --- a/src-tauri/com.tari.universe.alpha/cache/binaries_versions/xmrig-xmrig.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file From fae3a14a9ce5009042ae67b8feee106dbe60ffcf Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 16:03:46 +0200 Subject: [PATCH 18/21] rest of cleanup --- src-tauri/src/binaries/binaries_manager.rs | 25 +++++++--------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index ac42a4375..402209c93 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -427,29 +427,20 @@ impl BinaryManager { .map_err(|e| anyhow!("Error creating in progress folder. Error: {:?}", e))?; let in_progress_file_zip = in_progress_dir.join(asset.name.clone()); - if asset.source.is_github() { - download_file_with_retries( - asset.url.as_str(), - &in_progress_file_zip, - progress_tracker.clone(), - ) - .await - .map_err(|e| anyhow!("Error downloading version: {:?}. Error: {:?}", version, e))?; - } - if asset.source.is_mirror() { RequestClient::current() .check_if_cache_hits(asset.url.as_str()) .await?; - download_file_with_retries( - asset.url.as_str(), - &in_progress_file_zip, - progress_tracker.clone(), - ) - .await - .map_err(|e| anyhow!("Error downloading version: {:?}. Error: {:?}", version, e))?; } + download_file_with_retries( + asset.url.as_str(), + &in_progress_file_zip, + progress_tracker.clone(), + ) + .await + .map_err(|e| anyhow!("Error downloading version: {:?}. Error: {:?}", version, e))?; + info!(target: LOG_TARGET, "Downloaded version: {:?}", version); extract(&in_progress_file_zip, &destination_dir) From 55e6ef72f2bad457e17439cc69fc2b16cf3f03c5 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 16:18:48 +0200 Subject: [PATCH 19/21] cargo fmt and lints fix --- src-tauri/src/binaries/binaries_manager.rs | 2 +- src-tauri/src/github/cache.rs | 14 ++++++++++---- src-tauri/src/github/mod.rs | 3 +-- src-tauri/src/node_manager.rs | 2 +- src-tauri/src/progress_tracker.rs | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/binaries/binaries_manager.rs b/src-tauri/src/binaries/binaries_manager.rs index 402209c93..2a4a2706f 100644 --- a/src-tauri/src/binaries/binaries_manager.rs +++ b/src-tauri/src/binaries/binaries_manager.rs @@ -5,7 +5,7 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use tari_common::configuration::Network; use crate::{ - download_utils::{download_file, download_file_with_retries, extract, validate_checksum}, + download_utils::{download_file_with_retries, extract, validate_checksum}, github::request_client::RequestClient, progress_tracker::ProgressTracker, }; diff --git a/src-tauri/src/github/cache.rs b/src-tauri/src/github/cache.rs index 6bf057cff..b3ee9444b 100644 --- a/src-tauri/src/github/cache.rs +++ b/src-tauri/src/github/cache.rs @@ -40,7 +40,9 @@ impl CacheJsonFile { } fn initialize_paths() -> (PathBuf, PathBuf) { - let base_path = PathBuf::from(APPLICATION_FOLDER_ID).join("cache").join("binaries_versions"); + let base_path = PathBuf::from(APPLICATION_FOLDER_ID) + .join("cache") + .join("binaries_versions"); let cache_file_path = base_path.join("versions_releases_responses.json"); (cache_file_path, base_path) } @@ -129,7 +131,7 @@ impl CacheJsonFile { .versions_cache_folder_path .join(format!("{}-{}.json", repo_owner, repo_name)), }; - self.cache_entries.insert(identifier,cache_entry); + self.cache_entries.insert(identifier, cache_entry); self.save_version_releases_responses_cache_file()?; }; @@ -144,7 +146,11 @@ impl CacheJsonFile { fn get_file_content_path(&self, repo_owner: &str, repo_name: &str) -> Result { let cache_path = cache_dir().ok_or_else(|| anyhow!("Failed to get file content path"))?; let cache_entry = self.get_cache_entry(repo_owner, repo_name).ok_or_else(|| { - anyhow!("File content not found for repo_owner: {}, repo_name: {}", repo_owner, repo_name) + anyhow!( + "File content not found for repo_owner: {}, repo_name: {}", + repo_owner, + repo_name + ) })?; Ok(cache_path.join(cache_entry.file_path.clone())) } @@ -167,7 +173,7 @@ impl CacheJsonFile { let json = serde_json::to_string_pretty(&content)?; std::fs::write(&file_path, json)?; - + info!(target: LOG_TARGET, "File content saved successfully"); Ok(()) } diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 642b7c18f..f2274f6d4 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize}; use crate::binaries::binaries_resolver::{VersionAsset, VersionDownloadInfo}; - const LOG_TARGET: &str = "tari::universe::github"; #[derive(Deserialize)] @@ -189,7 +188,7 @@ async fn list_github_releases( repo_name: &str, ) -> Result, anyhow::Error> { let url = get_gh_url(repo_owner, repo_name); - info!(target: LOG_TARGET, "Github releases url: {}", url); + info!(target: LOG_TARGET, "Github releases url: {}", url); let (need_to_download, cache_entry_present, _) = check_if_need_download(repo_owner, repo_name, &url, ReleaseSource::Github).await?; diff --git a/src-tauri/src/node_manager.rs b/src-tauri/src/node_manager.rs index 310b61918..b53577d10 100644 --- a/src-tauri/src/node_manager.rs +++ b/src-tauri/src/node_manager.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use std::time::SystemTime; use chrono::{NaiveDateTime, TimeZone, Utc}; -use log::{debug, error}; +use log::error; use minotari_node_grpc_client::grpc::Peer; use tari_core::transactions::tari_amount::MicroMinotari; use tari_crypto::ristretto::RistrettoPublicKey; diff --git a/src-tauri/src/progress_tracker.rs b/src-tauri/src/progress_tracker.rs index 88daf64d4..416a25cdf 100644 --- a/src-tauri/src/progress_tracker.rs +++ b/src-tauri/src/progress_tracker.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, sync::Arc}; -use log::{debug, error}; +use log::error; use tokio::sync::RwLock; use crate::setup_status_event::SetupStatusEvent; From c4a65fd95c0ec20fc8b6f44bf6a7cdc86158aad1 Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 16:30:10 +0200 Subject: [PATCH 20/21] cargo fmt and lints fix --- src-tauri/src/github/mod.rs | 19 +++---------------- src-tauri/src/github/request_client.rs | 11 +++++++---- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index f2274f6d4..2a27bcb13 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -31,25 +31,12 @@ pub enum ReleaseSource { } impl ReleaseSource { - pub fn to_string(&self) -> String { - match self { - ReleaseSource::Github => "Github".to_string(), - ReleaseSource::Mirror => "Mirror".to_string(), - } - } - pub fn is_github(&self) -> bool { - match self { - ReleaseSource::Github => true, - _ => false, - } + matches!(self, ReleaseSource::Github) } pub fn is_mirror(&self) -> bool { - match self { - ReleaseSource::Mirror => true, - _ => false, - } + matches!(self, ReleaseSource::Mirror) } } @@ -241,7 +228,7 @@ async fn check_if_need_download( need_to_download = true; } - let response = RequestClient::current().send_head_request(&url).await?; + let response = RequestClient::current().send_head_request(url).await?; let remote_etag = RequestClient::current().get_etag_from_head_response(&response); let local_etag = match source { ReleaseSource::Mirror => cache_entry.mirror_etag.clone(), diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index 4d093ff20..06a968632 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -190,7 +190,7 @@ impl RequestClient { return Ok(false); } - let head_response = self.send_head_request(&url).await?; + let head_response = self.send_head_request(url).await?; let cf_cache_status = self.get_cf_cache_status_from_head_response(&head_response); cf_cache_status.log_warning_if_present(); @@ -203,9 +203,12 @@ impl RequestClient { if !content_length.eq(&0) { sleep_time = std::time::Duration::from_secs( - (self.convert_content_length_to_mb(content_length).div(10.0) as u64) - .min(MAX_WAIT_TIME) - .max(2), + (self + .convert_content_length_to_mb(content_length) + .div(10.0) + .to_bits()) + .min(MAX_WAIT_TIME) + .max(2), ); } From 99b42cdc88db2e888178e585c9bef09cb5ad064b Mon Sep 17 00:00:00 2001 From: Misieq01 Date: Fri, 25 Oct 2024 16:48:51 +0200 Subject: [PATCH 21/21] fix timeout calculation --- src-tauri/Cargo.toml | 28 ++++++++++----------- src-tauri/src/binaries/binaries_resolver.rs | 2 +- src-tauri/src/github/request_client.rs | 16 ++++++------ 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 574ebbfc4..7fa9f0094 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -9,12 +9,12 @@ version = "0.5.46" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] -tauri-build = {version = "1.5.5", features = ["isolation"]} +tauri-build = {version = "1.5.5", features = ["isolation"] } [dependencies] anyhow = "1" async-trait = "0.1.81" -async_zip = {version = "0.0.17", features = ["full"]} +async_zip = {version = "0.0.17", features = ["full"] } auto-launch = "0.5.0" blake2 = "0.10" chrono = "0.4.38" @@ -29,26 +29,26 @@ keyring = {version = "3.0.5", features = [ "windows-native", "apple-native", "linux-native", -]} +] } libsqlite3-sys = {version = "0.25.1", features = [ "bundled", -]}# Required for tari_wallet +] }# Required for tari_wallet log = "0.4.22" log4rs = "1.3.0" minotari_node_grpc_client = {git = "https://github.com/tari-project/tari.git", branch = "development"} minotari_wallet_grpc_client = {git = "https://github.com/tari-project/tari.git", branch = "development"} -nix = {version = "0.29.0", features = ["signal"]} +nix = {version = "0.29.0", features = ["signal"] } nvml-wrapper = "0.10.0" open = "5" phraze = "0.3.15" rand = "0.8.5" regex = "1.10.5" -reqwest = {version = "0.12.5", features = ["stream", "json", "multipart"]} +reqwest = {version = "0.12.5", features = ["stream", "json", "multipart"] } sanitize-filename = "0.5" semver = "1.0.23" -sentry = {version = "0.34.0", features = ["anyhow"]} +sentry = {version = "0.34.0", features = ["anyhow"] } sentry-tauri = "0.3.0" -serde = {version = "1", features = ["derive"]} +serde = {version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.10.8" sys-locale = "0.3.1" @@ -58,7 +58,7 @@ tari_common = {git = "https://github.com/tari-project/tari.git", branch = "devel tari_common_types = {git = "https://github.com/tari-project/tari.git", branch = "development"} tari_core = {git = "https://github.com/tari-project/tari.git", branch = "development", features = [ "transactions", -]} +] } tari_crypto = "0.21.0" tari_key_manager = {git = "https://github.com/tari-project/tari.git", branch = "development"} tari_shutdown = {git = "https://github.com/tari-project/tari.git", branch = "development"} @@ -80,12 +80,12 @@ tauri = {version = "1.8.0", features = [ "icon-ico", "icon-png", "process-command-api", -]} +] } tauri-plugin-single-instance = {git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1"} thiserror = "1.0.26" -tokio = {version = "1", features = ["full"]} -tokio-util = {version = "0.7.11", features = ["compat"]} -xz2 = {version = "0.1.7", features = ["static"]}# static bind lzma +tokio = {version = "1", features = ["full"] } +tokio-util = {version = "0.7.11", features = ["compat"] } +xz2 = {version = "0.1.7", features = ["static"] }# static bind lzma zip = "2.2.0" [target.'cfg(windows)'.dependencies] @@ -94,7 +94,7 @@ winreg = "0.52.0" # needed for keymanager. TODO: Find a way of creating a keymanager without bundling sqlite chrono = "0.4.38" device_query = "2.1.0" -libsqlite3-sys = {version = "0.25.1", features = ["bundled"]} +libsqlite3-sys = {version = "0.25.1", features = ["bundled"] } log = "0.4.22" nvml-wrapper = "0.10.0" rand = "0.8.5" diff --git a/src-tauri/src/binaries/binaries_resolver.rs b/src-tauri/src/binaries/binaries_resolver.rs index e43b8c0c6..29738f24b 100644 --- a/src-tauri/src/binaries/binaries_resolver.rs +++ b/src-tauri/src/binaries/binaries_resolver.rs @@ -223,7 +223,7 @@ impl BinaryResolver { manager.read_local_versions().await; - if should_check_for_update { + if true { // Will populate Vec of downloaded versions that meet the requirements manager.check_for_updates().await; } diff --git a/src-tauri/src/github/request_client.rs b/src-tauri/src/github/request_client.rs index 06a968632..66bad259b 100644 --- a/src-tauri/src/github/request_client.rs +++ b/src-tauri/src/github/request_client.rs @@ -1,4 +1,4 @@ -use std::{ops::Div, sync::LazyLock}; +use std::sync::LazyLock; use anyhow::anyhow; use log::info; @@ -182,7 +182,7 @@ impl RequestClient { pub async fn check_if_cache_hits(&self, url: &str) -> Result { const MAX_RETRIES: u8 = 3; const MAX_WAIT_TIME: u64 = 30; - const DEFAULT_WAIT_TIME: u64 = 2; + const MIN_WAIT_TIME: u64 = 2; let mut retries = 0; loop { @@ -199,16 +199,14 @@ impl RequestClient { info!(target: LOG_TARGET, "Content length: {}", content_length); info!(target: LOG_TARGET, "Content length in mb: {}", self.convert_content_length_to_mb(content_length)); - let mut sleep_time = std::time::Duration::from_secs(DEFAULT_WAIT_TIME); + let mut sleep_time = std::time::Duration::from_secs(MIN_WAIT_TIME); if !content_length.eq(&0) { sleep_time = std::time::Duration::from_secs( - (self - .convert_content_length_to_mb(content_length) - .div(10.0) - .to_bits()) - .min(MAX_WAIT_TIME) - .max(2), + #[allow(clippy::cast_possible_truncation)] + ((self.convert_content_length_to_mb(content_length) / 10.0).trunc() as u64) + .min(MAX_WAIT_TIME) + .max(MIN_WAIT_TIME), ); }