<commit_before>// This program watches a configured directory for incoming files.
// When a file is detected the following procedure is used to handle it:
//
//  * First we digest the file, and add the digest to our manifest.
//    This way we can intelligently handle future events for this file
//
//  * Next we attempt to detect a mime type using various heuristics, incl:
//
//    - Quick detection w/ our magic bytes table
//    - Querying external tools (e.g: ffmpeg, imagemagick, et al.)
//    - etc.
//
//  * If we cannot handle the mime type: the file is moved to an exception
//    directory, and an error is logged somewhere the user will see it.
//
//  * If we can handle the mime type: the file is moved to the content store
//    and an entry is created in the database.
//
//    - This should ideally be done atomically so that other aqua utility
//      processes (e.g: sister agnes) don't mistakenly mark the file as an
//      orphan / missing / etc.
//

#[macro_use] extern crate log;
#[macro_use] extern crate serde_derive;

extern crate aqua;
extern crate clap;
extern crate diesel;
extern crate dotenv;
extern crate env_logger;
extern crate image;
extern crate notify;
extern crate serde;
extern crate serde_json;

use aqua::controllers::prelude::hash_file;
use aqua::models::{Entry, NewEntry};
use aqua::schema;
use clap::{Arg, App};
use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use notify::{DebouncedEvent, Watcher, RecursiveMode, watcher};
use std::{env, fmt, process};
use std::collections::HashMap;
use std::convert::From;
use std::error::Error;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::time::Duration;

#[derive(Debug)]
enum ProcessingError {
    DigestFailed,
    DetectionFailed,
    ThumbnailFailed,

    DbConnErr(diesel::ConnectionError),
    DbQueryErr(diesel::result::Error),
    IoErr(io::Error),
    ImageErr(image::ImageError),
    Misc(Box<Error>),
}

impl Error for ProcessingError {
    fn description(&self) -> &str {
        match *self {
            // internal errors
            ProcessingError::DigestFailed      => "Unhandled error while generating SHA256 digest",
            ProcessingError::DetectionFailed   => "The file's type could not be detected",
            ProcessingError::ThumbnailFailed   => "The thumbnail could not be generated",

            // external errors
            ProcessingError::DbConnErr(ref inner)  => inner.description(),
            ProcessingError::DbQueryErr(ref inner) => inner.description(),
            ProcessingError::IoErr(ref inner)      => inner.description(),
            ProcessingError::ImageErr(ref inner)   => inner.description(),
            ProcessingError::Misc(ref inner)       => inner.description(),
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            ProcessingError::DbConnErr(ref err)  => Some(err),
            ProcessingError::DbQueryErr(ref err) => Some(err),
            ProcessingError::IoErr(ref err)      => Some(err),
            ProcessingError::ImageErr(ref err)   => Some(err),
            ProcessingError::Misc(ref err)       => Some(err.as_ref()),
            _ => None,
        }
    }
}

impl fmt::Display for ProcessingError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            // TODO: better display impl.
            _ => write!(f, "{}", self.description()),
        }
    }
}

impl From<diesel::ConnectionError> for ProcessingError {
    fn from(err: diesel::ConnectionError) -> Self { ProcessingError::DbConnErr(err) }
}

impl From<diesel::result::Error> for ProcessingError {
    fn from(err: diesel::result::Error) -> Self { ProcessingError::DbQueryErr(err) }
}

impl From<image::ImageError> for ProcessingError {
    fn from(err: image::ImageError) -> Self { ProcessingError::ImageErr(err) }
}

impl From<io::Error> for ProcessingError {
    fn from(err: io::Error) -> Self { ProcessingError::IoErr(err) }
}

impl From<serde_json::Error> for ProcessingError {
    fn from(err: serde_json::Error) -> Self { ProcessingError::Misc(Box::new(err)) }
}

impl From<std::string::FromUtf8Error> for ProcessingError {
    fn from(err: std::string::FromUtf8Error) -> Self { ProcessingError::Misc(Box::new(err)) }
}

fn main() {
    dotenv().expect("must provide .env file, see README (TODO: haha jk)");
    env_logger::init().expect("could not initialize console logging");

    // read command line arguments
    let matches = App::new("aqua-watch")
        .version("0.1.0")
        .author("himechi <hime@localhost>")
        .about("Watches a directory for new files and moves them to the `aqua` content store.")
        .arg(Arg::with_name("INPUT")
             .help("Determines the input directory to be watched.")
             .required(true)
             .index(1))
        .arg(Arg::with_name("OUTPUT")
             .help("The root of the aqua content store where files will be moved.")
             .required(true)
             .index(2))
        .get_matches();

    let dropbox_path  = matches.value_of("INPUT").unwrap();
    let content_store = matches.value_of("OUTPUT").unwrap();

    // setup fs watcher
    let (fs_tx, fs_rx) = channel();
    let mut fs_watcher = watcher(fs_tx, Duration::from_millis(1000))
        .expect("could not create file system watcher!");

    fs_watcher.watch(dropbox_path, RecursiveMode::NonRecursive)
        .expect("could not enroll dropbox in fs events queue");

    // process filesystem events ...
    loop {
        match fs_rx.recv() {
            Ok(DebouncedEvent::Create(path)) => {
                if path.is_file() {
                    match handle_new_file(path, content_store) {
                        Ok(_res) => info!("file processed successfully ..."),
                        Err(msg) => warn!("could not process file: {:?} (inner: {:?})", msg, msg.cause()),
                    };
                }
                else { info!("directory created, ignoring ..."); }
            },
            Ok(event) => info!("unhandled evt: {:?}", event),
            Err(msg) => warn!("fs err: {}", msg),
        }
    }
}

