<commit_before>#[macro_use]
extern crate log;

extern crate abstract_ns;
extern crate futures;
extern crate getopts;
extern crate ns_dns_tokio;
extern crate rand;
extern crate tokio_core;
extern crate tokio_io;

// the device under test
extern crate net_interceptor;

use abstract_ns::Resolver;
use futures::{Future, Map, Stream, Async};
use futures::future;
use ns_dns_tokio::DnsResolver;
use tokio_core::net::{TcpStream, TcpListener};
use tokio_core::reactor::Core;
use tokio_io::{AsyncRead, io};

use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::fmt::{Debug, Display};

use net_interceptor::logging::unittestlogger;

fn add_ten<F>(future: F) -> Map<F, fn(i32) -> i32>
    where F: Future<Item=i32>,
{
    error!("shutdown");
    fn add(a: i32) -> i32 { a + 10 }
    future.map(add)
}

// Here we'll express the handling of `client` and return it as a future
// to be spawned onto the event loop.
/*fn process(client: TcpStream) -> Box<Future<Item = (i64), Error = error>> {
    warn!("received");
    Box::new(futures::future::ok(0));
}
*/
#[test]
fn test_infra_connect() {
    unittestlogger();

    // this is the main event loop, powered by tokio core
    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let bind_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12000);
    let listener = TcpListener::bind(&bind_socket_addr, &handle)
        .expect(&format!("Unable to bind to {}", &bind_socket_addr));
    warn!("Listening on {}", listener.local_addr().unwrap());

    let addr = listener.local_addr().unwrap();
    assert!(addr.port() == 12000, "should accept {}");

    let connections = listener.incoming();
    let server = connections.for_each(move |(socket, _peer_addr)| {
        warn!("connection received");
        /*let (writer, reader) = socket.framed(LineCodec).split();
        let service = s.new_service()?;

        let responses = reader.and_then(move |req| service.call(req));
        let server = writer.send_all(responses)
            .then(|_| Ok(()));
        handle.spawn(server);
        */
        Ok(())
    });

    fn abortCondition() -> futures::Poll<(), std::io::Error> {
        warn!("poll");
        if true {
            Ok(Async::Ready(()))
        }
        else {
            Ok(Async::NotReady)
        }
    }

    let abort = futures::future::poll_fn(abortCondition);

    // run server, stop on panic
    core.run(server).unwrap();
//    core.run(server.select2(abort)).unwrap();
}
<commit_msg>broken!<commit_after>#[macro_use]
extern crate log;

extern crate abstract_ns;
extern crate futures;
extern crate getopts;
extern crate ns_dns_tokio;
extern crate rand;
extern crate tokio_core;
extern crate tokio_io;

// the device under test
extern crate net_interceptor;

use abstract_ns::Resolver;
use futures::{Future, Map, Stream, Async, Poll};
use futures::future;
use ns_dns_tokio::DnsResolver;
use tokio_core::net::{TcpStream, TcpListener};
use tokio_core::reactor::{Core, Handle};
use tokio_io::{AsyncRead, io};

use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::fmt::{Debug, Display};
use std::time::{Duration};

use std::io::Error;

use net_interceptor::logging::unittestlogger;

fn add_ten<F>(future: F) -> Map<F, fn(i32) -> i32>
    where F: Future<Item=i32>,
{
    error!("shutdown");
    fn add(a: i32) -> i32 { a + 10 }
    future.map(add)
}

pub struct AbortCondition {
    condition: bool,
    reactor_handle: futures::task::Task,
}

#[derive(Copy, Clone)]
pub struct Releaser {
    block: *const AbortCondition
}

impl AbortCondition {

    fn set(&mut self) {
        debug!("abort condition met");
        self.condition = true;
        self.reactor_handle.unpark();
    }

}

impl Future for AbortCondition {

    type Item = ();
    type Error = Error;

    fn poll(&mut self) -> Poll<(), Error> {
        if  self.condition {
            Ok(Async::Ready(()))
        } else {
            self.reactor_handle = futures::task::park();
            Ok(Async::NotReady)
        }
    }

}

/*impl Copy for Abort {
    fn clone(&self) -> Abort {
        *self
    }
}
*/
// Here we'll express the handling of `client` and return it as a future
// to be spawned onto the event loop.
/*fn process(client: TcpStream) -> Box<Future<Item = (i64), Error = error>> {
    warn!("received");
    Box::new(futures::future::ok(0));
}
*/
#[test]
fn test_infra_connect() {
    unittestlogger();

    // this is the main event loop, powered by tokio core
    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let bind_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12000);
    let listener = TcpListener::bind(&bind_socket_addr, &handle)
        .expect(&format!("Unable to bind to {}", &bind_socket_addr));
    warn!("Listening on {}", listener.local_addr().unwrap());

    let addr = listener.local_addr().unwrap();
    assert!(addr.port() == 12000, "should accept {}");

    let mut abort = AbortCondition { condition: false, reactor_handle: futures::task::park() };

    let connections = listener.incoming();
    let server = connections.for_each(move |(socket, _peer_addr)| {
        warn!("connection received");
        /*let (writer, reader) = socket.framed(LineCodec).split();
        let service = s.new_service()?;

        let responses = reader.and_then(move |req| service.call(req));
        let server = writer.send_all(responses)
            .then(|_| Ok(()));
        handle.spawn(server);
        */
        abort.set();
        Ok(())
    });

    fn abortCondition() -> futures::Poll<(), std::io::Error> {
        warn!("poll");
        if true {
            Ok(Async::Ready(()))
        }
        else {
            futures::task::park().unpark();
            Ok(Async::NotReady)
        }
    }

    //let abort = futures::future::poll_fn(abortCondition);

    //let timeout = tokio_core::reactor::Timeout::new_at(std::time::Duration::seconds(1));
    // run server, stop on panic
//    core.run(server.select2(timeout)).unwrap();
//    core.run(abort).unwrap();
    // core.spawn(server);
    core.run(server.select(abort)).unwrap();
}
<|endoftext|>


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

use conduit::{Request, Response};
use conduit_router::RequestParams;
use pg::GenericConnection;
use pg::rows::Row;
use rustc_serialize::json;
use semver;
use time::Duration;
use time::Timespec;
use url;

use {Model, Crate, User};
use app::RequestApp;
use db::RequestTransaction;
use dependency::{Dependency, EncodableDependency, Kind};
use download::{VersionDownload, EncodableVersionDownload};
use git;
use upload;
use user::RequestUser;
use owner::{rights, Rights};
use util::{RequestUtils, CargoResult, ChainError, internal, human};

#[derive(Clone)]
pub struct Version {
    pub id: i32,
    pub crate_id: i32,
    pub num: semver::Version,
    pub updated_at: Timespec,
    pub created_at: Timespec,
    pub downloads: i32,
    pub features: HashMap<String, Vec<String>>,
    pub yanked: bool,
}

pub enum Author {
    User(User),
    Name(String),
}

#[derive(RustcEncodable, RustcDecodable)]
pub struct EncodableVersion {
    pub id: i32,
    pub krate: String,
    pub num: String,
    pub dl_path: String,
    pub updated_at: String,
    pub created_at: String,
    pub downloads: i32,
    pub features: HashMap<String, Vec<String>>,
    pub yanked: bool,
    pub links: VersionLinks,
}

#[derive(RustcEncodable, RustcDecodable)]
pub struct VersionLinks {
    pub dependencies: String,
    pub version_downloads: String,
    pub authors: String,
}

impl Version {
    pub fn find_by_num(conn: &GenericConnection,
                       crate_id: i32,
                       num: &semver::Version)
                       -> CargoResult<Option<Version>> {
        let num = num.to_string();
        let stmt = conn.prepare("SELECT * FROM versions \
                                      WHERE crate_id = $1 AND num = $2")?;
        let rows = stmt.query(&[&crate_id, &num])?;
        Ok(rows.iter().next().map(|r| Model::from_row(&r)))
    }

    pub fn insert(conn: &GenericConnection,
                  crate_id: i32,
                  num: &semver::Version,
                  features: &HashMap<String, Vec<String>>,
                  authors: &[String])
                  -> CargoResult<Version> {
        let num = num.to_string();
        let features = json::encode(features).unwrap();
        let stmt = conn.prepare("INSERT INTO versions \
                                      (crate_id, num, features) \
                                      VALUES ($1, $2, $3) \
                                      RETURNING *")?;
        let rows = stmt.query(&[&crate_id, &num, &features])?;
        let ret: Version = Model::from_row(&rows.iter().next().chain_error(|| {
            internal("no version returned")
        })?);
        for author in authors.iter() {
            ret.add_author(conn, &author)?;
        }
        Ok(ret)
    }

    pub fn valid(version: &str) -> bool {
        semver::Version::parse(version).is_ok()
    }

    pub fn encodable(self, crate_name: &str) -> EncodableVersion {
        let Version { id, crate_id: _, num, updated_at, created_at,
                      downloads, features, yanked } = self;
        let num = num.to_string();
        EncodableVersion {
            dl_path: format!("/api/v1/crates/{}/{}/download", crate_name, num),
            num: num.clone(),
            id: id,
            krate: crate_name.to_string(),
            updated_at: ::encode_time(updated_at),
            created_at: ::encode_time(created_at),
            downloads: downloads,
            features: features,
            yanked: yanked,
            links: VersionLinks {
                dependencies: format!("/api/v1/crates/{}/{}/dependencies",
                                      crate_name, num),
                version_downloads: format!("/api/v1/crates/{}/{}/downloads",
                                           crate_name, num),
                authors: format!("/api/v1/crates/{}/{}/authors", crate_name, num),
            },
        }
    }

