<commit_before>use std::cell::Cell;

struct Coordinates {
    x: Cell<i32>,
    y: Cell<i32>,
}

fn main() {
    let origin = Coordinates { x: Cell::new(0), y: Cell::new(0) };

    move_up(&origin.y); // borrow the value
    move_up(&origin.y); // so that it can change

    println!("X is {:?} and Y is {:?}", origin.x, origin.y);
}

fn move_up(y_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = y_cord.get(); // get the value and store it
    y_cord.set(new_val + 1); // modify the stored value and set the incoming param
    y_cord // no semicolon here because it is the return value
}

fn move_down(y_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = y_cord.get(); // get the value and store it
    y_cord.set(new_val - 1); // modify the stored value and set the incoming param
    y_cord // no semicolon here because it is the return value
}

fn move_right(x_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = x_cord.get(); // get the value and store it
    x_cord.set(new_val + 1); // modify the stored value and set the incoming param
    x_cord // no semicolon here because it is the return value
}

#[test]
fn it_can_move() {
    let origin = Coordinates { x: Cell::new(0), y: Cell::new(0) };

    assert_eq!(0, origin.y.get());

    move_up(&origin.y);

    assert_eq!(1, origin.y.get());

    move_up(&origin.y);
    move_up(&origin.y);
    move_up(&origin.y);

    assert_eq!(4, origin.y.get());

    for _ in 0..19 { // _ instead of x so that rust does not complain about x not being used
        move_up(&origin.y); // moves up 19 times
    }

    assert_eq!(23, origin.y.get());

    for _ in 0..19 {
        move_down(&origin.y);
    }

    assert_eq!(4, origin.y.get());

    for _ in 0..19 {
        move_right(&origin.x);
    }

    assert_eq!(19, origin.x.get());
}
<commit_msg>can move left - fully tested<commit_after>use std::cell::Cell;

struct Coordinates {
    x: Cell<i32>,
    y: Cell<i32>,
}

fn main() {
    let origin = Coordinates { x: Cell::new(0), y: Cell::new(0) };

    move_up(&origin.y); // borrow the value
    move_up(&origin.y); // so that it can change

    println!("X is {:?} and Y is {:?}", origin.x, origin.y);
}

fn move_up(y_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = y_cord.get(); // get the value and store it
    y_cord.set(new_val + 1); // modify the stored value and set the incoming param
    y_cord // no semicolon here because it is the return value
}

fn move_down(y_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = y_cord.get(); // get the value and store it
    y_cord.set(new_val - 1); // modify the stored value and set the incoming param
    y_cord // no semicolon here because it is the return value
}

fn move_right(x_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = x_cord.get(); // get the value and store it
    x_cord.set(new_val + 1); // modify the stored value and set the incoming param
    x_cord // no semicolon here because it is the return value
}

fn move_left(x_cord: &Cell<i32>) -> &Cell<i32> {
    let new_val = x_cord.get(); // get the value and store it
    x_cord.set(new_val - 1); // modify the stored value and set the incoming param
    x_cord // no semicolon here because it is the return value
}

#[test]
fn it_can_move() {
    let origin = Coordinates { x: Cell::new(0), y: Cell::new(0) };

    assert_eq!(0, origin.y.get());

    move_up(&origin.y);

    assert_eq!(1, origin.y.get());

    move_up(&origin.y);
    move_up(&origin.y);
    move_up(&origin.y);

    assert_eq!(4, origin.y.get());

    for _ in 0..19 { // _ instead of x so that rust does not complain about x not being used
        move_up(&origin.y); // moves up 19 times
    }

    assert_eq!(23, origin.y.get());

    for _ in 0..19 {
        move_down(&origin.y);
    }

    assert_eq!(4, origin.y.get());

    for _ in 0..19 {
        move_right(&origin.x);
    }

    assert_eq!(19, origin.x.get());

    for _ in 0..10 {
        move_left(&origin.x);
    }

    assert_eq!(9, origin.x.get());
}
<|endoftext|>


<commit_before>extern crate iron;
extern crate rand;
extern crate urlencoded;

use iron::headers;
use iron::prelude::*;
use iron::method::Method;
use iron::modifiers::{Redirect, Header};
use iron::{Handler, Url, status};

use rand::Rng;

use std::collections::HashMap;
use std::sync::RwLock;

use urlencoded::UrlEncodedBody;

struct UrlShortenerHandler {
    shortened_urls: RwLock<HashMap<String, Url>>,
}

impl UrlShortenerHandler {
    fn new() -> Self {
        UrlShortenerHandler { shortened_urls: RwLock::new(HashMap::new()) }
    }
}

impl Handler for UrlShortenerHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        match &req.method {
            &Method::Post => {
                let long_url = Url::parse(req.get_ref::<UrlEncodedBody>()
                        .unwrap()
                        .get("url")
                        .unwrap()
                        .get(0)
                        .unwrap())
                    .unwrap();

                let random_key: String = rand::thread_rng().gen_ascii_chars().take(5).collect();
                let short_url = format!("http://localhost:3000/{}", random_key);

                self.shortened_urls.write().unwrap().insert(random_key, long_url);

                Ok(Response::with((status::Created, Header(headers::Location(short_url)))))
            }
            &Method::Get | &Method::Head => {
                let request_path = &req.url.path().join("/");
                match self.shortened_urls.read().unwrap().get(request_path) {
                    Some(url) => Ok(Response::with((status::Found, Redirect(url.clone())))),
                    None => Ok(Response::with(status::NotFound)),
                }
            }
            _ => Ok(Response::with(status::NotFound)),
        }
    }
}

fn main() {
    println!("Starting URL Shortener on port 3000...");

    let handler = UrlShortenerHandler::new();
    match Iron::new(handler).http("localhost:3000") {
        Ok(_) => println!("Server started"),
        Err(e) => {
            println!("Error: {}", e.to_string());
            std::process::exit(-1);
        }
    }
}
<commit_msg>Return textual error on incorrect client request<commit_after>extern crate iron;
extern crate rand;
extern crate urlencoded;

use iron::headers;
use iron::prelude::*;
use iron::method::Method;
use iron::modifiers::{Redirect, Header};
use iron::{Handler, Url, status};

use rand::Rng;

use std::collections::HashMap;
use std::sync::RwLock;

use urlencoded::UrlEncodedBody;

struct UrlShortenerHandler {
    shortened_urls: RwLock<HashMap<String, Url>>,
}

impl UrlShortenerHandler {
    fn new() -> Self {
        UrlShortenerHandler { shortened_urls: RwLock::new(HashMap::new()) }
    }
}

impl Handler for UrlShortenerHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        match &req.method {
            &Method::Post => {
                let long_url = req.get_ref::<UrlEncodedBody>()
                    .map_err(|_| "URL encoded body missing")
                    .and_then(|data| match data.get("url") {
                        Some(urls) => Ok(urls.first().unwrap()),
                        None => Err("Parameter 'url' missing from the URL encoded body"),
                    })
                    .and_then(|url| Url::parse(url).map_err(|_| "Parameter 'url' is invalid"));

                match long_url {
                    Ok(long_url) => {
                        let random_key: String =
                            rand::thread_rng().gen_ascii_chars().take(5).collect();
                        let short_url = format!("http://localhost:3000/{}", random_key);

                        self.shortened_urls.write().unwrap().insert(random_key, long_url);

                        Ok(Response::with((status::Created, Header(headers::Location(short_url)))))
                    }
                    Err(e) => Ok(Response::with((status::BadRequest, e))),
                }
            }
            &Method::Get | &Method::Head => {
                let request_path = &req.url.path().join("/");
                match self.shortened_urls.read().unwrap().get(request_path) {
                    Some(url) => Ok(Response::with((status::Found, Redirect(url.clone())))),
                    None => Ok(Response::with(status::NotFound)),
                }
            }
            _ => Ok(Response::with(status::NotFound)),
        }
    }
}

fn main() {
    println!("Starting URL Shortener on port 3000...");

    let handler = UrlShortenerHandler::new();
    match Iron::new(handler).http("localhost:3000") {
        Ok(_) => println!("Server started"),
        Err(e) => {
            println!("Error: {}", e.to_string());
            std::process::exit(-1);
        }
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

const COMMITTER_ERROR_MESSAGE: &'static str = r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    if github::can_release(&config) {
        logger::stdout("Creating GitHub release");
        github::release(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
    } else {
        logger::stdout("Project not hosted on GitHub. Skipping release step");
    }
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }
}

