use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NotificationType {
Starting,
Succeeded,
Failed,
Unknown,
}
impl NotificationType {
pub fn is_starting(&self) -> bool {
matches!(self, Self::Starting)
}
pub fn is_succeeded(&self) -> bool {
matches!(self, Self::Succeeded)
}
pub fn is_failed(&self) -> bool {
matches!(self, Self::Failed)
}
pub fn is_complete(&self) -> bool {
matches!(self, Self::Succeeded | Self::Failed)
}
}
impl From<&str> for NotificationType {
fn from(s: &str) -> Self {
if s.ends_with(".Starting") {
NotificationType::Starting
} else if s.ends_with(".Succeeded") {
NotificationType::Succeeded
} else if s.ends_with(".Failed") {
NotificationType::Failed
} else {
NotificationType::Unknown
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLoadingDetail {
pub res_id: i64,
pub hash: String,
pub path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControllerActionDetail {
pub ctrl_id: i64,
pub uuid: String,
pub action: String,
#[serde(default)]
pub param: Value,
#[serde(default)]
pub info: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskerTaskDetail {
pub task_id: i64,
pub entry: String,
#[serde(default)]
pub uuid: String,
#[serde(default)]
pub hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NextListItem {
pub name: String,
#[serde(default)]
pub jump_back: bool,
#[serde(default)]
pub anchor: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeNextListDetail {
pub task_id: i64,
pub name: String,
#[serde(default)]
pub list: Vec<NextListItem>,
#[serde(default)]
pub focus: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeWaitFreezesDetail {
pub task_id: i64,
pub wf_id: i64,
pub name: String,
pub phase: String,
pub roi: crate::common::Rect,
#[serde(default)]
pub param: Value,
#[serde(default)]
pub reco_ids: Vec<i64>,
#[serde(default)]
pub elapsed: Option<u64>,
#[serde(default)]
pub focus: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeRecognitionDetail {
pub task_id: i64,
pub reco_id: i64,
pub name: String,
#[serde(default)]
pub focus: Value,
pub anchor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeActionDetail {
pub task_id: i64,
pub action_id: i64,
pub name: String,
#[serde(default)]
pub focus: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodePipelineNodeDetail {
pub task_id: i64,
pub node_id: i64,
pub name: String,
#[serde(default)]
pub focus: Value,
}
pub mod msg {
pub const RESOURCE_LOADING_STARTING: &str = "Resource.Loading.Starting";
pub const RESOURCE_LOADING_SUCCEEDED: &str = "Resource.Loading.Succeeded";
pub const RESOURCE_LOADING_FAILED: &str = "Resource.Loading.Failed";
pub const CONTROLLER_ACTION_STARTING: &str = "Controller.Action.Starting";
pub const CONTROLLER_ACTION_SUCCEEDED: &str = "Controller.Action.Succeeded";
pub const CONTROLLER_ACTION_FAILED: &str = "Controller.Action.Failed";
pub const TASKER_TASK_STARTING: &str = "Tasker.Task.Starting";
pub const TASKER_TASK_SUCCEEDED: &str = "Tasker.Task.Succeeded";
pub const TASKER_TASK_FAILED: &str = "Tasker.Task.Failed";
pub const NODE_PIPELINE_NODE_STARTING: &str = "Node.PipelineNode.Starting";
pub const NODE_PIPELINE_NODE_SUCCEEDED: &str = "Node.PipelineNode.Succeeded";
pub const NODE_PIPELINE_NODE_FAILED: &str = "Node.PipelineNode.Failed";
pub const NODE_RECOGNITION_STARTING: &str = "Node.Recognition.Starting";
pub const NODE_RECOGNITION_SUCCEEDED: &str = "Node.Recognition.Succeeded";
pub const NODE_RECOGNITION_FAILED: &str = "Node.Recognition.Failed";
pub const NODE_ACTION_STARTING: &str = "Node.Action.Starting";
pub const NODE_ACTION_SUCCEEDED: &str = "Node.Action.Succeeded";
pub const NODE_ACTION_FAILED: &str = "Node.Action.Failed";
pub const NODE_WAIT_FREEZES_STARTING: &str = "Node.WaitFreezes.Starting";
pub const NODE_WAIT_FREEZES_SUCCEEDED: &str = "Node.WaitFreezes.Succeeded";
pub const NODE_WAIT_FREEZES_FAILED: &str = "Node.WaitFreezes.Failed";
pub const NODE_NEXT_LIST_STARTING: &str = "Node.NextList.Starting";
pub const NODE_NEXT_LIST_SUCCEEDED: &str = "Node.NextList.Succeeded";
pub const NODE_NEXT_LIST_FAILED: &str = "Node.NextList.Failed";
pub const NODE_RECOGNITION_NODE_STARTING: &str = "Node.RecognitionNode.Starting";
pub const NODE_RECOGNITION_NODE_SUCCEEDED: &str = "Node.RecognitionNode.Succeeded";
pub const NODE_RECOGNITION_NODE_FAILED: &str = "Node.RecognitionNode.Failed";
pub const NODE_ACTION_NODE_STARTING: &str = "Node.ActionNode.Starting";
pub const NODE_ACTION_NODE_SUCCEEDED: &str = "Node.ActionNode.Succeeded";
pub const NODE_ACTION_NODE_FAILED: &str = "Node.ActionNode.Failed";
}
pub fn parse_type(msg: &str) -> NotificationType {
NotificationType::from(msg)
}
pub fn parse_resource_loading(details: &str) -> Option<ResourceLoadingDetail> {
serde_json::from_str(details).ok()
}
pub fn parse_controller_action(details: &str) -> Option<ControllerActionDetail> {
serde_json::from_str(details).ok()
}
pub fn parse_tasker_task(details: &str) -> Option<TaskerTaskDetail> {
serde_json::from_str(details).ok()
}
pub fn parse_node_next_list(details: &str) -> Option<NodeNextListDetail> {
serde_json::from_str(details).ok()
}
pub fn parse_node_wait_freezes(details: &str) -> Option<NodeWaitFreezesDetail> {
serde_json::from_str(details).ok()
}
pub fn parse_node_recognition(details: &str) -> Option<NodeRecognitionDetail> {
serde_json::from_str(details).ok()
}
pub fn parse_node_action(details: &str) -> Option<NodeActionDetail> {
serde_json::from_str(details).ok()
}
pub fn parse_node_pipeline_node(details: &str) -> Option<NodePipelineNodeDetail> {
serde_json::from_str(details).ok()
}
#[derive(Debug, Clone)]
pub enum ContextEvent {
NodeNextList(NotificationType, NodeNextListDetail),
NodeRecognition(NotificationType, NodeRecognitionDetail),
NodeAction(NotificationType, NodeActionDetail),
NodeWaitFreezes(NotificationType, NodeWaitFreezesDetail),
NodePipelineNode(NotificationType, NodePipelineNodeDetail),
NodeRecognitionNode(NotificationType, NodePipelineNodeDetail),
NodeActionNode(NotificationType, NodePipelineNodeDetail),
Unknown(String, Value),
}
impl ContextEvent {
pub fn from_notification(msg: &str, details: &str) -> Option<Self> {
let noti_type = NotificationType::from(msg);
let parse_json = || -> Option<Value> { serde_json::from_str(details).ok() };
if msg.starts_with("Node.NextList") {
let detail = parse_node_next_list(details)?;
return Some(ContextEvent::NodeNextList(noti_type, detail));
}
if msg.starts_with("Node.Recognition.") {
let detail = parse_node_recognition(details)?;
return Some(ContextEvent::NodeRecognition(noti_type, detail));
}
if msg.starts_with("Node.Action.") {
let detail = parse_node_action(details)?;
return Some(ContextEvent::NodeAction(noti_type, detail));
}
if msg.starts_with("Node.WaitFreezes") {
let detail = parse_node_wait_freezes(details)?;
return Some(ContextEvent::NodeWaitFreezes(noti_type, detail));
}
if msg.starts_with("Node.PipelineNode") {
let detail = parse_node_pipeline_node(details)?;
return Some(ContextEvent::NodePipelineNode(noti_type, detail));
}
if msg.starts_with("Node.RecognitionNode") {
let detail = parse_node_pipeline_node(details)?;
return Some(ContextEvent::NodeRecognitionNode(noti_type, detail));
}
if msg.starts_with("Node.ActionNode") {
let detail = parse_node_pipeline_node(details)?;
return Some(ContextEvent::NodeActionNode(noti_type, detail));
}
Some(ContextEvent::Unknown(
msg.to_string(),
parse_json().unwrap_or(Value::Null),
))
}
}
pub trait ResourceEventHandler: Send + Sync {
fn on_resource_loading(&self, _noti_type: NotificationType, _detail: ResourceLoadingDetail) {}
fn on_unknown(&self, _msg: &str, _details: &Value) {}
}
pub trait ControllerEventHandler: Send + Sync {
fn on_controller_action(&self, _noti_type: NotificationType, _detail: ControllerActionDetail) {}
fn on_unknown(&self, _msg: &str, _details: &Value) {}
}
pub trait TaskerEventHandler: Send + Sync {
fn on_tasker_task(&self, _noti_type: NotificationType, _detail: TaskerTaskDetail) {}
fn on_unknown(&self, _msg: &str, _details: &Value) {}
}
pub trait ContextEventHandler: Send + Sync {
fn on_node_next_list(&self, _noti_type: NotificationType, _detail: NodeNextListDetail) {}
fn on_node_recognition(&self, _noti_type: NotificationType, _detail: NodeRecognitionDetail) {}
fn on_node_action(&self, _noti_type: NotificationType, _detail: NodeActionDetail) {}
fn on_node_wait_freezes(&self, _noti_type: NotificationType, _detail: NodeWaitFreezesDetail) {}
fn on_node_pipeline_node(&self, _noti_type: NotificationType, _detail: NodePipelineNodeDetail) {
}
fn on_node_recognition_node(
&self,
_noti_type: NotificationType,
_detail: NodePipelineNodeDetail,
) {
}
fn on_node_action_node(&self, _noti_type: NotificationType, _detail: NodePipelineNodeDetail) {}
fn on_unknown(&self, _msg: &str, _details: &Value) {}
}
#[derive(Debug, Clone)]
pub enum MaaEvent {
ResourceLoadingStarting(ResourceLoadingDetail),
ResourceLoadingSucceeded(ResourceLoadingDetail),
ResourceLoadingFailed(ResourceLoadingDetail),
ControllerActionStarting(ControllerActionDetail),
ControllerActionSucceeded(ControllerActionDetail),
ControllerActionFailed(ControllerActionDetail),
TaskerTaskStarting(TaskerTaskDetail),
TaskerTaskSucceeded(TaskerTaskDetail),
TaskerTaskFailed(TaskerTaskDetail),
NodePipelineNodeStarting(NodePipelineNodeDetail),
NodePipelineNodeSucceeded(NodePipelineNodeDetail),
NodePipelineNodeFailed(NodePipelineNodeDetail),
NodeRecognitionStarting(NodeRecognitionDetail),
NodeRecognitionSucceeded(NodeRecognitionDetail),
NodeRecognitionFailed(NodeRecognitionDetail),
NodeActionStarting(NodeActionDetail),
NodeActionSucceeded(NodeActionDetail),
NodeActionFailed(NodeActionDetail),
NodeWaitFreezesStarting(NodeWaitFreezesDetail),
NodeWaitFreezesSucceeded(NodeWaitFreezesDetail),
NodeWaitFreezesFailed(NodeWaitFreezesDetail),
NodeNextListStarting(NodeNextListDetail),
NodeNextListSucceeded(NodeNextListDetail),
NodeNextListFailed(NodeNextListDetail),
NodeRecognitionNodeStarting(NodePipelineNodeDetail),
NodeRecognitionNodeSucceeded(NodePipelineNodeDetail),
NodeRecognitionNodeFailed(NodePipelineNodeDetail),
NodeActionNodeStarting(NodePipelineNodeDetail),
NodeActionNodeSucceeded(NodePipelineNodeDetail),
NodeActionNodeFailed(NodePipelineNodeDetail),
Unknown {
msg: String,
raw_json: String,
err: Option<String>,
},
}
impl MaaEvent {
pub fn from_json(msg: &str, details: &str) -> Self {
match msg {
msg::RESOURCE_LOADING_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::ResourceLoadingStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::RESOURCE_LOADING_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::ResourceLoadingSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::RESOURCE_LOADING_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::ResourceLoadingFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::CONTROLLER_ACTION_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::ControllerActionStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::CONTROLLER_ACTION_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::ControllerActionSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::CONTROLLER_ACTION_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::ControllerActionFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::TASKER_TASK_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::TaskerTaskStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::TASKER_TASK_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::TaskerTaskSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::TASKER_TASK_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::TaskerTaskFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_PIPELINE_NODE_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodePipelineNodeStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_PIPELINE_NODE_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodePipelineNodeSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_PIPELINE_NODE_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodePipelineNodeFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_RECOGNITION_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeRecognitionStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_RECOGNITION_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeRecognitionSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_RECOGNITION_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeRecognitionFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_ACTION_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeActionStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_ACTION_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeActionSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_ACTION_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeActionFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_WAIT_FREEZES_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeWaitFreezesStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_WAIT_FREEZES_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeWaitFreezesSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_WAIT_FREEZES_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeWaitFreezesFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_NEXT_LIST_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeNextListStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_NEXT_LIST_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeNextListSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_NEXT_LIST_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeNextListFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_RECOGNITION_NODE_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeRecognitionNodeStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_RECOGNITION_NODE_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeRecognitionNodeSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_RECOGNITION_NODE_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeRecognitionNodeFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_ACTION_NODE_STARTING => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeActionNodeStarting(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_ACTION_NODE_SUCCEEDED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeActionNodeSucceeded(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
msg::NODE_ACTION_NODE_FAILED => match serde_json::from_str(details) {
Ok(d) => MaaEvent::NodeActionNodeFailed(d),
Err(e) => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: Some(e.to_string()),
},
},
_ => MaaEvent::Unknown {
msg: msg.to_string(),
raw_json: details.to_string(),
err: None,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_context_event_parsing_logic() {
let msg_wf = "Node.WaitFreezes.Succeeded";
let detail_wf = json!({
"task_id": 1,
"wf_id": 10,
"name": "WF",
"phase": "pre",
"roi": [1, 2, 3, 4],
"param": {},
"reco_ids": [100, 101],
"elapsed": 233,
"focus": null
}).to_string();
if let Some(ContextEvent::NodeWaitFreezes(t, d)) =
ContextEvent::from_notification(msg_wf, &detail_wf)
{
assert_eq!(t, NotificationType::Succeeded);
assert_eq!(d.wf_id, 10);
assert_eq!(d.reco_ids, vec![100, 101]);
assert_eq!(d.elapsed, Some(233));
} else {
panic!("Node.WaitFreezes parse failed");
}
let msg_reco = "Node.Recognition.Succeeded";
let detail_reco =
json!({ "task_id": 1, "reco_id": 100, "name": "R", "focus": null, "anchor": "A1" }).to_string();
if let Some(ContextEvent::NodeRecognition(t, d)) =
ContextEvent::from_notification(msg_reco, &detail_reco)
{
assert_eq!(t, NotificationType::Succeeded);
assert_eq!(d.anchor.as_deref(), Some("A1"));
} else {
panic!("Node.Recognition parse failed");
}
let msg_node = "Node.RecognitionNode.Starting";
let detail_node =
json!({ "task_id": 1, "node_id": 200, "name": "N", "focus": null }).to_string();
if let Some(ContextEvent::NodeRecognitionNode(t, _)) =
ContextEvent::from_notification(msg_node, &detail_node)
{
assert_eq!(t, NotificationType::Starting);
} else {
panic!("Node.RecognitionNode parse failed");
}
}
}