1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
use std::fs::remove_file;
use std::process::{Command, Stdio};
use serde::{Deserialize, Serialize};
use crate::error::Error::*;
use crate::error::*;
use crate::Value;
use crate::Value::*;
use notmuch::{Database, Message, MessageOwner};
/// Operations filters can apply.
///
/// Just a way to store operations, implementation may be found in
/// [`Operations::apply`].
///
/// [`Operations::apply`]: struct.Operations.html#method.apply
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct Operations {
/// Remove tags
pub rm: Option<Value>,
/// Add tags
pub add: Option<Value>,
/// Run arbitrary commands
pub run: Option<Vec<String>>,
/// Delete from disk and notmuch database
pub del: Option<bool>,
}
impl Operations {
/// Apply the operations defined in [`Filter::op`] to the supplied message
/// regardless if matches this filter or not
///
/// Operations can fail, but if not they let you know if the message's file
/// was deleted and dropped from the database.
///
/// If operations have both `run` and `del` defined, the command is run
/// before the message is deleted.
///
/// [`Filter::op`]: struct.Filter.html#structfield.op
pub fn apply<T>(
&self,
msg: &Message<'_, T>,
db: &Database,
name: &str,
) -> Result<bool>
where
T: MessageOwner,
{
if let Some(rm) = &self.rm {
match rm {
Single(tag) => {
msg.remove_tag(tag)?;
}
Multiple(tags) => {
for tag in tags {
msg.remove_tag(tag)?;
}
}
Bool(all) => {
if *all {
msg.remove_all_tags()?;
}
}
}
}
if let Some(add) = &self.add {
match add {
Single(tag) => {
msg.add_tag(tag)?;
}
Multiple(tags) => {
for tag in tags {
msg.add_tag(tag)?;
}
}
Bool(_) => {
return Err(UnsupportedValue(
"'add' operation doesn't support bool types"
.to_string(),
));
}
}
}
if let Some(argv) = &self.run {
Command::new(&argv[0])
.args(&argv[1..])
.stdout(Stdio::inherit())
.env("NOTCOAL_FILE_NAME", &msg.filename())
.env("NOTCOAL_MSG_ID", &msg.id())
.env("NOTCOAL_FILTER_NAME", name)
.spawn()?;
}
if let Some(del) = &self.del {
if *del {
// This file was just indexed, so we assume it exists - or do
// we? See XXX-file in filter.rs
remove_file(&msg.filename())?;
db.remove_message(&msg.filename())?;
return Ok(true);
}
}
Ok(false)
}
}