<commit_before>#[macro_use] extern crate clap;
extern crate url;
extern crate rss;
extern crate hyper;
extern crate hyper_native_tls;
extern crate curl; // https://docs.rs/curl/0.4.6/curl/easy/
extern crate rusqlite;

mod schema;
mod common;

use std::process;
use clap::{App, ArgMatches};
use schema::*;
use url::Url;
use rusqlite::Connection;
use curl::easy::Easy;
use std::path::{Path};
use std::fs::{File, create_dir};
use std::env::home_dir;

fn main() {
    let lock_socket = common::create_app_lock(12345); // https://rosettacode.org/wiki/Category:Rust

    // Clap
    let yaml = load_yaml!("cli.yml");
    let matches = App::from_yaml(yaml).get_matches();

    // Init Sqlite database
    let mut database_url = String::new();

    if matches.is_present("database") { // get path from command line
        database_url = matches.value_of("database").unwrap().to_string();
    } else { // or put database file into ~/.config/rusty/rusty.sqlite3
        let db_path = home_dir().expect("/tmp/").into_os_string().into_string().unwrap() + "/.config/rusty";
        if !Path::new(&db_path).exists() { // If path doesn't exist we create it
            create_dir(&db_path).unwrap();
        }
        database_url = db_path + "/rusty.sqlite3";
    }
    let connection = match Connection::open(&database_url) {
        Ok(connection) => connection,
        Err(e) => {
            println!("Error database connection: {} {}", e, database_url);
            process::exit(1)
        }
    };
    init(&connection); // Initialize database

    match matches.subcommand() {
        ("subscribe", Some(sub_matches)) => subscribe(sub_matches, &connection),
        ("unsubscribe", Some(sub_matches)) => unsubscribe(sub_matches, &connection),
        ("list", Some(_)) => list(&connection),
        ("update", Some(sub_matches)) => {
            let feed_id = sub_matches.value_of("id").unwrap_or("0").parse::<i64>().unwrap();
            update(sub_matches, &connection, feed_id);
        },
        ("pending", Some(_)) => pending(&connection),
        ("download", Some(_)) => download(&connection),
        ("download-dir", Some(sub_matches)) => downloaddir(sub_matches, &connection),
        ("", None) => println!("No subcommand was used"),
        _ => println!("No!"),
    }

    common::remove_app_lock(lock_socket);
}

fn init(connection: &Connection) {
    // execute only the first query if there are several queries. So we need to used split()
    let split = include_str!("sql/init.sql").split(";");
    for s in split {
        if s.trim() == "" {
            continue;
        }
        connection.execute(s, &[]).unwrap();
    }
}

fn subscribe(args: &ArgMatches, connection: &Connection) {
    match Url::parse(args.value_of("url").unwrap()) {
        Ok(url) => {
            let subscription = Subscription { id: 0, url: url.as_str().to_string(), label: String::new()};
            println!("Subscribing to: {}", subscription.url);

            let mut stmt = connection.prepare("select * from subscription where url = ?1").unwrap();
            if !stmt.exists(&[&subscription.url]).unwrap() {
                connection.execute("insert into subscription (url, label) values (?1, '')", &[&subscription.url]).unwrap();
            } else {
                println!("    Feed already subscribed to");
                return
            }
            update(args, connection, connection.last_insert_rowid());
            println!("Subscribed");
        }
        Err(e) => println!("Could not parse url '{}' {}", args.value_of("url").unwrap(), e),
    }
}

fn unsubscribe(args: &ArgMatches, connection: &Connection) {
    use std::io;
    use std::io::prelude::*;
    let mut stdin = io::stdin();
    let id = args.value_of("id").unwrap();
    let mut buffer = [0;1];

    let mut stmt = connection.prepare("select id, url, label from subscription where id = ?1").unwrap();
    let mut rows = stmt.query_map(&[&id], Subscription::map).unwrap();
    match rows.next() {
        Some(row) => {
            let subscription = row.unwrap();
            println!("Unsubscribing from: {}", subscription.url);
            println!("Sure? [y/N]");
            stdin.read_exact(&mut buffer).unwrap();
            if buffer[0] == 121u8 { // 121 is ascii code for 'y'
                connection.execute("delete from subscription where id = ?1", &[&id]).unwrap();
                println!("Unsubscribed from: {}", subscription.url);
            }
        },
        None => println!("Subscription doesn't exist"),
    }
}

fn list(connection: &Connection) {
    let mut stmt = connection.prepare("select id, url, label from subscription").unwrap();

    println!("Subscriptions list:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    No subscription");
    }

    for row in stmt.query_map(&[], Subscription::map).unwrap() {
        let subscription = row.unwrap();
        println!("    {}: {} ({})", subscription.id, subscription.label, subscription.url);
    }
}

fn update(args: &ArgMatches, connection: &Connection, feed_id: i64) {
    use hyper::Client; // https://hyper.rs/hyper/v0.10.9/hyper/index.html
    use hyper::net::HttpsConnector;
    use hyper_native_tls::NativeTlsClient;
    use std::io::Read; // needed for read_to_string trait
    use std::str::FromStr; // needed for FromStr trait on Channel
    use rss::Channel;

    let mut stmt = connection.prepare("select id, url, label from subscription").unwrap();
     let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector); // create http client with tls support

    let mut as_downloaded = 0;
    if args.is_present("as-downloaded") {
        as_downloaded = 1;
    }

    for row in stmt.query_map(&[], Subscription::map).unwrap() {
        let subscription = row.unwrap();

        if feed_id != subscription.id && feed_id > 0 { // if an id is set, update/populate only this id
            continue;
        }

        println!("Updating {}:", subscription.label);

        let mut res = client.get(subscription.clone()).send().unwrap(); // get query result thanks to IntoUrl trait implement for Subscription
        let mut body = String::new();
        res.read_to_string(&mut body).unwrap(); // extract body from query result

        let channel = Channel::from_str(&body).unwrap(); // parse rss into channel
        connection.execute("update subscription set label = ?1 where id = ?2", &[&channel.title(), &subscription.id]).unwrap(); // update podcast feed name
        for item in channel.items() {
            let url = Url::parse(item.enclosure().unwrap().url()).unwrap();
            let  path_segments = url.path_segments().unwrap();

            // create filename
            let mut filename = String::new();
            for segment in path_segments {
                if segment == "enclosure.mp3" || segment == "listen.mp3" {
                    filename = filename + ".mp3";
                    break;
                }
                filename = segment.to_string();
            }

            let podcast = Podcast { id: 0, subscription_id: subscription.id, url: url.as_str().to_string(), filename: filename};
            let previous_insert_rowid = connection.last_insert_rowid();
            connection.execute("insert or ignore into podcast (subscription_id, url, filename, downloaded) values (?1, ?2, ?3, ?4)", &[&podcast.subscription_id, &podcast.url, &podcast.filename, &as_downloaded]).unwrap();
            if previous_insert_rowid != connection.last_insert_rowid() {
                println!("    New podcast added: {:?}", podcast.filename);
            }

        }
        println!("Updated");
    }
}

fn pending(connection: &Connection) {
    let mut stmt = connection.prepare("select id, subscription_id, url, filename from podcast where downloaded = 0").unwrap();

    println!("Pending list:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    Nothing to download");
    }
    for row in stmt.query_map(&[], Podcast::map).unwrap(){
        let podcast = row.unwrap();
        println!("    {} ({})", podcast.filename, podcast.url);
    }
}

fn downloaddir(args: &ArgMatches, connection: &Connection) {
    let path = args.value_of("path").unwrap();
    connection.execute("insert or replace into config (key, value) values ('downloaddir', ?1)", &[&path]).unwrap();
    println!("Download dir set to: {}", path);
}

fn download(connection: &Connection) {
    use std::io::Write;
    use std::ops::Mul;
    let mut curl = Easy::new();

    curl.progress(true).unwrap();
    curl.follow_location(true).unwrap();
    curl.progress_function( |a, b, _, _| {
        print!("    Downloading: {}%      \r", (b.mul(100_f64)/a).floor());
        true
    }).unwrap();
    let mut stmt = connection.prepare("select id, subscription_id, url, filename from podcast where downloaded = 0").unwrap();

    println!("Download pending podcast:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    Nothing to download");
    }
    for row in stmt.query_map(&[], Podcast::map).unwrap(){
        let podcast = row.unwrap();
        let temp = "/tmp/".to_string() + podcast.filename.as_str();
        let path = Path::new(&temp);

        let mut file = match File::create(&path) {
            Err(why) => panic!("couldn't create {}",
                               why),
            Ok(file) => file,
        };
        curl.url(&podcast.url).unwrap();
        curl.write_function( move |data| {
            Ok(file.write(data).unwrap())
        }).unwrap();
        println!("    {}", podcast.filename);
        curl.perform().unwrap();
        connection.execute("update podcast set downloaded = 1, downloaded_at = current_timestamp where id = ?1", &[&podcast.id]).unwrap();
    }
}
<commit_msg>Fix bug preventing adding new podcast<commit_after>#[macro_use] extern crate clap;
extern crate url;
extern crate rss;
extern crate hyper;
extern crate hyper_native_tls;
extern crate curl; // https://docs.rs/curl/0.4.6/curl/easy/
extern crate rusqlite;

mod schema;
mod common;

use std::process;
use clap::{App, ArgMatches};
use schema::*;
use url::Url;
use rusqlite::Connection;
use curl::easy::Easy;
use std::path::{Path};
use std::fs::{File, create_dir};
use std::env::home_dir;

fn main() {
    let lock_socket = common::create_app_lock(12345); // https://rosettacode.org/wiki/Category:Rust

    // Clap
    let yaml = load_yaml!("cli.yml");
    let matches = App::from_yaml(yaml).get_matches();

    // Init Sqlite database
    let mut database_url = String::new();

    if matches.is_present("database") { // get path from command line
        database_url = matches.value_of("database").unwrap().to_string();
    } else { // or put database file into ~/.config/rusty/rusty.sqlite3
        let db_path = home_dir().expect("/tmp/").into_os_string().into_string().unwrap() + "/.config/rusty";
        if !Path::new(&db_path).exists() { // If path doesn't exist we create it
            create_dir(&db_path).unwrap();
        }
        database_url = db_path + "/rusty.sqlite3";
    }
    let connection = match Connection::open(&database_url) {
        Ok(connection) => connection,
        Err(e) => {
            println!("Error database connection: {} {}", e, database_url);
            process::exit(1)
        }
    };
    init(&connection); // Initialize database

    match matches.subcommand() {
        ("subscribe", Some(sub_matches)) => subscribe(sub_matches, &connection),
        ("unsubscribe", Some(sub_matches)) => unsubscribe(sub_matches, &connection),
        ("list", Some(_)) => list(&connection),
        ("update", Some(sub_matches)) => {
            let feed_id = sub_matches.value_of("id").unwrap_or("0").parse::<i64>().unwrap();
            update(sub_matches, &connection, feed_id);
        },
        ("pending", Some(_)) => pending(&connection),
        ("download", Some(_)) => download(&connection),
        ("download-dir", Some(sub_matches)) => downloaddir(sub_matches, &connection),
        ("", None) => println!("No subcommand was used"),
        _ => println!("No!"),
    }

    common::remove_app_lock(lock_socket);
}

fn init(connection: &Connection) {
    // execute only the first query if there are several queries. So we need to used split()
    let split = include_str!("sql/init.sql").split(";");
    for s in split {
        if s.trim() == "" {
            continue;
        }
        connection.execute(s, &[]).unwrap();
    }
}

fn subscribe(args: &ArgMatches, connection: &Connection) {
    match Url::parse(args.value_of("url").unwrap()) {
        Ok(url) => {
            let subscription = Subscription { id: 0, url: url.as_str().to_string(), label: String::new()};
            println!("Subscribing to: {}", subscription.url);

            let mut stmt = connection.prepare("select * from subscription where url = ?1").unwrap();
            if !stmt.exists(&[&subscription.url]).unwrap() {
                connection.execute("insert into subscription (url, label) values (?1, '')", &[&subscription.url]).unwrap();
            } else {
                println!("    Feed already subscribed to");
                return
            }
            update(args, connection, connection.last_insert_rowid());
            println!("Subscribed");
        }
        Err(e) => println!("Could not parse url '{}' {}", args.value_of("url").unwrap(), e),
    }
}

