melib: add set_tags command in BackendOp
parent
f632bc4c08
commit
0eaf17871a
|
@ -18,6 +18,16 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! tag_hash {
|
||||
($tag:ident) => {{
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write($tag.as_bytes());
|
||||
hasher.finish()
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(feature = "imap_backend")]
|
||||
pub mod imap;
|
||||
#[cfg(feature = "maildir_backend")]
|
||||
|
@ -288,6 +298,7 @@ pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
|
|||
//fn copy(&self
|
||||
fn fetch_flags(&self) -> Flag;
|
||||
fn set_flag(&mut self, envelope: &mut Envelope, flag: Flag, value: bool) -> Result<()>;
|
||||
fn set_tag(&mut self, envelope: &mut Envelope, tag: String, value: bool) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Wrapper for BackendOps that are to be set read-only.
|
||||
|
@ -318,6 +329,9 @@ impl BackendOp for ReadOnlyOp {
|
|||
fn set_flag(&mut self, _envelope: &mut Envelope, _flag: Flag, _value: bool) -> Result<()> {
|
||||
Err(MeliError::new("read-only set."))
|
||||
}
|
||||
fn set_tag(&mut self, _envelope: &mut Envelope, _tag: String, _value: bool) -> Result<()> {
|
||||
Err(MeliError::new("read-only set."))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Hash, Eq, Clone, Serialize, Deserialize, PartialEq)]
|
||||
|
|
|
@ -153,6 +153,7 @@ impl MailBackend for ImapType {
|
|||
let handle = {
|
||||
let tx = w.tx();
|
||||
let uid_store = self.uid_store.clone();
|
||||
let tag_index = self.tag_index.clone();
|
||||
let can_create_flags = self.can_create_flags.clone();
|
||||
let folder_path = folder.path().to_string();
|
||||
let folder_hash = folder.hash();
|
||||
|
@ -209,6 +210,7 @@ impl MailBackend for ImapType {
|
|||
conn.read_response(&mut response)
|
||||
);
|
||||
|
||||
let mut tag_lck = tag_index.write().unwrap();
|
||||
while exists > 1 {
|
||||
let mut envelopes = vec![];
|
||||
exit_on_error!(&tx,
|
||||
|
@ -231,8 +233,15 @@ impl MailBackend for ImapType {
|
|||
h.write_usize(uid);
|
||||
h.write(folder_path.as_bytes());
|
||||
env.set_hash(h.finish());
|
||||
if let Some(flags) = flags {
|
||||
if let Some((flags, keywords)) = flags {
|
||||
env.set_flags(flags);
|
||||
for f in keywords {
|
||||
let hash = tag_hash!(f);
|
||||
if !tag_lck.contains_key(&hash) {
|
||||
tag_lck.insert(hash, f);
|
||||
}
|
||||
env.labels_mut().push(hash);
|
||||
}
|
||||
}
|
||||
uid_store
|
||||
.hash_index
|
||||
|
@ -272,6 +281,7 @@ impl MailBackend for ImapType {
|
|||
.capabilities
|
||||
.contains(&b"IDLE"[0..]);
|
||||
let folders = self.folders.clone();
|
||||
let tag_index = self.tag_index.clone();
|
||||
let conn = ImapConnection::new_connection(&self.server_conf);
|
||||
let main_conn = self.connection.clone();
|
||||
let is_online = self.online.clone();
|
||||
|
@ -292,6 +302,7 @@ impl MailBackend for ImapType {
|
|||
folders,
|
||||
sender,
|
||||
work_context,
|
||||
tag_index,
|
||||
};
|
||||
if has_idle {
|
||||
idle(kit).ok().take();
|
||||
|
@ -338,6 +349,7 @@ impl MailBackend for ImapType {
|
|||
.to_string(),
|
||||
self.connection.clone(),
|
||||
self.uid_store.clone(),
|
||||
self.tag_index.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ pub struct ImapOp {
|
|||
flags: Cell<Option<Flag>>,
|
||||
connection: Arc<Mutex<ImapConnection>>,
|
||||
uid_store: Arc<UIDStore>,
|
||||
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
||||
}
|
||||
|
||||
impl ImapOp {
|
||||
|
@ -46,6 +47,7 @@ impl ImapOp {
|
|||
folder_path: String,
|
||||
connection: Arc<Mutex<ImapConnection>>,
|
||||
uid_store: Arc<UIDStore>,
|
||||
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
||||
) -> Self {
|
||||
ImapOp {
|
||||
uid,
|
||||
|
@ -56,6 +58,7 @@ impl ImapOp {
|
|||
folder_path,
|
||||
flags: Cell::new(None),
|
||||
uid_store,
|
||||
tag_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +92,7 @@ impl BackendOp for ImapOp {
|
|||
.to_full_result()
|
||||
.map_err(MeliError::from)
|
||||
{
|
||||
Ok(v) => {
|
||||
Ok(mut v) => {
|
||||
if v.len() != 1 {
|
||||
debug!("responses len is {}", v.len());
|
||||
/* TODO: Trigger cache invalidation here. */
|
||||
|
@ -98,11 +101,11 @@ impl BackendOp for ImapOp {
|
|||
self.uid
|
||||
)));
|
||||
}
|
||||
let (uid, flags, b) = v[0];
|
||||
let (uid, flags, b) = v.remove(0);
|
||||
assert_eq!(uid, self.uid);
|
||||
if flags.is_some() {
|
||||
self.flags.set(flags);
|
||||
cache.flags = flags;
|
||||
if let Some((flags, _)) = flags {
|
||||
self.flags.set(Some(flags));
|
||||
cache.flags = Some(flags);
|
||||
}
|
||||
cache.bytes = Some(unsafe { std::str::from_utf8_unchecked(b).to_string() });
|
||||
}
|
||||
|
@ -147,7 +150,7 @@ impl BackendOp for ImapOp {
|
|||
/* TODO: Trigger cache invalidation here. */
|
||||
panic!(format!("message with UID {} was not found", self.uid));
|
||||
}
|
||||
let (uid, flags) = v[0];
|
||||
let (uid, (flags, _)) = v[0];
|
||||
assert_eq!(uid, self.uid);
|
||||
cache.flags = Some(flags);
|
||||
self.flags.set(Some(flags));
|
||||
|
@ -184,7 +187,7 @@ impl BackendOp for ImapOp {
|
|||
Ok(v) => {
|
||||
if v.len() == 1 {
|
||||
debug!("responses len is {}", v.len());
|
||||
let (uid, flags) = v[0];
|
||||
let (uid, (flags, _)) = v[0];
|
||||
assert_eq!(uid, self.uid);
|
||||
self.flags.set(Some(flags));
|
||||
}
|
||||
|
@ -196,4 +199,41 @@ impl BackendOp for ImapOp {
|
|||
cache.flags = Some(flags);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tag(&mut self, envelope: &mut Envelope, tag: String, value: bool) -> Result<()> {
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
let mut conn = self.connection.lock().unwrap();
|
||||
conn.send_command(format!("SELECT \"{}\"", &self.folder_path,).as_bytes())?;
|
||||
conn.read_response(&mut response)?;
|
||||
conn.send_command(
|
||||
format!(
|
||||
"UID STORE {} {}FLAGS.SILENT ({})",
|
||||
self.uid,
|
||||
if value { "+" } else { "-" },
|
||||
&tag
|
||||
)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
conn.read_response(&mut response)?;
|
||||
protocol_parser::uid_fetch_flags_response(response.as_bytes())
|
||||
.to_full_result()
|
||||
.map_err(MeliError::from)?;
|
||||
let hash = tag_hash!(tag);
|
||||
if value {
|
||||
self.tag_index.write().unwrap().insert(hash, tag);
|
||||
} else {
|
||||
self.tag_index.write().unwrap().remove(&hash);
|
||||
}
|
||||
if !envelope.labels().iter().any(|&h_| h_ == hash) {
|
||||
if value {
|
||||
envelope.labels_mut().push(hash);
|
||||
}
|
||||
}
|
||||
if !value {
|
||||
if let Some(pos) = envelope.labels().iter().position(|&h_| h_ == hash) {
|
||||
envelope.labels_mut().remove(pos);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ named!(
|
|||
* "* 1 FETCH (FLAGS (\Seen) UID 1 RFC822.HEADER {5224}
"
|
||||
*/
|
||||
named!(
|
||||
pub uid_fetch_response<Vec<(usize, Option<Flag>, &[u8])>>,
|
||||
pub uid_fetch_response<Vec<(usize, Option<(Flag, Vec<String>)>, &[u8])>>,
|
||||
many0!(
|
||||
do_parse!(
|
||||
tag!("* ")
|
||||
|
@ -124,7 +124,7 @@ named!(
|
|||
);
|
||||
|
||||
named!(
|
||||
pub uid_fetch_flags_response<Vec<(usize, Flag)>>,
|
||||
pub uid_fetch_flags_response<Vec<(usize, (Flag, Vec<String>))>>,
|
||||
many0!(
|
||||
do_parse!(
|
||||
tag!("* ")
|
||||
|
@ -297,11 +297,11 @@ named!(
|
|||
pub struct SelectResponse {
|
||||
pub exists: usize,
|
||||
pub recent: usize,
|
||||
pub flags: Flag,
|
||||
pub flags: (Flag, Vec<String>),
|
||||
pub unseen: usize,
|
||||
pub uidvalidity: usize,
|
||||
pub uidnext: usize,
|
||||
pub permanentflags: Flag,
|
||||
pub permanentflags: (Flag, Vec<String>),
|
||||
/// if SELECT returns \* we can set arbritary flags permanently.
|
||||
pub can_create_flags: bool,
|
||||
pub read_only: bool,
|
||||
|
@ -366,8 +366,9 @@ pub fn select_response(input: &str) -> Result<SelectResponse> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn flags(input: &str) -> IResult<&str, Flag> {
|
||||
pub fn flags(input: &str) -> IResult<&str, (Flag, Vec<String>)> {
|
||||
let mut ret = Flag::default();
|
||||
let mut keywords = Vec::new();
|
||||
|
||||
let mut input = input;
|
||||
while !input.starts_with(")") && !input.is_empty() {
|
||||
|
@ -399,16 +400,16 @@ pub fn flags(input: &str) -> IResult<&str, Flag> {
|
|||
ret.set(Flag::DRAFT, true);
|
||||
}
|
||||
f => {
|
||||
debug!("unknown Flag token value: {}", f);
|
||||
keywords.push(f.to_string());
|
||||
}
|
||||
}
|
||||
input = &input[match_end..];
|
||||
input = input.trim_start();
|
||||
}
|
||||
IResult::Done(input, ret)
|
||||
IResult::Done(input, (ret, keywords))
|
||||
}
|
||||
|
||||
pub fn byte_flags(input: &[u8]) -> IResult<&[u8], Flag> {
|
||||
pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec<String>)> {
|
||||
let i = unsafe { std::str::from_utf8_unchecked(input) };
|
||||
match flags(i) {
|
||||
IResult::Done(rest, ret) => IResult::Done(rest.as_bytes(), ret),
|
||||
|
@ -587,7 +588,7 @@ named!(
|
|||
alt_complete!(map!(ws!(tag!("NIL")), |_| None) | map!(quoted, |v| Some(v))));
|
||||
|
||||
named!(
|
||||
pub uid_fetch_envelopes_response<Vec<(usize, Option<Flag>, Envelope)>>,
|
||||
pub uid_fetch_envelopes_response<Vec<(usize, Option<(Flag, Vec<String>)>, Envelope)>>,
|
||||
many0!(
|
||||
do_parse!(
|
||||
tag!("* ")
|
||||
|
|
|
@ -30,6 +30,7 @@ pub struct ImapWatchKit {
|
|||
pub folders: Arc<RwLock<FnvHashMap<FolderHash, ImapFolder>>>,
|
||||
pub sender: RefreshEventConsumer,
|
||||
pub work_context: WorkContext,
|
||||
pub tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
||||
}
|
||||
|
||||
macro_rules! exit_on_error {
|
||||
|
@ -57,6 +58,7 @@ pub fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
|
|||
folders,
|
||||
sender,
|
||||
work_context,
|
||||
tag_index,
|
||||
} = kit;
|
||||
loop {
|
||||
if *is_online.lock().unwrap() {
|
||||
|
@ -81,7 +83,14 @@ pub fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
|
|||
format!("examining `{}` for updates...", folder.path()),
|
||||
))
|
||||
.unwrap();
|
||||
examine_updates(folder, &sender, &mut conn, &uid_store, &work_context)?;
|
||||
examine_updates(
|
||||
folder,
|
||||
&sender,
|
||||
&mut conn,
|
||||
&uid_store,
|
||||
&work_context,
|
||||
&tag_index,
|
||||
)?;
|
||||
}
|
||||
let mut main_conn = main_conn.lock().unwrap();
|
||||
main_conn.send_command(b"NOOP").unwrap();
|
||||
|
@ -101,6 +110,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
folders,
|
||||
sender,
|
||||
work_context,
|
||||
tag_index,
|
||||
} = kit;
|
||||
loop {
|
||||
if *is_online.lock().unwrap() {
|
||||
|
@ -236,7 +246,14 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
folder_hash,
|
||||
work_context,
|
||||
thread_id,
|
||||
examine_updates(folder, &sender, &mut conn, &uid_store, &work_context)
|
||||
examine_updates(
|
||||
folder,
|
||||
&sender,
|
||||
&mut conn,
|
||||
&uid_store,
|
||||
&work_context,
|
||||
&tag_index
|
||||
)
|
||||
);
|
||||
}
|
||||
work_context
|
||||
|
@ -301,7 +318,9 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
format!("parsing {}/{} envelopes..", ctr, len),
|
||||
))
|
||||
.unwrap();
|
||||
if let Ok(env) = Envelope::from_bytes(&b, flags) {
|
||||
if let Ok(mut env) =
|
||||
Envelope::from_bytes(&b, flags.as_ref().map(|&(f, _)| f))
|
||||
{
|
||||
ctr += 1;
|
||||
uid_store
|
||||
.hash_index
|
||||
|
@ -315,6 +334,16 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
env.subject(),
|
||||
folder.path(),
|
||||
);
|
||||
if let Some((_, keywords)) = flags {
|
||||
let mut tag_lck = tag_index.write().unwrap();
|
||||
for f in keywords {
|
||||
let hash = tag_hash!(f);
|
||||
if !tag_lck.contains_key(&hash) {
|
||||
tag_lck.insert(hash, f);
|
||||
}
|
||||
env.labels_mut().push(hash);
|
||||
}
|
||||
}
|
||||
sender.send(RefreshEvent {
|
||||
hash: folder_hash,
|
||||
kind: Create(Box::new(env)),
|
||||
|
@ -402,7 +431,9 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
ctr += 1;
|
||||
continue;
|
||||
}
|
||||
if let Ok(env) = Envelope::from_bytes(&b, flags) {
|
||||
if let Ok(mut env) =
|
||||
Envelope::from_bytes(&b, flags.as_ref().map(|&(f, _)| f))
|
||||
{
|
||||
ctr += 1;
|
||||
uid_store
|
||||
.hash_index
|
||||
|
@ -410,6 +441,16 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
.unwrap()
|
||||
.insert(env.hash(), (uid, folder_hash));
|
||||
uid_store.uid_index.lock().unwrap().insert(uid, env.hash());
|
||||
if let Some((_, keywords)) = flags {
|
||||
let mut tag_lck = tag_index.write().unwrap();
|
||||
for f in keywords {
|
||||
let hash = tag_hash!(f);
|
||||
if !tag_lck.contains_key(&hash) {
|
||||
tag_lck.insert(hash, f);
|
||||
}
|
||||
env.labels_mut().push(hash);
|
||||
}
|
||||
}
|
||||
debug!(
|
||||
"Create event {} {} {}",
|
||||
env.hash(),
|
||||
|
@ -467,6 +508,7 @@ fn examine_updates(
|
|||
conn: &mut ImapConnection,
|
||||
uid_store: &Arc<UIDStore>,
|
||||
work_context: &WorkContext,
|
||||
tag_index: &Arc<RwLock<BTreeMap<u64, String>>>,
|
||||
) -> Result<()> {
|
||||
let thread_id: std::thread::ThreadId = std::thread::current().id();
|
||||
let folder_hash = folder.hash();
|
||||
|
@ -551,7 +593,10 @@ fn examine_updates(
|
|||
{
|
||||
Ok(v) => {
|
||||
for (uid, flags, b) in v {
|
||||
if let Ok(env) = Envelope::from_bytes(&b, flags) {
|
||||
if let Ok(mut env) = Envelope::from_bytes(
|
||||
&b,
|
||||
flags.as_ref().map(|&(f, _)| f),
|
||||
) {
|
||||
uid_store
|
||||
.hash_index
|
||||
.lock()
|
||||
|
@ -568,6 +613,16 @@ fn examine_updates(
|
|||
env.subject(),
|
||||
folder.path(),
|
||||
);
|
||||
if let Some((_, keywords)) = flags {
|
||||
let mut tag_lck = tag_index.write().unwrap();
|
||||
for f in keywords {
|
||||
let hash = tag_hash!(f);
|
||||
if !tag_lck.contains_key(&hash) {
|
||||
tag_lck.insert(hash, f);
|
||||
}
|
||||
env.labels_mut().push(hash);
|
||||
}
|
||||
}
|
||||
sender.send(RefreshEvent {
|
||||
hash: folder_hash,
|
||||
kind: Create(Box::new(env)),
|
||||
|
@ -614,13 +669,25 @@ fn examine_updates(
|
|||
{
|
||||
Ok(v) => {
|
||||
for (uid, flags, b) in v {
|
||||
if let Ok(env) = Envelope::from_bytes(&b, flags) {
|
||||
if let Ok(mut env) =
|
||||
Envelope::from_bytes(&b, flags.as_ref().map(|&(f, _)| f))
|
||||
{
|
||||
uid_store
|
||||
.hash_index
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(env.hash(), (uid, folder_hash));
|
||||
uid_store.uid_index.lock().unwrap().insert(uid, env.hash());
|
||||
if let Some((_, keywords)) = flags {
|
||||
let mut tag_lck = tag_index.write().unwrap();
|
||||
for f in keywords {
|
||||
let hash = tag_hash!(f);
|
||||
if !tag_lck.contains_key(&hash) {
|
||||
tag_lck.insert(hash, f);
|
||||
}
|
||||
env.labels_mut().push(hash);
|
||||
}
|
||||
}
|
||||
debug!(
|
||||
"Create event {} {} {}",
|
||||
env.hash(),
|
||||
|
|
|
@ -162,6 +162,10 @@ impl<'a> BackendOp for MaildirOp {
|
|||
debug!("success in rename");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tag(&mut self, _envelope: &mut Envelope, _tag: String, _value: bool) -> Result<()> {
|
||||
Err(MeliError::new("Maildir doesn't support tags."))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
|
@ -241,6 +241,10 @@ impl BackendOp for MboxOp {
|
|||
fn set_flag(&mut self, _envelope: &mut Envelope, _flag: Flag, _value: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tag(&mut self, _envelope: &mut Envelope, _tag: String, _value: bool) -> Result<()> {
|
||||
Err(MeliError::new("mbox doesn't support tags."))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mbox_parse(
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::structs::StackVec;
|
|||
use fnv::FnvHashMap;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -313,6 +313,7 @@ impl MailBackend for NotmuchDb {
|
|||
hash: env_hash,
|
||||
index: index.clone(),
|
||||
bytes: Some(response),
|
||||
tag_index: tag_index.clone(),
|
||||
});
|
||||
if let Some(mut env) = Envelope::from_token(op, env_hash) {
|
||||
let mut tag_lock = tag_index.write().unwrap();
|
||||
|
@ -372,6 +373,7 @@ impl MailBackend for NotmuchDb {
|
|||
hash,
|
||||
index: self.index.clone(),
|
||||
bytes: None,
|
||||
tag_index: self.tag_index.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -410,6 +412,7 @@ impl MailBackend for NotmuchDb {
|
|||
struct NotmuchOp {
|
||||
hash: EnvelopeHash,
|
||||
index: Arc<RwLock<FnvHashMap<EnvelopeHash, &'static CStr>>>,
|
||||
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
||||
database: DbWrapper,
|
||||
bytes: Option<String>,
|
||||
}
|
||||
|
@ -547,6 +550,58 @@ impl BackendOp for NotmuchOp {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tag(&mut self, envelope: &mut Envelope, tag: String, value: bool) -> Result<()> {
|
||||
let mut message: *mut notmuch_message_t = std::ptr::null_mut();
|
||||
let index_lck = self.index.read().unwrap();
|
||||
unsafe {
|
||||
notmuch_database_find_message_by_filename(
|
||||
*self.database.inner.read().unwrap(),
|
||||
index_lck[&self.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[&self.hash]
|
||||
)));
|
||||
}
|
||||
if value {
|
||||
if unsafe {
|
||||
notmuch_message_add_tag(message, CString::new(tag.as_str()).unwrap().as_ptr())
|
||||
} != _notmuch_status_NOTMUCH_STATUS_SUCCESS
|
||||
{
|
||||
return Err(MeliError::new("Could not set tag."));
|
||||
}
|
||||
debug!("added tag {}", &tag);
|
||||
} else {
|
||||
if unsafe {
|
||||
notmuch_message_remove_tag(message, CString::new(tag.as_str()).unwrap().as_ptr())
|
||||
} != _notmuch_status_NOTMUCH_STATUS_SUCCESS
|
||||
{
|
||||
return Err(MeliError::new("Could not set tag."));
|
||||
}
|
||||
debug!("removed tag {}", &tag);
|
||||
}
|
||||
let hash = tag_hash!(tag);
|
||||
if value {
|
||||
self.tag_index.write().unwrap().insert(hash, tag);
|
||||
} else {
|
||||
self.tag_index.write().unwrap().remove(&hash);
|
||||
}
|
||||
if !envelope.labels().iter().any(|&h_| h_ == hash) {
|
||||
if value {
|
||||
envelope.labels_mut().push(hash);
|
||||
}
|
||||
}
|
||||
if !value {
|
||||
if let Some(pos) = envelope.labels().iter().position(|&h_| h_ == hash) {
|
||||
envelope.labels_mut().remove(pos);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MessageIterator {
|
||||
|
|
|
@ -154,7 +154,10 @@ pub mod query_parser {
|
|||
fn flags<'a>() -> impl Parser<'a, Query> {
|
||||
move |input| {
|
||||
whitespace_wrap(either(
|
||||
either(
|
||||
match_literal_anycase("flags:"),
|
||||
match_literal_anycase("tags:"),
|
||||
),
|
||||
match_literal_anycase("is:"),
|
||||
))
|
||||
.parse(input)
|
||||
|
@ -322,6 +325,10 @@ pub mod query_parser {
|
|||
Ok(("", Flags(vec!["test".to_string(), "testtest".to_string()]))),
|
||||
query().parse_complete("flags:test,testtest")
|
||||
);
|
||||
assert_eq!(
|
||||
query().parse_complete("flags:test,testtest"),
|
||||
query().parse_complete("tags:test,testtest")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -387,7 +394,7 @@ pub fn query_to_imap(q: &Query) -> String {
|
|||
s.push_str(" UNANSWERED ");
|
||||
}
|
||||
keyword => {
|
||||
s.push_str(" ");
|
||||
s.push_str(" KEYWORD ");
|
||||
s.extend(keyword.chars());
|
||||
s.push_str(" ");
|
||||
}
|
||||
|
|
|
@ -105,42 +105,27 @@ pub trait MailListingTrait: ListingTrait {
|
|||
continue;
|
||||
}
|
||||
ListingAction::Tag(Remove(ref tag_str)) => {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
let h = {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write(tag_str.as_bytes());
|
||||
hasher.finish()
|
||||
};
|
||||
let backend_lck = account.backend.write().unwrap();
|
||||
if let Some(t) = backend_lck.tags() {
|
||||
let mut tags_lck = t.write().unwrap();
|
||||
if !tags_lck.contains_key(&h) {
|
||||
tags_lck.insert(h, tag_str.to_string());
|
||||
}
|
||||
if let Some(pos) = envelope.labels().iter().position(|&el| el == h) {
|
||||
envelope.labels_mut().remove(pos);
|
||||
}
|
||||
} else {
|
||||
let mut op = backend_lck.operation(envelope.hash());
|
||||
if let Err(err) = op.set_tag(&mut envelope, tag_str.to_string(), false) {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
Some("Could not set tag.".to_string()),
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
ListingAction::Tag(Add(ref tag_str)) => {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
let h = {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hasher.write(tag_str.as_bytes());
|
||||
hasher.finish()
|
||||
};
|
||||
let backend_lck = account.backend.write().unwrap();
|
||||
if let Some(t) = backend_lck.tags() {
|
||||
let mut tags_lck = t.write().unwrap();
|
||||
if !tags_lck.contains_key(&h) {
|
||||
tags_lck.insert(h, tag_str.to_string());
|
||||
}
|
||||
envelope.labels_mut().push(h);
|
||||
} else {
|
||||
let mut op = backend_lck.operation(envelope.hash());
|
||||
|
||||
if let Err(err) = op.set_tag(&mut envelope, tag_str.to_string(), true) {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
Some("Could not set tag.".to_string()),
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1131,6 +1131,9 @@ impl Component for ConversationsListing {
|
|||
area,
|
||||
Some(get_x(upper_left)),
|
||||
);
|
||||
for c in grid.row_iter((x, get_x(bottom_right)), y) {
|
||||
grid[c] = Cell::default();
|
||||
}
|
||||
clear_area(grid, ((x, y), set_y(bottom_right, y)));
|
||||
context
|
||||
.dirty_areas
|
||||
|
|
|
@ -1576,9 +1576,6 @@ pub fn write_string_to_grid(
|
|||
if c == '\r' {
|
||||
continue;
|
||||
}
|
||||
grid[(x, y)].set_attrs(attrs);
|
||||
grid[(x, y)].set_fg(fg_color);
|
||||
grid[(x, y)].set_bg(bg_color);
|
||||
if c == '\t' {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
x += 1;
|
||||
|
@ -1587,6 +1584,9 @@ pub fn write_string_to_grid(
|
|||
} else {
|
||||
grid[(x, y)].set_ch(c);
|
||||
}
|
||||
grid[(x, y)].set_attrs(attrs);
|
||||
grid[(x, y)].set_fg(fg_color);
|
||||
grid[(x, y)].set_bg(bg_color);
|
||||
|
||||
match wcwidth(u32::from(c)) {
|
||||
Some(0) | None => {
|
||||
|
@ -1598,6 +1598,10 @@ pub fn write_string_to_grid(
|
|||
* drawn over. Set it as empty to skip drawing it. */
|
||||
x += 1;
|
||||
inspect_bounds!(grid, area, x, y, line_break);
|
||||
grid[(x, y)] = Cell::default();
|
||||
grid[(x, y)].set_attrs(attrs);
|
||||
grid[(x, y)].set_fg(fg_color);
|
||||
grid[(x, y)].set_bg(bg_color);
|
||||
grid[(x, y)].empty = true;
|
||||
}
|
||||
_ => {}
|
||||
|
|
Loading…
Reference in New Issue