use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use notmuch::Database;
pub mod error;
use crate::error::Error::*;
use crate::error::Result;
mod filter;
pub use crate::filter::*;
mod operations;
pub use crate::operations::*;
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(untagged)]
pub enum Value {
Single(String),
Multiple(Vec<String>),
Bool(bool),
}
pub struct FilterOptions {
pub leave_tag: bool,
pub sync_tags: bool,
}
fn validate_query_tag(tag: &str) -> Result<String> {
if tag.is_empty() {
let e = "Tag to query can't be empty".to_string();
return Err(UnsupportedQuery(e));
};
if tag.contains(' ') || tag.contains('"') || tag.contains('\'') {
let e = "Query tags can't contain whitespace or quotes".to_string();
Err(UnsupportedQuery(e))
} else {
Ok(format!("tag:{}", tag))
}
}
pub fn filter(
db: &Database,
query_tag: &str,
options: &FilterOptions,
filters: &[Filter],
) -> Result<usize> {
let query = validate_query_tag(query_tag)?;
let q = db.create_query(&query)?;
let mut matches = 0;
for msg in q.search_messages()? {
let mut exists = true;
for filter in filters {
let (applied, deleted) = filter.apply_if_match(&msg, db)?;
if applied {
matches += 1;
}
if deleted {
exists = !deleted;
break;
}
}
if exists {
if !options.leave_tag {
msg.remove_tag(query_tag)?;
}
if options.sync_tags {
msg.tags_to_maildir_flags()?;
}
}
}
Ok(matches)
}
pub fn filter_dry(
db: &Database,
query_tag: &str,
filters: &[Filter],
) -> Result<(usize, Vec<String>)> {
let query = validate_query_tag(query_tag)?;
let q = db.create_query(&query)?;
let mut matches = 0;
let mut mtchinf = Vec::<String>::new();
for msg in q.search_messages()? {
let mut msg_matches = 0;
match filters
.iter()
.map(|f| {
let is_match = f.is_match(&msg, db)?;
if is_match {
msg_matches += 1;
mtchinf.push(format!("{}: {}", msg.id(), f.name()));
}
Ok(())
})
.collect::<Result<Vec<()>>>()
{
Ok(_) => matches += msg_matches,
Err(e) => return Err(e),
};
}
Ok((matches, mtchinf))
}
pub fn filters_from(buf: &[u8]) -> Result<Vec<Filter>> {
serde_json::from_slice::<Vec<Filter>>(buf)?
.into_iter()
.map(|f| f.compile())
.collect()
}
pub fn filters_from_file<P>(filename: &P) -> Result<Vec<Filter>>
where
P: AsRef<Path>,
{
let mut buf = Vec::new();
let mut file = File::open(filename)?;
file.read_to_end(&mut buf)?;
filters_from(&buf)
}