Skip to content

Commit 69bb892

Browse files
committed
feat:auto download essential modules
1 parent 91a9d50 commit 69bb892

File tree

3 files changed

+135
-1
lines changed

3 files changed

+135
-1
lines changed

src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use tauri_plugin_opener::OpenerExt;
1818

1919
mod logging;
2020
mod manager;
21+
mod modules_dl;
2122

2223
use log::info;
2324
use tauri::{

src-tauri/src/manager.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
/// their state.
88
///
99
/// If a module crashes, the manager will notify the user and ask if they want to restart it.
10-
1110
#[cfg(unix)]
1211
use {
1312
nix::sys::signal::{self, Signal},
@@ -33,6 +32,7 @@ use std::{env, fs, thread};
3332
use tauri::menu::{CheckMenuItem, Menu, MenuItem, SubmenuBuilder};
3433
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
3534

35+
use crate::modules_dl::has_essential_modules;
3636
use crate::{get_app_handle, get_config, get_tray_id, HANDLE_CONDVAR};
3737

3838
#[derive(Debug)]
@@ -53,6 +53,8 @@ pub enum ModuleMessage {
5353
pub struct ManagerState {
5454
tx: Sender<ModuleMessage>,
5555
pub modules_running: BTreeMap<String, bool>,
56+
// TODO: the next four could be merged into one
57+
// modules_metadata hashmap? worse for readability
5658
pub modules_discovered: BTreeMap<String, PathBuf>,
5759
pub modules_pid: HashMap<String, u32>,
5860
pub modules_restart_count: HashMap<String, u32>,
@@ -103,6 +105,15 @@ impl ManagerState {
103105
let quit = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)
104106
.expect("failed to create quit menu item");
105107

108+
if !has_essential_modules(self.modules_discovered.keys().cloned().collect()) {
109+
// todo!()
110+
thread::spawn(|| {
111+
tauri::async_runtime::block_on(async {
112+
crate::modules_dl::download_modules().await.unwrap();
113+
});
114+
});
115+
}
116+
106117
let mut modules_submenu_builder = SubmenuBuilder::new(app, "Modules");
107118
for (module, running) in self.modules_running.iter() {
108119
let label = module;

src-tauri/src/modules_dl.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/// Downloads essential modules such as the window and afk watchers
2+
/// Module metadata is stored in a csv file that is downloaded
3+
/// the fields appear in the order below
4+
/// name,os,display_server,version,arch,release_date,link
5+
///
6+
/// More fields can be added as long as it maintains backward compatibility
7+
use crate::get_config;
8+
use csv::ReaderBuilder;
9+
use log::error;
10+
use regex::Regex;
11+
use std::{fs::File, io::Write, vec};
12+
use tauri_plugin_http::reqwest;
13+
14+
fn is_wayland() -> bool {
15+
std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland"
16+
}
17+
18+
async fn download_module(url: &str) -> Result<(), Box<dyn std::error::Error>> {
19+
let mut response = reqwest::get(url).await?;
20+
let file_name = url.split('/').last().unwrap();
21+
let file_path = get_config().defaults.discovery_path.clone().join(file_name);
22+
let mut file = File::create(file_path.clone())?;
23+
while let Some(chunk) = response.chunk().await? {
24+
file.write_all(&chunk)?;
25+
}
26+
// TODO: testing check if it matches correctly
27+
let tar_regex = Regex::new(r"(?i)\.tar(?:\.gz)?$").unwrap();
28+
if file_name.ends_with(".zip") {
29+
let output = std::process::Command::new("unzip")
30+
.arg(&file_path)
31+
.arg("-d")
32+
.arg(get_config().defaults.discovery_path.clone())
33+
.output()?;
34+
error!("{}", String::from_utf8_lossy(&output.stdout));
35+
} else if tar_regex.is_match(file_name) {
36+
let output = std::process::Command::new("tar")
37+
.arg("-xvf")
38+
.arg(&file_path)
39+
.arg("-C")
40+
.arg(get_config().defaults.discovery_path.clone())
41+
.output()?;
42+
error!("{}", String::from_utf8_lossy(&output.stdout));
43+
}
44+
Ok(())
45+
}
46+
47+
async fn fetch_releases_file() -> Result<String, Box<dyn std::error::Error>> {
48+
// TODO: use a better source
49+
let url = "https://gist.githubusercontent.com/0xbrayo/f7b25a2ff9ed24ce21fa8397837265b6/raw/120ddb3d31d7f009d66f070bd4a0dc06d3c0aacf/aw-releases.csv";
50+
let response = reqwest::get(url).await?;
51+
let body = response.text().await?;
52+
Ok(body)
53+
}
54+
55+
pub(crate) async fn download_modules() -> Result<(), Box<dyn std::error::Error>> {
56+
let releases = fetch_releases_file().await?;
57+
let mut reader = ReaderBuilder::new().from_reader(releases.as_bytes());
58+
59+
if cfg!(target_os = "linux") {
60+
let display_server = if is_wayland() { "wayland" } else { "x11" };
61+
for row in reader.records() {
62+
let row = row.expect("Malformed releases file");
63+
if &row[1] != "linux" {
64+
continue;
65+
}
66+
if !row[2].is_empty() && &row[2] != display_server {
67+
continue;
68+
}
69+
let url = &row[6];
70+
download_module(url).await?;
71+
}
72+
} else if cfg!(target_os = "windows") {
73+
for row in reader.records() {
74+
let row = row.expect("Malformed releases file");
75+
if &row[1] != "windows" {
76+
continue;
77+
}
78+
let url = &row[6];
79+
download_module(url).await?;
80+
}
81+
} else if cfg!(target_os = "macos") {
82+
for row in reader.records() {
83+
let row = row.expect("Malformed releases file");
84+
if &row[2] != "macos" {
85+
continue;
86+
}
87+
let url = &row[6];
88+
download_module(url).await?;
89+
}
90+
} else {
91+
// should be unreachable
92+
panic!("Unsupported OS");
93+
}
94+
Ok(())
95+
}
96+
97+
pub(crate) fn has_essential_modules(modules: Vec<String>) -> bool {
98+
// TODO: confirm that windows and mac the watchers are separate and not in the same module
99+
100+
let essential_modules = if cfg!(target_os = "linux") {
101+
if is_wayland() {
102+
vec!["aw-awatcher".to_string()]
103+
} else {
104+
vec![
105+
"aw-watcher-afk".to_string(),
106+
"aw-watcher-window".to_string(),
107+
]
108+
}
109+
} else {
110+
vec![
111+
"aw-watcher-afk".to_string(),
112+
"aw-watcher-window".to_string(),
113+
]
114+
};
115+
116+
for module in essential_modules {
117+
if !modules.iter().any(|m| m == &module) {
118+
return false;
119+
}
120+
}
121+
true
122+
}

0 commit comments

Comments
 (0)