fn handle_new_file(path: PathBuf, content_store: &str) -> Result<(), ProcessingError> {
    let digest = hash_file(path.as_path())
        .ok_or(ProcessingError::DigestFailed)?;

    let mut file = OpenOptions::new()
        .read(true)
        .write(false)
        .create_new(false)
        .open(path.as_path())?;

    // read file into memory
    let mut buf = vec![];
    file.read_to_end(&mut buf)?;

    if let Some(image_metadata) = aqua::util::mime_detect(&buf) {
        info!("got an image ...");
        process_image(content_store, &digest, &buf)?;
        move_file(path.as_path(), content_store, &digest, image_metadata.extension())?;

        let db_entry = create_db_entry(&digest, image_metadata.mime())?;
        info!("inserted: {:?} into database", db_entry);

        Ok(())
    } else if let Some(ffmpeg_metadata) = ffmpeg_detect(path.as_path())? {
        info!("got an video ...");
        process_video(content_store, &digest, &path)?;
        move_file(path.as_path(), content_store, &digest, ffmpeg_metadata.ext)?;

        let db_entry = create_db_entry(&digest, ffmpeg_metadata.mime)?;
        info!("inserted: {:?} into database", db_entry);

        Ok(())
    } else {
        Err(ProcessingError::DetectionFailed)
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct FFProbeResult {
    format: Option<FFProbeFormat>,
    streams: Option<Vec<FFProbeStream>>,
}

#[derive(Debug, Serialize, Deserialize)]
struct FFProbeFormat {
    filename:    String,
    nb_streams:  i32,
    format_name: String,
    start_time:  String, // NOTE: these appear to be fixed decimal
    duration:    String, // NOTE: these appear to be fixed decimal
    size:        String, // NOTE: appears to be an integer
    bit_rate:    String, // NOTE: appears to be an integer
    probe_score: i32,    // NOTE: accuracy of the detection? (???)

    tags: HashMap<String, String>, // NOTE: not sure this is correct type
}

#[derive(Debug, Serialize, Deserialize)]
struct FFProbeStream {
    codec_name: String,
    codec_type: String,
}

struct FFProbeMeta {
    pub mime: &'static str,
    pub ext:  &'static str,
}

fn ffmpeg_detect(path: &Path) -> Result<Option<FFProbeMeta>, ProcessingError> {
    let ffprobe_cmd = process::Command::new("ffprobe")
        .arg("-v").arg("quiet")            // silence debug output
        .arg("-hide_banner")               // don't print ffmpeg configuration
        .arg("-print_format").arg("json") // serialize to json
        .arg("-show_format")              // display format data
        .arg("-show_streams")             // display stream data
        .arg("-i").arg(path.as_os_str())  // set the input to current file
        .output()?;

    let json_str = String::from_utf8(ffprobe_cmd.stdout)?;
    let probe_result: FFProbeResult = serde_json::from_str(&json_str)?;
    info!("got result: {:?}", probe_result);

    // see if ffprobe was able to determine the file type ...
    let probe_format = match probe_result.format {
        Some(format_data) => format_data,
        None => return Ok(None),
    };

    let probe_streams = match probe_result.streams {
        Some(stream_data) => stream_data,
        None => return Ok(None),
    };

    // welp ... guess there's nothing to thumbnail (!!!)
    info!("got format: {:?}", probe_format);
    info!("got streams: {:?}", probe_streams);

    let number_of_videos = probe_streams
        .iter()
        .filter(|el| el.codec_type == "video")
        .count();

    if number_of_videos <= 0 { return Err(ProcessingError::DetectionFailed) }

    let meta_data = if probe_format.format_name.contains("matroska") {
        Some(FFProbeMeta { mime: "video/x-matroska", ext: "mkv" })
    } else if probe_format.format_name.contains("mp4") {
        Some(FFProbeMeta { mime: "video/mp4", ext: "mp4" })
    } else { None };

    Ok(meta_data)
}

fn establish_connection() -> Result<PgConnection, ProcessingError> {
    let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL not set in `.env` file !!!");

    Ok(PgConnection::establish(&database_url)?)
}

// create entry in database
fn create_db_entry(digest: &str, mime_ty: &str) -> Result<Entry, ProcessingError> {
    let pg_conn = establish_connection()?;
    let aqua_entry = NewEntry { hash: &digest, mime: Some(&mime_ty) };
    let entry: Result<Entry, diesel::result::Error> = diesel::insert(&aqua_entry)
        .into(schema::entries::table)
        .get_result(&pg_conn);

    Ok(entry?)
}

// moves the file from `src_path` to the `content_store` based on its digest
fn move_file(src_path: &Path, content_store: &str, digest: &str, file_ext: &str) -> Result<(), ProcessingError> {
    // carve out a bucket based on first byte of SHA256 digest
    // create the bucket if it does not exist
    let file_bucket    = format!("f{}", &digest[0..2]);
    let file_filename  = format!("{}.{}", &digest, file_ext);

    // create destination path
    let dest = PathBuf::from(content_store)
        .join(file_bucket)
        .join(file_filename);

    // TODO: bad error type ...
    let bucket_dir = dest.parent().ok_or(ProcessingError::ThumbnailFailed)?;
    fs::create_dir_all(bucket_dir)?;

    // move the file
    Ok(fs::rename(src_path, &dest)?)
}

// creates a thumbnail in the content store for the specified digest
// this expects an `ImageMeta` structure describing the input.
fn process_image(content_store: &str, digest: &str, buf: &[u8]) -> Result<(), ProcessingError> {
    // create in memory thumbnail
    let image = image::load_from_memory(&buf)?;

    let thumb = image.resize(200, 200, image::FilterType::Nearest);
    let thumb_bucket   = format!("t{}", &digest[0..2]);
    let thumb_filename = format!("{}.thumbnail", &digest);

    // store them in content store
    let dest = PathBuf::from(content_store)
        .join(thumb_bucket)
        .join(thumb_filename);

    // write thumbnail file to disk
    fs::create_dir_all(dest.parent().unwrap())?;
    let mut dest_file = File::create(dest)?;
    thumb.save(&mut dest_file, image::ImageFormat::JPEG)?;
    Ok(dest_file.flush()?)
}

fn process_video(content_store: &str, digest: &str, src: &Path) -> Result<(), ProcessingError> {
    let thumb_bucket   = format!("t{}", &digest[0..2]);
    let thumb_filename = format!("{}.thumbnail", &digest);

    // store them in content store
    let dest = PathBuf::from(content_store)
        .join(thumb_bucket)
        .join(thumb_filename);

    // write thumbnail file to disk
    fs::create_dir_all(dest.parent().unwrap())?;
    let ffmpeg_cmd = process::Command::new("ffmpeg")
        .arg("-i").arg(src.as_os_str())            // the input file
        .arg("-vf").arg("thumbnail,scale=200:200") // have ffmpeg seek for a "thumbnail"
        .arg("-frames:v").arg("1")                 // take a single frame
        .arg("-f").arg("mjpeg")                    // save it as jpeg
        .arg(dest.as_path().as_os_str())           // into the content store
        .output()?;

    debug!("ffmpeg stderr: {}", String::from_utf8_lossy(&ffmpeg_cmd.stderr));
    debug!("ffmpeg stdout: {}", String::from_utf8_lossy(&ffmpeg_cmd.stdout));

    info!("digest? {}", digest);
    info!("dest exists? {:?} => {}", dest, dest.is_file());

    match dest.is_file() {
        true  => Ok(()),
        false => Err(ProcessingError::ThumbnailFailed),
    }
}
<commit_msg>(doc) misc notes about aqua watch<commit_after>// This program watches a configured directory for incoming files.
// When a file is detected the following procedure is used to handle it:
//
//  * First we digest the file, and add the digest to our manifest.
//    This way we can intelligently handle future events for this file
//
//  * Next we attempt to detect a mime type using various heuristics, incl:
//
//    - Quick detection w/ our magic bytes table
//    - Querying external tools (e.g: ffmpeg, imagemagick, et al.)
//    - etc.
//
//  * If we cannot handle the mime type: the file is moved to an exception
//    directory, and an error is logged somewhere the user will see it.
//
//  * If we can handle the mime type: the file is moved to the content store
//    and an entry is created in the database.
//
//    - This should ideally be done atomically so that other aqua utility
//      processes (e.g: sister agnes) don't mistakenly mark the file as an
//      orphan / missing / etc.
//

#[macro_use] extern crate log;
#[macro_use] extern crate serde_derive;

extern crate aqua;
extern crate clap;
extern crate diesel;
extern crate dotenv;
extern crate env_logger;
extern crate image;
extern crate notify;
extern crate serde;
extern crate serde_json;

use aqua::controllers::prelude::hash_file;
use aqua::models::{Entry, NewEntry};
use aqua::schema;
use clap::{Arg, App};
use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use notify::{DebouncedEvent, Watcher, RecursiveMode, watcher};
use std::{env, fmt, process};
use std::collections::HashMap;
use std::convert::From;
use std::error::Error;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::time::Duration;

#[derive(Debug)]
enum ProcessingError {
    DigestFailed,
    DetectionFailed,
    ThumbnailFailed,

    DbConnErr(diesel::ConnectionError),
    DbQueryErr(diesel::result::Error),
    IoErr(io::Error),
    ImageErr(image::ImageError),
    Misc(Box<Error>),
}

impl Error for ProcessingError {
    fn description(&self) -> &str {
        match *self {
            // internal errors
            ProcessingError::DigestFailed      => "Unhandled error while generating SHA256 digest",
            ProcessingError::DetectionFailed   => "The file's type could not be detected",
            ProcessingError::ThumbnailFailed   => "The thumbnail could not be generated",

            // external errors
            ProcessingError::DbConnErr(ref inner)  => inner.description(),
            ProcessingError::DbQueryErr(ref inner) => inner.description(),
            ProcessingError::IoErr(ref inner)      => inner.description(),
            ProcessingError::ImageErr(ref inner)   => inner.description(),
            ProcessingError::Misc(ref inner)       => inner.description(),
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            ProcessingError::DbConnErr(ref err)  => Some(err),
            ProcessingError::DbQueryErr(ref err) => Some(err),
            ProcessingError::IoErr(ref err)      => Some(err),
            ProcessingError::ImageErr(ref err)   => Some(err),
            ProcessingError::Misc(ref err)       => Some(err.as_ref()),
            _ => None,
        }
    }
}

impl fmt::Display for ProcessingError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            // TODO: better display impl.
            _ => write!(f, "{}", self.description()),
        }
    }
}

impl From<diesel::ConnectionError> for ProcessingError {
    fn from(err: diesel::ConnectionError) -> Self { ProcessingError::DbConnErr(err) }
}

impl From<diesel::result::Error> for ProcessingError {
    fn from(err: diesel::result::Error) -> Self { ProcessingError::DbQueryErr(err) }
}

impl From<image::ImageError> for ProcessingError {
    fn from(err: image::ImageError) -> Self { ProcessingError::ImageErr(err) }
}

impl From<io::Error> for ProcessingError {
    fn from(err: io::Error) -> Self { ProcessingError::IoErr(err) }
}

impl From<serde_json::Error> for ProcessingError {
    fn from(err: serde_json::Error) -> Self { ProcessingError::Misc(Box::new(err)) }
}

impl From<std::string::FromUtf8Error> for ProcessingError {
    fn from(err: std::string::FromUtf8Error) -> Self { ProcessingError::Misc(Box::new(err)) }
}

fn main() {
    dotenv().expect("must provide .env file, see README (TODO: haha jk)");
    env_logger::init().expect("could not initialize console logging");

    // read command line arguments
    let matches = App::new("aqua-watch")
        .version("0.1.0")
        .author("himechi <hime@localhost>")
        .about("Watches a directory for new files and moves them to the `aqua` content store.")
        .arg(Arg::with_name("INPUT")
             .help("Determines the input directory to be watched.")
             .required(true)
             .index(1))
        .arg(Arg::with_name("OUTPUT")
             .help("The root of the aqua content store where files will be moved.")
             .required(true)
             .index(2))
        .get_matches();

    let dropbox_path  = matches.value_of("INPUT").unwrap();
    let content_store = matches.value_of("OUTPUT").unwrap();

    // setup fs watcher
    let (fs_tx, fs_rx) = channel();
    let mut fs_watcher = watcher(fs_tx, Duration::from_millis(1000))
        .expect("could not create file system watcher!");

    fs_watcher.watch(dropbox_path, RecursiveMode::NonRecursive)
        .expect("could not enroll dropbox in fs events queue");

    // process filesystem events ...
    loop {
        match fs_rx.recv() {
            Ok(DebouncedEvent::Create(path)) => {
                if path.is_file() {
                    match handle_new_file(path, content_store) {
                        Ok(_res) => info!("file processed successfully ..."),
                        Err(msg) => warn!("could not process file: {:?} (inner: {:?})", msg, msg.cause()),
                    };
                }
                else { info!("directory created, ignoring ..."); }
            },
            Ok(event) => info!("unhandled evt: {:?}", event),
            Err(msg) => warn!("fs err: {}", msg),
        }
    }
}

