Skip to content

Commit afbfdb1

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

File tree

3 files changed

+143
-1
lines changed

3 files changed

+143
-1
lines changed

src-tauri/src/lib.rs

+1
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

+12-1
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

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

0 commit comments

Comments
 (0)