fn get_repository_path(args: &Args) -> String {
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is
                                                            not a directory"));
    let repo_path = path.to_str().unwrap_or_else(|| print_exit!("Path is not valid unicode"));
    repo_path.to_string()
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");
    println!("semantic.rs 🚀");

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

    let mut config_builder = ConfigBuilder::new();

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch.clone());
    config_builder.repository_path(get_repository_path(&args));
}
<commit_msg>refactor: get committer's signature<commit_after>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

const COMMITTER_ERROR_MESSAGE: &'static str = r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    if github::can_release(&config) {
        logger::stdout("Creating GitHub release");
        github::release(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
    } else {
        logger::stdout("Project not hosted on GitHub. Skipping release step");
    }
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }
}

fn get_repository_path(args: &Args) -> String {
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is
                                                            not a directory"));
    let repo_path = path.to_str().unwrap_or_else(|| print_exit!("Path is not valid unicode"));
    repo_path.to_string()
}

fn get_signature<'a>(repository_path: String) -> git2::Signature<'a> {
    let repo = match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    };
    let signature = match git::get_signature(&repo) {
        Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(COMMITTER_ERROR_MESSAGE);
                process::exit(1);
            }
    };

    signature.to_owned()
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");
    println!("semantic.rs 🚀");

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

    let mut config_builder = ConfigBuilder::new();

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch.clone());
    config_builder.repository_path(get_repository_path(&args));
    config_builder.signature(get_signature(get_repository_path(&args)));
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate bytes;
extern crate futures;
extern crate tokio_core;
extern crate net2;

use std::net::SocketAddr;
use net2::UdpBuilder;

use tokio_core::reactor::Core;
use tokio_core::net::{UdpSocket, UdpCodec};

use bytes::{Bytes, IntoBuf, Buf};
use futures::{Future, Stream, Sink};
use futures::sync::mpsc::channel;

use clap::{App, Arg};

struct DgramCodec {
    addr: SocketAddr,
}

impl DgramCodec {
    pub fn new(addr: SocketAddr) -> Self {
        DgramCodec { addr: addr }
    }
}

impl UdpCodec for DgramCodec {
    type In = Bytes;
    type Out = Bytes;

    fn decode(&mut self,
              _src: &SocketAddr,
              buf: &[u8])
              -> ::std::result::Result<Self::In, ::std::io::Error> {
        Ok(buf.into())
    }

    fn encode(&mut self, msg: Self::Out, buf: &mut Vec<u8>) -> SocketAddr {
        use std::io::Read;
        msg.into_buf().reader().read_to_end(buf).unwrap();
        self.addr
    }
}

fn main() {
    let matches = App::new("MEGA UDP DUPLICATOR")
        .version("0.0.0.0.0.0.1alpha")
        .author("Sergey Noskov <snoskov@avito.ru>")
        .about("UDP multiplexor: copies packets from listened address to specified backends")
        .arg(Arg::with_name("listen")
             .short("l")
             .long("listen")
             .value_name("IP:PORT")
             .help("ip and port to listen to")
             .takes_value(true))
        .arg(Arg::with_name("queue_size")
             .short("q")
             .long("queue")
             .takes_value(true)
             .default_value("1048576")
            )
        .arg(Arg::with_name("backend")
             .short("b")
             .long("backend")
             .value_name("IP:PORT")
             .help("IP and port of a backend to forward data to. Can be specified multiple times.")
             .multiple(true)
             .takes_value(true)
            )
        .get_matches();

    let listen = value_t!(matches.value_of("listen"), SocketAddr).unwrap_or_else(|e| e.exit());
    let backends = values_t!(matches.values_of("backend"), SocketAddr).unwrap_or_else(|e| e.exit());
    let size = value_t!(matches.value_of("queue_size"), usize).unwrap_or_else(|e| e.exit());

    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let socket = UdpBuilder::new_v4().unwrap();
    let socket = socket.bind(&listen).unwrap();

    let sender = UdpSocket::from_socket(socket, &handle).unwrap();
    let server = sender.framed(DgramCodec::new(listen));

    let mut chans = Vec::new();

    for b in backends.into_iter() {
        let (tx, rx) = channel::<Bytes>(size);
        chans.push(tx);

        ::std::thread::spawn(move || {
            let mut core = Core::new().unwrap();
            let handle = core.handle();
            let socket = UdpBuilder::new_v4().unwrap();
            let bind: SocketAddr = "0.0.0.0:0".parse().unwrap();
            let socket = socket.bind(&bind).unwrap();

            let sender = UdpSocket::from_socket(socket, &handle).unwrap();
            let sender = sender
                .framed(DgramCodec::new(b))
                .sink_map_err(|e| println!("SEND ERROR: {:?}", e));
            let sender = rx.forward(sender).map(|_| ());
            core.run(sender)
        });
    }

    let server = server.for_each(move |buf| {
        chans
            .iter_mut()
            .map(|tx| {
                     tx.start_send(buf.clone()).unwrap();
                     tx.poll_complete().unwrap();
                 })
            .last();
        Ok(())
    });

    core.run(server).unwrap();
}
<commit_msg>Rewritten to socket cloning<commit_after>#[macro_use]
extern crate clap;
extern crate bytes;
extern crate futures;
extern crate tokio_core;
extern crate net2;

use std::net::SocketAddr;
use net2::UdpBuilder;
use std::thread;
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};

use tokio_core::reactor::{Core, Interval};
use tokio_core::net::{UdpSocket, UdpCodec};

use bytes::{Bytes, IntoBuf, Buf};
use futures::{Future, Stream, Sink};
use futures::sync::mpsc::channel;

use clap::{App, Arg};

pub static CHUNK_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;

struct DgramCodec {
    addr: SocketAddr,
}

impl DgramCodec {
    pub fn new(addr: SocketAddr) -> Self {
        DgramCodec { addr: addr }
    }
}

impl UdpCodec for DgramCodec {
    type In = Bytes;
    type Out = Bytes;

    fn decode(&mut self,
              _src: &SocketAddr,
              buf: &[u8])
              -> ::std::result::Result<Self::In, ::std::io::Error> {
        Ok(buf.into())
    }

    fn encode(&mut self, msg: Self::Out, buf: &mut Vec<u8>) -> SocketAddr {
        use std::io::Read;
        msg.into_buf().reader().read_to_end(buf).unwrap();
        self.addr
    }
}

fn main() {
    let matches = App::new("MEGA UDP DUPLICATOR")
        .version("0.0.0.0.0.0.1alpha")
        .author("Sergey Noskov <snoskov@avito.ru>")
        .about("UDP multiplexor: copies packets from listened address to specified backends")
        .arg(Arg::with_name("listen")
             .short("l")
             .long("listen")
             .value_name("IP:PORT")
             .help("ip and port to listen to")
             .takes_value(true))
        .arg(Arg::with_name("queue_size")
             .short("q")
             .long("queue")
             .takes_value(true)
             .default_value("1048576")
            )
        .arg(Arg::with_name("backend")
             .short("b")
             .long("backend")
             .value_name("IP:PORT")
             .help("IP and port of a backend to forward data to. Can be specified multiple times.")
             .multiple(true)
             .takes_value(true)
            )
         .arg(Arg::with_name("nthreads")
             .short("n")
             .long("nthreads")
             .takes_value(true)
             .default_value("0")
            )
        .get_matches();

    let listen = value_t!(matches.value_of("listen"), SocketAddr).unwrap_or_else(|e| e.exit());
    let backends = values_t!(matches.values_of("backend"), SocketAddr).unwrap_or_else(|e| e.exit());
    let size = value_t!(matches.value_of("queue_size"), usize).unwrap_or_else(|e| e.exit());
    let nthreads = value_t!(matches.value_of("nthreads"), usize).unwrap_or_else(|e| e.exit());

    // Init chunk counter
    // In the main thread start a reporting timer
    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let timer = Interval::new(::std::time::Duration::from_secs(1), &handle).unwrap();
    let timer = timer.for_each(|()|{
                println!("chunks: {:?}", CHUNK_COUNTER.load(Ordering::Relaxed));
                CHUNK_COUNTER.store(0, Ordering::Relaxed);
                //println!("total drops: {:?}", METRIC_DROPS.load(Ordering::Relaxed));
                Ok(())
    });

    let socket = UdpBuilder::new_v4().unwrap();
    let socket = socket.bind(&listen).unwrap();

    // Create sockets from backend addresses
    let senders = backends
        .iter()
        .map(|_| {
                 let socket = UdpBuilder::new_v4().unwrap();
                 let bind: SocketAddr = "0.0.0.0:0".parse().unwrap();
                 socket.bind(&bind).unwrap()
             })
        .collect::<Vec<_>>();

    for _ in 1..8 {
        // create clone of each sender socket for thread
        let senders = senders
            .iter()
            .map(|socket| socket.try_clone().unwrap())
            .collect::<Vec<_>>();

        let socket = socket.try_clone().unwrap();
        let mut backends = backends.clone();
        thread::spawn(move || {
            // each thread runs it's own core
            let mut core = Core::new().unwrap();
            let handle = core.handle();

            // make senders into framed UDP
            let mut senders = senders
                .into_iter()
                .map(|socket| {
                         let sender = UdpSocket::from_socket(socket, &handle).unwrap();
                         // socket order becomes reversed but we don't care about this
                         // because the order is same everywhere
                         let sender = sender.framed(DgramCodec::new(backends.pop().unwrap()));
                         sender
                     })
                .collect::<Vec<_>>();

            // create server now
            let socket = UdpSocket::from_socket(socket, &handle).unwrap();
            let server = socket.framed(DgramCodec::new(listen));

            let server = server.for_each(move |buf| {
                CHUNK_COUNTER.fetch_add(1, Ordering::Relaxed);
                senders
                    .iter_mut()
                    .map(|sender| {
                             sender.start_send(buf.clone()).unwrap();
                             sender.poll_complete().unwrap();
                         })
                    .last();
                Ok(())
            });

            core.run(server).unwrap();
        });
    }
    core.run(timer).unwrap();
}
<|endoftext|>


