From bf4f26f7b43927f4cb9efbb824ff707b0ce23c52 Mon Sep 17 00:00:00 2001 From: AHQ Miness Date: Fri, 29 Nov 2024 19:28:50 +0530 Subject: [PATCH] a new update, user wide installs --- src-ahqstore-types/Cargo.lock | 2 +- src-ahqstore-types/Cargo.toml | 2 +- src-ahqstore-types/README.md | 2 + src-ahqstore-types/src/app/install.rs | 2 +- src-ahqstore-types/src/app/other_fields.rs | 9 +- src-ahqstore-types/src/lib.rs | 3 +- src-service/Cargo.lock | 2 +- src-service/src/handlers/daemon/dwn.rs | 9 +- src-service/src/handlers/daemon/mod.rs | 42 ++++++++-- src-service/src/handlers/daemon/recv.rs | 10 ++- src-service/src/handlers/mod.rs | 12 ++- .../src/handlers/service/windows/exe/mod.rs | 15 +++- .../src/handlers/service/windows/mod.rs | 84 +++++++++++++++++-- .../src/handlers/service/windows/user/mod.rs | 21 +++++ .../src/handlers/service/windows/user/zip.rs | 7 ++ src-service/src/main.rs | 1 + src-service/src/utils/structs.rs | 18 ++++ 17 files changed, 206 insertions(+), 35 deletions(-) create mode 100644 src-service/src/handlers/service/windows/user/mod.rs create mode 100644 src-service/src/handlers/service/windows/user/zip.rs diff --git a/src-ahqstore-types/Cargo.lock b/src-ahqstore-types/Cargo.lock index f3925866..6a9c6568 100644 --- a/src-ahqstore-types/Cargo.lock +++ b/src-ahqstore-types/Cargo.lock @@ -19,7 +19,7 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahqstore-types" -version = "3.10.2" +version = "3.10.3" dependencies = [ "anyhow", "fuse-rust", diff --git a/src-ahqstore-types/Cargo.toml b/src-ahqstore-types/Cargo.toml index 5417e524..69302daf 100644 --- a/src-ahqstore-types/Cargo.toml +++ b/src-ahqstore-types/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ahqstore-types" description = "Standard types used by AHQ Store" -version = "3.10.2" +version = "3.10.3" edition = "2021" license-file = "../LICENSE.md" repository = "https://github.com/ahqsoftwares/tauri-ahq-store" diff --git a/src-ahqstore-types/README.md b/src-ahqstore-types/README.md index 7003eb48..bfdb9cfe 100644 --- a/src-ahqstore-types/README.md +++ b/src-ahqstore-types/README.md @@ -3,3 +3,5 @@ Types used by AHQ Store app **NOTE:** This crate is used internally by AHQ Store Organisation projects for sharing the unified app schema and protocol signatures + +THIS CRATE DOES NOT FOLLOW SEMANTIC VERSIONING diff --git a/src-ahqstore-types/src/app/install.rs b/src-ahqstore-types/src/app/install.rs index dcef9d6f..f3e3012a 100644 --- a/src-ahqstore-types/src/app/install.rs +++ b/src-ahqstore-types/src/app/install.rs @@ -25,7 +25,7 @@ pub struct InstallerOptions { #[allow(non_snake_case)] #[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr(feature = "js", wasm_bindgen(getter_with_clone))] +#[cfg_attr(feature = "js", wasm_bindgen)] pub enum WindowsInstallScope { User, Machine diff --git a/src-ahqstore-types/src/app/other_fields.rs b/src-ahqstore-types/src/app/other_fields.rs index 359acc64..ccec0914 100644 --- a/src-ahqstore-types/src/app/other_fields.rs +++ b/src-ahqstore-types/src/app/other_fields.rs @@ -25,19 +25,12 @@ pub enum InstallerFormat { #[doc = "🎯 Stable as of v2\n\n"] WindowsInstallerMsi, - #[doc = "🔬 Planned as of v2.5 or v3\n\n"] - /// **Doesn't work** - /// **⚠️ AHQ Store will act just like downloading from the web and running it ONCE[^1]** - /// - /// [^1]: You'll need to provide app's final location + #[doc = "🎯 Stable after v2\n\n"] WindowsInstallerExe, #[doc = "🔬 Planned as of v3\n\n"] /// **Doesn't work** /// **Won't be worked on, until other formats are supported** - /// **⚠️ AHQ Store will act just like downloading from the web and running it ONCE[^1]** - /// - /// [^1]: You'll need to provide app's final location WindowsUWPMsix, #[doc = "🎯 Stable as of v2\n\n"] diff --git a/src-ahqstore-types/src/lib.rs b/src-ahqstore-types/src/lib.rs index 14ed6680..ef3a9ae0 100644 --- a/src-ahqstore-types/src/lib.rs +++ b/src-ahqstore-types/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(dead_code, unused_imports, reason = "Conditional compilation")] +#![allow(dead_code, unused_imports, non_local_definitions, reason = "Conditional compilation")] use serde::{Deserialize, Serialize}; use serde_json::{from_str, to_string, to_string_pretty}; @@ -157,6 +157,7 @@ pub struct Library { pub progress: f64, pub max: u64, pub app: Option, + pub user: String } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/src-service/Cargo.lock b/src-service/Cargo.lock index 2c18d184..7606b4ab 100644 --- a/src-service/Cargo.lock +++ b/src-service/Cargo.lock @@ -30,7 +30,7 @@ dependencies = [ [[package]] name = "ahqstore-types" -version = "3.9.2" +version = "3.10.3" dependencies = [ "anyhow", "fuse-rust", diff --git a/src-service/src/handlers/daemon/dwn.rs b/src-service/src/handlers/daemon/dwn.rs index d5b4bb26..cb710c94 100644 --- a/src-service/src/handlers/daemon/dwn.rs +++ b/src-service/src/handlers/daemon/dwn.rs @@ -26,7 +26,6 @@ pub async fn handle(resp: &mut Library, state: &mut DaemonState, imp: &mut bool) println!("100 %"); let _ = x.file.flush(); let _ = x.file.sync_all(); - //let app = x.app.clone(); //Drop the File let mut data = replace(&mut state.data, None); @@ -38,7 +37,7 @@ pub async fn handle(resp: &mut Library, state: &mut DaemonState, imp: &mut bool) resp.status = AppStatus::AVScanning; let inst = get_installer_file(&x.app); - state.data = Some(DaemonData::AVScan((x.app, av::scan::scan_threaded(&inst)))); + state.data = Some(DaemonData::AVScan(x.app, av::scan::scan_threaded(&inst))); state.step = Step::AVScanning; *imp = true; } @@ -50,13 +49,13 @@ pub async fn handle(resp: &mut Library, state: &mut DaemonState, imp: &mut bool) pub async fn av_scan(resp: &mut Library, state: &mut DaemonState, imp: &mut bool) { let data = state.data.as_mut().unwrap(); - if let DaemonData::AVScan((app, x)) = data { + if let DaemonData::AVScan(app, x) = data { if x.is_finished() { *imp = true; let mut data = replace(&mut state.data, None); let data = data.expect("Impossible to be null"); - let DaemonData::AVScan((app, x)) = data else { + let DaemonData::AVScan(app, x) = data else { panic!("Impossible panic"); }; @@ -65,7 +64,7 @@ pub async fn av_scan(resp: &mut Library, state: &mut DaemonState, imp: &mut bool if !av_flagged.unwrap_or(true) { resp.status = AppStatus::Installing; - if let Some(x) = install_app(&app, resp.is_update).await { + if let Some(x) = install_app(&app, resp.is_update, &resp.user).await { state.step = Step::Installing; state.data = Some(DaemonData::Inst(x)); } else { diff --git a/src-service/src/handlers/daemon/mod.rs b/src-service/src/handlers/daemon/mod.rs index 20e67874..a0310cc7 100644 --- a/src-service/src/handlers/daemon/mod.rs +++ b/src-service/src/handlers/daemon/mod.rs @@ -11,6 +11,7 @@ use std::{ default, fs::File, future::{Future, IntoFuture}, + mem::replace, process::Child, sync::mpsc::{channel, Receiver, Sender}, thread::JoinHandle, @@ -21,7 +22,7 @@ use tokio::{ time::{sleep, Sleep}, }; -use crate::utils::{get_iprocess, ws_send}; +use crate::utils::{get_iprocess, structs::get_current_user, ws_send}; #[cfg(windows)] use crate::handlers::pipe; @@ -33,6 +34,9 @@ use super::{ uninstall_app, InstallResult, }; +#[cfg(windows)] +use super::list_user_apps; + pub static mut UPDATE_STATUS_REPORT: Option = None; pub static mut LIBRARY: Option> = None; @@ -92,7 +96,7 @@ pub enum Step { pub enum DaemonData { Dwn(DownloadData), - AVScan((AHQStoreApplication, JoinHandle>)), + AVScan(AHQStoreApplication, JoinHandle>), Inst(InstallResult), Unst(JoinHandle), None, @@ -297,7 +301,7 @@ pub async fn check_update() -> Option { .await; } - if let Some(x) = list_apps() { + let mut manage = async |x: Vec<(String, String)>, user: String| { for (id, ver) in x { if &ver == "custom" { continue; @@ -306,14 +310,40 @@ pub async fn check_update() -> Option { let app = get_app(0, id).await; match app { Response::AppData(_, id, app) => { + use ahqstore_types::{InstallerFormat, WindowsInstallScope}; + + // Skip if the user is not the currently logged in user + // Because WindowsInstallerExe cannot install if the user is not logged in + if let (Some(x), Some(y)) = (app.get_win_download(), app.get_win_options()) { + let scope = y.scope.as_ref().unwrap_or(&WindowsInstallScope::Machine); + + if matches!(&x.installerType, &InstallerFormat::WindowsInstallerExe) + && matches!(scope, &WindowsInstallScope::User) + { + if get_current_user().unwrap_or("") != &user { + continue; + } + } + } if &ver == &app.version { continue; } - to_update.push((id, app)); + to_update.push((id, app, user.clone())); } _ => {} } } + }; + + if let Some(x) = list_apps() { + manage(x, "machine".into()).await; + } + + #[cfg(windows)] + if let Some(x) = list_user_apps(None) { + for (user, apps) in x { + manage(apps, user).await; + } } let library = unsafe { LIBRARY.as_mut().unwrap() }; @@ -331,7 +361,7 @@ pub async fn check_update() -> Option { return Some(false); } - to_update.into_iter().for_each(|(id, app)| { + to_update.into_iter().for_each(|(id, app, user)| { library.push(Library { app_id: id.clone(), is_update: true, @@ -340,6 +370,7 @@ pub async fn check_update() -> Option { to: ToDo::Uninstall, max: 0, app: Some(app.clone()), + user: user.clone(), }); library.push(Library { app_id: id, @@ -349,6 +380,7 @@ pub async fn check_update() -> Option { to: ToDo::Install, app: Some(app), max: 0, + user, }); }); diff --git a/src-service/src/handlers/daemon/recv.rs b/src-service/src/handlers/daemon/recv.rs index 5fc94af5..fe2f6b11 100644 --- a/src-service/src/handlers/daemon/recv.rs +++ b/src-service/src/handlers/daemon/recv.rs @@ -3,7 +3,7 @@ use std::sync::mpsc::Receiver; use crate::{ handlers::{get_app, get_app_local}, - utils::{ws_send, ServiceIpc}, + utils::{structs::get_current_user, ws_send, ServiceIpc}, }; use super::{lib_msg, BETWEEN}; @@ -26,6 +26,10 @@ pub async fn recv( progress: 0.0, max: 0, app: None, + // It is important to clone here so that it can be processed correctly even if the user changes + user: get_current_user() + .expect("Impossible to be null") + .to_string(), }); } Command::UninstallApp(ref_id, app_id) => { @@ -38,6 +42,10 @@ pub async fn recv( max: 0, status: AppStatus::Pending, to: ToDo::Uninstall, + // It is important to clone here so that it can be processed correctly even if the user changes + user: get_current_user() + .expect("Impossible to be null") + .to_string(), }); } } diff --git a/src-service/src/handlers/mod.rs b/src-service/src/handlers/mod.rs index 8b5a6f59..34f0839c 100644 --- a/src-service/src/handlers/mod.rs +++ b/src-service/src/handlers/mod.rs @@ -10,7 +10,7 @@ use tokio::{io::AsyncWriteExt, spawn}; use crate::{ utils::{ get_iprocess, get_perms, - structs::{Command, ErrorType, Reason, Response}, + structs::{get_current_user, Command, ErrorType, Reason, Response}, }, write_log, }; @@ -65,7 +65,15 @@ pub fn handle_msg(admin: bool, data: String) { } (_, _, _, Command::ListApps(ref_id)) => { - if let Some(x) = list_apps() { + if let Some(mut x) = list_apps() { + let user = get_current_user().unwrap_or(""); + + if let Some(mut a) = list_user_apps(Some(user.into())) { + let (_, mut data) = a.remove(0); + drop(a); + x.extend(data); + } + let val = Response::as_msg(Response::ListApps(ref_id, x)); ws_send(&mut ws, &val).await; diff --git a/src-service/src/handlers/service/windows/exe/mod.rs b/src-service/src/handlers/service/windows/exe/mod.rs index f7218c79..41764c35 100644 --- a/src-service/src/handlers/service/windows/exe/mod.rs +++ b/src-service/src/handlers/service/windows/exe/mod.rs @@ -16,7 +16,13 @@ static mut COUNTER: RefId = 0; mod daemon; pub mod pipe; -pub async fn install(path: &str, app: &AHQStoreApplication, update: bool) -> Option { +pub async fn install( + path: &str, + app: &AHQStoreApplication, + update: bool, + _user: bool, + _username: &str, +) -> Option { if unsafe { !CONNECTED } { return None; } @@ -40,12 +46,19 @@ pub async fn install(path: &str, app: &AHQStoreApplication, update: bool) -> Opt resp.push(0); } + let windows_options = app.get_win_options()?; + let args = windows_options + .installerArgs + .as_ref() + .map_or_else(|| vec![], |x| x.iter().map(|x| x.as_str()).collect()); + let data = serde_json::to_string(&json!({ "display": app.appDisplayName, "id": app.appId, "icon": &icon_path, "path": &path, "count": count, + "args": args })) .ok()?; diff --git a/src-service/src/handlers/service/windows/mod.rs b/src-service/src/handlers/service/windows/mod.rs index 76daf140..4407bd9d 100644 --- a/src-service/src/handlers/service/windows/mod.rs +++ b/src-service/src/handlers/service/windows/mod.rs @@ -3,7 +3,7 @@ pub mod av; mod exe; mod msi; -use ahqstore_types::InstallerFormat; +use ahqstore_types::{InstallerFormat, WindowsInstallScope}; use mslnk::ShellLink; use serde_json::to_string_pretty; use std::{ @@ -15,15 +15,18 @@ use std::{ time::Duration, }; use tokio::spawn; +use user::{get_user_program_folder, get_user_programs, install_user_zip}; use crate::utils::{ get_installer_file, get_program_folder, get_programs, get_target_lnk, - structs::{AHQStoreApplication, AppData}, + structs::{get_current_user, AHQStoreApplication, AppData}, }; use super::{InstallResult, UninstallResult}; pub use exe::pipe; +mod user; + pub fn run(path: &str, args: &[&str]) -> Result { Command::new(path) .creation_flags(0x08000000) @@ -41,17 +44,33 @@ pub fn unzip(path: &str, dest: &str) -> Result { .spawn() } -pub async fn install_app(app: &AHQStoreApplication, update: bool) -> Option { +pub async fn install_app( + app: &AHQStoreApplication, + update: bool, + user: &str, +) -> Option { let file = get_installer_file(app); - let Some(win32) = app.get_win_download() else { - return None; - }; + let win32 = app.get_win_download()?; + let opt = app.get_win_options()?.scope.as_ref(); + let scope = opt.unwrap_or(&WindowsInstallScope::Machine); match win32.installerType { - InstallerFormat::WindowsZip => load_zip(&file, app), + InstallerFormat::WindowsZip => match scope { + &WindowsInstallScope::Machine => load_zip(&file, app), + &WindowsInstallScope::User => install_user_zip(&file, app, user), + }, InstallerFormat::WindowsInstallerMsi => install_msi(&file, app), - InstallerFormat::WindowsInstallerExe => exe::install(&file, app, update).await, + InstallerFormat::WindowsInstallerExe => { + exe::install( + &file, + app, + update, + matches!(scope, &WindowsInstallScope::User), + user, + ) + .await + } _ => None, } } @@ -196,3 +215,52 @@ pub fn list_apps() -> Option> { Some(vec) } + +pub fn list_user_apps(user: Option) -> Option)>> { + let Some(user) = user else { + let mut result: Vec<(String, Vec)> = vec![]; + + for x in fs::read_dir("C:\\Users").ok()? { + let x = x.ok()?; + let file = x.file_name().into_string().ok()?; + + if let Some(mut data) = list_user_apps(Some(file)) { + result.push(data.remove(0)); + } + } + + return Some(result); + }; + + let folder = get_user_programs(&user); + + let dirs = fs::read_dir(&folder).ok()?; + + let mut vec = vec![]; + + for dir in dirs { + let dir = dir.ok()?.file_name(); + let dir = dir.to_str().unwrap_or("unknown"); + + let version = fs::read_to_string(format!( + "{}\\{}", + &get_user_program_folder(&dir, &user), + "ahqStoreVersion" + )) + .unwrap_or("unknown".into()); + + if version == "unknown" { + let _ = fs::remove_dir_all(get_user_program_folder(&dir, &user)); + } else if version != "custom" { + if msi::is_msi(dir) { + if msi::exists(dir).unwrap_or(false) { + vec.push((dir.to_owned(), version)); + } + } else { + vec.push((dir.to_owned(), version)); + } + } + } + + Some(vec![(user, vec)]) +} diff --git a/src-service/src/handlers/service/windows/user/mod.rs b/src-service/src/handlers/service/windows/user/mod.rs new file mode 100644 index 00000000..8187f15d --- /dev/null +++ b/src-service/src/handlers/service/windows/user/mod.rs @@ -0,0 +1,21 @@ +mod zip; +pub use zip::*; + +use crate::utils::get_main_drive; + +pub(super) fn get_user_program_folder(app_id: &str, user: &str) -> String { + format!( + "{}\\Users\\{}\\AHQ Store Applications\\Programs\\{}", + &get_main_drive(), + user, + app_id, + ) +} + +pub(super) fn get_user_programs(user: &str) -> String { + format!( + "{}\\Users\\{}\\AHQ Store Applications\\Programs\\", + &get_main_drive(), + user, + ) +} \ No newline at end of file diff --git a/src-service/src/handlers/service/windows/user/zip.rs b/src-service/src/handlers/service/windows/user/zip.rs new file mode 100644 index 00000000..efd76669 --- /dev/null +++ b/src-service/src/handlers/service/windows/user/zip.rs @@ -0,0 +1,7 @@ +use ahqstore_types::AHQStoreApplication; + +use crate::handlers::InstallResult; + +pub fn install_user_zip(zip: &str, app: &AHQStoreApplication, user: &str) -> Option { + None +} diff --git a/src-service/src/main.rs b/src-service/src/main.rs index f6a2cb5d..2b218e70 100644 --- a/src-service/src/main.rs +++ b/src-service/src/main.rs @@ -1,4 +1,5 @@ #![allow(static_mut_refs)] +#![feature(async_closure)] #[cfg(windows)] use std::time::Duration; diff --git a/src-service/src/utils/structs.rs b/src-service/src/utils/structs.rs index 562b84f8..d32b0a58 100644 --- a/src-service/src/utils/structs.rs +++ b/src-service/src/utils/structs.rs @@ -1 +1,19 @@ pub use ahqstore_types::*; + +static mut CURRENT_USER: Option = None; + +pub fn set_current_user(user: String) { + unsafe { + CURRENT_USER = Some(user); + } +} + +pub fn rem_current_user() { + unsafe { + CURRENT_USER = None; + } +} + +pub fn get_current_user() -> Option<&'static str> { + unsafe { CURRENT_USER.as_ref().map(|x| x.as_str()) } +}