fn handle_new_file(path: PathBuf, content_store: &str) -> Result<(), ProcessingError> {
    let digest = hash_file(path.as_path())
        .ok_or(ProcessingError::DigestFailed)?;

    let mut file = OpenOptions::new()
        .read(true)
        .write(false)
        .create_new(false)
        .open(path.as_path())?;

    // read file into memory
    let mut buf = vec![];
    file.read_to_end(&mut buf)?;

    // TODO: move_file() & db() is probably going to be common to all handlers?
    if let Some(image_metadata) = aqua::util::mime_detect(&buf) {
        info!("got an image ...");
        process_image(content_store, &digest, &buf)?;
        move_file(path.as_path(), content_store, &digest, image_metadata.extension())?;

        let db_entry = create_db_entry(&digest, image_metadata.mime())?;
        info!("inserted: {:?} into database", db_entry);

        Ok(())
    } else if let Some(ffmpeg_metadata) = ffmpeg_detect(path.as_path())? {
        info!("got an video ...");
        process_video(content_store, &digest, &path)?;
        move_file(path.as_path(), content_store, &digest, ffmpeg_metadata.ext)?;

        let db_entry = create_db_entry(&digest, ffmpeg_metadata.mime)?;
        info!("inserted: {:?} into database", db_entry);

        Ok(())
    } else {
        Err(ProcessingError::DetectionFailed)
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct FFProbeResult {
    format: Option<FFProbeFormat>,
    streams: Option<Vec<FFProbeStream>>,
}

#[derive(Debug, Serialize, Deserialize)]
struct FFProbeFormat {
    filename:    String,
    nb_streams:  i32,
    format_name: String,
    start_time:  String, // NOTE: these appear to be fixed decimal
    duration:    String, // NOTE: these appear to be fixed decimal
    size:        String, // NOTE: appears to be an integer
    bit_rate:    String, // NOTE: appears to be an integer
    probe_score: i32,    // NOTE: accuracy of the detection? (???)

    tags: HashMap<String, String>, // NOTE: not sure this is correct type
}

#[derive(Debug, Serialize, Deserialize)]
struct FFProbeStream {
    codec_name: String,
    codec_type: String,
}

struct FFProbeMeta {
    pub mime: &'static str,
    pub ext:  &'static str,
}

fn ffmpeg_detect(path: &Path) -> Result<Option<FFProbeMeta>, ProcessingError> {
    let ffprobe_cmd = process::Command::new("ffprobe")
        .arg("-v").arg("quiet")            // silence debug output
        .arg("-hide_banner")               // don't print ffmpeg configuration
        .arg("-print_format").arg("json") // serialize to json
        .arg("-show_format")              // display format data
        .arg("-show_streams")             // display stream data
        .arg("-i").arg(path.as_os_str())  // set the input to current file
        .output()?;

    let json_str = String::from_utf8(ffprobe_cmd.stdout)?;
    let probe_result: FFProbeResult = serde_json::from_str(&json_str)?;
    info!("got result: {:?}", probe_result);

    // see if ffprobe was able to determine the file type ...
    let probe_format = match probe_result.format {
        Some(format_data) => format_data,
        None => return Ok(None),
    };

    let probe_streams = match probe_result.streams {
        Some(stream_data) => stream_data,
        None => return Ok(None),
    };

    // welp ... guess there's nothing to thumbnail (!!!)
    info!("got format: {:?}", probe_format);
    info!("got streams: {:?}", probe_streams);

    let number_of_videos = probe_streams
        .iter()
        .filter(|el| el.codec_type == "video")
        .count();

    if number_of_videos <= 0 { return Err(ProcessingError::DetectionFailed) }

    // TODO: how do we correlate format_name w/ stream & stream position?
    // TODO: I believe this should be matching on containers (which is what will be moved
    //       to the content store; and therefore what will be played back ...)
    //
    let meta_data = if probe_format.format_name.contains("matroska") {
        Some(FFProbeMeta { mime: "video/x-matroska", ext: "mkv" })
    } else if probe_format.format_name.contains("mp4") {
        Some(FFProbeMeta { mime: "video/mp4", ext: "mp4" })
    } else { None };

    Ok(meta_data)
}

fn establish_connection() -> Result<PgConnection, ProcessingError> {
    let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL not set in `.env` file !!!");

    Ok(PgConnection::establish(&database_url)?)
}

// create entry in database
fn create_db_entry(digest: &str, mime_ty: &str) -> Result<Entry, ProcessingError> {
    let pg_conn = establish_connection()?;
    let aqua_entry = NewEntry { hash: &digest, mime: Some(&mime_ty) };
    let entry: Result<Entry, diesel::result::Error> = diesel::insert(&aqua_entry)
        .into(schema::entries::table)
        .get_result(&pg_conn);

    Ok(entry?)
}

// moves the file from `src_path` to the `content_store` based on its digest
fn move_file(src_path: &Path, content_store: &str, digest: &str, file_ext: &str) -> Result<(), ProcessingError> {
    // carve out a bucket based on first byte of SHA256 digest
    // create the bucket if it does not exist
    let file_bucket    = format!("f{}", &digest[0..2]);
    let file_filename  = format!("{}.{}", &digest, file_ext);

    // create destination path
    let dest = PathBuf::from(content_store)
        .join(file_bucket)
        .join(file_filename);

    // TODO: bad error type ...
    let bucket_dir = dest.parent().ok_or(ProcessingError::ThumbnailFailed)?;
    fs::create_dir_all(bucket_dir)?;

    // move the file
    Ok(fs::rename(src_path, &dest)?)
}

// creates a thumbnail in the content store for the specified digest
// this expects an `ImageMeta` structure describing the input.
fn process_image(content_store: &str, digest: &str, buf: &[u8]) -> Result<(), ProcessingError> {
    // create in memory thumbnail
    let image = image::load_from_memory(&buf)?;

    let thumb = image.resize(200, 200, image::FilterType::Nearest);
    let thumb_bucket   = format!("t{}", &digest[0..2]);
    let thumb_filename = format!("{}.thumbnail", &digest);

    // store them in content store
    let dest = PathBuf::from(content_store)
        .join(thumb_bucket)
        .join(thumb_filename);

    // write thumbnail file to disk
    fs::create_dir_all(dest.parent().unwrap())?;
    let mut dest_file = File::create(dest)?;
    thumb.save(&mut dest_file, image::ImageFormat::JPEG)?;
    Ok(dest_file.flush()?)
}

fn process_video(content_store: &str, digest: &str, src: &Path) -> Result<(), ProcessingError> {
    let thumb_bucket   = format!("t{}", &digest[0..2]);
    let thumb_filename = format!("{}.thumbnail", &digest);

    // store them in content store
    let dest = PathBuf::from(content_store)
        .join(thumb_bucket)
        .join(thumb_filename);

    // TODO: seems weird to have "mjpeg" in here... but I couldn't find any other
    //       JPEG muxer/encoder in my ffmpeg install ...
    //
    // write thumbnail file to disk
    fs::create_dir_all(dest.parent().unwrap())?;
    let ffmpeg_cmd = process::Command::new("ffmpeg")
        .arg("-i").arg(src.as_os_str())            // the input file
        .arg("-vf").arg("thumbnail,scale=200:200") // have ffmpeg seek for a "thumbnail"
        .arg("-frames:v").arg("1")                 // take a single frame
        .arg("-f").arg("mjpeg")                    // save it as jpeg
        .arg(dest.as_path().as_os_str())           // into the content store
        .output()?;

    debug!("ffmpeg stderr: {}", String::from_utf8_lossy(&ffmpeg_cmd.stderr));
    debug!("ffmpeg stdout: {}", String::from_utf8_lossy(&ffmpeg_cmd.stdout));

    info!("digest? {}", digest);
    info!("dest exists? {:?} => {}", dest, dest.is_file());

    match dest.is_file() {
        true  => Ok(()),
        false => Err(ProcessingError::ThumbnailFailed),
    }
}
<|endoftext|>


<commit_before>use crate::models::IsoKey;
use serde::{
    de::{self, Deserializer},
    Deserialize, Serialize,
};
use snafu::Snafu;
use std::{collections::BTreeMap, str::FromStr};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct DesktopKeyMap(BTreeMap<IsoKey, String>);

impl<'de> Deserialize<'de> for DesktopKeyMap {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Debug, Clone, PartialEq, Eq)]
        #[derive(Serialize, Deserialize)]
        #[serde(untagged)]
        enum Wat {
            String(String),
            Map(BTreeMap<IsoKey, String>),
        }

        match Wat::deserialize(deserializer)? {
            Wat::String(s) => Ok(s.parse().map_err(de::Error::custom)?),
            Wat::Map(m) => Ok(DesktopKeyMap(m)),
        }
    }
}

impl FromStr for DesktopKeyMap {
    type Err = Error;

    // TODO: Implement this, actually
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(DesktopKeyMap(BTreeMap::new()))
    }
}

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("{}", description))]
    ParseError { description: String },
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct MobileKeyMap(Vec<Vec<String>>);

impl<'de> Deserialize<'de> for MobileKeyMap {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Ok(MobileKeyMap(
            s.lines()
                .map(|l| l.split_whitespace().map(String::from).collect())
                .collect(),
        ))
    }
}
<commit_msg>Implement key map "parser"<commit_after>use crate::models::IsoKey;
use serde::{
    de::{self, Deserializer},
    Deserialize, Serialize,
};
use snafu::Snafu;
use std::{collections::BTreeMap, str::FromStr};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct DesktopKeyMap(BTreeMap<IsoKey, String>);

impl<'de> Deserialize<'de> for DesktopKeyMap {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Debug, Clone, PartialEq, Eq)]
        #[derive(Serialize, Deserialize)]
        #[serde(untagged)]
        enum Wat {
            String(String),
            Map(BTreeMap<IsoKey, String>),
        }

        match Wat::deserialize(deserializer)? {
            Wat::String(s) => Ok(s.parse().map_err(de::Error::custom)?),
            Wat::Map(m) => Ok(DesktopKeyMap(m)),
        }
    }
}

impl FromStr for DesktopKeyMap {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use strum::IntoEnumIterator;

        Ok(DesktopKeyMap(
            s.lines()
                .flat_map(|l| l.split_whitespace().map(String::from))
                .zip(IsoKey::iter())
                .map(|(val, key)| (key, val))
                .collect(),
        ))
    }
}

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("{}", description))]
    ParseError { description: String },
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct MobileKeyMap(Vec<Vec<String>>);

impl<'de> Deserialize<'de> for MobileKeyMap {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Ok(MobileKeyMap(
            s.lines()
                .map(|l| l.split_whitespace().map(String::from).collect())
                .collect(),
        ))
    }
}
<|endoftext|>


<commit_before>use std::collections::hash_map::{HashMap, Entry};
use std::hash::Hash;
use std::fmt;
use std::io;
use std::net::SocketAddr;

use rotor::mio::tcp::TcpStream;
use url::Url;

use net::{HttpStream, HttpsStream, Transport, SslClient};
use super::dns::Dns;
use super::Registration;

/// A connector creates a Transport to a remote address..
pub trait Connect {
    /// Type of Transport to create
    type Output: Transport;
    /// The key used to determine if an existing socket can be used.
    type Key: Eq + Hash + Clone + fmt::Debug;
    /// Returns the key based off the Url.
    fn key(&self, &Url) -> Option<Self::Key>;
    /// Connect to a remote address.
    fn connect(&mut self, &Url) -> io::Result<Self::Key>;
    /// Returns a connected socket and associated host.
    fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)>;
    #[doc(hidden)]
    /// Configure number of dns workers to use.
    fn dns_workers(&mut self, usize);
    #[doc(hidden)]
    fn register(&mut self, Registration);
}