<commit_before>#[macro_use] extern crate log;
extern crate env_logger;

use std::io::prelude::*;
use std::io;
use std::fs::File;
use std::collections::HashMap;
use std::string::String;
use std::vec::Vec;
use std::path::Path;
use std::net::{TcpListener, TcpStream};
use std::thread;
use std::fs;
use std::sync::{Arc, Mutex};
use std::env;
use std::env::Args;

/// The different run modes for REM
enum Mode {
    NONE,
    CLIENT,
    SERVER
}

/// Simple error structure to be used when errors occur during a cache operation
struct CacheError {
    reason: String
}

impl CacheError{
    pub fn with_reason( reason: String ) -> CacheError {
        return CacheError{
            reason: reason.clone()
        }
    }
}

/// A structure to store a series of cache operations and a value
/// Cache operations are represented by a single character
struct CacheOperation {
    commands: Vec<char>,
    value: String
}

impl CacheOperation {
    /// Creates a new CacheOperation instance from an input string
    /// Stores the commands and corresponding value
    /// Write Example: ```W$abc:def```
    /// The resulting command would be W with a value of abc:def
    fn new_from_string(cache_op_str: &String) -> CacheOperation {
        let mut cache_op = CacheOperation {
            commands: Vec::new(),
            value: String::new()
        };
        let mut idx = 0;
        let chars_iter = cache_op_str.chars();
        // Read the string until $ is found
        for c in chars_iter {
            idx += 1;
            if c == '$' {
                // Set the value to everything after $
                cache_op.value = String::from(&cache_op_str[idx..]);
                break;
            }
            // Add a new command for each iteration where $ hasn't been found yet
            cache_op.commands.push(c);
        }
        return cache_op;
    }
}

/// Cache object -- Simple wrapper around a map
struct Cache {
    map_internal: HashMap<String, Vec<u8>>
}

impl Cache {
    fn new() -> Cache {
        return Cache {
            map_internal: HashMap::new()
        }
    }

    /// Writes the provided value to the cache using the provided key
    ///
    /// The value will be written to the in memory store and the file store
    ///
    /// The _cache directory will be created if it does not exist
    ///
    /// If a value for the provided key already exists it will be overwritten
    fn cache_item(&mut self, key: String, val: Vec<u8>) -> Result<(), CacheError>{
        debug!("Writing to cache: key={:?} val={:?}", key, val);
        fs::create_dir("_cache");
        let mut f: File = File::create(format!("_cache/{}", key)).unwrap();
        f.write(&val.as_slice());
        f.flush();
        self.map_internal.insert(key, val);
        return Ok(());
    }

    /// Reads a value from the cache
    ///
    /// If the key is found in the in memory map then the corresponding value is returned
    ///
    /// If the key cannot be found in the map then an attempt will be made to load the value
    /// from the file corresponding with the key
    fn read_item(&self, key: String) -> Option<Box<Vec<u8>>> {
        let mut res: Option<Box<Vec<u8>>> = None;
        if self.map_internal.contains_key(&key) {
            let res_opt = self.map_internal.get(&key);
            match res_opt {
                Some(vec_ref) => res = Some(Box::new(vec_ref.clone())),
                None => res = None
            }
        } else {
            let mut buf: Vec<u8> = Vec::new();
            let f_res = match File::open(format!("_cache/{}", key)) {
                Err(why) => res = None,
                Ok(mut file) => {
                    file.read_to_end(&mut buf);
                    res = Some(Box::new(buf));
                }
            };
        }
        return res;
    }

    /// Delete's an item from the cache
    ///
    /// If the key is found in the in memory map then that entry will be removed
    ///
    /// The file corresponding to the key will also be deleted
    fn delete_item(&mut self, key: String) -> Result<(), CacheError>{
        if self.map_internal.contains_key(&key) {
            self.map_internal.remove(&key);
        }
        let path = format!("_cache/{}", key);
        if Path::new(&path).exists() {
            match fs::remove_file(path) {
                Ok(x) => x,
                Err(e) => return Err(CacheError::with_reason(format!("Failed to delete file for key: {}", key)))
            }
        }
        return Ok(())
    }
}

/// Parses a TCP input stream and extracts the data
/// Allocates a 64 byte buffer which is used to read the input info from the stream
/// The expected format is ```{size}|{content}```
/// Ex. ```5|W$a:b```
fn string_from_stream( stream: &mut TcpStream ) -> String {
    //Read in the first 54 bytes of the stram
    stream.set_nodelay(true);
    let mut buf_arr: [u8; 64] = [0; 64];
    stream.read(&mut buf_arr);
    // Parse the message size
    let mut size_str = String::new();
    let mut buf_size: usize = 0;
    for i in 0..64 {
        buf_size += 1;
        if buf_arr[i] == '|' as u8 {
            break;
        }
        size_str.push(buf_arr[i as usize] as char);
    }

    // Convert the size string to a usize so it can be used to drain the buffer
    let upper_idx: usize = size_str.parse::<i32>().unwrap() as usize;
    let mut buf_temp: Vec<u8> = buf_arr.to_vec();
    // Create a new buffer using the parsed indicies
    let mut buf: Vec<u8> = buf_temp.drain(buf_size..upper_idx + buf_size).collect();

    stream.flush().unwrap();

    // Return the value as a string
    let buf_str: String = String::from_utf8(buf).unwrap();
    return buf_str;
}

fn handle_client(mut stream: &mut TcpStream, mut cache: &Arc<Mutex<Cache>>) -> Result<(), CacheError>{
    let buf_str: String = string_from_stream( stream );
    if buf_str.len() > 0 {
        let cache_op: CacheOperation = CacheOperation::new_from_string(&buf_str);

        debug!("cmds = {:?}, value = {}", cache_op.commands, cache_op.value);

        if cache_op.commands.len() > 0 {
            let prim_cmd: char = cache_op.commands[0];
            return match prim_cmd {
                'W' => write_stream_str_to_cache(cache_op.value, cache),
                'R' => read_value_from_cache(cache_op.value, cache, &mut stream),
                'D' => delete_value_from_cache(cache_op.value, cache),
                _ => Err(CacheError::with_reason(format!("Invalid cache command {:?}", prim_cmd)))
            }
        }
    } else {
        panic!("Empty cache operation");
    }
    return Ok(());
}

fn read_value_from_cache(key: String, cache_mtx: &Arc<Mutex<Cache>>, mut stream: &mut TcpStream) -> Result<(), CacheError> {
    let cache = cache_mtx.lock().unwrap();
    let cache_opt: Option<Box<Vec<u8>>> = cache.read_item(key);
    match cache_opt {
        Some(boxed_val) => {
            let mut val:Vec<u8> = *boxed_val;
            debug!("Writing to stream {:?}", val);
            write_str_to_stream_with_size( &mut stream, String::from_utf8( val ).unwrap());
            return Ok(());
        },
        None => {
            write_str_to_stream_with_size( &mut stream, String::from("Invalid Key"));
            return Err(CacheError::with_reason(String::from("Could not read from cache")));
        }
    }
}

