glop 0.2.5

Glue Language for OPerations
Documentation
extern crate textnonce;

use std;
use std::collections::HashSet;
use std::fs::OpenOptions;
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
use std::sync::{Arc, Mutex};

use super::*;
use self::context::Context;
use self::value::Value;

pub struct Transaction {
    pub m: Match,
    pub seq: i32,
    pub ctx: Arc<Mutex<Context>>,
    pub applied: Vec<Action>,
    matched_topics: HashSet<String>,
}

impl Transaction {
    pub fn new(m: Match, seq: i32, ctx: Context) -> Transaction {
        Transaction {
            m: m,
            seq: seq,
            ctx: Arc::new(Mutex::new(ctx)),
            applied: vec![],
            matched_topics: HashSet::new(),
        }
    }

    pub fn apply(&mut self) -> Result<Vec<Action>> {
        self.matched_topics = self.m.topics();
        let mut actions = self.m.actions.clone();
        let mut applied: Vec<Action> = vec![];
        loop {
            if actions.is_empty() {
                return Ok(applied);
            }
            let action = actions.remove(0);
            let mut resulting_actions = match action {
                Action::SetVar(ref k, ref v) => {
                    let mut ctx = self.ctx.lock().unwrap();
                    ctx.set_var(k, Value::Str(v.to_string()));
                    vec![action.clone()]
                }
                Action::UnsetVar(ref k) => {
                    let mut ctx = self.ctx.lock().unwrap();
                    ctx.unset_var(k);
                    vec![action.clone()]
                }
                Action::Script(ref contents) => self.exec_script(contents)?,
                Action::SendMsg {
                    dst_remote: _,
                    dst_agent: _,
                    topic: _,
                    in_reply_to: _,
                    contents: _,
                } => vec![action],
                Action::ReplyTo {
                    ref topic,
                    ref contents,
                } => {
                    let ctx = self.ctx.lock().unwrap();
                    match ctx.msgs.get(topic) {
                        Some(ref subject) => {
                            vec![Action::SendMsg {
                                     dst_agent: subject.src_agent.to_string(),
                                     dst_remote: subject.src_remote.clone(),
                                     topic: topic.to_string(),
                                     in_reply_to: subject.in_reply_to.clone(),
                                     contents: contents.clone(),
                                 }]
                        }
                        None => return Err(Error::UndeliverableMessage(topic.to_string())),
                    }
                }
                Action::Match(ref m) => {
                    if self.eval_match(m) {
                        self.matched_topics.extend(m.topics());
                        actions.append(&mut m.actions.clone())
                    }
                    continue;
                }
            };
            applied.append(&mut resulting_actions);
        }
    }

    pub fn matched_topics(&self) -> HashSet<String> {
        self.matched_topics.clone()
    }

    pub fn eval(&self) -> bool {
        self.eval_match(&self.m)
    }

    fn eval_match(&self, m: &Match) -> bool {
        m.conditions
            .iter()
            .fold(true, |acc, c| acc && self.eval_condition(c))
    }

    fn eval_condition(&self, cond: &Condition) -> bool {
        let ctx = self.ctx.lock().unwrap();
        match cond {
            &Condition::Cmp(ref l, ref op, ref r) => {
                match l.get(&ctx.vars) {
                    Some(v) => op.eval(v, r),
                    None => false,
                }
            }
            &Condition::IsSet(ref k) => k.is_set(&ctx.vars),
            &Condition::IsUnset(ref k) => !k.is_set(&ctx.vars),
            &Condition::Message {
                 ref topic,
                 ref src_role,
                 acting_role: _,
             } => {
                if let Some(msg) = ctx.msgs.get(topic) {
                    src_role.eq(&msg.src_role)
                } else {
                    false
                }
            }
        }
    }

    pub fn with_context<F, T>(&mut self, f: F) -> T
        where F: Fn(&mut Context) -> T
    {
        let mut ctx = self.ctx.lock().unwrap();
        f(&mut ctx)
    }

    fn exec_script(&mut self, contents: &str) -> Result<Vec<Action>> {
        let mut script_path_buf = std::env::temp_dir();
        let script_path_base = textnonce::TextNonce::sized_urlsafe(32)
            .unwrap()
            .into_string();
        script_path_buf.push(&script_path_base);
        let script_path = script_path_buf.to_str().unwrap();
        let cleanup = cleanup::Cleanup::File(script_path.to_string());
        {
            let mut script_file = OpenOptions::new()
                .write(true)
                .mode(0o700)
                .create_new(true)
                .open(script_path)
                .map_err(error::Error::IO)?;
            script_file
                .write_all(contents.as_bytes())
                .map_err(error::Error::IO)?;
        }

        let actions = script::run_script(self.ctx.clone(), script_path)?;
        drop(cleanup);
        Ok(actions)
    }
}