type Scheme = String;
type Port = u16;

/// A connector for the `http` scheme.
pub struct HttpConnector {
    dns: Option<Dns>,
    threads: usize,
    resolving: HashMap<String, Vec<(&'static str, String, u16)>>,
}

impl HttpConnector {
    /// Set the number of resolver threads.
    ///
    /// Default is 4.
    pub fn threads(mut self, threads: usize) -> HttpConnector {
        debug_assert!(self.dns.is_none(), "setting threads after Dns is created does nothing");
        self.threads = threads;
        self
    }
}

impl Default for HttpConnector {
    fn default() -> HttpConnector {
        HttpConnector {
            dns: None,
            threads: 4,
            resolving: HashMap::new(),
        }
    }
}

impl fmt::Debug for HttpConnector {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("HttpConnector")
            .field("threads", &self.threads)
            .field("resolving", &self.resolving)
            .finish()
    }
}

impl Connect for HttpConnector {
    type Output = HttpStream;
    type Key = (&'static str, String, u16);

    fn dns_workers(&mut self, count: usize) {
        self.threads = count;
    }

    fn key(&self, url: &Url) -> Option<Self::Key> {
        if url.scheme() == "http" {
            Some((
                "http",
                url.host_str().expect("http scheme must have host").to_owned(),
                url.port().unwrap_or(80),
            ))
        } else {
            None
        }
    }

    fn connect(&mut self, url: &Url) -> io::Result<Self::Key> {
        debug!("Http::connect({:?})", url);
        if let Some(key) = self.key(url) {
            let host = url.host_str().expect("http scheme must have a host");
            self.dns.as_ref().expect("dns workers lost").resolve(host);
            self.resolving.entry(host.to_owned()).or_insert_with(Vec::new).push(key.clone());
            Ok(key)
        } else {
            Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http"))
        }
    }

    fn connected(&mut self) -> Option<(Self::Key, io::Result<HttpStream>)> {
        let (host, addrs) = match self.dns.as_ref().expect("dns workers lost").resolved() {
            Ok(res) => res,
            Err(_) => return None
        };
        //TODO: try all addrs
        let addr = addrs.and_then(|mut addrs| Ok(addrs.next().unwrap()));
        debug!("Http::resolved <- ({:?}, {:?})", host, addr);
        if let Entry::Occupied(mut entry) = self.resolving.entry(host) {
            let resolved = entry.get_mut().remove(0);
            if entry.get().is_empty() {
                entry.remove();
            }
            let port = resolved.2;
            Some((resolved, addr.and_then(|addr| TcpStream::connect(&SocketAddr::new(addr, port))
                                                            .map(HttpStream))
                ))
        } else {
            trace!("^--  resolved but not in hashmap?");
            None
        }
    }

    fn register(&mut self, reg: Registration) {
        self.dns = Some(Dns::new(reg.notify, self.threads));
    }
}

/// A connector that can protect HTTP streams using SSL.
#[derive(Debug, Default)]
pub struct HttpsConnector<S: SslClient> {
    http: HttpConnector,
    ssl: S
}

impl<S: SslClient> HttpsConnector<S> {
    /// Create a new connector using the provided SSL implementation.
    pub fn new(s: S) -> HttpsConnector<S> {
        HttpsConnector {
            http: HttpConnector::default(),
            ssl: s,
        }
    }
}

impl<S: SslClient> Connect for HttpsConnector<S> {
    type Output = HttpsStream<S::Stream>;
    type Key = (&'static str, String, u16);

    fn dns_workers(&mut self, count: usize) {
        self.http.dns_workers(count)
    }

    fn key(&self, url: &Url) -> Option<Self::Key> {
        let scheme = match url.scheme() {
            "http" => "http",
            "https" => "https",
            _ => return None
        };
        Some((
            scheme,
            url.host_str().expect("http scheme must have host").to_owned(),
            url.port_or_known_default().expect("http scheme must have a port"),
        ))
    }

    fn connect(&mut self, url: &Url) -> io::Result<Self::Key> {
        debug!("Https::connect({:?})", url);
        if let Some(key) = self.key(url) {
            let host = url.host_str().expect("http scheme must have a host");
            self.http.dns.as_ref().expect("dns workers lost").resolve(host);
            self.http.resolving.entry(host.to_owned()).or_insert_with(Vec::new).push(key.clone());
            Ok(key)
        } else {
            Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http or https"))
        }
    }

    fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)> {
        self.http.connected().map(|(key, res)| {
            let res = res.and_then(|http| {
                if key.0 == "https" {
                    self.ssl.wrap_client(http, &key.1)
                        .map(HttpsStream::Https)
                        .map_err(|e| match e {
                            ::Error::Io(e) => e,
                            e => io::Error::new(io::ErrorKind::Other, e)
                        })
                } else {
                    Ok(HttpsStream::Http(http))
                }
            });
            (key, res)
        })
    }

    fn register(&mut self, reg: Registration) {
        self.http.register(reg);
    }
}

#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
#[doc(hidden)]
pub type DefaultConnector = HttpConnector;

#[cfg(all(feature = "openssl", not(feature = "security-framework")))]
#[doc(hidden)]
pub type DefaultConnector = HttpsConnector<::net::Openssl>;

#[cfg(feature = "security-framework")]
#[doc(hidden)]
pub type DefaultConnector = HttpsConnector<::net::SecureTransportClient>;

#[doc(hidden)]
pub type DefaultTransport = <DefaultConnector as Connect>::Output;

fn _assert_defaults() {
    fn _assert<T, U>() where T: Connect<Output=U>, U: Transport {}

    _assert::<DefaultConnector, DefaultTransport>();
}
<commit_msg>refactor(client): remove unused client type aliases<commit_after>use std::collections::hash_map::{HashMap, Entry};
use std::hash::Hash;
use std::fmt;
use std::io;
use std::net::SocketAddr;

use rotor::mio::tcp::TcpStream;
use url::Url;

use net::{HttpStream, HttpsStream, Transport, SslClient};
use super::dns::Dns;
use super::Registration;

/// A connector creates a Transport to a remote address..
pub trait Connect {
    /// Type of Transport to create
    type Output: Transport;
    /// The key used to determine if an existing socket can be used.
    type Key: Eq + Hash + Clone + fmt::Debug;
    /// Returns the key based off the Url.
    fn key(&self, &Url) -> Option<Self::Key>;
    /// Connect to a remote address.
    fn connect(&mut self, &Url) -> io::Result<Self::Key>;
    /// Returns a connected socket and associated host.
    fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)>;
    #[doc(hidden)]
    /// Configure number of dns workers to use.
    fn dns_workers(&mut self, usize);
    #[doc(hidden)]
    fn register(&mut self, Registration);
}

/// A connector for the `http` scheme.
pub struct HttpConnector {
    dns: Option<Dns>,
    threads: usize,
    resolving: HashMap<String, Vec<(&'static str, String, u16)>>,
}

impl HttpConnector {
    /// Set the number of resolver threads.
    ///
    /// Default is 4.
    pub fn threads(mut self, threads: usize) -> HttpConnector {
        debug_assert!(self.dns.is_none(), "setting threads after Dns is created does nothing");
        self.threads = threads;
        self
    }
}

impl Default for HttpConnector {
    fn default() -> HttpConnector {
        HttpConnector {
            dns: None,
            threads: 4,
            resolving: HashMap::new(),
        }
    }
}

impl fmt::Debug for HttpConnector {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("HttpConnector")
            .field("threads", &self.threads)
            .field("resolving", &self.resolving)
            .finish()
    }
}

impl Connect for HttpConnector {
    type Output = HttpStream;
    type Key = (&'static str, String, u16);

    fn dns_workers(&mut self, count: usize) {
        self.threads = count;
    }

    fn key(&self, url: &Url) -> Option<Self::Key> {
        if url.scheme() == "http" {
            Some((
                "http",
                url.host_str().expect("http scheme must have host").to_owned(),
                url.port().unwrap_or(80),
            ))
        } else {
            None
        }
    }

    fn connect(&mut self, url: &Url) -> io::Result<Self::Key> {
        debug!("Http::connect({:?})", url);
        if let Some(key) = self.key(url) {
            let host = url.host_str().expect("http scheme must have a host");
            self.dns.as_ref().expect("dns workers lost").resolve(host);
            self.resolving.entry(host.to_owned()).or_insert_with(Vec::new).push(key.clone());
            Ok(key)
        } else {
            Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http"))
        }
    }

    fn connected(&mut self) -> Option<(Self::Key, io::Result<HttpStream>)> {
        let (host, addrs) = match self.dns.as_ref().expect("dns workers lost").resolved() {
            Ok(res) => res,
            Err(_) => return None
        };
        //TODO: try all addrs
        let addr = addrs.and_then(|mut addrs| Ok(addrs.next().unwrap()));
        debug!("Http::resolved <- ({:?}, {:?})", host, addr);
        if let Entry::Occupied(mut entry) = self.resolving.entry(host) {
            let resolved = entry.get_mut().remove(0);
            if entry.get().is_empty() {
                entry.remove();
            }
            let port = resolved.2;
            Some((resolved, addr.and_then(|addr| TcpStream::connect(&SocketAddr::new(addr, port))
                                                            .map(HttpStream))
                ))
        } else {
            trace!("^--  resolved but not in hashmap?");
            None
        }
    }

    fn register(&mut self, reg: Registration) {
        self.dns = Some(Dns::new(reg.notify, self.threads));
    }
}

/// A connector that can protect HTTP streams using SSL.
#[derive(Debug, Default)]
pub struct HttpsConnector<S: SslClient> {
    http: HttpConnector,
    ssl: S
}

impl<S: SslClient> HttpsConnector<S> {
    /// Create a new connector using the provided SSL implementation.
    pub fn new(s: S) -> HttpsConnector<S> {
        HttpsConnector {
            http: HttpConnector::default(),
            ssl: s,
        }
    }
}

impl<S: SslClient> Connect for HttpsConnector<S> {
    type Output = HttpsStream<S::Stream>;
    type Key = (&'static str, String, u16);

    fn dns_workers(&mut self, count: usize) {
        self.http.dns_workers(count)
    }

    fn key(&self, url: &Url) -> Option<Self::Key> {
        let scheme = match url.scheme() {
            "http" => "http",
            "https" => "https",
            _ => return None
        };
        Some((
            scheme,
            url.host_str().expect("http scheme must have host").to_owned(),
            url.port_or_known_default().expect("http scheme must have a port"),
        ))
    }

    fn connect(&mut self, url: &Url) -> io::Result<Self::Key> {
        debug!("Https::connect({:?})", url);
        if let Some(key) = self.key(url) {
            let host = url.host_str().expect("http scheme must have a host");
            self.http.dns.as_ref().expect("dns workers lost").resolve(host);
            self.http.resolving.entry(host.to_owned()).or_insert_with(Vec::new).push(key.clone());
            Ok(key)
        } else {
            Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http or https"))
        }
    }

    fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)> {
        self.http.connected().map(|(key, res)| {
            let res = res.and_then(|http| {
                if key.0 == "https" {
                    self.ssl.wrap_client(http, &key.1)
                        .map(HttpsStream::Https)
                        .map_err(|e| match e {
                            ::Error::Io(e) => e,
                            e => io::Error::new(io::ErrorKind::Other, e)
                        })
                } else {
                    Ok(HttpsStream::Http(http))
                }
            });
            (key, res)
        })
    }