fn write_stream_str_to_cache(stream_str: String, cache_mtx: &Arc<Mutex<Cache>>) -> Result<(), CacheError>{
    let mut key: String = String::new();
    let mut val: String = String::new();
    let mut idx = 0;
    let mut chars_iter = stream_str.chars();
    for c in chars_iter {
        idx += 1;
        if c == ':' {
            val = String::from(&stream_str[idx..]);
            break;
        }
        key.push(c);
    }
    let bytes = val.into_bytes();
    let mut cache = cache_mtx.lock().unwrap();
    return cache.cache_item(key, bytes);
}

fn delete_value_from_cache(key: String, cache_mtx: &Arc<Mutex<Cache>>) -> Result<(), CacheError>{
    let mut cache = cache_mtx.lock().unwrap();
    return cache.delete_item(key);
}

fn write_str_to_stream_with_size( stream:&mut TcpStream, value:String ){
    let sized_val = String::from( format!("{}|{}", value.len(), value));
    stream.write( String::from(sized_val).as_bytes());
    stream.flush();
}

fn launch_client( ip:String, port:String ){
    info!("Connection to {}:{}", ip, port);

    match TcpStream::connect(format!("{}:{}", ip, port).as_str()){
        Ok( mut stream ) => {
            loop {
                 let stdin = io::stdin();
                 for line_res in stdin.lock().lines() {
                    let mut line:String = line_res.unwrap();
                    if line.starts_with("write "){
                        let key_val = String::from(&line["write ".len()..line.len()]);
                        let delim_idx = key_val.find(" ").unwrap();
                        let key = &key_val[0..delim_idx];
                        let val = &key_val[delim_idx + 1..key_val.len()];
                        let sized_val = String::from( format!("W${}:{}", key, val));
                        write_str_to_stream_with_size( &mut stream, sized_val );
                    }else if line.starts_with("read "){
                        let key = String::from(&line["read ".len()..line.len()]);
                        let cmd_val = String::from( format!("R${}", key));
                        write_str_to_stream_with_size( &mut stream, cmd_val );
                        let val:String = string_from_stream( &mut stream );
                        println!("{}", val);
                        io::stdout().flush();
                    }else if line.starts_with("delete "){
                        let key = String::from(&line["delete ".len()..line.len()]);
                        let cmd_val = String::from( format!("D${}", key));
                        write_str_to_stream_with_size( &mut stream, cmd_val )
                    }
                }
            }
        },
        Err(e) => {
            panic!("Failed to connect to server");
        }
    }
}

fn launch_server( ip:String, port:String ){

    info!("Starting on {}:{}", ip, port);

    let listener: TcpListener = TcpListener::bind( format!( "{}:{}", ip, port).as_str() ).unwrap();

    let cache = Arc::new(Mutex::new(Cache::new()));

    loop{
        // accept connections and process them, spawning a new thread for each one
        for stream in listener.incoming() {
            let cache: Arc<Mutex<Cache>> = cache.clone();
            match stream {
                Ok(mut stream) => {
                    thread::spawn(move || {
                        info!("Incoming connection: {:?}", stream.peer_addr());
                        io::stdout().flush().unwrap();
                        loop {
                            handle_client(&mut stream, &cache);
                        }
                    });
                }
                Err(e) => { error! {"Incoming connection failed"} }
            }
        }
    }
}

fn main() {
    env_logger::init().unwrap();

    let mut args: Args = env::args();

    // Set default values for arguments
    let mut ip:String = String::from("127.0.0.1");
    let mut port:String = String::from("8080");

    let mut mode:Mode = Mode::NONE;

    loop{
        match args.next().as_ref() {
            Some(opt)=>{
                match opt.as_ref(){
                    "server" => {
                        mode = Mode::SERVER;
                    },
                    "client" => {
                        mode = Mode::CLIENT;
                    }
                    "-port" => {
                        match args.next(){
                            Some(x) => port = x,
                            None => break
                        }
                    },
                    "-ip" =>{
                        match args.next(){
                            Some(x) => ip = x,
                            None => break
                        }
                    }
                    _ => continue
                }
            }
            None => break
        }
    }

    match mode {
        Mode::CLIENT => launch_client(ip, port),
        Mode::SERVER => launch_server(ip, port),
        Mode::NONE   => panic!("Mode must be specified")
    }
}

<commit_msg>Bit of refactoring and cleanup<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;

use std::io::prelude::*;
use std::io;
use std::fs::File;
use std::collections::HashMap;
use std::string::String;
use std::vec::Vec;
use std::path::Path;
use std::net::{TcpListener, TcpStream};
use std::thread;
use std::fs;
use std::sync::{Arc, Mutex};
use std::env;
use std::env::Args;

/// The different run modes for REM
enum Mode {
    NONE,
    CLIENT,
    SERVER,
}

/// Simple error structure to be used when errors occur during a cache operation
#[derive(Debug)]
struct CacheError {
    reason: String,
}

impl CacheError {
    pub fn with_reason(reason: String) -> CacheError {
        return CacheError { reason: reason };
    }
}

/// A structure to store a series of cache operations and a value
/// Cache operations are represented by a single character
struct CacheOperation {
    commands: Vec<char>,
    value: String,
}

impl CacheOperation {
    /// Creates a new CacheOperation instance from an input string
    /// Stores the commands and corresponding value
    /// Write Example: ```W$abc:def```
    /// The resulting command would be W with a value of abc:def
    fn new_from_string(cache_op_str: &String) -> CacheOperation {
        let mut cache_op = CacheOperation {
            commands: Vec::new(),
            value: String::new(),
        };
        let mut idx = 0;
        let chars_iter = cache_op_str.chars();
        // Read the string until $ is found
        for c in chars_iter {
            idx += 1;
            if c == '$' {
                // Set the value to everything after $
                cache_op.value = String::from(&cache_op_str[idx..]);
                break;
            }
            // Add a new command for each iteration where $ hasn't been found yet
            cache_op.commands.push(c);
        }
        return cache_op;
    }
}

/// Cache object -- Simple wrapper around a map
struct Cache {
    map_internal: HashMap<String, Vec<u8>>,
}

impl Cache {
    fn new() -> Cache {
        return Cache { map_internal: HashMap::new() };
    }

    /// Writes the provided value to the cache using the provided key
    ///
    /// The value will be written to the in memory store and the file store
    ///
    /// The _cache directory will be created if it does not exist
    ///
    /// If a value for the provided key already exists it will be overwritten
    fn cache_item(&mut self, key: String, val: Vec<u8>) -> Result<(), CacheError> {
        debug!("Writing to cache: key={:?} val={:?}", key, val);
        fs::create_dir("_cache");
        let mut f: File = File::create(format!("_cache/{}", key)).unwrap();
        f.write(&val.as_slice());
        f.flush();
        self.map_internal.insert(key, val);
        return Ok(());
    }

    /// Reads a value from the cache
    ///
    /// If the key is found in the in memory map then the corresponding value is returned
    ///
    /// If the key cannot be found in the map then an attempt will be made to load the value
    /// from the file corresponding with the key
    fn read_item(&self, key: String) -> Option<Box<Vec<u8>>> {
        let mut res: Option<Box<Vec<u8>>>;
        if self.map_internal.contains_key(&key) {
            let res_opt = self.map_internal.get(&key);
            match res_opt {
                Some(vec_ref) => res = Some(Box::new(vec_ref.clone())),
                None => res = None,
            }
        } else {
            let mut buf: Vec<u8> = Vec::new();
            let f_res = match File::open(format!("_cache/{}", key)) {
                Err(why) => res = None,
                Ok(mut file) => {
                    file.read_to_end(&mut buf);
                    res = Some(Box::new(buf));
                }
            };
        }
        return res;
    }

    /// Delete's an item from the cache
    ///
    /// If the key is found in the in memory map then that entry will be removed
    ///
    /// The file corresponding to the key will also be deleted
    fn delete_item(&mut self, key: String) -> Result<(), CacheError> {
        if self.map_internal.contains_key(&key) {
            self.map_internal.remove(&key);
        }
        let path = format!("_cache/{}", key);
        if Path::new(&path).exists() {
            match fs::remove_file(path) {
                Ok(x) => x,
                Err(e) => {
                    return Err(CacheError::with_reason(format!("Failed to delete file for key: \
                                                                {}",
                                                               key)))
                }
            }
        }
        return Ok(());
    }
}

