1use std::ffi::{CStr, CString};
2use std::fmt::Debug;
3use std::ops::Drop;
4use std::path::Path;
5use std::ptr;
6use std::rc::Rc;
7
8use libc;
9use std::cmp::{Ordering, PartialEq, PartialOrd};
10
11use config_pairs::ConfigPairs;
12use error::{Error, Result};
13use ffi;
14use ffi::ConfigKey;
15use ffi::Status;
16use utils::ToStr;
17use ConfigList;
18use ConfigValues;
19use Directory;
20use IndexOpts;
21use Message;
22use Query;
23use Tags;
24
25pub use ffi::DatabaseMode;
27
28#[derive(Clone, Debug)]
29pub struct Revision {
30 pub revision: libc::c_ulong,
31 pub uuid: String,
32}
33
34impl PartialEq for Revision {
35 fn eq(&self, other: &Revision) -> bool {
36 self.uuid == other.uuid && self.revision == other.revision
37 }
38}
39
40impl PartialOrd for Revision {
41 fn partial_cmp(&self, other: &Revision) -> Option<Ordering> {
42 if self.uuid != other.uuid {
43 return None;
44 }
45 self.revision.partial_cmp(&other.revision)
46 }
47}
48
49#[derive(Debug)]
50pub(crate) struct DatabasePtr(*mut ffi::notmuch_database_t);
51
52impl Drop for DatabasePtr {
53 fn drop(&mut self) {
54 unsafe { ffi::notmuch_database_destroy(self.0) };
55 }
56}
57
58#[derive(Clone, Debug)]
59pub struct Database {
60 ptr: Rc<DatabasePtr>,
61}
62
63impl Database {
64 pub fn create<P>(path: P) -> Result<Self>
65 where
66 P: AsRef<Path>,
67 {
68 let path_str = CString::new(path.as_ref().to_str().unwrap()).unwrap();
69
70 let mut db = ptr::null_mut();
71 unsafe { ffi::notmuch_database_create(path_str.as_ptr(), &mut db) }.as_result()?;
72
73 Ok(Database {
74 ptr: Rc::new(DatabasePtr(db)),
75 })
76 }
77
78 #[deprecated = "Replaced with `open_with_config`"]
79 pub fn open<P>(path: P, mode: DatabaseMode) -> Result<Self>
80 where
81 P: AsRef<Path>,
82 {
83 let path_str = CString::new(path.as_ref().to_str().unwrap()).unwrap();
84
85 let mut db = ptr::null_mut();
86 unsafe { ffi::notmuch_database_open(path_str.as_ptr(), mode.into(), &mut db) }
87 .as_result()?;
88
89 Ok(Database {
90 ptr: Rc::new(DatabasePtr(db)),
91 })
92 }
93
94 #[cfg(feature = "v0_32")]
95 pub fn open_with_config<DP, CP>(
96 database_path: Option<DP>,
97 mode: DatabaseMode,
98 config_path: Option<CP>,
99 profile: Option<&str>,
100 ) -> Result<Self>
101 where
102 DP: AsRef<Path>,
103 CP: AsRef<Path>,
104 {
105 let database_path_str =
106 database_path.map(|p| CString::new(p.as_ref().to_str().unwrap()).unwrap());
107 let database_path_ptr = database_path_str
108 .as_ref()
109 .map(|p| p.as_ptr())
110 .unwrap_or_else(|| ptr::null());
111
112 let config_path_str =
113 config_path.map(|p| CString::new(p.as_ref().to_str().unwrap()).unwrap());
114 let config_path_ptr = config_path_str
115 .as_ref()
116 .map(|p| p.as_ptr())
117 .unwrap_or_else(|| ptr::null());
118
119 let profile_str = profile.map(|p| CString::new(p).unwrap());
120 let profile_ptr = profile_str
121 .as_ref()
122 .map(|p| p.as_ptr())
123 .unwrap_or_else(|| ptr::null());
124
125 let mut db = ptr::null_mut();
126 let mut error_message = ptr::null_mut();
127 unsafe {
128 ffi::notmuch_database_open_with_config(
129 database_path_ptr,
130 mode.into(),
131 config_path_ptr,
132 profile_ptr,
133 &mut db,
134 &mut error_message,
135 )
136 }
137 .as_verbose_result(error_message)?;
138
139 Ok(Database {
140 ptr: Rc::new(DatabasePtr(db)),
141 })
142 }
143
144 pub fn close(&self) -> Result<()> {
145 unsafe { ffi::notmuch_database_close(self.ptr.0) }.as_result()?;
146
147 Ok(())
148 }
149
150 pub fn compact<P, F>(path: P, backup_path: Option<&P>) -> Result<()>
151 where
152 P: AsRef<Path>,
153 F: FnMut(&str),
154 {
155 let status: Option<F> = None;
156 Database::_compact(path, backup_path, status)
157 }
158
159 pub fn compact_with_status<P, F>(path: P, backup_path: Option<&P>, status: F) -> Result<()>
160 where
161 P: AsRef<Path>,
162 F: FnMut(&str),
163 {
164 Database::_compact(path, backup_path, Some(status))
165 }
166
167 fn _compact<P, F>(path: P, backup_path: Option<&P>, status: Option<F>) -> Result<()>
168 where
169 P: AsRef<Path>,
170 F: FnMut(&str),
171 {
172 extern "C" fn wrapper<F: FnMut(&str)>(
173 message: *const libc::c_char,
174 closure: *mut libc::c_void,
175 ) {
176 let closure = closure as *mut F;
177 unsafe { (*closure)(message.to_str().unwrap()) }
178 }
179
180 let path_str = CString::new(path.as_ref().to_str().unwrap()).unwrap();
181
182 let backup_path = backup_path.map(|p| CString::new(p.as_ref().to_str().unwrap()).unwrap());
183
184 unsafe {
185 ffi::notmuch_database_compact(
186 path_str.as_ptr(),
187 backup_path.map_or(ptr::null(), |p| p.as_ptr()),
188 if status.is_some() {
189 Some(wrapper::<F>)
190 } else {
191 None
192 },
193 status.map_or(ptr::null_mut(), |f| &f as *const _ as *mut libc::c_void),
194 )
195 }
196 .as_result()?;
197
198 Ok(())
199 }
200
201 pub fn path(&self) -> &Path {
202 Path::new(
203 unsafe { ffi::notmuch_database_get_path(self.ptr.0) }
204 .to_str()
205 .unwrap(),
206 )
207 }
208
209 pub fn version(&self) -> u32 {
210 unsafe { ffi::notmuch_database_get_version(self.ptr.0) }
211 }
212
213 #[cfg(feature = "v0_21")]
214 pub fn revision(&self) -> Revision {
215 let uuid_p: *const libc::c_char = ptr::null();
216 let revision = unsafe {
217 ffi::notmuch_database_get_revision(
218 self.ptr.0,
219 (&uuid_p) as *const _ as *mut *const libc::c_char,
220 )
221 };
222
223 let uuid = unsafe { CStr::from_ptr(uuid_p) };
224
225 Revision {
226 revision,
227 uuid: uuid.to_string_lossy().into_owned(),
228 }
229 }
230
231 pub fn needs_upgrade(&self) -> bool {
232 unsafe { ffi::notmuch_database_needs_upgrade(self.ptr.0) == 1 }
233 }
234
235 pub fn upgrade<F>(&self) -> Result<()>
236 where
237 F: FnMut(f64),
238 {
239 let status: Option<F> = None;
240 self._upgrade(status)
241 }
242
243 pub fn upgrade_with_status<F>(&self, status: F) -> Result<()>
244 where
245 F: FnMut(f64),
246 {
247 self._upgrade(Some(status))
248 }
249
250 fn _upgrade<F>(&self, status: Option<F>) -> Result<()>
251 where
252 F: FnMut(f64),
253 {
254 #[allow(trivial_numeric_casts)]
255 extern "C" fn wrapper<F>(closure: *mut libc::c_void, progress: libc::c_double)
256 where
257 F: FnMut(f64),
258 {
259 let closure = closure as *mut F;
260 unsafe { (*closure)(progress as f64) }
261 }
262
263 unsafe {
264 ffi::notmuch_database_upgrade(
265 self.ptr.0,
266 if status.is_some() {
267 Some(wrapper::<F>)
268 } else {
269 None
270 },
271 status.map_or(ptr::null_mut(), |f| &f as *const _ as *mut libc::c_void),
272 )
273 }
274 .as_result()?;
275
276 Ok(())
277 }
278
279 pub fn directory<P>(&self, path: P) -> Result<Option<Directory>>
280 where
281 P: AsRef<Path>,
282 {
283 let path_str = CString::new(path.as_ref().to_str().unwrap()).unwrap();
284
285 let mut dir = ptr::null_mut();
286 unsafe { ffi::notmuch_database_get_directory(self.ptr.0, path_str.as_ptr(), &mut dir) }
287 .as_result()?;
288
289 if dir.is_null() {
290 Ok(None)
291 } else {
292 Ok(Some(Directory::from_ptr(dir, self.clone())))
293 }
294 }
295
296 pub fn config_list(&self, prefix: &str) -> Result<ConfigList> {
297 let prefix_str = CString::new(prefix).unwrap();
298
299 let mut cfgs = ptr::null_mut();
300 unsafe {
301 ffi::notmuch_database_get_config_list(self.ptr.0, prefix_str.as_ptr(), &mut cfgs)
302 }
303 .as_result()?;
304
305 Ok(ConfigList::from_ptr(cfgs, self.clone()))
306 }
307
308 #[cfg(feature = "v0_32")]
309 pub fn config(&self, key: ConfigKey) -> Option<String> {
310 let val_str = unsafe { ffi::notmuch_config_get(self.ptr.0, key.into()) };
311
312 if val_str.is_null() {
313 None
314 } else {
315 Some(val_str.to_string_lossy().to_string())
316 }
317 }
318
319 #[cfg(feature = "v0_32")]
320 pub fn config_set(&self, key: ConfigKey, val: &str) -> Result<()> {
321 let val_str = CString::new(val).unwrap();
322
323 unsafe { ffi::notmuch_config_set(self.ptr.0, key.into(), val_str.as_ptr()) }.as_result()
324 }
325
326 #[cfg(feature = "v0_32")]
327 pub fn config_values(&self, key: ConfigKey) -> Option<ConfigValues> {
328 let values = unsafe { ffi::notmuch_config_get_values(self.ptr.0, key.into()) };
329
330 if values.is_null() {
331 None
332 } else {
333 Some(ConfigValues::from_ptr(values, self.clone()))
334 }
335 }
336
337 #[cfg(feature = "v0_32")]
338 pub fn config_values_string(&self, key: &str) -> Option<ConfigValues> {
339 let key_str = CString::new(key).unwrap();
340
341 let values = unsafe { ffi::notmuch_config_get_values_string(self.ptr.0, key_str.as_ptr()) };
342
343 if values.is_null() {
344 None
345 } else {
346 Some(ConfigValues::from_ptr(values, self.clone()))
347 }
348 }
349
350 #[cfg(feature = "v0_32")]
351 pub fn config_pairs(&self, prefix: &str) -> Option<ConfigPairs> {
352 let prefix_str = CString::new(prefix).unwrap();
353
354 let pairs = unsafe { ffi::notmuch_config_get_pairs(self.ptr.0, prefix_str.as_ptr()) };
355
356 if pairs.is_null() {
357 None
358 } else {
359 Some(ConfigPairs::from_ptr(pairs, self.clone()))
360 }
361 }
362
363 #[cfg(feature = "v0_32")]
364 pub fn config_bool(&self, key: ConfigKey) -> Result<bool> {
365 let mut value: ffi::notmuch_bool_t = 0;
366
367 unsafe { ffi::notmuch_config_get_bool(self.ptr.0, key.into(), &mut value) }.as_result()?;
368
369 Ok(value != 0)
370 }
371
372 #[cfg(feature = "v0_32")]
373 pub fn config_path(&self) -> Option<&Path> {
374 let config_path_str = unsafe { ffi::notmuch_config_path(self.ptr.0) };
375
376 if config_path_str.is_null() {
377 None
378 } else {
379 Some(Path::new(config_path_str.to_str().unwrap()))
380 }
381 }
382
383 pub fn create_query(&self, query_string: &str) -> Result<Query> {
384 let query_str = CString::new(query_string).unwrap();
385
386 let query = unsafe { ffi::notmuch_query_create(self.ptr.0, query_str.as_ptr()) };
387
388 Ok(Query::from_ptr(query, self.clone()))
389 }
390
391 pub fn all_tags(&self) -> Result<Tags> {
392 let tags = unsafe { ffi::notmuch_database_get_all_tags(self.ptr.0) };
393
394 Ok(Tags::from_ptr(tags, self.clone()))
395 }
396
397 pub fn find_message(&self, message_id: &str) -> Result<Option<Message>> {
398 let message_id_str = CString::new(message_id).unwrap();
399
400 let mut msg = ptr::null_mut();
401 unsafe {
402 ffi::notmuch_database_find_message(self.ptr.0, message_id_str.as_ptr(), &mut msg)
403 }
404 .as_result()?;
405
406 if msg.is_null() {
407 Ok(None)
408 } else {
409 Ok(Some(Message::from_ptr(msg, self.clone())))
410 }
411 }
412
413 pub fn find_message_by_filename<P>(&self, filename: &P) -> Result<Option<Message>>
414 where
415 P: AsRef<Path>,
416 {
417 let path_str = CString::new(filename.as_ref().to_str().unwrap()).unwrap();
418
419 let mut msg = ptr::null_mut();
420 unsafe {
421 ffi::notmuch_database_find_message_by_filename(self.ptr.0, path_str.as_ptr(), &mut msg)
422 }
423 .as_result()?;
424
425 if msg.is_null() {
426 Ok(None)
427 } else {
428 Ok(Some(Message::from_ptr(msg, self.clone())))
429 }
430 }
431
432 pub fn remove_message<P>(&self, path: P) -> Result<()>
433 where
434 P: AsRef<Path>,
435 {
436 match path.as_ref().to_str() {
437 Some(path_str) => {
438 let msg_path = CString::new(path_str).unwrap();
439
440 unsafe { ffi::notmuch_database_remove_message(self.ptr.0, msg_path.as_ptr()) }
441 .as_result()
442 }
443 None => Err(Error::NotmuchError(Status::FileError)),
444 }
445 }
446
447 pub fn default_indexopts(&self) -> Result<IndexOpts> {
448 let opts = unsafe { ffi::notmuch_database_get_default_indexopts(self.ptr.0) };
449
450 Ok(IndexOpts::from_ptr(opts, self.clone()))
451 }
452
453 pub fn index_file<P>(&self, path: P, indexopts: Option<IndexOpts>) -> Result<Message>
454 where
455 P: AsRef<Path>,
456 {
457 let opts = indexopts.map_or(ptr::null_mut(), |opt| opt.ptr.0);
458
459 match path.as_ref().to_str() {
460 Some(path_str) => {
461 let msg_path = CString::new(path_str).unwrap();
462
463 let mut msg = ptr::null_mut();
464 unsafe {
465 ffi::notmuch_database_index_file(self.ptr.0, msg_path.as_ptr(), opts, &mut msg)
466 }
467 .as_result()?;
468
469 Ok(Message::from_ptr(msg, self.clone()))
470 }
471 None => Err(Error::NotmuchError(Status::FileError)),
472 }
473 }
474
475 pub fn begin_atomic(&self) -> Result<()> {
476 unsafe { ffi::notmuch_database_begin_atomic(self.ptr.0) }.as_result()
477 }
478
479 pub fn end_atomic(&self) -> Result<()> {
480 unsafe { ffi::notmuch_database_end_atomic(self.ptr.0) }.as_result()
481 }
482}
483
484#[derive(Debug)]
485pub struct AtomicOperation {
486 database: Database,
487}
488
489impl AtomicOperation {
490 pub fn new(database: &Database) -> Result<Self> {
491 database.begin_atomic()?;
492 Ok(AtomicOperation {
493 database: database.clone(),
494 })
495 }
496}
497
498impl Drop for AtomicOperation {
499 fn drop(&mut self) {
500 let _ = self.database.end_atomic();
501 }
502}