<commit_before>//! Generate the site content.

// Standard library
use std::borrow::Cow::{Borrowed, Owned};
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

// Third party
use glob::{glob, Paths};
use pulldown_cmark as cmark;
use pulldown_cmark::{Event, Parser, Tag};
use syntect::highlighting::ThemeSet;

// First party
use crate::config::{Config, SyntaxOption};

/// Load the `Paths` for all markdown files in the specified content directory.
///
/// If the specified configuration values are wrong, we return a vector of all
/// the errors in the config as far as this is concerned.
///
/// TODO: move that validation elsewhere? Should probably extract a more general
/// site configuration validator.
fn load_markdown_paths(site_directory: &PathBuf, config: &Config) -> Result<Vec<PathBuf>, String> {
    let site = site_directory
        .to_str()
        .ok_or(format!("bad `site`: {:?}", site_directory));

    let directories = config.directories.content.to_str().ok_or(format!(
        "bad content directory: {:?}",
        config.directories.content
    ));

    let (site, directories) = match (site, directories) {
        (Err(s), Err(d)) => Err(s + "\n" + &d),
        (Err(s), Ok(_)) => Err(s),
        (Ok(_), Err(d)) => Err(d),
        (Ok(s), Ok(d)) => Ok((s, d)),
    }?;

    let content_glob = format!("{}/{}/**/*.md", site, directories);

    let paths = glob(&content_glob).map_err(|err| format!("{:?}", err))?;

    let (valid, errors): (Vec<Result<PathBuf, _>>, Vec<Result<_, String>>) = paths
        .map(|glob_result| glob_result.map_err(|err| format!("{:?}", err)))
        .partition(|path| path.is_ok());

    let errors: Vec<String> = errors.into_iter().map(|e| e.unwrap_err()).collect();
    if errors.len() == 0 {
        Ok(valid.into_iter().map(|v| v.unwrap()).collect())
    } else {
        Err(errors.join(",\n"))
    }
}

type LoadTuple<'a> = (&'a PathBuf, Result<String, String>);

fn load_content<'p>(paths: &'p [PathBuf]) -> Result<Vec<(&'p PathBuf, String)>, String> {
    let (contents, errs): (Vec<LoadTuple>, Vec<LoadTuple>) = paths
        .into_iter()
        .map(|path| {
            (
                path,
                std::fs::read_to_string(path).map_err(|e| format!("{:?}", e)),
            )
        })
        .partition(|(_, result)| result.is_ok());

    if errs.len() == 0 {
        Ok(contents
            .into_iter()
            .map(|(path, contents)| (path, contents.unwrap()))
            .collect())
    } else {
        Err(errs
            .into_iter()
            .map(|(_path, err)| err.unwrap_err())
            .collect::<Vec<String>>()
            .join(",\n"))
    }
}

/// Load the templates associated with each taxonomy.
fn load_templates(_site_directory: &PathBuf, _config: &Config) -> Result<Paths, String> {
    unimplemented!()
}

// TODO: put this somewhere else when extracting all the parser logic below into
// its own module.
#[derive(Debug)]
enum ParseState<'s> {
    CodeBlock(&'s syntect::parsing::SyntaxReference),
    PlainTextBlock,
    NonCode,
}

impl<'s> std::default::Default for ParseState<'s> {
    fn default() -> ParseState<'s> {
        ParseState::NonCode
    }
}

