Skip to main content

maa_framework/
resource.rs

1//! Resource management for pipelines, models, and images.
2
3use std::ffi::CString;
4use std::ptr::NonNull;
5
6use crate::{common, sys, MaaError, MaaResult};
7
8/// Resource manager.
9///
10/// Handles loading and management of:
11/// - Image resources
12/// - OCR models
13/// - Task pipelines
14/// - Custom recognizers and actions
15use std::sync::Arc;
16
17struct ResourceInner {
18    handle: NonNull<sys::MaaResource>,
19    owns_handle: bool,
20    custom_actions: std::sync::Mutex<std::collections::HashMap<String, usize>>, // Store pointer address
21    custom_recognitions: std::sync::Mutex<std::collections::HashMap<String, usize>>,
22    callbacks: std::sync::Mutex<std::collections::HashMap<sys::MaaSinkId, usize>>,
23    event_sinks: std::sync::Mutex<std::collections::HashMap<sys::MaaSinkId, usize>>,
24}
25
26unsafe impl Send for ResourceInner {}
27unsafe impl Sync for ResourceInner {}
28
29#[derive(Clone)]
30pub struct Resource {
31    inner: Arc<ResourceInner>,
32}
33
34unsafe impl Send for Resource {}
35unsafe impl Sync for Resource {}
36
37impl Resource {
38    /// Create a new resource manager.
39    pub fn new() -> MaaResult<Self> {
40        let handle = unsafe { sys::MaaResourceCreate() };
41        if let Some(ptr) = NonNull::new(handle) {
42            Ok(Self {
43                inner: Arc::new(ResourceInner {
44                    handle: ptr,
45                    owns_handle: true,
46                    custom_actions: std::sync::Mutex::new(std::collections::HashMap::new()),
47                    custom_recognitions: std::sync::Mutex::new(std::collections::HashMap::new()),
48                    callbacks: std::sync::Mutex::new(std::collections::HashMap::new()),
49                    event_sinks: std::sync::Mutex::new(std::collections::HashMap::new()),
50                }),
51            })
52        } else {
53            Err(MaaError::NullPointer)
54        }
55    }
56
57    /// Create a Resource from a raw pointer, taking ownership.
58    ///
59    /// # Safety
60    /// The pointer must be valid and the caller transfers ownership to the Resource.
61    /// The Resource will call `MaaResourceDestroy` when dropped.
62    pub unsafe fn from_raw(ptr: *mut sys::MaaResource) -> MaaResult<Self> {
63        if let Some(handle) = NonNull::new(ptr) {
64            Ok(Self {
65                inner: Arc::new(ResourceInner {
66                    handle,
67                    owns_handle: true,
68                    custom_actions: std::sync::Mutex::new(std::collections::HashMap::new()),
69                    custom_recognitions: std::sync::Mutex::new(std::collections::HashMap::new()),
70                    callbacks: std::sync::Mutex::new(std::collections::HashMap::new()),
71                    event_sinks: std::sync::Mutex::new(std::collections::HashMap::new()),
72                }),
73            })
74        } else {
75            Err(MaaError::NullPointer)
76        }
77    }
78
79    /// Load a resource bundle from the specified directory.
80    ///
81    /// The bundle should contain pipeline definitions, images, and models.
82    pub fn post_bundle(&self, path: &str) -> MaaResult<crate::job::ResJob> {
83        let c_path = CString::new(path)?;
84        let id = unsafe { sys::MaaResourcePostBundle(self.inner.handle.as_ptr(), c_path.as_ptr()) };
85        Ok(crate::job::Job::for_resource(self, id))
86    }
87
88    /// Check if resources have been loaded.
89    pub fn loaded(&self) -> bool {
90        unsafe { sys::MaaResourceLoaded(self.inner.handle.as_ptr()) != 0 }
91    }
92
93    /// Clear all loaded resources.
94    pub fn clear(&self) -> MaaResult<()> {
95        let ret = unsafe { sys::MaaResourceClear(self.inner.handle.as_ptr()) };
96        common::check_bool(ret)
97    }
98
99    /// Get the status of a loading operation.
100    pub fn status(&self, id: common::MaaId) -> common::MaaStatus {
101        let status = unsafe { sys::MaaResourceStatus(self.inner.handle.as_ptr(), id) };
102        common::MaaStatus(status)
103    }
104
105    /// Wait for a loading operation to complete.
106    pub fn wait(&self, id: common::MaaId) -> common::MaaStatus {
107        let status = unsafe { sys::MaaResourceWait(self.inner.handle.as_ptr(), id) };
108        common::MaaStatus(status)
109    }
110
111    /// Get the raw resource handle.
112    pub fn raw(&self) -> *mut sys::MaaResource {
113        self.inner.handle.as_ptr()
114    }
115
116    // === Additional resource loading ===
117
118    /// Load an OCR model from the specified directory.
119    pub fn post_ocr_model(&self, path: &str) -> MaaResult<crate::job::ResJob> {
120        let c_path = CString::new(path)?;
121        let id =
122            unsafe { sys::MaaResourcePostOcrModel(self.inner.handle.as_ptr(), c_path.as_ptr()) };
123        Ok(crate::job::Job::for_resource(self, id))
124    }
125
126    /// Load additional pipeline definitions from the specified directory.
127    pub fn post_pipeline(&self, path: &str) -> MaaResult<crate::job::ResJob> {
128        let c_path = CString::new(path)?;
129        let id =
130            unsafe { sys::MaaResourcePostPipeline(self.inner.handle.as_ptr(), c_path.as_ptr()) };
131        Ok(crate::job::Job::for_resource(self, id))
132    }
133
134    /// Load image resources from the specified directory.
135    pub fn post_image(&self, path: &str) -> MaaResult<crate::job::ResJob> {
136        let c_path = CString::new(path)?;
137        let id = unsafe { sys::MaaResourcePostImage(self.inner.handle.as_ptr(), c_path.as_ptr()) };
138        Ok(crate::job::Job::for_resource(self, id))
139    }
140
141    // === Pipeline operations ===
142
143    /// Override pipeline parameters with a JSON string.
144    pub fn override_pipeline(&self, pipeline_override: &str) -> MaaResult<()> {
145        let c_json = CString::new(pipeline_override)?;
146        let ret = unsafe {
147            sys::MaaResourceOverridePipeline(self.inner.handle.as_ptr(), c_json.as_ptr())
148        };
149        common::check_bool(ret)
150    }
151
152    /// Override pipeline with JSON value.
153    ///
154    /// Convenience method that accepts `serde_json::Value` for pipeline overrides.
155    ///
156    /// # Example
157    /// ```ignore
158    /// use serde_json::json;
159    /// resource.override_pipeline_json(&json!({
160    ///     "MyNode": { "enabled": false }
161    /// }))?;
162    /// ```
163    pub fn override_pipeline_json(&self, pipeline_override: &serde_json::Value) -> MaaResult<()> {
164        self.override_pipeline(&pipeline_override.to_string())
165    }
166
167    /// Override the next node list for a specific node.
168    ///
169    /// # Arguments
170    /// * `node_name` - The name of the node to modify
171    /// * `next_list` - The new list of next nodes
172    pub fn override_next(&self, node_name: &str, next_list: &[&str]) -> MaaResult<()> {
173        let c_name = CString::new(node_name)?;
174        let list_buf = crate::buffer::MaaStringListBuffer::new()?;
175        for item in next_list {
176            list_buf.append(item)?;
177        }
178        let ret = unsafe {
179            sys::MaaResourceOverrideNext(
180                self.inner.handle.as_ptr(),
181                c_name.as_ptr(),
182                list_buf.raw(),
183            )
184        };
185        common::check_bool(ret)
186    }
187
188    /// Get node data as a JSON string.
189    pub fn get_node_data(&self, node_name: &str) -> MaaResult<Option<String>> {
190        let c_name = CString::new(node_name)?;
191        let buffer = crate::buffer::MaaStringBuffer::new()?;
192        let ret = unsafe {
193            sys::MaaResourceGetNodeData(self.inner.handle.as_ptr(), c_name.as_ptr(), buffer.raw())
194        };
195        if ret != 0 {
196            Ok(Some(buffer.to_string()))
197        } else {
198            Ok(None)
199        }
200    }
201
202    /// Get node data as a deserialized `PipelineData` object.
203    pub fn get_node_object(
204        &self,
205        node_name: &str,
206    ) -> MaaResult<Option<crate::pipeline::PipelineData>> {
207        if let Some(json_str) = self.get_node_data(node_name)? {
208            let data: crate::pipeline::PipelineData =
209                serde_json::from_str(&json_str).map_err(|e| {
210                    MaaError::InvalidConfig(format!("Failed to parse pipeline data: {}", e))
211                })?;
212            Ok(Some(data))
213        } else {
214            Ok(None)
215        }
216    }
217
218    /// Get a list of all node names in the loaded pipelines.
219    pub fn node_list(&self) -> MaaResult<Vec<String>> {
220        let buffer = crate::buffer::MaaStringListBuffer::new()?;
221        let ret = unsafe { sys::MaaResourceGetNodeList(self.inner.handle.as_ptr(), buffer.raw()) };
222        if ret != 0 {
223            Ok(buffer.to_vec())
224        } else {
225            Err(MaaError::FrameworkError(0))
226        }
227    }
228
229    /// Get a hash of the loaded resources for cache validation.
230    pub fn hash(&self) -> MaaResult<String> {
231        let buffer = crate::buffer::MaaStringBuffer::new()?;
232        let ret = unsafe { sys::MaaResourceGetHash(self.inner.handle.as_ptr(), buffer.raw()) };
233        if ret != 0 {
234            Ok(buffer.to_string())
235        } else {
236            Err(MaaError::FrameworkError(0))
237        }
238    }
239
240    /// Get valid default parameters for a recognition type as JSON.
241    ///
242    /// # Arguments
243    /// * `reco_type` - The recognition type (e.g. "TemplateMatch", "OCR").
244    pub fn get_default_recognition_param(
245        &self,
246        reco_type: &str,
247    ) -> MaaResult<Option<serde_json::Value>> {
248        let c_type = CString::new(reco_type)?;
249        let buffer = crate::buffer::MaaStringBuffer::new()?;
250        let ret = unsafe {
251            sys::MaaResourceGetDefaultRecognitionParam(
252                self.inner.handle.as_ptr(),
253                c_type.as_ptr(),
254                buffer.raw(),
255            )
256        };
257        if ret != 0 {
258            let json_str = buffer.to_string();
259            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
260                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
261            })?;
262            Ok(Some(val))
263        } else {
264            Ok(None)
265        }
266    }
267
268    /// Get valid default parameters for an action type as JSON.
269    ///
270    /// # Arguments
271    /// * `action_type` - The action type (e.g. "Click", "Swipe").
272    pub fn get_default_action_param(
273        &self,
274        action_type: &str,
275    ) -> MaaResult<Option<serde_json::Value>> {
276        let c_type = CString::new(action_type)?;
277        let buffer = crate::buffer::MaaStringBuffer::new()?;
278        let ret = unsafe {
279            sys::MaaResourceGetDefaultActionParam(
280                self.inner.handle.as_ptr(),
281                c_type.as_ptr(),
282                buffer.raw(),
283            )
284        };
285        if ret != 0 {
286            let json_str = buffer.to_string();
287            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
288                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
289            })?;
290            Ok(Some(val))
291        } else {
292            Ok(None)
293        }
294    }
295
296    // === Inference device ===
297
298    /// Use CPU for inference (default).
299    pub fn use_cpu(&self) -> MaaResult<()> {
300        let mut ep: i32 =
301            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_CPU as i32;
302        let ret1 = unsafe {
303            sys::MaaResourceSetOption(
304                self.inner.handle.as_ptr(),
305                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
306                &mut ep as *mut _ as *mut std::ffi::c_void,
307                std::mem::size_of::<i32>() as u64,
308            )
309        };
310        common::check_bool(ret1)?;
311
312        let mut device: i32 = sys::MaaInferenceDeviceEnum_MaaInferenceDevice_CPU as i32;
313        let ret2 = unsafe {
314            sys::MaaResourceSetOption(
315                self.inner.handle.as_ptr(),
316                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
317                &mut device as *mut _ as *mut std::ffi::c_void,
318                std::mem::size_of::<i32>() as u64,
319            )
320        };
321        common::check_bool(ret2)
322    }
323
324    /// Use DirectML for GPU-accelerated inference (Windows only).
325    ///
326    /// # Arguments
327    /// * `device_id` - GPU device index (0 for first GPU)
328    pub fn use_directml(&self, device_id: i32) -> MaaResult<()> {
329        let mut ep: i32 =
330            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_DirectML as i32;
331        let ret1 = unsafe {
332            sys::MaaResourceSetOption(
333                self.inner.handle.as_ptr(),
334                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
335                &mut ep as *mut _ as *mut std::ffi::c_void,
336                std::mem::size_of::<i32>() as u64,
337            )
338        };
339        common::check_bool(ret1)?;
340
341        let mut device = device_id;
342        let ret2 = unsafe {
343            sys::MaaResourceSetOption(
344                self.inner.handle.as_ptr(),
345                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
346                &mut device as *mut _ as *mut std::ffi::c_void,
347                std::mem::size_of::<i32>() as u64,
348            )
349        };
350        common::check_bool(ret2)
351    }
352
353    // === EventSink ===
354
355    /// Register a raw callback to receive events.
356    ///
357    /// The callback receives event type and details as JSON strings.
358    pub fn add_sink<F>(&self, callback: F) -> MaaResult<sys::MaaSinkId>
359    where
360        F: Fn(&str, &str) + Send + Sync + 'static,
361    {
362        let (cb_fn, cb_arg) = crate::callback::EventCallback::new(callback);
363        let sink_id = unsafe { sys::MaaResourceAddSink(self.inner.handle.as_ptr(), cb_fn, cb_arg) };
364        if sink_id != 0 {
365            self.inner
366                .callbacks
367                .lock()
368                .unwrap()
369                .insert(sink_id, cb_arg as usize);
370            Ok(sink_id)
371        } else {
372            unsafe { crate::callback::EventCallback::drop_callback(cb_arg) };
373            Err(MaaError::FrameworkError(0))
374        }
375    }
376
377    /// Register a strongly-typed event sink.
378    ///
379    /// This method registers an implementation of the [`EventSink`](crate::event_sink::EventSink) trait
380    /// to receive structured notifications from this resource.
381    ///
382    /// # Arguments
383    /// * `sink` - The event sink implementation (must be boxed).
384    ///
385    /// # Returns
386    /// A `MaaSinkId` which can be used to manually remove the sink later via [`remove_sink`](Self::remove_sink).
387    /// The sink will be automatically unregistered and dropped when the `Resource` is dropped.
388    pub fn add_event_sink(
389        &self,
390        sink: Box<dyn crate::event_sink::EventSink>,
391    ) -> MaaResult<sys::MaaSinkId> {
392        let handle_id = self.inner.handle.as_ptr() as crate::common::MaaId;
393        let (cb, arg) = crate::callback::EventCallback::new_sink(handle_id, sink);
394        let id = unsafe { sys::MaaResourceAddSink(self.inner.handle.as_ptr(), cb, arg) };
395        if id > 0 {
396            self.inner
397                .event_sinks
398                .lock()
399                .unwrap()
400                .insert(id, arg as usize);
401            Ok(id)
402        } else {
403            unsafe { crate::callback::EventCallback::drop_sink(arg) };
404            Err(MaaError::FrameworkError(0))
405        }
406    }
407
408    /// Remove a previously registered event sink.
409    pub fn remove_sink(&self, sink_id: sys::MaaSinkId) {
410        unsafe { sys::MaaResourceRemoveSink(self.inner.handle.as_ptr(), sink_id) }
411        if let Some(ptr) = self.inner.callbacks.lock().unwrap().remove(&sink_id) {
412            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut std::ffi::c_void) };
413        } else if let Some(ptr) = self.inner.event_sinks.lock().unwrap().remove(&sink_id) {
414            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void) };
415        }
416    }
417
418    /// Remove all registered event sinks.
419    pub fn clear_sinks(&self) {
420        unsafe { sys::MaaResourceClearSinks(self.inner.handle.as_ptr()) }
421        let mut callbacks = self.inner.callbacks.lock().unwrap();
422        for (_, ptr) in callbacks.drain() {
423            unsafe { crate::callback::EventCallback::drop_callback(ptr as *mut std::ffi::c_void) };
424        }
425        let mut event_sinks = self.inner.event_sinks.lock().unwrap();
426        for (_, ptr) in event_sinks.drain() {
427            unsafe { crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void) };
428        }
429    }
430
431    // === Image override ===
432
433    /// Override an image resource at runtime.
434    ///
435    /// # Arguments
436    /// * `image_name` - The name of the image to override
437    /// * `image` - The new image buffer to use
438    pub fn override_image(
439        &self,
440        image_name: &str,
441        image: &crate::buffer::MaaImageBuffer,
442    ) -> MaaResult<()> {
443        let c_name = CString::new(image_name)?;
444        let ret = unsafe {
445            sys::MaaResourceOverrideImage(self.inner.handle.as_ptr(), c_name.as_ptr(), image.raw())
446        };
447        common::check_bool(ret)
448    }
449
450    // === Inference device (extended) ===
451
452    /// Auto-select the best inference execution provider.
453    pub fn use_auto_ep(&self) -> MaaResult<()> {
454        let mut ep: i32 =
455            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_Auto as i32;
456        let ret1 = unsafe {
457            sys::MaaResourceSetOption(
458                self.inner.handle.as_ptr(),
459                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
460                &mut ep as *mut _ as *mut std::ffi::c_void,
461                std::mem::size_of::<i32>() as u64,
462            )
463        };
464        common::check_bool(ret1)?;
465
466        let mut device: i32 = sys::MaaInferenceDeviceEnum_MaaInferenceDevice_Auto as i32;
467        let ret2 = unsafe {
468            sys::MaaResourceSetOption(
469                self.inner.handle.as_ptr(),
470                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
471                &mut device as *mut _ as *mut std::ffi::c_void,
472                std::mem::size_of::<i32>() as u64,
473            )
474        };
475        common::check_bool(ret2)
476    }
477
478    /// Use CoreML for inference (macOS only).
479    ///
480    /// # Arguments
481    /// * `coreml_flag` - CoreML configuration flag
482    pub fn use_coreml(&self, coreml_flag: i32) -> MaaResult<()> {
483        let mut ep: i32 =
484            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_CoreML as i32;
485        let ret1 = unsafe {
486            sys::MaaResourceSetOption(
487                self.inner.handle.as_ptr(),
488                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
489                &mut ep as *mut _ as *mut std::ffi::c_void,
490                std::mem::size_of::<i32>() as u64,
491            )
492        };
493        common::check_bool(ret1)?;
494
495        let mut device = coreml_flag;
496        let ret2 = unsafe {
497            sys::MaaResourceSetOption(
498                self.inner.handle.as_ptr(),
499                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
500                &mut device as *mut _ as *mut std::ffi::c_void,
501                std::mem::size_of::<i32>() as u64,
502            )
503        };
504        common::check_bool(ret2)
505    }
506
507    /// Use CUDA for inference (NVIDIA GPU only).
508    ///
509    /// # Arguments
510    /// * `nvidia_gpu_id` - NVIDIA GPU device ID (typically 0 for first GPU)
511    pub fn use_cuda(&self, nvidia_gpu_id: i32) -> MaaResult<()> {
512        let mut ep: i32 =
513            sys::MaaInferenceExecutionProviderEnum_MaaInferenceExecutionProvider_CUDA as i32;
514        let ret1 = unsafe {
515            sys::MaaResourceSetOption(
516                self.inner.handle.as_ptr(),
517                sys::MaaResOptionEnum_MaaResOption_InferenceExecutionProvider as i32,
518                &mut ep as *mut _ as *mut std::ffi::c_void,
519                std::mem::size_of::<i32>() as u64,
520            )
521        };
522        common::check_bool(ret1)?;
523
524        let mut device = nvidia_gpu_id;
525        let ret2 = unsafe {
526            sys::MaaResourceSetOption(
527                self.inner.handle.as_ptr(),
528                sys::MaaResOptionEnum_MaaResOption_InferenceDevice as i32,
529                &mut device as *mut _ as *mut std::ffi::c_void,
530                std::mem::size_of::<i32>() as u64,
531            )
532        };
533        common::check_bool(ret2)
534    }
535
536    // === Custom component management ===
537
538    /// Unregister a custom recognition by name.
539    pub fn unregister_custom_recognition(&self, name: &str) -> MaaResult<()> {
540        let c_name = CString::new(name)?;
541        let ret = unsafe {
542            sys::MaaResourceUnregisterCustomRecognition(self.inner.handle.as_ptr(), c_name.as_ptr())
543        };
544        if ret != 0 {
545            self.inner.custom_recognitions.lock().unwrap().remove(name);
546        }
547        common::check_bool(ret)
548    }
549
550    /// Unregister a custom action by name.
551    pub fn unregister_custom_action(&self, name: &str) -> MaaResult<()> {
552        let c_name = CString::new(name)?;
553        let ret = unsafe {
554            sys::MaaResourceUnregisterCustomAction(self.inner.handle.as_ptr(), c_name.as_ptr())
555        };
556        if ret != 0 {
557            self.inner.custom_actions.lock().unwrap().remove(name);
558        }
559        common::check_bool(ret)
560    }
561
562    /// Get the list of registered custom recognitions.
563    pub fn custom_recognition_list(&self) -> MaaResult<Vec<String>> {
564        let buffer = crate::buffer::MaaStringListBuffer::new()?;
565        let ret = unsafe {
566            sys::MaaResourceGetCustomRecognitionList(self.inner.handle.as_ptr(), buffer.raw())
567        };
568        if ret != 0 {
569            Ok(buffer.to_vec())
570        } else {
571            Err(MaaError::FrameworkError(0))
572        }
573    }
574
575    /// Get the list of registered custom actions.
576    pub fn custom_action_list(&self) -> MaaResult<Vec<String>> {
577        let buffer = crate::buffer::MaaStringListBuffer::new()?;
578        let ret = unsafe {
579            sys::MaaResourceGetCustomActionList(self.inner.handle.as_ptr(), buffer.raw())
580        };
581        if ret != 0 {
582            Ok(buffer.to_vec())
583        } else {
584            Err(MaaError::FrameworkError(0))
585        }
586    }
587
588    /// Clear all registered custom recognitions.
589    pub fn clear_custom_recognition(&self) -> MaaResult<()> {
590        let ret = unsafe { sys::MaaResourceClearCustomRecognition(self.inner.handle.as_ptr()) };
591        if ret != 0 {
592            let mut recos = self.inner.custom_recognitions.lock().unwrap();
593            for (_, ptr) in recos.drain() {
594                unsafe {
595                    let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomRecognition>);
596                }
597            }
598        }
599        common::check_bool(ret)
600    }
601
602    /// Clear all registered custom actions.
603    pub fn clear_custom_action(&self) -> MaaResult<()> {
604        let ret = unsafe { sys::MaaResourceClearCustomAction(self.inner.handle.as_ptr()) };
605        if ret != 0 {
606            let mut actions = self.inner.custom_actions.lock().unwrap();
607            for (_, ptr) in actions.drain() {
608                unsafe {
609                    let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomAction>);
610                }
611            }
612        }
613        common::check_bool(ret)
614    }
615    pub(crate) fn custom_recognitions(
616        &self,
617    ) -> &std::sync::Mutex<std::collections::HashMap<String, usize>> {
618        &self.inner.custom_recognitions
619    }
620
621    pub(crate) fn custom_actions(
622        &self,
623    ) -> &std::sync::Mutex<std::collections::HashMap<String, usize>> {
624        &self.inner.custom_actions
625    }
626}
627
628impl Drop for ResourceInner {
629    fn drop(&mut self) {
630        unsafe {
631            {
632                let mut callbacks = self.callbacks.lock().unwrap();
633                for (_, ptr) in callbacks.drain() {
634                    let _ =
635                        crate::callback::EventCallback::drop_callback(ptr as *mut std::ffi::c_void);
636                }
637            }
638            {
639                let mut event_sinks = self.event_sinks.lock().unwrap();
640                for (_, ptr) in event_sinks.drain() {
641                    let _ = crate::callback::EventCallback::drop_sink(ptr as *mut std::ffi::c_void);
642                }
643            }
644            sys::MaaResourceClearSinks(self.handle.as_ptr());
645            sys::MaaResourceClearCustomAction(self.handle.as_ptr());
646            sys::MaaResourceClearCustomRecognition(self.handle.as_ptr());
647
648            {
649                let mut actions = self.custom_actions.lock().unwrap();
650                for (_, ptr) in actions.drain() {
651                    let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomAction>);
652                }
653            }
654            {
655                let mut recos = self.custom_recognitions.lock().unwrap();
656                for (_, ptr) in recos.drain() {
657                    let _ = Box::from_raw(ptr as *mut Box<dyn crate::custom::CustomRecognition>);
658                }
659            }
660
661            if self.owns_handle {
662                sys::MaaResourceDestroy(self.handle.as_ptr())
663            }
664        }
665    }
666}
667
668/// A borrowed reference to a Resource.
669///
670/// This is a non-owning view that can be used for read-only operations.
671/// It does NOT call destroy when dropped and should only be used while
672/// the underlying Resource is still alive.
673///
674/// Obtained from `Tasker::resource()`.
675pub struct ResourceRef<'a> {
676    handle: *mut sys::MaaResource,
677    _marker: std::marker::PhantomData<&'a ()>,
678}
679
680impl<'a> ResourceRef<'a> {
681    pub(crate) fn from_ptr(handle: *mut sys::MaaResource) -> Option<Self> {
682        if handle.is_null() {
683            None
684        } else {
685            Some(Self {
686                handle,
687                _marker: std::marker::PhantomData,
688            })
689        }
690    }
691
692    /// Check if resources have been loaded.
693    pub fn loaded(&self) -> bool {
694        unsafe { sys::MaaResourceLoaded(self.handle) != 0 }
695    }
696
697    /// Get the status of a loading operation.
698    pub fn status(&self, id: common::MaaId) -> common::MaaStatus {
699        let status = unsafe { sys::MaaResourceStatus(self.handle, id) };
700        common::MaaStatus(status)
701    }
702
703    /// Wait for a loading operation to complete.
704    pub fn wait(&self, id: common::MaaId) -> common::MaaStatus {
705        let status = unsafe { sys::MaaResourceWait(self.handle, id) };
706        common::MaaStatus(status)
707    }
708
709    /// Get resource hash.
710    pub fn hash(&self) -> MaaResult<String> {
711        let buffer = crate::buffer::MaaStringBuffer::new()?;
712        let ret = unsafe { sys::MaaResourceGetHash(self.handle, buffer.raw()) };
713        if ret != 0 {
714            Ok(buffer.to_string())
715        } else {
716            Err(MaaError::FrameworkError(0))
717        }
718    }
719
720    /// Get valid default parameters for a recognition type as JSON.
721    pub fn get_default_recognition_param(
722        &self,
723        reco_type: &str,
724    ) -> MaaResult<Option<serde_json::Value>> {
725        let c_type = std::ffi::CString::new(reco_type)?;
726        let buffer = crate::buffer::MaaStringBuffer::new()?;
727        let ret = unsafe {
728            sys::MaaResourceGetDefaultRecognitionParam(self.handle, c_type.as_ptr(), buffer.raw())
729        };
730        if ret != 0 {
731            let json_str = buffer.to_string();
732            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
733                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
734            })?;
735            Ok(Some(val))
736        } else {
737            Ok(None)
738        }
739    }
740
741    /// Get valid default parameters for an action type as JSON.
742    pub fn get_default_action_param(
743        &self,
744        action_type: &str,
745    ) -> MaaResult<Option<serde_json::Value>> {
746        let c_type = std::ffi::CString::new(action_type)?;
747        let buffer = crate::buffer::MaaStringBuffer::new()?;
748        let ret = unsafe {
749            sys::MaaResourceGetDefaultActionParam(self.handle, c_type.as_ptr(), buffer.raw())
750        };
751        if ret != 0 {
752            let json_str = buffer.to_string();
753            let val: serde_json::Value = serde_json::from_str(&json_str).map_err(|e| {
754                MaaError::InvalidConfig(format!("Failed to parse default params: {}", e))
755            })?;
756            Ok(Some(val))
757        } else {
758            Ok(None)
759        }
760    }
761
762    /// Get node list.
763    pub fn node_list(&self) -> MaaResult<Vec<String>> {
764        let buffer = crate::buffer::MaaStringListBuffer::new()?;
765        let ret = unsafe { sys::MaaResourceGetNodeList(self.handle, buffer.raw()) };
766        if ret != 0 {
767            Ok(buffer.to_vec())
768        } else {
769            Err(MaaError::FrameworkError(0))
770        }
771    }
772
773    /// Get node data as JSON string.
774    pub fn get_node_data(&self, node_name: &str) -> MaaResult<Option<String>> {
775        let c_name = std::ffi::CString::new(node_name)?;
776        let buffer = crate::buffer::MaaStringBuffer::new()?;
777        let ret =
778            unsafe { sys::MaaResourceGetNodeData(self.handle, c_name.as_ptr(), buffer.raw()) };
779        if ret != 0 {
780            Ok(Some(buffer.to_string()))
781        } else {
782            Ok(None)
783        }
784    }
785
786    /// Get the raw handle.
787    pub fn raw(&self) -> *mut sys::MaaResource {
788        self.handle
789    }
790}