fn unsubscribe(args: &ArgMatches, connection: &Connection) {
    use std::io;
    use std::io::prelude::*;
    let mut stdin = io::stdin();
    let id = args.value_of("id").unwrap();
    let mut buffer = [0;1];

    let mut stmt = connection.prepare("select id, url, label from subscription where id = ?1").unwrap();
    let mut rows = stmt.query_map(&[&id], Subscription::map).unwrap();
    match rows.next() {
        Some(row) => {
            let subscription = row.unwrap();
            println!("Unsubscribing from: {}", subscription.url);
            println!("Sure? [y/N]");
            stdin.read_exact(&mut buffer).unwrap();
            if buffer[0] == 121u8 { // 121 is ascii code for 'y'
                connection.execute("delete from subscription where id = ?1", &[&id]).unwrap();
                println!("Unsubscribed from: {}", subscription.url);
            }
        },
        None => println!("Subscription doesn't exist"),
    }
}

fn list(connection: &Connection) {
    let mut stmt = connection.prepare("select id, url, label from subscription").unwrap();

    println!("Subscriptions list:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    No subscription");
    }

    for row in stmt.query_map(&[], Subscription::map).unwrap() {
        let subscription = row.unwrap();
        println!("    {}: {} ({})", subscription.id, subscription.label, subscription.url);
    }
}

fn update(args: &ArgMatches, connection: &Connection, feed_id: i64) {
    use hyper::Client; // https://hyper.rs/hyper/v0.10.9/hyper/index.html
    use hyper::net::HttpsConnector;
    use hyper_native_tls::NativeTlsClient;
    use std::io::Read; // needed for read_to_string trait
    use std::str::FromStr; // needed for FromStr trait on Channel
    use rss::Channel;

    let mut stmt = connection.prepare("select id, url, label from subscription").unwrap();
     let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector); // create http client with tls support

    let mut as_downloaded = 0;
    if args.is_present("as-downloaded") {
        as_downloaded = 1;
    }

    for row in stmt.query_map(&[], Subscription::map).unwrap() {
        let subscription = row.unwrap();

        if feed_id != subscription.id && feed_id > 0 { // if an id is set, update/populate only this id
            continue;
        }

        println!("Updating {}:", subscription.label);

        let mut res = client.get(subscription.clone()).send().unwrap(); // get query result thanks to IntoUrl trait implement for Subscription
        let mut body = String::new();
        let mut previous_insert_rowid: i64 = 0;
        res.read_to_string(&mut body).unwrap(); // extract body from query result

        let channel = Channel::from_str(&body).unwrap(); // parse rss into channel
        connection.execute("update subscription set label = ?1 where id = ?2", &[&channel.title(), &subscription.id]).unwrap(); // update podcast feed name
        for item in channel.items() {
            let url = Url::parse(item.enclosure().unwrap().url()).unwrap();
            let path_segments = url.path_segments().unwrap();

            // create filename
            let mut filename = String::new();
            for segment in path_segments {
                if segment == "enclosure.mp3" || segment == "listen.mp3" {
                    filename = filename + ".mp3";
                    break;
                }
                filename = segment.to_string();
            }

            let podcast = Podcast { id: 0, subscription_id: subscription.id, url: url.as_str().to_string(), filename: filename};
            connection.execute("insert or ignore into podcast (subscription_id, url, filename, downloaded) values (?1, ?2, ?3, ?4)", &[&podcast.subscription_id, &podcast.url, &podcast.filename, &as_downloaded]).unwrap();
            if previous_insert_rowid != connection.last_insert_rowid() {
                println!("    New podcast added: {:?}", podcast.filename);
            }
            previous_insert_rowid = connection.last_insert_rowid();
        }
        println!("Updated");
    }
}

fn pending(connection: &Connection) {
    let mut stmt = connection.prepare("select id, subscription_id, url, filename from podcast where downloaded = 0").unwrap();

    println!("Pending list:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    Nothing to download");
    }
    for row in stmt.query_map(&[], Podcast::map).unwrap(){
        let podcast = row.unwrap();
        println!("    {} ({})", podcast.filename, podcast.url);
    }
}

fn downloaddir(args: &ArgMatches, connection: &Connection) {
    let path = args.value_of("path").unwrap();
    connection.execute("insert or replace into config (key, value) values ('downloaddir', ?1)", &[&path]).unwrap();
    println!("Download dir set to: {}", path);
}

fn download(connection: &Connection) {
    use std::io::Write;
    use std::ops::Mul;
    let mut curl = Easy::new();

    curl.progress(true).unwrap();
    curl.follow_location(true).unwrap();
    curl.progress_function( |a, b, _, _| {
        print!("    Downloading: {}%      \r", (b.mul(100_f64)/a).floor());
        true
    }).unwrap();
    let mut stmt = connection.prepare("select id, subscription_id, url, filename from podcast where downloaded = 0").unwrap();

    println!("Download pending podcast:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    Nothing to download");
    }
    for row in stmt.query_map(&[], Podcast::map).unwrap(){
        let podcast = row.unwrap();
        let temp = "/tmp/".to_string() + podcast.filename.as_str();
        let path = Path::new(&temp);

        let mut file = match File::create(&path) {
            Err(why) => panic!("couldn't create {}",
                               why),
            Ok(file) => file,
        };
        curl.url(&podcast.url).unwrap();
        curl.write_function( move |data| {
            Ok(file.write(data).unwrap())
        }).unwrap();
        println!("    {}", podcast.filename);
        curl.perform().unwrap();
        connection.execute("update podcast set downloaded = 1, downloaded_at = current_timestamp where id = ?1", &[&podcast.id]).unwrap();
    }
}
<|endoftext|>


<commit_before>#![feature(step_by)]
#![feature(str_char)]
#![feature(non_ascii_idents)]

extern crate libc;
extern crate ncurses;
extern crate unicode_width;

use std::thread;
use locale::Category;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

mod locale;

fn main() {
    locale::setlocale(Category::All, "");
    let scr = ncurses::initscr();

    unsafe {
        libc::signal(libc::SIGINT, libc::SIG_IGN);
    }

    ncurses::noecho();
    ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
    ncurses::nodelay(scr, true);
    ncurses::leaveok(scr, true);
    ncurses::scrollok(scr, false);

    for x in (ncurses::COLS - 1 ..).step_by(-1) {
        ncurses::clear();
        match render_般若心経(100, 100, x, true) {
            Err(()) => panic!(),
            Ok(false) => break,
            Ok(true) => {}
        }
        ncurses::getch();
        ncurses::refresh();
        thread::sleep_ms(4);
    }

    ncurses::mvcur(0, ncurses::COLS - 1, ncurses::LINES - 1, 0);
    ncurses::endwin();
}

fn mvaddstr(y: i32, x: i32, s: &str) -> Result<(), ()> {
    if ncurses::mvaddstr(y, x, s) == ncurses::ERR {
        Err(())
    } else {
        Ok(())
    }
}

fn trim_right_by_width(mut s: &str, width: usize, is_cjk: bool) -> (&str, usize) {
    let mut w = 0;
    while w < width {
        if s.is_empty() { return (s, w) }
        let range = s.char_range_at_reverse(s.len());
        w += if is_cjk {
            range.ch.width_cjk().unwrap()
        } else {
            UnicodeWidthChar::width(range.ch).unwrap()
        };
        s = &s[.. range.next];
    }
    assert!(w >= width);
    (s, w)
}

fn trim_left_by_width(mut s: &str, width: usize, is_cjk: bool) -> (&str, usize) {
    if s.is_empty() { return (s, 0) }

    let mut w = 0;
    while w < width {
        if s.is_empty() { return (s, w) }
        let range = s.char_range_at(0);
        w += if is_cjk {
            range.ch.width_cjk().unwrap()
        } else {
            UnicodeWidthChar::width(range.ch).unwrap()
        };
        s = &s[range.next ..];
    }
    assert!(w >= width);
    (s, w)
}

fn slice_by_width(s: &str, width: usize, is_cjk: bool) -> (&str, &str, usize) {
    let mut taken_width = 0;
    let mut i = 0;
    loop {
        let range = s.char_range_at(i);

        let w = if is_cjk {
            range.ch.width_cjk().unwrap()
        } else {
            UnicodeWidthChar::width(range.ch).unwrap()
        };
        if w + taken_width > width {
            break
        }

        taken_width += w;
        i = range.next;
        if i >= s.len() { break }
    }

    assert!(taken_width <= width);
    (&s[.. i], &s[i ..], taken_width)
}

fn split_by_width(mut s: &str, width: usize, is_cjk: bool) -> Vec<&str> {
    let mut result = vec![];

    while !s.is_empty() {
        let (line, rest, _) = slice_by_width(s, width, is_cjk);
        result.push(line);
        s = rest;
    }
    result
}

fn render_line(y: i32, x: i32, mut line: &str, is_cjk: bool) -> Result<bool, ()> {
    if line.is_empty() { return Ok(false) }
    if x >= ncurses::COLS { return Ok(true) }
    if y >= ncurses::LINES || y < 0 { return Ok(false) }

    let render_width = (ncurses::COLS - x) as usize;

    let w = if is_cjk {
        line.width_cjk()
    } else {
        UnicodeWidthStr::width(line)
    };
    // let w = line.width(is_cjk);
    if w > render_width {
        line = trim_right_by_width(line, w - render_width, is_cjk).0;
        if line.is_empty() { return Ok(true) }
    }

    if x < 0 {
        let (line, w) = trim_left_by_width(line, (-x) as usize, is_cjk);
        if line.is_empty() { return Ok(false) }

        try!(mvaddstr(y, (w as i32) + x, line));
    } else {
        try!(mvaddstr(y, x, line));
    }

    Ok(true)
}

fn render_般若心経(repeat: usize, width: usize, x0: i32, is_cjk: bool) -> Result<bool, ()> {
    let s = 般若心経.replace("\n", "");
    let lines = split_by_width(&s, width, is_cjk);

    let num_line_blocks = (ncurses::LINES as usize) / (lines.len() + 1);
    let (d, m) = (repeat / num_line_blocks, repeat % num_line_blocks);

    let y0 = (ncurses::LINES - ((num_line_blocks * (lines.len() + 1)) as i32)) / 2;
    let mut cont = false;
    for i in 0 .. num_line_blocks {
        let mut num_column_blocks = d;
        if i < m { num_column_blocks += 1; }
        for j in 0 .. num_column_blocks {
            let x = x0 + ((width + 1) * j) as i32;
            let y = y0 + (i * (lines.len() + 1)) as i32;
            for (dy, line) in lines.iter().enumerate() {
                cont |= try!(render_line(y + (dy as i32), x, &line, is_cjk));
            }
        }
    }
    Ok(cont)
}

const 般若心経: &'static str = "
摩訶般若波羅蜜多心経
観自在菩薩行深般若波羅蜜多時照見五
蘊皆空度一切苦厄舎利子色不異空空不
異色色即是空空即是色受想行識亦復如
是舎利子是諸法空相不生不滅不垢不浄
不増不減是故空中無色無受想行識無眼
耳鼻舌身意無色声香味触法無眼界乃至
無意識界無無明亦無無明尽乃至無老死
亦無老死尽無苦集滅道無智亦無得以無
所得故菩提薩埵依般若波羅蜜多故心無
罣礙無罣礙故無有恐怖遠離一切顛倒夢
想究竟涅槃三世諸仏依般若波羅蜜多故
得阿耨多羅三藐三菩提故知般若波羅蜜
多是大神咒是大明咒是無上咒是無等等
咒能除一切苦真実不虚故説般若波羅蜜
多咒即説咒曰
掲諦掲諦波羅掲諦波羅僧掲諦菩提薩婆訶
般若心経";
<commit_msg>Stop using deprecated function<commit_after>#![feature(step_by)]
#![feature(str_char)]
#![feature(non_ascii_idents)]

extern crate libc;
extern crate ncurses;
extern crate unicode_width;

use std::thread;
use std::time::Duration;
use locale::Category;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

mod locale;

