Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions src-tauri/src/commands/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use super::types::{MaaCallbackEvent, MaaState, StateChangedEvent};
use crate::ws_broadcast::{WsBroadcast, WsEvent};
use log::{error, warn};
use std::path::PathBuf;
use std::sync::Arc;
use tauri::{AppHandle, Emitter, Manager};
Expand Down Expand Up @@ -277,3 +278,182 @@ pub fn build_launch_command(

cmd
}

/// 获取当前 instance 所有已勾选 task 的状态
///
/// - 输出为数组,按照 instance 中 task 的顺序排列。
/// - 数组的每项是一个包含两个字符串的数组:[任务名称(i18n), 任务状态("idle","pending","running","succeeded","failed")]。
pub fn get_checked_task_status_of_instance(
app_handle: Option<&AppHandle>,
instance_id: Option<&str>,
) -> Vec<Vec<String>> {
let app_config_state =
match app_handle.and_then(|app| app.try_state::<Arc<crate::commands::AppConfigState>>()) {
Some(state) => state,
None => {
error!("[MXU_STATUS] fail to get resource [app_config_state]");
return vec![];
}
};
let translations = match app_config_state.translations.lock() {
Ok(guard) => guard,
Err(e) => {
error!(
"[MXU_STATUS] fail to lock resource [app_config_state.translations]: {:?}",
e
);
return vec![];
}
};
let config = match app_config_state.config.lock() {
Ok(guard) => guard,
Err(e) => {
error!(
"[MXU_STATUS] fail to lock resource [app_config_state.config]: {:?}",
e
);
return vec![];
}
};

// i18n
let language = config
.get("settings")
.and_then(|v| v.get("language"))
.and_then(|v| v.as_str())
.unwrap_or("system");
let i18n = translations
.get(match language {
// get i18n task name by (i18n["task.<task-name>.label"])
"zh-TW" => "zh_tw",
"en-US" => "en_us",
"ja-JP" => "ja_jp",
"ko-KR" => "ko_kr",
_ => "zh_cn",
})
.unwrap_or_default();

// instance (config)
let id = instance_id.unwrap_or("");
let instance_config_list = match config.get("instances").and_then(|v| v.as_array()) {
Some(list) => list,
None => {
error!("[MXU_STATUS] config data [configs/mxu-*.json > .instances] should be [array]");
return vec![];
}
};
let instance_config = match instance_config_list
.iter()
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
.find(|inst| inst.get("id").and_then(|v| v.as_str()) == Some(id))
{
Some(inst) => inst,
None => {
error!(
"[MXU_STATUS] config data [configs/mxu-*.json > .instances] should contains [object] item whose [.id = \"{:?}\"]",
id
);
return vec![];
}
};

// instance (runtime)
let maa_state =
match app_handle.and_then(|app| app.try_state::<Arc<crate::commands::MaaState>>()) {
Some(state) => state,
None => {
error!("[MXU_STATUS] fail to get resource [maa_state]");
return vec![];
}
};

let instance_runtime_list = match maa_state.instances.lock() {
Ok(guard) => guard,
Err(e) => {
error!(
"[MXU_STATUS] fail to lock resource [maa_state]: {:?}",
e
);
return vec![];
}
};

let instance_runtime = match instance_runtime_list.get(id) {
Some(runtime) => runtime,
None => {
error!(
"[MXU_STATUS] runtime data [maa_state.instances[\"{:?}\"]] should be [object]",
id
);
return vec![];
}
};

return instance_runtime
.task_run_state
.pending_task_ids
.iter()
.filter_map(|&maa_task_id| {
if let Some(selected_task_id) =
instance_runtime.task_run_state.mappings.get(&maa_task_id)
{
if let Some(status) = instance_runtime
.task_run_state
.statuses
.get(selected_task_id)
{
let task_config_list = instance_config.get("tasks")?.as_array()?;
let task_config = task_config_list.iter().find(|task| {
task.get("id").and_then(|v| v.as_str()) == Some(selected_task_id.as_str())
})?;
let task_name = task_config
.get("taskName")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or("".to_string());
let custom_name = task_config
.get("customName")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let task_name_i18n = i18n
.get(format!("task.{:?}.label", task_name))
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or("".to_string());
// custom name / task name (i18n) / task name
// => task status ("idle","pending","running","succeeded","failed")
let name: String = if !custom_name.is_empty() {
custom_name
} else if !task_name_i18n.is_empty() {
task_name_i18n
} else if !task_name.is_empty(){
task_name
} else {
warn!(
"[MXU_STATUS] config data [configs/mxu-*.json > .instances[.id = \"{:?}\"].tasks[.id = \"{:?}\"].taskName] should be [non-empty string]",
id,
selected_task_id
);
return None
};
return Some(vec![name, status.to_string()])
} else {
warn!(
"[MXU_STATUS] status not found in runtime data [maa_state.instances[\"{:?}\"].task_run_state.statuses] for task in config data [configs/mxu-*.json > .instances[.id = \"{:?}\"].tasks[.id = \"{:?}\"]",
id,
id,
selected_task_id
);
return None
}
} else {
warn!(
"[MXU_STATUS] task not found in config data [configs/mxu-*.json > .instances[.id = \"{:?}\"] related to runtime data [maa_state.instances[\"{:?}\"].task_run_state.pending_task_ids]",
id,
id
);
return None
}
})
.collect();
}
110 changes: 108 additions & 2 deletions src-tauri/src/mxu_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//!
//! 提供 MXU 特有的自定义动作实现,如 MXU_SLEEP 等