/// Parses a TCP input stream and extracts the data
/// Allocates a 64 byte buffer which is used to read the input info from the stream
/// The expected format is ```{size}|{content}```
/// Ex. ```5|W$a:b```
fn string_from_stream(stream: &mut TcpStream) -> String {
    //Read in the first 54 bytes of the stram
    stream.set_nodelay(true);
    let mut buf_arr: [u8; 64] = [0; 64];
    stream.read(&mut buf_arr);
    // Parse the message size
    let mut size_str = String::new();
    let mut buf_size: usize = 0;
    for i in 0..64 {
        buf_size += 1;
        if buf_arr[i] == '|' as u8 {
            break;
        }
        size_str.push(buf_arr[i as usize] as char);
    }

    // Convert the size string to a usize so it can be used to drain the buffer
    let upper_idx: usize = size_str.parse::<i32>().unwrap() as usize;
    let mut buf_temp: Vec<u8> = buf_arr.to_vec();
    // Create a new buffer using the parsed indicies
    let buf: Vec<u8> = buf_temp.drain(buf_size..upper_idx + buf_size).collect();

    stream.flush().unwrap();

    // Return the value as a string
    let buf_str: String = String::from_utf8(buf).unwrap();
    return buf_str;
}

fn handle_client(mut stream: &mut TcpStream,
                 cache: &Arc<Mutex<Cache>>)
                 -> Result<(), CacheError> {
    let buf_str: String = string_from_stream(stream);
    if buf_str.len() > 0 {
        let cache_op: CacheOperation = CacheOperation::new_from_string(&buf_str);

        debug!("cmds = {:?}, value = {}", cache_op.commands, cache_op.value);

        if cache_op.commands.len() > 0 {
            let prim_cmd: char = cache_op.commands[0];
            return match prim_cmd {
                'W' => write_stream_str_to_cache(cache_op.value, cache),
                'R' => read_value_from_cache(cache_op.value, cache, &mut stream),
                'D' => delete_value_from_cache(cache_op.value, cache),
                _ => Err(CacheError::with_reason(format!("Invalid cache command {:?}", prim_cmd))),
            };
        }
    } else {
        panic!("Empty cache operation");
    }
    return Ok(());
}

fn read_value_from_cache(key: String,
                         cache_mtx: &Arc<Mutex<Cache>>,
                         mut stream: &mut TcpStream)
                         -> Result<(), CacheError> {
    let cache = cache_mtx.lock().unwrap();
    let cache_opt: Option<Box<Vec<u8>>> = cache.read_item(key);
    match cache_opt {
        Some(boxed_val) => {
            let val: Vec<u8> = *boxed_val;
            debug!("Writing to stream {:?}", val);
            write_str_to_stream_with_size(&mut stream, String::from_utf8(val).unwrap());
            return Ok(());
        }
        None => {
            write_str_to_stream_with_size(&mut stream, String::from("Invalid Key"));
            return Err(CacheError::with_reason(String::from("Could not read from cache")));
        }
    }
}

fn write_stream_str_to_cache(stream_str: String,
                             cache_mtx: &Arc<Mutex<Cache>>)
                             -> Result<(), CacheError> {
    let mut key: String = String::new();
    let mut val: String = String::new();
    let mut idx = 0;
    let mut chars_iter = stream_str.chars();
    for c in chars_iter {
        idx += 1;
        if c == ':' {
            val = String::from(&stream_str[idx..]);
            break;
        }
        key.push(c);
    }
    let bytes = val.into_bytes();
    let mut cache = cache_mtx.lock().unwrap();
    return cache.cache_item(key, bytes);
}

fn delete_value_from_cache(key: String, cache_mtx: &Arc<Mutex<Cache>>) -> Result<(), CacheError> {
    let mut cache = cache_mtx.lock().unwrap();
    return cache.delete_item(key);
}

fn write_str_to_stream_with_size(stream: &mut TcpStream, value: String) {
    let sized_val = String::from(format!("{}|{}", value.len(), value));
    stream.write(String::from(sized_val).as_bytes());
    stream.flush();
}

fn launch_client(ip: String, port: String) {
    info!("Connection to {}:{}", ip, port);

    match TcpStream::connect(format!("{}:{}", ip, port).as_str()) {
        Ok(mut stream) => {
            loop {
                // Contine looping, executing any commands from the user
                let stdin = io::stdin();
                for line_res in stdin.lock().lines() {
                    let line: String = line_res.unwrap();
                    if line.starts_with("write ") {
                        client_exec_write(line, &mut stream);
                    } else if line.starts_with("read ") {
                        client_exec_read(line, &mut stream);
                    } else if line.starts_with("delete ") {
                        client_exec_delete(line, &mut stream);
                    }
                }
            }
        }
        Err(e) => {
            panic!("Failed to connect to server. Error '{}'", e);
        }
    }
}

/// Executres a write operation by parsing the client command and converting it to REM format
/// ex: write abc:def would be converted to 9|W$abc:def and sent to the REM server
fn client_exec_write(line: String, mut stream: &mut TcpStream) {
    let key_val = String::from(&line["write ".len()..line.len()]);
    let delim_idx = key_val.find(" ").unwrap();
    let key = &key_val[0..delim_idx];
    let val = &key_val[delim_idx + 1..key_val.len()];
    let sized_val = String::from(format!("W${}:{}", key, val));
    write_str_to_stream_with_size(&mut stream, sized_val);
}

/// Executres a read operation by parsing the client command and converting it to REM format
/// ex: read abc:def would be converted to 5|R$abc and sent to the REM launch_server
/// The respone from the REM server is writen to stdout
/// If stdout::flush fail a warning will be logged
fn client_exec_read(line: String, mut stream: &mut TcpStream) {
    let key = String::from(&line["read ".len()..line.len()]);
    let cmd_val = String::from(format!("R${}", key));
    write_str_to_stream_with_size(&mut stream, cmd_val);
    let val: String = string_from_stream(&mut stream);
    println!("{}", val);
    let flush_res = io::stdout().flush();
    if flush_res.is_err() {
        warn!("Failed to flush standart out. Error '{:?}'",
              flush_res.err());
    }
}

/// Executres a delete operation by parsing the client command and converting it to REM format
/// ex: delete abc would be converted to 5|D$abc and sent to the REM server
fn client_exec_delete(line: String, mut stream: &mut TcpStream) {
    let key = String::from(&line["delete ".len()..line.len()]);
    let cmd_val = String::from(format!("D${}", key));
    write_str_to_stream_with_size(&mut stream, cmd_val)
}

fn launch_server(ip: String, port: String) {

    info!("Starting on {}:{}", ip, port);

    let listener: TcpListener = TcpListener::bind(format!("{}:{}", ip, port).as_str()).unwrap();

    let cache = Arc::new(Mutex::new(Cache::new()));

    loop {
        // accept connections and process them, spawning a new thread for each one
        for stream in listener.incoming() {
            let cache: Arc<Mutex<Cache>> = cache.clone();
            match stream {
                Ok(mut stream) => {
                    thread::spawn(move || {
                        info!("Incoming connection: {:?}", stream.peer_addr());
                        io::stdout().flush().unwrap();
                        loop {
                            let client_res = handle_client(&mut stream, &cache);
                            if client_res.is_err() {
                                error!("An error occured while handling a client connection {:?}",
                                       client_res.err());
                            }
                        }
                    });
                }
                Err(e) => error!("Incoming connection failed. Error {}", e),
            }
        }
    }
}

fn main() {
    env_logger::init().unwrap();

    let mut args: Args = env::args();

    // Set default values for arguments
    let mut ip: String = String::from("127.0.0.1");
    let mut port: String = String::from("8080");

    let mut mode: Mode = Mode::NONE;

    loop {
        match args.next().as_ref() {
            Some(opt) => {
                match opt.as_ref() {
                    "server" => {
                        mode = Mode::SERVER;
                    }
                    "client" => {
                        mode = Mode::CLIENT;
                    }
                    "-port" => {
                        match args.next() {
                            Some(x) => port = x,
                            None => break,
                        }
                    }
                    "-ip" => {
                        match args.next() {
                            Some(x) => ip = x,
                            None => break,
                        }
                    }
                    _ => continue,
                }
            }
            None => break,
        }
    }

    match mode {
        Mode::CLIENT => launch_client(ip, port),
        Mode::SERVER => launch_server(ip, port),
        Mode::NONE => panic!("Mode must be specified"),
    }
}
<|endoftext|>


<commit_before>#![feature(phase)]
#[phase(link, plugin)]
extern crate eccles;
use eccles::{World, EntityRefMut, Processor};

