CH-J Toolsspráva serverů přes SSH
Menu
Vydané zdroje

CH-J Tools

Procházejte adresáře a soubory konkrétního vydání aplikace.

Stáhnout zdroje ZIP
CH-J / Server Manager 872 souborů
core-20261030T220507Z-61e4558d889f Přidáno: · Sestaveno:
module_api.rs Přidáno: Datum není evidováno 29,6 KB · 412 řádků
Kód Stáhnout soubor
1//! Public Module API commands (Plugin API 1.7.0 compatible). Every command checks the
2//! calling module window and its manifest permission before touching a Core service.
4use super::{module_call, module_result, text, CommandResult};
5use crate::core::app::CoreSlot;
6use crate::core::error::{CoreError, CoreResult};
7use crate::core::logging::{normalize_limit, read_tail};
8use serde_json::{json, Value};
9use tauri::{Manager, State, Webview};
11macro_rules! module_cmd {
12 ($name:ident, $channel:expr, $perm:expr, |$core:ident, $ctx:ident, $p:ident| $body:expr) => {
13 #[tauri::command]
14 pub async fn $name(webview: Webview, slot: State<'_, CoreSlot>, payload: Option<Value>) -> CommandResult {
15 #[allow(unused_variables)]
16 let ($core, $ctx) = module_call(&webview, &slot, $perm)?;
17 #[allow(unused_variables)]
18 let $p = super::payload(payload);
19 let result: CoreResult<Value> = async { $body }.await;
20 module_result($channel, result)
21 }
22 };
25/// Native dialog texts of the Hash & Checksum module come from its own catalogs (as in 0.1.0).
26fn hash_text(core: &crate::core::app::Core, key: &str) -> String {
27 let dir = core.paths.bundled_modules().join("chj.hash-checksum").join("0.0.1").join("ui").join("i18n");
28 core.i18n
29 .chain(&core.language())
30 .iter()
31 .find_map(|code| {
32 std::fs::read(dir.join(format!("{code}.json")))
33 .ok()
34 .and_then(|bytes| serde_json::from_slice::<Value>(&bytes).ok())
35 .and_then(|catalog| catalog.get(key).and_then(Value::as_str).map(str::to_string))
36 })
37 .unwrap_or_else(|| key.to_string())
40/// `payload.options || {}`.
41fn options(payload: &Value) -> Value {
42 payload.get("options").filter(|v| v.is_object()).cloned().unwrap_or_else(|| json!({}))
45/// JavaScript truthiness for flags passed as `Boolean(value)`.
46fn truthy(value: &Value) -> bool {
47 match value {
48 Value::Null => false,
49 Value::Bool(b) => *b,
50 Value::Number(n) => n.as_f64().is_some_and(|f| f != 0.0 && !f.is_nan()),
51 Value::String(s) => !s.is_empty(),
52 _ => true,
53 }
56/// 0.1.0 `ids(payload)`.
57fn transfer_ids(payload: &Value) -> Value {
58 match &payload["ids"] {
59 Value::String(s) if s == "all" => json!("all"),
60 Value::Array(list) => Value::Array(list.iter().take(20000).map(|v| json!(v.as_str().map(str::to_string).unwrap_or_else(|| v.to_string()))).collect()),
61 other => json!([other.as_str().map(str::to_string).unwrap_or_else(|| other.to_string())]),
62 }
65/// Explicit file/folder choice; both native pickers support multiple selections on every OS.
66async fn pick_upload_paths(core: &crate::core::app::Core, label: &str) -> Vec<std::path::PathBuf> {
67 use tauri_plugin_dialog::{DialogExt, MessageDialogButtons};
68 let title = core.t("native.files.selectUpload");
69 let parent = core.app.get_webview_window(label);
70 let files = core.t("chrome.files");
71 let folders = core.t("chrome.folders");
72 let (tx, rx) = tokio::sync::oneshot::channel();
73 let mut dialog = core.app.dialog().message(&title).title(&title)
74 .buttons(MessageDialogButtons::YesNoCancelCustom(files.clone(), folders.clone(), core.t("chrome.cancel")));
75 if let Some(window) = &parent { dialog = dialog.parent(window); }
76 dialog.show_with_result(move |result| { let _ = tx.send(result); });
77 let Ok(choice) = rx.await else { return Vec::new() };
78 choose_upload_paths(choice, &files, &folders,
79 crate::dialogs::pick_files(&core.app, parent.as_ref(), Some(&title), &[]),
80 async {
81 let (tx, rx) = tokio::sync::oneshot::channel();
82 let mut dialog = core.app.dialog().file().set_title(&title).set_can_create_directories(true);
83 if let Some(window) = &parent { dialog = dialog.set_parent(window); }
84 dialog.pick_folders(move |paths| {
85 let _ = tx.send(paths.unwrap_or_default().into_iter().filter_map(|p| p.into_path().ok()).collect());
86 });
87 rx.await.unwrap_or_default()
88 }).await
91async fn choose_upload_paths(
92 choice: tauri_plugin_dialog::MessageDialogResult, files: &str, folders: &str,
93 pick_files: impl std::future::Future<Output = Vec<std::path::PathBuf>>,
94 pick_folders: impl std::future::Future<Output = Vec<std::path::PathBuf>>,
95) -> Vec<std::path::PathBuf> {
96 use tauri_plugin_dialog::MessageDialogResult;
97 if choice == MessageDialogResult::Yes || choice == MessageDialogResult::Custom(files.to_string()) {
98 pick_files.await
99 } else if choice == MessageDialogResult::No || choice == MessageDialogResult::Custom(folders.to_string()) {
100 pick_folders.await
101 } else { Vec::new() }
104// --- window, language, sessions --------------------------------------------------------------
106module_cmd!(module_get_info, "plugin:getInfo", None, |core, ctx, p| Ok(ctx.manifest.clone()));
107module_cmd!(module_ui_get_language, "plugin:ui:getLanguage", None, |core, ctx, p| Ok(json!(core.language())));
108module_cmd!(module_close, "plugin:close", None, |core, ctx, p| {
109 core.services.modules.close_label(&core, &ctx.label);
110 Ok(json!({ "ok": true }))
111});
112module_cmd!(module_window_minimize, "plugin:window:minimize", None, |core, ctx, p| {
113 core.services.modules.minimize_label(&core, &ctx.label, true);
114 Ok(json!({ "ok": true }))
115});
116module_cmd!(module_sessions_list, "plugin:sessions:list", Some("session.read"), |core, ctx, p| Ok(Value::Array(core.services.sessions.list())));
118// --- system monitor ---------------------------------------------------------------------------
120module_cmd!(module_system_metrics, "plugin:system:metrics", Some("system.metrics.read"), |core, ctx, p| {
121 crate::core::ssh::metrics::read_system_metrics(&core.services.sessions, text(&p, "sessionId")).await
122});
123module_cmd!(module_system_virtual_memory, "plugin:system:virtualMemory", Some("system.metrics.read"), |core, ctx, p| {
124 core.services.virtual_memory.read(text(&p, "sessionId"), &json!({ "sudoPassword": p["sudoPassword"], "privileged": p["privileged"] == true })).await
125});
126module_cmd!(module_system_manage_virtual_memory, "plugin:system:manageVirtualMemory", Some("system.memory.manage"), |core, ctx, p| core.services.virtual_memory.manage(text(&p, "sessionId"), &p).await);
128// --- disks -------------------------------------------------------------------------------------
130module_cmd!(module_disks_inspect, "plugin:disks:inspect", Some("disks.read"), |core, ctx, p| core.services.disks.inspect(text(&p, "sessionId"), &json!({ "privileged": p["privileged"] == true, "sudoPassword": p["sudoPassword"], "rememberSudo": p["rememberSudo"] == true })).await);
131module_cmd!(module_disks_smart, "plugin:disks:smart", Some("disks.read"), |core, ctx, p| core.services.disks.smart(text(&p, "sessionId"), &p).await);
132module_cmd!(module_disks_jobs, "plugin:disks:jobs", Some("disks.read"), |core, ctx, p| core.services.disks.jobs(text(&p, "sessionId"), &p).await);
133module_cmd!(module_disks_job_status, "plugin:disks:jobStatus", Some("disks.read"), |core, ctx, p| core.services.disks.job_status(text(&p, "sessionId"), &p).await);
134module_cmd!(module_disks_plan, "plugin:disks:plan", Some("disks.manage"), |core, ctx, p| core.services.disks.plan(text(&p, "sessionId"), &p).await);
135module_cmd!(module_disks_auto_expand, "plugin:disks:autoExpand", Some("disks.manage"), |core, ctx, p| core.services.disks.auto_expand(text(&p, "sessionId"), &p).await);
136module_cmd!(module_disks_cancel_plan, "plugin:disks:cancelPlan", Some("disks.manage"), |core, ctx, p| Ok(core.services.disks.cancel_plan(&p["planId"])));
137module_cmd!(module_disks_apply, "plugin:disks:apply", Some("disks.manage"), |core, ctx, p| core.services.disks.apply(text(&p, "sessionId"), &p).await);
139// --- keys, logs --------------------------------------------------------------------------------
141module_cmd!(module_keys_generate, "plugin:keys:generate", Some("keys.generate"), |core, ctx, p| core.services.keys.generate(&p).await);
142module_cmd!(module_keys_save, "plugin:keys:save", Some("keys.generate"), |core, ctx, p| {
143 let generated = core.services.keys.generated(text(&p, "generationId"))?;
144 let title = core.i18n.t(&core.language(), "native.keys.saveTitle");
145 let parent = core.app.get_webview_window(&ctx.label);
146 let suggested = generated["suggestedName"].as_str().unwrap_or("id_key").to_string();
147 match crate::dialogs::save_file(&core.app, parent.as_ref(), Some(&title), &suggested, &[]).await {
148 Some(path) => crate::core::keys::save_pair(&path, &generated),
149 None => Ok(json!({ "canceled": true })),
150 }
151});
152module_cmd!(module_keys_password, "plugin:keys:password", Some("keys.generate"), |core, ctx, p| core.services.keys.generate_password(&p));
153module_cmd!(module_keys_copy_password, "plugin:keys:copyPassword", Some("keys.generate"), |core, ctx, p| {
154 use tauri_plugin_clipboard_manager::ClipboardExt;
155 let password = core.services.keys.password(text(&p, "passwordId"))?;
156 core.app.clipboard().write_text(password.as_str()).map_err(|_| CoreError::plain("Clipboard is unavailable."))?;
157 Ok(json!({ "copied": true }))
158});
159module_cmd!(module_logs_read, "plugin:logs:read", Some("logs.read"), |core, ctx, p| {
160 let tail = read_tail(core.logger.path(), normalize_limit(p.get("maxBytes")))?;
161 Ok(serde_json::to_value(tail)?)
162});
164// --- users --------------------------------------------------------------------------------------
166module_cmd!(module_users_list, "plugin:users:list", Some("users.read"), |core, ctx, p| core.services.users.list(text(&p, "sessionId")).await);
167module_cmd!(module_users_privileges, "plugin:users:privileges", Some("users.read"), |core, ctx, p| core.services.users.privileges(text(&p, "sessionId"), p["refresh"] == true).await);
168module_cmd!(module_users_inventory, "plugin:users:inventory", Some("users.read"), |core, ctx, p| core.services.users.inventory(text(&p, "sessionId"), &p).await);
169module_cmd!(module_users_detail, "plugin:users:detail", Some("users.read"), |core, ctx, p| core.services.users.detail(text(&p, "sessionId"), &p).await);
170module_cmd!(module_users_manage, "plugin:users:manage", Some("users.manage"), |core, ctx, p| core.services.users.manage(text(&p, "sessionId"), &p).await);
171module_cmd!(module_users_keys, "plugin:users:keys", Some("users.manage"), |core, ctx, p| core.services.users.keys(text(&p, "sessionId"), &p).await);
172module_cmd!(module_users_ssh_policy, "plugin:users:sshPolicy", Some("users.manage"), |core, ctx, p| core.services.users.ssh_policy(text(&p, "sessionId"), &p).await);
174// --- nginx ---------------------------------------------------------------------------------------
176use crate::core::ssh::nginx;
177module_cmd!(module_nginx_inspect, "plugin:nginx:inspect", Some("nginx.read"), |core, ctx, p| nginx::inspect(&core.services.sessions, text(&p, "sessionId")).await);
178module_cmd!(module_nginx_read_config, "plugin:nginx:readConfig", Some("nginx.read"), |core, ctx, p| nginx::read_config(&core.services.sessions, text(&p, "sessionId"), &p).await);
179module_cmd!(module_nginx_dump_config, "plugin:nginx:dumpConfig", Some("nginx.read"), |core, ctx, p| nginx::dump_config(&core.services.sessions, text(&p, "sessionId"), &p).await);
180module_cmd!(module_nginx_test_config, "plugin:nginx:testConfig", Some("nginx.read"), |core, ctx, p| nginx::test_config(&core.services.sessions, text(&p, "sessionId"), &p).await);
181module_cmd!(module_nginx_save_config, "plugin:nginx:saveConfig", Some("nginx.manage"), |core, ctx, p| nginx::save_config(&core.services.sessions, text(&p, "sessionId"), &p).await);
182module_cmd!(module_nginx_reload, "plugin:nginx:reload", Some("nginx.manage"), |core, ctx, p| nginx::reload(&core.services.sessions, text(&p, "sessionId"), &p).await);
184// --- files / transfers ---------------------------------------------------------------------------
186module_cmd!(module_files_list, "plugin:files:list", Some("files.read"), |core, ctx, p| core.services.files.list(text(&p, "sessionId"), &p["path"], &options(&p)).await);
187module_cmd!(module_files_privileges, "plugin:files:privileges", Some("files.read"), |core, ctx, p| core.services.files.privileges(text(&p, "sessionId")).await);
188module_cmd!(module_files_read_text, "plugin:files:readText", Some("files.read"), |core, ctx, p| core.services.files.read_text(text(&p, "sessionId"), &p["path"], &options(&p), &ctx.id).await);
189module_cmd!(module_files_write_text, "plugin:files:writeText", Some("files.write"), |core, ctx, p| core.services.files.write_text(text(&p, "sessionId"), &p["path"], &p["text"], &options(&p), &ctx.id).await);
190module_cmd!(module_files_save_text, "plugin:files:saveText", Some("files.write"), |core, ctx, p| Ok(core.services.files.save_text(text(&p, "sessionId"), &p["path"], &p["text"], &options(&p), &ctx.id).await));
191module_cmd!(module_files_list_recovery, "plugin:files:listRecovery", Some("files.read"), |core, ctx, p| core.services.files.list_recovery(text(&p, "sessionId"), &options(&p)).await);
192module_cmd!(module_files_read_recovery, "plugin:files:readRecovery", Some("files.read"), |core, ctx, p| core.services.files.read_recovery(text(&p, "sessionId"), &p["recoveryId"], &options(&p)).await);
193module_cmd!(module_files_cleanup_recovery, "plugin:files:cleanupRecovery", Some("files.write"), |core, ctx, p| core.services.files.cleanup_recovery(text(&p, "sessionId"), &p["recoveryId"], &options(&p)).await);
194module_cmd!(module_files_close_text, "plugin:files:closeText", Some("files.read"), |core, ctx, p| Ok(core.services.files.close_text(text(&p, "editId"), &ctx.id)));
195module_cmd!(module_files_create_file, "plugin:files:createFile", Some("files.write"), |core, ctx, p| core.services.files.create_file(text(&p, "sessionId"), &p["path"], &options(&p)).await);
196module_cmd!(module_files_mkdir, "plugin:files:mkdir", Some("files.write"), |core, ctx, p| core.services.files.mkdir(text(&p, "sessionId"), &p["path"], &options(&p)).await);
197module_cmd!(module_files_rename, "plugin:files:rename", Some("files.write"), |core, ctx, p| core.services.files.rename(text(&p, "sessionId"), &p["from"], &p["to"], &options(&p)).await);
198module_cmd!(module_files_remove, "plugin:files:remove", Some("files.write"), |core, ctx, p| core.services.files.remove(text(&p, "sessionId"), &p["path"], &options(&p)).await);
199module_cmd!(module_files_remove_many, "plugin:files:removeMany", Some("files.write"), |core, ctx, p| core.services.files.remove_many(text(&p, "sessionId"), &p["paths"], p["recursive"].as_bool().unwrap_or(false) || truthy(&p["recursive"]), &options(&p)).await);
200module_cmd!(module_files_upload, "plugin:files:upload", Some("files.transfer"), |core, ctx, p| {
201 let picked = pick_upload_paths(&core, &ctx.label).await;
202 core.services.files.upload(text(&p, "sessionId"), &p["directory"], picked).await
203});
204module_cmd!(module_files_download, "plugin:files:download", Some("files.transfer"), |core, ctx, p| {
205 let session = text(&p, "sessionId");
206 let (target, size) = core.services.files.download_check(session, &p["path"]).await?;
207 let title = core.t("native.files.saveDownload");
208 let parent = core.app.get_webview_window(&ctx.label);
209 match crate::dialogs::save_file(&core.app, parent.as_ref(), Some(&title), &crate::core::files::posix::basename(&target), &[]).await {
210 Some(local) => core.services.files.download_to(session, &target, size, &local).await,
211 None => Ok(json!({ "canceled": true })),
212 }
213});
214module_cmd!(module_files_download_many, "plugin:files:downloadMany", Some("files.transfer"), |core, ctx, p| {
215 let targets = crate::core::files::service::RemoteFileService::normalize_selection(&p["paths"])?;
216 let title = core.t("native.files.selectDownloadDirectory");
217 let parent = core.app.get_webview_window(&ctx.label);
218 match crate::dialogs::pick_folder(&core.app, parent.as_ref(), Some(&title)).await {
219 Some(directory) => core.services.files.download_many(text(&p, "sessionId"), &targets, &directory).await,
220 None => Ok(json!({ "canceled": true, "items": [] })),
221 }
222});
223module_cmd!(module_files_download_archive, "plugin:files:downloadArchive", Some("files.transfer"), |core, ctx, p| {
224 use crate::core::files::service::{safe_entry_name, RemoteFileService};
225 let targets = RemoteFileService::normalize_selection(&p["paths"])?;
226 if targets.iter().any(|t| t == "/") {
227 return Err(CoreError::plain("Archiving the entire remote root is not allowed."));
228 }
229 let format = RemoteFileService::archive_format(&p["format"])?;
230 let base = if targets.len() == 1 { safe_entry_name(&crate::core::files::posix::basename(&targets[0]))? } else { "remote-selection".to_string() };
231 let title = core.t("native.files.saveArchive");
232 let parent = core.app.get_webview_window(&ctx.label);
233 let extensions: &[&str] = match format.as_str() { "tar.gz" => &["tar.gz", "tgz"], "tar" => &["tar"], _ => &["zip"] };
234 let upper = format.to_uppercase();
235 let filters = [crate::dialogs::Filter { name: &upper, extensions }];
236 match crate::dialogs::save_file(&core.app, parent.as_ref(), Some(&title), &format!("{base}.{format}"), &filters).await {
237 Some(destination) => core.services.files.download_archive(text(&p, "sessionId"), &targets, &format, &destination).await,
238 None => Ok(json!({ "canceled": true })),
239 }
240});
241// The local disk belongs to the first-party File Manager only, whatever another module's manifest asks for.
242fn local_data_dir(core: &crate::core::app::Core, ctx: &crate::security::caller::ModuleContext) -> CoreResult<std::path::PathBuf> {
243 if ctx.id != "chj.file-manager" {
244 return Err(CoreError::new("MODULE_PERMISSION_DENIED", "Only the File Manager can use the local disk."));
245 }
246 Ok(core.paths.data.clone())
248module_cmd!(module_local_places, "plugin:files:local:places", Some("files.local"), |core, ctx, p| {
249 local_data_dir(&core, &ctx)?;
250 Ok(crate::core::files::local::places())
251});
252module_cmd!(module_local_list, "plugin:files:local:list", Some("files.local"), |core, ctx, p| {
253 let data = local_data_dir(&core, &ctx)?;
254 let path = text(&p, "path").to_string();
255 tokio::task::spawn_blocking(move || crate::core::files::local::list(&path, &data)).await.map_err(|_| CoreError::plain("Local folder read failed."))?
256});
257module_cmd!(module_local_mkdir, "plugin:files:local:mkdir", Some("files.local"), |core, ctx, p| {
258 let data = local_data_dir(&core, &ctx)?;
259 crate::core::files::local::mkdir(text(&p, "parent"), text(&p, "name"), &data)
260});
261module_cmd!(module_local_create_file, "plugin:files:local:createFile", Some("files.local"), |core, ctx, p| {
262 let data = local_data_dir(&core, &ctx)?;
263 crate::core::files::local::create_file(text(&p, "parent"), text(&p, "name"), &data)
264});
265module_cmd!(module_local_rename, "plugin:files:local:rename", Some("files.local"), |core, ctx, p| {
266 let data = local_data_dir(&core, &ctx)?;
267 crate::core::files::local::rename(text(&p, "path"), text(&p, "name"), &data)
268});
269module_cmd!(module_local_remove, "plugin:files:local:remove", Some("files.local"), |core, ctx, p| {
270 let data = local_data_dir(&core, &ctx)?;
271 let paths: Vec<String> = p["paths"].as_array().map(|list| list.iter().filter_map(Value::as_str).map(str::to_string).collect()).unwrap_or_default();
272 tokio::task::spawn_blocking(move || crate::core::files::local::remove(&paths, &data)).await.map_err(|_| CoreError::plain("Local delete failed."))?
273});
274// Copy between panels: no picker, the destination comes from the panel. Local ends need `files.local`.
275module_cmd!(module_transfers_plan_copy, "plugin:files:transfers:planCopy", Some("files.transfer"), |core, ctx, p| {
276 let (from, to) = (&p["source"], &p["destination"]);
277 match (text(from, "kind"), text(to, "kind")) {
278 ("remote", "remote") => core.services.transfers.plan_relay(&ctx.id, text(from, "sessionId"), &from["paths"], text(to, "sessionId"), &to["directory"]).await,
279 ("local", "remote") => {
280 ctx.require("files.local")?;
281 let data = local_data_dir(&core, &ctx)?;
282 let mut paths = Vec::new();
283 for value in from["paths"].as_array().cloned().unwrap_or_default() {
284 paths.push(crate::core::files::local::checked_path(value.as_str().unwrap_or(""), &data)?);
285 }
286 let pick: crate::core::files::transfers::PickPaths = Box::new(move || Box::pin(async move { Some(paths) }));
287 core.services.transfers.plan_upload(&ctx.id, text(to, "sessionId"), &to["directory"], pick).await
288 }
289 ("remote", "local") => {
290 ctx.require("files.local")?;
291 let data = local_data_dir(&core, &ctx)?;
292 let directory = crate::core::files::local::checked_path(text(to, "directory"), &data)?;
293 let file_directory = directory.clone();
294 let pick_file: crate::core::files::transfers::PickSavePath = Box::new(move |name: String| {
295 Box::pin(async move { crate::core::files::service::safe_entry_name(&name).ok().map(|name| file_directory.join(name)) })
296 });
297 let pick_directory: crate::core::files::transfers::PickDirectory = Box::new(move || Box::pin(async move { Some(directory) }));
298 core.services.transfers.plan_download(&ctx.id, text(from, "sessionId"), &from["paths"], &json!({ "noSaveDialog": true }), pick_file, pick_directory).await
299 }
300 _ => Err(crate::core::files::service::file_error("TRANSFER_INVALID_SELECTION", "Copy between two different panels; a local copy is not a transfer.")),
301 }
302});
303module_cmd!(module_transfers_list, "plugin:files:transfers:list", Some("files.transfer"), |core, ctx, p| Ok(core.services.transfers.list(&ctx.id)));
304module_cmd!(module_transfers_plan_upload, "plugin:files:transfers:planUpload", Some("files.transfer"), |core, ctx, p| {
305 let (dialog_core, label) = (core.clone(), ctx.label.clone());
306 let pick: crate::core::files::transfers::PickPaths = Box::new(move || Box::pin(async move { Some(pick_upload_paths(&dialog_core, &label).await) }));
307 core.services.transfers.plan_upload(&ctx.id, text(&p, "sessionId"), &p["directory"], pick).await
308});
309module_cmd!(module_transfers_plan_download, "plugin:files:transfers:planDownload", Some("files.transfer"), |core, ctx, p| {
310 let (file_core, file_label) = (core.clone(), ctx.label.clone());
311 let pick_file: crate::core::files::transfers::PickSavePath = Box::new(move |name: String| {
312 Box::pin(async move {
313 let title = file_core.t("native.files.saveDownload");
314 let parent = file_core.app.get_webview_window(&file_label);
315 crate::dialogs::save_file(&file_core.app, parent.as_ref(), Some(&title), &name, &[]).await
316 })
317 });
318 let (dir_core, dir_label) = (core.clone(), ctx.label.clone());
319 let pick_directory: crate::core::files::transfers::PickDirectory = Box::new(move || {
320 Box::pin(async move {
321 let title = dir_core.t("native.files.selectDownloadDirectory");
322 let parent = dir_core.app.get_webview_window(&dir_label);
323 crate::dialogs::pick_folder(&dir_core.app, parent.as_ref(), Some(&title)).await
324 })
325 });
326 core.services.transfers.plan_download(&ctx.id, text(&p, "sessionId"), &p["paths"], &options(&p), pick_file, pick_directory).await
327});
328module_cmd!(module_transfers_start, "plugin:files:transfers:start", Some("files.transfer"), |core, ctx, p| core.services.transfers.start(&ctx.id, &p["planId"], &options(&p)).await);
329module_cmd!(module_transfers_pause, "plugin:files:transfers:pause", Some("files.transfer"), |core, ctx, p| Ok(core.services.transfers.pause(&ctx.id, &transfer_ids(&p))));
330module_cmd!(module_transfers_resume, "plugin:files:transfers:resume", Some("files.transfer"), |core, ctx, p| Ok(core.services.transfers.resume(&ctx.id, &transfer_ids(&p), &options(&p))));
331module_cmd!(module_transfers_retry, "plugin:files:transfers:retry", Some("files.transfer"), |core, ctx, p| Ok(core.services.transfers.retry(&ctx.id, &transfer_ids(&p), &options(&p))));
332module_cmd!(module_transfers_cancel, "plugin:files:transfers:cancel", Some("files.transfer"), |core, ctx, p| Ok(core.services.transfers.cancel(&ctx.id, &transfer_ids(&p)).await));
333module_cmd!(module_transfers_remove, "plugin:files:transfers:remove", Some("files.transfer"), |core, ctx, p| Ok(core.services.transfers.remove(&ctx.id, &transfer_ids(&p))));
334module_cmd!(module_transfers_settings, "plugin:files:transfers:settings", Some("files.transfer"), |core, ctx, p| core.services.transfers.update_settings(p.get("settings").filter(|v| v.is_object()).unwrap_or(&json!({}))));
336// --- hashing ---------------------------------------------------------------------------------------
338module_cmd!(module_hashing_algorithms, "plugin:hashing:algorithms", Some("local.hash"), |core, ctx, p| Ok(core.services.hashing.algorithms()));
339module_cmd!(module_hashing_select_files, "plugin:hashing:selectFiles", Some("local.hash"), |core, ctx, p| {
340 let multiple = p["multiple"] == true;
341 let title = hash_text(&core, if multiple { "Select local files to hash" } else { "Select a local file to hash" });
342 let parent = core.app.get_webview_window(&ctx.label);
343 let picked = if multiple {
344 Some(crate::dialogs::pick_files(&core.app, parent.as_ref(), Some(&title), &[]).await).filter(|v| !v.is_empty())
345 } else {
346 crate::dialogs::pick_file(&core.app, parent.as_ref(), Some(&title), &[]).await.map(|p| vec![p])
347 };
348 core.services.hashing.select_files(&ctx.id, multiple, picked)
349});
350module_cmd!(module_hashing_select_directory, "plugin:hashing:selectDirectory", Some("local.hash"), |core, ctx, p| {
351 let title = hash_text(&core, "Select a local directory");
352 let parent = core.app.get_webview_window(&ctx.label);
353 let picked = crate::dialogs::pick_folder(&core.app, parent.as_ref(), Some(&title)).await;
354 core.services.hashing.select_directory(&ctx.id, picked)
355});
356module_cmd!(module_hashing_select_manifest, "plugin:hashing:selectManifest", Some("local.hash"), |core, ctx, p| {
357 let title = hash_text(&core, "Select a checksum manifest");
358 let name = hash_text(&core, "Checksum manifests");
359 let parent = core.app.get_webview_window(&ctx.label);
360 let filters = [crate::dialogs::Filter { name: &name, extensions: &["sha224", "sha256", "sha384", "sha512", "sha3", "blake3", "md5", "sha1", "sfv", "checksums", "txt"] }];
361 let picked = crate::dialogs::pick_file(&core.app, parent.as_ref(), Some(&title), &filters).await;
362 core.services.hashing.select_manifest(&ctx.id, picked)
363});
364module_cmd!(module_hashing_select_manifest_destination, "plugin:hashing:selectManifestDestination", Some("local.hash"), |core, ctx, p| {
365 let suggested = crate::core::hashing::service::LocalHashService::suggested_manifest_name(&p);
366 let title = hash_text(&core, "Save checksum manifest");
367 let parent = core.app.get_webview_window(&ctx.label);
368 let picked = crate::dialogs::save_file(&core.app, parent.as_ref(), Some(&title), &suggested, &[]).await;
369 core.services.hashing.select_manifest_destination(&ctx.id, picked)
370});
371module_cmd!(module_hashing_start, "plugin:hashing:start", Some("local.hash"), |core, ctx, p| core.services.hashing.start(&ctx.id, &p));
372module_cmd!(module_hashing_verify, "plugin:hashing:verify", Some("local.hash"), |core, ctx, p| core.services.hashing.verify(&ctx.id, &p));
373module_cmd!(module_hashing_compare, "plugin:hashing:compare", Some("local.hash"), |core, ctx, p| core.services.hashing.compare(&ctx.id, &p));
374module_cmd!(module_hashing_generate_manifest, "plugin:hashing:generateManifest", Some("local.hash"), |core, ctx, p| core.services.hashing.generate_manifest(&ctx.id, &p));
375module_cmd!(module_hashing_verify_manifest, "plugin:hashing:verifyManifest", Some("local.hash"), |core, ctx, p| {
376 let (service, id) = (core.services.hashing.clone(), ctx.id.clone());
377 // Manifest parsing and path checks touch the disk: keep them off the async workers.
378 tokio::task::spawn_blocking(move || service.verify_manifest(&id, &p)).await.map_err(|_| CoreError::new("HASH_INTERNAL_ERROR", "An internal hashing error occurred."))?
379});
380module_cmd!(module_hashing_export_results, "plugin:hashing:exportResults", Some("local.hash"), |core, ctx, p| core.services.hashing.export_results(&ctx.id, &p));
381module_cmd!(module_hashing_copy_result, "plugin:hashing:copyResult", Some("local.hash"), |core, ctx, p| {
382 use tauri_plugin_clipboard_manager::ClipboardExt;
383 let (text, count) = core.services.hashing.copy_text(&ctx.id, &p)?;
384 core.app.clipboard().write_text(text).map_err(|_| CoreError::plain("Clipboard is unavailable."))?;
385 Ok(json!({ "ok": true, "count": count }))
386});
387module_cmd!(module_hashing_status, "plugin:hashing:status", Some("local.hash"), |core, ctx, p| core.services.hashing.status(&ctx.id, &p["jobId"]));
388module_cmd!(module_hashing_cancel, "plugin:hashing:cancel", Some("local.hash"), |core, ctx, p| core.services.hashing.cancel(&ctx.id, &p["jobId"]));
390pub fn _keep(app: &tauri::AppHandle) {
391 let _ = app.app_handle();
394#[cfg(test)]
395mod upload_tests {
396 use super::*;
397 use tauri_plugin_dialog::MessageDialogResult;
399 #[tokio::test]
400 async fn upload_choice_preserves_multiselection_and_cancel_opens_no_picker() {
401 for choice in [MessageDialogResult::Yes, MessageDialogResult::Custom("Soubory".into())] {
402 let paths = choose_upload_paths(choice, "Soubory", "Složky", async { vec!["a.txt".into(), "b.txt".into()] }, async { panic!("folder picker opened") }).await;
403 assert_eq!(paths, vec![std::path::PathBuf::from("a.txt"), "b.txt".into()]);
404 }
405 for choice in [MessageDialogResult::No, MessageDialogResult::Custom("Složky".into())] {
406 let paths = choose_upload_paths(choice, "Soubory", "Složky", async { panic!("file picker opened") }, async { vec!["dir-a".into(), "dir-b".into()] }).await;
407 assert_eq!(paths, vec![std::path::PathBuf::from("dir-a"), "dir-b".into()]);
408 }
409 let paths = choose_upload_paths(MessageDialogResult::Cancel, "Soubory", "Složky", async { panic!("file picker opened") }, async { panic!("folder picker opened") }).await;
410 assert!(paths.is_empty());
411 }
SHA-256cdde761f12c836722b135bf5fe71aae7cea8cf91906f703f73a03fd033db0ffd

CH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.

SHA-256 archivu77380d4805485ffa4027fbe7858b5836f197a4aa9d52358a43e2d12d018744df
Soubory