    fn register(&mut self, reg: Registration) {
        self.http.register(reg);
    }
}

#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
#[doc(hidden)]
pub type DefaultConnector = HttpConnector;

#[cfg(all(feature = "openssl", not(feature = "security-framework")))]
#[doc(hidden)]
pub type DefaultConnector = HttpsConnector<::net::Openssl>;

#[cfg(feature = "security-framework")]
#[doc(hidden)]
pub type DefaultConnector = HttpsConnector<::net::SecureTransportClient>;

#[doc(hidden)]
pub type DefaultTransport = <DefaultConnector as Connect>::Output;

fn _assert_defaults() {
    fn _assert<T, U>() where T: Connect<Output=U>, U: Transport {}

    _assert::<DefaultConnector, DefaultTransport>();
}
<|endoftext|>


<commit_before>use cli::arg_types::Timeframe;

use hyper::{Client, Url};
use hyper::header::{Authorization, Bearer};
use rustc_serialize::json;
use chrono::{Date, DateTime, UTC};
use chrono::duration::Duration;

use std::io::prelude::*; // Required for read_to_string use later.

use api::error::TellerClientError;

pub type ApiServiceResult<T> = Result<T, TellerClientError>;

#[derive(Debug, RustcDecodable)]
struct AccountResponse {
    data: Account,
}

#[derive(Debug, RustcDecodable)]
struct AccountsResponse {
    data: Vec<Account>,
}

#[derive(Debug, RustcDecodable)]
struct TransactionsResponse {
    data: Vec<Transaction>,
}

#[derive(Debug, RustcDecodable)]
pub struct Account {
    pub name: String,
    pub institution: String,
    pub id: String,
    pub currency: String,
    pub bank_code: String,
    pub balance: String,
    pub account_number_last_4: String,
}

#[derive(Debug, RustcDecodable)]
pub struct Transaction {
    pub description: String,
    pub date: String,
    pub counterparty: String,
    pub amount: String,
}

pub fn parse_utc_date_from_transaction(t: &Transaction) -> Date<UTC> {
    let full_date = &(t.date.to_owned() + "T00:00:00-00:00");
    let past_transaction_date_without_tz = DateTime::parse_from_rfc3339(full_date).unwrap().date();
    let past_transaction_date = past_transaction_date_without_tz.with_timezone(&UTC);
    past_transaction_date
}

const TELLER_API_SERVER_URL: &'static str = "https://api.teller.io";

pub struct TellerClient<'a> {
    auth_token: &'a str,
}

impl<'a> TellerClient<'a> {
    pub fn new(auth_token: &'a str) -> TellerClient {
        TellerClient {
            auth_token: auth_token,
        }
    }

    fn get_body(&self, url: &str) -> ApiServiceResult<String> {
        let client = Client::new();
        let mut res = try!(client.get(url)
                                 .header(Authorization(
                                     Bearer { token: self.auth_token.to_string() }
                                 ))
                                 .send());
        if res.status.is_client_error() {
            return Err(TellerClientError::AuthenticationError);
        }

        let mut body = String::new();
        try!(res.read_to_string(&mut body));

        debug!("GET {} response: {}", url, body);

        Ok(body)
    }

    pub fn get_accounts(&self) -> ApiServiceResult<Vec<Account>> {
        let body = try!(self.get_body(&format!("{}/accounts", TELLER_API_SERVER_URL)));
        let accounts_response: AccountsResponse = try!(json::decode(&body));

        Ok(accounts_response.data)
    }

    pub fn get_account(&self, account_id: &str) -> ApiServiceResult<Account> {
        let body = try!(self.get_body(&format!("{}/accounts/{}", TELLER_API_SERVER_URL, account_id)));
        let account_response: AccountResponse = try!(json::decode(&body));

        Ok(account_response.data)
    }

    pub fn raw_transactions(&self,
                            account_id: &str,
                            count: u32,
                            page: u32)
                            -> ApiServiceResult<Vec<Transaction>> {
        let mut url = Url::parse(&format!("{}/accounts/{}/transactions",
                                          TELLER_API_SERVER_URL,
                                          account_id)).unwrap();

        const COUNT: &'static str = "count";
        const PAGE: &'static str = "page";
        let query = vec![(COUNT, count.to_string()), (PAGE, page.to_string())];
        url.set_query_from_pairs(query.into_iter());

        let body = try!(self.get_body(&url.serialize()));
        let transactions_response: TransactionsResponse = try!(json::decode(&body));

        Ok(transactions_response.data)
    }

    pub fn get_transactions(&self,
                            account_id: &str,
                            timeframe: &Timeframe)
                            -> ApiServiceResult<Vec<Transaction>> {
        let page_through_transactions = |from| -> ApiServiceResult<Vec<Transaction>> {
            let mut all_transactions = vec![];

            let mut fetching = true;
            let mut page = 1;
            let count = 250;
            while fetching {
                let mut transactions = try!(self.raw_transactions(&account_id, count, page));
                match transactions.last() {
                    None => {
                        // If there are no transactions left, do not fetch forever...
                        fetching = false
                    }
                    Some(past_transaction) => {
                        let past_transaction_date = parse_utc_date_from_transaction(&past_transaction);
                        if past_transaction_date < from {
                            fetching = false;
                        }
                    }
                };

                all_transactions.append(&mut transactions);
                page = page + 1;
            }

            all_transactions = all_transactions.into_iter()
                                               .filter(|t| {
                                                   let transaction_date =
                                                       parse_utc_date_from_transaction(&t);
                                                   transaction_date > from
                                               })
                                               .collect();

            all_transactions.reverse();
            Ok(all_transactions)
        };

        match *timeframe {
            Timeframe::ThreeMonths => {
                let to = UTC::today();
                let from = to - Duration::days(91); // close enough... 😅

                page_through_transactions(from)
            }
            Timeframe::SixMonths => {
                let to = UTC::today();
                let from = to - Duration::days(183);

                page_through_transactions(from)
            }
            Timeframe::Year => {
                let to = UTC::today();
                let from = to - Duration::days(365);

                page_through_transactions(from)
            }
        }
    }

}
<commit_msg>style(debug): easier to debug<commit_after>use cli::arg_types::Timeframe;

use hyper::{Client, Url};
use hyper::header::{Authorization, Bearer};
use rustc_serialize::json;
use chrono::{Date, DateTime, UTC};
use chrono::duration::Duration;

use std::io::prelude::*; // Required for read_to_string use later.

use api::error::TellerClientError;

pub type ApiServiceResult<T> = Result<T, TellerClientError>;

#[derive(Debug, RustcDecodable)]
struct AccountResponse {
    data: Account,
}

#[derive(Debug, RustcDecodable)]
struct AccountsResponse {
    data: Vec<Account>,
}

#[derive(Debug, RustcDecodable)]
struct TransactionsResponse {
    data: Vec<Transaction>,
}

#[derive(Debug, RustcDecodable)]
pub struct Account {
    pub name: String,
    pub institution: String,
    pub id: String,
    pub currency: String,
    pub bank_code: String,
    pub balance: String,
    pub account_number_last_4: String,
}

#[derive(Debug, RustcDecodable)]
pub struct Transaction {
    pub description: String,
    pub date: String,
    pub counterparty: String,
    pub amount: String,
}

pub fn parse_utc_date_from_transaction(t: &Transaction) -> Date<UTC> {
    let full_date = &(t.date.to_owned() + "T00:00:00-00:00");
    let past_transaction_date_without_tz = DateTime::parse_from_rfc3339(full_date).unwrap().date();
    let past_transaction_date = past_transaction_date_without_tz.with_timezone(&UTC);
    past_transaction_date
}

const TELLER_API_SERVER_URL: &'static str = "https://api.teller.io";

pub struct TellerClient<'a> {
    auth_token: &'a str,
}

impl<'a> TellerClient<'a> {
    pub fn new(auth_token: &'a str) -> TellerClient {
        TellerClient {
            auth_token: auth_token,
        }
    }

    fn get_body(&self, url: &str) -> ApiServiceResult<String> {
        let client = Client::new();
        let mut res = try!(client.get(url)
                                 .header(Authorization(
                                     Bearer { token: self.auth_token.to_string() }
                                 ))
                                 .send());
        if res.status.is_client_error() {
            return Err(TellerClientError::AuthenticationError);
        }

        let mut body = String::new();
        try!(res.read_to_string(&mut body));

        info!("GET {}", url);
        debug!("Response: {}", body);

        Ok(body)
    }

    pub fn get_accounts(&self) -> ApiServiceResult<Vec<Account>> {
        let body = try!(self.get_body(&format!("{}/accounts", TELLER_API_SERVER_URL)));
        let accounts_response: AccountsResponse = try!(json::decode(&body));

        Ok(accounts_response.data)
    }

    pub fn get_account(&self, account_id: &str) -> ApiServiceResult<Account> {
        let body = try!(self.get_body(&format!("{}/accounts/{}", TELLER_API_SERVER_URL, account_id)));
        let account_response: AccountResponse = try!(json::decode(&body));

        Ok(account_response.data)
    }