fn main() {
    let mut world = world!{
        components: [Position, Velocity]
    };
    let entities = world.build_entities(10, entity![
        Position {
            x: 3.0,
            y: 15.0
        },
        Velocity {
            x: -21.0,
            y: 0.5
        }
    ]);
    println!("{}", world.entities().collect::<Vec<_>>());
    world.update(1.0);
    println!("{}", world.get_entity(entities[0]).get::<Position>().x);
}

pub struct Movement;
impl Processor for Movement {
    fn run(&mut self, mut entity:EntityRefMut, delta: f64) {
        let vel = entity.get::<Velocity>().unwrap();
        let pos = entity.borrow_mut::<Position>().unwrap();
        pos.x += vel.x * delta;
        pos.y += vel.y * delta;
    }
}

comp!(Position = {
    x: f64 = 0.0,
    y: f64 = 0.0
});
comp!(Velocity = {
    x: f64 = 0.0,
    y: f64 = 0.0
});<commit_msg>Implement copy<commit_after>#![feature(phase)]
#[phase(link, plugin)]
extern crate eccles;
use eccles::{World, EntityRefMut, Processor};

fn main() {
    let mut world = world!{
        components: [Position, Velocity]
    };
    let entities = world.build_entities(10, entity![
        Position {
            x: 3.0,
            y: 15.0
        },
        Velocity {
            x: -21.0,
            y: 0.5
        }
    ]);
    println!("{}", world.entities().collect::<Vec<_>>());
    world.update(1.0);
    println!("{}", world.get_entity(entities[0]).get::<Position>().x);
}

#[deriving(Copy)]
pub struct Movement;
impl Processor for Movement {
    fn run(&mut self, mut entity:EntityRefMut, delta: f64) {
        let vel = entity.get::<Velocity>().unwrap();
        let pos = entity.borrow_mut::<Position>().unwrap();
        pos.x += vel.x * delta;
        pos.y += vel.y * delta;
    }
}

comp!(Position = {
    x: f64 = 0.0,
    y: f64 = 0.0
});
comp!(Velocity = {
    x: f64 = 0.0,
    y: f64 = 0.0
});<|endoftext|>


<commit_before>#![deny(rust_2018_idioms, unused, unused_crate_dependencies, unused_import_braces, unused_qualifications, warnings)]

use {
    std::{
        borrow::Cow,
        collections::{
            BTreeMap,
            HashMap,
        },
        convert::Infallible,
        env,
        ffi::OsString,
        fmt,
        io,
        time::Duration,
    },
    bitbar::{
        Command,
        ContentItem,
        Image,
        Menu,
        MenuItem,
    },
    chrono::prelude::*,
    css_color_parser::ColorParseError,
    derive_more::From,
    image::ImageError,
    itertools::Itertools as _,
    mime::Mime,
    notify_rust::Notification,
    serde::Deserialize,
    url::Url,
    crate::{
        files::{
            Cache,
            Config,
            Data,
            LauncherData,
            VersionLink,
        },
        model::*,
        util::ResultNeverExt as _,
    },
};

mod files;
mod model;
mod util;

const MAIN_WORLD: &str = "wurstmineberg";

#[derive(Debug, From)]
enum Error {
    ColorParse(ColorParseError),
    #[from(ignore)]
    CommandLength(usize),
    EmptyTimespec,
    Header(reqwest::header::ToStrError),
    InvalidMime(Mime),
    Image(ImageError),
    Io(io::Error),
    Json(serde_json::Error),
    MimeFromStr(mime::FromStrError),
    MissingCliArg,
    MissingHomeDir,
    OsString(OsString),
    Reqwest(reqwest::Error),
    Timespec(timespec::Error),
    UnknownLauncherProfile(String),
    UnknownWorldName(String, String),
    Url(url::ParseError),
    Xdg(xdg_basedir::Error),
}

impl From<Infallible> for Error {
    fn from(never: Infallible) -> Error {
        match never {}
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::ColorParse(e) => e.fmt(f),
            Error::CommandLength(num_params) => write!(f, "BitBar command should have 1–6 parameters including the command name, but this one has {}", num_params),
            Error::EmptyTimespec => write!(f, "given timespec matches no dates"),
            Error::Header(e) => e.fmt(f),
            Error::InvalidMime(mime) => write!(f, "{} is not a known image MIME type", mime),
            Error::Image(e) => write!(f, "image error: {}", e),
            Error::Io(e) => e.fmt(f),
            Error::Json(e) => e.fmt(f),
            Error::MimeFromStr(e) => e.fmt(f),
            Error::MissingCliArg => write!(f, "missing command-line argument(s)"),
            Error::MissingHomeDir => write!(f, "could not find your user folder"),
            Error::OsString(_) => write!(f, "command argument was not valid UTF-8"),
            Error::Reqwest(e) => if let Some(url) = e.url() {
                write!(f, "reqwest error at {}: {}", url, e)
            } else {
                write!(f, "reqwest error: {}", e)
            },
            Error::Timespec(e) => e.fmt(f),
            Error::UnknownLauncherProfile(profile_id) => write!(f, "no profile named “{}” in launcher data", profile_id),
            Error::UnknownWorldName(profile_id, world_name) => write!(f, "unknown world name “{}” in versionMatch config for profile {}", world_name, profile_id),
            Error::Url(e) => e.fmt(f),
            Error::Xdg(e) => e.fmt(f),
        }
    }
}

impl From<Error> for Menu {
    fn from(e: Error) -> Menu {
        let zoom = Config::load().map(|config| config.zoom).unwrap_or(1);
        let mut error_menu = vec![
            ContentItem::new("?").template_image(wurstpick(zoom)).never_unwrap().into(),
            MenuItem::Sep
        ];
        match e {
            Error::Reqwest(e) => {
                error_menu.push(MenuItem::new(format!("reqwest error: {}", e)));
                if let Some(url) = e.url() {
                    error_menu.push(ContentItem::new(format!("URL: {}", url))
                        .href(url.clone()).expect("failed to add link to error menu")
                        .color("blue").expect("failed to parse the color blue")
                        .into());
                }
            }
            e => {
                error_menu.push(MenuItem::new(&e));
                error_menu.push(MenuItem::new(format!("{:?}", e)));
            }
        }
        error_menu.push(ContentItem::new("Report a Bug")
            .href("https://github.com/wurstmineberg/bitbar-server-status/issues/new").expect("failed to add link to error menu")
            .color("blue").expect("failed to parse the color blue")
            .into());
        Menu(error_menu)
    }
}

#[derive(Debug, Deserialize)]
struct Status {
    #[serde(default)]
    list: Vec<Uid>,
    running: bool,
    version: String,
}

impl Status {
    async fn load(client: &reqwest::Client) -> Result<BTreeMap<String, Status>, Error> {
        Ok(
            client.get("https://wurstmineberg.de/api/v3/server/worlds.json")
                .query(&[("list", "1")])
                .send().await?
                .error_for_status()?
                .json().await?
        )
    }
}

#[derive(Debug, Deserialize)]
struct People {
    people: HashMap<Uid, Person>,
}

impl People {
    async fn load(client: &reqwest::Client) -> Result<People, Error> {
        Ok(
            client.get("https://wurstmineberg.de/api/v3/people.json")
                .send().await?
                .error_for_status()?
                .json().await?
        )
    }

    fn get(&self, uid: impl Into<Uid>) -> Option<&Person> {
        self.people.get(&uid.into())
    }
}

#[derive(Debug, Deserialize)]
struct AvatarInfo {
    url: Url,
    #[serde(default)]
    fallbacks: Vec<AvatarInfo>,
}

fn wurstpick(zoom: u8) -> Image {
    if zoom >= 2 {
        Image::template(&include_bytes!("../assets/wurstpick-2x.png")[..]).never_unwrap()
    } else {
        Image::template(&include_bytes!("../assets/wurstpick.png")[..]).never_unwrap()
    }
}

fn notify(summary: impl fmt::Display, body: impl fmt::Display) {
    //let _ = notify_rust::set_application(&notify_rust::get_bundle_identifier_or_default("BitBar")); //TODO uncomment when https://github.com/h4llow3En/mac-notification-sys/issues/8 is fixed
    let _ = Notification::default()
        .summary(&summary.to_string())
        .sound_name("Funk")
        .body(&body.to_string())
        .show();
}

trait ResultExt {
    type Ok;

    fn notify(self, summary: impl fmt::Display) -> Self::Ok;
}

impl<T, E: fmt::Debug> ResultExt for Result<T, E> {
    type Ok = T;