/// Generate content from a configuration.
pub fn build(site_directory: PathBuf) -> Result<(), String> {
    // NOTE: this is almost certainly not what we *ultimately* want here, but
    // it'll do as a starting point. There's a lot of `into_iter` and `collect`
    // happening here, which forces us to eagerly materialize a lot of these
    // collections rather than being able to deal with them in a more
    // "streaming" fashion.
    let config = Config::from_file(&PathBuf::from(&site_directory))?;
    let markdown_paths = load_markdown_paths(&site_directory, &config)?;
    let contents = load_content(&markdown_paths)?;

    // TODO: migrate this logic out elsewhere
    // TODO: when doing ^ take into account that we want to be able to avoid a
    // bunch of extra layers of indirection here. If we don't have a theme, that
    // translates to being in `SyntaxOption::Off` or `SyntaxOption::TagOnly`,
    // since the case where we can't *load* the theme is an error. What I want,
    // therefore, is to reflect that in the parsing behavior later.
    let theme = match &config.options.syntax {
        SyntaxOption::Highlight(theme) => {
            let theme_file = PathBuf::from("data").join(theme);
            let theme = ThemeSet::get_theme(theme_file).map_err(|err| format!("{:?}", err))?;
            Some(theme)
        }
        SyntaxOption::Off | SyntaxOption::TagOnly => None,
    };

    // TODO: generate these! Best move: on build, generate a `.packdump`, since
    // that seems to be what Syntect knows how to deal with.
    let syntax_dir = PathBuf::from("data/syntaxes");
    let syntax_set =
        syntect::parsing::SyntaxSet::load_from_folder(syntax_dir).expect("can't load syntaxes");

    let parsed_content: Vec<(&PathBuf, String)> = contents
        .into_iter()
        .map(|(path, content)| {
            let parser = Parser::new_ext(&content, pulldown_cmark::Options::all()).scan(
                ParseState::default(),
                |state, event| match event {
                    Event::Start(Tag::CodeBlock(ref language)) => match state {
                        ParseState::NonCode => {
                            *state = match syntax_set.find_syntax_by_name(&language) {
                                Some(syntax) => ParseState::CodeBlock(syntax),
                                None => ParseState::PlainTextBlock,
                            };

                            // We start every code block with `<pre><code class="...">`
                            // so that we always have semantically correct HTML. However, we
                            // don't add the language class if the language isn't set.
                            let emit_event = if language.len() > 0 {
                                Event::Html(Owned(format!(r#"
"#, language)))
                            } else {
                                Event::Html(Borrowed("
"))
                            };

                            Some(emit_event)
                        }
                            // TODO: don't panic.
                            panic!("Bad event/state: {:?} with {:?}", event, state);
                        ParseState::CodeBlock(_) | ParseState::PlainTextBlock => {
                        }
                    },

                    Event::End(Tag::CodeBlock(_)) => match state {
                        ParseState::CodeBlock(_) | ParseState::PlainTextBlock => {
                            *state = ParseState::NonCode;
                            Some(Event::Html(Borrowed("
")))
                        }
                            // TODO: don't panic.
                            panic!("Bad event/state: {:?} with {:?}", event, state);
                        ParseState::NonCode => {
                        }
                    },

                    Event::Text(s) => {
                        match state {
                            ParseState::CodeBlock(syntax) => match &theme {
                                Some(theme) => {
                                    let highlighted = syntect::html::highlighted_html_for_string(
                                        &s,
                                        &syntax_set,
                                        &syntax,
                                        theme,
                                    );

                                    Some(Event::Text(Owned(highlighted)))
                                }
                                None => {
                                    // TODO: implement tag-only parsing with syntect
                                    unimplemented!()
                                }
                            },
                            ParseState::PlainTextBlock => Some(Event::Text(s)),
                            ParseState::NonCode => Some(Event::Text(s)),
                        }
                    }
                    Event::Start(s) => Some(Event::Start(s)),
                    Event::End(e) => Some(Event::End(e)),
                    Event::InlineHtml(s) => Some(Event::InlineHtml(s)),
                    Event::Html(h) => Some(Event::Html(h)),
                    Event::SoftBreak => Some(Event::SoftBreak),
                    Event::HardBreak => Some(Event::HardBreak),
                    Event::FootnoteReference(s) => Some(Event::FootnoteReference(s)),
                },
            );

            // These numbers are derived from a brief survey of content in my
            // own website historically, including both code-heavy and
            // prose-heavy posts. It's probably worth exploring other corpora
            // to see if this is basically right.
            let length_bound_estimate = match &config.options.syntax {
                SyntaxOption::Off | SyntaxOption::TagOnly => 2,
                SyntaxOption::Highlight(_) => 8, // TODO: check other pages
            };

            let mut html = String::with_capacity(content.len() * length_bound_estimate);
            cmark::html::push_html(&mut html, parser);

            (path, html)
        })
        .collect();

    // TODO: extract this as part of the writing it out process.
    let output_dir = site_directory.join(&config.directories.output);

    if output_dir.exists() && !output_dir.is_dir() {
        // TODO: don't panic!
        panic!("{:?} already exists!", output_dir);
    }

    if !output_dir.exists() {
        std::fs::create_dir(&output_dir).expect(&format!(
            "could not create output directory {:?}",
            &output_dir
        ));
    }

    for (path, content) in parsed_content {
        let file_path = path
            .file_name()
            .ok_or(format!("invalid file: {:?}", path))?;

        let dest = output_dir.join(file_path).with_extension("html");

        let mut fd = match File::create(&dest) {
            Ok(file) => file,
            Err(reason) => {
                return Err(format!(
                    "Could not open {} for write: {}",
                    dest.to_string_lossy(),
                    reason
                ));
            }
        };

        let result = write!(fd, "{}", content);
        if let Err(reason) = result {
            return Err(format!("{:?}", reason.kind()));
        }
    }

    Ok(())
}
<commit_msg>fix: error handling in parsing and printing files<commit_after>//! Generate the site content.

// Standard library
use std::borrow::Cow::{Borrowed, Owned};
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

// Third party
use glob::{glob, Paths};
use pulldown_cmark as cmark;
use pulldown_cmark::{Event, Parser, Tag};
use syntect::highlighting::ThemeSet;

// First party
use crate::config::{Config, SyntaxOption};

/// Load the `Paths` for all markdown files in the specified content directory.
///
/// If the specified configuration values are wrong, we return a vector of all
/// the errors in the config as far as this is concerned.
///
/// TODO: move that validation elsewhere? Should probably extract a more general
/// site configuration validator.
fn load_markdown_paths(site_directory: &PathBuf, config: &Config) -> Result<Vec<PathBuf>, String> {
    let site = site_directory
        .to_str()
        .ok_or(format!("bad `site`: {:?}", site_directory));

    let directories = config.directories.content.to_str().ok_or(format!(
        "bad content directory: {:?}",
        config.directories.content
    ));

    let (site, directories) = match (site, directories) {
        (Err(s), Err(d)) => Err(s + "\n" + &d),
        (Err(s), Ok(_)) => Err(s),
        (Ok(_), Err(d)) => Err(d),
        (Ok(s), Ok(d)) => Ok((s, d)),
    }?;

    let content_glob = format!("{}/{}/**/*.md", site, directories);

    let paths = glob(&content_glob).map_err(|err| format!("{:?}", err))?;

    let (valid, errors): (Vec<Result<PathBuf, _>>, Vec<Result<_, String>>) = paths
        .map(|glob_result| glob_result.map_err(|err| format!("{:?}", err)))
        .partition(|path| path.is_ok());

    let errors: Vec<String> = errors.into_iter().map(|e| e.unwrap_err()).collect();
    if errors.len() == 0 {
        Ok(valid.into_iter().map(|v| v.unwrap()).collect())
    } else {
        Err(errors.join(",\n"))
    }
}

type LoadTuple<'a> = (&'a PathBuf, Result<String, String>);

fn load_content<'p>(paths: &'p [PathBuf]) -> Result<Vec<(&'p PathBuf, String)>, String> {
    let (contents, errs): (Vec<LoadTuple>, Vec<LoadTuple>) = paths
        .into_iter()
        .map(|path| {
            (
                path,
                std::fs::read_to_string(path).map_err(|e| format!("{:?}", e)),
            )
        })
        .partition(|(_, result)| result.is_ok());

    if errs.len() == 0 {
        Ok(contents
            .into_iter()
            .map(|(path, contents)| (path, contents.unwrap()))
            .collect())
    } else {
        Err(errs
            .into_iter()
            .map(|(_path, err)| err.unwrap_err())
            .collect::<Vec<String>>()
            .join(",\n"))
    }
}

/// Load the templates associated with each taxonomy.
fn load_templates(_site_directory: &PathBuf, _config: &Config) -> Result<Paths, String> {
    unimplemented!()
}

// TODO: put this somewhere else when extracting all the parser logic below into
// its own module.
#[derive(Debug)]
enum ParseState<'s> {
    CodeBlock(&'s syntect::parsing::SyntaxReference),
    PlainTextBlock,
    NonCode,
}

impl<'s> std::default::Default for ParseState<'s> {
    fn default() -> ParseState<'s> {
        ParseState::NonCode
    }
}

/// Generate content from a configuration.
pub fn build(site_directory: PathBuf) -> Result<(), String> {
    // NOTE: this is almost certainly not what we *ultimately* want here, but
    // it'll do as a starting point. There's a lot of `into_iter` and `collect`
    // happening here, which forces us to eagerly materialize a lot of these
    // collections rather than being able to deal with them in a more
    // "streaming" fashion.
    let config = Config::from_file(&PathBuf::from(&site_directory))?;
    let markdown_paths = load_markdown_paths(&site_directory, &config)?;
    let contents = load_content(&markdown_paths)?;

    // TODO: migrate this logic out elsewhere
    // TODO: when doing ^ take into account that we want to be able to avoid a
    // bunch of extra layers of indirection here. If we don't have a theme, that
    // translates to being in `SyntaxOption::Off` or `SyntaxOption::TagOnly`,
    // since the case where we can't *load* the theme is an error. What I want,
    // therefore, is to reflect that in the parsing behavior later.
    let theme = match &config.options.syntax {
        SyntaxOption::Highlight(theme) => {
            let theme_file = PathBuf::from("data").join(theme);
            let theme = ThemeSet::get_theme(theme_file).map_err(|err| format!("{:?}", err))?;
            Some(theme)
        }
        SyntaxOption::Off | SyntaxOption::TagOnly => None,
    };

    // TODO: generate these! Best move: on build, generate a `.packdump`, since
    // that seems to be what Syntect knows how to deal with.
    let syntax_dir = PathBuf::from("data/syntaxes");
    let syntax_set =
        syntect::parsing::SyntaxSet::load_from_folder(syntax_dir).expect("can't load syntaxes");

    let parsed_content: Vec<(&PathBuf, String)> = contents
        .into_iter()
        .map(|(path, content)| {
            let parser = Parser::new_ext(&content, pulldown_cmark::Options::all()).scan(
                ParseState::default(),
                |state, event| match event {
                    Event::Start(Tag::CodeBlock(ref language)) => match state {
                        ParseState::NonCode => {
                            *state = match syntax_set.find_syntax_by_name(&language) {
                                Some(syntax) => ParseState::CodeBlock(syntax),
                                None => ParseState::PlainTextBlock,
                            };

                            // We start every code block with `<pre><code class="...">`
                            // so that we always have semantically correct HTML. However, we
                            // don't add the language class if the language isn't set.
                            let emit_event = if language.len() > 0 {
                                Event::Html(Owned(format!(r#"
"#, language)))
                            } else {
                                Event::Html(Borrowed("
"))
                            };

                            Some(emit_event)
                        }
                        ParseState::CodeBlock(_) | ParseState::PlainTextBlock => {
                            unreachable!("Bad event/state: {:?} with {:?}", event, state);
                        }
                    },

                    Event::End(Tag::CodeBlock(_)) => match state {
                        ParseState::CodeBlock(_) | ParseState::PlainTextBlock => {
                            *state = ParseState::NonCode;
                            Some(Event::Html(Borrowed("
")))
                        }
                        ParseState::NonCode => {
                            unreachable!("Bad event/state: {:?} with {:?}", event, state);
                        }
                    },

                    Event::Text(s) => {
                        match state {
                            ParseState::CodeBlock(syntax) => match &theme {
                                Some(theme) => {
                                    let highlighted = syntect::html::highlighted_html_for_string(
                                        &s,
                                        &syntax_set,
                                        &syntax,
                                        theme,
                                    );

                                    Some(Event::Text(Owned(highlighted)))
                                }
                                None => {
                                    // TODO: implement tag-only parsing with syntect
                                    unimplemented!()
                                }
                            },
                            ParseState::PlainTextBlock => Some(Event::Text(s)),
                            ParseState::NonCode => Some(Event::Text(s)),
                        }
                    }
                    Event::Start(s) => Some(Event::Start(s)),
                    Event::End(e) => Some(Event::End(e)),
                    Event::InlineHtml(s) => Some(Event::InlineHtml(s)),
                    Event::Html(h) => Some(Event::Html(h)),
                    Event::SoftBreak => Some(Event::SoftBreak),
                    Event::HardBreak => Some(Event::HardBreak),
                    Event::FootnoteReference(s) => Some(Event::FootnoteReference(s)),
                },
            );

            // These numbers are derived from a brief survey of content in my
            // own website historically, including both code-heavy and
            // prose-heavy posts. It's probably worth exploring other corpora
            // to see if this is basically right.
            let length_bound_estimate = match &config.options.syntax {
                SyntaxOption::Off | SyntaxOption::TagOnly => 2,
                SyntaxOption::Highlight(_) => 8, // TODO: check other pages
            };

            let mut html = String::with_capacity(content.len() * length_bound_estimate);
            cmark::html::push_html(&mut html, parser);

            (path, html)
        })
        .collect();

    // TODO: extract this as part of the writing it out process.
    let output_dir = site_directory.join(&config.directories.output);

    if output_dir.exists() && !output_dir.is_dir() {
        return Err(format!("Output directory {:?} already exists!", output_dir));
    }

    if !output_dir.exists() {
        std::fs::create_dir(&output_dir).expect(&format!(
            "could not create output directory {:?}",
            &output_dir
        ));
    }

    for (path, content) in parsed_content {
        let file_path = path
            .file_name()
            .ok_or(format!("invalid file: {:?}", path))?;

        let dest = output_dir.join(file_path).with_extension("html");

        let mut fd = match File::create(&dest) {
            Ok(file) => file,
            Err(reason) => {
                return Err(format!(
                    "Could not open {} for write: {}",
                    dest.to_string_lossy(),
                    reason
                ));
            }
        };

        let result = write!(fd, "{}", content);
        if let Err(reason) = result {
            return Err(format!("{:?}", reason.kind()));
        }
    }

    Ok(())
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use common::{Result, Id, Op2, Const};
use explicit;
use implicit;
use env;
use hindley_milner;

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Imm {
    Bool(bool),
    Int(i64),
    Var(Id),
    //    Fun(Id, Box<Expr>),
    //    Fix(Id, Box<Expr>),
    //    Star,
    //    V,
}

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Op {
    Op2(Op2, Box<Imm>, Box<Imm>),
    MkArray(Box<Imm>, Box<Imm>),
    GetArray(Box<Imm>, Box<Imm>),
    SetArray(Box<Imm>, Box<Imm>, Box<Imm>),
    Imm(Imm),
}
//    WellFormed(Imm), // Var-only

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Expr {
    If(Box<Imm>, Box<Expr>, Box<Expr>),
    //    App(Box<Imm>, Box<Imm>),
    Let(Id, Box<Expr>, Box<Expr>),
    Op(Op),
}

#[derive(PartialEq, Eq, Clone, Debug)]
struct ConvEnv {
    //env: HashMap<Id, explicit::Type>,
    next_id: i32,
}

impl ConvEnv {
    fn new() -> ConvEnv {
        ConvEnv {
            //env: HashMap::new(),
            next_id: 0,
        }
    }

    fn tmp(&self) -> (ConvEnv, Id) {
        let id = self.next_id;
        let c = ConvEnv{
            //env: self.env.clone(),
            next_id: self.next_id+1,
        };

        (c, format!("!tmp!{}", id))
    }
}

fn constant(c: &Const) -> Imm {
    match *c {
        Const::Bool(b) => Imm::Bool(b),
        Const::Int(i) => Imm::Int(i),
    }
}

fn identity(cenv: ConvEnv, i: Imm) -> (ConvEnv, Expr) {
    (cenv, Expr::Op(Op::Imm(i)))
}

// 1:1 translation -- can't fail
fn expr(cenv: ConvEnv, e: &explicit::Expr, k: &Fn(ConvEnv, Imm) -> (ConvEnv, Expr)) -> (ConvEnv, Expr) {

    use self::Imm as I;
    use typed::Expr as E;
    use self::Op::*;
    use self::Expr::*;

    match *e {
        E::Const(ref c) => k(cenv, constant(c)),
        E::Op2(op, ref l, ref r) => {
            expr(cenv, l, &|cenv: ConvEnv, ll: I| -> (ConvEnv, Expr) {
                expr(cenv, r, &|cenv: ConvEnv, rr: I| -> (ConvEnv, Expr) {

                    let (cenv, op_tmp) = cenv.tmp();
                    let op_val = Op(Op2(op, box ll.clone(), box rr));
                    // value to pass to the continuation
                    let op_ref = I::Var(op_tmp.clone());

                    // our inner expression is whatever the
                    // continuation says it is.
                    let (cenv, result) = k(cenv, op_ref);

                    (cenv, Let(op_tmp, box op_val, box result))
                })
            })
        }
        E::Let(ref id, ref e1, ref e2) => {
            expr(cenv, e1, &|cenv: ConvEnv, e1l: I| -> (ConvEnv, Expr) {
                let (cenv, inner) = expr(cenv, e2, k);
                (cenv, Let(id.clone(), box Op(Imm(e1l.clone())), box inner))
            })
        }
        E::Var(ref x) => {
            k(cenv, I::Var(x.clone()))
        }
        E::If(ref e1, ref e2, ref e3) => {
            expr(cenv, e1, &|cenv: ConvEnv, cond_ref: I| -> (ConvEnv, Expr) {

                let (cenv, l_true) = expr(cenv, e2, &identity);
                let (cenv, l_false) = expr(cenv, e3, &identity);

                // value to pass to the continuation
                let val = If(box cond_ref, box l_true, box l_false);
                let (cenv, val_tmp) = cenv.tmp();

                // our inner expression is whatever the
                // continuation says it is.
                let (cenv, mut result) = k(cenv, I::Var(val_tmp.clone()));

                result = Let(val_tmp, box val, box result);

                (cenv, result)
            })
        }
        E::MkArray(ref sz, ref val) => {
            println!("O mkarray");
            expr(cenv, sz, &|cenv: ConvEnv, isz: I| -> (ConvEnv, Expr) {
                expr(cenv, val, &|cenv: ConvEnv, ival: I| -> (ConvEnv, Expr) {
                    println!("I mkarray");
                    let val = Op(MkArray(box isz.clone(), box ival));
                    let (cenv, val_ref) = cenv.tmp();
                    let (cenv, result) = k(cenv, I::Var(val_ref.clone()));
                    (cenv, Let(val_ref, box val, box result))
                })
            })
        }
        E::GetArray(ref id, ref idx) => {
            println!("O getarray");
            expr(cenv, id, &|cenv: ConvEnv, iid: I| -> (ConvEnv, Expr) {
                expr(cenv, idx, &|cenv: ConvEnv, iidx: I| -> (ConvEnv, Expr) {
                    println!("I getarray");
                    let val = Op(GetArray(box iid.clone(), box iidx));
                    let (cenv, val_ref) = cenv.tmp();
                    let (cenv, result) = k(cenv, I::Var(val_ref.clone()));
                    (cenv, Let(val_ref, box val, box result))
                })
            })
        }
        E::SetArray(ref id, ref idx, ref v) => {
            expr(cenv, id, &|cenv: ConvEnv, iid: I| -> (ConvEnv, Expr) {
                expr(cenv, idx, &|cenv: ConvEnv, iidx: I| -> (ConvEnv, Expr) {
                    expr(cenv, v, &|cenv: ConvEnv, iv: I| -> (ConvEnv, Expr) {
                        let val = Op(SetArray(box iid.clone(), box iidx.clone(), box iv));
                        let (cenv, val_ref) = cenv.tmp();
                        let (cenv, result) = k(cenv, I::Var(val_ref.clone()));
                        (cenv, Let(val_ref, box val, box result))
                    })
                })
            })
        }
        _ => {
            panic!("TODO: implement expr for {:?}", e);
        }
    }
    // E::Fun(ref id, ref t1, ref e) => {
    //     let mut renamed = renamed.clone();
    //     let (n, αid) = (n+1, format!("{}!a{}", id, n));

    //     renamed.insert(id.clone(), αid.clone());
    //     env.insert(αid.clone(), t1.clone());

    //     let (n, e, t2) = convert(n, env, &renamed, e);

    //     (n, I::Fun(αid, box e), Type::TFun(box t1.clone(), box t2))
    // }
    // E::Fix(ref id, ref t1, ref e) => {
    //     let mut renamed = renamed.clone();
    //     let (n, αid) = (n+1, format!("{}!a{}", id, n));

    //     renamed.insert(id.clone(), αid.clone());
    //     env.insert(αid.clone(), t1.clone());

    //     let (n, e, t2) = convert(n, env, &renamed, e);

    //     (n, I::Fix(αid, box e), t2)
    // }
    // E::App(ref e1, ref e2) => {
    //     let (n, e1, t1) = convert(n, env, renamed, e1);
    //     let (n, e2, _) = convert(n, env, renamed, e2);
    //     let t = match t1 {
    //         Type::TFun(_, t2) => *t2,
    //         _ => panic!("expected TFun, not {:?}", t1),
    //     };
    //     (n, I::App(box e1, box e2), t)
    // }
}

pub fn anf(implicit_expr: &implicit::Expr) -> Result<(Expr, HashMap<Id, explicit::Type>)> {

    let explicit_expr = hindley_milner::infer(implicit_expr)?;
    // alpha-renaming
    let (alpha_expr, env) = env::extract(&explicit_expr);

    let cenv = ConvEnv::new();
    let (_, expr) = expr(cenv, &alpha_expr, &identity);

    Ok((expr, env))
}

macro_rules! test_anf(
    ($s:expr, $ae:expr) => { {
        use implicit_parse;
        use tok::Tokenizer;
        use std;
        let s = $s;
        let tokenizer = Tokenizer::new(&s);
        let iexpr = match implicit_parse::parse_Program(&s, tokenizer) {
            Ok(iexpr) => iexpr,
            Err(e) => {
                die!("parse_Program({}): {:?}", $s, e);
            }
        };
        let anf_expr = match anf(&iexpr) {
            Ok((anf_expr, _)) => anf_expr,
            Err(e) => {
                die!("anf: {:?}", e);
            }
        };
        if anf_expr != $ae {
            die!("mismatch {:?} != {:?}", anf_expr, $ae);
        }
    } }
);

#[test]
fn anf_transforms() {
    use common::Op2 as O;
    use self::Imm as I;
    use self::Op::*;
    use self::Expr::*;

    test_anf!(
        "-22",
        Op(Imm(I::Int(-22))));

    test_anf!(
        "2+3",
        Let(String::from("!tmp!0"), box Op(Op2(O::Add, box I::Int(2), box I::Int(3))),
            box Op(Imm(I::Var(String::from("!tmp!0"))))));

    test_anf!(
        "2+(3 - 2)",
        Let(String::from("!tmp!0"), box Op(Op2(O::Sub, box I::Int(3), box I::Int(2))),
            box Let(String::from("!tmp!1"), box Op(Op2(O::Add, box I::Int(2), box I::Var(String::from("!tmp!0")))),
                    box Op(Imm(I::Var(String::from("!tmp!1")))))));

    test_anf!(
        "2 + 3 - 2",
        Let(String::from("!tmp!0"), box Op(Op2(O::Add, box I::Int(2), box I::Int(3))),
            box Let(String::from("!tmp!1"), box Op(Op2(O::Sub, box I::Var(String::from("!tmp!0")), box I::Int(2))),
                    box Op(Imm(I::Var(String::from("!tmp!1")))))));

    test_anf!(
        "let x = 1 in x",
        Let(String::from("x!a1"), box Op(Imm(I::Int(1))),
            box Op(Imm(I::Var(String::from("x!a1"))))));

    test_anf!(
        "let x = (if true then 1 else 2) in x",
        Let(String::from("!tmp!0"), box If(box I::Bool(true), box Op(Imm(I::Int(1))), box Op(Imm(I::Int(2)))),
            box Let(String::from("x!a1"), box Op(Imm(I::Var(String::from("!tmp!0")))),
                    box Op(Imm(I::Var(String::from("x!a1")))))));

    test_anf!(
        "let a = array(3, 5) in a[0]",
        Let(String::from("!tmp!0"), box Op(MkArray(box I::Int(3), box I::Int(5))),
            box Let(String::from("a!a1"), box Op(Imm(I::Var(String::from("!tmp!0")))),
                    box Let(String::from("!tmp!1"), box Op(GetArray(box I::Var(String::from("a!a1")), box I::Int(0))),
                            box Op(Imm(I::Var(String::from("!tmp!1"))))))));
}
<commit_msg>lambdal: full A-Normal Form translation (next: build Γ from ANF)<commit_after>use std::collections::HashMap;

use common::{Result, Id, Op2, Const};
use explicit;
use implicit;
use env;
use hindley_milner;

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Imm {
    Bool(bool),
    Int(i64),
    Var(Id),
    Fun(Id, Box<Expr>),
    Fix(Id, Box<Expr>),
    //    Star,
    //    V,
}

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Op {
    Op2(Op2, Box<Imm>, Box<Imm>),
    MkArray(Box<Imm>, Box<Imm>),
    GetArray(Box<Imm>, Box<Imm>),
    SetArray(Box<Imm>, Box<Imm>, Box<Imm>),
    Imm(Imm),
}
//    WellFormed(Imm), // Var-only

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Expr {
    If(Box<Imm>, Box<Expr>, Box<Expr>),
    App(Box<Imm>, Box<Imm>),
    Let(Id, Box<Expr>, Box<Expr>),
    Op(Op),
}

#[derive(PartialEq, Eq, Clone, Debug)]
struct ConvEnv {
    //env: HashMap<Id, explicit::Type>,
    next_id: i32,
}

impl ConvEnv {
    fn new() -> ConvEnv {
        ConvEnv {
            //env: HashMap::new(),
            next_id: 0,
        }
    }

    fn tmp(&self) -> (ConvEnv, Id) {
        let id = self.next_id;
        let c = ConvEnv{
            //env: self.env.clone(),
            next_id: self.next_id+1,
        };

        (c, format!("!tmp!{}", id))
    }
}

fn constant(c: &Const) -> Imm {
    match *c {
        Const::Bool(b) => Imm::Bool(b),
        Const::Int(i) => Imm::Int(i),
    }
}

fn identity(cenv: ConvEnv, i: Imm) -> (ConvEnv, Expr) {
    (cenv, Expr::Op(Op::Imm(i)))
}

// 1:1 translation -- can't fail
fn expr(cenv: ConvEnv, e: &explicit::Expr, k: &Fn(ConvEnv, Imm) -> (ConvEnv, Expr)) -> (ConvEnv, Expr) {

    use self::Imm as I;
    use typed::Expr as E;
    use self::Op::*;
    use self::Expr::*;

    match *e {
        E::Const(ref c) => k(cenv, constant(c)),
        E::Op2(op, ref l, ref r) => {
            expr(cenv, l, &|cenv: ConvEnv, ll: I| -> (ConvEnv, Expr) {
                expr(cenv, r, &|cenv: ConvEnv, rr: I| -> (ConvEnv, Expr) {

                    let (cenv, op_tmp) = cenv.tmp();
                    let op_val = Op(Op2(op, box ll.clone(), box rr));
                    // value to pass to the continuation
                    let op_ref = I::Var(op_tmp.clone());

                    // our inner expression is whatever the
                    // continuation says it is.
                    let (cenv, result) = k(cenv, op_ref);

                    (cenv, Let(op_tmp, box op_val, box result))
                })
            })
        }
        E::Let(ref id, ref e1, ref e2) => {
            expr(cenv, e1, &|cenv: ConvEnv, e1l: I| -> (ConvEnv, Expr) {
                let (cenv, inner) = expr(cenv, e2, k);
                (cenv, Let(id.clone(), box Op(Imm(e1l.clone())), box inner))
            })
        }
        E::Var(ref x) => {
            k(cenv, I::Var(x.clone()))
        }
        E::If(ref e1, ref e2, ref e3) => {
            expr(cenv, e1, &|cenv: ConvEnv, cond_ref: I| -> (ConvEnv, Expr) {

                let (cenv, l_true) = expr(cenv, e2, &identity);
                let (cenv, l_false) = expr(cenv, e3, &identity);

                // value to pass to the continuation
                let val = If(box cond_ref, box l_true, box l_false);
                let (cenv, val_tmp) = cenv.tmp();

                // our inner expression is whatever the
                // continuation says it is.
                let (cenv, mut result) = k(cenv, I::Var(val_tmp.clone()));

                result = Let(val_tmp, box val, box result);

                (cenv, result)
            })
        }
        E::Fun(ref id, _, ref e) => {

            let (cenv, fun) = expr(cenv, e, &identity);
            let (cenv, fun_ref) = cenv.tmp();
            let (cenv, result) = k(cenv, I::Var(fun_ref.clone()));

            (cenv, Let(fun_ref, box Op(Imm(I::Fun(id.clone(), box fun))), box result))
        }
        E::Fix(ref id, _, ref e) => {

            let (cenv, fix) = expr(cenv, e, &identity);
            let (cenv, fix_ref) = cenv.tmp();
            let (cenv, result) = k(cenv, I::Var(fix_ref.clone()));

            (cenv, Let(fix_ref, box Op(Imm(I::Fix(id.clone(), box fix))), box result))
        }
        E::App(ref e1, ref e2) => {
            expr(cenv, e1, &|cenv: ConvEnv, ie1: I| -> (ConvEnv, Expr) {
                expr(cenv, e2, &|cenv: ConvEnv, ie2: I| -> (ConvEnv, Expr) {

                    let (cenv, app_tmp) = cenv.tmp();
                    let app_val = App(box ie1.clone(), box ie2.clone());
                    // value to pass to the continuation
                    let app_ref = I::Var(app_tmp.clone());

                    // our inner expression is whatever the
                    // continuation says it is.
                    let (cenv, result) = k(cenv, app_ref);

                    (cenv, Let(app_tmp, box app_val, box result))
                })
            })
        }
        E::MkArray(ref sz, ref val) => {
            println!("O mkarray");
            expr(cenv, sz, &|cenv: ConvEnv, isz: I| -> (ConvEnv, Expr) {
                expr(cenv, val, &|cenv: ConvEnv, ival: I| -> (ConvEnv, Expr) {
                    println!("I mkarray");
                    let val = Op(MkArray(box isz.clone(), box ival));
                    let (cenv, val_ref) = cenv.tmp();
                    let (cenv, result) = k(cenv, I::Var(val_ref.clone()));
                    (cenv, Let(val_ref, box val, box result))
                })
            })
        }
        E::GetArray(ref id, ref idx) => {
            println!("O getarray");
            expr(cenv, id, &|cenv: ConvEnv, iid: I| -> (ConvEnv, Expr) {
                expr(cenv, idx, &|cenv: ConvEnv, iidx: I| -> (ConvEnv, Expr) {
                    println!("I getarray");
                    let val = Op(GetArray(box iid.clone(), box iidx));
                    let (cenv, val_ref) = cenv.tmp();
                    let (cenv, result) = k(cenv, I::Var(val_ref.clone()));
                    (cenv, Let(val_ref, box val, box result))
                })
            })
        }
        E::SetArray(ref id, ref idx, ref v) => {
            expr(cenv, id, &|cenv: ConvEnv, iid: I| -> (ConvEnv, Expr) {
                expr(cenv, idx, &|cenv: ConvEnv, iidx: I| -> (ConvEnv, Expr) {
                    expr(cenv, v, &|cenv: ConvEnv, iv: I| -> (ConvEnv, Expr) {
                        let val = Op(SetArray(box iid.clone(), box iidx.clone(), box iv));
                        let (cenv, val_ref) = cenv.tmp();
                        let (cenv, result) = k(cenv, I::Var(val_ref.clone()));
                        (cenv, Let(val_ref, box val, box result))
                    })
                })
            })
        }
    }
}

pub fn anf(implicit_expr: &implicit::Expr) -> Result<(Expr, HashMap<Id, explicit::Type>)> {

    let explicit_expr = hindley_milner::infer(implicit_expr)?;
    // alpha-renaming
    let (alpha_expr, env) = env::extract(&explicit_expr);

    let cenv = ConvEnv::new();
    let (_, expr) = expr(cenv, &alpha_expr, &identity);

    Ok((expr, env))
}

macro_rules! test_anf(
    ($s:expr, $ae:expr) => { {
        use implicit_parse;
        use tok::Tokenizer;
        use std;
        let s = $s;
        let tokenizer = Tokenizer::new(&s);
        let iexpr = match implicit_parse::parse_Program(&s, tokenizer) {
            Ok(iexpr) => iexpr,
            Err(e) => {
                die!("parse_Program({}): {:?}", $s, e);
            }
        };
        let anf_expr = match anf(&iexpr) {
            Ok((anf_expr, _)) => anf_expr,
            Err(e) => {
                die!("anf: {:?}", e);
            }
        };
        if anf_expr != $ae {
            die!("mismatch {:?} != {:?}", anf_expr, $ae);
        }
    } }
);

#[test]
fn anf_transforms() {
    use common::Op2 as O;
    use self::Imm as I;
    use self::Op::*;
    use self::Expr::*;

    test_anf!(
        "-22",
        Op(Imm(I::Int(-22))));

    test_anf!(
        "2+3",
        Let(String::from("!tmp!0"), box Op(Op2(O::Add, box I::Int(2), box I::Int(3))),
            box Op(Imm(I::Var(String::from("!tmp!0"))))));

    test_anf!(
        "2+(3 - 2)",
        Let(String::from("!tmp!0"), box Op(Op2(O::Sub, box I::Int(3), box I::Int(2))),
            box Let(String::from("!tmp!1"), box Op(Op2(O::Add, box I::Int(2), box I::Var(String::from("!tmp!0")))),
                    box Op(Imm(I::Var(String::from("!tmp!1")))))));

    test_anf!(
        "2 + 3 - 2",
        Let(String::from("!tmp!0"), box Op(Op2(O::Add, box I::Int(2), box I::Int(3))),
            box Let(String::from("!tmp!1"), box Op(Op2(O::Sub, box I::Var(String::from("!tmp!0")), box I::Int(2))),
                    box Op(Imm(I::Var(String::from("!tmp!1")))))));

    test_anf!(
        "let x = 1 in x",
        Let(String::from("x!a1"), box Op(Imm(I::Int(1))),
            box Op(Imm(I::Var(String::from("x!a1"))))));

    test_anf!(
        "let x = (if true then 1 else 2) in x",
        Let(String::from("!tmp!0"), box If(box I::Bool(true), box Op(Imm(I::Int(1))), box Op(Imm(I::Int(2)))),
            box Let(String::from("x!a1"), box Op(Imm(I::Var(String::from("!tmp!0")))),
                    box Op(Imm(I::Var(String::from("x!a1")))))));

    test_anf!(
        "let a = array(3, 5) in a[0]",
        Let(String::from("!tmp!0"), box Op(MkArray(box I::Int(3), box I::Int(5))),
            box Let(String::from("a!a1"), box Op(Imm(I::Var(String::from("!tmp!0")))),
                    box Let(String::from("!tmp!1"), box Op(GetArray(box I::Var(String::from("a!a1")), box I::Int(0))),
                            box Op(Imm(I::Var(String::from("!tmp!1"))))))));

    test_anf!(
        "let f = (fun n -> n + 1) in f (2+3)",
        Let(String::from("!tmp!1"), box Op(Imm(I::Fun(String::from("n!a2"),
                                                      box Let(String::from("!tmp!0"), box Op(Op2(O::Add, box I::Var(String::from("n!a2")), box I::Int(1))),
                                                              box Op(Imm(I::Var(String::from("!tmp!0")))))))),
            box Let(String::from("f!a1"), box Op(Imm(I::Var(String::from("!tmp!1")))),
                    box Let(String::from("!tmp!2"), box Op(Op2(O::Add, box I::Int(2), box I::Int(3))),
                            box Let(String::from("!tmp!3"), box App(box I::Var(String::from("f!a1")), box I::Var(String::from("!tmp!2"))),
                                    box Op(Imm(I::Var(String::from("!tmp!3")))))))));
}
<|endoftext|>


<commit_before>pub mod config;
pub mod downloader;
pub mod converter;

use mysql::conn::MyOpts;
use mysql::conn::pool;
use mysql::conn::pool::{MyPooledConn,MyPool};
use mysql::value::from_value;
use lib::downloader::{Downloader,DownloadDB};

use mysql::error::MyError;
use std::error::Error;
use std::io;
use CONFIG;

use std::thread::sleep_ms;

use std::ascii::AsciiExt;

use std::fs::{rename};

macro_rules! try_option { ($e:expr) => (match $e { Some(x) => x, None => return None }) }

///Move result value out, return with none on err & print
macro_rules! try_reoption { ($e:expr) => (match $e { Ok(x) => x, Err(e) => {println!("{}",e);return None }}) }

#[derive(Debug)]
pub enum DownloadError{
    DownloadError(String),
    FFMPEGError(String),
    DMCAError,
    InternalError(String),
    DBError(String),
}

impl From<MyError> for DownloadError {
    fn from(err: MyError) -> DownloadError {
        DownloadError::DBError(err.description().into())
    }
}

impl From<io::Error> for DownloadError {
    fn from(err: io::Error) -> DownloadError {
        DownloadError::InternalError(err.description().into())
    }
}

pub fn db_connect(opts: MyOpts, sleep_time: u32) -> MyPool {
    loop {
        match pool::MyPool::new(opts.clone()) {
            Ok(conn) => {return conn;},
            Err(err) => println!("Unable to establish a connection:\n{}",err),
        };
        sleep_ms(sleep_time);
    }
}

///Return whether the quality is a split container or not: video only
///as specified in the docs
pub fn is_split_container(quality: &i16) -> bool {
    if CONFIG.extensions.mp3.contains(quality) {
        false
    } else if CONFIG.extensions.aac.contains(quality) {
        false
    } else if CONFIG.extensions.m4a.contains(quality) {
        false
    } else {
        true
    }
}

///Set the state of the current query, code dependent, see QueryCodes
pub fn set_query_state(pool: & pool::MyPool,qid: &i64 , state: &str, finished: bool){ // same here
    let mut conn = pool.get_conn().unwrap();
    let progress: i32 = if finished {
        100
    }else{
        0
    };
    let mut stmt = conn.prepare("UPDATE querydetails SET status = ? , progress = ? WHERE qid = ?").unwrap();
    let result = stmt.execute(&[&state,&progress,qid]); // why is this var needed ?!
    match result {
        Ok(_) => (),
        Err(why) => println!("Error setting query state: {}",why),
    }
}

///Update status code for query entry
pub fn set_query_code(conn: & mut MyPooledConn, code: &i8, qid: &i64) -> Result<(), DownloadError> { // same here
    let mut stmt = conn.prepare("UPDATE querydetails SET code = ? WHERE qid = ?").unwrap();
    let result = stmt.execute(&[code,qid]);
    match result {
        Ok(_) => Ok(()),
        Err(why) => Err(DownloadError::DBError(why.description().into())),
    }
}

///Update progress steps for db entry
pub fn update_steps(pool: & pool::MyPool, qid: &i64, step: i32, max_steps: i32, finished: bool){
    set_query_state(&pool,qid, &format!("{}|{}", step, max_steps), finished);
}

///add file to db including it's name & fid based on qid
pub fn add_file_entry(pool: & pool::MyPool, fid: &i64, name: &str, real_name: &str){
    println!("name: {}",name);
    let mut conn = pool.get_conn().unwrap();
    let mut stmt = conn.prepare("INSERT INTO files (fid,name,rname,valid) VALUES (?,?,?,?)").unwrap();
    let result = stmt.execute(&[fid,&real_name,&name,&1]); // why is this var needed ?!
    match result {
        Ok(_) => (),
        Err(why) => println!("Error adding file: {}",why),
    }
}

///Format file name for
pub fn format_file_name<'a>(name: &str, download: &'a Downloader, qid: &i64) -> String {
    println!("Fileextension: {:?}", get_file_ext(download));
    format!("{}-{}.{}",url_encode(name), qid, get_file_ext(download))
}

///Returns the file extension
pub fn get_file_ext<'a>(download: &Downloader) -> &'a str {
    if download.is_audio() {
        if CONFIG.extensions.aac.contains(&download.ddb.quality) {
            "aac"
        }else if CONFIG.extensions.mp3.contains(&download.ddb.quality) {
            "mp3"
        }else{
            "unknown"
        }
    }else{
        if CONFIG.extensions.mp4.contains(&download.ddb.quality) {
            "mp4"
        } else if CONFIG.extensions.flv.contains(&download.ddb.quality) {
            "flv"
        } else {
            "unknown"
        }
    }
}

///Move file to location
pub fn move_file(original: &str, destination: &str) -> Result<(),DownloadError> {
    match rename(original, destination) { // no try possible..
        Err(v) => Err(v.into()),
        Ok(_) => Ok(()),
    }
}

///Return an sanitized String (url encode still required)
pub fn url_encode(input: &str) -> String {
    // iterator over input, apply function to each element(function
    input.chars().map(|char| {
        match char {
            '\'' | '"' | '\\' => '_',
            '&' => '-',
            c if c.is_ascii() => c,
            _ => '_'
        }
    }).collect()
    // match for each char, then do collect: loop through the iterator, collect all elements
    // into container FromIterator
}

///Format save location for file, zip dependent
///audio files get an 'a' as suffix
pub fn format_file_path(qid: &i64, folder: Option<String>, audio: bool) -> String {
    let suffix = if audio {
        "a"
    }else {
        ""
    };
    match folder {
        Some(v) => format!("{}/{}/{}", &CONFIG.general.save_dir, v, qid),
        None => format!("{}/{}{}", &CONFIG.general.save_dir, qid,suffix),
    }
}

///Format save path, dependent on zip option.
pub fn format_save_path<'a>(folder: Option<String>, name: &str, download: &'a Downloader, qid: &i64) -> String {
    match folder {
        Some(v) => format!("{}/{}/{}", &CONFIG.general.save_dir, v, format_file_name(name,download,qid)),
        None => format!("{}/{}", &CONFIG.general.download_dir, format_file_name(name,download,qid)),
    }
}

///Request an entry from the DB to handle
pub fn request_entry(pool: & pool::MyPool) -> Option<DownloadDB> {
    let mut conn = try_reoption!(pool.get_conn());
    let mut stmt = try_reoption!(conn.prepare("SELECT queries.qid,url,type,quality FROM querydetails \
                    INNER JOIN queries \
                    ON querydetails.qid = queries.qid \
                    WHERE querydetails.code = 0 \
                    ORDER BY queries.created \
                    LIMIT 1"));
    let mut result = try_reoption!(stmt.execute(&[]));
    let result = try_reoption!(try_option!(result.next())); // result.next().'Some'->value.'unwrap'

    println!("Result: {:?}", result[0]);
    println!("result str: {}", result[1].into_str());
    let download_db = DownloadDB { url: from_value::<String>(&result[1]),
                                    quality: from_value::<i16>(&result[3]),
                                    qid: from_value::<i64>(&result[0]),
                                    folder: CONFIG.general.save_dir.clone(),
                                    pool: pool.clone(),
                                    playlist: false, //TEMP
                                    compress: false };
    Some(download_db)
}

///Set dbms connection settings
pub fn mysql_options() -> MyOpts {
    MyOpts {
        tcp_addr: Some(CONFIG.db.ip.clone()),
        tcp_port: CONFIG.db.port,
        user: Some(CONFIG.db.user.clone()),
        pass: Some(CONFIG.db.password.clone()),
        db_name: Some(CONFIG.db.db.clone()),
        ..Default::default() // set others to default
    }
}<commit_msg>added zip_folder & create_zip_cmd to zip a folder<commit_after>pub mod config;
pub mod downloader;
pub mod converter;

use mysql::conn::MyOpts;
use mysql::conn::pool;
use mysql::conn::pool::{MyPooledConn,MyPool};
use mysql::value::from_value;
use lib::downloader::{Downloader,DownloadDB};

use mysql::error::MyError;
use std::process::{Command,Output};
use std::error::Error;
use std::io;
use std::str;
use CONFIG;

use std::thread::sleep_ms;

use std::ascii::AsciiExt;

use std::fs::{rename};

macro_rules! try_option { ($e:expr) => (match $e { Some(x) => x, None => return None }) }

///Move result value out, return with none on err & print
macro_rules! try_reoption { ($e:expr) => (match $e { Ok(x) => x, Err(e) => {println!("{}",e);return None }}) }

#[derive(Debug)]
pub enum DownloadError{
    DownloadError(String),
    FFMPEGError(String),
    DMCAError,
    InternalError(String),
    DBError(String),
}

impl From<MyError> for DownloadError {
    fn from(err: MyError) -> DownloadError {
        DownloadError::DBError(err.description().into())
    }
}

impl From<io::Error> for DownloadError {
    fn from(err: io::Error) -> DownloadError {
        DownloadError::InternalError(err.description().into())
    }
}

pub fn db_connect(opts: MyOpts, sleep_time: u32) -> MyPool {
    loop {
        match pool::MyPool::new(opts.clone()) {
            Ok(conn) => {return conn;},
            Err(err) => println!("Unable to establish a connection:\n{}",err),
        };
        sleep_ms(sleep_time);
    }
}

///Return whether the quality is a split container or not: video only
///as specified in the docs
pub fn is_split_container(quality: &i16) -> bool {
    if CONFIG.extensions.mp3.contains(quality) {
        false
    } else if CONFIG.extensions.aac.contains(quality) {
        false
    } else if CONFIG.extensions.m4a.contains(quality) {
        false
    } else {
        true
    }
}

///Set the state of the current query, code dependent, see QueryCodes
pub fn set_query_state(pool: & pool::MyPool,qid: &i64 , state: &str, finished: bool){ // same here
    let mut conn = pool.get_conn().unwrap();
    let progress: i32 = if finished {
        100
    }else{
        0
    };
    let mut stmt = conn.prepare("UPDATE querydetails SET status = ? , progress = ? WHERE qid = ?").unwrap();
    let result = stmt.execute(&[&state,&progress,qid]); // why is this var needed ?!
    match result {
        Ok(_) => (),
        Err(why) => println!("Error setting query state: {}",why),
    }
}

///Update status code for query entry
pub fn set_query_code(conn: & mut MyPooledConn, code: &i8, qid: &i64) -> Result<(), DownloadError> { // same here
    let mut stmt = conn.prepare("UPDATE querydetails SET code = ? WHERE qid = ?").unwrap();
    let result = stmt.execute(&[code,qid]);
    match result {
        Ok(_) => Ok(()),
        Err(why) => Err(DownloadError::DBError(why.description().into())),
    }
}

///Update progress steps for db entry
pub fn update_steps(pool: & pool::MyPool, qid: &i64, step: i32, max_steps: i32, finished: bool){
    set_query_state(&pool,qid, &format!("{}|{}", step, max_steps), finished);
}

///add file to db including it's name & fid based on qid
pub fn add_file_entry(pool: & pool::MyPool, fid: &i64, name: &str, real_name: &str){
    println!("name: {}",name);
    let mut conn = pool.get_conn().unwrap();
    let mut stmt = conn.prepare("INSERT INTO files (fid,name,rname,valid) VALUES (?,?,?,?)").unwrap();
    let result = stmt.execute(&[fid,&real_name,&name,&1]); // why is this var needed ?!
    match result {
        Ok(_) => (),
        Err(why) => println!("Error adding file: {}",why),
    }
}

///Format file name for
pub fn format_file_name<'a>(name: &str, download: &'a Downloader, qid: &i64) -> String {
    println!("Fileextension: {:?}", get_file_ext(download));
    format!("{}-{}.{}",url_encode(name), qid, get_file_ext(download))
}

///Returns the file extension
pub fn get_file_ext<'a>(download: &Downloader) -> &'a str {
    if download.is_audio() {
        if CONFIG.extensions.aac.contains(&download.ddb.quality) {
            "aac"
        }else if CONFIG.extensions.mp3.contains(&download.ddb.quality) {
            "mp3"
        }else{
            "unknown"
        }
    }else{
        if CONFIG.extensions.mp4.contains(&download.ddb.quality) {
            "mp4"
        } else if CONFIG.extensions.flv.contains(&download.ddb.quality) {
            "flv"
        } else {
            "unknown"
        }
    }
}

///Move file to location
pub fn move_file(original: &str, destination: &str) -> Result<(),DownloadError> {
    match rename(original, destination) { // no try possible..
        Err(v) => Err(v.into()),
        Ok(_) => Ok(()),
    }
}

///Return an sanitized String (url encode still required)
pub fn url_encode(input: &str) -> String {
    // iterator over input, apply function to each element(function
    input.chars().map(|char| {
        match char {
            '\'' | '"' | '\\' => '_',
            '&' => '-',
            c if c.is_ascii() => c,
            _ => '_'
        }
    }).collect()
    // match for each char, then do collect: loop through the iterator, collect all elements
    // into container FromIterator
}

///Format save location for file, zip dependent
///audio files get an 'a' as suffix
pub fn format_file_path(qid: &i64, folder: Option<String>, audio: bool) -> String {
    let suffix = if audio {
        "a"
    }else {
        ""
    };
    match folder {
        Some(v) => format!("{}/{}/{}", &CONFIG.general.save_dir, v, qid),
        None => format!("{}/{}{}", &CONFIG.general.save_dir, qid,suffix),
    }
}

///Format save path, dependent on zip option.
pub fn format_save_path<'a>(folder: Option<String>, name: &str, download: &'a Downloader, qid: &i64) -> String {
    match folder {
        Some(v) => format!("{}/{}/{}", &CONFIG.general.save_dir, v, format_file_name(name,download,qid)),
        None => format!("{}/{}", &CONFIG.general.download_dir, format_file_name(name,download,qid)),
    }
}

///Request an entry from the DB to handle
pub fn request_entry(pool: & pool::MyPool) -> Option<DownloadDB> {
    let mut conn = try_reoption!(pool.get_conn());
    let mut stmt = try_reoption!(conn.prepare("SELECT queries.qid,url,type,quality FROM querydetails \
                    INNER JOIN queries \
                    ON querydetails.qid = queries.qid \
                    WHERE querydetails.code = 0 \
                    ORDER BY queries.created \
                    LIMIT 1"));
    let mut result = try_reoption!(stmt.execute(&[]));
    let result = try_reoption!(try_option!(result.next())); // result.next().'Some'->value.'unwrap'

    println!("Result: {:?}", result[0]);
    println!("result str: {}", result[1].into_str());
    let download_db = DownloadDB { url: from_value::<String>(&result[1]),
                                    quality: from_value::<i16>(&result[3]),
                                    qid: from_value::<i64>(&result[0]),
                                    folder: CONFIG.general.save_dir.clone(),
                                    pool: pool.clone(),
                                    playlist: false, //TEMP
                                    compress: false };
    Some(download_db)
}

pub fn zip_folder(folder: &str, zip_name: &str) -> Result<(), DownloadError> {
    let io = try!(create_zip_cmd(folder,zip_name));
    if str::from_utf8(&io.stdout).unwrap().contains("error") {
        return Err(DownloadError::DownloadError(format!("error: {:?}",&io.stdout)))
    }
    Ok(())
}

fn create_zip_cmd(folder: &str, zip_file: &str) -> Result<Output, DownloadError> {
    match Command::new("tar").arg("zcvf").arg(folder).arg(zip_file).output() {
                     Err(e) => Err(DownloadError::InternalError(format!("failed to zip: {}", e))),
                     Ok(v) => Ok(v),
    }
}

///Set dbms connection settings
pub fn mysql_options() -> MyOpts {
    MyOpts {
        tcp_addr: Some(CONFIG.db.ip.clone()),
        tcp_port: CONFIG.db.port,
        user: Some(CONFIG.db.user.clone()),
        pass: Some(CONFIG.db.password.clone()),
        db_name: Some(CONFIG.db.db.clone()),
        ..Default::default() // set others to default
    }
}<|endoftext|>


<commit_before>/*
  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};

use album::Album;
use audit::{audit_get_str_value, Report, Reporter, SkipReason};
use folder::Folder;
use keyword::{parse_keywords, Keyword};
use master::Master;
use plist::Value;
use plutils;
use store;
use version::Version;
use AplibObject;
use PlistLoadable;

// This is mostly from db_version = 110

const INFO_PLIST: &str = "Info.plist";

const BUNDLE_IDENTIFIER: &str = "com.apple.Aperture.library";

const DATABASE_DIR: &str = "Database";

// in Database
const DATAMODEL_VERSION_PLIST: &str = "DataModelVersion.plist";
const KEYWORDS_PLIST: &str = "Keywords.plist";
const ALBUMS_DIR: &str = "Albums";
const FOLDERS_DIR: &str = "Folders";
const VERSIONS_BASE_DIR: &str = "Versions";

/// Info of the library data model
pub struct ModelInfo {
    pub is_iphoto_library: Option<bool>,
    pub db_version: Option<i64>,
    pub db_minor_back_compatible_version: Option<i64>,
    pub db_minor_version: Option<i64>,
    pub db_uuid: Option<String>,
    pub create_date: Option<String>,
    pub image_io_version: Option<String>,
    pub raw_camera_bundle_version: Option<String>,
    pub touched_by_aperture: Option<bool>,
    pub master_count: Option<i64>,
    pub version_count: Option<i64>,
    pub project_compat_back_to_version: Option<i64>,
    pub project_version: Option<i64>,
}

impl ModelInfo {
    fn parse(plist: &Value) -> Option<ModelInfo> {
        use plutils::{get_bool_value, get_int_value, get_str_value};

        match *plist {
            Value::Dictionary(ref dict) => Some(ModelInfo {
                db_uuid: get_str_value(dict, "databaseUuid"),
                db_minor_back_compatible_version: get_int_value(
                    dict,
                    "DatabaseCompatibleBackToMinorVersion",
                ),
                db_minor_version: get_int_value(dict, "DatabaseMinorVersion"),
                db_version: get_int_value(dict, "DatabaseVersion"),
                is_iphoto_library: get_bool_value(dict, "isIPhotoLibrary"),
                create_date: get_str_value(dict, "createDate"),
                image_io_version: get_str_value(dict, "imageIOVersion"),
                raw_camera_bundle_version: get_str_value(dict, "rawCameraBundleVersion"),
                touched_by_aperture: get_bool_value(dict, "touchedByAperture"),
                master_count: get_int_value(dict, "masterCount"),
                version_count: get_int_value(dict, "versionCount"),
                project_version: get_int_value(dict, "projectVersion"),
                project_compat_back_to_version: get_int_value(
                    dict,
                    "projectCompatibleBackToVersion",
                ),
            }),
            _ => None,
        }
    }
}

/// Library is the Aperture library.
pub struct Library {
    /// The path to the .aplib bundle (the directory)
    path: String,

    /// It's version string (displayed by get info in the Finder)
    version: String,

    /// All the folders UUID
    folders: HashSet<String>,
    /// All the albums UUID
    albums: HashSet<String>,
    //    keywords: HashSet<String>,
    /// All the masters UUID
    masters: HashSet<String>,
    /// All the version UUID
    versions: HashSet<String>,

    /// The object store. The key is the UUID
    objects: HashMap<String, store::Wrapper>,
    /// Auditor for the audit mode.
    auditor: Option<Reporter>,
}

impl Library {
    /// Create a new library object from the exist path to
    /// the bundle directory.
    pub fn new(p: &str) -> Library {
        Library {
            path: p.to_owned(),
            version: String::new(),

            folders: HashSet::new(),
            albums: HashSet::new(),
            //            keywords: HashSet::new(),
            masters: HashSet::new(),
            versions: HashSet::new(),

            objects: HashMap::new(),
            auditor: None,
        }
    }

    /// Set an auditor.
    pub fn set_auditor(&mut self, auditor: Option<Reporter>) {
        self.auditor = auditor;
    }
    /// Get the auditor
    pub fn get_auditor(&self) -> Option<&Reporter> {
        self.auditor.as_ref()
    }

    /// Store the wrapped object.
    /// Return true if the object was stored
    /// Return false if there already was an object with the same uuid
    /// or if the uuid in invalid.
    pub fn store(&mut self, obj: store::Wrapper) -> bool {
        let uuid_str = {
            let uuid = obj.uuid();
            if uuid.is_none() {
                return false;
            }
            uuid.unwrap().to_owned()
        };
        match self.objects.insert(uuid_str, obj) {
            None => true,
            _ => false,
        }
    }

    /// Get an object out of the store by UUID
    pub fn get(&self, uuid: &str) -> Option<&store::Wrapper> {
        self.objects.get(uuid)
    }

    /// Get the library version. Will parse the plist for that
    /// if needed.
    pub fn library_version(&mut self) -> Result<&String, SkipReason> {
        if self.version.is_empty() {
            let plist_path = self.build_path(INFO_PLIST, false);
            let plist = plutils::parse_plist(&plist_path);
            let audit = self.auditor.is_some();
            let mut report = if audit { Some(Report::new()) } else { None };

            match plist {
                Value::Dictionary(ref dict) => {
                    let version = audit_get_str_value(
                        dict,
                        "CFBundleShortVersionString",
                        &mut report.as_mut(),
                    );
                    if version.is_none() {
                        println!("FATAL no library version found");
                        return Err(SkipReason::NotFound);
                    }
                    self.version = version.unwrap();

                    let bundle_id =
                        audit_get_str_value(dict, "CFBundleIdentifier", &mut report.as_mut());
                    if let Some(id) = bundle_id {
                        if id != BUNDLE_IDENTIFIER {
                            if audit {
                                if let Some(ref mut r) = report {
                                    r.skip("CFBundleIdentifier", SkipReason::InvalidData);
                                }
                            }
                            println!("FATAL not a library");
                            return Err(SkipReason::InvalidData);
                        }
                    } else if audit {
                        if let Some(ref mut r) = report {
                            r.skip("CFBundleIdentifier", SkipReason::NotFound);
                        }
                        println!("FATAL no bundle identifier");
                        return Err(SkipReason::NotFound);
                    }

                    if audit {
                        if let Some(ref mut r) = report {
                            r.audit_ignored(dict, None);
                        }
                        self.auditor
                            .as_mut()
                            .unwrap()
                            .parsed(&plist_path.to_string_lossy(), report.unwrap());
                    }
                }
                _ => {
                    if audit {
                        self.auditor
                            .as_mut()
                            .unwrap()
                            .skip(&plist_path.to_string_lossy(), SkipReason::InvalidType);
                    }
                }
            }
        }
        Ok(&self.version)
    }

    /// Build a path from the bundle root.
    /// If database is true, will be from the Database subdirectory.
    fn build_path(&self, dir: &str, database: bool) -> PathBuf {
        let mut ppath = PathBuf::from(self.path.to_owned());
        if database {
            ppath.push(DATABASE_DIR);
        }
        ppath.push(dir);

        ppath
    }

    /// list items in dir with extension ext.
    /// Return a vector with full path for each.
    fn list_items(&self, dir: &str, ext: &str) -> Vec<PathBuf> {
        let ppath = self.build_path(dir, true);
        let mut list: Vec<PathBuf> = Vec::new();

        if !fs::metadata(&ppath).unwrap().is_dir() {
            // XXX return a Result
            return list;
        }

        for entry in fs::read_dir(&ppath).unwrap() {
            let p = entry.unwrap().path();
            if p.extension().unwrap() == ext {
                list.push(p.to_owned());
            }
        }

        list
    }

    /// Return the model info block
    pub fn get_model_info(&self) -> Option<ModelInfo> {
        let ppath = self.build_path(DATAMODEL_VERSION_PLIST, true);
        let plist = plutils::parse_plist(ppath);

        ModelInfo::parse(&plist)
    }

    /// Load items from directory `dir` with extension `ext`
    /// and store the uuids into `set`
    fn load_items<T, F>(&mut self, dir: &str, ext: &str, set: &mut HashSet<String>, pg: &mut F)
    where
        T: PlistLoadable + AplibObject,
        F: FnMut(u64) -> bool,
    {
        let file_list = self.list_items(dir, ext);
        let audit = self.auditor.is_some();
        for file in file_list {
            let mut report = if audit { Some(Report::new()) } else { None };
            if let Some(obj) = T::from_path(&file, report.as_mut()) {
                let mut store = false;
                if let Some(ref uuid) = *obj.uuid() {
                    set.insert(uuid.to_owned());
                    if audit {
                        self.auditor
                            .as_mut()
                            .unwrap()
                            .parsed(&file.to_string_lossy(), report.unwrap());
                    }
                    store = true;
                }
                if store {
                    self.store(T::wrap(obj));
                }
            } else {
                if audit {
                    self.auditor
                        .as_mut()
                        .unwrap()
                        .skip(&file.to_string_lossy(), SkipReason::ParseFailed);
                }
                println!("Failed to decode object from {:?}", file);
            }
            if !pg(1) {
                println!("Cancelled");
                return;
            }
        }
    }

    /// Load albums.
    pub fn load_albums<F: FnMut(u64) -> bool>(&mut self, pg: &mut F) {
        if self.albums.is_empty() {
            let mut albums: HashSet<String> = HashSet::new();
            self.load_items::<Album, F>(ALBUMS_DIR, "apalbum", &mut albums, pg);
            self.albums = albums;
        }
    }

    /// Get albums uuids.
    pub fn get_albums(&self) -> &HashSet<String> {
        &self.albums
    }

    /// Load folders.
    pub fn load_folders<F: FnMut(u64) -> bool>(&mut self, pg: &mut F) {
        if self.folders.is_empty() {
            let mut folders: HashSet<String> = HashSet::new();
            self.load_items::<Folder, F>(FOLDERS_DIR, "apfolder", &mut folders, pg);
            self.folders = folders;
        }
    }

    /// Get folders uuids.
    pub fn get_folders(&self) -> &HashSet<String> {
        &self.folders
    }

    fn recurse_list_directory(path: &Path, level: i32) -> Vec<PathBuf> {
        let mut list: Vec<PathBuf> = Vec::new();
        for entry in fs::read_dir(path).unwrap() {
            let entry = entry.unwrap();
            if entry.metadata().unwrap().is_dir() {
                if level == 0 {
                    list.push(entry.path());
                } else {
                    let mut sublist = Library::recurse_list_directory(&entry.path(), level - 1);
                    list.append(&mut sublist)
                }
            }
        }

        list
    }

    fn list_versions_items_dirs(&self) -> Vec<PathBuf> {
        let ppath = self.build_path(VERSIONS_BASE_DIR, true);

        if !fs::metadata(&ppath).unwrap().is_dir() {
            // XXX return a Result
            return Vec::new();
        }

        Library::recurse_list_directory(&ppath, 4)
    }

    fn list_versions_items(&self, ext: &str) -> Vec<PathBuf> {
        let list = self.list_versions_items_dirs();
        let mut items = Vec::new();

        for dir in list {
            if !fs::metadata(&dir).unwrap().is_dir() {
                continue;
            }

            for entry in fs::read_dir(&dir).unwrap() {
                let entry = entry.unwrap();
                let p = entry.path();
                if p.extension().unwrap() == ext {
                    items.push(entry.path().to_owned());
                }
            }
        }

        items
    }

    fn load_versions_items<T, F>(&mut self, ext: &str, set: &mut HashSet<String>, pg: &mut F)
    where
        T: PlistLoadable + AplibObject,
        F: FnMut(u64) -> bool,
    {
        let file_list = self.list_versions_items(ext);
        let audit = self.auditor.is_some();
        for file in file_list {
            let mut report = if audit { Some(Report::new()) } else { None };
            if let Some(obj) = T::from_path(&file, report.as_mut()) {
                let mut store = false;
                if let Some(ref uuid) = *obj.uuid() {
                    set.insert(uuid.to_owned());
                    store = true;
                    if audit {
                        self.auditor
                            .as_mut()
                            .unwrap()
                            .parsed(&file.to_string_lossy(), report.unwrap());
                    }
                }
                if store {
                    self.store(T::wrap(obj));
                }
            } else {
                if audit {
                    self.auditor
                        .as_mut()
                        .unwrap()
                        .skip(&file.to_string_lossy(), SkipReason::ParseFailed);
                }
                println!("Error decoding object from {:?}", file);
            }
            if !pg(1) {
                println!("Cancelled!");
                break;
            }
        }
    }

    /// Load versions.
    pub fn load_versions<F: FnMut(u64) -> bool>(&mut self, pg: &mut F) {
        if self.versions.is_empty() {
            let mut versions: HashSet<String> = HashSet::new();
            self.load_versions_items::<Version, F>("apversion", &mut versions, pg);
            self.versions = versions;
        }
    }

    /// Load masters.
    pub fn load_masters<F: FnMut(u64) -> bool>(&mut self, pg: &mut F) {
        if self.masters.is_empty() {
            let mut masters: HashSet<String> = HashSet::new();
            self.load_versions_items::<Master, F>("apmaster", &mut masters, pg);
            self.masters = masters;
        }
    }

    /// Return masters uuids.
    pub fn get_masters(&self) -> &HashSet<String> {
        &self.masters
    }

    /// Return versions uuids.
    pub fn get_versions(&self) -> &HashSet<String> {
        &self.versions
    }

    /// List keywords.
    pub fn list_keywords(&mut self) -> Option<Vec<Keyword>> {
        let audit = self.auditor.is_some();
        let mut report = if audit { Some(Report::new()) } else { None };
        let result = parse_keywords(self.build_path(KEYWORDS_PLIST, true), &mut report.as_mut());
        if audit {
            if result.is_some() {
                self.auditor
                    .as_mut()
                    .unwrap()
                    .parsed(KEYWORDS_PLIST, report.unwrap());
            } else {
                self.auditor
                    .as_mut()
                    .unwrap()
                    .skip(KEYWORDS_PLIST, SkipReason::ParseFailed);
            }
        }
        result
    }
}
<commit_msg>fix clippy issues<commit_after>/*
  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};

use album::Album;
use audit::{audit_get_str_value, Report, Reporter, SkipReason};
use folder::Folder;
use keyword::{parse_keywords, Keyword};
use master::Master;
use plist::Value;
use plutils;
use store;
use version::Version;
use AplibObject;
use PlistLoadable;

// This is mostly from db_version = 110

const INFO_PLIST: &str = "Info.plist";

const BUNDLE_IDENTIFIER: &str = "com.apple.Aperture.library";

const DATABASE_DIR: &str = "Database";

// in Database
const DATAMODEL_VERSION_PLIST: &str = "DataModelVersion.plist";
const KEYWORDS_PLIST: &str = "Keywords.plist";
const ALBUMS_DIR: &str = "Albums";
const FOLDERS_DIR: &str = "Folders";
const VERSIONS_BASE_DIR: &str = "Versions";

/// Info of the library data model
pub struct ModelInfo {
    pub is_iphoto_library: Option<bool>,
    pub db_version: Option<i64>,
    pub db_minor_back_compatible_version: Option<i64>,
    pub db_minor_version: Option<i64>,
    pub db_uuid: Option<String>,
    pub create_date: Option<String>,
    pub image_io_version: Option<String>,
    pub raw_camera_bundle_version: Option<String>,
    pub touched_by_aperture: Option<bool>,
    pub master_count: Option<i64>,
    pub version_count: Option<i64>,
    pub project_compat_back_to_version: Option<i64>,
    pub project_version: Option<i64>,
}

impl ModelInfo {
    fn parse(plist: &Value) -> Option<ModelInfo> {
        use plutils::{get_bool_value, get_int_value, get_str_value};

        match *plist {
            Value::Dictionary(ref dict) => Some(ModelInfo {
                db_uuid: get_str_value(dict, "databaseUuid"),
                db_minor_back_compatible_version: get_int_value(
                    dict,
                    "DatabaseCompatibleBackToMinorVersion",
                ),
                db_minor_version: get_int_value(dict, "DatabaseMinorVersion"),
                db_version: get_int_value(dict, "DatabaseVersion"),
                is_iphoto_library: get_bool_value(dict, "isIPhotoLibrary"),
                create_date: get_str_value(dict, "createDate"),
                image_io_version: get_str_value(dict, "imageIOVersion"),
                raw_camera_bundle_version: get_str_value(dict, "rawCameraBundleVersion"),
                touched_by_aperture: get_bool_value(dict, "touchedByAperture"),
                master_count: get_int_value(dict, "masterCount"),
                version_count: get_int_value(dict, "versionCount"),
                project_version: get_int_value(dict, "projectVersion"),
                project_compat_back_to_version: get_int_value(
                    dict,
                    "projectCompatibleBackToVersion",
                ),
            }),
            _ => None,
        }
    }
}

/// Library is the Aperture library.
pub struct Library {
    /// The path to the .aplib bundle (the directory)
    path: String,

    /// It's version string (displayed by get info in the Finder)
    version: String,

    /// All the folders UUID
    folders: HashSet<String>,
    /// All the albums UUID
    albums: HashSet<String>,
    //    keywords: HashSet<String>,
    /// All the masters UUID
    masters: HashSet<String>,
    /// All the version UUID
    versions: HashSet<String>,

    /// The object store. The key is the UUID
    objects: HashMap<String, store::Wrapper>,
    /// Auditor for the audit mode.
    auditor: Option<Reporter>,
}

impl Library {
    /// Create a new library object from the exist path to
    /// the bundle directory.
    pub fn new(p: &str) -> Library {
        Library {
            path: p.to_owned(),
            version: String::new(),

            folders: HashSet::new(),
            albums: HashSet::new(),
            //            keywords: HashSet::new(),
            masters: HashSet::new(),
            versions: HashSet::new(),

            objects: HashMap::new(),
            auditor: None,
        }
    }

    /// Set an auditor.
    pub fn set_auditor(&mut self, auditor: Option<Reporter>) {
        self.auditor = auditor;
    }
    /// Get the auditor
    pub fn get_auditor(&self) -> Option<&Reporter> {
        self.auditor.as_ref()
    }

    /// Store the wrapped object.
    /// Return true if the object was stored
    /// Return false if there already was an object with the same uuid
    /// or if the uuid in invalid.
    pub fn store(&mut self, obj: store::Wrapper) -> bool {
        if let Some(uuid_str) = obj.uuid() {
            match self.objects.insert(uuid_str, obj) {
                None => true,
                _ => false,
            }
        } else {
            false
        }
    }

    /// Get an object out of the store by UUID
    pub fn get(&self, uuid: &str) -> Option<&store::Wrapper> {
        self.objects.get(uuid)
    }

    /// Get the library version. Will parse the plist for that
    /// if needed.
    pub fn library_version(&mut self) -> Result<&String, SkipReason> {
        if self.version.is_empty() {
            let plist_path = self.build_path(INFO_PLIST, false);
            let plist = plutils::parse_plist(&plist_path);
            let audit = self.auditor.is_some();
            let mut report = if audit { Some(Report::new()) } else { None };

            match plist {
                Value::Dictionary(ref dict) => {
                    let version = audit_get_str_value(
                        dict,
                        "CFBundleShortVersionString",
                        &mut report.as_mut(),
                    );
                    if version.is_none() {
                        println!("FATAL no library version found");
                        return Err(SkipReason::NotFound);
                    }
                    self.version = version.unwrap();

                    let bundle_id =
                        audit_get_str_value(dict, "CFBundleIdentifier", &mut report.as_mut());
                    if let Some(id) = bundle_id {
                        if id != BUNDLE_IDENTIFIER {
                            if audit {
                                if let Some(ref mut r) = report {
                                    r.skip("CFBundleIdentifier", SkipReason::InvalidData);
                                }
                            }
                            println!("FATAL not a library");
                            return Err(SkipReason::InvalidData);
                        }
                    } else if audit {
                        if let Some(ref mut r) = report {
                            r.skip("CFBundleIdentifier", SkipReason::NotFound);
                        }
                        println!("FATAL no bundle identifier");
                        return Err(SkipReason::NotFound);
                    }

                    if audit {
                        if let Some(ref mut r) = report {
                            r.audit_ignored(dict, None);
                        }
                        self.auditor
                            .as_mut()
                            .unwrap()
                            .parsed(&plist_path.to_string_lossy(), report.unwrap());
                    }
                }
                _ => {
                    if audit {
                        self.auditor
                            .as_mut()
                            .unwrap()
                            .skip(&plist_path.to_string_lossy(), SkipReason::InvalidType);
                    }
                }
            }
        }
        Ok(&self.version)
    }

    /// Build a path from the bundle root.
    /// If database is true, will be from the Database subdirectory.
    fn build_path(&self, dir: &str, database: bool) -> PathBuf {
        let mut ppath = PathBuf::from(self.path.to_owned());
        if database {
            ppath.push(DATABASE_DIR);
        }
        ppath.push(dir);

        ppath
    }

    /// list items in dir with extension ext.
    /// Return a vector with full path for each.
    fn list_items(&self, dir: &str, ext: &str) -> Vec<PathBuf> {
        let ppath = self.build_path(dir, true);
        let mut list: Vec<PathBuf> = Vec::new();

        if !fs::metadata(&ppath).unwrap().is_dir() {
            // XXX return a Result
            return list;
        }

        for entry in fs::read_dir(&ppath).unwrap() {
            let p = entry.unwrap().path();
            if p.extension().unwrap() == ext {
                list.push(p.to_owned());
            }
        }

        list
    }

    /// Return the model info block
    pub fn get_model_info(&self) -> Option<ModelInfo> {
        let ppath = self.build_path(DATAMODEL_VERSION_PLIST, true);
        let plist = plutils::parse_plist(ppath);

        ModelInfo::parse(&plist)
    }

    /// Load items from directory `dir` with extension `ext`
    /// and store the uuids into `set`
    fn load_items<T, F>(&mut self, dir: &str, ext: &str, set: &mut HashSet<String>, pg: &mut F)
    where
        T: PlistLoadable + AplibObject,
        F: FnMut(u64) -> bool,
    {
        let file_list = self.list_items(dir, ext);
        let audit = self.auditor.is_some();
        for file in file_list {
            let mut report = if audit { Some(Report::new()) } else { None };
            if let Some(obj) = T::from_path(&file, report.as_mut()) {
                let mut store = false;
                if let Some(ref uuid) = *obj.uuid() {
                    set.insert(uuid.to_owned());
                    if audit {
                        self.auditor
                            .as_mut()
                            .unwrap()
                            .parsed(&file.to_string_lossy(), report.unwrap());
                    }
                    store = true;
                }
                if store {
                    self.store(T::wrap(obj));
                }
            } else {
                if audit {
                    self.auditor
                        .as_mut()
                        .unwrap()
                        .skip(&file.to_string_lossy(), SkipReason::ParseFailed);
                }
                println!("Failed to decode object from {:?}", file);
            }
            if !pg(1) {
                println!("Cancelled");
                return;
            }
        }
    }

    /// Load albums.
    pub fn load_albums<F: FnMut(u64) -> bool>(&mut self, pg: &mut F) {
        if self.albums.is_empty() {
            let mut albums: HashSet<String> = HashSet::new();
            self.load_items::<Album, F>(ALBUMS_DIR, "apalbum", &mut albums, pg);
            self.albums = albums;
        }
    }

    /// Get albums uuids.
    pub fn get_albums(&self) -> &HashSet<String> {
        &self.albums
    }

    /// Load folders.
    pub fn load_folders<F: FnMut(u64) -> bool>(&mut self, pg: &mut F) {
        if self.folders.is_empty() {
            let mut folders: HashSet<String> = HashSet::new();
            self.load_items::<Folder, F>(FOLDERS_DIR, "apfolder", &mut folders, pg);
            self.folders = folders;
        }
    }

    /// Get folders uuids.
    pub fn get_folders(&self) -> &HashSet<String> {
        &self.folders
    }

    fn recurse_list_directory(path: &Path, level: i32) -> Vec<PathBuf> {
        let mut list: Vec<PathBuf> = Vec::new();
        for entry in fs::read_dir(path).unwrap() {
            let entry = entry.unwrap();
            if entry.metadata().unwrap().is_dir() {
                if level == 0 {
                    list.push(entry.path());
                } else {
                    let mut sublist = Library::recurse_list_directory(&entry.path(), level - 1);
                    list.append(&mut sublist)
                }
            }
        }

        list
    }

    fn list_versions_items_dirs(&self) -> Vec<PathBuf> {
        let ppath = self.build_path(VERSIONS_BASE_DIR, true);

        if !fs::metadata(&ppath).unwrap().is_dir() {
            // XXX return a Result
            return Vec::new();
        }

        Library::recurse_list_directory(&ppath, 4)
    }

    fn list_versions_items(&self, ext: &str) -> Vec<PathBuf> {
        let list = self.list_versions_items_dirs();
        let mut items = Vec::new();

        for dir in list {
            if !fs::metadata(&dir).unwrap().is_dir() {
                continue;
            }

            for entry in fs::read_dir(&dir).unwrap() {
                let entry = entry.unwrap();
                let p = entry.path();
                if p.extension().unwrap() == ext {
                    items.push(entry.path().to_owned());
                }
            }
        }

        items
    }

    fn load_versions_items<T, F>(&mut self, ext: &str, set: &mut HashSet<String>, pg: &mut F)
    where
        T: PlistLoadable + AplibObject,
        F: FnMut(u64) -> bool,
    {
        let file_list = self.list_versions_items(ext);
        let audit = self.auditor.is_some();
        for file in file_list {
            let mut report = if audit { Some(Report::new()) } else { None };
            if let Some(obj) = T::from_path(&file, report.as_mut()) {
                let mut store = false;
                if let Some(ref uuid) = *obj.uuid() {
                    set.insert(uuid.to_owned());
                    store = true;
                    if audit {
                        self.auditor
                            .as_mut()
                            .unwrap()
                            .parsed(&file.to_string_lossy(), report.unwrap());
                    }
                }
                if store {
                    self.store(T::wrap(obj));
                }
            } else {
                if audit {
                    self.auditor
                        .as_mut()
                        .unwrap()
                        .skip(&file.to_string_lossy(), SkipReason::ParseFailed);
                }
                println!("Error decoding object from {:?}", file);
            }
            if !pg(1) {
                println!("Cancelled!");
                break;
            }
        }
    }

    /// Load versions.
    pub fn load_versions<F: FnMut(u64) -> bool>(&mut self, pg: &mut F) {
        if self.versions.is_empty() {
            let mut versions: HashSet<String> = HashSet::new();
            self.load_versions_items::<Version, F>("apversion", &mut versions, pg);
            self.versions = versions;
        }
    }

    /// Load masters.
    pub fn load_masters<F: FnMut(u64) -> bool>(&mut self, pg: &mut F) {
        if self.masters.is_empty() {
            let mut masters: HashSet<String> = HashSet::new();
            self.load_versions_items::<Master, F>("apmaster", &mut masters, pg);
            self.masters = masters;
        }
    }

    /// Return masters uuids.
    pub fn get_masters(&self) -> &HashSet<String> {
        &self.masters
    }

    /// Return versions uuids.
    pub fn get_versions(&self) -> &HashSet<String> {
        &self.versions
    }

    /// List keywords.
    pub fn list_keywords(&mut self) -> Option<Vec<Keyword>> {
        let audit = self.auditor.is_some();
        let mut report = if audit { Some(Report::new()) } else { None };
        let result = parse_keywords(self.build_path(KEYWORDS_PLIST, true), &mut report.as_mut());
        if audit {
            if result.is_some() {
                self.auditor
                    .as_mut()
                    .unwrap()
                    .parsed(KEYWORDS_PLIST, report.unwrap());
            } else {
                self.auditor
                    .as_mut()
                    .unwrap()
                    .skip(KEYWORDS_PLIST, SkipReason::ParseFailed);
            }
        }
        result
    }
}
<|endoftext|>


<commit_before>//! This is a collection of string metrics that are suitable for use with a
//! BK-tree.

use std::cmp::min;

/// This calculates the Levenshtein distance between two strings.
///
/// The [distance metric itself][1] is calculated using the [Wagner-Fischer][2]
/// dynamic programming algorithm.
///
/// # Examples
///
/// ```
/// use bk_tree::metrics::levenshtein;
///
/// assert_eq!(levenshtein("bar", "baz"), 1);
/// assert_eq!(levenshtein("kitten", "sitting"), 3);
/// ```
///
/// [1]: https://en.wikipedia.org/wiki/Levenshtein_distance
/// [2]: https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
pub fn levenshtein(a: &str, b: &str) -> u64 {
    let len_a: u64 = a.chars().collect::<Vec<char>>().len() as u64;
    let len_b: u64 = b.chars().collect::<Vec<char>>().len() as u64;
    if len_a == 0 {
        return len_b;
    }
    if len_b == 0 {
        return len_a;
    }

    // This is a case-insensitive algorithm
    let a_lower = a.to_lowercase();
    let b_lower = b.to_lowercase();

    // Initialize the array
    let mut d: Vec<Vec<u64>> = Vec::new();
    for j in 0..(len_b + 1) {
        let mut cur_vec = Vec::new();
        for i in 0..(len_a + 1) {
            if j == 0 {
                cur_vec.push(i);
            } else if i == 0 {
                cur_vec.push(j);
            } else {
                cur_vec.push(0);
            }
        }
        d.push(cur_vec);
    }

    for j in 0..len_b as usize {
        for i in 0..len_a as usize {
            let chr_a = a_lower.chars().nth(i).unwrap();
            let chr_b = b_lower.chars().nth(j).unwrap();
            if chr_a == chr_b {
                // If they're the same, then don't modify the value
                d[j + 1][i + 1] = d[j][i];
            } else {
                // Otherwise, pick the lowest cost option for an error
                let deletion = d[j + 1][i] + 1;
                let insertion = d[j][i + 1] + 1;
                let substitution = d[j][i] + 1;
                d[j + 1][i + 1] = min(min(deletion, insertion), substitution);
            }
        }
    }

    d[len_b as usize][len_a as usize]
}
<commit_msg>Fix metrics::levenshtein to work with ToString instead of just &str<commit_after>//! This is a collection of string metrics that are suitable for use with a
//! BK-tree.

use std::cmp::min;

/// This calculates the Levenshtein distance between two strings.
///
/// The [distance metric itself][1] is calculated using the [Wagner-Fischer][2]
/// dynamic programming algorithm.
///
/// # Examples
///
/// ```
/// use bk_tree::metrics::levenshtein;
///
/// assert_eq!(levenshtein("bar", "baz"), 1);
/// assert_eq!(levenshtein("kitten", "sitting"), 3);
/// ```
///
/// [1]: https://en.wikipedia.org/wiki/Levenshtein_distance
/// [2]: https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
pub fn levenshtein<S: ToString>(a: S, b: S) -> u64 {
    let str_a: &str = &a.to_string();
    let str_b: &str = &b.to_string();

    let len_a: u64 = str_a.chars().collect::<Vec<char>>().len() as u64;
    let len_b: u64 = str_b.chars().collect::<Vec<char>>().len() as u64;
    if len_a == 0 {
        return len_b;
    }
    if len_b == 0 {
        return len_a;
    }

    // This is a case-insensitive algorithm
    let a_lower = str_a.to_lowercase();
    let b_lower = str_b.to_lowercase();

    // Initialize the array
    let mut d: Vec<Vec<u64>> = Vec::new();
    for j in 0..(len_b + 1) {
        let mut cur_vec = Vec::new();
        for i in 0..(len_a + 1) {
            if j == 0 {
                cur_vec.push(i);
            } else if i == 0 {
                cur_vec.push(j);
            } else {
                cur_vec.push(0);
            }
        }
        d.push(cur_vec);
    }

    for j in 0..len_b as usize {
        for i in 0..len_a as usize {
            let chr_a = a_lower.chars().nth(i).unwrap();
            let chr_b = b_lower.chars().nth(j).unwrap();
            if chr_a == chr_b {
                // If they're the same, then don't modify the value
                d[j + 1][i + 1] = d[j][i];
            } else {
                // Otherwise, pick the lowest cost option for an error
                let deletion = d[j + 1][i] + 1;
                let insertion = d[j][i + 1] + 1;
                let substitution = d[j][i] + 1;
                d[j + 1][i + 1] = min(min(deletion, insertion), substitution);
            }
        }
    }

    d[len_b as usize][len_a as usize]
}
<|endoftext|>