diff --git a/Cargo.lock b/Cargo.lock index 0e85415a5..af1506ce0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1625,6 +1625,12 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + [[package]] name = "signal-hook" version = "0.1.15" @@ -1902,6 +1908,7 @@ checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ "rand", "serde", + "sha1", ] [[package]] diff --git a/melib/Cargo.toml b/melib/Cargo.toml index dbaf3c333..bc2903bc8 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -32,7 +32,7 @@ native-tls = { version ="0.2.3", optional=true } serde = { version = "1.0.71", features = ["rc", ] } serde_derive = "1.0.71" bincode = "1.2.0" -uuid = { version = "0.8.1", features = ["serde", "v4"] } +uuid = { version = "0.8.1", features = ["serde", "v4", "v5"] } unicode-segmentation = { version = "1.2.1", optional = true } libc = {version = "0.2.59", features = ["extra_traits",]} diff --git a/melib/src/backends/notmuch.rs b/melib/src/backends/notmuch.rs index 44b0ede88..bc81471b1 100644 --- a/melib/src/backends/notmuch.rs +++ b/melib/src/backends/notmuch.rs @@ -37,19 +37,6 @@ use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, RwLock}; -pub mod bindings; -use bindings::*; - -#[derive(Debug, Clone)] -struct DbConnection { - lib: Arc, - inner: Arc>, - database_ph: std::marker::PhantomData<&'static mut notmuch_database_t>, -} - -unsafe impl Send for DbConnection {} -unsafe impl Sync for DbConnection {} - macro_rules! call { ($lib:expr, $func:ty) => {{ let func: libloading::Symbol<$func> = $lib.get(stringify!($func).as_bytes()).unwrap(); @@ -57,21 +44,6 @@ macro_rules! call { }}; } -#[derive(Debug)] -pub struct NotmuchError(String); - -impl std::fmt::Display for NotmuchError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } -} - -impl Error for NotmuchError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - None - } -} - macro_rules! try_call { ($lib:expr, $call:expr) => {{ let status = $call; @@ -86,6 +58,39 @@ macro_rules! try_call { }}; } +pub mod bindings; +use bindings::*; +mod message; +pub use message::*; +mod tags; +pub use tags::*; +mod thread; +pub use thread::*; + +#[derive(Debug, Clone)] +pub struct DbConnection { + pub lib: Arc, + pub inner: Arc>, + pub database_ph: std::marker::PhantomData<&'static mut notmuch_database_t>, +} + +unsafe impl Send for DbConnection {} +unsafe impl Sync for DbConnection {} +#[derive(Debug)] +pub struct NotmuchError(String); + +impl std::fmt::Display for NotmuchError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +impl Error for NotmuchError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } +} + impl Drop for DbConnection { fn drop(&mut self) { let inner = self.inner.write().unwrap(); @@ -271,14 +276,7 @@ impl NotmuchDb { let mut ret = SmallVec::new(); let iter = query.search()?; for message in iter { - let msg_id = unsafe { call!(self.lib, notmuch_message_get_message_id)(message) }; - let c_str = unsafe { CStr::from_ptr(msg_id) }; - let env_hash = { - let mut hasher = DefaultHasher::default(); - c_str.hash(&mut hasher); - hasher.finish() - }; - ret.push(env_hash); + ret.push(message.env_hash()); } Ok(ret) @@ -359,24 +357,14 @@ impl MailBackend for NotmuchDb { let mut done: bool = false; for _ in 0..chunk_size { if let Some(message_id) = self.iter.next() { - let mut message: *mut notmuch_message_t = std::ptr::null_mut(); - unsafe { - call!(self.lib, notmuch_database_find_message)( - *self.database.inner.read().unwrap(), - message_id.as_ptr(), - &mut message as *mut _, - ) - }; - if message.is_null() { + let message = if let Ok(v) = + Message::find_message(self.lib.clone(), &self.database, &message_id) + { + v + } else { continue; - } - match notmuch_message_into_envelope( - self.lib.clone(), - self.index.clone(), - self.tag_index.clone(), - self.database.clone(), - message, - ) { + }; + match message.into_envelope(self.index.clone(), self.tag_index.clone()) { Ok(env) => { mailbox_index_lck .entry(env.hash()) @@ -388,13 +376,7 @@ impl MailBackend for NotmuchDb { ret.push(env); } Err(err) => { - debug!("could not parse message {:?} {}", err, { - let fs_path = unsafe { - call!(self.lib, notmuch_message_get_filename)(message) - }; - let c_str = unsafe { CStr::from_ptr(fs_path) }; - String::from_utf8_lossy(c_str.to_bytes()) - }); + debug!("could not parse message {:?}", err); } } } else { @@ -438,10 +420,14 @@ impl MailBackend for NotmuchDb { *total_lck = query.count()? as usize; *unseen_lck = 0; } + let mut index_lck = index.write().unwrap(); v = query .search()? .into_iter() - .map(|m| notmuch_message_insert(&lib, &index, m)) + .map(|m| { + index_lck.insert(m.env_hash(), m.msg_id_cstr().into()); + m.msg_id_cstr().into() + }) .collect(); } @@ -723,22 +709,16 @@ impl MailBackend for NotmuchDb { let tag_index = self.tag_index.clone(); let mut index_lck = self.index.write().unwrap(); for env_hash in env_hashes.iter() { - let mut message: *mut notmuch_message_t = std::ptr::null_mut(); - unsafe { - call!(self.lib, notmuch_database_find_message)( - *database.inner.read().unwrap(), - index_lck[&env_hash].as_ptr(), - &mut message as *mut _, - ) - }; - if message.is_null() { - return Err(MeliError::new(format!( - "Error, message with path {:?} not found in notmuch database.", - index_lck[&env_hash] - ))); - } + let message = + match Message::find_message(self.lib.clone(), &database, &index_lck[&env_hash]) { + Ok(v) => v, + Err(err) => { + debug!("not found {}", err); + continue; + } + }; - let tags = TagIterator::new(self.lib.clone(), message).collect::>(); + let tags = TagIterator::new(message.clone()).collect::>(); //flags.set(f, value); macro_rules! cstr { @@ -755,16 +735,7 @@ impl MailBackend for NotmuchDb { if tags.contains(l) { continue; } - if let Err(err) = unsafe { - try_call!( - self.lib, - call!(self.lib, notmuch_message_add_tag)(message, l.as_ptr()) - ) - } { - return Err( - MeliError::new("Could not set tag.").set_source(Some(Arc::new(err))) - ); - } + message.add_tag(l)?; }}; } macro_rules! remove_tag { @@ -776,16 +747,7 @@ impl MailBackend for NotmuchDb { if !tags.contains(l) { continue; } - if let Err(err) = unsafe { - try_call!( - self.lib, - call!(self.lib, notmuch_message_remove_tag)(message, l.as_ptr()) - ) - } { - return Err( - MeliError::new("Could not set tag.").set_source(Some(Arc::new(err))) - ); - } + message.remove_tag(l)?; }}; } @@ -817,19 +779,11 @@ impl MailBackend for NotmuchDb { } /* Update message filesystem path. */ - if let Err(err) = unsafe { - try_call!( - self.lib, - call!(self.lib, notmuch_message_tags_to_maildir_flags)(message) - ) - } { - return Err(MeliError::new("Could not set flags.").set_source(Some(Arc::new(err)))); - } + message.tags_to_maildir_flags()?; - let msg_id = unsafe { call!(self.lib, notmuch_message_get_message_id)(message) }; - let c_str = unsafe { CStr::from_ptr(msg_id) }; + let msg_id = message.msg_id_cstr(); if let Some(p) = index_lck.get_mut(&env_hash) { - *p = c_str.into(); + *p = msg_id.into(); } } for (f, v) in flags.iter() { @@ -867,18 +821,10 @@ struct NotmuchOp { impl BackendOp for NotmuchOp { fn as_bytes(&mut self) -> ResultFuture> { - let mut message: *mut notmuch_message_t = std::ptr::null_mut(); let index_lck = self.index.write().unwrap(); - unsafe { - call!(self.lib, notmuch_database_find_message)( - *self.database.inner.read().unwrap(), - index_lck[&self.hash].as_ptr(), - &mut message as *mut _, - ) - }; - let fs_path = unsafe { call!(self.lib, notmuch_message_get_filename)(message) }; - let c_str = unsafe { CStr::from_ptr(fs_path) }; - let mut f = std::fs::File::open(&OsStr::from_bytes(c_str.to_bytes()))?; + let message = + Message::find_message(self.lib.clone(), &self.database, &index_lck[&self.hash])?; + let mut f = std::fs::File::open(message.get_filename())?; let mut response = Vec::new(); f.read_to_end(&mut response)?; self.bytes = Some(response); @@ -887,144 +833,14 @@ impl BackendOp for NotmuchOp { } fn fetch_flags(&self) -> ResultFuture { - let mut message: *mut notmuch_message_t = std::ptr::null_mut(); let index_lck = self.index.write().unwrap(); - unsafe { - call!(self.lib, notmuch_database_find_message)( - *self.database.inner.read().unwrap(), - index_lck[&self.hash].as_ptr(), - &mut message as *mut _, - ) - }; - let (flags, _tags) = TagIterator::new(self.lib.clone(), message).collect_flags_and_tags(); + let message = + Message::find_message(self.lib.clone(), &self.database, &index_lck[&self.hash])?; + let (flags, _tags) = TagIterator::new(message).collect_flags_and_tags(); Ok(Box::pin(async move { Ok(flags) })) } } -pub struct MessageIterator<'query> { - lib: Arc, - messages: *mut notmuch_messages_t, - _ph: std::marker::PhantomData<*const Query<'query>>, -} - -impl Iterator for MessageIterator<'_> { - type Item = *mut notmuch_message_t; - fn next(&mut self) -> Option { - if self.messages.is_null() { - None - } else if unsafe { call!(self.lib, notmuch_messages_valid)(self.messages) } == 1 { - let ret = Some(unsafe { call!(self.lib, notmuch_messages_get)(self.messages) }); - unsafe { - call!(self.lib, notmuch_messages_move_to_next)(self.messages); - } - ret - } else { - self.messages = std::ptr::null_mut(); - None - } - } -} - -pub struct TagIterator { - lib: Arc, - tags: *mut notmuch_tags_t, - message: *mut notmuch_message_t, -} - -impl TagIterator { - fn new(lib: Arc, message: *mut notmuch_message_t) -> Self { - TagIterator { - tags: unsafe { call!(lib, notmuch_message_get_tags)(message) }, - lib, - message, - } - } - - fn collect_flags_and_tags(self) -> (Flag, Vec) { - fn flags(path: &CStr) -> Flag { - let mut flag = Flag::default(); - let mut ptr = path.to_bytes().len().saturating_sub(1); - let mut is_valid = true; - while !path.to_bytes()[..ptr + 1].ends_with(b":2,") { - match path.to_bytes()[ptr] { - b'D' => flag |= Flag::DRAFT, - b'F' => flag |= Flag::FLAGGED, - b'P' => flag |= Flag::PASSED, - b'R' => flag |= Flag::REPLIED, - b'S' => flag |= Flag::SEEN, - b'T' => flag |= Flag::TRASHED, - _ => { - is_valid = false; - break; - } - } - if ptr == 0 { - is_valid = false; - break; - } - ptr -= 1; - } - - if !is_valid { - return Flag::default(); - } - - flag - } - let fs_path = unsafe { call!(self.lib, notmuch_message_get_filename)(self.message) }; - let c_str = unsafe { CStr::from_ptr(fs_path) }; - - let tags = self.collect::>(); - let mut flag = Flag::default(); - let mut vec = vec![]; - for t in tags { - match t.to_bytes() { - b"draft" => { - flag.set(Flag::DRAFT, true); - } - b"flagged" => { - flag.set(Flag::FLAGGED, true); - } - b"passed" => { - flag.set(Flag::PASSED, true); - } - b"replied" => { - flag.set(Flag::REPLIED, true); - } - b"unread" => { - flag.set(Flag::SEEN, false); - } - b"trashed" => { - flag.set(Flag::TRASHED, true); - } - _other => { - vec.push(t.to_string_lossy().into_owned()); - } - } - } - - (flag | flags(c_str), vec) - } -} - -impl Iterator for TagIterator { - type Item = &'static CStr; - fn next(&mut self) -> Option { - if self.tags.is_null() { - None - } else if unsafe { call!(self.lib, notmuch_tags_valid)(self.tags) } == 1 { - let ret = Some(unsafe { CStr::from_ptr(call!(self.lib, notmuch_tags_get)(self.tags)) }); - unsafe { - call!(self.lib, notmuch_tags_move_to_next)(self.tags); - } - ret - } else { - self.tags = std::ptr::null_mut(); - None - } - } -} - pub struct Query<'s> { lib: Arc, ptr: *mut notmuch_query_t, @@ -1078,6 +894,7 @@ impl<'s> Query<'s> { messages, lib: self.lib.clone(), _ph: std::marker::PhantomData, + is_from_thread: false, }) } } @@ -1089,73 +906,3 @@ impl Drop for Query<'_> { } } } - -fn notmuch_message_insert( - lib: &libloading::Library, - index: &RwLock>, - message: *mut notmuch_message_t, -) -> CString { - let msg_id = unsafe { call!(lib, notmuch_message_get_message_id)(message) }; - let env_hash = { - let c_str = unsafe { CStr::from_ptr(msg_id) }; - let mut hasher = DefaultHasher::default(); - c_str.hash(&mut hasher); - hasher.finish() - }; - let c_str = unsafe { CStr::from_ptr(msg_id) }; - index.write().unwrap().insert(env_hash, c_str.into()); - c_str.into() -} - -fn notmuch_message_into_envelope( - lib: Arc, - index: Arc>>, - tag_index: Arc>>, - database: Arc, - message: *mut notmuch_message_t, -) -> Result { - let mut response = Vec::new(); - let fs_path = unsafe { call!(lib, notmuch_message_get_filename)(message) }; - let c_str = unsafe { CStr::from_ptr(fs_path) }; - let mut f = std::fs::File::open(&OsStr::from_bytes(c_str.to_bytes()))?; - f.read_to_end(&mut response)?; - let msg_id = unsafe { call!(lib, notmuch_message_get_message_id)(message) }; - let env_hash = { - let c_str = unsafe { CStr::from_ptr(msg_id) }; - let mut hasher = DefaultHasher::default(); - c_str.hash(&mut hasher); - hasher.finish() - }; - { - let c_str = unsafe { CStr::from_ptr(msg_id) }; - index.write().unwrap().insert(env_hash, c_str.into()); - } - let op = Box::new(NotmuchOp { - database, - lib: lib.clone(), - hash: env_hash, - index: index.clone(), - bytes: Some(response), - tag_index: tag_index.clone(), - }); - Envelope::from_token(op, env_hash) - .map(|mut env| { - let mut tag_lock = tag_index.write().unwrap(); - let (flags, tags) = TagIterator::new(lib.clone(), message).collect_flags_and_tags(); - for tag in tags { - let mut hasher = DefaultHasher::new(); - hasher.write(tag.as_bytes()); - let num = hasher.finish(); - if !tag_lock.contains_key(&num) { - tag_lock.insert(num, tag); - } - env.labels_mut().push(num); - } - env.set_flags(flags); - env - }) - .chain_err_summary(|| { - index.write().unwrap().remove(&env_hash); - format!("could not parse path {:?}", c_str) - }) -} diff --git a/melib/src/backends/notmuch/message.rs b/melib/src/backends/notmuch/message.rs new file mode 100644 index 000000000..063fdda96 --- /dev/null +++ b/melib/src/backends/notmuch/message.rs @@ -0,0 +1,229 @@ +/* + * melib - notmuch backend + * + * Copyright 2020 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; +use crate::thread::{ThreadHash, ThreadNode, ThreadNodeHash}; + +#[derive(Clone)] +pub struct Message<'m> { + pub lib: Arc, + pub message: *mut notmuch_message_t, + pub is_from_thread: bool, + pub _ph: std::marker::PhantomData<&'m notmuch_message_t>, +} + +impl<'m> Message<'m> { + pub fn find_message( + lib: Arc, + db: &'m DbConnection, + msg_id: &CStr, + ) -> Result> { + let mut message: *mut notmuch_message_t = std::ptr::null_mut(); + unsafe { + call!(lib, notmuch_database_find_message)( + *db.inner.read().unwrap(), + msg_id.as_ptr(), + &mut message as *mut _, + ) + }; + if message.is_null() { + return Err(MeliError::new(format!( + "Message with message id {:?} not found in notmuch database.", + msg_id + ))); + } + Ok(Message { + lib, + message, + is_from_thread: false, + _ph: std::marker::PhantomData, + }) + } + + pub fn env_hash(&self) -> EnvelopeHash { + let msg_id = unsafe { call!(self.lib, notmuch_message_get_message_id)(self.message) }; + let c_str = unsafe { CStr::from_ptr(msg_id) }; + { + let mut hasher = DefaultHasher::default(); + c_str.hash(&mut hasher); + hasher.finish() + } + } + + pub fn msg_id(&self) -> &[u8] { + let c_str = self.msg_id_cstr(); + c_str.to_bytes() + } + + pub fn msg_id_cstr(&self) -> &CStr { + let msg_id = unsafe { call!(self.lib, notmuch_message_get_message_id)(self.message) }; + unsafe { CStr::from_ptr(msg_id) } + } + + pub fn date(&self) -> crate::datetime::UnixTimestamp { + (unsafe { call!(self.lib, notmuch_message_get_date)(self.message) }) as u64 + } + + pub fn into_envelope( + self, + index: Arc>>, + tag_index: Arc>>, + ) -> Result { + let mut contents = Vec::new(); + let path = self.get_filename().to_os_string(); + let mut f = std::fs::File::open(&path)?; + f.read_to_end(&mut contents)?; + let env_hash = self.env_hash(); + let mut env = Envelope::from_bytes(&contents, None).chain_err_summary(|| { + index.write().unwrap().remove(&env_hash); + format!("could not parse path {:?}", path) + })?; + env.set_hash(env_hash); + index + .write() + .unwrap() + .insert(env_hash, self.msg_id_cstr().into()); + let mut tag_lock = tag_index.write().unwrap(); + let (flags, tags) = TagIterator::new(self).collect_flags_and_tags(); + for tag in tags { + let mut hasher = DefaultHasher::new(); + hasher.write(tag.as_bytes()); + let num = hasher.finish(); + if !tag_lock.contains_key(&num) { + tag_lock.insert(num, tag); + } + env.labels_mut().push(num); + } + env.set_flags(flags); + Ok(env) + } + + pub fn replies_iter(&self) -> Option { + if self.is_from_thread { + let messages = unsafe { call!(self.lib, notmuch_message_get_replies)(self.message) }; + if messages.is_null() { + None + } else { + Some(MessageIterator { + lib: self.lib.clone(), + messages, + _ph: std::marker::PhantomData, + is_from_thread: true, + }) + } + } else { + None + } + } + + pub fn into_thread_node(&self) -> (ThreadNodeHash, ThreadNode) { + ( + ThreadNodeHash::from(self.msg_id()), + ThreadNode { + message: Some(self.env_hash()), + parent: None, + children: vec![], + date: self.date(), + show_subject: true, + group: ThreadHash::new(), + unseen: false, + }, + ) + } + + pub fn add_tag(&self, tag: &CStr) -> Result<()> { + if let Err(err) = unsafe { + try_call!( + self.lib, + call!(self.lib, notmuch_message_add_tag)(self.message, tag.as_ptr()) + ) + } { + return Err(MeliError::new("Could not set tag.").set_source(Some(Arc::new(err)))); + } + Ok(()) + } + + pub fn remove_tag(&self, tag: &CStr) -> Result<()> { + if let Err(err) = unsafe { + try_call!( + self.lib, + call!(self.lib, notmuch_message_remove_tag)(self.message, tag.as_ptr()) + ) + } { + return Err(MeliError::new("Could not set tag.").set_source(Some(Arc::new(err)))); + } + Ok(()) + } + + pub fn tags_to_maildir_flags(&self) -> Result<()> { + if let Err(err) = unsafe { + try_call!( + self.lib, + call!(self.lib, notmuch_message_tags_to_maildir_flags)(self.message) + ) + } { + return Err(MeliError::new("Could not set flags.").set_source(Some(Arc::new(err)))); + } + Ok(()) + } + + pub fn get_filename(&self) -> &OsStr { + let fs_path = unsafe { call!(self.lib, notmuch_message_get_filename)(self.message) }; + let c_str = unsafe { CStr::from_ptr(fs_path) }; + &OsStr::from_bytes(c_str.to_bytes()) + } +} + +impl Drop for Message<'_> { + fn drop(&mut self) { + unsafe { call!(self.lib, notmuch_message_destroy)(self.message) }; + } +} + +pub struct MessageIterator<'query> { + pub lib: Arc, + pub messages: *mut notmuch_messages_t, + pub is_from_thread: bool, + pub _ph: std::marker::PhantomData<*const Query<'query>>, +} + +impl<'q> Iterator for MessageIterator<'q> { + type Item = Message<'q>; + fn next(&mut self) -> Option { + if self.messages.is_null() { + None + } else if unsafe { call!(self.lib, notmuch_messages_valid)(self.messages) } == 1 { + let message = unsafe { call!(self.lib, notmuch_messages_get)(self.messages) }; + unsafe { + call!(self.lib, notmuch_messages_move_to_next)(self.messages); + } + Some(Message { + lib: self.lib.clone(), + message, + is_from_thread: self.is_from_thread, + _ph: std::marker::PhantomData, + }) + } else { + self.messages = std::ptr::null_mut(); + None + } + } +} diff --git a/melib/src/backends/notmuch/tags.rs b/melib/src/backends/notmuch/tags.rs new file mode 100644 index 000000000..972f9483e --- /dev/null +++ b/melib/src/backends/notmuch/tags.rs @@ -0,0 +1,129 @@ +/* + * melib - notmuch backend + * + * Copyright 2020 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +pub struct TagIterator<'m> { + pub tags: *mut notmuch_tags_t, + pub message: Message<'m>, +} + +impl Drop for TagIterator<'_> { + fn drop(&mut self) { + unsafe { call!(self.message.lib, notmuch_tags_destroy)(self.tags) }; + } +} + +impl<'m> TagIterator<'m> { + pub fn new(message: Message<'m>) -> TagIterator<'m> { + TagIterator { + tags: unsafe { call!(message.lib, notmuch_message_get_tags)(message.message) }, + message, + } + } + + pub fn collect_flags_and_tags(self) -> (Flag, Vec) { + fn flags(path: &CStr) -> Flag { + let mut flag = Flag::default(); + let mut ptr = path.to_bytes().len().saturating_sub(1); + let mut is_valid = true; + while !path.to_bytes()[..ptr + 1].ends_with(b":2,") { + match path.to_bytes()[ptr] { + b'D' => flag |= Flag::DRAFT, + b'F' => flag |= Flag::FLAGGED, + b'P' => flag |= Flag::PASSED, + b'R' => flag |= Flag::REPLIED, + b'S' => flag |= Flag::SEEN, + b'T' => flag |= Flag::TRASHED, + _ => { + is_valid = false; + break; + } + } + if ptr == 0 { + is_valid = false; + break; + } + ptr -= 1; + } + + if !is_valid { + return Flag::default(); + } + + flag + } + let fs_path = + unsafe { call!(self.message.lib, notmuch_message_get_filename)(self.message.message) }; + let c_str = unsafe { CStr::from_ptr(fs_path) }; + + let tags = self.collect::>(); + let mut flag = Flag::default(); + let mut vec = vec![]; + for t in tags { + match t.to_bytes() { + b"draft" => { + flag.set(Flag::DRAFT, true); + } + b"flagged" => { + flag.set(Flag::FLAGGED, true); + } + b"passed" => { + flag.set(Flag::PASSED, true); + } + b"replied" => { + flag.set(Flag::REPLIED, true); + } + b"unread" => { + flag.set(Flag::SEEN, false); + } + b"trashed" => { + flag.set(Flag::TRASHED, true); + } + _other => { + vec.push(t.to_string_lossy().into_owned()); + } + } + } + + (flag | flags(c_str), vec) + } +} + +impl<'m> Iterator for TagIterator<'m> { + type Item = &'m CStr; + fn next(&mut self) -> Option { + if self.tags.is_null() { + None + } else if unsafe { call!(self.message.lib, notmuch_tags_valid)(self.tags) } == 1 { + let ret = Some(unsafe { + CStr::from_ptr(call!(self.message.lib, notmuch_tags_get)(self.tags)) + }); + unsafe { + call!(self.message.lib, notmuch_tags_move_to_next)(self.tags); + } + ret + } else { + self.tags = std::ptr::null_mut(); + None + } + } +} diff --git a/melib/src/backends/notmuch/thread.rs b/melib/src/backends/notmuch/thread.rs new file mode 100644 index 000000000..ea2b47e51 --- /dev/null +++ b/melib/src/backends/notmuch/thread.rs @@ -0,0 +1,88 @@ +/* + * melib - notmuch backend + * + * Copyright 2020 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ +use super::*; +use crate::thread::ThreadHash; + +pub struct Thread<'query> { + pub lib: Arc, + pub ptr: *mut notmuch_thread_t, + pub _ph: std::marker::PhantomData<*const Query<'query>>, +} + +impl<'q> Thread<'q> { + pub fn id(&self) -> ThreadHash { + let thread_id = unsafe { call!(self.lib, notmuch_thread_get_thread_id)(self.ptr) }; + let c_str = unsafe { CStr::from_ptr(thread_id) }; + ThreadHash::from(c_str.to_bytes()) + } + + pub fn date(&self) -> crate::datetime::UnixTimestamp { + (unsafe { call!(self.lib, notmuch_thread_get_newest_date)(self.ptr) }) as u64 + } + + pub fn len(&self) -> usize { + (unsafe { call!(self.lib, notmuch_thread_get_total_messages)(self.ptr) }) as usize + } + + pub fn iter(&'q self) -> MessageIterator<'q> { + let ptr = unsafe { call!(self.lib, notmuch_thread_get_messages)(self.ptr) }; + MessageIterator { + lib: self.lib.clone(), + messages: ptr, + is_from_thread: true, + _ph: std::marker::PhantomData, + } + } +} + +impl Drop for Thread<'_> { + fn drop(&mut self) { + unsafe { call!(self.lib, notmuch_thread_destroy)(self.ptr) } + } +} + +pub struct ThreadsIterator<'query> { + pub lib: Arc, + pub threads: *mut notmuch_threads_t, + pub _ph: std::marker::PhantomData<*const Query<'query>>, +} + +impl<'q> Iterator for ThreadsIterator<'q> { + type Item = Thread<'q>; + fn next(&mut self) -> Option { + if self.threads.is_null() { + None + } else if unsafe { call!(self.lib, notmuch_threads_valid)(self.threads) } == 1 { + let thread = unsafe { call!(self.lib, notmuch_threads_get)(self.threads) }; + unsafe { + call!(self.lib, notmuch_threads_move_to_next)(self.threads); + } + Some(Thread { + lib: self.lib.clone(), + ptr: thread, + _ph: std::marker::PhantomData, + }) + } else { + self.threads = std::ptr::null_mut(); + None + } + } +} diff --git a/melib/src/thread.rs b/melib/src/thread.rs index 04bf5fb8f..0b765a03b 100644 --- a/melib/src/thread.rs +++ b/melib/src/thread.rs @@ -75,10 +75,17 @@ macro_rules! uuid_hash_type { } } + impl From<&[u8]> for $n { + fn from(val: &[u8]) -> Self { + $n(Uuid::new_v5(&Uuid::NAMESPACE_URL, val)) + } + } + impl $n { - fn new() -> Self { + pub fn new() -> Self { $n(Uuid::new_v4()) } + pub fn null() -> Self { $n(Uuid::nil()) } @@ -329,16 +336,13 @@ impl Thread { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ThreadNode { - message: Option, - parent: Option, - children: Vec, - date: UnixTimestamp, - show_subject: bool, - pruned: bool, - is_root: bool, + pub message: Option, + pub parent: Option, + pub children: Vec, + pub date: UnixTimestamp, + pub show_subject: bool, pub group: ThreadHash, - - unseen: bool, + pub unseen: bool, } impl Default for ThreadNode { @@ -349,10 +353,7 @@ impl Default for ThreadNode { children: Vec::new(), date: UnixTimestamp::default(), show_subject: true, - pruned: false, - is_root: false, group: ThreadHash::new(), - unseen: false, } } @@ -360,10 +361,9 @@ impl Default for ThreadNode { impl ThreadNode { fn new() -> Self { - ThreadNode { - ..Default::default() - } + ThreadNode::default() } + pub fn show_subject(&self) -> bool { self.show_subject }