    fn notify(self, summary: impl fmt::Display) -> T {
        match self {
            Ok(t) => t,
            Err(e) => {
                notify(&summary, format!("{:?}", e));
                panic!("{}: {:?}", summary, e);
            }
        }
    }
}

#[bitbar::command]
fn defer(args: impl Iterator<Item = OsString>) -> Result<(), Error> {
    let args = args.map(|arg| arg.into_string()).collect::<Result<Vec<_>, _>>()?;
    if args.is_empty() { return Err(Error::MissingCliArg) }
    let mut data = Data::load()?;
    data.deferred = Some(timespec::next(args)?.ok_or(Error::EmptyTimespec)?);
    data.save()?;
    Ok(())
}

#[bitbar::main(error_template_image = "../assets/wurstpick-2x.png")] //TODO use wurstpick.png for low-DPI screens?
async fn main() -> Result<Menu, Error> {
    let current_exe = env::current_exe()?;
    let client = reqwest::Client::builder()
        .user_agent(concat!("bitbar-wurstmineberg-status/", env!("CARGO_PKG_VERSION")))
        .timeout(Duration::from_secs(30))
        .use_rustls_tls()
        .build()?;
    let data = Data::load()?;
    if data.deferred.map_or(false, |deferred| deferred >= Utc::now()) {
        return Ok(Menu::default())
    }
    let config = Config::load()?;
    let statuses = Status::load(&client).await?;
    if !config.version_match.is_empty() {
        let mut launcher_data = LauncherData::load()?;
        let mut modified = false;
        for (profile_id, world_name) in config.version_match {
            let launcher_profile = launcher_data.profiles.get_mut(&profile_id).ok_or_else(|| Error::UnknownLauncherProfile(profile_id.clone()))?;
            let world_version = &statuses.get(&world_name).ok_or_else(|| Error::UnknownWorldName(profile_id, world_name))?.version;
            if launcher_profile.last_version_id != *world_version {
                launcher_profile.last_version_id = world_version.clone();
                modified = true;
            }
        }
        if modified { launcher_data.save()? }
    }
    if statuses.values().all(|status| status.list.is_empty())
    && !if statuses[MAIN_WORLD].running { config.show_if_empty } else { config.show_if_offline } {
        return Ok(Menu::default())
    }
    let people = People::load(&client).await?;
    let mut cache = Cache::load()?;
    let mut menu = vec![{
        let total = statuses.values().map(|status| status.list.len()).sum::<usize>();
        let head = ContentItem::new(if total > 0 {
            Cow::Owned(total.to_string())
        } else if !statuses[MAIN_WORLD].running {
            Cow::Borrowed("!")
        } else {
            Cow::Borrowed("")
        }).template_image(wurstpick(config.zoom))?;
        if let Some(fav_color) = (config.single_color && total == 1).then(|| ())
            .and_then(|()| people.get(statuses.values().flat_map(|status| &status.list).exactly_one().expect("total == 1 but not exactly 1 player online")))
            .and_then(|person| person.fav_color)
        { head.color(fav_color)? } else { head }.into()
    }];
    for (world_name, status) in statuses {
        if (world_name == MAIN_WORLD && !status.running) || !status.list.is_empty() {
            menu.push(MenuItem::Sep);
            menu.push(MenuItem::new(world_name));
            menu.push(if status.running {
                let version_item = ContentItem::new(format!("Version: {}", status.version));
                match config.version_link {
                    VersionLink::Enabled => version_item.href(format!("https://minecraft.fandom.com/wiki/Java_Edition_{}", status.version))?,
                    VersionLink::Alternate => version_item.alt(ContentItem::new(format!("Version: {}", status.version)).color("blue")?.href(format!("https://minecraft.fandom.com/wiki/Java_Edition_{}", status.version))?),
                    VersionLink::Disabled => version_item,
                }.into()
            } else {
                MenuItem::new("Offline") //TODO add link to Discord channel?
            });
            for uid in status.list {
                let person = people.get(&uid).cloned().unwrap_or_default();
                let mut item = ContentItem::new(person.name.map_or_else(|| uid.to_string(), |name| name.to_string()))
                    .href(format!("https://wurstmineberg.de/people/{}", uid))?
                    .image(cache.get_img(&client, uid.clone(), config.zoom).await?)?;
                if let Some(fav_color) = person.fav_color {
                    item = item.color(fav_color)?;
                }
                if let Some(discord) = person.discord {
                    item = item.alt(
                        ContentItem::new(format!("@{}", discord.name()))
                            .color("blue")?
                            .href(discord.url())?
                            .image(cache.get_img(&client, uid.clone(), config.zoom).await?)?
                    );
                }
                menu.push(item.into());
            }
        }
    }
    menu.push(MenuItem::Sep);
    menu.push(ContentItem::new("Start Minecraft")
        .command(("/usr/bin/open", "-a", "Minecraft"))
        .into());
    if !config.defer_specs.is_empty() {
        menu.push(MenuItem::Sep);
        for spec in config.defer_specs {
            menu.push(ContentItem::new(format!("Defer Until {}", spec.join(" ")))
                .command(
                    Command::try_from(
                        vec![format!("{}", current_exe.display()), format!("defer")]
                            .into_iter()
                            .chain(spec)
                            .collect::<Vec<_>>()
                    ).map_err(|v| Error::CommandLength(v.len()))?
                )
                .refresh()
                .into());
        }
    }
    cache.save()?;
    Ok(Menu(menu))
}
<commit_msg>Deduplicate error menu header<commit_after>#![deny(rust_2018_idioms, unused, unused_crate_dependencies, unused_import_braces, unused_qualifications, warnings)]

use {
    std::{
        borrow::Cow,
        collections::{
            BTreeMap,
            HashMap,
        },
        convert::Infallible,
        env,
        ffi::OsString,
        fmt,
        io,
        time::Duration,
    },
    bitbar::{
        Command,
        ContentItem,
        Image,
        Menu,
        MenuItem,
    },
    chrono::prelude::*,
    css_color_parser::ColorParseError,
    derive_more::From,
    image::ImageError,
    itertools::Itertools as _,
    mime::Mime,
    notify_rust::Notification,
    serde::Deserialize,
    url::Url,
    crate::{
        files::{
            Cache,
            Config,
            Data,
            LauncherData,
            VersionLink,
        },
        model::*,
        util::ResultNeverExt as _,
    },
};

mod files;
mod model;
mod util;

const MAIN_WORLD: &str = "wurstmineberg";

#[derive(Debug, From)]
enum Error {
    ColorParse(ColorParseError),
    #[from(ignore)]
    CommandLength(usize),
    EmptyTimespec,
    Header(reqwest::header::ToStrError),
    InvalidMime(Mime),
    Image(ImageError),
    Io(io::Error),
    Json(serde_json::Error),
    MimeFromStr(mime::FromStrError),
    MissingCliArg,
    MissingHomeDir,
    OsString(OsString),
    Reqwest(reqwest::Error),
    Timespec(timespec::Error),
    UnknownLauncherProfile(String),
    UnknownWorldName(String, String),
    Url(url::ParseError),
    Xdg(xdg_basedir::Error),
}

impl From<Infallible> for Error {
    fn from(never: Infallible) -> Error {
        match never {}
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::ColorParse(e) => e.fmt(f),
            Error::CommandLength(num_params) => write!(f, "BitBar command should have 1–6 parameters including the command name, but this one has {}", num_params),
            Error::EmptyTimespec => write!(f, "given timespec matches no dates"),
            Error::Header(e) => e.fmt(f),
            Error::InvalidMime(mime) => write!(f, "{} is not a known image MIME type", mime),
            Error::Image(e) => write!(f, "image error: {}", e),
            Error::Io(e) => e.fmt(f),
            Error::Json(e) => e.fmt(f),
            Error::MimeFromStr(e) => e.fmt(f),
            Error::MissingCliArg => write!(f, "missing command-line argument(s)"),
            Error::MissingHomeDir => write!(f, "could not find your user folder"),
            Error::OsString(_) => write!(f, "command argument was not valid UTF-8"),
            Error::Reqwest(e) => if let Some(url) = e.url() {
                write!(f, "reqwest error at {}: {}", url, e)
            } else {
                write!(f, "reqwest error: {}", e)
            },
            Error::Timespec(e) => e.fmt(f),
            Error::UnknownLauncherProfile(profile_id) => write!(f, "no profile named “{}” in launcher data", profile_id),
            Error::UnknownWorldName(profile_id, world_name) => write!(f, "unknown world name “{}” in versionMatch config for profile {}", world_name, profile_id),
            Error::Url(e) => e.fmt(f),
            Error::Xdg(e) => e.fmt(f),
        }
    }
}

