1use std::{env, fs};
2use std::fs::File;
3use std::io::Cursor;
4use dotenv::from_path;
5use serde::{Deserialize, Serialize};
6use crate::{paths, PisPasResult};
7
8pub async fn install_sharing(file_path: &str, args: &[&str]) -> std::process::ExitStatus {
9 let full_command = format!(
11 "Start-Process '{}' -ArgumentList '{}' -Wait",
12 file_path,
13 args.join(" ")
14 );
15
16 println!("Executing: {}", full_command);
17 let status = std::process::Command::new("powershell")
18 .arg("-Command")
19 .arg(&full_command)
20 .status()
21 .expect("No se pudo ejecutar el instalador");
22
23 println!("Install Script ended with status: {}", status);
24 status
25}
26
27#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
56pub struct ConfigEnv {
57 pub service_name: String,
60 pub service_vers: String,
62 pub pispas_host: String,
64 pub remote_host: String,
67 pub remote_port: u16,
69 pub remote_ussl: bool,
71 pub local_host: String,
74 pub local_port: u16,
76 pub local_ussl: bool,
80 pub modules: Vec<String>,
83 pub list_printers: Option<Vec<String>>,
86}
87
88impl Default for ConfigEnv {
89 fn default() -> Self {
90 Self {
91 service_name: "local_service".to_string(),
92 service_vers: env::var("SERVICE_VERS").unwrap_or_else(|_| "1.0.0.0".to_string()),
93 pispas_host: env::var("PISPAS_HOST").unwrap_or_else(|_| "api.unpispas.es".to_string()),
94 remote_host: env::var("REMOTE_HOST").unwrap_or_else(|_| "wss.unpispas.es".to_string()),
95 remote_port: env::var("REMOTE_PORT")
96 .unwrap_or_else(|_| "443".to_string())
97 .parse()
98 .unwrap_or(443),
99 remote_ussl: env::var("REMOTE_USSL")
100 .unwrap_or_else(|_| "true".to_string())
101 .eq_ignore_ascii_case("true"),
102 local_host: env::var("LOCAL_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
103 local_port: env::var("LOCAL_PORT")
104 .unwrap_or_else(|_| "5005".to_string())
105 .parse()
106 .unwrap_or(5005),
107 local_ussl: env::var("LOCAL_USSL")
108 .unwrap_or_else(|_| "true".to_string())
109 .eq_ignore_ascii_case("true"),
110 modules: env::var("MODULES")
111 .unwrap_or_else(|_| "base,print".to_string()) .split(',')
113 .map(|s| s.trim().to_string())
114 .collect(),
115 list_printers: None, }
117 }
118}
119
120impl ConfigEnv {
121 pub fn load() -> Self {
122 let env_path = paths::env_file_path();
123 tracing::info!("Loading cfg from {}", env_path.display());
124
125 if env_path.exists() {
127 from_path(&env_path).ok();
128 } else {
129 init_env();
130 }
131
132 Self {
133 service_name: env::var("SERVICE_NAME").unwrap_or_else(|_| "unpispas_pdfwritter".to_string()),
134 service_vers: env::var("SERVICE_VERS").unwrap_or_else(|_| "1.0.0.2".to_string()),
135 pispas_host: env::var("PISPAS_HOST").unwrap_or_else(|_| "api.unpispas.es".to_string()),
136 remote_host: env::var("REMOTE_HOST").unwrap_or_else(|_| "wss.unpispas.es".to_string()),
137 remote_port: env::var("REMOTE_PORT")
138 .unwrap_or_else(|_| "443".to_string())
139 .parse()
140 .unwrap_or(8765),
141 remote_ussl: env::var("REMOTE_USSL")
142 .unwrap_or_else(|_| "true".to_string())
143 .parse()
144 .unwrap_or(false),
145 local_host: env::var("LOCAL_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
146 local_port: env::var("LOCAL_PORT")
147 .unwrap_or_else(|_| "5005".to_string())
148 .parse()
149 .unwrap_or(5005),
150 local_ussl: env::var("LOCAL_USSL")
151 .unwrap_or_else(|_| "true".to_string())
152 .eq_ignore_ascii_case("true"),
153 modules: env::var("MODULES")
154 .unwrap_or_else(|_| "base,print".to_string()) .split(',')
156 .map(|s| s.trim().to_string())
157 .collect(), list_printers: None, }
160 }
161
162 pub fn change_service_name(&mut self, new_name: &str) {
163 self.service_name = new_name.to_string();
164 }
165
166 pub fn change_service_vers(&mut self, new_vers: &str) {
167 self.service_vers = new_vers.to_string();
168 }
169
170 pub fn change_pispas_host(&mut self, new_host: &str) {
171 self.pispas_host = new_host.to_string();
172 }
173
174 pub fn change_remote_host(&mut self, new_host: &str) {
175 self.remote_host = new_host.to_string();
176 }
177
178 pub fn change_remote_port(&mut self, new_port: u16) {
179 self.remote_port = new_port;
180 }
181
182 pub fn change_remote_ussl(&mut self, ussl: bool) {
183 self.remote_ussl = ussl;
184 }
185
186 pub fn change_local_host(&mut self, new_host: &str) {
187 self.local_host = new_host.to_string();
188 }
189
190 pub fn change_local_port(&mut self, new_port: u16) {
191 self.local_port = new_port;
192 }
193
194 pub fn change_local_ussl(&mut self, ussl: bool) {
195 self.local_ussl = ussl;
196 }
197
198 pub fn change_modules(&mut self, new_modules: Vec<String>) {
199 self.modules = new_modules;
200 }
201
202 pub fn save(&self) {
203 let list_printers_str = self.list_printers.as_ref().map_or("".to_string(), |printers| printers.join(","));
204 tracing::info!("Saving config: {:?}", self);
205 let env_content = format!(
206 "SERVICE_NAME={}\nSERVICE_VERS={}\nPISPAS_HOST={}\nREMOTE_HOST={}\nREMOTE_PORT={}\nREMOTE_USSL={}\nLOCAL_HOST={}\nLOCAL_PORT={}\nLOCAL_USSL={}\nMODULES={}\nLIST_PRINTERS={}\n",
207 self.service_name,
208 self.service_vers,
209 self.pispas_host,
210 self.remote_host,
211 self.remote_port,
212 self.remote_ussl,
213 self.local_host,
214 self.local_port,
215 self.local_ussl,
216 self.modules.join(","),
217 list_printers_str
218 );
219
220 let env_path = crate::paths::env_file_path();
221
222 if !paths::bin_dir().exists() {
223 fs::create_dir_all(paths::bin_dir()).unwrap();
224 }
225
226 println!("Saving config to {}", env_path.display());
227
228 match fs::write(&env_path, env_content) {
229 Ok(_) => tracing::info!("Config saved successfully to {}", env_path.display()),
230 Err(e) => {
231 println!("Failed to save config to {}: {}", env_path.display(), e);
232 tracing::error!("Failed to save config to {}: {}", env_path.display(), e)
233 },
234 }
235 }
236}
237fn init_env() {
238 let bin_dir = paths::bin_dir();
239 let env_path = bin_dir.join(".env");
240
241 let service_name = crate::natives::api::get_name_service();
242
243
244 if !env_path.exists() {
245 let default_env_content = format!(r#"SERVICE_NAME={}
246SERVICE_VERS=1.0.0.3
247LOCAL_HOST=127.0.0.1
248LOCAL_PORT=5005
249LOCAL_USSL=true
250REMOTE_USSL=true
251REMOTE_HOST=wss.unpispas.es
252REMOTE_PORT=443
253PISPAS_HOST=api.unpispas.es
254MODULES=base,print
255LIST_PRINTERS=POS80, CommandViewer
256"#, service_name);
257 if !bin_dir.exists() {
258 match fs::create_dir_all(&bin_dir) {
259 Ok(_) => tracing::info!("Created bin directory at {}", bin_dir.display()),
260 Err(e) => {
261 tracing::error!("Failed to create bin directory at {}: {}", bin_dir.display(), e);
262 }
263 }
264 }
265
266 match fs::write(&env_path, default_env_content) {
267 Ok(_) => tracing::info!("Created default .env file at {}", env_path.display()),
268 Err(e) => {
269 tracing::error!("Failed to create .env file at {}: {}", env_path.display(), e);
270 }
271 }
272 }
273
274 from_path(env_path).ok();
275}
276
277
278
279pub fn open_folder(path: &str) {
280 let _ = std::process::Command::new("cmd")
281 .args(&["/C", "explorer", &path])
282 .spawn();
283}
284
285
286
287
288
289fn get_last_modified_time(path: &std::path::Path) -> std::io::Result<std::time::SystemTime> {
290 let metadata = fs::metadata(path)?;
291 metadata.modified()
292}
293
294pub fn delete_folders_with_prefix(path: &str, prefix: &str) -> std::io::Result<()> {
295 let system32_path = std::path::Path::new(path);
296 let mut latest_folder: Option<(std::time::SystemTime, std::path::PathBuf)> = None;
297
298 for entry in fs::read_dir(system32_path)? {
300 let entry = entry?;
301 let path = entry.path();
302
303 if path.is_dir() {
305 if let Some(folder_name) = path.file_name() {
306 if let Some(folder_name_str) = folder_name.to_str() {
307 if folder_name_str.starts_with(prefix) {
308 let modified_time = get_last_modified_time(&path)?;
309
310 if let Some((latest_time, _)) = &latest_folder {
312 if modified_time > *latest_time {
313 if let Some((_, old_path)) = latest_folder.take() {
315 tracing::info!("Deleting older folder => {}", old_path.display());
316 fs::remove_dir_all(&old_path)?;
317 }
318 latest_folder = Some((modified_time, path));
320 } else {
321 tracing::info!("Deleting older folder => {}", path.display());
323 fs::remove_dir_all(&path)?;
324 }
325 } else {
326 latest_folder = Some((modified_time, path));
328 }
329 }
330 }
331 }
332 }
333 }
334
335 Ok(())
336}
337
338use include_dir::{include_dir, Dir};
339use zip::read::ZipArchive;
340pub const RESOURCES_WIN: Dir = include_dir!("resources/win");
341pub const RESOURCES_TOOLS: Dir = include_dir!("resources/tools");
342
343pub fn extract_all_resources(destination: &std::path::Path, resources: Dir) -> PisPasResult<()> {
344 for file in resources.files() {
345 let file_path = file.path();
346
347 let final_destination = match file_path.file_name() {
348 Some(filename) if filename == crate::SERVICE_PYTHON_NAME =>
349 destination.parent().unwrap_or(destination).join(filename),
350 Some(filename) => destination.join(filename),
351 None => continue,
352 };
353
354 if let Some(parent) = final_destination.parent() {
355 if !parent.exists() {
356 fs::create_dir_all(parent)?;
357 }
358 }
359
360 if file_path.extension().map_or(false, |ext| ext == "zip") {
361 tracing::info!("Extracting zip file => {file_path:?}");
362 let zip_destination = final_destination.parent().unwrap_or(destination);
364
365 let reader = Cursor::new(file.contents());
366 let mut archive = ZipArchive::new(reader)?;
367
368 for i in 0..archive.len() {
369 let mut zip_file = archive.by_index(i)?;
370 let outpath = zip_destination.join(zip_file.mangled_name());
371
372 if (&*zip_file.name()).ends_with('/') {
373 fs::create_dir_all(&outpath)?;
374 } else {
375 if let Some(p) = outpath.parent() {
376 if !p.exists() {
377 fs::create_dir_all(&p)?;
378 }
379 }
380 let mut outfile = File::create(&outpath)?;
381 std::io::copy(&mut zip_file, &mut outfile)?;
382 }
383 }
384 } else {
385 if final_destination.exists() {
386 let content = fs::read(&final_destination)?;
387
388 if content != file.contents() {
389 tracing::info!("Updating file => {:?}", final_destination);
390 fs::write(&final_destination, file.contents())?;
391 } else {
392 tracing::info!("File already up-to-date => {:?}", final_destination);
393 }
394 } else {
395 tracing::info!("Extracting new file => {:?}", final_destination);
396 fs::write(&final_destination, file.contents())?;
397 }
398 }
399 }
400
401 for dir in resources.dirs() {
403 let dir_name = dir.path().file_name().unwrap_or_default();
404 let final_destination = destination.join(dir_name);
405 if !final_destination.exists() {
406 fs::create_dir_all(&final_destination)?;
407 }
408 extract_all_resources(&final_destination, dir.clone())?;
409 }
410
411 Ok(())
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use std::fs;
418
419 #[test]
420 fn test_config_env_default() {
421 let config = ConfigEnv::default();
422
423 assert_eq!(config.service_name, "local_service");
424 assert!(!config.pispas_host.is_empty());
425 assert!(!config.remote_host.is_empty());
426 assert!(config.remote_port > 0);
427 assert!(config.local_port > 0);
428 assert!(!config.modules.is_empty());
429 }
430
431 #[test]
432 fn test_config_env_change_service_name() {
433 let mut config = ConfigEnv::default();
434 let new_name = "new_service_name";
435
436 config.change_service_name(new_name);
437 assert_eq!(config.service_name, new_name);
438 }
439
440 #[test]
441 fn test_config_env_change_service_vers() {
442 let mut config = ConfigEnv::default();
443 let new_vers = "2.0.0";
444
445 config.change_service_vers(new_vers);
446 assert_eq!(config.service_vers, new_vers);
447 }
448
449 #[test]
450 fn test_config_env_change_pispas_host() {
451 let mut config = ConfigEnv::default();
452 let new_host = "new.api.example.com";
453
454 config.change_pispas_host(new_host);
455 assert_eq!(config.pispas_host, new_host);
456 }
457
458 #[test]
459 fn test_config_env_change_remote_host() {
460 let mut config = ConfigEnv::default();
461 let new_host = "new.remote.example.com";
462
463 config.change_remote_host(new_host);
464 assert_eq!(config.remote_host, new_host);
465 }
466
467 #[test]
468 fn test_config_env_change_remote_port() {
469 let mut config = ConfigEnv::default();
470 let new_port = 8080;
471
472 config.change_remote_port(new_port);
473 assert_eq!(config.remote_port, new_port);
474 }
475
476 #[test]
477 fn test_config_env_change_remote_ussl() {
478 let mut config = ConfigEnv::default();
479
480 config.change_remote_ussl(false);
481 assert_eq!(config.remote_ussl, false);
482
483 config.change_remote_ussl(true);
484 assert_eq!(config.remote_ussl, true);
485 }
486
487 #[test]
488 fn test_config_env_change_local_host() {
489 let mut config = ConfigEnv::default();
490 let new_host = "192.168.1.1";
491
492 config.change_local_host(new_host);
493 assert_eq!(config.local_host, new_host);
494 }
495
496 #[test]
497 fn test_config_env_change_local_port() {
498 let mut config = ConfigEnv::default();
499 let new_port = 9000;
500
501 config.change_local_port(new_port);
502 assert_eq!(config.local_port, new_port);
503 }
504
505 #[test]
506 fn test_config_env_change_modules() {
507 let mut config = ConfigEnv::default();
508 let new_modules = vec!["module1".to_string(), "module2".to_string()];
509
510 config.change_modules(new_modules.clone());
511 assert_eq!(config.modules, new_modules);
512 }
513
514 #[test]
515 fn test_config_env_default_modules_parsing() {
516 let config = ConfigEnv::default();
517 assert!(!config.modules.is_empty());
519 }
520
521 #[test]
522 fn test_delete_folders_with_prefix() {
523 let temp_dir = std::env::temp_dir().join("test_delete_prefix");
525 let prefix = "test-prefix-";
526
527 let _ = fs::remove_dir_all(&temp_dir);
529 fs::create_dir_all(&temp_dir).unwrap();
530
531 let folder1 = temp_dir.join(format!("{}folder1", prefix));
533 let folder2 = temp_dir.join(format!("{}folder2", prefix));
534 let other_folder = temp_dir.join("other-folder");
535
536 fs::create_dir_all(&folder1).unwrap();
537 fs::create_dir_all(&folder2).unwrap();
538 fs::create_dir_all(&other_folder).unwrap();
539
540 let file1 = folder1.join("file.txt");
542 fs::write(&file1, "content").unwrap();
543
544 std::thread::sleep(std::time::Duration::from_millis(100));
546
547 let file2 = folder2.join("file.txt");
549 fs::write(&file2, "content").unwrap();
550
551 let result = delete_folders_with_prefix(temp_dir.to_str().unwrap(), prefix);
553 assert!(result.is_ok(), "delete_folders_with_prefix should succeed");
554
555 let entries: Vec<_> = fs::read_dir(&temp_dir)
557 .unwrap()
558 .filter_map(|e| e.ok())
559 .collect();
560
561 let prefix_folders: Vec<_> = entries
562 .iter()
563 .filter(|e| {
564 e.path().is_dir() &&
565 e.path().file_name()
566 .and_then(|n| n.to_str())
567 .map(|s| s.starts_with(prefix))
568 .unwrap_or(false)
569 })
570 .collect();
571
572 assert!(prefix_folders.len() <= 1, "Should keep at most one folder with prefix");
574
575 assert!(other_folder.exists(), "Non-prefix folder should not be deleted");
577
578 let _ = fs::remove_dir_all(&temp_dir);
580 }
581}
582