Skip to content

Commit f6aa63a

Browse files
committed
feat: v0.8.2
1 parent 718d8a8 commit f6aa63a

28 files changed

Lines changed: 1874 additions & 434 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"url": "https://github.com/chloecinders/emuNEX-client"
1010
},
1111
"private": true,
12-
"version": "0.8.1",
12+
"version": "0.8.2",
1313
"type": "module",
1414
"scripts": {
1515
"dev": "vite",

src-tauri/src/commands/emulator.rs

Lines changed: 184 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use futures_util::StreamExt;
88
use serde::{Deserialize, Serialize};
99
use tauri::{AppHandle, Runtime};
1010

11-
use crate::{store, ApiResponse};
11+
use crate::{
12+
commands::http::{perform_backend_request, BackendBody},
13+
store, ApiResponse,
14+
};
1215

1316
#[derive(Deserialize, Serialize, Debug, Clone)]
1417
pub struct ApiEmulator {
@@ -26,6 +29,14 @@ pub struct ApiEmulator {
2629
pub zipped: bool,
2730
pub file_size: i64,
2831
pub md5_hash: Option<String>,
32+
pub version: Option<String>,
33+
}
34+
35+
#[derive(Serialize, Debug, Clone)]
36+
pub struct ApiEmulatorExtra {
37+
#[serde(flatten)]
38+
pub emulator: ApiEmulator,
39+
pub source_server: String,
2940
}
3041

3142
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -49,6 +60,10 @@ pub struct StoreEmulator {
4960
pub zipped: bool,
5061
#[serde(default)]
5162
pub file_size: i64,
63+
#[serde(default)]
64+
pub version: Option<String>,
65+
#[serde(default)]
66+
pub source_server: Option<String>,
5267
}
5368

5469
#[tauri::command]
@@ -76,12 +91,12 @@ pub async fn fetch_server_emulators<R: Runtime>(
7691
platform
7792
);
7893

79-
let api_res = crate::commands::http::perform_backend_request(
94+
let api_res = perform_backend_request(
8095
&app,
8196
reqwest::Method::GET,
8297
&api_url,
8398
Some(&token),
84-
crate::commands::http::BackendBody::None,
99+
BackendBody::None,
85100
false,
86101
)
87102
.await?
@@ -95,60 +110,80 @@ pub async fn fetch_server_emulators<R: Runtime>(
95110
#[tauri::command]
96111
pub async fn fetch_all_server_emulators<R: Runtime>(
97112
app: AppHandle<R>,
98-
) -> Result<Vec<ApiEmulator>, String> {
99-
let store = store::get_current_store(&app)?;
100-
101-
let domain = store
102-
.get("domain")
103-
.and_then(|v| v.as_str().map(|s| s.to_string()))
104-
.ok_or("Domain not found")?;
105-
let token = store
106-
.get("token")
107-
.and_then(|v| v.as_str().map(|s| s.to_string()))
108-
.ok_or("Token not found")?;
113+
) -> Result<Vec<ApiEmulatorExtra>, String> {
114+
let global_store = store::get_global_store(&app)?;
115+
let domains: Vec<String> = global_store
116+
.get("domains")
117+
.and_then(|v| serde_json::from_value(v.clone()).ok())
118+
.unwrap_or_default();
109119

120+
let mut all_results = Vec::new();
110121
let platform = std::env::consts::OS.to_lowercase();
111-
let api_url = format!("{}/api/v1/emulators/all", domain.trim_end_matches('/'));
112122

113-
let api_res = crate::commands::http::perform_backend_request(
114-
&app,
115-
reqwest::Method::GET,
116-
&api_url,
117-
Some(&token),
118-
crate::commands::http::BackendBody::None,
119-
false,
120-
)
121-
.await?
122-
.into_json::<ApiResponse<Vec<ApiEmulator>>>()?;
123+
for domain in domains {
124+
let domain_store = match store::get_domain_store(&app, &domain) {
125+
Ok(s) => s,
126+
Err(_) => continue,
127+
};
123128

124-
let emulators = api_res.data.unwrap_or_default();
129+
let token = match domain_store
130+
.get("token")
131+
.and_then(|v| v.as_str().map(|s| s.to_string()))
132+
{
133+
Some(t) => t,
134+
None => continue,
135+
};
125136

126-
let filtered: Vec<ApiEmulator> = emulators
127-
.into_iter()
128-
.filter(|e| e.platform.to_lowercase() == platform)
129-
.collect();
137+
let api_url = format!("{}/api/v1/emulators/all", domain.trim_end_matches('/'));
138+
139+
if let Ok(res) = perform_backend_request(
140+
&app,
141+
reqwest::Method::GET,
142+
&api_url,
143+
Some(&token),
144+
BackendBody::None,
145+
false,
146+
)
147+
.await
148+
{
149+
if let Ok(api_res) = res.into_json::<ApiResponse<Vec<ApiEmulator>>>() {
150+
if let Some(emulators) = api_res.data {
151+
for e in emulators {
152+
if e.platform.to_lowercase() == platform {
153+
all_results.push(ApiEmulatorExtra {
154+
emulator: e,
155+
source_server: domain.clone(),
156+
});
157+
}
158+
}
159+
}
160+
}
161+
}
162+
}
130163

131-
Ok(filtered)
164+
Ok(all_results)
132165
}
133166

134167
#[tauri::command]
135168
pub async fn download_emulator<R: Runtime>(
136169
app: AppHandle<R>,
137170
console: String,
138171
emulator_id: Option<String>,
172+
keep_config: Option<bool>,
173+
source_server: Option<String>,
139174
) -> Result<(), String> {
140-
let current_store = store::get_current_store(&app)?;
141175
let global_store = store::get_global_store(&app)?;
176+
let domain = match source_server {
177+
Some(d) => d,
178+
None => store::get_current_domain(&app).ok_or("Domain not found")?,
179+
};
142180

143-
let domain = current_store
144-
.get("domain")
145-
.and_then(|v| v.as_str().map(|s| s.to_string()))
146-
.ok_or("Domain not found")?;
147-
let token = current_store
181+
let domain_store = store::get_domain_store(&app, &domain)?;
182+
let token = domain_store
148183
.get("token")
149184
.and_then(|v| v.as_str().map(|s| s.to_string()))
150185
.ok_or("Token not found")?;
151-
let storage_path = current_store
186+
let storage_path = domain_store
152187
.get("storage_path")
153188
.and_then(|v| v.as_str().map(|s| s.to_string()))
154189
.ok_or("Storage path not found")?;
@@ -162,12 +197,12 @@ pub async fn download_emulator<R: Runtime>(
162197
platform
163198
);
164199

165-
let api_res = crate::commands::http::perform_backend_request(
200+
let api_res = perform_backend_request(
166201
&app,
167202
reqwest::Method::GET,
168203
&api_url,
169204
Some(&token),
170-
crate::commands::http::BackendBody::None,
205+
BackendBody::None,
171206
false,
172207
)
173208
.await?
@@ -207,12 +242,12 @@ pub async fn download_emulator<R: Runtime>(
207242
emulator.binary_path.trim_start_matches('/')
208243
);
209244

210-
let response = crate::commands::http::perform_backend_request(
245+
let response = perform_backend_request(
211246
&app,
212247
reqwest::Method::GET,
213248
&download_url,
214249
Some(&token),
215-
crate::commands::http::BackendBody::None,
250+
BackendBody::None,
216251
true,
217252
)
218253
.await?
@@ -299,12 +334,18 @@ pub async fn download_emulator<R: Runtime>(
299334

300335
if let Some(existing) = stored_emulators.get_mut(&server_id) {
301336
existing.binary_path = final_binary_path.to_string_lossy().to_string();
302-
existing.run_command = emulator.run_command.clone();
303-
existing.save_path = emulator.save_path.clone();
304-
existing.save_extensions = emulator.save_extensions.clone();
305-
existing.input_config_file = emulator.input_config_file.clone();
306-
existing.input_mapper = emulator.input_mapper.clone();
307-
existing.zipped = emulator.zipped;
337+
let should_keep = keep_config.unwrap_or(false);
338+
339+
if !should_keep {
340+
existing.run_command = emulator.run_command.clone();
341+
existing.save_path = emulator.save_path.clone();
342+
existing.save_extensions = emulator.save_extensions.clone();
343+
existing.input_config_file = emulator.input_config_file.clone();
344+
existing.input_mapper = emulator.input_mapper.clone();
345+
existing.zipped = emulator.zipped;
346+
}
347+
existing.version = emulator.version.clone();
348+
existing.source_server = Some(domain.clone());
308349

309350
for c in &emulator.consoles {
310351
if !existing.consoles.contains(c) {
@@ -327,6 +368,8 @@ pub async fn download_emulator<R: Runtime>(
327368
input_mapper: emulator.input_mapper,
328369
zipped: emulator.zipped,
329370
file_size: emulator.file_size,
371+
version: emulator.version,
372+
source_server: Some(domain.clone()),
330373
},
331374
);
332375
}
@@ -489,3 +532,96 @@ pub async fn migrate_emulator_files<R: Runtime>(app: AppHandle<R>) -> Result<(),
489532

490533
Ok(())
491534
}
535+
536+
#[tauri::command]
537+
pub async fn refresh_emulator_config<R: Runtime>(
538+
app: AppHandle<R>,
539+
emulator_id: String,
540+
) -> Result<(), String> {
541+
let global_store = store::get_global_store(&app)?;
542+
543+
let mut stored_emulators: HashMap<String, StoreEmulator> = global_store
544+
.get("emulators")
545+
.and_then(|v| serde_json::from_value(v.clone()).ok())
546+
.unwrap_or_default();
547+
548+
let existing = stored_emulators
549+
.get(&emulator_id)
550+
.ok_or("Emulator not found locally")?;
551+
552+
if emulator_id.starts_with("custom-") {
553+
return Err("Cannot refresh config for custom emulator".to_string());
554+
}
555+
556+
let domain = existing
557+
.source_server
558+
.clone()
559+
.or_else(|| store::get_current_domain(&app))
560+
.ok_or("Source folder not known for this emulator")?;
561+
562+
let domain_store = store::get_domain_store(&app, &domain)?;
563+
let token = domain_store
564+
.get("token")
565+
.and_then(|v| v.as_str().map(|s| s.to_string()))
566+
.ok_or_else(|| format!("Login session expired for server {}", domain))?;
567+
568+
let platform = std::env::consts::OS.to_lowercase();
569+
let api_url = format!("{}/api/v1/emulators/all", domain.trim_end_matches('/'));
570+
571+
let api_res = perform_backend_request(
572+
&app,
573+
reqwest::Method::GET,
574+
&api_url,
575+
Some(&token),
576+
BackendBody::None,
577+
false,
578+
)
579+
.await?
580+
.into_json::<ApiResponse<Vec<ApiEmulator>>>()?;
581+
582+
let emulators = api_res.data.unwrap_or_default();
583+
let safe_domain = domain
584+
.replace(|c: char| !c.is_alphanumeric(), "_")
585+
.to_lowercase();
586+
587+
let mut updated = false;
588+
for server_emu in emulators {
589+
if server_emu.platform.to_lowercase() != platform {
590+
continue;
591+
}
592+
593+
let expected_server_id = format!("server-{}-{}", safe_domain, server_emu.id);
594+
595+
if expected_server_id == emulator_id {
596+
if let Some(existing) = stored_emulators.get_mut(&emulator_id) {
597+
existing.run_command = server_emu.run_command;
598+
existing.save_path = server_emu.save_path;
599+
existing.save_extensions = server_emu.save_extensions;
600+
existing.input_config_file = server_emu.input_config_file;
601+
existing.input_mapper = server_emu.input_mapper;
602+
existing.zipped = server_emu.zipped;
603+
existing.file_size = server_emu.file_size;
604+
605+
for c in &server_emu.consoles {
606+
if !existing.consoles.contains(c) {
607+
existing.consoles.push(c.clone());
608+
}
609+
}
610+
updated = true;
611+
break;
612+
}
613+
}
614+
}
615+
616+
if !updated {
617+
return Err("Server emulator config not found or mismatched.".to_string());
618+
}
619+
620+
global_store.set(
621+
"emulators",
622+
serde_json::to_value(stored_emulators).map_err(|e| e.to_string())?,
623+
);
624+
global_store.save().map_err(|e| e.to_string())?;
625+
626+
Ok(())
627+
}

0 commit comments

Comments
 (0)