    pub fn raw_transactions(&self,
                            account_id: &str,
                            count: u32,
                            page: u32)
                            -> ApiServiceResult<Vec<Transaction>> {
        let mut url = Url::parse(&format!("{}/accounts/{}/transactions",
                                          TELLER_API_SERVER_URL,
                                          account_id)).unwrap();

        const COUNT: &'static str = "count";
        const PAGE: &'static str = "page";
        let query = vec![(COUNT, count.to_string()), (PAGE, page.to_string())];
        url.set_query_from_pairs(query.into_iter());

        let body = try!(self.get_body(&url.serialize()));
        let transactions_response: TransactionsResponse = try!(json::decode(&body));

        Ok(transactions_response.data)
    }

    pub fn get_transactions(&self,
                            account_id: &str,
                            timeframe: &Timeframe)
                            -> ApiServiceResult<Vec<Transaction>> {
        let page_through_transactions = |from| -> ApiServiceResult<Vec<Transaction>> {
            let mut all_transactions = vec![];

            let mut fetching = true;
            let mut page = 1;
            let count = 250;
            while fetching {
                let mut transactions = try!(self.raw_transactions(&account_id, count, page));
                match transactions.last() {
                    None => {
                        // If there are no transactions left, do not fetch forever...
                        fetching = false
                    }
                    Some(past_transaction) => {
                        let past_transaction_date = parse_utc_date_from_transaction(&past_transaction);
                        if past_transaction_date < from {
                            fetching = false;
                        }
                    }
                };

                all_transactions.append(&mut transactions);
                page = page + 1;
            }

            all_transactions = all_transactions.into_iter()
                                               .filter(|t| {
                                                   let transaction_date =
                                                       parse_utc_date_from_transaction(&t);
                                                   transaction_date > from
                                               })
                                               .collect();

            all_transactions.reverse();
            Ok(all_transactions)
        };

        match *timeframe {
            Timeframe::ThreeMonths => {
                let to = UTC::today();
                let from = to - Duration::days(91); // close enough... 😅

                page_through_transactions(from)
            }
            Timeframe::SixMonths => {
                let to = UTC::today();
                let from = to - Duration::days(183);

                page_through_transactions(from)
            }
            Timeframe::Year => {
                let to = UTC::today();
                let from = to - Duration::days(365);

                page_through_transactions(from)
            }
        }
    }

}
<|endoftext|>


<commit_before>use serenity::client::CACHE;
use serenity::model::Mentionable;

command!(join(ctx, msg) {

    let mut channel = None;

    if let Some(guild) = msg.guild() {
        for (_, vs) in guild.read().unwrap().voice_states.iter() {
            if msg.author.id == vs.user_id {
                channel = vs.channel_id;
            }
        }
    };

    let connect_to = match channel {
        Some(id) => id,
        None => {
            let _ = msg.channel_id.say("You need to join a voice channel first");
            return Ok(());
        }
    };

    let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
        Some(channel) => channel.read().unwrap().guild_id,
        None => {
            let _ = msg.channel_id.say("Groups and DMs not supported");
            return Ok(());
        },
    };

    let mut shard = ctx.shard.lock();
    shard.manager.join(guild_id, connect_to);

    let _ = msg.channel_id.say(&format!("Joined {}", connect_to.mention()));
});

command!(leave(ctx, msg) {
    let guild_id = match CACHE.read().unwrap().guild_channel(msg.channel_id) {
        Some(channel) => channel.read().unwrap().guild_id,
        None => {
            let _ = msg.channel_id.say("Groups and DMs are not supported");
            return Ok(());
        },
    };

    let mut shard = ctx.shard.lock();
    let has_handler = shard.manager.get(guild_id).is_some();

    if has_handler {
        shard.manager.remove(guild_id);
        let _ = msg.channel_id.say("Left voice channel");
    } else {
        let _ = msg.channel_id.say("Not in a voice channel");
    }
});

use serenity::voice;

command!(play(ctx, msg) {
    let guild_id = match msg.guild_id() {
        Some(id) => id,
        None => {
            let _ = msg.channel_id.say("Error finding guild id");
            return Ok(());
        }
    };

    let airhorn_sound = ".\\audio\\airhorn.dca";

    if let Some(handler) = ctx.shard.lock().manager.get(guild_id) {
        let source = match voice::dca(airhorn_sound) {
            Ok(source) => source,
            Err(why) => {
                println!("Err starting source: {:?}", why);

                let _ = msg.channel_id.say("Couldn't find file");

                return Ok(());
            },
        };

        handler.play(source);

        let _ = msg.channel_id.say("Playing");
    } else {
        let _ = msg.channel_id.say("Not in a voice channel");
    }
});<commit_msg>Made getting guild_id more consistent<commit_after>use serenity::model::Mentionable;

command!(join(ctx, msg) {

    let mut channel = None;

    if let Some(guild) = msg.guild() {
        for (_, vs) in guild.read().unwrap().voice_states.iter() {
            if msg.author.id == vs.user_id {
                channel = vs.channel_id;
            }
        }
    };

    let connect_to = match channel {
        Some(id) => id,
        None => {
            let _ = msg.channel_id.say("You need to join a voice channel first");
            return Ok(());
        }
    };

    let guild_id = match msg.guild_id() {
        Some(id) => id,
        None => {
            let _ = msg.channel_id.say("Groups and DMs not supported");
            return Ok(());
        }
    };

    let mut shard = ctx.shard.lock();
    shard.manager.join(guild_id, connect_to);

    let _ = msg.channel_id.say(&format!("Joined {}", connect_to.mention()));
});

command!(leave(ctx, msg) {
    let guild_id = match msg.guild_id() {
        Some(id) => id,
        None => {
            let _ = msg.channel_id.say("Groups and DMs not supported");
            return Ok(());
        }
    };

    let mut shard = ctx.shard.lock();
    let has_handler = shard.manager.get(guild_id).is_some();

    if has_handler {
        shard.manager.remove(guild_id);
        let _ = msg.channel_id.say("Left voice channel");
    } else {
        let _ = msg.channel_id.say("Not in a voice channel");
    }
});

use serenity::voice;

command!(play(ctx, msg) {
    let guild_id = match msg.guild_id() {
        Some(id) => id,
        None => {
            let _ = msg.channel_id.say("Error finding guild id");
            return Ok(());
        }
    };

    let airhorn_sound = ".\\audio\\airhorn.dca";

    if let Some(handler) = ctx.shard.lock().manager.get(guild_id) {
        let source = match voice::dca(airhorn_sound) {
            Ok(source) => source,
            Err(why) => {
                println!("Err starting source: {:?}", why);

                let _ = msg.channel_id.say("Couldn't find file");

                return Ok(());
            },
        };

        handler.play(source);

        let _ = msg.channel_id.say("Playing");
    } else {
        let _ = msg.channel_id.say("Not in a voice channel");
    }
});<|endoftext|>


<commit_before>
use color::Color;
use draw::Drawable;
use graphics;
use graphics::Graphics;
use graphics::character::CharacterCache;
use ui_context::UiContext;

/// The context from which we'll draw the background.
#[derive(Copy, Clone)]
pub struct Background {
    maybe_color: Option<Color>,
}

impl Background {
    pub fn new() -> Background {
        Background {
            maybe_color: None,
        }
    }
}

/*
quack! {
    bg: Background[]
    get:
    set:
        fn (val: Color) [] { bg.maybe_color = Some(val) }
    action:
}
*/

impl Drawable for Background {
    fn draw<B, C>(&mut self, uic: &mut UiContext<C>, graphics: &mut B)
        where
            B: Graphics<Texture = <C as CharacterCache>::Texture>,
            C: CharacterCache
    {
        let Color(col) = self.maybe_color
            .unwrap_or(uic.theme.background_color);
        graphics::clear(col, graphics);
    }
}
<commit_msg>Implemented `Colorable` for `Background`<commit_after>
use color::{ Color, Colorable };
use draw::Drawable;
use graphics;
use graphics::Graphics;
use graphics::character::CharacterCache;
use ui_context::UiContext;

/// The context from which we'll draw the background.
#[derive(Copy, Clone)]
pub struct Background {
    maybe_color: Option<Color>,
}

impl Background {
    pub fn new() -> Background {
        Background {
            maybe_color: None,
        }
    }
}

impl Colorable for Background {
    fn color(mut self, color: Color) -> Self {
        self.maybe_color = Some(color);
        self
    }
}

/*
quack! {
    bg: Background[]
    get:
    set:
        fn (val: Color) [] { bg.maybe_color = Some(val) }
    action:
}
*/

impl Drawable for Background {
    fn draw<B, C>(&mut self, uic: &mut UiContext<C>, graphics: &mut B)
        where
            B: Graphics<Texture = <C as CharacterCache>::Texture>,
            C: CharacterCache
    {
        let Color(col) = self.maybe_color
            .unwrap_or(uic.theme.background_color);
        graphics::clear(col, graphics);
    }
}
<|endoftext|>


<commit_before>extern crate tempdir;
extern crate hyper;
extern crate grnenvlib;
#[macro_use]
extern crate clap;

use clap::{Arg, App, AppSettings, SubCommand};
use grnenvlib::command;

fn main() {
    default_main();
}