    /// Add a dependency to this version, returning both the dependency and the
    /// crate that the dependency points to
    pub fn add_dependency(&mut self,
                          conn: &GenericConnection,
                          dep: &upload::CrateDependency)
                          -> CargoResult<(Dependency, Crate)> {
        let name = &dep.name;
        let krate = Crate::find_by_name(conn, name).map_err(|_| {
            human(format!("no known crate named `{}`", &**name))
        })?;
        if dep.version_req.0 == semver::VersionReq::parse("*").unwrap() {
            return Err(human(format!("wildcard (`*`) dependency constraints are not allowed \
                                      on crates.io. See http://doc.crates.io/faq.html#can-\
                                      libraries-use--as-a-version-for-their-dependencies for more \
                                      information")));
        }
        let features: Vec<String> = dep.features.iter().map(|s| {
            s[..].to_string()
        }).collect();
        let dep = Dependency::insert(conn, self.id, krate.id,
                                     &*dep.version_req,
                                     dep.kind.unwrap_or(Kind::Normal),
                                     dep.optional,
                                     dep.default_features,
                                     &features,
                                     &dep.target)?;
        Ok((dep, krate))
    }

    /// Returns (dependency, crate dependency name)
    pub fn dependencies(&self, conn: &GenericConnection)
                        -> CargoResult<Vec<(Dependency, String)>> {
        let stmt = conn.prepare("SELECT dependencies.*,
                                             crates.name AS crate_name
                                      FROM dependencies
                                      LEFT JOIN crates
                                        ON crates.id = dependencies.crate_id
                                      WHERE dependencies.version_id = $1")?;
        let rows = stmt.query(&[&self.id])?;
        Ok(rows.iter().map(|r| {
            (Model::from_row(&r), r.get("crate_name"))
        }).collect())
    }

    pub fn authors(&self, conn: &GenericConnection) -> CargoResult<Vec<Author>> {
        let stmt = conn.prepare("SELECT * FROM version_authors
                                       WHERE version_id = $1")?;
        let rows = stmt.query(&[&self.id])?;
        rows.into_iter().map(|row| {
            let user_id: Option<i32> = row.get("user_id");
            let name: String = row.get("name");
            Ok(match user_id {
                Some(id) => Author::User(User::find(conn, id)?),
                None => Author::Name(name),
            })
        }).collect()
    }

    pub fn add_author(&self,
                      conn: &GenericConnection,
                      name: &str) -> CargoResult<()> {
        println!("add author: {}", name);
        // TODO: at least try to link `name` to a pre-existing user
        conn.execute("INSERT INTO version_authors (version_id, name)
                           VALUES ($1, $2)", &[&self.id, &name])?;
        Ok(())
    }

    pub fn yank(&self, conn: &GenericConnection, yanked: bool) -> CargoResult<()> {
        conn.execute("UPDATE versions SET yanked = $1 WHERE id = $2",
                     &[&yanked, &self.id])?;
        Ok(())
    }
}

impl Model for Version {
    fn from_row(row: &Row) -> Version {
        let num: String = row.get("num");
        let features: Option<String> = row.get("features");
        let features = features.map(|s| {
            json::decode(&s).unwrap()
        }).unwrap_or_else(|| HashMap::new());
        Version {
            id: row.get("id"),
            crate_id: row.get("crate_id"),
            num: semver::Version::parse(&num).unwrap(),
            updated_at: row.get("updated_at"),
            created_at: row.get("created_at"),
            downloads: row.get("downloads"),
            features: features,
            yanked: row.get("yanked"),
        }
    }
    fn table_name(_: Option<Version>) -> &'static str { "versions" }
}

/// Handles the `GET /versions` route.
pub fn index(req: &mut Request) -> CargoResult<Response> {
    let conn = req.tx()?;

    // Extract all ids requested.
    let query = url::form_urlencoded::parse(req.query_string().unwrap_or("")
                                               .as_bytes());
    let ids = query.filter_map(|(ref a, ref b)| {
        if *a == "ids[]" {
            b.parse().ok()
        } else {
            None
        }
    }).collect::<Vec<i32>>();

    // Load all versions
    //
    // TODO: can rust-postgres do this for us?
    let mut versions = Vec::new();
    if ids.len() > 0 {
        let stmt = conn.prepare("\
            SELECT versions.*, crates.name AS crate_name
              FROM versions
            LEFT JOIN crates ON crates.id = versions.crate_id
            WHERE versions.id = ANY($1)
        ")?;
        for row in stmt.query(&[&ids])?.iter() {
            let v: Version = Model::from_row(&row);
            let crate_name: String = row.get("crate_name");
            versions.push(v.encodable(&crate_name));
        }
    }

    #[derive(RustcEncodable)]
    struct R { versions: Vec<EncodableVersion> }
    Ok(req.json(&R { versions: versions }))
}

/// Handles the `GET /versions/:version_id` route.
pub fn show(req: &mut Request) -> CargoResult<Response> {
    let (version, krate) = match req.params().find("crate_id") {
        Some(..) => version_and_crate(req)?,
        None => {
            let id = &req.params()["version_id"];
            let id = id.parse().unwrap_or(0);
            let conn = req.tx()?;
            let version = Version::find(&*conn, id)?;
            let krate = Crate::find(&*conn, version.crate_id)?;
            (version, krate)
        }
    };

    #[derive(RustcEncodable)]
    struct R { version: EncodableVersion }
    Ok(req.json(&R { version: version.encodable(&krate.name) }))
}

fn version_and_crate(req: &mut Request) -> CargoResult<(Version, Crate)> {
    let crate_name = &req.params()["crate_id"];
    let semver = &req.params()["version"];
    let semver = semver::Version::parse(semver).map_err(|_| {
        human(format!("invalid semver: {}", semver))
    })?;
    let tx = req.tx()?;
    let krate = Crate::find_by_name(tx, crate_name)?;
    let version = Version::find_by_num(tx, krate.id, &semver)?;
    let version = version.chain_error(|| {
        human(format!("crate `{}` does not have a version `{}`",
                      crate_name, semver))
    })?;
    Ok((version, krate))
}

/// Handles the `GET /crates/:crate_id/:version/dependencies` route.
pub fn dependencies(req: &mut Request) -> CargoResult<Response> {
    let (version, _) = version_and_crate(req)?;
    let tx = req.tx()?;
    let deps = version.dependencies(tx)?;
    let deps = deps.into_iter().map(|(dep, crate_name)| {
        dep.encodable(&crate_name, None)
    }).collect();

    #[derive(RustcEncodable)]
    struct R { dependencies: Vec<EncodableDependency> }
    Ok(req.json(&R{ dependencies: deps }))
}

/// Handles the `GET /crates/:crate_id/:version/downloads` route.
pub fn downloads(req: &mut Request) -> CargoResult<Response> {
    let (version, _) = version_and_crate(req)?;

    let tx = req.tx()?;
    let cutoff_date = ::now() + Duration::days(-90);
    let stmt = tx.prepare("SELECT * FROM version_downloads
                                WHERE date > $1 AND version_id = $2
                                ORDER BY date ASC")?;
    let mut downloads = Vec::new();
    for row in stmt.query(&[&cutoff_date, &version.id])?.iter() {
        let download: VersionDownload = Model::from_row(&row);
        downloads.push(download.encodable());
    }

    #[derive(RustcEncodable)]
    struct R { version_downloads: Vec<EncodableVersionDownload> }
    Ok(req.json(&R{ version_downloads: downloads }))
}

/// Handles the `GET /crates/:crate_id/:version/authors` route.
pub fn authors(req: &mut Request) -> CargoResult<Response> {
    let (version, _) = version_and_crate(req)?;
    let tx = req.tx()?;
    let (mut users, mut names) = (Vec::new(), Vec::new());
    for author in version.authors(tx)?.into_iter() {
        match author {
            Author::User(u) => users.push(u.encodable()),
            Author::Name(n) => names.push(n),
        }
    }

    #[derive(RustcEncodable)]
    struct R { users: Vec<::user::EncodableUser>, meta: Meta }
    #[derive(RustcEncodable)]
    struct Meta { names: Vec<String> }
    Ok(req.json(&R{ users: users, meta: Meta { names: names } }))
}

/// Handles the `DELETE /crates/:crate_id/:version/yank` route.
pub fn yank(req: &mut Request) -> CargoResult<Response> {
    modify_yank(req, true)
}

/// Handles the `PUT /crates/:crate_id/:version/unyank` route.
pub fn unyank(req: &mut Request) -> CargoResult<Response> {
    modify_yank(req, false)
}

fn modify_yank(req: &mut Request, yanked: bool) -> CargoResult<Response> {
    let (version, krate) = version_and_crate(req)?;
    let user = req.user()?;
    let tx = req.tx()?;
    let owners = krate.owners(tx)?;
    if rights(req.app(), &owners, &user)? < Rights::Publish {
        return Err(human("must already be an owner to yank or unyank"))
    }

    if version.yanked != yanked {
        version.yank(tx, yanked)?;
        git::yank(&**req.app(), &krate.name, &version.num, yanked)?;
    }

    #[derive(RustcEncodable)]
    struct R { ok: bool }
    Ok(req.json(&R{ ok: true }))
}
<commit_msg>Sort authors for ui #467<commit_after>use std::collections::HashMap;

use conduit::{Request, Response};
use conduit_router::RequestParams;
use pg::GenericConnection;
use pg::rows::Row;
use rustc_serialize::json;
use semver;
use time::Duration;
use time::Timespec;
use url;

use {Model, Crate, User};
use app::RequestApp;
use db::RequestTransaction;
use dependency::{Dependency, EncodableDependency, Kind};
use download::{VersionDownload, EncodableVersionDownload};
use git;
use upload;
use user::RequestUser;
use owner::{rights, Rights};
use util::{RequestUtils, CargoResult, ChainError, internal, human};

#[derive(Clone)]
pub struct Version {
    pub id: i32,
    pub crate_id: i32,
    pub num: semver::Version,
    pub updated_at: Timespec,
    pub created_at: Timespec,
    pub downloads: i32,
    pub features: HashMap<String, Vec<String>>,
    pub yanked: bool,
}

pub enum Author {
    User(User),
    Name(String),
}

#[derive(RustcEncodable, RustcDecodable)]
pub struct EncodableVersion {
    pub id: i32,
    pub krate: String,
    pub num: String,
    pub dl_path: String,
    pub updated_at: String,
    pub created_at: String,
    pub downloads: i32,
    pub features: HashMap<String, Vec<String>>,
    pub yanked: bool,
    pub links: VersionLinks,
}

#[derive(RustcEncodable, RustcDecodable)]
pub struct VersionLinks {
    pub dependencies: String,
    pub version_downloads: String,
    pub authors: String,
}

impl Version {
    pub fn find_by_num(conn: &GenericConnection,
                       crate_id: i32,
                       num: &semver::Version)
                       -> CargoResult<Option<Version>> {
        let num = num.to_string();
        let stmt = conn.prepare("SELECT * FROM versions \
                                      WHERE crate_id = $1 AND num = $2")?;
        let rows = stmt.query(&[&crate_id, &num])?;
        Ok(rows.iter().next().map(|r| Model::from_row(&r)))
    }

    pub fn insert(conn: &GenericConnection,
                  crate_id: i32,
                  num: &semver::Version,
                  features: &HashMap<String, Vec<String>>,
                  authors: &[String])
                  -> CargoResult<Version> {
        let num = num.to_string();
        let features = json::encode(features).unwrap();
        let stmt = conn.prepare("INSERT INTO versions \
                                      (crate_id, num, features) \
                                      VALUES ($1, $2, $3) \
                                      RETURNING *")?;
        let rows = stmt.query(&[&crate_id, &num, &features])?;
        let ret: Version = Model::from_row(&rows.iter().next().chain_error(|| {
            internal("no version returned")
        })?);
        for author in authors.iter() {
            ret.add_author(conn, &author)?;
        }
        Ok(ret)
    }

    pub fn valid(version: &str) -> bool {
        semver::Version::parse(version).is_ok()
    }

    pub fn encodable(self, crate_name: &str) -> EncodableVersion {
        let Version { id, crate_id: _, num, updated_at, created_at,
                      downloads, features, yanked } = self;
        let num = num.to_string();
        EncodableVersion {
            dl_path: format!("/api/v1/crates/{}/{}/download", crate_name, num),
            num: num.clone(),
            id: id,
            krate: crate_name.to_string(),
            updated_at: ::encode_time(updated_at),
            created_at: ::encode_time(created_at),
            downloads: downloads,
            features: features,
            yanked: yanked,
            links: VersionLinks {
                dependencies: format!("/api/v1/crates/{}/{}/dependencies",
                                      crate_name, num),
                version_downloads: format!("/api/v1/crates/{}/{}/downloads",
                                           crate_name, num),
                authors: format!("/api/v1/crates/{}/{}/authors", crate_name, num),
            },
        }
    }

    /// Add a dependency to this version, returning both the dependency and the
    /// crate that the dependency points to
    pub fn add_dependency(&mut self,
                          conn: &GenericConnection,
                          dep: &upload::CrateDependency)
                          -> CargoResult<(Dependency, Crate)> {
        let name = &dep.name;
        let krate = Crate::find_by_name(conn, name).map_err(|_| {
            human(format!("no known crate named `{}`", &**name))
        })?;
        if dep.version_req.0 == semver::VersionReq::parse("*").unwrap() {
            return Err(human(format!("wildcard (`*`) dependency constraints are not allowed \
                                      on crates.io. See http://doc.crates.io/faq.html#can-\
                                      libraries-use--as-a-version-for-their-dependencies for more \
                                      information")));
        }
        let features: Vec<String> = dep.features.iter().map(|s| {
            s[..].to_string()
        }).collect();
        let dep = Dependency::insert(conn, self.id, krate.id,
                                     &*dep.version_req,
                                     dep.kind.unwrap_or(Kind::Normal),
                                     dep.optional,
                                     dep.default_features,
                                     &features,
                                     &dep.target)?;
        Ok((dep, krate))
    }

    /// Returns (dependency, crate dependency name)
    pub fn dependencies(&self, conn: &GenericConnection)
                        -> CargoResult<Vec<(Dependency, String)>> {
        let stmt = conn.prepare("SELECT dependencies.*,
                                             crates.name AS crate_name
                                      FROM dependencies
                                      LEFT JOIN crates
                                        ON crates.id = dependencies.crate_id
                                      WHERE dependencies.version_id = $1")?;
        let rows = stmt.query(&[&self.id])?;
        Ok(rows.iter().map(|r| {
            (Model::from_row(&r), r.get("crate_name"))
        }).collect())
    }

    pub fn authors(&self, conn: &GenericConnection) -> CargoResult<Vec<Author>> {
        let stmt = try!(conn.prepare("SELECT * FROM version_authors
                                       WHERE version_id = $1"));
        let rows = try!(stmt.query(&[&self.id]));
        let mut authors: CargoResult<Vec<Author>> = rows.into_iter().map(|row| {
            let user_id: Option<i32> = row.get("user_id");
            let name: String = row.get("name");
            Ok(match user_id {
                Some(id) => Author::User(User::find(conn, id)?),
                None => Author::Name(name),
            })
        }).collect();
        if let Ok(ref mut v) = authors {
            v.sort_by(|ref a, ref b| a.name().cmp(&b.name()));
        }
        authors
    }

    pub fn add_author(&self,
                      conn: &GenericConnection,
                      name: &str) -> CargoResult<()> {
        println!("add author: {}", name);
        // TODO: at least try to link `name` to a pre-existing user
        conn.execute("INSERT INTO version_authors (version_id, name)
                           VALUES ($1, $2)", &[&self.id, &name])?;
        Ok(())
    }

    pub fn yank(&self, conn: &GenericConnection, yanked: bool) -> CargoResult<()> {
        conn.execute("UPDATE versions SET yanked = $1 WHERE id = $2",
                     &[&yanked, &self.id])?;
        Ok(())
    }
}

impl Model for Version {
    fn from_row(row: &Row) -> Version {
        let num: String = row.get("num");
        let features: Option<String> = row.get("features");
        let features = features.map(|s| {
            json::decode(&s).unwrap()
        }).unwrap_or_else(|| HashMap::new());
        Version {
            id: row.get("id"),
            crate_id: row.get("crate_id"),
            num: semver::Version::parse(&num).unwrap(),
            updated_at: row.get("updated_at"),
            created_at: row.get("created_at"),
            downloads: row.get("downloads"),
            features: features,
            yanked: row.get("yanked"),
        }
    }
    fn table_name(_: Option<Version>) -> &'static str { "versions" }
}

impl Author {
    fn name(&self) -> Option<&str> {
        match self {
            &Author::Name(ref n) => {Some(&n)},
            &Author::User(ref u) => {u.name.as_ref().map(String::as_str)}
        }
    }
}

/// Handles the `GET /versions` route.
pub fn index(req: &mut Request) -> CargoResult<Response> {
    let conn = req.tx()?;

    // Extract all ids requested.
    let query = url::form_urlencoded::parse(req.query_string().unwrap_or("")
                                               .as_bytes());
    let ids = query.filter_map(|(ref a, ref b)| {
        if *a == "ids[]" {
            b.parse().ok()
        } else {
            None
        }
    }).collect::<Vec<i32>>();

    // Load all versions
    //
    // TODO: can rust-postgres do this for us?
    let mut versions = Vec::new();
    if ids.len() > 0 {
        let stmt = conn.prepare("\
            SELECT versions.*, crates.name AS crate_name
              FROM versions
            LEFT JOIN crates ON crates.id = versions.crate_id
            WHERE versions.id = ANY($1)
        ")?;
        for row in stmt.query(&[&ids])?.iter() {
            let v: Version = Model::from_row(&row);
            let crate_name: String = row.get("crate_name");
            versions.push(v.encodable(&crate_name));
        }
    }

    #[derive(RustcEncodable)]
    struct R { versions: Vec<EncodableVersion> }
    Ok(req.json(&R { versions: versions }))
}

/// Handles the `GET /versions/:version_id` route.
pub fn show(req: &mut Request) -> CargoResult<Response> {
    let (version, krate) = match req.params().find("crate_id") {
        Some(..) => version_and_crate(req)?,
        None => {
            let id = &req.params()["version_id"];
            let id = id.parse().unwrap_or(0);
            let conn = req.tx()?;
            let version = Version::find(&*conn, id)?;
            let krate = Crate::find(&*conn, version.crate_id)?;
            (version, krate)
        }
    };

    #[derive(RustcEncodable)]
    struct R { version: EncodableVersion }
    Ok(req.json(&R { version: version.encodable(&krate.name) }))
}

fn version_and_crate(req: &mut Request) -> CargoResult<(Version, Crate)> {
    let crate_name = &req.params()["crate_id"];
    let semver = &req.params()["version"];
    let semver = semver::Version::parse(semver).map_err(|_| {
        human(format!("invalid semver: {}", semver))
    })?;
    let tx = req.tx()?;
    let krate = Crate::find_by_name(tx, crate_name)?;
    let version = Version::find_by_num(tx, krate.id, &semver)?;
    let version = version.chain_error(|| {
        human(format!("crate `{}` does not have a version `{}`",
                      crate_name, semver))
    })?;
    Ok((version, krate))
}

/// Handles the `GET /crates/:crate_id/:version/dependencies` route.
pub fn dependencies(req: &mut Request) -> CargoResult<Response> {
    let (version, _) = version_and_crate(req)?;
    let tx = req.tx()?;
    let deps = version.dependencies(tx)?;
    let deps = deps.into_iter().map(|(dep, crate_name)| {
        dep.encodable(&crate_name, None)
    }).collect();

    #[derive(RustcEncodable)]
    struct R { dependencies: Vec<EncodableDependency> }
    Ok(req.json(&R{ dependencies: deps }))
}

/// Handles the `GET /crates/:crate_id/:version/downloads` route.
pub fn downloads(req: &mut Request) -> CargoResult<Response> {
    let (version, _) = version_and_crate(req)?;

    let tx = req.tx()?;
    let cutoff_date = ::now() + Duration::days(-90);
    let stmt = tx.prepare("SELECT * FROM version_downloads
                                WHERE date > $1 AND version_id = $2
                                ORDER BY date ASC")?;
    let mut downloads = Vec::new();
    for row in stmt.query(&[&cutoff_date, &version.id])?.iter() {
        let download: VersionDownload = Model::from_row(&row);
        downloads.push(download.encodable());
    }

    #[derive(RustcEncodable)]
    struct R { version_downloads: Vec<EncodableVersionDownload> }
    Ok(req.json(&R{ version_downloads: downloads }))
}

/// Handles the `GET /crates/:crate_id/:version/authors` route.
pub fn authors(req: &mut Request) -> CargoResult<Response> {
    let (version, _) = version_and_crate(req)?;
    let tx = req.tx()?;
    let (mut users, mut names) = (Vec::new(), Vec::new());
    for author in version.authors(tx)?.into_iter() {
        match author {
            Author::User(u) => users.push(u.encodable()),
            Author::Name(n) => names.push(n),
        }
    }

    #[derive(RustcEncodable)]
    struct R { users: Vec<::user::EncodableUser>, meta: Meta }
    #[derive(RustcEncodable)]
    struct Meta { names: Vec<String> }
    Ok(req.json(&R{ users: users, meta: Meta { names: names } }))
}

/// Handles the `DELETE /crates/:crate_id/:version/yank` route.
pub fn yank(req: &mut Request) -> CargoResult<Response> {
    modify_yank(req, true)
}

/// Handles the `PUT /crates/:crate_id/:version/unyank` route.
pub fn unyank(req: &mut Request) -> CargoResult<Response> {
    modify_yank(req, false)
}

fn modify_yank(req: &mut Request, yanked: bool) -> CargoResult<Response> {
    let (version, krate) = version_and_crate(req)?;
    let user = req.user()?;
    let tx = req.tx()?;
    let owners = krate.owners(tx)?;
    if rights(req.app(), &owners, &user)? < Rights::Publish {
        return Err(human("must already be an owner to yank or unyank"))
    }

    if version.yanked != yanked {
        version.yank(tx, yanked)?;
        git::yank(&**req.app(), &krate.name, &version.num, yanked)?;
    }

    #[derive(RustcEncodable)]
    struct R { ok: bool }
    Ok(req.json(&R{ ok: true }))
}
<|endoftext|>


<commit_before>use clap::{self, Arg, App, SubCommand, AppSettings};
use std::io::{self, Write};
use std::process;
use std::path::PathBuf;
use std::error;
use std::result;
use std::fmt;
use mprovision as mp;

#[derive(Debug, PartialEq)]
pub enum Command {
    List(ListParams),
    ShowUuid(String, Option<PathBuf>),
    ShowFile(PathBuf),
    Remove(String, Option<PathBuf>),
    Clean(Option<PathBuf>),
}

#[derive(Debug, Default, PartialEq)]
pub struct ListParams {
    pub filter: Option<String>,
    pub expire_in_days: Option<i64>,
    pub directory: Option<PathBuf>,
}

pub type Result = result::Result<Command, Error>;

#[derive(Debug)]
pub enum Error {
    Lib(mp::Error),
    Clap(clap::Error),
    Custom(String),
}

impl Error {
    pub fn exit(&self) -> ! {
        match *self {
            Error::Clap(ref e) => e.exit(),
            _ => {
                writeln!(&mut io::stderr(), "{}", self).unwrap();
                process::exit(1);
            }
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Lib(ref e) => e.description(),
            Error::Clap(ref e) => e.description(),
            Error::Custom(ref e) => e,
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::Lib(ref e) => Some(e),
            Error::Clap(ref e) => Some(e),
            Error::Custom(_) => None,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Lib(ref e) => error::Error::description(e).fmt(f),
            Error::Clap(ref e) => error::Error::description(e).fmt(f),
            Error::Custom(ref e) => e.fmt(f),
        }
    }
}

impl From<mp::Error> for Error {
    fn from(e: mp::Error) -> Self {
        Error::Lib(e)
    }
}

impl From<clap::Error> for Error {
    fn from(e: clap::Error) -> Self {
        Error::Clap(e)
    }
}

pub fn parse<I, S>(args: I) -> Result
    where I: IntoIterator<Item = S>,
          S: Clone,
          ::std::ffi::OsString: From<S>
{
    let app = App::new("mprovision")
        .about("A tool that helps iOS developers to manage mobileprovision files.")
        .subcommand(SubCommand::with_name("list")
            .about("Lists provisioning profiles")
            .display_order(0)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("TEXT")
                .long("text")
                .short("t")
                .help("Lists provisioning profiles that contain this text")
                .display_order(0)
                .required(false)
                .empty_values(false)
                .takes_value(true))
            .arg(Arg::with_name("DAYS")
                .long("expire-in-days")
                .short("d")
                .help("Lists provisioning profiles that will expire in days")
                .display_order(1)
                .required(false)
                .empty_values(false)
                .takes_value(true))
            .arg(Arg::with_name("DIRECTORY")
                .help("A directory where to search provisioning profiles")
                .required(false)
                .empty_values(false)
                .takes_value(true)))
        .subcommand(SubCommand::with_name("show")
            .about("Shows details of a provisioning profile using its uuid")
            .display_order(1)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("UUID")
                .help("An uuid of a provisioning profile")
                .required(true)
                .empty_values(false)
                .takes_value(true))
            .arg(Arg::with_name("DIRECTORY")
                .help("A directory where to search a provisioning profile")
                .required(false)
                .empty_values(false)
                .takes_value(true)))
        .subcommand(SubCommand::with_name("show-file")
            .about("Shows details of a provisioning profile")
            .display_order(2)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("PATH")
                .help("A file path of a provisioning profile")
                .required(true)
                .empty_values(false)
                .takes_value(true)))
        .subcommand(SubCommand::with_name("remove")
            .about("Removes a provisioning profile")
            .display_order(3)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("UUID")
                .help("An uuid of a provisioning profile")
                .required(true)
                .empty_values(false)
                .takes_value(true))
            .arg(Arg::with_name("DIRECTORY")
                .help("A directory where to search a provisioning profile")
                .required(false)
                .empty_values(false)
                .takes_value(true)))
        .subcommand(SubCommand::with_name("clean")
            .about("Removes expired provisioning profiles")
            .display_order(4)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("DIRECTORY")
                .help("A directory where to clean")
                .required(false)
                .empty_values(false)
                .takes_value(true)));

    let matches = app.get_matches_from_safe(args)?;
    if let Some(list_matches) = matches.subcommand_matches("list") {
        let mut params = ListParams::default();
        params.filter = list_matches.value_of("TEXT").map(|text| text.to_string());
        if let Some(days) = list_matches.value_of("DAYS")
            .and_then(|days| days.parse::<i64>().ok()) {
            if days < 0 || days > 365 {
                return Err(Error::Custom("DAYS should be between 0 and 365".to_string()));
            }
            params.expire_in_days = Some(days);
        }
        params.directory = list_matches.value_of("DIRECTORY").map(|dir| dir.into());
        Ok(Command::List(params))
    } else if let Some(show_matches) = matches.subcommand_matches("show") {
        let uuid = show_matches.value_of("UUID").map(|uuid| uuid.to_string()).unwrap();
        let directory = show_matches.value_of("DIRECTORY").map(|dir| dir.into());
        Ok(Command::ShowUuid(uuid, directory))
    } else if let Some(show_file_matches) = matches.subcommand_matches("show-file") {
        let path = show_file_matches.value_of("PATH").map(|path| path.into()).unwrap();
        Ok(Command::ShowFile(path))
    } else if let Some(remove_matches) = matches.subcommand_matches("remove") {
        let uuid = remove_matches.value_of("UUID").map(|uuid| uuid.to_string()).unwrap();
        let directory = remove_matches.value_of("DIRECTORY").map(|dir| dir.into());
        Ok(Command::Remove(uuid, directory))
    } else if let Some(clean_matches) = matches.subcommand_matches("clean") {
        let directory = clean_matches.value_of("DIRECTORY").map(|dir| dir.into());
        Ok(Command::Clean(directory))
    } else {
        Err(Error::Custom("Command isn't implemented".to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use expectest::prelude::*;

    #[test]
    fn list_command() {
        expect!(parse(&["mprovision", "list"]))
            .to(be_ok().value(Command::List(ListParams::default())));

        expect!(parse(&["mprovision", "list", "."])).to(be_ok().value(Command::List(ListParams {
            filter: None,
            expire_in_days: None,
            directory: Some(".".into()),
        })));

        expect!(parse(&["mprovision", "list", "--text", "abc"]))
            .to(be_ok().value(Command::List(ListParams {
                filter: Some("abc".to_string()),
                expire_in_days: None,
                directory: None,
            })));

        expect!(parse(&["mprovision", "list", "-t", "abc"]))
            .to(be_ok().value(Command::List(ListParams {
                filter: Some("abc".to_string()),
                expire_in_days: None,
                directory: None,
            })));

        expect!(parse(&["mprovision", "list", "--text", ""])).to(be_err());

        expect!(parse(&["mprovision", "list", "-t", ""])).to(be_err());

        expect!(parse(&["mprovision", "list", "--expire-in-days", "3"]))
            .to(be_ok().value(Command::List(ListParams {
                filter: None,
                expire_in_days: Some(3),
                directory: None,
            })));

        expect!(parse(&["mprovision", "list", "-d", "3"]))
            .to(be_ok().value(Command::List(ListParams {
                filter: None,
                expire_in_days: Some(3),
                directory: None,
            })));

        expect!(parse(&["mprovision", "list", "--expire-in-days", "-3"])).to(be_err());
        expect!(parse(&["mprovision", "list", "-d", "-3"])).to(be_err());
        expect!(parse(&["mprovision", "list", "--expire-in-days", "366"])).to(be_err());
        expect!(parse(&["mprovision", "list", "-d", "366"])).to(be_err());

        expect!(parse(&["mprovision", "list", "--text", "abc", "--expire-in-days", "3", "."]))
            .to(be_ok().value(Command::List(ListParams {
                filter: Some("abc".to_string()),
                expire_in_days: Some(3),
                directory: Some(".".into()),
            })));

        expect!(parse(&["mprovision", "list", "-t", "abc", "-d", "3", "."]))
            .to(be_ok().value(Command::List(ListParams {
                filter: Some("abc".to_string()),
                expire_in_days: Some(3),
                directory: Some(".".into()),
            })));
    }

    #[test]
    fn show_uuid_command() {
        expect!(parse(&["mprovision", "show", "abcd"]))
            .to(be_ok().value(Command::ShowUuid("abcd".to_string(), None)));

        expect!(parse(&["mprovision", "show", "abcd", "."]))
            .to(be_ok().value(Command::ShowUuid("abcd".to_string(), Some(".".into()))));
    }

    #[test]
    fn show_path_command() {
        expect!(parse(&["mprovision", "show-file", "file.mprovision"]))
            .to(be_ok().value(Command::ShowFile("file.mprovision".into())));

        expect!(parse(&["mprovision", "show-file", "file.mprovision", "."])).to(be_err());
    }

    #[test]
    fn remove_uuid_command() {
        expect!(parse(&["mprovision", "remove", "abcd"]))
            .to(be_ok().value(Command::Remove("abcd".to_string(), None)));

        expect!(parse(&["mprovision", "remove", "abcd", "."]))
            .to(be_ok().value(Command::Remove("abcd".to_string(), Some(".".into()))));
    }

    #[test]
    fn clean_command() {
        expect!(parse(&["mprovision", "clean"])).to(be_ok().value(Command::Clean(None)));

        expect!(parse(&["mprovision", "clean", "."]))
            .to(be_ok().value(Command::Clean(Some(".".into()))));
    }
}
<commit_msg>Make <DIRECTORY> as --source argument<commit_after>use clap::{self, Arg, App, SubCommand, AppSettings};
use std::io::{self, Write};
use std::process;
use std::path::PathBuf;
use std::error;
use std::result;
use std::fmt;
use mprovision as mp;

#[derive(Debug, PartialEq)]
pub enum Command {
    List(ListParams),
    ShowUuid(String, Option<PathBuf>),
    ShowFile(PathBuf),
    Remove(String, Option<PathBuf>),
    Clean(Option<PathBuf>),
}

#[derive(Debug, Default, PartialEq)]
pub struct ListParams {
    pub filter: Option<String>,
    pub expire_in_days: Option<i64>,
    pub directory: Option<PathBuf>,
}

pub type Result = result::Result<Command, Error>;

#[derive(Debug)]
pub enum Error {
    Lib(mp::Error),
    Clap(clap::Error),
    Custom(String),
}

impl Error {
    pub fn exit(&self) -> ! {
        match *self {
            Error::Clap(ref e) => e.exit(),
            _ => {
                writeln!(&mut io::stderr(), "{}", self).unwrap();
                process::exit(1);
            }
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Lib(ref e) => e.description(),
            Error::Clap(ref e) => e.description(),
            Error::Custom(ref e) => e,
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::Lib(ref e) => Some(e),
            Error::Clap(ref e) => Some(e),
            Error::Custom(_) => None,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Lib(ref e) => error::Error::description(e).fmt(f),
            Error::Clap(ref e) => error::Error::description(e).fmt(f),
            Error::Custom(ref e) => e.fmt(f),
        }
    }
}

impl From<mp::Error> for Error {
    fn from(e: mp::Error) -> Self {
        Error::Lib(e)
    }
}

impl From<clap::Error> for Error {
    fn from(e: clap::Error) -> Self {
        Error::Clap(e)
    }
}

pub fn parse<I, S>(args: I) -> Result
    where I: IntoIterator<Item = S>,
          S: Clone,
          ::std::ffi::OsString: From<S>
{
    let app = App::new("mprovision")
        .about("A tool that helps iOS developers to manage mobileprovision files.")
        .subcommand(SubCommand::with_name("list")
            .about("Lists provisioning profiles")
            .display_order(0)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("TEXT")
                .long("text")
                .short("t")
                .help("Lists provisioning profiles that contain this text")
                .display_order(0)
                .required(false)
                .empty_values(false)
                .takes_value(true))
            .arg(Arg::with_name("DAYS")
                .long("expire-in-days")
                .short("d")
                .help("Lists provisioning profiles that will expire in days")
                .display_order(1)
                .required(false)
                .empty_values(false)
                .takes_value(true))
            .arg(Arg::with_name("DIRECTORY")
                .long("source")
                .help("A directory where to search provisioning profiles")
                .required(false)
                .empty_values(false)
                .takes_value(true)))
        .subcommand(SubCommand::with_name("show")
            .about("Shows details of a provisioning profile using its uuid")
            .display_order(1)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("UUID")
                .help("An uuid of a provisioning profile")
                .required(true)
                .empty_values(false)
                .takes_value(true))
            .arg(Arg::with_name("DIRECTORY")
                .long("source")
                .help("A directory where to search a provisioning profile")
                .required(false)
                .empty_values(false)
                .takes_value(true)))
        .subcommand(SubCommand::with_name("show-file")
            .about("Shows details of a provisioning profile")
            .display_order(2)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("PATH")
                .help("A file path of a provisioning profile")
                .required(true)
                .empty_values(false)
                .takes_value(true)))
        .subcommand(SubCommand::with_name("remove")
            .about("Removes a provisioning profile")
            .display_order(3)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("UUID")
                .help("An uuid of a provisioning profile")
                .required(true)
                .empty_values(false)
                .takes_value(true))
            .arg(Arg::with_name("DIRECTORY")
                .long("source")
                .help("A directory where to search a provisioning profile")
                .required(false)
                .empty_values(false)
                .takes_value(true)))
        .subcommand(SubCommand::with_name("clean")
            .about("Removes expired provisioning profiles")
            .display_order(4)
            .setting(AppSettings::DisableVersion)
            .arg(Arg::with_name("DIRECTORY")
                .long("source")
                .help("A directory where to clean")
                .required(false)
                .empty_values(false)
                .takes_value(true)));

    let matches = app.get_matches_from_safe(args)?;
    if let Some(list_matches) = matches.subcommand_matches("list") {
        let mut params = ListParams::default();
        params.filter = list_matches.value_of("TEXT").map(|text| text.to_string());
        if let Some(days) = list_matches.value_of("DAYS")
            .and_then(|days| days.parse::<i64>().ok()) {
            if days < 0 || days > 365 {
                return Err(Error::Custom("DAYS should be between 0 and 365".to_string()));
            }
            params.expire_in_days = Some(days);
        }
        params.directory = list_matches.value_of("DIRECTORY").map(|dir| dir.into());
        Ok(Command::List(params))
    } else if let Some(show_matches) = matches.subcommand_matches("show") {
        let uuid = show_matches.value_of("UUID").map(|uuid| uuid.to_string()).unwrap();
        let directory = show_matches.value_of("DIRECTORY").map(|dir| dir.into());
        Ok(Command::ShowUuid(uuid, directory))
    } else if let Some(show_file_matches) = matches.subcommand_matches("show-file") {
        let path = show_file_matches.value_of("PATH").map(|path| path.into()).unwrap();
        Ok(Command::ShowFile(path))
    } else if let Some(remove_matches) = matches.subcommand_matches("remove") {
        let uuid = remove_matches.value_of("UUID").map(|uuid| uuid.to_string()).unwrap();
        let directory = remove_matches.value_of("DIRECTORY").map(|dir| dir.into());
        Ok(Command::Remove(uuid, directory))
    } else if let Some(clean_matches) = matches.subcommand_matches("clean") {
        let directory = clean_matches.value_of("DIRECTORY").map(|dir| dir.into());
        Ok(Command::Clean(directory))
    } else {
        Err(Error::Custom("Command isn't implemented".to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use expectest::prelude::*;

    #[test]
    fn list_command() {
        expect!(parse(&["mprovision", "list"]))
            .to(be_ok().value(Command::List(ListParams::default())));

        expect!(parse(&["mprovision", "list", "--source", "."]))
            .to(be_ok().value(Command::List(ListParams {
                filter: None,
                expire_in_days: None,
                directory: Some(".".into()),
            })));

        expect!(parse(&["mprovision", "list", "--text", "abc"]))
            .to(be_ok().value(Command::List(ListParams {
                filter: Some("abc".to_string()),
                expire_in_days: None,
                directory: None,
            })));

        expect!(parse(&["mprovision", "list", "-t", "abc"]))
            .to(be_ok().value(Command::List(ListParams {
                filter: Some("abc".to_string()),
                expire_in_days: None,
                directory: None,
            })));

        expect!(parse(&["mprovision", "list", "--text", ""])).to(be_err());

        expect!(parse(&["mprovision", "list", "-t", ""])).to(be_err());

        expect!(parse(&["mprovision", "list", "--expire-in-days", "3"]))
            .to(be_ok().value(Command::List(ListParams {
                filter: None,
                expire_in_days: Some(3),
                directory: None,
            })));

        expect!(parse(&["mprovision", "list", "-d", "3"]))
            .to(be_ok().value(Command::List(ListParams {
                filter: None,
                expire_in_days: Some(3),
                directory: None,
            })));

        expect!(parse(&["mprovision", "list", "--expire-in-days", "-3"])).to(be_err());
        expect!(parse(&["mprovision", "list", "-d", "-3"])).to(be_err());
        expect!(parse(&["mprovision", "list", "--expire-in-days", "366"])).to(be_err());
        expect!(parse(&["mprovision", "list", "-d", "366"])).to(be_err());

        expect!(parse(&["mprovision",
                        "list",
                        "--text",
                        "abc",
                        "--expire-in-days",
                        "3",
                        "--source",
                        "."]))
            .to(be_ok().value(Command::List(ListParams {
                filter: Some("abc".to_string()),
                expire_in_days: Some(3),
                directory: Some(".".into()),
            })));

        expect!(parse(&["mprovision", "list", "-t", "abc", "-d", "3", "--source", "."]))
            .to(be_ok().value(Command::List(ListParams {
                filter: Some("abc".to_string()),
                expire_in_days: Some(3),
                directory: Some(".".into()),
            })));
    }

    #[test]
    fn show_uuid_command() {
        expect!(parse(&["mprovision", "show", "abcd"]))
            .to(be_ok().value(Command::ShowUuid("abcd".to_string(), None)));

        expect!(parse(&["mprovision", "show", "abcd", "--source", "."]))
            .to(be_ok().value(Command::ShowUuid("abcd".to_string(), Some(".".into()))));
    }

    #[test]
    fn show_path_command() {
        expect!(parse(&["mprovision", "show-file", "file.mprovision"]))
            .to(be_ok().value(Command::ShowFile("file.mprovision".into())));

        expect!(parse(&["mprovision", "show-file", "file.mprovision", "."])).to(be_err());
    }

    #[test]
    fn remove_uuid_command() {
        expect!(parse(&["mprovision", "remove", "abcd"]))
            .to(be_ok().value(Command::Remove("abcd".to_string(), None)));

        expect!(parse(&["mprovision", "remove", "abcd", "--source", "."]))
            .to(be_ok().value(Command::Remove("abcd".to_string(), Some(".".into()))));
    }

    #[test]
    fn clean_command() {
        expect!(parse(&["mprovision", "clean"])).to(be_ok().value(Command::Clean(None)));

        expect!(parse(&["mprovision", "clean", "--source", "."]))
            .to(be_ok().value(Command::Clean(Some(".".into()))));
    }
}
<|endoftext|>


<commit_before>use super::daemon::*;

use std::io::{Error, ErrorKind};
use std::ptr;
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;

use kernel32::*;
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, LPVOID, TRUE};
use winapi::um::winnt::{SERVICE_WIN32_OWN_PROCESS, LPWSTR};
use winapi::um::winsvc::*;

declare_singleton!(
    singleton,
    DaemonHolder,
    DaemonHolder {
        holder: 0 as *mut DaemonStatic
    }
);

struct DaemonHolder {
    holder: *mut DaemonStatic,
}

struct DaemonStatic {
    name: String,
    holder: Box<DaemonFunc>,
    handle: SERVICE_STATUS_HANDLE,
}

trait DaemonFunc {
    fn exec(&mut self) -> Result<(), Error>;
    fn send(&mut self, state: State) -> Result<(), Error>;
    fn take_tx(&mut self) -> Option<Sender<State>>;
}

struct DaemonFuncHolder<F: FnOnce(Receiver<State>)> {
    tx: Option<Sender<State>>,
    func: Option<(F, Receiver<State>)>,
}

impl<F: FnOnce(Receiver<State>)> DaemonFunc for DaemonFuncHolder<F> {
    fn exec(&mut self) -> Result<(), Error> {
        match self.func.take() {
            Some((func, rx)) => {
                func(rx);
                Ok(())
            }
            None => Err(Error::new(
                ErrorKind::Other,
                "INTERNAL ERROR: Can't unwrap daemon function",
            )),
        }
    }

    fn send(&mut self, state: State) -> Result<(), Error> {
        match self.tx {
            Some(ref tx) => match tx.send(state) {
                Ok(()) => Ok(()),
                Err(e) => Err(Error::new(ErrorKind::Other, e)),
            },
            None => Err(Error::new(ErrorKind::Other, "Service is already exited")),
        }
    }

    fn take_tx(&mut self) -> Option<Sender<State>> {
        self.tx.take()
    }
}

fn daemon_wrapper<R, F: FnOnce(&mut DaemonHolder) -> R>(func: F) -> R {
    let singleton = singleton();
    let result = match singleton.lock() {
        Ok(ref mut daemon) => func(daemon),
        Err(e) => {
            panic!("Mutex error: {:?}", e);
        }
    };
    result
}

impl DaemonRunner for Daemon {
    fn run<F: 'static + FnOnce(Receiver<State>)>(&self, func: F) -> Result<(), Error> {
        let (tx, rx) = channel();
        tx.send(State::Start).unwrap();
        let mut daemon = DaemonStatic {
            name: self.name.clone(),
            holder: Box::new(DaemonFuncHolder {
                tx: Some(tx),
                func: Some((func, rx)),
            }),
            handle: 0 as SERVICE_STATUS_HANDLE,
        };
        try!(guard_compare_and_swap(daemon_null(), &mut daemon));
        let result = daemon_service(&mut daemon);
        try!(guard_compare_and_swap(&mut daemon, daemon_null()));
        result
    }
}

fn guard_compare_and_swap(
    old_value: *mut DaemonStatic,
    new_value: *mut DaemonStatic,
) -> Result<(), Error> {
    daemon_wrapper(|daemon_static: &mut DaemonHolder| -> Result<(), Error> {
        if daemon_static.holder != old_value {
            return Err(Error::new(
                ErrorKind::Other,
                "This function is not reentrant.",
            ));
        }
        daemon_static.holder = new_value;
        Ok(())
    })
}

fn daemon_service(daemon: &mut DaemonStatic) -> Result<(), Error> {
    unsafe {
        let service_name = service_name(&daemon.name);
        let service_table: &[*const SERVICE_TABLE_ENTRYW] = &[
            &SERVICE_TABLE_ENTRYW {
                lpServiceName: service_name.as_ptr(),
                lpServiceProc: Some(service_main),
            },
            ptr::null(),
        ];
        match StartServiceCtrlDispatcherW(*service_table.as_ptr()) {
            0 => daemon_console(daemon),
            _ => Ok(()),
        }
    }
}

fn daemon_console(daemon: &mut DaemonStatic) -> Result<(), Error> {
    let result;
    unsafe {
        if SetConsoleCtrlHandler(Some(console_handler), TRUE) == FALSE {
            return Err(Error::last_os_error());
        }
        result = daemon.holder.exec();
        if SetConsoleCtrlHandler(Some(console_handler), FALSE) == FALSE {
            return Err(Error::last_os_error());
        }
    }
    result
}

fn service_name(name: &str) -> Vec<u16> {
    let mut result: Vec<u16> = name.chars().map(|c| c as u16).collect();
    result.push(0);
    result
}

fn create_service_status(current_state: DWORD) -> SERVICE_STATUS {
    SERVICE_STATUS {
        dwServiceType: SERVICE_WIN32_OWN_PROCESS,
        dwCurrentState: current_state,
        dwControlsAccepted: SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN,
        dwWin32ExitCode: 0,
        dwServiceSpecificExitCode: 0,
        dwCheckPoint: 0,
        dwWaitHint: 0,
    }
}

unsafe extern "system" fn service_main(
    _: DWORD,       // dw_num_services_args
    _: *mut LPWSTR, // lp_service_arg_vectors
) {
    daemon_wrapper(|daemon_static: &mut DaemonHolder| {
        if daemon_static.holder != daemon_null() {
            let daemon = &mut *daemon_static.holder;
            let service_name = service_name(&daemon.name);
            daemon.handle = RegisterServiceCtrlHandlerExW(
                service_name.as_ptr(),
                Some(service_handler),
                ptr::null_mut(),
            );
            SetServiceStatus(
                daemon.handle,
                &mut create_service_status(SERVICE_START_PENDING),
            );
            SetServiceStatus(daemon.handle, &mut create_service_status(SERVICE_RUNNING));

            daemon.holder.exec().unwrap();
            SetServiceStatus(daemon.handle, &mut create_service_status(SERVICE_STOPPED));
        }
    });
}

unsafe extern "system" fn service_handler(
    dw_control: DWORD,
    _: DWORD,  // dw_event_type
    _: LPVOID, // lp_event_data
    _: LPVOID, // lp_context
) -> DWORD {
    daemon_wrapper(|daemon_static: &mut DaemonHolder| {
        let daemon = &mut *daemon_static.holder;
        match dw_control {
            SERVICE_CONTROL_STOP | SERVICE_CONTROL_SHUTDOWN => match daemon.holder.take_tx() {
                Some(ref tx) => {
                    SetServiceStatus(
                        daemon.handle,
                        &mut create_service_status(SERVICE_STOP_PENDING),
                    );
                    let _ = tx.send(State::Stop);
                }
                None => {}
            },
            _ => {}
        };
    });
    0
}

unsafe extern "system" fn console_handler(_: DWORD) -> BOOL {
    daemon_wrapper(|daemon_static: &mut DaemonHolder| -> BOOL {
        if daemon_static.holder != daemon_null() {
            let daemon = &mut *daemon_static.holder;
            match daemon.holder.take_tx() {
                Some(ref tx) => match tx.send(State::Stop) {
                    Ok(_) => TRUE,
                    Err(_) => FALSE,
                },
                None => TRUE,
            }
        } else {
            FALSE
        }
    })
}

fn daemon_null() -> *mut DaemonStatic {
    0 as *mut DaemonStatic
}
<commit_msg>fix windows service lockup<commit_after>use super::daemon::*;

use std::io::{Error, ErrorKind};
use std::ptr;
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;

use kernel32::*;
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, LPVOID, TRUE};
use winapi::um::winnt::{SERVICE_WIN32_OWN_PROCESS, LPWSTR};
use winapi::um::winsvc::*;

declare_singleton!(
    singleton,
    DaemonHolder,
    DaemonHolder {
        holder: 0 as *mut DaemonStatic
    }
);

struct DaemonHolder {
    holder: *mut DaemonStatic,
}

struct DaemonStatic {
    name: String,
    holder: Box<DaemonFunc>,
    handle: SERVICE_STATUS_HANDLE,
}

trait DaemonFunc {
    fn exec(&mut self) -> Result<(), Error>;
    fn send(&mut self, state: State) -> Result<(), Error>;
    fn take_tx(&mut self) -> Option<Sender<State>>;
}

struct DaemonFuncHolder<F: FnOnce(Receiver<State>)> {
    tx: Option<Sender<State>>,
    func: Option<(F, Receiver<State>)>,
}

impl<F: FnOnce(Receiver<State>)> DaemonFunc for DaemonFuncHolder<F> {
    fn exec(&mut self) -> Result<(), Error> {
        match self.func.take() {
            Some((func, rx)) => {
                func(rx);
                Ok(())
            }
            None => Err(Error::new(
                ErrorKind::Other,
                "INTERNAL ERROR: Can't unwrap daemon function",
            )),
        }
    }

    fn send(&mut self, state: State) -> Result<(), Error> {
        match self.tx {
            Some(ref tx) => match tx.send(state) {
                Ok(()) => Ok(()),
                Err(e) => Err(Error::new(ErrorKind::Other, e)),
            },
            None => Err(Error::new(ErrorKind::Other, "Service is already exited")),
        }
    }

    fn take_tx(&mut self) -> Option<Sender<State>> {
        self.tx.take()
    }
}

fn daemon_wrapper<R, F: FnOnce(&mut DaemonHolder) -> R>(func: F) -> R {
    let singleton = singleton();
    let result = match singleton.lock() {
        Ok(ref mut daemon) => func(daemon),
        Err(e) => {
            panic!("Mutex error: {:?}", e);
        }
    };
    result
}

impl DaemonRunner for Daemon {
    fn run<F: 'static + FnOnce(Receiver<State>)>(&self, func: F) -> Result<(), Error> {
        let (tx, rx) = channel();
        tx.send(State::Start).unwrap();
        let mut daemon = DaemonStatic {
            name: self.name.clone(),
            holder: Box::new(DaemonFuncHolder {
                tx: Some(tx),
                func: Some((func, rx)),
            }),
            handle: 0 as SERVICE_STATUS_HANDLE,
        };
        guard_compare_and_swap(daemon_null(), &mut daemon)?;
        let result = daemon_service(&mut daemon);
        guard_compare_and_swap(&mut daemon, daemon_null())?;
        result
    }
}

fn guard_compare_and_swap(
    old_value: *mut DaemonStatic,
    new_value: *mut DaemonStatic,
) -> Result<(), Error> {
    daemon_wrapper(|daemon_static: &mut DaemonHolder| -> Result<(), Error> {
        if daemon_static.holder != old_value {
            return Err(Error::new(
                ErrorKind::Other,
                "This function is not reentrant.",
            ));
        }
        daemon_static.holder = new_value;
        Ok(())
    })
}

fn daemon_service(daemon: &mut DaemonStatic) -> Result<(), Error> {
    unsafe {
        let service_name = service_name(&daemon.name);
        let service_table: &[*const SERVICE_TABLE_ENTRYW] = &[
            &SERVICE_TABLE_ENTRYW {
                lpServiceName: service_name.as_ptr(),
                lpServiceProc: Some(service_main),
            },
            ptr::null(),
        ];
        match StartServiceCtrlDispatcherW(*service_table.as_ptr()) {
            0 => daemon_console(daemon),
            _ => Ok(()),
        }
    }
}

fn daemon_console(daemon: &mut DaemonStatic) -> Result<(), Error> {
    let result;
    unsafe {
        if SetConsoleCtrlHandler(Some(console_handler), TRUE) == FALSE {
            return Err(Error::last_os_error());
        }
        result = daemon.holder.exec();
        if SetConsoleCtrlHandler(Some(console_handler), FALSE) == FALSE {
            return Err(Error::last_os_error());
        }
    }
    result
}

fn service_name(name: &str) -> Vec<u16> {
    let mut result: Vec<u16> = name.chars().map(|c| c as u16).collect();
    result.push(0);
    result
}

fn create_service_status(current_state: DWORD) -> SERVICE_STATUS {
    SERVICE_STATUS {
        dwServiceType: SERVICE_WIN32_OWN_PROCESS,
        dwCurrentState: current_state,
        dwControlsAccepted: SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN,
        dwWin32ExitCode: 0,
        dwServiceSpecificExitCode: 0,
        dwCheckPoint: 0,
        dwWaitHint: 0,
    }
}

unsafe extern "system" fn service_main(
    _: DWORD,       // dw_num_services_args
    _: *mut LPWSTR, // lp_service_arg_vectors
) {
    let daemon_holder = daemon_wrapper(|daemon_static: &mut DaemonHolder| {
        if daemon_static.holder != daemon_null() {
            let daemon = &mut *daemon_static.holder;
            let service_name = service_name(&daemon.name);
            daemon.handle = RegisterServiceCtrlHandlerExW(
                service_name.as_ptr(),
                Some(service_handler),
                ptr::null_mut(),
            );
            SetServiceStatus(
                daemon.handle,
                &mut create_service_status(SERVICE_START_PENDING),
            );
            SetServiceStatus(daemon.handle, &mut create_service_status(SERVICE_RUNNING));

            Some(daemon)
        } else {
            None
        }
    });
    if let Some(daemon) = daemon_holder {
        daemon.holder.exec().unwrap();
    }
    daemon_wrapper(|daemon_static: &mut DaemonHolder| {
        if daemon_static.holder != daemon_null() {
            let daemon = &mut *daemon_static.holder;
            SetServiceStatus(daemon.handle, &mut create_service_status(SERVICE_STOPPED));
        }
    });
}

unsafe extern "system" fn service_handler(
    dw_control: DWORD,
    _: DWORD,  // dw_event_type
    _: LPVOID, // lp_event_data
    _: LPVOID, // lp_context
) -> DWORD {
    daemon_wrapper(|daemon_static: &mut DaemonHolder| {
        let daemon = &mut *daemon_static.holder;
        match dw_control {
            SERVICE_CONTROL_STOP | SERVICE_CONTROL_SHUTDOWN => match daemon.holder.take_tx() {
                Some(ref tx) => {
                    SetServiceStatus(
                        daemon.handle,
                        &mut create_service_status(SERVICE_STOP_PENDING),
                    );
                    let _ = tx.send(State::Stop);
                }
                None => {}
            },
            _ => {}
        };
    });
    0
}

unsafe extern "system" fn console_handler(_: DWORD) -> BOOL {
    daemon_wrapper(|daemon_static: &mut DaemonHolder| -> BOOL {
        if daemon_static.holder != daemon_null() {
            let daemon = &mut *daemon_static.holder;
            match daemon.holder.take_tx() {
                Some(ref tx) => match tx.send(State::Stop) {
                    Ok(_) => TRUE,
                    Err(_) => FALSE,
                },
                None => TRUE,
            }
        } else {
            FALSE
        }
    })
}

fn daemon_null() -> *mut DaemonStatic {
    0 as *mut DaemonStatic
}
<|endoftext|>


<commit_before>extern crate pwx;
extern crate docopt;
extern crate rustc_serialize;
extern crate uuid;
extern crate rpassword;

use pwx::{Pwx,PwxIterator};
use std::io::{Write,stdout,stderr};
use std::process::exit;
use std::path::PathBuf;
use docopt::Docopt;
use uuid::Uuid;
use pwx::util::fuzzy_eq;

const USAGE: &'static str = "
Usage: pwx [options] [<file>] list [-R URL] [-G GROUP] [-U USERNAME] [-T TITLE] [<filter>]
       pwx [options] [<file>] info
       pwx [options] [<file>] get <uuid> <name>
       pwx [options] [<file>] set <uuid <name> <val>
       pwx (--help | --version)

Options:
    -h, --help          Show this help message
    -v, --version       Show pwx version
    --pass-interactive  Read password from console

Filters:
    -R, --url URL
    -G, --group GROUP
    -U, --username USERNAME
    -T, --title TITLE
";

// Get pkg version at compile time
const VERSION: &'static str = env!("CARGO_PKG_VERSION");

macro_rules! usage {
    ($code:expr, $err:expr) => {{
        let _ = writeln!(std::io::stderr(), "{}\n{}", $err, USAGE);
        return $code;
    }};
}

#[derive(RustcDecodable, Debug)]
struct Args {
    arg_file: String,
    arg_name: String,
    arg_val: String,
    arg_uuid: String,
    arg_filter: String,
    flag_url: String,
    flag_group: String,
    flag_username: String,
    flag_title: String,
    cmd_list: bool,
    cmd_set: bool,
    cmd_get: bool,
    cmd_info: bool,
    flag_version: bool,
    flag_pass_interactive: bool,
}

fn get_password_from_console() -> String {
    // Get password from terminal
    print!("Password: ");
    stdout().flush().unwrap();
    rpassword::read_password().ok().expect("Unable to read password from console")
}

/**
 * Get password
 * 1. If --pass-interactive read from console
 * 2. If PWX_PASSWORD is set use it
 * 3. Otherwise read from console
 *
 * This function may panic on encoding issues
 */
fn get_password(args: &Args) -> String {
    if args.flag_pass_interactive {
        return get_password_from_console();
    }

    let var = std::env::var("PWX_PASSWORD");
    if var.is_ok() {
        return var.unwrap()
    }

    get_password_from_console()
}

/** Filters for the 'list' command */
struct ListFilter<'a> {
    m_url: bool,
    m_group: bool,
    m_username: bool,
    m_title: bool,
    m_filter: bool,
    args: &'a Args,
}

impl<'a> ListFilter<'a> {
    fn new(args: &Args) -> ListFilter {
        ListFilter {
            m_group: args.flag_group.is_empty(),
            m_url: args.flag_url.is_empty(),
            m_username: args.flag_username.is_empty(),
            m_title: args.flag_title.is_empty(),
            m_filter: args.arg_filter.is_empty(),
            args: args,
        }
    }

    /** Process field */
    fn process(&mut self, typ: u8, val: &[u8]) {
        match typ {
            0x02 => {
                self.m_group = self.m_group || fuzzy_eq(&self.args.flag_group, &String::from_utf8_lossy(val.as_ref()));
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x03 => {
                self.m_title = self.m_title || fuzzy_eq(&self.args.flag_title, &String::from_utf8_lossy(val.as_ref()));
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x04 => {
                self.m_username = self.m_username || fuzzy_eq(&self.args.flag_username, &String::from_utf8_lossy(val.as_ref()));
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x05 => {
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x0d => {
                self.m_url = self.m_url || fuzzy_eq(&self.args.flag_url, &String::from_utf8_lossy(val.as_ref()));
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x14 => {
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0xff => *self = ListFilter::new(self.args),
            _ => (),
        }

    }

    /** Returns true if the record matches the filter */
    fn matched(&self) -> bool {
        self.m_group && self.m_url && self.m_username && self.m_title && self.m_filter
    }
}

fn real_main() -> i32 {

    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());

    if args.flag_version {
        println!("pwx {}", VERSION);
        return 0;
    }

    // The password safe is one of
    // 1. Command line [<file>]
    // 2. PWX_DATABASE env var
    // 3. ~/.pwsafe/psafe.psafe3
    let env_db = std::env::var("PWX_DATABASE").unwrap_or(String::new());

    let path = if !args.arg_file.is_empty() {
        PathBuf::from(&args.arg_file)
    } else if !env_db.is_empty() {
        PathBuf::from(&env_db)
    } else {
        std::env::home_dir().expect("Cannot find your HOME path")
            .join(".pwsafe").join("pwsafe.psafe3")
    };

    // FIXME: PathBuf::exists is still Unstable, but we need to verify
    // if the file exists before prompting for password
    match std::fs::File::open(&path) {
        Err(err) => {
            let _ = writeln!(stderr(), "{}: {}", path.to_string_lossy(), err);
            return -1;
        },
        _ => (),
    }

    let mut p = match Pwx::open(&path, get_password(&args).as_bytes()) {
        Err(f) => {
            let _ = writeln!(stderr(), "Error: {} {}", f, path.to_string_lossy());
            exit(-1);
        },
        Ok(p) => p,
    };

    if !p.is_authentic() {
        return -1;
    }

    if args.cmd_list {
        let mut fields = PwxIterator::from_start(&mut p).unwrap();
        fields.skip_record();

        let mut uuid = String::new();
        let mut title = String::new();
        let mut username = String::new();

        let mut filter = ListFilter::new(&args);
        for (typ,val) in fields {
            match typ {
                0x01 => uuid = Uuid::from_bytes(val.as_ref()).unwrap_or(Uuid::nil()).to_hyphenated_string(),
                0x03 => {
                    title = String::from_utf8_lossy(val.as_ref()).into_owned();
                },
                0x04 => {
                    username = String::from_utf8_lossy(val.as_ref()).into_owned();
                },
                0xff => {
                    if filter.matched() {
                        println!("{} {}[{}]", uuid, title, username);
                        uuid = String::new();
                        title = String::new();
                        username = String::new();
                    }
                }
                _ => (),
            }
            filter.process(typ, &val);
        }
    } else if args.cmd_info {

        let fields = PwxIterator::from_start(&mut p).unwrap();
        for (typ,val) in fields {
            match typ {
                0x01 => print!("{} ", Uuid::from_bytes(val.as_ref()).unwrap_or(Uuid::nil()).to_hyphenated_string()),
                0x07 => print!("{} ", String::from_utf8_lossy(val.as_ref())),
                0x08 => println!("@{} ", String::from_utf8_lossy(val.as_ref())),
                0x09 => println!("{} ", String::from_utf8_lossy(val.as_ref())),
                0x0a => println!("\"{}\" ", String::from_utf8_lossy(val.as_ref())),
                0xff => break,
                _ => (),
            }
        }
    }

    return 0
}

// Rust set_exit_status cannot be used yet, the only way to
// set an exit code to exit using exit(code) so we wrap the
// main function like this
fn main() {
    let exit_code = real_main();
    exit(exit_code);
}

<commit_msg>Remove set command from help<commit_after>extern crate pwx;
extern crate docopt;
extern crate rustc_serialize;
extern crate uuid;
extern crate rpassword;

use pwx::{Pwx,PwxIterator};
use std::io::{Write,stdout,stderr};
use std::process::exit;
use std::path::PathBuf;
use docopt::Docopt;
use uuid::Uuid;
use pwx::util::fuzzy_eq;

const USAGE: &'static str = "
Usage: pwx [options] [<file>] list [-R URL] [-G GROUP] [-U USERNAME] [-T TITLE] [<filter>]
       pwx [options] [<file>] info
       pwx [options] [<file>] get <uuid> <name>
       pwx (--help | --version)

Options:
    -h, --help          Show this help message
    -v, --version       Show pwx version
    --pass-interactive  Read password from console

Filters:
    -R, --url URL
    -G, --group GROUP
    -U, --username USERNAME
    -T, --title TITLE
";

// Get pkg version at compile time
const VERSION: &'static str = env!("CARGO_PKG_VERSION");

macro_rules! usage {
    ($code:expr, $err:expr) => {{
        let _ = writeln!(std::io::stderr(), "{}\n{}", $err, USAGE);
        return $code;
    }};
}

#[derive(RustcDecodable, Debug)]
struct Args {
    arg_file: String,
    arg_name: String,
    arg_uuid: String,
    arg_filter: String,
    flag_url: String,
    flag_group: String,
    flag_username: String,
    flag_title: String,
    cmd_list: bool,
    cmd_get: bool,
    cmd_info: bool,
    flag_version: bool,
    flag_pass_interactive: bool,
}

fn get_password_from_console() -> String {
    // Get password from terminal
    print!("Password: ");
    stdout().flush().unwrap();
    rpassword::read_password().ok().expect("Unable to read password from console")
}

/**
 * Get password
 * 1. If --pass-interactive read from console
 * 2. If PWX_PASSWORD is set use it
 * 3. Otherwise read from console
 *
 * This function may panic on encoding issues
 */
fn get_password(args: &Args) -> String {
    if args.flag_pass_interactive {
        return get_password_from_console();
    }

    let var = std::env::var("PWX_PASSWORD");
    if var.is_ok() {
        return var.unwrap()
    }

    get_password_from_console()
}

/** Filters for the 'list' command */
struct ListFilter<'a> {
    m_url: bool,
    m_group: bool,
    m_username: bool,
    m_title: bool,
    m_filter: bool,
    args: &'a Args,
}

impl<'a> ListFilter<'a> {
    fn new(args: &Args) -> ListFilter {
        ListFilter {
            m_group: args.flag_group.is_empty(),
            m_url: args.flag_url.is_empty(),
            m_username: args.flag_username.is_empty(),
            m_title: args.flag_title.is_empty(),
            m_filter: args.arg_filter.is_empty(),
            args: args,
        }
    }

    /** Process field */
    fn process(&mut self, typ: u8, val: &[u8]) {
        match typ {
            0x02 => {
                self.m_group = self.m_group || fuzzy_eq(&self.args.flag_group, &String::from_utf8_lossy(val.as_ref()));
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x03 => {
                self.m_title = self.m_title || fuzzy_eq(&self.args.flag_title, &String::from_utf8_lossy(val.as_ref()));
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x04 => {
                self.m_username = self.m_username || fuzzy_eq(&self.args.flag_username, &String::from_utf8_lossy(val.as_ref()));
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x05 => {
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x0d => {
                self.m_url = self.m_url || fuzzy_eq(&self.args.flag_url, &String::from_utf8_lossy(val.as_ref()));
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0x14 => {
                self.m_filter = self.m_filter || fuzzy_eq(&self.args.arg_filter, &String::from_utf8_lossy(val.as_ref()));
            },
            0xff => *self = ListFilter::new(self.args),
            _ => (),
        }

    }

    /** Returns true if the record matches the filter */
    fn matched(&self) -> bool {
        self.m_group && self.m_url && self.m_username && self.m_title && self.m_filter
    }
}

fn real_main() -> i32 {

    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());

    if args.flag_version {
        println!("pwx {}", VERSION);
        return 0;
    }

    // The password safe is one of
    // 1. Command line [<file>]
    // 2. PWX_DATABASE env var
    // 3. ~/.pwsafe/psafe.psafe3
    let env_db = std::env::var("PWX_DATABASE").unwrap_or(String::new());

    let path = if !args.arg_file.is_empty() {
        PathBuf::from(&args.arg_file)
    } else if !env_db.is_empty() {
        PathBuf::from(&env_db)
    } else {
        std::env::home_dir().expect("Cannot find your HOME path")
            .join(".pwsafe").join("pwsafe.psafe3")
    };

    // FIXME: PathBuf::exists is still Unstable, but we need to verify
    // if the file exists before prompting for password
    match std::fs::File::open(&path) {
        Err(err) => {
            let _ = writeln!(stderr(), "{}: {}", path.to_string_lossy(), err);
            return -1;
        },
        _ => (),
    }

    let mut p = match Pwx::open(&path, get_password(&args).as_bytes()) {
        Err(f) => {
            let _ = writeln!(stderr(), "Error: {} {}", f, path.to_string_lossy());
            exit(-1);
        },
        Ok(p) => p,
    };

    if !p.is_authentic() {
        return -1;
    }

    if args.cmd_list {
        let mut fields = PwxIterator::from_start(&mut p).unwrap();
        fields.skip_record();

        let mut uuid = String::new();
        let mut title = String::new();
        let mut username = String::new();

        let mut filter = ListFilter::new(&args);
        for (typ,val) in fields {
            match typ {
                0x01 => uuid = Uuid::from_bytes(val.as_ref()).unwrap_or(Uuid::nil()).to_hyphenated_string(),
                0x03 => {
                    title = String::from_utf8_lossy(val.as_ref()).into_owned();
                },
                0x04 => {
                    username = String::from_utf8_lossy(val.as_ref()).into_owned();
                },
                0xff => {
                    if filter.matched() {
                        println!("{} {}[{}]", uuid, title, username);
                        uuid = String::new();
                        title = String::new();
                        username = String::new();
                    }
                }
                _ => (),
            }
            filter.process(typ, &val);
        }
    } else if args.cmd_info {

        let fields = PwxIterator::from_start(&mut p).unwrap();
        for (typ,val) in fields {
            match typ {
                0x01 => print!("{} ", Uuid::from_bytes(val.as_ref()).unwrap_or(Uuid::nil()).to_hyphenated_string()),
                0x07 => print!("{} ", String::from_utf8_lossy(val.as_ref())),
                0x08 => println!("@{} ", String::from_utf8_lossy(val.as_ref())),
                0x09 => println!("{} ", String::from_utf8_lossy(val.as_ref())),
                0x0a => println!("\"{}\" ", String::from_utf8_lossy(val.as_ref())),
                0xff => break,
                _ => (),
            }
        }
    }

    return 0
}

// Rust set_exit_status cannot be used yet, the only way to
// set an exit code to exit using exit(code) so we wrap the
// main function like this
fn main() {
    let exit_code = real_main();
    exit(exit_code);
}

<|endoftext|>