fn main() {
    locale::setlocale(Category::All, "");
    let scr = ncurses::initscr();

    unsafe {
        libc::signal(libc::SIGINT, libc::SIG_IGN);
    }

    ncurses::noecho();
    ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
    ncurses::nodelay(scr, true);
    ncurses::leaveok(scr, true);
    ncurses::scrollok(scr, false);

    for x in (ncurses::COLS - 1 ..).step_by(-1) {
        ncurses::clear();
        match render_般若心経(100, 100, x, true) {
            Err(()) => panic!(),
            Ok(false) => break,
            Ok(true) => {}
        }
        ncurses::getch();
        ncurses::refresh();
        thread::sleep(Duration::from_millis(4));
    }

    ncurses::mvcur(0, ncurses::COLS - 1, ncurses::LINES - 1, 0);
    ncurses::endwin();
}

fn mvaddstr(y: i32, x: i32, s: &str) -> Result<(), ()> {
    if ncurses::mvaddstr(y, x, s) == ncurses::ERR {
        Err(())
    } else {
        Ok(())
    }
}

fn trim_right_by_width(mut s: &str, width: usize, is_cjk: bool) -> (&str, usize) {
    let mut w = 0;
    while w < width {
        if s.is_empty() { return (s, w) }
        let range = s.char_range_at_reverse(s.len());
        w += if is_cjk {
            range.ch.width_cjk().unwrap()
        } else {
            UnicodeWidthChar::width(range.ch).unwrap()
        };
        s = &s[.. range.next];
    }
    assert!(w >= width);
    (s, w)
}

fn trim_left_by_width(mut s: &str, width: usize, is_cjk: bool) -> (&str, usize) {
    if s.is_empty() { return (s, 0) }

    let mut w = 0;
    while w < width {
        if s.is_empty() { return (s, w) }
        let range = s.char_range_at(0);
        w += if is_cjk {
            range.ch.width_cjk().unwrap()
        } else {
            UnicodeWidthChar::width(range.ch).unwrap()
        };
        s = &s[range.next ..];
    }
    assert!(w >= width);
    (s, w)
}

fn slice_by_width(s: &str, width: usize, is_cjk: bool) -> (&str, &str, usize) {
    let mut taken_width = 0;
    let mut i = 0;
    loop {
        let range = s.char_range_at(i);

        let w = if is_cjk {
            range.ch.width_cjk().unwrap()
        } else {
            UnicodeWidthChar::width(range.ch).unwrap()
        };
        if w + taken_width > width {
            break
        }

        taken_width += w;
        i = range.next;
        if i >= s.len() { break }
    }

    assert!(taken_width <= width);
    (&s[.. i], &s[i ..], taken_width)
}

fn split_by_width(mut s: &str, width: usize, is_cjk: bool) -> Vec<&str> {
    let mut result = vec![];

    while !s.is_empty() {
        let (line, rest, _) = slice_by_width(s, width, is_cjk);
        result.push(line);
        s = rest;
    }
    result
}

fn render_line(y: i32, x: i32, mut line: &str, is_cjk: bool) -> Result<bool, ()> {
    if line.is_empty() { return Ok(false) }
    if x >= ncurses::COLS { return Ok(true) }
    if y >= ncurses::LINES || y < 0 { return Ok(false) }

    let render_width = (ncurses::COLS - x) as usize;

    let w = if is_cjk {
        line.width_cjk()
    } else {
        UnicodeWidthStr::width(line)
    };
    // let w = line.width(is_cjk);
    if w > render_width {
        line = trim_right_by_width(line, w - render_width, is_cjk).0;
        if line.is_empty() { return Ok(true) }
    }

    if x < 0 {
        let (line, w) = trim_left_by_width(line, (-x) as usize, is_cjk);
        if line.is_empty() { return Ok(false) }

        try!(mvaddstr(y, (w as i32) + x, line));
    } else {
        try!(mvaddstr(y, x, line));
    }

    Ok(true)
}

fn render_般若心経(repeat: usize, width: usize, x0: i32, is_cjk: bool) -> Result<bool, ()> {
    let s = 般若心経.replace("\n", "");
    let lines = split_by_width(&s, width, is_cjk);

    let num_line_blocks = (ncurses::LINES as usize) / (lines.len() + 1);
    let (d, m) = (repeat / num_line_blocks, repeat % num_line_blocks);

    let y0 = (ncurses::LINES - ((num_line_blocks * (lines.len() + 1)) as i32)) / 2;
    let mut cont = false;
    for i in 0 .. num_line_blocks {
        let mut num_column_blocks = d;
        if i < m { num_column_blocks += 1; }
        for j in 0 .. num_column_blocks {
            let x = x0 + ((width + 1) * j) as i32;
            let y = y0 + (i * (lines.len() + 1)) as i32;
            for (dy, line) in lines.iter().enumerate() {
                cont |= try!(render_line(y + (dy as i32), x, &line, is_cjk));
            }
        }
    }
    Ok(cont)
}

const 般若心経: &'static str = "
摩訶般若波羅蜜多心経
観自在菩薩行深般若波羅蜜多時照見五
蘊皆空度一切苦厄舎利子色不異空空不
異色色即是空空即是色受想行識亦復如
是舎利子是諸法空相不生不滅不垢不浄
不増不減是故空中無色無受想行識無眼
耳鼻舌身意無色声香味触法無眼界乃至
無意識界無無明亦無無明尽乃至無老死
亦無老死尽無苦集滅道無智亦無得以無
所得故菩提薩埵依般若波羅蜜多故心無
罣礙無罣礙故無有恐怖遠離一切顛倒夢
想究竟涅槃三世諸仏依般若波羅蜜多故
得阿耨多羅三藐三菩提故知般若波羅蜜
多是大神咒是大明咒是無上咒是無等等
咒能除一切苦真実不虚故説般若波羅蜜
多咒即説咒曰
掲諦掲諦波羅掲諦波羅僧掲諦菩提薩婆訶
般若心経";
<|endoftext|>


<commit_before>#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate clap;

extern crate qrcode;
extern crate termion;
extern crate image;
extern crate regex;

use clap::{App, Arg, SubCommand, AppSettings};
use qrcode::{QrCode, EcLevel};
use termion::color;
use image::GrayImage;

mod payloads;

const WIFI_COMMAND: &'static str = "wifi";
const MAIL_COMMAND: &'static str = "mail";
// const SMS_COMMAND: &'static str = "sms";
// const MMS_COMMAND: &'static str = "mms";
// const GEO_COMMAND: &'static str = "geo";
const PHONE_COMMAND: &'static str = "phone";
// const SKYPE_COMMAND: &'static str = "skype";
const URL_COMMAND: &'static str = "url";
// const BOOKMARK_COMMAND: &'static str = "bookmark";
// const BITCOIN_COMMAND: &'static str = "bitcoin";
// const GIRO_COMMAND: &'static str = "giro";
// const CALENDAR_COMMAND: &'static str = "calendar";

/*TODO: add arguments for:
- Add help texts for the subcommands
- Add error texts for some exit branches (mostly file output and qrcode gen)
- shell completions (see build.rasst file)
- implement different types of qr payloads
*/
fn main() {
    // create the cli interface with all subcommands, flags and args
    let app = App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!("\n"))
        .about(crate_description!())
        .global_setting(AppSettings::SubcommandsNegateReqs)
        .arg(Arg::with_name("safe_zone")
            .global(true)
            .required(false)
            .short("s")
            .long("safe-zone")
            .help("Sets wether the safe zone around the code should be drawn or not.")
            .takes_value(false)
            .multiple(false))
        .arg(Arg::with_name("output")
            .global(true)
            .short("o").long("output")
            .help("Prints the QR-Code to a file. The image format is derived from the file extension. Currently only jpeg and png files are supported.")
            .takes_value(true)
            .value_name("FILE")
            .multiple(false))
        .arg(Arg::with_name("error")
            .global(true)
            .short("e").long("error")
            .help("Set the desired error correction level")
            .takes_value(true)
            .value_name("LEVEL")
            .possible_values(&["L", "M", "Q", "H"])
            .default_value("H"))
        .arg(Arg::with_name("INPUT")
            .help("The input string to use")
            .required(true))
        .subcommand(SubCommand::with_name(WIFI_COMMAND)
            .about("formats to a wifi access string")
            .arg(Arg::with_name("ssid").required(true))
            .arg(Arg::with_name("pwd").required(true))
            .arg(Arg::with_name("mode")
                .possible_values(&["WEP", "WPA", "nopass"])
                .default_value("WPA")
                .value_name("MODE"))
            .arg(Arg::with_name("hidden")
                .required(false)
                .possible_values(&["true", "false"])
                .default_value("false")
                .value_name("HIDDEN")))
        .subcommand(SubCommand::with_name(MAIL_COMMAND)
            .about("formats to a mail adress string")
            .arg(Arg::with_name("receiver").required(true))
            .arg(Arg::with_name("subject"))
            .arg(Arg::with_name("message"))
            .arg(Arg::with_name("encoding")
                .possible_values(&["MAILTO", "MATMSG", "SMTP"])
                .default_value("MAILTO")
                .value_name("ENCODING")))
        .subcommand(SubCommand::with_name(URL_COMMAND)
            .about("formats to an URL")
            .arg(Arg::with_name("url")
                .required(true)
                .value_name("URL")))
        .subcommand(SubCommand::with_name(PHONE_COMMAND)
            .about("formats to a phone number")
            .arg(Arg::with_name("number")
                .required(true)
                .value_name("NUMBER"))
        );

    let matches = app.get_matches();

    // deduce the string payload
    let payload = get_payload(&matches);
    // what error level can we expect? always defaults to "H"
    let error: EcLevel = match matches.value_of("error").unwrap() {
        "L" => EcLevel::L,
        "M" => EcLevel::M,
        "Q" => EcLevel::Q,
        &_ => EcLevel::H,
    };

    //TODO: catch the possible errors
    let code = QrCode::with_error_correction_level(payload, error).unwrap();

    // are we drawing to the terminal or to a file?
    match matches.occurrences_of("output") {
        1 => save(&code, safe, matches.value_of("output").unwrap()),
        _ => draw(&code, safe),
    }
}