use base64::{engine::general_purpose, Engine as _};
use chrono::TimeZone;
use log::{info, warn};
use maa_framework::custom::FnAction;
Expand Down Expand Up @@ -200,7 +201,62 @@ const MXU_LAUNCH_ACTION: &str = "MXU_LAUNCH_ACTION";
fn mxu_launch_action_fn(
_ctx: &maa_framework::context::Context,
args: &maa_framework::custom::ActionArgs,
app_handle: Option<&AppHandle>,
instance_id: Option<&str>,
) -> bool {
//
// {{STATUS}}
//
// success - 任务1
// failed - 任务2
// pending - 任务3
//
let task_status =
crate::commands::utils::get_checked_task_status_of_instance(app_handle, instance_id);
let status = task_status
.iter()
.map(|row| format!("{} - {}", row[1], row[0]))
.collect::<Vec<String>>()
.join("\n");
//
// {{S_BASE64}}
//
// c3VjY2VzcyAtIOS7u+WKoTEKZmFpbGVkIC0g5Lu75YqhMgpwZW5kaW5nIC0g5Lu75YqhMw==
//
let s_base64 = general_purpose::STANDARD.encode(&status);
//
// {{S_CSV}}
//
// NAME,STATUS
// 任务1,success
// 任务2,failed
// 任务3,pending
//
let s_csv = format!(
"NAME,STATUS\n{}",
task_status
.iter()
.map(|row| format!("{},{}", row[0], row[1]))
.collect::<Vec<String>>()
.join("\n")
);
//
// {{S_JSON}}
//
// [["任务1","success"],["任务2","failed"],["任务3","pending"]]
//
let s_json = match serde_json::to_string(&task_status) {
Ok(v) => v,
Err(_) => String::from("[]"),
};
//
// {{S_JSON_BASE64}}
//
// W1si5Lu75YqhMSIsInN1Y2Nlc3MiXSxbIuS7u+WKoTIiLCJmYWlsZWQiXSxbIuS7u+WKoTMiLCJwZW5kaW5nIl1d
//
let s_json_base64 = general_purpose::STANDARD.encode(&s_json);
info!("[MXU_LAUNCH] Generated task(s) status: {}", &s_json);

let param_str = args.param;
info!("[MXU_LAUNCH] Received param: {}", param_str);

Expand All @@ -224,7 +280,12 @@ fn mxu_launch_action_fn(
.get("args")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
.to_string()
.replace("{{STATUS}}", &status)
.replace("{{S_BASE64}}", &s_base64)
.replace("{{S_CSV}}", &s_csv)
.replace("{{S_JSON}}", &s_json)
.replace("{{S_JSON_BASE64}}", &s_json_base64);

let wait_for_exit = json
.get("wait_for_exit")
Expand Down Expand Up @@ -878,11 +939,56 @@ pub fn register_all_mxu_actions(

reg_action!(MXU_SLEEP_ACTION, mxu_sleep_action_fn);
reg_action!(MXU_WAITUNTIL_ACTION, mxu_waituntil_action_fn);
reg_action!(MXU_LAUNCH_ACTION, mxu_launch_action_fn);
reg_action!(MXU_WEBHOOK_ACTION, mxu_webhook_action_fn);
reg_action!(MXU_NOTIFY_ACTION, mxu_notify_action_fn);
reg_action!(MXU_POWER_ACTION, mxu_power_action_fn);

// launch

let launch_app_handle = app_handle.clone();
let launch_instance_id = instance_id.to_string();
let launch_wrapper = move |ctx: &maa_framework::context::Context,
args: &maa_framework::custom::ActionArgs|
-> bool {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
mxu_launch_action_fn(
ctx,
args,
Some(&launch_app_handle),
Some(&launch_instance_id),
)
}))
.unwrap_or_else(|e| {
let msg = if let Some(s) = e.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = e.downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic payload".to_string()
};
log::error!(
"[MXU] Custom action {} panicked: {}",
MXU_LAUNCH_ACTION,
msg
);
false
})
};