fn cli() -> App<'static, 'static> {
    App::new("grnenv-rs")
        .version(crate_version!())
        .author(crate_authors!())
        .about("A tiny tool for obtain and select multiple Groonga.")
        .setting(AppSettings::AllowExternalSubcommands)
        .subcommand(SubCommand::with_name("init").about("Prepare grnenv-rs."))
        .subcommand(SubCommand::with_name("install")
            .about("Install a given Groonga version and arch")
            .arg(Arg::with_name("VERSION").required(true))
            .arg(Arg::with_name("arch")
                .short("a")
                .long("arch")
                .value_name("ARCH")
                .help("Select architectures. e.g.) x86, x64")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("switch")
            .about("Switch Groonga with given version and arch")
            .arg(Arg::with_name("VERSION").required(true))
            .arg(Arg::with_name("arch")
                .short("a")
                .long("arch")
                .value_name("ARCH")
                .help("Select architectures. e.g.) x86, x64")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("versions").about("Display installed Groonga versions"))
        .subcommand(SubCommand::with_name("uninstall")
            .about("Uninstall a given Groonga version and arch")
            .arg(Arg::with_name("VERSION").required(true))
            .arg(Arg::with_name("arch")
                .short("a")
                .long("arch")
                .value_name("ARCH")
                .help("Select architectures. e.g.) x86, x64")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("list").about("Display installable Groonga versions"))

}

#[cfg(not(windows))]
fn default_main() {
    let mut app = cli();
    let matches = app.clone().get_matches();
    match matches.subcommand() {
        ("init", _) => command::unix::init(),
        ("install", Some(m)) => command::unix::install(m),
        ("switch", Some(m)) => command::unix::switch(m),
        ("versions", _) => command::common::versions(),
        ("uninstall", Some(m)) => command::unix::uninstall(m),
        ("list", _) => command::unix::list(),
        (external, Some(ext_m)) => command::common::execute_external_command(external, ext_m),
        (_, _) => {
            let _ = app.print_help();
        },
    }
}

#[cfg(windows)]
fn default_main() {
    let mut app = cli();
    let matches = app.clone().get_matches();
    match matches.subcommand() {
        ("init", _) => command::windows::init(),
        ("install", Some(m)) => command::windows::install(m),
        ("switch", Some(m)) => command::windows::switch(m),
        ("versions", _) => command::common::versions(),
        ("uninstall", Some(m)) => command::windows::uninstall(m),
        ("list", _) => command::windows::list(),
        (external, Some(ext_m)) => command::common::execute_external_command(external, ext_m),
        (_, _) => {
            let _ = app.print_help();
        },
    }
}
<commit_msg>Fix about description<commit_after>extern crate tempdir;
extern crate hyper;
extern crate grnenvlib;
#[macro_use]
extern crate clap;

use clap::{Arg, App, AppSettings, SubCommand};
use grnenvlib::command;

fn main() {
    default_main();
}

fn cli() -> App<'static, 'static> {
    App::new("grnenv-rs")
        .version(crate_version!())
        .author(crate_authors!())
        .about("A tiny tool for switch multiple Groonga versions.")
        .setting(AppSettings::AllowExternalSubcommands)
        .subcommand(SubCommand::with_name("init").about("Prepare grnenv-rs."))
        .subcommand(SubCommand::with_name("install")
            .about("Install a given Groonga version and arch")
            .arg(Arg::with_name("VERSION").required(true))
            .arg(Arg::with_name("arch")
                .short("a")
                .long("arch")
                .value_name("ARCH")
                .help("Select architectures. e.g.) x86, x64")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("switch")
            .about("Switch Groonga with given version and arch")
            .arg(Arg::with_name("VERSION").required(true))
            .arg(Arg::with_name("arch")
                .short("a")
                .long("arch")
                .value_name("ARCH")
                .help("Select architectures. e.g.) x86, x64")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("versions").about("Display installed Groonga versions"))
        .subcommand(SubCommand::with_name("uninstall")
            .about("Uninstall a given Groonga version and arch")
            .arg(Arg::with_name("VERSION").required(true))
            .arg(Arg::with_name("arch")
                .short("a")
                .long("arch")
                .value_name("ARCH")
                .help("Select architectures. e.g.) x86, x64")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("list").about("Display installable Groonga versions"))

}

#[cfg(not(windows))]
fn default_main() {
    let mut app = cli();
    let matches = app.clone().get_matches();
    match matches.subcommand() {
        ("init", _) => command::unix::init(),
        ("install", Some(m)) => command::unix::install(m),
        ("switch", Some(m)) => command::unix::switch(m),
        ("versions", _) => command::common::versions(),
        ("uninstall", Some(m)) => command::unix::uninstall(m),
        ("list", _) => command::unix::list(),
        (external, Some(ext_m)) => command::common::execute_external_command(external, ext_m),
        (_, _) => {
            let _ = app.print_help();
        },
    }
}

#[cfg(windows)]
fn default_main() {
    let mut app = cli();
    let matches = app.clone().get_matches();
    match matches.subcommand() {
        ("init", _) => command::windows::init(),
        ("install", Some(m)) => command::windows::install(m),
        ("switch", Some(m)) => command::windows::switch(m),
        ("versions", _) => command::common::versions(),
        ("uninstall", Some(m)) => command::windows::uninstall(m),
        ("list", _) => command::windows::list(),
        (external, Some(ext_m)) => command::common::execute_external_command(external, ext_m),
        (_, _) => {
            let _ = app.print_help();
        },
    }
}
<|endoftext|>


<commit_before>use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::rc::Rc;

use interpolation;
use rustc_serialize::{self, Decodable, Decoder, json};

use animation::{AnimationClip, SQT};
use math;

pub type ClipId = String;
pub type ParamId = String;

pub enum BlendTreeNode {
    LerpNode(Box<BlendTreeNode>, Box<BlendTreeNode>, ParamId),
    ClipNode(Rc<RefCell<AnimationClip>>),
}

#[derive(Clone)]
pub enum BlendTreeNodeDef {
    LerpNode(Box<BlendTreeNodeDef>, Box<BlendTreeNodeDef>, ParamId),
    ClipNode(ClipId),
}

impl BlendTreeNodeDef {
    pub fn from_path(path: &str) -> Result<BlendTreeNodeDef, &'static str> {
        let file_result = File::open(path);

        let mut file = match file_result {
            Ok(file) => file,
            Err(_) => return Err("Failed to open definition file at path.")
        };

        let mut json_string = String::new();
        match file.read_to_string(&mut json_string) {
            Ok(_) => {},
            Err(_) => return Err("Failed to read definition file.")
        };

        Ok(json::decode(&json_string[..]).unwrap())
    }
}

impl BlendTreeNodeDef {

    pub fn get_parameters(&self) -> Vec<String> {
        let mut parameters = Vec::new();
        self.get_parameters_internal(&mut parameters);
        parameters
    }

    fn get_parameters_internal(&self, parameters: &mut Vec<String>) {
        match self {
            &BlendTreeNodeDef::LerpNode(ref input_1, ref input_2, ref param_name) => {

                if !parameters.iter().any(|p| p == param_name) {
                    parameters.push(param_name.clone());
                }
                input_1.get_parameters_internal(parameters);
                input_2.get_parameters_internal(parameters);
            },
            &BlendTreeNodeDef::ClipNode(_) => {}
        }
    }
}

impl Decodable for BlendTreeNodeDef {

    fn decode<D: Decoder>(decoder: &mut D) -> Result<BlendTreeNodeDef, D::Error> {
        decoder.read_struct("root", 0, |decoder| {

            let node_type = try!(decoder.read_struct_field("type", 0, |decoder| { Ok(try!(decoder.read_str())) }));

            match &node_type[..] {
                "LerpNode" => {

                    let (input_1, input_2) = try!(decoder.read_struct_field("inputs", 0, |decoder| {
                        decoder.read_seq(|decoder, len| {
                            Ok((
                                try!(decoder.read_seq_elt(0, Decodable::decode)),
                                try!(decoder.read_seq_elt(1, Decodable::decode))
                            ))
                        })
                    }));

                    let blend_param_name = try!(decoder.read_struct_field("param", 0, |decoder| { Ok(try!(decoder.read_str())) }));

                    Ok(BlendTreeNodeDef::LerpNode(Box::new(input_1), Box::new(input_2), blend_param_name))

                },
                "ClipNode" => {
                    let clip_source = try!(decoder.read_struct_field("clip_source", 0, |decoder| { Ok(try!(decoder.read_str())) }));
                    Ok(BlendTreeNodeDef::ClipNode(clip_source))
                }
                _ => panic!("Unexpected blend node type")
            }
        })
    }

}

impl BlendTreeNode {

    ///
    /// Initialize a new BlendTreeNode from a BlendTreeNodeDef and
    /// a mapping from animation names to AnimationClip
    ///
    pub fn from_def(
        def: BlendTreeNodeDef,
        animations: &HashMap<ClipId, Rc<RefCell<AnimationClip>>>
    ) -> BlendTreeNode {

        match def {

            BlendTreeNodeDef::LerpNode(input_1, input_2, param_id) => {
                BlendTreeNode::LerpNode(
                    Box::new(BlendTreeNode::from_def(*input_1, animations)),
                    Box::new(BlendTreeNode::from_def(*input_2, animations)),
                    param_id.clone()
                )
            }

            BlendTreeNodeDef::ClipNode(clip_id) => {
                let clip = animations.get(&clip_id[..]).expect(&format!("Missing animation clip: {}", clip_id)[..]);
                BlendTreeNode::ClipNode(clip.clone())
            }
        }
    }

    ///
    /// Get the output skeletal pose for this node and the given time and parameters
    ///
    pub fn get_output_pose(&self, elapsed_time: f32, params: &HashMap<String, f32>, output_poses: &mut [SQT]) {
        match self {
            &BlendTreeNode::LerpNode(ref input_1, ref input_2, ref param_name) => {

                let mut input_poses = [ SQT { translation: [0.0, 0.0, 0.0], scale: 0.0, rotation: (0.0, [0.0, 0.0, 0.0]) }; 64 ];

                let sample_count = output_poses.len();

                input_1.get_output_pose(elapsed_time, params, &mut input_poses[0 .. sample_count]);
                input_2.get_output_pose(elapsed_time, params, output_poses);

                let blend_parameter = params[&param_name[..]];

                for i in (0 .. output_poses.len()) {
                    let pose_1 = input_poses[i];
                    let pose_2 = &mut output_poses[i];
                    pose_2.scale = interpolation::lerp(&pose_1.scale, &pose_2.scale, &blend_parameter);
                    pose_2.translation = interpolation::lerp(&pose_1.translation, &pose_2.translation, &blend_parameter);
                    pose_2.rotation = math::lerp_quaternion(&pose_1.rotation, &pose_2.rotation, &blend_parameter);
                }

            }
            &BlendTreeNode::ClipNode(ref clip) => {
                clip.borrow().get_interpolated_poses_at_time(elapsed_time, output_poses);
            }
        }
    }
}
<commit_msg>Cleaned up, documented blend_tree<commit_after>use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::rc::Rc;

use interpolation;
use rustc_serialize::{Decodable, Decoder, json};

use animation::{AnimationClip, SQT};
use math;

pub type ClipId = String;
pub type ParamId = String;

///
/// Definition of a blend tree, to be converted to BlendTreeNode
/// at runtime
///
#[derive(Clone)]
pub enum BlendTreeNodeDef {
    LerpNode(Box<BlendTreeNodeDef>, Box<BlendTreeNodeDef>, ParamId),
    ClipNode(ClipId),
}

impl Decodable for BlendTreeNodeDef {
    fn decode<D: Decoder>(decoder: &mut D) -> Result<BlendTreeNodeDef, D::Error> {
        decoder.read_struct("root", 0, |decoder| {

            let node_type = try!(decoder.read_struct_field("type", 0, |decoder| { Ok(try!(decoder.read_str())) }));

            match &node_type[..] {
                "LerpNode" => {

                    let (input_1, input_2) = try!(decoder.read_struct_field("inputs", 0, |decoder| {
                        decoder.read_seq(|decoder, _len| {
                            Ok((
                                try!(decoder.read_seq_elt(0, Decodable::decode)),
                                try!(decoder.read_seq_elt(1, Decodable::decode))
                            ))
                        })
                    }));

                    let blend_param_name = try!(decoder.read_struct_field("param", 0, |decoder| { Ok(try!(decoder.read_str())) }));

                    Ok(BlendTreeNodeDef::LerpNode(Box::new(input_1), Box::new(input_2), blend_param_name))

                },
                "ClipNode" => {
                    let clip_source = try!(decoder.read_struct_field("clip_source", 0, |decoder| { Ok(try!(decoder.read_str())) }));
                    Ok(BlendTreeNodeDef::ClipNode(clip_source))
                }
                _ => panic!("Unexpected blend node type")
            }
        })
    }
}

///
/// Runtime representation of a blend tree.
///
pub enum BlendTreeNode {
    ///
    /// Pose output is linearly blend between the output of
    /// two child BlendTreeNodes, with blend factor according
    /// the paramater value for name ParamId
    ///
    LerpNode(Box<BlendTreeNode>, Box<BlendTreeNode>, ParamId),

    ///
    /// Pose output is from an AnimationClip
    ///
    ClipNode(Rc<RefCell<AnimationClip>>),
}

impl BlendTreeNode {

    ///
    /// Initialize a new BlendTreeNode from a BlendTreeNodeDef and
    /// a mapping from animation names to AnimationClip
    ///
    pub fn from_def(
        def: BlendTreeNodeDef,
        animations: &HashMap<ClipId, Rc<RefCell<AnimationClip>>>
    ) -> BlendTreeNode {

        match def {

            BlendTreeNodeDef::LerpNode(input_1, input_2, param_id) => {
                BlendTreeNode::LerpNode(
                    Box::new(BlendTreeNode::from_def(*input_1, animations)),
                    Box::new(BlendTreeNode::from_def(*input_2, animations)),
                    param_id.clone()
                )
            }

            BlendTreeNodeDef::ClipNode(clip_id) => {
                let clip = animations.get(&clip_id[..]).expect(&format!("Missing animation clip: {}", clip_id)[..]);
                BlendTreeNode::ClipNode(clip.clone())
            }
        }
    }

    ///
    /// Get the output skeletal pose for this node and the given time and parameters
    ///
    pub fn get_output_pose(&self, elapsed_time: f32, params: &HashMap<String, f32>, output_poses: &mut [SQT]) {
        match self {
            &BlendTreeNode::LerpNode(ref input_1, ref input_2, ref param_name) => {

                let mut input_poses = [ SQT { translation: [0.0, 0.0, 0.0], scale: 0.0, rotation: (0.0, [0.0, 0.0, 0.0]) }; 64 ];

                let sample_count = output_poses.len();

                input_1.get_output_pose(elapsed_time, params, &mut input_poses[0 .. sample_count]);
                input_2.get_output_pose(elapsed_time, params, output_poses);

                let blend_parameter = params[&param_name[..]];

                for i in (0 .. output_poses.len()) {
                    let pose_1 = input_poses[i];
                    let pose_2 = &mut output_poses[i];
                    pose_2.scale = interpolation::lerp(&pose_1.scale, &pose_2.scale, &blend_parameter);
                    pose_2.translation = interpolation::lerp(&pose_1.translation, &pose_2.translation, &blend_parameter);
                    pose_2.rotation = math::lerp_quaternion(&pose_1.rotation, &pose_2.rotation, &blend_parameter);
                }

            }
            &BlendTreeNode::ClipNode(ref clip) => {
                clip.borrow().get_interpolated_poses_at_time(elapsed_time, output_poses);
            }
        }
    }
}
<|endoftext|>


<commit_before>
#[inline]
pub fn alpha(term: u8) -> bool {
	(term >= 0x41 && term <= 0x5A) || (term >= 0x61 && term <= 0x7A)
}

#[inline]
pub fn digit(term: u8) -> bool {
	term >= 0x30 && term <= 0x39
}

#[inline]
pub fn alphanum(term: u8) -> bool {
	alpha(term) || digit(term)
}

#[inline]
pub fn hex_digit(term: u8) -> bool {
	(term >= 0x30 && term <= 0x39) ||
	(term >= 0x41 && term <= 0x46) ||
	(term >= 0x61 && term <= 0x66)
}

#[inline]
pub fn oct_digit(term: u8) -> bool {
	term >= 0x30 && term <= 0x37
}
<commit_msg>Add space and multispace char classes<commit_after>/// Recognises an alphabetic character, `a-zA-Z`.
#[inline]
pub fn alpha(term: u8) -> bool {
	(term >= 0x41 && term <= 0x5A) || (term >= 0x61 && term <= 0x7A)
}

/// Recognises a decimal digit, `0-9`.
#[inline]
pub fn digit(term: u8) -> bool {
	term >= 0x30 && term <= 0x39
}

/// Recognises an alphanumeric character, `a-zA-Z0-9`.
#[inline]
pub fn alphanum(term: u8) -> bool {
	alpha(term) || digit(term)
}

/// Recognises a hexadecimal digit, `0-9a-fA-F`.
#[inline]
pub fn hex_digit(term: u8) -> bool {
	(term >= 0x30 && term <= 0x39) ||
	(term >= 0x41 && term <= 0x46) ||
	(term >= 0x61 && term <= 0x66)
}

/// Recognises an octal digit, `0-7`.
#[inline]
pub fn oct_digit(term: u8) -> bool {
	term >= 0x30 && term <= 0x37
}

/// Recognises a space or tab.
#[inline]
pub fn space(term: u8) -> bool {
	term == b' ' || term == b'\t'
}

/// Recognises a space, tab, line feed, or carriage returns.
#[inline]
pub fn multispace(term: u8) -> bool {
	space(term) || term == b'\n' || term == b'\r'
}
<|endoftext|>


<commit_before>//! Hyper client bindings for unix domain sockets

use std::io;

use futures::future::{self, FutureResult};
use futures::{Future, IntoFuture};
use hyper::client::connect::{Connect, Connected, Destination};
use tokio_uds::UnixStream;

use super::Uri;

const UNIX_SCHEME: &str = "unix";

/// A type which implements hyper's client connector interface
/// for unix domain sockets
///
/// `UnixConnector` instances assume uri's
/// constructued with `hyperlocal::Uri::new()` which produce uris with a `unix://`
/// scheme
///
/// # examples
///
/// ```no_run
/// extern crate hyper;
/// extern crate hyperlocal;
///
/// let client = hyper::Client::builder()
///    .build::<_, hyper::Body>(hyperlocal::UnixConnector::new());
/// ```
#[derive(Clone)]
pub struct UnixConnector;

impl UnixConnector {
    pub fn new() -> Self {
        UnixConnector
    }
}

impl Connect for UnixConnector {
    type Transport = UnixStream;
    type Error = io::Error;
    type Future = FutureResult<(UnixStream, Connected), io::Error>;

    fn connect(&self, destination: Destination) -> Self::Future {
        if destination.scheme() != UNIX_SCHEME {
            return future::err(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("Invalid uri {:?}", destination),
            ));
        }
        match Uri::socket_path_dest(&destination) {
            Some(ref path) => UnixStream::connect(path)
                                .wait() // We have to block because we
                                .map(|s| (s, Connected::new()))
                                .into_future(),
            _ => future::err(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("Invalid uri {:?}", destination),
            )),
        }
    }
}
<commit_msg>consistency<commit_after>//! Hyper client bindings for unix domain sockets

// Std lib
use std::io;

// Third party
use futures::future::{self, FutureResult};
use futures::{Future, IntoFuture};
use hyper::client::connect::{Connect, Connected, Destination};
use tokio_uds::UnixStream;

use super::Uri;

const UNIX_SCHEME: &str = "unix";

/// A type which implements hyper's client connector interface
/// for unix domain sockets
///
/// `UnixConnector` instances assume uri's
/// constructued with `hyperlocal::Uri::new()` which produce uris with a `unix://`
/// scheme
///
/// # examples
///
/// ```no_run
/// extern crate hyper;
/// extern crate hyperlocal;
///
/// let client = hyper::Client::builder()
///    .build::<_, hyper::Body>(hyperlocal::UnixConnector::new());
/// ```
#[derive(Clone)]
pub struct UnixConnector;

impl UnixConnector {
    pub fn new() -> Self {
        UnixConnector
    }
}

impl Connect for UnixConnector {
    type Transport = UnixStream;
    type Error = io::Error;
    type Future = FutureResult<(UnixStream, Connected), io::Error>;

    fn connect(&self, destination: Destination) -> Self::Future {
        if destination.scheme() != UNIX_SCHEME {
            return future::err(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("Invalid uri {:?}", destination),
            ));
        }
        match Uri::socket_path_dest(&destination) {
            Some(ref path) => UnixStream::connect(path)
                                .wait() // We have to block because we
                                .map(|s| (s, Connected::new()))
                                .into_future(),
            _ => future::err(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("Invalid uri {:?}", destination),
            )),
        }
    }
}
<|endoftext|>