// deduces wich kind of string we are going to encode
fn get_payload(matches: &clap::ArgMatches) -> String {
    if let Some(sub) = matches.subcommand_matches(WIFI_COMMAND) {
        let auth = match sub.value_of("mode") {
            Some("WEP") => payloads::Authentication::WEP,
            Some("WPA") => payloads::Authentication::WPA,
            _ => payloads::Authentication::nopass,
        };
        return payloads::wifi_string(sub.value_of("ssid").unwrap(),
                                     sub.value_of("pwd").unwrap(),
                                     &auth,
                                     sub.value_of("hidden").unwrap() == "true");
    } else if let Some(sub) = matches.subcommand_matches(MAIL_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("MATMSG") => payloads::MailEncoding::MATMSG,
            Some("SMTP") => payloads::MailEncoding::SMTP,
            _ => payloads::MailEncoding::MAILTO,
        };
        return payloads::mail_string(sub.value_of("receiver").unwrap(),
                                     sub.value_of("subject").unwrap(),
                                     sub.value_of("message").unwrap(),
                                     &encoding);
    } else if let Some(sub) = matches.subcommand_matches(URL_COMMAND) {
        return payloads::url_string(sub.value_of("url").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(PHONE_COMMAND) {
        return payloads::phone_string(sub.value_of("phone").unwrap());
    } else {
        return String::from(matches.value_of("INPUT").unwrap());
    }
}

    let safe: bool = match matches.occurrences_of("safe_zone") {
        0 => true,
        _ => false,
    };
// save to a file at the path
fn save(code: &QrCode, safe: bool, path: &str) {
    // render to a image struct
    let image: GrayImage = code.render().quiet_zone(safe).to_image();

    // save the image
    match image.save(path) {
        Ok(..) => println!("Image successfully saved to: {:?}", path),
        Err(e) => println!("Tried to create file but there was a problem: {:?}",
            if let Some(inner_err) = e.get_ref() { inner_err.to_string() } else { e.to_string() }),
    };
}

// draw to the terminal
fn draw(code: &QrCode, safe: bool) {
    // get "bit" array
    let bit_array = code.to_vec();

    // get the code width and add extra space for the safe zone
    let w = code.width();
    let wide = w + 6;

    // draw the first white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    // main drawing loop
    for (i, item) in bit_array.iter().enumerate() {
        // left safe zone
        if safe && i % w == 0 {
            print!("{}      ", color::Bg(color::LightWhite));
        }

        // draw black or white blocks
        if *item {
            print!("{}  ", color::Bg(color::Black));
        } else {
            print!("{}  ", color::Bg(color::LightWhite));
        }

        if (i + 1) % w == 0 {
            if safe {
                //draw right safe zone
                println!("{}      {}",
                         color::Bg(color::LightWhite),
                         color::Bg(color::Reset));
            } else {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    //Draw the last white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }
}
<commit_msg>changed subcommand strings<commit_after>#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate clap;

extern crate qrcode;
extern crate termion;
extern crate image;
extern crate regex;

use clap::{App, Arg, SubCommand, AppSettings};
use qrcode::{QrCode, EcLevel};
use termion::color;
use image::GrayImage;

mod payloads;

const WIFI_COMMAND: &'static str = "wifi";
const MAIL_COMMAND: &'static str = "mail";
// const SMS_COMMAND: &'static str = "sms";
// const MMS_COMMAND: &'static str = "mms";
// const GEO_COMMAND: &'static str = "geo";
const PHONE_COMMAND: &'static str = "phone";
// const SKYPE_COMMAND: &'static str = "skype";
const URL_COMMAND: &'static str = "url";
// const BOOKMARK_COMMAND: &'static str = "bookmark";
// const BITCOIN_COMMAND: &'static str = "bitcoin";
// const GIRO_COMMAND: &'static str = "giro";
// const CALENDAR_COMMAND: &'static str = "calendar";

/*TODO: add arguments for:
- Add help texts for the subcommands
- Add error texts for some exit branches (mostly file output and qrcode gen)
- shell completions (see build.rasst file)
- implement different types of qr payloads
*/
fn main() {
    // create the cli interface with all subcommands, flags and args
    let app = App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!("\n"))
        .about(crate_description!())
        .global_setting(AppSettings::SubcommandsNegateReqs)
        .arg(Arg::with_name("safe_zone")
            .global(true)
            .required(false)
            .short("s")
            .long("safe-zone")
            .help("Sets wether the safe zone around the code should be drawn or not.")
            .takes_value(false)
            .multiple(false))
        .arg(Arg::with_name("output")
            .global(true)
            .short("o").long("output")
            .help("Prints the QR-Code to a file. The image format is derived from the file extension. Currently only jpeg and png files are supported.")
            .takes_value(true)
            .value_name("FILE")
            .multiple(false))
        .arg(Arg::with_name("error")
            .global(true)
            .short("e").long("error")
            .help("Set the desired error correction level")
            .takes_value(true)
            .value_name("LEVEL")
            .possible_values(&["L", "M", "Q", "H"])
            .default_value("H"))
        .arg(Arg::with_name("INPUT")
            .help("The input string to use")
            .required(true))
        .subcommand(SubCommand::with_name(WIFI_COMMAND)
            .about("formats to a wifi access string QR-Code")
            .arg(Arg::with_name("ssid").required(true))
            .arg(Arg::with_name("pwd").required(true))
            .arg(Arg::with_name("mode")
                .possible_values(&["WEP", "WPA", "nopass"])
                .default_value("WPA")
                .value_name("MODE"))
            .arg(Arg::with_name("hidden")
                .required(false)
                .possible_values(&["true", "false"])
                .default_value("false")
                .value_name("HIDDEN")))
        .subcommand(SubCommand::with_name(MAIL_COMMAND)
            .about("formats to a mail adress string QR-Code")
            .arg(Arg::with_name("receiver").required(true))
            .arg(Arg::with_name("subject"))
            .arg(Arg::with_name("message"))
            .arg(Arg::with_name("encoding")
                .possible_values(&["MAILTO", "MATMSG", "SMTP"])
                .default_value("MAILTO")
                .value_name("ENCODING")))
        .subcommand(SubCommand::with_name(URL_COMMAND)
            .about("formats to an URL QR-Code")
            .arg(Arg::with_name("url")
                .required(true)
                .value_name("URL")))
        .subcommand(SubCommand::with_name(PHONE_COMMAND)
            .about("formats to a phone number QR-Code")
            .arg(Arg::with_name("number")
                .required(true)
                .value_name("NUMBER"))
        );

    let matches = app.get_matches();

    // deduce the string payload
    let payload = get_payload(&matches);
    // what error level can we expect? always defaults to "H"
    let error: EcLevel = match matches.value_of("error").unwrap() {
        "L" => EcLevel::L,
        "M" => EcLevel::M,
        "Q" => EcLevel::Q,
        &_ => EcLevel::H,
    };

    //TODO: catch the possible errors
    let code = QrCode::with_error_correction_level(payload, error).unwrap();

    // are we drawing to the terminal or to a file?
    match matches.occurrences_of("output") {
        1 => save(&code, safe, matches.value_of("output").unwrap()),
        _ => draw(&code, safe),
    }
}

// deduces wich kind of string we are going to encode
fn get_payload(matches: &clap::ArgMatches) -> String {
    if let Some(sub) = matches.subcommand_matches(WIFI_COMMAND) {
        let auth = match sub.value_of("mode") {
            Some("WEP") => payloads::Authentication::WEP,
            Some("WPA") => payloads::Authentication::WPA,
            _ => payloads::Authentication::nopass,
        };
        return payloads::wifi_string(sub.value_of("ssid").unwrap(),
                                     sub.value_of("pwd").unwrap(),
                                     &auth,
                                     sub.value_of("hidden").unwrap() == "true");
    } else if let Some(sub) = matches.subcommand_matches(MAIL_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("MATMSG") => payloads::MailEncoding::MATMSG,
            Some("SMTP") => payloads::MailEncoding::SMTP,
            _ => payloads::MailEncoding::MAILTO,
        };
        return payloads::mail_string(sub.value_of("receiver").unwrap(),
                                     sub.value_of("subject").unwrap(),
                                     sub.value_of("message").unwrap(),
                                     &encoding);
    } else if let Some(sub) = matches.subcommand_matches(URL_COMMAND) {
        return payloads::url_string(sub.value_of("url").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(PHONE_COMMAND) {
        return payloads::phone_string(sub.value_of("phone").unwrap());
    } else {
        return String::from(matches.value_of("INPUT").unwrap());
    }
}

    let safe: bool = match matches.occurrences_of("safe_zone") {
        0 => true,
        _ => false,
    };
// save to a file at the path
fn save(code: &QrCode, safe: bool, path: &str) {
    // render to a image struct
    let image: GrayImage = code.render().quiet_zone(safe).to_image();

    // save the image
    match image.save(path) {
        Ok(..) => println!("Image successfully saved to: {:?}", path),
        Err(e) => println!("Tried to create file but there was a problem: {:?}",
            if let Some(inner_err) = e.get_ref() { inner_err.to_string() } else { e.to_string() }),
    };
}

// draw to the terminal
fn draw(code: &QrCode, safe: bool) {
    // get "bit" array
    let bit_array = code.to_vec();

    // get the code width and add extra space for the safe zone
    let w = code.width();
    let wide = w + 6;

    // draw the first white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    // main drawing loop
    for (i, item) in bit_array.iter().enumerate() {
        // left safe zone
        if safe && i % w == 0 {
            print!("{}      ", color::Bg(color::LightWhite));
        }

        // draw black or white blocks
        if *item {
            print!("{}  ", color::Bg(color::Black));
        } else {
            print!("{}  ", color::Bg(color::LightWhite));
        }

        if (i + 1) % w == 0 {
            if safe {
                //draw right safe zone
                println!("{}      {}",
                         color::Bg(color::LightWhite),
                         color::Bg(color::Reset));
            } else {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    //Draw the last white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate num;
extern crate portaudio;
extern crate rtlsdr;

use portaudio as pa;
use rtlsdr::{RTLSDRDevice, RTLSDRError};
use num::{Num, Zero};
use num::complex::Complex;
use std::env;
use std::f32::consts::PI;
use std::error::Error;
use pa::error::Error as PAError;
use std::thread;
use std::sync::mpsc::channel;

// Make sure that the buffer size is radix-2, otherwise the read_sync function
// will fail with an error code of -8. I want the buffer sizes between the SDR
// and the audio sizes to match, but since we get two samples per read the
// buffer size needs to be double.
const SDR_BUF_SIZE: usize = 524288;

// The buffer size for the audio sink.
const AUDIO_BUF_SIZE: usize = 262144;

// Most other sample rates fail, but this one works for my particular device.
// I will investigate exactly what's happening here and generate a list of
// possible sample rates (if there are others besides this one).
const SDR_SAMPLE_RATE: u32 = 2400000;

// Here is the sample rate of the output waveform we'll try to use.
const AUDIO_SAMPLE_RATE: f32 = 48000.0;

// The number of channels being used for audio.
const CHANNELS: i32 = 2;

fn main() {
    match run() {
        Ok(()) => (),
        Err(e) => println!("ERROR: {}", e),
    }
}

struct RTLSDR {
    rtlsdr: RTLSDRDevice,
}

unsafe impl Send for RTLSDR {}

fn run() -> Result<(), Box<Error>> {
    let sdr_index: i32 = env::args()
        .nth(1)
        .expect("Please specify the SDR index.")
        .parse::<i32>()?;
    let fm_freq_mhz: f32 = env::args()
        .nth(2)
        .expect("Please specify a center frequency in MHz.")
        .parse::<f32>()?;
    let bandwidth: u32 = env::args()
        .nth(3)
        .expect("Please specify the bandwidth.")
        .parse::<u32>()?;

    // Set up the threading channels for communication between our two threads.
    let (dongle_tx, dongle_rx) = channel();
    let (demod_tx, demod_rx) = channel();

    // This thread only collects data off of the SDR as quickly as possible and
    // sends it out.
    thread::spawn(move || {
        // Initialize the SDR based on the user's input parameters.
        let fm_freq: u32 = (fm_freq_mhz * 1e6) as u32;
        let mut sdr =
            RTLSDR { rtlsdr: init_sdr(sdr_index, fm_freq, bandwidth).unwrap() };

        loop {
            let bytes = sdr.rtlsdr.read_sync(SDR_BUF_SIZE).unwrap();
            let iq_vec = read_samples(bytes).unwrap();
            dongle_tx.send(iq_vec).unwrap();
        }
    });

    // This thread does all of the signal processing required to demodulate
    // the signal and get it to a nice audio stream.
    thread::spawn(move || {
        // Decimating down to 100k samples seems to work the best. I don't
        // really quite get why that is, so I'll need to do more research.
        let dec_rate_filt = 6;
        let dec_rate_audio = 4;

        let taps_filt = windowed_sinc(
            bandwidth as f32,
            SDR_SAMPLE_RATE as f32,
            128,
            &hamming,
        );
        let taps_audio = windowed_sinc(
            bandwidth as f32 / dec_rate_filt as f32,
            SDR_SAMPLE_RATE as f32 / dec_rate_filt as f32,
            128,
            &hamming,
        );

        let mut prev = Complex::zero();
        loop {
            // First, low_pass and decimate the raw IQ signal.
            let mut iq_vec = dongle_rx.recv().unwrap();
            iq_vec = filter(&iq_vec, &taps_filt);
            iq_vec = decimate(iq_vec, dec_rate_filt);

            // Next, demodulate the signal and filter the signal again. The
            // second filter seems to help with the noise after demodulation.
            let res = demod_fm(iq_vec, prev);
            let mut demod_iq = filter_real(&res.0, &taps_audio);
            demod_iq = decimate(demod_iq, dec_rate_audio);
            prev = res.1;
            demod_tx.send(demod_iq).unwrap();
        }
    });

    // Initialize the PortAudio class. We use a blocking stream since we're
    // doing processing on the signal and can't keep up with non-blocking
    // timing requirements.
    let audio = try!(pa::PortAudio::new());
    let settings = try!(audio.default_output_stream_settings(
        CHANNELS,
        AUDIO_SAMPLE_RATE as f64,
        AUDIO_BUF_SIZE as u32,
    ));
    let mut stream = try!(audio.open_blocking_stream(settings));
    try!(stream.start());

    loop {
        let demod_iq = demod_rx.recv().unwrap();

        let out_frames = match stream.write_available() {
            Ok(available) => {
                match available {
                    pa::StreamAvailable::Frames(frames) => frames as u32,
                    pa::StreamAvailable::InputOverflowed => {
                        return Err(Box::new(PAError::InputOverflowed))
                    }
                    pa::StreamAvailable::OutputUnderflowed => {
                        return Err(Box::new(PAError::OutputUnderflowed))
                    }
                }
            }
            Err(e) => return Err(Box::new(e)),
        };

        let buffer_frames = (demod_iq.len() / CHANNELS as usize) as u32;
        let write_frames = std::cmp::min(buffer_frames, out_frames);
        let n_write_samples = write_frames as usize * CHANNELS as usize;

        stream.write(
            write_frames,
            |output| for ix in 0..n_write_samples {
                output[ix] = demod_iq[ix];
            },
        )?;
    }
}

fn init_sdr(
    sdr_index: i32,
    fm_freq: u32,
    bandwidth: u32,
) -> Result<RTLSDRDevice, RTLSDRError> {
    let mut sdr = try!(rtlsdr::open(sdr_index));
    try!(sdr.set_center_freq(fm_freq));
    try!(sdr.set_sample_rate(SDR_SAMPLE_RATE));
    try!(sdr.set_tuner_bandwidth(bandwidth));
    try!(sdr.set_agc_mode(true));
    try!(sdr.reset_buffer());
    Ok(sdr)
}

fn decimate<T: Copy>(signal: Vec<T>, dec_rate: usize) -> Vec<T> {
    let mut ix = 0;
    let new_size = (signal.len() / dec_rate + 1) as usize;
    let mut signal_dec = Vec::<T>::with_capacity(new_size);
    while ix < signal.len() {
        signal_dec.push(signal[ix]);
        ix += dec_rate;
    }
    signal_dec
}

fn read_samples(bytes: Vec<u8>) -> Option<Vec<Complex<f32>>> {
    // First, check that we've been given an even number of bytes. Not sure
    // what to do if we don't get I and Q.
    let bytes_len = bytes.len();
    if bytes_len % 2 != 0 {
        println!("Received an odd number of samples.");
        return None;

    }
    let mut iq_vec: Vec<Complex<f32>> = Vec::with_capacity(bytes_len / 2);

    // Write the values to the complex value and normalize from [0, 255] to
    // [-1, 1].
    for iq in bytes.chunks(2) {
        let iq_cmplx = Complex::new(
            (iq[0] as f32 - 127.0) / 127.0,
            (iq[1] as f32 - 127.0) / 127.0,
        );
        iq_vec.push(iq_cmplx);
    }
    Some(iq_vec)
}

fn demod_fm(
    iq: Vec<Complex<f32>>,
    prev: Complex<f32>,
) -> (Vec<f32>, Complex<f32>) {

    let mut p = prev.clone();
    let mut demod_queue: Vec<f32> = Vec::with_capacity(iq.len());

    let gain = SDR_SAMPLE_RATE as f32 / (2.0 * PI * 75e3);

    for samp in iq.iter() {
        let conj = p.conj() * samp;
        demod_queue.push(conj.arg() * gain);
        p = *samp;
    }
    (demod_queue, p)
}

///
/// Filters a Complex vector.
///
fn filter<T: Copy + Num + Zero>(
    samples: &Vec<Complex<T>>,
    taps: &Vec<T>,
) -> Vec<Complex<T>> {
    // We'll lose taps.len() - 1 samples from the filtering.
    let mut filt_samps: Vec<Complex<T>> =
        Vec::with_capacity(samples.len() - taps.len() + 1);

    for window in samples.windows(taps.len()) {
        let iter = window.iter().zip(taps.iter());
        let filt_samp = iter.fold(Complex::zero(), |acc, (x, y)| acc + *x * *y);
        filt_samps.push(filt_samp);
    }
    filt_samps
}

///
/// Filter a real vector.
///
fn filter_real<T: Copy + Num + Zero>(
    samples: &Vec<T>,
    taps: &Vec<T>,
) -> Vec<T> {
    // We'll lose taps.len() - 1 samples from the filtering.
    let mut filt_samps: Vec<T> =
        Vec::with_capacity(samples.len() - taps.len() + 1);
    for window in samples.windows(taps.len()) {
        let iter = window.iter().zip(taps.iter());
        let filt_samp = iter.fold(T::zero(), |acc, (x, y)| acc + *x * *y);
        filt_samps.push(filt_samp);
    }
    filt_samps
}

///
/// Generates taps for a Hamming window.
///
fn hamming(ntaps: usize) -> Vec<f32> {
    let m: f32 = ntaps as f32 - 1.0;
    let mut taps: Vec<f32> = Vec::with_capacity(ntaps);
    for ix in 0..ntaps {
        taps.push(0.54 - 0.46 * (2.0 * PI * ix as f32 / m).cos());
    }
    taps
}

///
/// Generates filter taps for a sinc filter with a Hamming window.
///
fn windowed_sinc(
    cutoff_freq: f32,
    sample_rate: f32,
    ntaps: usize,
    window: &Fn(usize) -> Vec<f32>,
) -> Vec<f32> {
    let wc: f32 = 2.0 * PI * cutoff_freq / sample_rate;
    let mut taps: Vec<f32> = Vec::with_capacity(ntaps);
    let win_taps: Vec<f32> = window(ntaps);
    let ntaps_i: i32 = ntaps as i32;
    let m: i32 = (ntaps_i - 1) / 2;

    for ix in -m..m + 1 {
        let ix_f: f32 = ix as f32;
        if ix == 0 {
            let hi = wc / PI * win_taps[(ix + m) as usize];
            taps.push(hi);
        } else {
            let hi = (ix_f * wc).sin() / (ix_f * PI) *
                win_taps[(ix + m) as usize];
            taps.push(hi);
        }
    }
    taps
}
<commit_msg> Some code renaming and playing with audio overflows<commit_after>extern crate num;
extern crate portaudio;
extern crate rtlsdr;

use portaudio as pa;
use rtlsdr::{RTLSDRDevice, RTLSDRError};
use num::{Num, Zero};
use num::complex::Complex;
use std::env;
use std::f32::consts::PI;
use std::error::Error;
use pa::error::Error as PAError;
use std::thread;
use std::sync::mpsc::channel;

// Make sure that the buffer size is radix-2, otherwise the read_sync function
// will fail with an error code of -8. I want the buffer sizes between the SDR
// and the audio sizes to match, but since we get two samples per read the
// buffer size needs to be double.
const SDR_BUF_SIZE: usize = 262144;

// The buffer size for the audio sink.
const AUDIO_BUF_SIZE: usize = 524288;

// Most other sample rates fail, but this one works for my particular device.
// I will investigate exactly what's happening here and generate a list of
// possible sample rates (if there are others besides this one).
const SDR_SAMPLE_RATE: u32 = 2400000;

// Here is the sample rate of the output waveform we'll try to use.
const AUDIO_SAMPLE_RATE: f32 = 48000.0;

// The number of channels being used for audio.
const CHANNELS: i32 = 2;

// We set the gain manually right now since it seems to be a better option
// than using the AGC (the SDR gets blazing hot with the AGC on).
const SDR_GAIN: i32 = 496;

fn main() {
    match run() {
        Ok(()) => (),
        Err(e) => println!("ERROR: {}", e),
    }
}

struct RTLSDR {
    rtlsdr: RTLSDRDevice,
}

unsafe impl Send for RTLSDR {}

fn run() -> Result<(), Box<Error>> {
    let sdr_index: i32 = env::args()
        .nth(1)
        .expect("Please specify the SDR index.")
        .parse::<i32>()?;
    let fm_freq_mhz: f32 = env::args()
        .nth(2)
        .expect("Please specify a center frequency in MHz.")
        .parse::<f32>()?;
    let bandwidth: u32 = env::args()
        .nth(3)
        .expect("Please specify the bandwidth.")
        .parse::<u32>()?;

    // Set up the threading channels for communication between our two threads.
    let (dongle_tx, dongle_rx) = channel();
    let (demod_tx, demod_rx) = channel();

    // This thread only collects data off of the SDR as quickly as possible and
    // sends it out.
    thread::spawn(move || {
        // Initialize the SDR based on the user's input parameters.
        let fm_freq: u32 = (fm_freq_mhz * 1e6) as u32;
        let mut sdr =
            RTLSDR { rtlsdr: init_sdr(sdr_index, fm_freq, bandwidth).unwrap() };

        loop {
            let bytes = sdr.rtlsdr.read_sync(SDR_BUF_SIZE).unwrap();
            let iq_vec = samps_to_cmplx(bytes).unwrap();
            dongle_tx.send(iq_vec).unwrap();
        }
    });

    // This thread does all of the signal processing required to demodulate
    // the signal and get it to a nice audio stream.
    thread::spawn(move || {
        // Decimating down to 100k samples seems to work the best. I don't
        // really quite get why that is, so I'll need to do more research.
        let dec_rate_filt = 6;
        let dec_rate_audio = 4;

        let taps_filt = windowed_sinc(
            bandwidth as f32,
            SDR_SAMPLE_RATE as f32,
            128,
            &hamming,
        );
        let taps_audio = windowed_sinc(
            bandwidth as f32 / dec_rate_filt as f32,
            SDR_SAMPLE_RATE as f32 / dec_rate_filt as f32,
            128,
            &hamming,
        );

        let mut prev = Complex::zero();
        loop {
            // First, low_pass and decimate the raw IQ signal.
            let mut iq_vec = dongle_rx.recv().unwrap();
            iq_vec = filter(&iq_vec, &taps_filt);
            iq_vec = decimate(iq_vec, dec_rate_filt);

            // Next, demodulate the signal and filter the signal again. The
            // second filter seems to help with the noise after demodulation.
            let res = demod_fm(iq_vec, prev);
            let mut demod_iq = filter_real(&res.0, &taps_audio);
            demod_iq = decimate(demod_iq, dec_rate_audio);
            prev = res.1;
            demod_tx.send(demod_iq).unwrap();
        }
    });

    // Initialize the PortAudio class. We use a blocking stream since we're
    // doing processing on the signal and can't keep up with non-blocking
    // timing requirements.
    let audio = try!(pa::PortAudio::new());
    let settings = try!(audio.default_output_stream_settings(
        CHANNELS,
        AUDIO_SAMPLE_RATE as f64,
        AUDIO_BUF_SIZE as u32,
    ));
    let mut stream = try!(audio.open_blocking_stream(settings));
    try!(stream.start());

    loop {
        let demod_iq = demod_rx.recv().unwrap();

        let out_frames = match stream.write_available() {
            Ok(available) => {
                match available {
                    pa::StreamAvailable::Frames(frames) => frames as u32,
                    pa::StreamAvailable::InputOverflowed => {
                        return Err(Box::new(PAError::InputOverflowed))
                    }
                    pa::StreamAvailable::OutputUnderflowed => {
                        return Err(Box::new(PAError::OutputUnderflowed))
                    }
                }
            }
            Err(e) => return Err(Box::new(e)),
        };

        let buffer_frames = (demod_iq.len() / CHANNELS as usize) as u32;
        let write_frames = std::cmp::min(buffer_frames, out_frames);
        let n_write_samples = write_frames as usize * CHANNELS as usize;
        if write_frames == 0 {
            println!("We can't write any more frames!");
        }

        stream.write(
            write_frames,
            |output| for ix in 0..n_write_samples {
                output[ix] = demod_iq[ix];
            },
        )?;
    }
}

fn init_sdr(
    sdr_index: i32,
    fm_freq: u32,
    bandwidth: u32,
) -> Result<RTLSDRDevice, RTLSDRError> {
    let mut sdr = try!(rtlsdr::open(sdr_index));
    try!(sdr.set_center_freq(fm_freq));
    try!(sdr.set_sample_rate(SDR_SAMPLE_RATE));
    try!(sdr.set_tuner_bandwidth(bandwidth));
    try!(sdr.set_tuner_gain(SDR_GAIN));
    try!(sdr.set_agc_mode(false));
    try!(sdr.reset_buffer());
    Ok(sdr)
}

fn decimate<T: Copy>(signal: Vec<T>, dec_rate: usize) -> Vec<T> {
    let mut ix = 0;
    let new_size = (signal.len() / dec_rate + 1) as usize;
    let mut signal_dec = Vec::<T>::with_capacity(new_size);
    while ix < signal.len() {
        signal_dec.push(signal[ix]);
        ix += dec_rate;
    }
    signal_dec
}

fn samps_to_cmplx(bytes: Vec<u8>) -> Option<Vec<Complex<f32>>> {
    // First, check that we've been given an even number of bytes. Not sure
    // what to do if we don't get I and Q.
    let bytes_len = bytes.len();
    if bytes_len % 2 != 0 {
        println!("Received an odd number of samples.");
        return None;

    }

    // Write the values to the complex value and normalize from [0, 255] to
    // [-1, 1].
    let mut iq_vec: Vec<Complex<f32>> = Vec::with_capacity(bytes_len / 2);
    for iq in bytes.chunks(2) {
        let iq_cmplx = Complex::new(
            (iq[0] as f32 - 127.0) / 127.0,
            (iq[1] as f32 - 127.0) / 127.0,
        );
        iq_vec.push(iq_cmplx);
    }
    Some(iq_vec)
}

fn demod_fm(
    iq: Vec<Complex<f32>>,
    prev: Complex<f32>,
) -> (Vec<f32>, Complex<f32>) {

    let mut p = prev.clone();
    let mut demod_queue: Vec<f32> = Vec::with_capacity(iq.len());

    let gain = SDR_SAMPLE_RATE as f32 / (2.0 * PI * 75e3);

    for samp in iq.iter() {
        let conj = p.conj() * samp;
        demod_queue.push(conj.arg() * gain);
        p = *samp;
    }
    (demod_queue, p)
}

///
/// Filters a Complex vector.
///
fn filter<T: Copy + Num + Zero>(
    samples: &Vec<Complex<T>>,
    taps: &Vec<T>,
) -> Vec<Complex<T>> {
    // We'll lose taps.len() - 1 samples from the filtering.
    let mut filt_samps: Vec<Complex<T>> =
        Vec::with_capacity(samples.len() - taps.len() + 1);

    for window in samples.windows(taps.len()) {
        let iter = window.iter().zip(taps.iter());
        let filt_samp = iter.fold(Complex::zero(), |acc, (x, y)| acc + *x * *y);
        filt_samps.push(filt_samp);
    }
    filt_samps
}

///
/// Filter a real vector.
///
fn filter_real<T: Copy + Num + Zero>(
    samples: &Vec<T>,
    taps: &Vec<T>,
) -> Vec<T> {
    // We'll lose taps.len() - 1 samples from the filtering.
    let mut filt_samps: Vec<T> =
        Vec::with_capacity(samples.len() - taps.len() + 1);
    for window in samples.windows(taps.len()) {
        let iter = window.iter().zip(taps.iter());
        let filt_samp = iter.fold(T::zero(), |acc, (x, y)| acc + *x * *y);
        filt_samps.push(filt_samp);
    }
    filt_samps
}

///
/// Generates taps for a Hamming window.
///
fn hamming(ntaps: usize) -> Vec<f32> {
    let m: f32 = ntaps as f32 - 1.0;
    let mut taps: Vec<f32> = Vec::with_capacity(ntaps);
    for ix in 0..ntaps {
        taps.push(0.54 - 0.46 * (2.0 * PI * ix as f32 / m).cos());
    }
    taps
}

///
/// Generates filter taps for a sinc filter with a Hamming window.
///
fn windowed_sinc(
    cutoff_freq: f32,
    sample_rate: f32,
    ntaps: usize,
    window: &Fn(usize) -> Vec<f32>,
) -> Vec<f32> {
    // Convert the frequency to radians and make it a ratio of the sample rate.
    let wc: f32 = 2.0 * PI * cutoff_freq / sample_rate;

    let win_taps: Vec<f32> = window(ntaps);
    let mut taps: Vec<f32> = Vec::with_capacity(ntaps);

    let ntaps_i: i32 = ntaps as i32;
    let m: i32 = (ntaps_i - 1) / 2;

    for ix in -m..m + 1 {
        let ix_f: f32 = ix as f32;

        // The windowed sinc function is undefined at zero, so set it to
        // something sensible here.
        if ix == 0 {
            let hi = wc / PI * win_taps[(ix + m) as usize];
            taps.push(hi);
        } else {
            let hi = (ix_f * wc).sin() / (ix_f * PI) *
                win_taps[(ix + m) as usize];
            taps.push(hi);
        }
    }
    taps
}
<|endoftext|>


<commit_before>use std::path::Path;
use std::fs::File;
use std::error::Error;
use std::env;
use std::io::BufReader;
use std::io::BufRead;
use std::io::Lines;
use std::collections::HashMap;

use CommandType::{ACommand, CCommand, LCommand};

enum CommandType {
	ACommand,
	CCommand,
	LCommand,
}

struct Parser {
	input_lines: Lines<BufReader<File>>,
	current_command: String,
}

impl Parser {
	/// Opens the input file/stream and gets ready to parse it.
	fn new(filename: &str) -> Parser {
		let path = Path::new(filename);
		let file = match File::open(&path) {
			Err(why) => panic!("couldn't open {}: {}", path.display(), Error::description(&why)),
			Ok(file) => file,
		};

		let lines = BufReader::new(file).lines(); // iterator
		Parser {
			input_lines: lines,
			current_command: String::new(),
		}
	}

	/// Contains the main program logc
	///
	/// TODO Consider moving it to a separte stand alone module.
	///
	///		Are there any more commands in the input?
	///			Reads the next command from the input and makes it the current command
	fn parse(&mut self) {
		loop {
			match self.input_lines.next() {
				Some(line) => {
					let content = line.unwrap().trim().to_string();

					// ignore line comments and empty lines
					if content.starts_with("//") || content == "\n" || content == "" {
						continue
					}

					// remove inline commnents
					let mut content_without_inline = String::new();
					if content.contains("//") {
						let v: Vec<&str> = content.split("//").collect();
						content_without_inline = v[0].trim().to_string();
					}

					// decide whether to use content_with_inline or content
					if content_without_inline != String::new() {
						self.current_command = content_without_inline;
					} else {
						self.current_command = content;
					}

					match self.command_type() {
						ACommand => {
							println!("ACommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());
						},
						CCommand => {
							println!("CCommand: {0}", self.current_command);
							println!("\tdest: {0}", self.dest());
							println!("\tcomp: {0}", self.comp());
							println!("\tjump: {0}", self.jump());
						},
						LCommand => {
							println!("LCommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());
						},
					}
				},
				None => break,
			}
		}
	}

	/// Returns the type of the current command.
	///
	/// * ACommand: For @xxx where xxx is either a symbol or a decimal number
	/// * CCommand: For dest=comp;jump
	/// * LCommand: Pseudo-Command. For (xxx) where xxx is a symbol
	fn command_type(&self) -> CommandType {
		if self.current_command.starts_with("@") {
			return ACommand
		} else if self.current_command.starts_with("(") {
			return LCommand
		}
		CCommand
	}

	/// Returns the symbol or decimal xxx of the current command @xxx or (xxx).
	///
	/// Should be called only when command_type() is ACommand or LCommand.
	fn symbol(&self) -> String {
		let pattern: &[_] = &['(', ')', '@'];
		self.current_command.trim_matches(pattern).to_string()
	}

	/// Returns the dest mnemonic in the current CCommand. dest=comp;jump
	///
	///(8 posibilities.)
	/// Should only be called when cammand_type() is CCommand.
	fn dest(&self) -> String {
		// dest or jump field may be empy
		// if dest is empty, the '=' is omitted.
		// if jump is empty, the ';' is omitted.
		match self.current_command.contains('=') {
			true => {
				let v: Vec<&str> = self.current_command.split('=').collect();
				return v[0].to_string()
			},
			false => "null".to_string(),
		}
	}

	// /// Returns the comp mnemonic in the current CCommand. dest=comp;jump
	// ///
	// /// 28 possibilities.
	// /// Should only be called when cammand_type() is CCommand.
	fn comp(&self) -> String {
		match self.current_command.contains('=') {
			true => {
				match self.current_command.contains(';') {
					true => { // dest=comp;jump
						let v: Vec<&str> = self.current_command.split(|c: char| c == ';' || c == '=' ).collect();
						v[1].to_string()
					},
					false => { // dest=comp
						let v: Vec<&str> = self.current_command.split('=').collect();
						v[1].to_string()
					}
				}
			},
			false => {
				match self.current_command.contains(';') {
					true => { // comp;jump
						let v: Vec<&str> = self.current_command.split(';').collect();
						v[0].to_string()
					},
					false => { // comp
						self.current_command.clone()
					},
				}
			}
		}
	}

	/// Returns the jump mnemonic in the current CCommand. dest=com;jump
	///
	/// 8-possibilities.
	/// Should only be called when cammand_type() is CCommand.
	fn jump(&self) -> String {
		match self.current_command.contains(';') {
			true => {
				let v: Vec<&str> = self.current_command.split(';').collect();
				return v[1].to_string()
			},
			false => "null".to_string(),
		}
	}
}

/// Translate Hack assembly language mnemonic into binary codes
struct Code;

impl Code {
	fn new() -> Code {
		Code
	}

	/// Returns the binary code of the dest mnemonic
	///
	/// returns 3 bits
	fn dest(mnemonic: &str) -> u8 {
		match mnemonic {
			"null" 	=> 0x00,
			"M"		=> 0x01,
			"D"		=> 0x02,
			"MD"	=> 0x03,
			"A"		=> 0x04,
			"AM"	=> 0x05,
			"AD"	=> 0x06,
			"AMD"	=> 0x07,
			_		=> 0x08,
		}
	}

	/// Returns the binary code of the comp mnemonic
	///
	/// returns 7 bits
	// fn comp(mnemonic: &str) -> u8 {

	// }

	/// Returns the binary code of the jump mnemonic
	///
	/// returns 3 bits
	fn jump(mnemonic: &str) -> CInstruction {
		let mut c_instr = CInstruction::new();
		match mnemonic {
			"null" 	=> ,
			"JGT"	=> {
				c_instr.j3 = 1;
			},
			"JEQ"	=> {
				c_instr.j2 = 1;
			},
			"JGE"	=> {
				c_instr.j3 = 1;
				c_instr.j2 = 1;
			},
			"JLT"	=> {
				c_instr.j1 = 1;
			},
			"JNE"	=> {
				c_instr.j1 = 1;
				c_instr.j3 = 1;
			},
			"JLE"	=> {
				c_instr.j1 = 1;
				c_instr.j2 = 1;
			},
			"JMP"	=> {
				c_instr.j1 = 1;
				c_instr.j2 = 1;
				c_instr.j3 = 1;
			},
			_		=> ,
		}
		c_instr
	}
}

/// Change the bits at indexes in index to values in dest
///
/// Takes a hashmap such as bit_index -> bit_value
fn set_bits(mut word:i16, index_bitvalue: HashMap<i16, i16>) -> i16 {
	for index in index_bitvalue.keys() {
		let bit_value: i16 = match index_bitvalue.get(index) {
			Some(value) => value.clone(),
			None => 0,
		};
		word ^= (-bit_value ^ word) & (1i16 << index);
	}
	word
}

/// CInstruction bitfield
struct CInstruction {
	a: u8,
	c1: u8,
	c2: u8,
	c3: u8,
	c4: u8,
	c5: u8,
	c6: u8,
	d1: u8,
	d2: u8,
	d3: u8,
	j1: u8,
	j2: u8,
	j3: u8,
}

impl CInstruction {
	fn new() -> CInstruction {
		CInstruction {
			a: 0,
			c1: 0,
			c2: 0,
			c3: 0,
			c4: 0,
			c5: 0,
			c6: 0,
			d1: 0,
			d2: 0,
			d3: 0,
			j1: 0,
			j2: 0,
			j3: 0,
		}
	}
}

fn main() {
	let args: Vec<String> = env::args().collect();
	if args.len() != 2 {
		println!("HACK Assembler. Translates assembly (mnemonic) into binary
			machine code.\n\nUsage:\n\tassembler [PATH_TO_ASM_FILE]");
		return;
	}
	let path_to_asm = &args[1];
	let mut parser = Parser::new(path_to_asm);
	parser.parse();
}
<commit_msg>fn for translating mnemonics into binary code representation have been added.<commit_after>use std::path::Path;
use std::fs::File;
use std::error::Error;
use std::env;
use std::io::BufReader;
use std::io::BufRead;
use std::io::Lines;
use std::collections::HashMap;

use CommandType::{ACommand, CCommand, LCommand};

enum CommandType {
	ACommand,
	CCommand,
	LCommand,
}

struct Parser {
	input_lines: Lines<BufReader<File>>,
	current_command: String,
}

impl Parser {
	/// Opens the input file/stream and gets ready to parse it.
	fn new(filename: &str) -> Parser {
		let path = Path::new(filename);
		let file = match File::open(&path) {
			Err(why) => panic!("couldn't open {}: {}", path.display(), Error::description(&why)),
			Ok(file) => file,
		};

		let lines = BufReader::new(file).lines(); // iterator
		Parser {
			input_lines: lines,
			current_command: String::new(),
		}
	}

	/// Contains the main program logc
	///
	/// TODO Consider moving it to a separte stand alone module.
	///
	///		Are there any more commands in the input?
	///			Reads the next command from the input and makes it the current command
	fn parse(&mut self) {
		loop {
			match self.input_lines.next() {
				Some(line) => {
					let content = line.unwrap().trim().to_string();

					// ignore line comments and empty lines
					if content.starts_with("//") || content == "\n" || content == "" {
						continue
					}

					// remove inline commnents
					let mut content_without_inline = String::new();
					if content.contains("//") {
						let v: Vec<&str> = content.split("//").collect();
						content_without_inline = v[0].trim().to_string();
					}

					// decide whether to use content_with_inline or content
					if content_without_inline != String::new() {
						self.current_command = content_without_inline;
					} else {
						self.current_command = content;
					}

					match self.command_type() {
						ACommand => {
							println!("ACommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());
						},
						CCommand => {
							println!("CCommand: {0}", self.current_command);
							println!("\tdest: {0}", self.dest());
							println!("\tcomp: {0}", self.comp());
							println!("\tjump: {0}", self.jump());
						},
						LCommand => {
							println!("LCommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());
						},
					}
				},
				None => break,
			}
		}
	}

	/// Returns the type of the current command.
	///
	/// * ACommand: For @xxx where xxx is either a symbol or a decimal number
	/// * CCommand: For dest=comp;jump
	/// * LCommand: Pseudo-Command. For (xxx) where xxx is a symbol
	fn command_type(&self) -> CommandType {
		if self.current_command.starts_with("@") {
			return ACommand
		} else if self.current_command.starts_with("(") {
			return LCommand
		}
		CCommand
	}

	/// Returns the symbol or decimal xxx of the current command @xxx or (xxx).
	///
	/// Should be called only when command_type() is ACommand or LCommand.
	fn symbol(&self) -> String {
		let pattern: &[_] = &['(', ')', '@'];
		self.current_command.trim_matches(pattern).to_string()
	}

	/// Returns the dest mnemonic in the current CCommand. dest=comp;jump
	///
	///(8 posibilities.)
	/// Should only be called when cammand_type() is CCommand.
	fn dest(&self) -> String {
		// dest or jump field may be empy
		// if dest is empty, the '=' is omitted.
		// if jump is empty, the ';' is omitted.
		match self.current_command.contains('=') {
			true => {
				let v: Vec<&str> = self.current_command.split('=').collect();
				return v[0].to_string()
			},
			false => "null".to_string(),
		}
	}

	// /// Returns the comp mnemonic in the current CCommand. dest=comp;jump
	// ///
	// /// 28 possibilities.
	// /// Should only be called when cammand_type() is CCommand.
	fn comp(&self) -> String {
		match self.current_command.contains('=') {
			true => {
				match self.current_command.contains(';') {
					true => { // dest=comp;jump
						let v: Vec<&str> = self.current_command.split(|c: char| c == ';' || c == '=' ).collect();
						v[1].to_string()
					},
					false => { // dest=comp
						let v: Vec<&str> = self.current_command.split('=').collect();
						v[1].to_string()
					}
				}
			},
			false => {
				match self.current_command.contains(';') {
					true => { // comp;jump
						let v: Vec<&str> = self.current_command.split(';').collect();
						v[0].to_string()
					},
					false => { // comp
						self.current_command.clone()
					},
				}
			}
		}
	}

	/// Returns the jump mnemonic in the current CCommand. dest=com;jump
	///
	/// 8-possibilities.
	/// Should only be called when cammand_type() is CCommand.
	fn jump(&self) -> String {
		match self.current_command.contains(';') {
			true => {
				let v: Vec<&str> = self.current_command.split(';').collect();
				return v[1].to_string()
			},
			false => "null".to_string(),
		}
	}
}

/// Translate Hack assembly language mnemonic into binary codes
struct Code;

impl Code {
	fn new() -> Code {
		Code
	}

	/// Returns the binary code of the dest mnemonic
	///
	/// returns 3 bits
	fn dest(mnemonic: &str) -> CInstruction {
		let mut c_instr = CInstruction::new();
		match mnemonic {
			"null" 	=> {},
			"M"		=> {
				c_instr.j3 = 1;
			},
			"D"		=> {
				c_instr.j2 = 1;
			},
			"MD"	=> {
				c_instr.j3 = 1;
				c_instr.j2 = 1;
			},
			"A"		=> {
				c_instr.j1 = 1;
			},
			"AM"	=> {
				c_instr.j1 = 1;
				c_instr.j3 = 1;
			},
			"AD"	=> {
				c_instr.j1 = 1;
				c_instr.j2 = 1;
			},
			"AMD"	=> {
				c_instr.j1 = 1;
				c_instr.j2 = 1;
				c_instr.j3 = 1;
			},
			_		=> {},
		}
		c_instr
	}

	/// Returns the binary code of the comp mnemonic
	///
	/// returns 7 bits
	fn comp(mnemonic: &str) -> CInstruction {
		let mut c_instr = CInstruction::new();
		match mnemonic {
			"0" => {
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"1" => {
				c_instr.c1 = 1;
				c_instr.c2 = 1;
				c_instr.c3 = 1;
				c_instr.c4 = 1;
				c_instr.c5 = 1;
				c_instr.c6 = 1;
			},
			"-1" => {
				c_instr.c1 = 1;
				c_instr.c2 = 1;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"D" => {
				c_instr.c1 = 0;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 1;
				c_instr.c5 = 0;
				c_instr.c6 = 0;
			},
			"A" | "!M" => {
				if mnemonic == "!M" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"!D" => {
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"!A" => {
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"-D" => {
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"-A" | "-M" => {
				if mnemonic == "-M" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"D+1" => {
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"A+1" | "M+1" => {
				if mnemonic == "M+1" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"D-1" => {
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"A-1" | "M-1" => {
				if mnemonic == "M-1" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"D+A" | "D+M" => {
				if mnemonic == "D+M" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"D-A" | "D-M" => {
				if mnemonic == "D-M" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"A-D" | "M-D" => {
				if mnemonic == "M-D" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"D&A" | "D&M" => {
				if mnemonic == "D&M" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			"D|A" | "D|M" => {
				if mnemonic == "D|M" {c_instr.a = 1;}
				c_instr.c1 = 1;
				c_instr.c2 = 0;
				c_instr.c3 = 1;
				c_instr.c4 = 0;
				c_instr.c5 = 1;
				c_instr.c6 = 0;
			},
			_ => {},
		}
		c_instr
	}

	/// Returns the binary code of the jump mnemonic
	///
	/// returns 3 bits
	fn jump(mnemonic: &str) -> CInstruction {
		let mut c_instr = CInstruction::new();
		match mnemonic {
			"null" 	=> {},
			"JGT"	=> {
				c_instr.j3 = 1;
			},
			"JEQ"	=> {
				c_instr.j2 = 1;
			},
			"JGE"	=> {
				c_instr.j3 = 1;
				c_instr.j2 = 1;
			},
			"JLT"	=> {
				c_instr.j1 = 1;
			},
			"JNE"	=> {
				c_instr.j1 = 1;
				c_instr.j3 = 1;
			},
			"JLE"	=> {
				c_instr.j1 = 1;
				c_instr.j2 = 1;
			},
			"JMP"	=> {
				c_instr.j1 = 1;
				c_instr.j2 = 1;
				c_instr.j3 = 1;
			},
			_		=> {},
		}
		c_instr
	}
}

/// Change the bits at indexes in index to values in dest
///
/// Takes a hashmap such as bit_index -> bit_value
fn set_bits(mut word:i16, index_bitvalue: HashMap<i16, i16>) -> i16 {
	for index in index_bitvalue.keys() {
		let bit_value: i16 = match index_bitvalue.get(index) {
			Some(value) => value.clone(),
			None => 0,
		};
		word ^= (-bit_value ^ word) & (1i16 << index);
	}
	word
}

/// CInstruction bitfield
struct CInstruction {
	a: u8,
	c1: u8,
	c2: u8,
	c3: u8,
	c4: u8,
	c5: u8,
	c6: u8,
	d1: u8,
	d2: u8,
	d3: u8,
	j1: u8,
	j2: u8,
	j3: u8,
}

impl CInstruction {
	fn new() -> CInstruction {
		CInstruction {
			a: 0,
			c1: 0,
			c2: 0,
			c3: 0,
			c4: 0,
			c5: 0,
			c6: 0,
			d1: 0,
			d2: 0,
			d3: 0,
			j1: 0,
			j2: 0,
			j3: 0,
		}
	}
}

fn main() {
	let args: Vec<String> = env::args().collect();
	if args.len() != 2 {
		println!("HACK Assembler. Translates assembly (mnemonic) into binary
			machine code.\n\nUsage:\n\tassembler [PATH_TO_ASM_FILE]");
		return;
	}
	let path_to_asm = &args[1];
	let mut parser = Parser::new(path_to_asm);
	parser.parse();
}
<|endoftext|>


<commit_before>extern crate piston;
extern crate graphics;
extern crate glutin_window;
extern crate opengl_graphics;

use piston::window::WindowSettings;
use piston::event::*;
use glutin_window::GlutinWindow as Window;
use opengl_graphics::{ GlGraphics, OpenGL };
use piston::input::{ Button };
use piston::input::mouse::MouseButton;

pub struct Square {
    rotation: f64,   // Rotation for the square.
    position: [f64; 2],
    target: [f64; 2],
    selected: bool,
}

impl Square {
    fn new(position: [f64;2]) -> Square {
        println!("Create square at {:?}", position);
        Square {
            rotation: 0.0,
            position: position,
            target: position,
            selected: false
        }
    }
}

pub struct App {
    gl: GlGraphics, // OpenGL drawing backend.
    squares: Vec<Square>,
}

impl App {
    fn select(&mut self, position: &[f64;2]) {
        for s in &mut self.squares {
            s.selected = position[0]< s.position[0]+25.0 &&
                position[0]> s.position[0]-25.0 &&
                position[1]< s.position[1]+25.0 &&
                position[1]> s.position[1]-25.0;
        };
    }

    fn render(&mut self, args: &RenderArgs) {
        use graphics::*;

        const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
        const RED:   [f32; 4] = [1.0, 0.0, 0.0, 1.0];
        const BLUE:  [f32; 4] = [0.0, 0.0, 1.0, 1.0];
        let squares = &self.squares;

        self.gl.draw(args.viewport(), |c, gl| {
            // Clear the screen.
            clear(GREEN, gl);
            for s in squares.iter() {
                let square = rectangle::square(0.0, 0.0, 50.0);
                let transform = c.transform.trans(s.position[0], s.position[1])
                    .rot_rad(s.rotation)
                    .trans(-25.0, -25.0);

                // Draw the box RED if selected
                if s.selected {
                    rectangle(RED, square, transform, gl);
                } else {
                    rectangle(BLUE, square, transform, gl);
                }
            }
        });
    }

    fn update(&mut self, args: &UpdateArgs) {
        for s in &mut self.squares {
            // Rotate 2 radians per second.
            s.rotation += 2.0 * args.dt;

            let diff = [s.target[0]-s.position[0], s.target[1]-s.position[1]];
            s.position[0] += diff[0]/2.0*args.dt;
            s.position[1] += diff[1]/2.0*args.dt;
        }
    }

    fn move_selected(&mut self, position: [f64;2]) {
        for s in &mut self.squares {
            if s.selected {
                s.target = position;
            }
        }
    }
}

fn main() {
    let opengl = OpenGL::_3_2;

    // Create an Glutin window.
    let window = Window::new(
        WindowSettings::new(
            "rpsrtsrs",
            [640, 480]
        )
        .exit_on_esc(true)
    );

    // Create a new game and run it.
    let mut app = App {
        gl: GlGraphics::new(opengl),
        squares: vec![Square::new([50.0, 50.0]), Square::new([100.0, 100.0])],
    };

    let mut cursor = [0.0,0.0];

    for e in window.events() {
        if let Some(r) = e.render_args() {
            app.render(&r);
        }
        if let Some(Button::Mouse(button)) = e.press_args() {
            match button {
                MouseButton::Left  => app.select(&cursor),
                MouseButton::Right => app.move_selected(cursor),
                _ => println!("Pressed mouse button '{:?}'", button),
            }
        }
        e.mouse_cursor(|x, y| {
            cursor = [x, y];
            println!("Mouse moved '{} {}'", x, y);
        });

        if let Some(u) = e.update_args() {
            app.update(&u);
        }
    }
}

<commit_msg>remove 'Mouse moved' debug messages<commit_after>extern crate piston;
extern crate graphics;
extern crate glutin_window;
extern crate opengl_graphics;

use piston::window::WindowSettings;
use piston::event::*;
use glutin_window::GlutinWindow as Window;
use opengl_graphics::{ GlGraphics, OpenGL };
use piston::input::{ Button };
use piston::input::mouse::MouseButton;

pub struct Square {
    rotation: f64,   // Rotation for the square.
    position: [f64; 2],
    target: [f64; 2],
    selected: bool,
}

impl Square {
    fn new(position: [f64;2]) -> Square {
        println!("Create square at {:?}", position);
        Square {
            rotation: 0.0,
            position: position,
            target: position,
            selected: false
        }
    }
}

pub struct App {
    gl: GlGraphics, // OpenGL drawing backend.
    squares: Vec<Square>,
}

impl App {
    fn select(&mut self, position: &[f64;2]) {
        for s in &mut self.squares {
            s.selected = position[0]< s.position[0]+25.0 &&
                position[0]> s.position[0]-25.0 &&
                position[1]< s.position[1]+25.0 &&
                position[1]> s.position[1]-25.0;
        };
    }

    fn render(&mut self, args: &RenderArgs) {
        use graphics::*;

        const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
        const RED:   [f32; 4] = [1.0, 0.0, 0.0, 1.0];
        const BLUE:  [f32; 4] = [0.0, 0.0, 1.0, 1.0];
        let squares = &self.squares;

        self.gl.draw(args.viewport(), |c, gl| {
            // Clear the screen.
            clear(GREEN, gl);
            for s in squares.iter() {
                let square = rectangle::square(0.0, 0.0, 50.0);
                let transform = c.transform.trans(s.position[0], s.position[1])
                    .rot_rad(s.rotation)
                    .trans(-25.0, -25.0);

                // Draw the box RED if selected
                if s.selected {
                    rectangle(RED, square, transform, gl);
                } else {
                    rectangle(BLUE, square, transform, gl);
                }
            }
        });
    }

    fn update(&mut self, args: &UpdateArgs) {
        for s in &mut self.squares {
            // Rotate 2 radians per second.
            s.rotation += 2.0 * args.dt;

            let diff = [s.target[0]-s.position[0], s.target[1]-s.position[1]];
            s.position[0] += diff[0]/2.0*args.dt;
            s.position[1] += diff[1]/2.0*args.dt;
        }
    }

    fn move_selected(&mut self, position: [f64;2]) {
        for s in &mut self.squares {
            if s.selected {
                s.target = position;
            }
        }
    }
}

fn main() {
    let opengl = OpenGL::_3_2;

    // Create an Glutin window.
    let window = Window::new(
        WindowSettings::new(
            "rpsrtsrs",
            [640, 480]
        )
        .exit_on_esc(true)
    );

    // Create a new game and run it.
    let mut app = App {
        gl: GlGraphics::new(opengl),
        squares: vec![Square::new([50.0, 50.0]), Square::new([100.0, 100.0])],
    };

    let mut cursor = [0.0,0.0];

    for e in window.events() {
        if let Some(r) = e.render_args() {
            app.render(&r);
        }
        if let Some(Button::Mouse(button)) = e.press_args() {
            match button {
                MouseButton::Left  => app.select(&cursor),
                MouseButton::Right => app.move_selected(cursor),
                _ => println!("Pressed mouse button '{:?}'", button),
            }
        }
        e.mouse_cursor(|x, y| {
            cursor = [x, y];
        });

        if let Some(u) = e.update_args() {
            app.update(&u);
        }
    }
}

<|endoftext|>


<commit_before>extern crate clap;
extern crate walkdir;
extern crate twox_hash;

use std::collections::HashMap;
use std::collections::HashSet;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::os::linux::fs::MetadataExt;

use clap::{App, Arg, ArgMatches};
use walkdir::WalkDir;
use twox_hash::XxHash;
use std::hash::Hasher;

#[derive(Default)]
struct Stats {
    file_count: u64,
    total_size: u64,
    duplicate_count: u64,
}

fn parse_args<'a>() -> ArgMatches<'a> {
    App::new("dupr")
        .version("0.1.0")
        .author("Mike Sampson")
        .about("Duplicate file finder")
        .arg(
            Arg::with_name("DIR")
                .help("Directory to process")
                .required(true),
        )
        .arg(Arg::with_name("summary").short("s").long("summary").help(
            "Print out summary information after duplicates",
        ))
        .get_matches()
}

fn collect_paths(path: &str, stats: &mut Stats) -> HashMap<u64, Vec<PathBuf>> {

    let mut length_paths = HashMap::new();

    for entry in WalkDir::new(path) {
        let entry = match entry {
            Ok(e) => e,
            Err(err) => {
                println!("dupr: {}", err); // stderr
                continue;
            }
        };

        if !entry.file_type().is_file() {
            continue;
        }

        let metadata = match entry.metadata() {
            Ok(e) => e,
            Err(err) => {
                println!("dupr: {}", err); //std err
                continue;
            }
        };

        if metadata.len() == 0 {
            continue;
        }

        stats.file_count += 1;
        stats.total_size += metadata.len();

        let path = entry.path().to_path_buf();

        length_paths
            .entry(metadata.len())
            .or_insert(Vec::new())
            .push(path);
    }

    length_paths
}

fn main() {
    let matches = parse_args();

    let mut stats: Stats = Default::default();

    let files = collect_paths(matches.value_of("DIR").unwrap(), &mut stats);

    for f_paths in files.values().filter(|x| x.len() > 1) {

        // Avoid hardlinks to same inode
        let mut hardlinks = Vec::new();
        let mut di = HashSet::new();
        for f_path in f_paths {
            let metadata = match f_path.metadata() {
                Ok(m) => m,
                Err(err) => {
                    println!("dupr: {}", err);
                    continue;
                }
            };
            let dev_inode = (metadata.st_dev(), metadata.st_ino());
            if !di.contains(&dev_inode) {
                hardlinks.push(f_path);
                di.insert(dev_inode);
            }
        }

        let mut hashes = HashMap::new();
        for f_path in hardlinks {
            hashes
                .entry(hash_file(&f_path))
                .or_insert(Vec::new())
                .push(f_path);
        }

        for (hash, paths) in hashes.iter().filter(|&(_, v)| v.len() > 1) {
            stats.duplicate_count += paths.len() as u64;
            for p in paths {
                println!("{:016x} - {}", hash, p.display());
            }
        }
    }

    if matches.is_present("summary") {
        println!(
            "Processed {} files with a total size of {} bytes. {} duplicates found.",
            stats.file_count,
            stats.total_size,
            stats.duplicate_count
        );
    }
}

fn hash_file<P: AsRef<Path>>(path: P) -> u64 {
    let mut file = File::open(path).unwrap();

    let mut hash = XxHash::with_seed(0);

    let mut buffer = [0; 32 * 1024];

    while let Ok(n) = file.read(&mut buffer) {
        if n == 0 {
            break;
        }
        hash.write(&buffer[0..n]);
    }

    hash.finish()
}
<commit_msg>Send errors to stderr<commit_after>extern crate clap;
extern crate walkdir;
extern crate twox_hash;

use std::collections::HashMap;
use std::collections::HashSet;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::os::linux::fs::MetadataExt;

use clap::{App, Arg, ArgMatches};
use walkdir::WalkDir;
use twox_hash::XxHash;
use std::hash::Hasher;

#[derive(Default)]
struct Stats {
    file_count: u64,
    total_size: u64,
    duplicate_count: u64,
}

fn parse_args<'a>() -> ArgMatches<'a> {
    App::new("dupr")
        .version("0.1.0")
        .author("Mike Sampson")
        .about("Duplicate file finder")
        .arg(
            Arg::with_name("DIR")
                .help("Directory to process")
                .required(true),
        )
        .arg(Arg::with_name("summary").short("s").long("summary").help(
            "Print out summary information after duplicates",
        ))
        .get_matches()
}

fn collect_paths(path: &str, stats: &mut Stats) -> HashMap<u64, Vec<PathBuf>> {

    let mut length_paths = HashMap::new();

    for entry in WalkDir::new(path) {
        let entry = match entry {
            Ok(e) => e,
            Err(err) => {
                eprintln!("dupr: {}", err);
                continue;
            }
        };

        if !entry.file_type().is_file() {
            continue;
        }

        let metadata = match entry.metadata() {
            Ok(e) => e,
            Err(err) => {
                eprintln!("dupr: {}", err);
                continue;
            }
        };

        if metadata.len() == 0 {
            continue;
        }

        stats.file_count += 1;
        stats.total_size += metadata.len();

        let path = entry.path().to_path_buf();

        length_paths
            .entry(metadata.len())
            .or_insert(Vec::new())
            .push(path);
    }

    length_paths
}

fn main() {
    let matches = parse_args();

    let mut stats: Stats = Default::default();

    let files = collect_paths(matches.value_of("DIR").unwrap(), &mut stats);

    for f_paths in files.values().filter(|x| x.len() > 1) {

        // Avoid hardlinks to same inode
        let mut hardlinks = Vec::new();
        let mut di = HashSet::new();
        for f_path in f_paths {
            let metadata = match f_path.metadata() {
                Ok(m) => m,
                Err(err) => {
                    eprintln!("dupr: {}", err);
                    continue;
                }
            };
            let dev_inode = (metadata.st_dev(), metadata.st_ino());
            if !di.contains(&dev_inode) {
                hardlinks.push(f_path);
                di.insert(dev_inode);
            }
        }

        let mut hashes = HashMap::new();
        for f_path in hardlinks {
            hashes
                .entry(hash_file(&f_path))
                .or_insert(Vec::new())
                .push(f_path);
        }

        for (hash, paths) in hashes.iter().filter(|&(_, v)| v.len() > 1) {
            stats.duplicate_count += paths.len() as u64;
            for p in paths {
                println!("{:016x} - {}", hash, p.display());
            }
        }
    }

    if matches.is_present("summary") {
        println!(
            "Processed {} files with a total size of {} bytes. {} duplicates found.",
            stats.file_count,
            stats.total_size,
            stats.duplicate_count
        );
    }
}

fn hash_file<P: AsRef<Path>>(path: P) -> u64 {
    let mut file = File::open(path).unwrap();

    let mut hash = XxHash::with_seed(0);

    let mut buffer = [0; 32 * 1024];

    while let Ok(n) = file.read(&mut buffer) {
        if n == 0 {
            break;
        }
        hash.write(&buffer[0..n]);
    }

    hash.finish()
}
<|endoftext|>