notmuch/
database.rs

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
25// Re-exported under database module for pretty namespacin'.
26pub 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}