impl From<Error> for Menu {
    fn from(e: Error) -> Menu {
        let mut error_menu = Vec::default();
        match e {
            Error::Reqwest(e) => {
                error_menu.push(MenuItem::new(format!("reqwest error: {}", e)));
                if let Some(url) = e.url() {
                    error_menu.push(ContentItem::new(format!("URL: {}", url))
                        .href(url.clone()).expect("failed to add link to error menu")
                        .color("blue").expect("failed to parse the color blue")
                        .into());
                }
            }
            e => {
                error_menu.push(MenuItem::new(&e));
                error_menu.push(MenuItem::new(format!("{:?}", e)));
            }
        }
        error_menu.push(ContentItem::new("Report a Bug")
            .href("https://github.com/wurstmineberg/bitbar-server-status/issues/new").expect("failed to add link to error menu")
            .color("blue").expect("failed to parse the color blue")
            .into());
        Menu(error_menu)
    }
}

#[derive(Debug, Deserialize)]
struct Status {
    #[serde(default)]
    list: Vec<Uid>,
    running: bool,
    version: String,
}

impl Status {
    async fn load(client: &reqwest::Client) -> Result<BTreeMap<String, Status>, Error> {
        Ok(
            client.get("https://wurstmineberg.de/api/v3/server/worlds.json")
                .query(&[("list", "1")])
                .send().await?
                .error_for_status()?
                .json().await?
        )
    }
}

#[derive(Debug, Deserialize)]
struct People {
    people: HashMap<Uid, Person>,
}

impl People {
    async fn load(client: &reqwest::Client) -> Result<People, Error> {
        Ok(
            client.get("https://wurstmineberg.de/api/v3/people.json")
                .send().await?
                .error_for_status()?
                .json().await?
        )
    }

    fn get(&self, uid: impl Into<Uid>) -> Option<&Person> {
        self.people.get(&uid.into())
    }
}

#[derive(Debug, Deserialize)]
struct AvatarInfo {
    url: Url,
    #[serde(default)]
    fallbacks: Vec<AvatarInfo>,
}

fn wurstpick(zoom: u8) -> Image {
    if zoom >= 2 {
        Image::template(&include_bytes!("../assets/wurstpick-2x.png")[..]).never_unwrap()
    } else {
        Image::template(&include_bytes!("../assets/wurstpick.png")[..]).never_unwrap()
    }
}

fn notify(summary: impl fmt::Display, body: impl fmt::Display) {
    //let _ = notify_rust::set_application(&notify_rust::get_bundle_identifier_or_default("BitBar")); //TODO uncomment when https://github.com/h4llow3En/mac-notification-sys/issues/8 is fixed
    let _ = Notification::default()
        .summary(&summary.to_string())
        .sound_name("Funk")
        .body(&body.to_string())
        .show();
}

trait ResultExt {
    type Ok;

    fn notify(self, summary: impl fmt::Display) -> Self::Ok;
}

impl<T, E: fmt::Debug> ResultExt for Result<T, E> {
    type Ok = T;

    fn notify(self, summary: impl fmt::Display) -> T {
        match self {
            Ok(t) => t,
            Err(e) => {
                notify(&summary, format!("{:?}", e));
                panic!("{}: {:?}", summary, e);
            }
        }
    }
}

#[bitbar::command]
fn defer(args: impl Iterator<Item = OsString>) -> Result<(), Error> {
    let args = args.map(|arg| arg.into_string()).collect::<Result<Vec<_>, _>>()?;
    if args.is_empty() { return Err(Error::MissingCliArg) }
    let mut data = Data::load()?;
    data.deferred = Some(timespec::next(args)?.ok_or(Error::EmptyTimespec)?);
    data.save()?;
    Ok(())
}

#[bitbar::main(error_template_image = "../assets/wurstpick-2x.png")] //TODO use wurstpick.png for low-DPI screens?
async fn main() -> Result<Menu, Error> {
    let current_exe = env::current_exe()?;
    let client = reqwest::Client::builder()
        .user_agent(concat!("bitbar-wurstmineberg-status/", env!("CARGO_PKG_VERSION")))
        .timeout(Duration::from_secs(30))
        .use_rustls_tls()
        .build()?;
    let data = Data::load()?;
    if data.deferred.map_or(false, |deferred| deferred >= Utc::now()) {
        return Ok(Menu::default())
    }
    let config = Config::load()?;
    let statuses = Status::load(&client).await?;
    if !config.version_match.is_empty() {
        let mut launcher_data = LauncherData::load()?;
        let mut modified = false;
        for (profile_id, world_name) in config.version_match {
            let launcher_profile = launcher_data.profiles.get_mut(&profile_id).ok_or_else(|| Error::UnknownLauncherProfile(profile_id.clone()))?;
            let world_version = &statuses.get(&world_name).ok_or_else(|| Error::UnknownWorldName(profile_id, world_name))?.version;
            if launcher_profile.last_version_id != *world_version {
                launcher_profile.last_version_id = world_version.clone();
                modified = true;
            }
        }
        if modified { launcher_data.save()? }
    }
    if statuses.values().all(|status| status.list.is_empty())
    && !if statuses[MAIN_WORLD].running { config.show_if_empty } else { config.show_if_offline } {
        return Ok(Menu::default())
    }
    let people = People::load(&client).await?;
    let mut cache = Cache::load()?;
    let mut menu = vec![{
        let total = statuses.values().map(|status| status.list.len()).sum::<usize>();
        let head = ContentItem::new(if total > 0 {
            Cow::Owned(total.to_string())
        } else if !statuses[MAIN_WORLD].running {
            Cow::Borrowed("!")
        } else {
            Cow::Borrowed("")
        }).template_image(wurstpick(config.zoom))?;
        if let Some(fav_color) = (config.single_color && total == 1).then(|| ())
            .and_then(|()| people.get(statuses.values().flat_map(|status| &status.list).exactly_one().expect("total == 1 but not exactly 1 player online")))
            .and_then(|person| person.fav_color)
        { head.color(fav_color)? } else { head }.into()
    }];
    for (world_name, status) in statuses {
        if (world_name == MAIN_WORLD && !status.running) || !status.list.is_empty() {
            menu.push(MenuItem::Sep);
            menu.push(MenuItem::new(world_name));
            menu.push(if status.running {
                let version_item = ContentItem::new(format!("Version: {}", status.version));
                match config.version_link {
                    VersionLink::Enabled => version_item.href(format!("https://minecraft.fandom.com/wiki/Java_Edition_{}", status.version))?,
                    VersionLink::Alternate => version_item.alt(ContentItem::new(format!("Version: {}", status.version)).color("blue")?.href(format!("https://minecraft.fandom.com/wiki/Java_Edition_{}", status.version))?),
                    VersionLink::Disabled => version_item,
                }.into()
            } else {
                MenuItem::new("Offline") //TODO add link to Discord channel?
            });
            for uid in status.list {
                let person = people.get(&uid).cloned().unwrap_or_default();
                let mut item = ContentItem::new(person.name.map_or_else(|| uid.to_string(), |name| name.to_string()))
                    .href(format!("https://wurstmineberg.de/people/{}", uid))?
                    .image(cache.get_img(&client, uid.clone(), config.zoom).await?)?;
                if let Some(fav_color) = person.fav_color {
                    item = item.color(fav_color)?;
                }
                if let Some(discord) = person.discord {
                    item = item.alt(
                        ContentItem::new(format!("@{}", discord.name()))
                            .color("blue")?
                            .href(discord.url())?
                            .image(cache.get_img(&client, uid.clone(), config.zoom).await?)?
                    );
                }
                menu.push(item.into());
            }
        }
    }
    menu.push(MenuItem::Sep);
    menu.push(ContentItem::new("Start Minecraft")
        .command(("/usr/bin/open", "-a", "Minecraft"))
        .into());
    if !config.defer_specs.is_empty() {
        menu.push(MenuItem::Sep);
        for spec in config.defer_specs {
            menu.push(ContentItem::new(format!("Defer Until {}", spec.join(" ")))
                .command(
                    Command::try_from(
                        vec![format!("{}", current_exe.display()), format!("defer")]
                            .into_iter()
                            .chain(spec)
                            .collect::<Vec<_>>()
                    ).map_err(|v| Error::CommandLength(v.len()))?
                )
                .refresh()
                .into());
        }
    }
    cache.save()?;
    Ok(Menu(menu))
}
<|endoftext|>