mdquery_rs/apple/
query.rs1use std::ptr::{self, NonNull};
2use super::{api::*, MDItemKey};
3use super::{MDItem, MDQueryBuilder, MDQueryScope};
4use anyhow::{anyhow, Result};
5use objc2_core_foundation::{CFArrayCreate, CFIndex, CFRetained, CFString};
6
7pub struct MDQuery(CFRetained<CoreMDQuery>);
10
11impl MDQuery {
12 pub fn builder() -> MDQueryBuilder {
17 MDQueryBuilder::default()
18 }
19
20 pub fn new(
30 query: &str,
31 scopes: Option<Vec<MDQueryScope>>,
32 max_count: Option<usize>,
33 ) -> Result<Self> {
34 let query = CFString::from_str(query);
35
36 let md_query = unsafe {
37 MDQueryCreate(
38 None, &query, None, None,
40 )
41 }
42 .ok_or(anyhow!("MDQuery create failed, check query syntax."))?;
43
44 if let Some(scopes) = scopes {
45 let scopes = scopes
46 .into_iter()
47 .map(|scope| scope.into_scope_string())
48 .map(|scope| CFString::from_str(&scope))
49 .collect::<Vec<_>>();
50
51 let scopes = unsafe {
52 CFArrayCreate(
53 None,
54 scopes.as_ptr() as *mut _,
55 scopes.len() as CFIndex,
56 ptr::null(),
57 )
58 }
59 .ok_or(anyhow!("MDQuery create failed when create scope array."))?;
60
61 unsafe {
62 MDQuerySetSearchScope(&md_query, &scopes, 0);
63 }
64 }
65
66 if let Some(max_count) = max_count {
67 unsafe {
68 MDQuerySetMaxCount(&md_query, max_count as CFIndex);
69 }
70 }
71
72 Ok(Self(md_query))
73 }
74
75 pub fn execute(self) -> Result<Vec<MDItem>> {
80 unsafe {
81 let success = MDQueryExecute(&self.0, MDQueryOptionsFlags::SYNCHRONOUS as _);
82
83 if !success {
84 return Err(anyhow!("MDQuery execute failed."));
85 }
86
87 let count = MDQueryGetResultCount(&self.0);
88 let mut items = Vec::with_capacity(count as usize);
89 for i in 0..count {
90 let item_ptr = MDQueryGetResultAtIndex(&self.0, i as _) as *mut CoreMDItem;
91 if let Some(item) = NonNull::new(item_ptr) {
92 if let Some(value) = MDItemCopyAttribute(
93 item.as_ref(),
94 &CFString::from_str(MDItemKey::Path.as_str()),
95 ) {
96 if let Ok(path_str) = value.downcast::<CFString>() {
97 let path = (*path_str).to_string();
98 if let Ok(item) = MDItem::from_path(&path) {
99 items.push(item);
100 }
101 }
102 }
103 }
104 }
105 Ok(items)
106 }
107 }
108}
109
110#[repr(C)]
112struct MDQueryOptionsFlags(u32);
113
114#[allow(unused)]
115impl MDQueryOptionsFlags {
116 const NONE: u32 = 0;
117 const SYNCHRONOUS: u32 = 1;
118 const WANTS_UPDATES: u32 = 4;
119 const ALLOW_FS_TRANSLATIONS: u32 = 8;
120}
121
122#[cfg(test)]
123mod tests {
124 use std::path::PathBuf;
125
126 use super::*;
127
128 #[test]
129 fn test_md_query_execute() {
130 let query = MDQuery::new(
131 "kMDItemFSName = \"Safari.app\"",
132 Some(vec![MDQueryScope::Custom("/Applications".into())]),
133 Some(5),
134 )
135 .unwrap();
136
137 let items = query.execute().unwrap();
138 assert_eq!(items.len(), 1);
139 assert_eq!(
140 items[0].path().unwrap(),
141 PathBuf::from("/Applications/Safari.app")
142 );
143 }
144
145 #[test]
146 fn test_empty_result() {
147 let query = MDQuery::new(
148 "kMDItemFSName = \"ThisFileDoesNotExist123456789.xyz\"",
149 Some(vec![MDQueryScope::Computer]),
150 None,
151 )
152 .unwrap();
153 let items = query.execute().unwrap();
154 assert_eq!(items.len(), 0);
155 }
156
157 #[test]
158 fn test_invalid_query() {
159 let result = MDQuery::new(
160 "invalid query syntax !!!",
161 Some(vec![MDQueryScope::Computer]),
162 None,
163 );
164 assert!(result.is_err());
165 }
166}