if let Err(e) =
resource.register_custom_action(MXU_LAUNCH_ACTION, Box::new(FnAction::new(launch_wrapper)))
{
warn!("[MXU] Failed to register {}: {:?}", MXU_LAUNCH_ACTION, e);
failed_count += 1;
} else {
info!(
"[MXU] Custom action {} registered successfully",
MXU_LAUNCH_ACTION
);
}

// kill process

let killproc_app_handle = app_handle.clone();
let killproc_instance_id = instance_id.to_string();
let killproc_wrapper = move |ctx: &maa_framework::context::Context,
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ export default {
programLabel: 'Program Path',
programPlaceholder: 'Enter program path or click browse...',
argsLabel: 'Additional Arguments',
argsPlaceholder: 'Enter additional arguments (optional)',
argsPlaceholder: 'Enter additional arguments (optional, template variables supported)',
argsDescription: 'Template variables {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} represent the real-time status of selected tasks of the current instance. See https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs for details.',
waitLabel: 'Wait for Exit',
waitDescription:
'When disabled, continues immediately after launch; when enabled, waits for the process to exit before continuing, suitable for scripts that need to complete synchronously',
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ export default {
program: 'プログラムパス',
programPlaceholder: 'プログラムパスを入力または参照...',
args: '追加引数',
argsPlaceholder: '追加引数を入力(オプション)',
argsPlaceholder: '追加パラメータを入力(オプション、テンプレート変数可)',
argsDescription: 'テンプレート変数 {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} は、現在のインスタンスの選択されたタスクのリアルタイム状態を表します。詳細は https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs を参照してください。',
browse: '参照',
waitForExit: '終了を待機',
waitForExitHintPre:
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/ko-KR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ export default {
programLabel: '프로그램 경로',
programPlaceholder: '프로그램 경로를 입력하거나 오른쪽 찾아보기를 클릭...',
argsLabel: '추가 인수',
argsPlaceholder: '추가 인수 입력 (선택 사항)',
argsPlaceholder: '추가 인수 입력(선택 사항, 템플릿 변수 지원)',
argsDescription: '템플릿 변수 {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} 는 현재 인스턴스의 선택된 모든 작업의 실시간 상태를 나타냅니다. 자세한 내용은 https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs 를 참조하세요.',
waitLabel: '종료 대기',
waitDescription:
'비활성화하면 실행 후 즉시 계속합니다. 활성화하면 프로세스 종료 후 계속합니다. 스크립트 등 동기 완료가 필요한 작업에 적합합니다',
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ export default {
programLabel: '程序路径',
programPlaceholder: '输入程序路径或点击右侧浏览...',
argsLabel: '附加参数',
argsPlaceholder: '输入附加参数(可选)',
argsPlaceholder: '输入附加参数(可选,支持模板变量)',
argsDescription: '模板变量 {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} 表示 当前实例-所有选定任务-实时状态,详见 https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs 。',
waitLabel: '等待退出',
waitDescription: '禁用时启动进程后立即继续;启用时等待进程退出后再继续工作',
waitYes: '等待程序退出后继续',
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ export default {
programLabel: '程式路徑',
programPlaceholder: '輸入程式路徑或點擊右側瀏覽...',
argsLabel: '附加參數',
argsPlaceholder: '輸入附加參數(可選)',
argsPlaceholder: '輸入附加參數(可選,支持模板變量)',
argsDescription: '模板變量 {{STATUS}} {{S_BASE64}} {{S_CSV}} {{S_JSON}} {{S_JSON_BASE64}} 表示 當前實例-所有選定任務-即時狀態,詳見 https://github.com/MistEO/MXU/blob/main/src-tauri/src/mxu_actions.rs 。',
waitLabel: '等待退出',
waitDescription:
'禁用時啟動程序後立即繼續;啟用時等待程序退出後再繼續,適用於執行腳本等需要同步完成的操作',
Expand Down
1 change: 1 addition & 0 deletions src/types/specialTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const MXU_LAUNCH_INPUT_OPTION_DEF_INTERNAL: InputOption = {
default: '',
pipeline_type: 'string',
placeholder: 'specialTask.launch.argsPlaceholder',
description: 'specialTask.launch.argsDescription',
},
],
pipeline_override: {
Expand Down
Loading