<commit_before>//! VM Runtime
use utils::bigint::M256;
use utils::gas::Gas;
use utils::address::Address;
use super::commit::{AccountState, BlockhashState};
use super::errors::{RequireError, MachineError, CommitError, EvalError, PCError};
use super::{Stack, Context, BlockHeader, Patch, PC, Memory, AccountCommitment, Log};

use std::str::FromStr;

use self::check::{check_opcode, extra_check_opcode};
use self::run::run_opcode;
use self::cost::{gas_refund, gas_stipend, gas_cost, memory_cost, memory_gas, code_deposit_gas};
use self::utils::copy_into_memory;

mod cost;
mod run;
mod check;
mod utils;

/// A VM state without PC.
pub struct State<M> {
    pub memory: M,
    pub stack: Stack,

    pub context: Context,
    pub block: BlockHeader,
    pub patch: Patch,

    pub out: Vec<u8>,

    pub memory_cost: Gas,
    pub used_gas: Gas,
    pub refunded_gas: Gas,

    pub account_state: AccountState,
    pub blockhash_state: BlockhashState,
    pub logs: Vec<Log>,

    pub depth: usize,
}

impl<M> State<M> {
    pub fn memory_gas(&self) -> Gas {
        memory_gas(self.memory_cost)
    }

    pub fn available_gas(&self) -> Gas {
        self.context.gas_limit - self.memory_gas() - self.used_gas
    }
}

/// A VM state with PC.
pub struct Machine<M> {
    state: State<M>,
    pc: PC,
    status: MachineStatus,
}

#[derive(Debug, Clone)]
/// Represents the current runtime status.
pub enum MachineStatus {
    /// This runtime is actively running or has just been started.
    Running,
    /// This runtime has exited successfully. Calling `step` on this
    /// runtime again would panic.
    ExitedOk,
    /// This runtime has exited with errors. Calling `step` on this
    /// runtime again would panic.
    ExitedErr(MachineError),
    /// This runtime requires execution of a sub runtime, which is a
    /// ContractCreation instruction.
    InvokeCreate(Context),
    /// This runtime requires execution of a sub runtime, which is a
    /// MessageCall instruction.
    InvokeCall(Context, (M256, M256)),
}

#[derive(Debug, Clone)]
/// Used for `check` for additional checks related to the runtime.
pub enum ControlCheck {
    Jump(M256),
}

#[derive(Debug, Clone)]
/// Used for `step` for additional operations related to the runtime.
pub enum Control {
    Stop,
    Jump(M256),
    InvokeCreate(Context),
    InvokeCall(Context, (M256, M256)),
}

impl<M: Memory + Default> Machine<M> {
    /// Create a new runtime.
    pub fn new(context: Context, block: BlockHeader, patch: Patch, depth: usize) -> Self {
        Machine {
            pc: PC::new(context.code.as_slice()),
            status: MachineStatus::Running,
            state: State {
                memory: M::default(),
                stack: Stack::default(),

                context: context,
                block: block,
                patch: patch,

                out: Vec::new(),

                memory_cost: Gas::zero(),
                used_gas: Gas::zero(),
                refunded_gas: Gas::zero(),

                account_state: AccountState::default(),
                blockhash_state: BlockhashState::default(),
                logs: Vec::new(),

                depth: depth,
            },
        }
    }

    /// Derive this runtime to create a sub runtime. This will not
    /// modify the current runtime, and it will have a chance to
    /// review whether it wants to accept the result of this sub
    /// runtime afterwards.
    pub fn derive(&self, context: Context) -> Self {
        Machine {
            pc: PC::new(context.code.as_slice()),
            status: MachineStatus::Running,
            state: State {
                memory: M::default(),
                stack: Stack::default(),

                context: context,
                block: self.state.block.clone(),
                patch: self.state.patch.clone(),

                out: Vec::new(),

                memory_cost: Gas::zero(),
                used_gas: Gas::zero(),
                refunded_gas: Gas::zero(),

                account_state: self.state.account_state.clone(),
                blockhash_state: self.state.blockhash_state.clone(),
                logs: self.state.logs.clone(),

                depth: self.state.depth + 1,
            },
        }
    }

    /// Commit a new account into this runtime.
    pub fn commit_account(&mut self, commitment: AccountCommitment) -> Result<(), CommitError> {
        self.state.account_state.commit(commitment)
    }

    /// Commit a new blockhash into this runtime.
    pub fn commit_blockhash(&mut self, number: M256, hash: M256) -> Result<(), CommitError> {
        self.state.blockhash_state.commit(number, hash)
    }

    #[allow(unused_variables)]
    /// Apply a sub runtime into the current runtime. This sub runtime
    /// should have been created by the current runtime's `derive`
    /// function. Depending whether the current runtime is invoking a
    /// ContractCreation or MessageCall instruction, it will apply
    /// various states back.
    pub fn apply_sub(&mut self, sub: Machine<M>) {
        use std::mem::swap;
        let mut status = MachineStatus::Running;
        swap(&mut status, &mut self.status);
        match status {
            MachineStatus::InvokeCreate(_) => {
                self.apply_create(sub);
            },
            MachineStatus::InvokeCall(_, (out_start, out_len)) => {
                self.apply_call(sub, out_start, out_len);
            },
            _ => panic!(),
        }
    }

    fn apply_create(&mut self, sub: Machine<M>) {
        if self.state.available_gas() < sub.state.used_gas {
            panic!();
        }

        match sub.status() {
            MachineStatus::ExitedOk => {
                self.state.account_state = sub.state.account_state;
                self.state.blockhash_state = sub.state.blockhash_state;
                self.state.logs = sub.state.logs;
                self.state.used_gas = self.state.used_gas + sub.state.used_gas;
                self.state.refunded_gas = self.state.refunded_gas + sub.state.refunded_gas;
                if self.state.available_gas() >= code_deposit_gas(sub.state.out.len()) {
                    self.state.account_state.decrease_balance(self.state.context.address,
                                                              sub.state.context.value);
                    self.state.account_state.create(sub.state.context.address,
                                                    sub.state.context.value,
                                                    sub.state.out.as_slice());
                }

            },
            MachineStatus::ExitedErr(_) => {
                // self.state.used_gas = self.state.used_gas + sub.state.used_gas;
                // self.state.stack.pop().unwrap();
                // self.state.stack.push(M256::zero()).unwrap();
            },
            _ => panic!(),
        }
    }

    fn apply_call(&mut self, sub: Machine<M>, out_start: M256, out_len: M256) {
        if self.state.available_gas() < sub.state.used_gas {
            panic!();
        }

        match sub.status() {
            MachineStatus::ExitedOk => {
                self.state.account_state = sub.state.account_state;
                self.state.blockhash_state = sub.state.blockhash_state;
                self.state.logs = sub.state.logs;
                self.state.used_gas = self.state.used_gas + sub.state.used_gas;
                self.state.refunded_gas = self.state.refunded_gas + sub.state.refunded_gas;
                self.state.account_state.decrease_balance(self.state.context.address,
                                                          sub.state.context.value);
                self.state.account_state.increase_balance(sub.state.context.address,
                                                          sub.state.context.value);
                copy_into_memory(&mut self.state.memory, sub.state.out.as_slice(),
                                 out_start, M256::zero(), out_len);
            },
            MachineStatus::ExitedErr(_) => {
                // self.state.used_gas = self.state.used_gas + sub.state.used_gas;
                self.state.stack.pop().unwrap();
                self.state.stack.push(M256::from(1u64)).unwrap();
            },
            _ => panic!(),
        }
    }

    /// Check the next instruction about whether it will return
    /// errors.
    pub fn check(&self) -> Result<(), EvalError> {
        let instruction = self.pc.peek()?;
        check_opcode(instruction, &self.state).and_then(|v| {
            match v {
                None => Ok(()),
                Some(ControlCheck::Jump(dest)) => {
                    if dest <= M256::from(usize::max_value()) && self.pc.is_valid(dest.into()) {
                        Ok(())
                    } else {
                        Err(EvalError::Machine(MachineError::PC(PCError::BadJumpDest)))
                    }
                }
            }
        })
    }

    #[allow(unused_variables)]
    fn step_precompiled(&mut self) -> bool {
        let ecrec_address = Address::from_str("0x0000000000000000000000000000000000000001").unwrap();
        let sha256_address = Address::from_str("0x0000000000000000000000000000000000000002").unwrap();
        let rip160_address = Address::from_str("0x0000000000000000000000000000000000000003").unwrap();
        let id_address = Address::from_str("0x0000000000000000000000000000000000000004").unwrap();

        match self.state.context.address {
            id_address => {
                let gas = Gas::from(15u64) +
                    Gas::from(3u64) * (Gas::from(self.state.context.data.len()) / Gas::from(32u64));
                if gas > self.state.context.gas_limit {
                    self.state.used_gas = self.state.context.gas_limit;
                    self.status = MachineStatus::ExitedErr(MachineError::EmptyGas);
                } else {
                    self.state.used_gas = gas;
                    self.state.out = self.state.context.data.clone();
                    self.status = MachineStatus::ExitedOk;
                }
                return true;
            },
            ecrec_address => unimplemented!(),
            sha256_address => unimplemented!(),
            rip160_address => unimplemented!(),
            _ => return false,
        }
    }

    /// Step an instruction in the PC. The eval result is refected by
    /// the runtime status, and it will only return an error if
    /// there're accounts or blockhashes to be committed to this
    /// runtime for it to run. In that case, the state of the current
    /// runtime will not be affected.
    pub fn step(&mut self) -> Result<(), RequireError> {
        match &self.status {
            &MachineStatus::Running => (),
            _ => panic!(),
        }

        if self.step_precompiled() {
            return Ok(());
        }

        if self.state.depth >= 2 {
            self.status = MachineStatus::ExitedErr(MachineError::CallstackOverflow);
            return Ok(());
        }

        if self.pc.is_end() {
            self.status = MachineStatus::ExitedOk;
            return Ok(());
        }

        match self.check() {
            Ok(()) => (),
            Err(EvalError::Machine(error)) => {
                self.status = MachineStatus::ExitedErr(error);
                return Ok(());
            },
            Err(EvalError::Require(error)) => {
                return Err(error);
            },
        };

        let instruction = self.pc.peek().unwrap();
        let position = self.pc.position();
        let memory_cost = memory_cost(instruction, &self.state);
        let memory_gas = memory_gas(memory_cost);
        let gas_cost = gas_cost(instruction, &self.state);
        let gas_stipend = gas_stipend(instruction, &self.state);
        let gas_refund = gas_refund(instruction, &self.state);
        let after_gas = self.state.context.gas_limit - memory_gas - self.state.used_gas - gas_cost + gas_stipend;

        match extra_check_opcode(instruction, &self.state, gas_stipend, after_gas) {
            Ok(()) => (),
            Err(EvalError::Machine(error)) => {
                self.status = MachineStatus::ExitedErr(error);
                return Ok(());
            },
            Err(EvalError::Require(error)) => {
                return Err(error);
            },
        }

        if self.state.context.gas_limit < memory_gas + self.state.used_gas + gas_cost - gas_stipend {
            self.status = MachineStatus::ExitedErr(MachineError::EmptyGas);
            return Ok(());
        }

        let instruction = self.pc.read().unwrap();
        let result = run_opcode((instruction, position),
                                &mut self.state, gas_stipend, after_gas);

        self.state.used_gas = self.state.used_gas + gas_cost - gas_stipend;
        self.state.memory_cost = memory_cost;
        self.state.refunded_gas = self.state.refunded_gas + gas_refund;

        match result {
            None => Ok(()),
            Some(Control::Jump(dest)) => {
                self.pc.jump(dest.into()).unwrap();
                Ok(())
            },
            Some(Control::InvokeCall(context, (from, len))) => {
                self.status = MachineStatus::InvokeCall(context, (from, len));
                Ok(())
            },
            Some(Control::InvokeCreate(context)) => {
                self.status = MachineStatus::InvokeCreate(context);
                Ok(())
            },
            Some(Control::Stop) => {
                self.status = MachineStatus::ExitedOk;
                Ok(())
            },
        }
    }

    /// Get the runtime state.
    pub fn state(&self) -> &State<M> {
        &self.state
    }

    /// Get the current runtime status.
    pub fn status(&self) -> MachineStatus {
        self.status.clone()
    }
}
<commit_msg>Problem: incorrect use of match for testing precompiled address<commit_after>//! VM Runtime
use utils::bigint::M256;
use utils::gas::Gas;
use utils::address::Address;
use super::commit::{AccountState, BlockhashState};
use super::errors::{RequireError, MachineError, CommitError, EvalError, PCError};
use super::{Stack, Context, BlockHeader, Patch, PC, Memory, AccountCommitment, Log};

use std::str::FromStr;

use self::check::{check_opcode, extra_check_opcode};
use self::run::run_opcode;
use self::cost::{gas_refund, gas_stipend, gas_cost, memory_cost, memory_gas, code_deposit_gas};
use self::utils::copy_into_memory;

mod cost;
mod run;
mod check;
mod utils;

/// A VM state without PC.
pub struct State<M> {
    pub memory: M,
    pub stack: Stack,

    pub context: Context,
    pub block: BlockHeader,
    pub patch: Patch,

    pub out: Vec<u8>,

    pub memory_cost: Gas,
    pub used_gas: Gas,
    pub refunded_gas: Gas,

    pub account_state: AccountState,
    pub blockhash_state: BlockhashState,
    pub logs: Vec<Log>,

    pub depth: usize,
}

impl<M> State<M> {
    pub fn memory_gas(&self) -> Gas {
        memory_gas(self.memory_cost)
    }

    pub fn available_gas(&self) -> Gas {
        self.context.gas_limit - self.memory_gas() - self.used_gas
    }
}

/// A VM state with PC.
pub struct Machine<M> {
    state: State<M>,
    pc: PC,
    status: MachineStatus,
}

#[derive(Debug, Clone)]
/// Represents the current runtime status.
pub enum MachineStatus {
    /// This runtime is actively running or has just been started.
    Running,
    /// This runtime has exited successfully. Calling `step` on this
    /// runtime again would panic.
    ExitedOk,
    /// This runtime has exited with errors. Calling `step` on this
    /// runtime again would panic.
    ExitedErr(MachineError),
    /// This runtime requires execution of a sub runtime, which is a
    /// ContractCreation instruction.
    InvokeCreate(Context),
    /// This runtime requires execution of a sub runtime, which is a
    /// MessageCall instruction.
    InvokeCall(Context, (M256, M256)),
}

#[derive(Debug, Clone)]
/// Used for `check` for additional checks related to the runtime.
pub enum ControlCheck {
    Jump(M256),
}

#[derive(Debug, Clone)]
/// Used for `step` for additional operations related to the runtime.
pub enum Control {
    Stop,
    Jump(M256),
    InvokeCreate(Context),
    InvokeCall(Context, (M256, M256)),
}

impl<M: Memory + Default> Machine<M> {
    /// Create a new runtime.
    pub fn new(context: Context, block: BlockHeader, patch: Patch, depth: usize) -> Self {
        Machine {
            pc: PC::new(context.code.as_slice()),
            status: MachineStatus::Running,
            state: State {
                memory: M::default(),
                stack: Stack::default(),

                context: context,
                block: block,
                patch: patch,

                out: Vec::new(),

                memory_cost: Gas::zero(),
                used_gas: Gas::zero(),
                refunded_gas: Gas::zero(),

                account_state: AccountState::default(),
                blockhash_state: BlockhashState::default(),
                logs: Vec::new(),

                depth: depth,
            },
        }
    }

    /// Derive this runtime to create a sub runtime. This will not
    /// modify the current runtime, and it will have a chance to
    /// review whether it wants to accept the result of this sub
    /// runtime afterwards.
    pub fn derive(&self, context: Context) -> Self {
        Machine {
            pc: PC::new(context.code.as_slice()),
            status: MachineStatus::Running,
            state: State {
                memory: M::default(),
                stack: Stack::default(),

                context: context,
                block: self.state.block.clone(),
                patch: self.state.patch.clone(),

                out: Vec::new(),

                memory_cost: Gas::zero(),
                used_gas: Gas::zero(),
                refunded_gas: Gas::zero(),

                account_state: self.state.account_state.clone(),
                blockhash_state: self.state.blockhash_state.clone(),
                logs: self.state.logs.clone(),

                depth: self.state.depth + 1,
            },
        }
    }

    /// Commit a new account into this runtime.
    pub fn commit_account(&mut self, commitment: AccountCommitment) -> Result<(), CommitError> {
        self.state.account_state.commit(commitment)
    }

    /// Commit a new blockhash into this runtime.
    pub fn commit_blockhash(&mut self, number: M256, hash: M256) -> Result<(), CommitError> {
        self.state.blockhash_state.commit(number, hash)
    }

    #[allow(unused_variables)]
    /// Apply a sub runtime into the current runtime. This sub runtime
    /// should have been created by the current runtime's `derive`
    /// function. Depending whether the current runtime is invoking a
    /// ContractCreation or MessageCall instruction, it will apply
    /// various states back.
    pub fn apply_sub(&mut self, sub: Machine<M>) {
        use std::mem::swap;
        let mut status = MachineStatus::Running;
        swap(&mut status, &mut self.status);
        match status {
            MachineStatus::InvokeCreate(_) => {
                self.apply_create(sub);
            },
            MachineStatus::InvokeCall(_, (out_start, out_len)) => {
                self.apply_call(sub, out_start, out_len);
            },
            _ => panic!(),
        }
    }

    fn apply_create(&mut self, sub: Machine<M>) {
        if self.state.available_gas() < sub.state.used_gas {
            panic!();
        }

        match sub.status() {
            MachineStatus::ExitedOk => {
                self.state.account_state = sub.state.account_state;
                self.state.blockhash_state = sub.state.blockhash_state;
                self.state.logs = sub.state.logs;
                self.state.used_gas = self.state.used_gas + sub.state.used_gas;
                self.state.refunded_gas = self.state.refunded_gas + sub.state.refunded_gas;
                if self.state.available_gas() >= code_deposit_gas(sub.state.out.len()) {
                    self.state.account_state.decrease_balance(self.state.context.address,
                                                              sub.state.context.value);
                    self.state.account_state.create(sub.state.context.address,
                                                    sub.state.context.value,
                                                    sub.state.out.as_slice());
                }

            },
            MachineStatus::ExitedErr(_) => {
                // self.state.used_gas = self.state.used_gas + sub.state.used_gas;
                // self.state.stack.pop().unwrap();
                // self.state.stack.push(M256::zero()).unwrap();
            },
            _ => panic!(),
        }
    }

    fn apply_call(&mut self, sub: Machine<M>, out_start: M256, out_len: M256) {
        if self.state.available_gas() < sub.state.used_gas {
            panic!();
        }

        match sub.status() {
            MachineStatus::ExitedOk => {
                self.state.account_state = sub.state.account_state;
                self.state.blockhash_state = sub.state.blockhash_state;
                self.state.logs = sub.state.logs;
                self.state.used_gas = self.state.used_gas + sub.state.used_gas;
                self.state.refunded_gas = self.state.refunded_gas + sub.state.refunded_gas;
                self.state.account_state.decrease_balance(self.state.context.address,
                                                          sub.state.context.value);
                self.state.account_state.increase_balance(sub.state.context.address,
                                                          sub.state.context.value);
                copy_into_memory(&mut self.state.memory, sub.state.out.as_slice(),
                                 out_start, M256::zero(), out_len);
            },
            MachineStatus::ExitedErr(_) => {
                // self.state.used_gas = self.state.used_gas + sub.state.used_gas;
                self.state.stack.pop().unwrap();
                self.state.stack.push(M256::from(1u64)).unwrap();
            },
            _ => panic!(),
        }
    }

    /// Check the next instruction about whether it will return
    /// errors.
    pub fn check(&self) -> Result<(), EvalError> {
        let instruction = self.pc.peek()?;
        check_opcode(instruction, &self.state).and_then(|v| {
            match v {
                None => Ok(()),
                Some(ControlCheck::Jump(dest)) => {
                    if dest <= M256::from(usize::max_value()) && self.pc.is_valid(dest.into()) {
                        Ok(())
                    } else {
                        Err(EvalError::Machine(MachineError::PC(PCError::BadJumpDest)))
                    }
                }
            }
        })
    }

    #[allow(unused_variables)]
    fn step_precompiled(&mut self) -> bool {
        let ecrec_address = Address::from_str("0x0000000000000000000000000000000000000001").unwrap();
        let sha256_address = Address::from_str("0x0000000000000000000000000000000000000002").unwrap();
        let rip160_address = Address::from_str("0x0000000000000000000000000000000000000003").unwrap();
        let id_address = Address::from_str("0x0000000000000000000000000000000000000004").unwrap();

        if self.state.context.address == id_address {
            let gas = Gas::from(15u64) +
                Gas::from(3u64) * (Gas::from(self.state.context.data.len()) / Gas::from(32u64));
            if gas > self.state.context.gas_limit {
                self.state.used_gas = self.state.context.gas_limit;
                self.status = MachineStatus::ExitedErr(MachineError::EmptyGas);
            } else {
                self.state.used_gas = gas;
                self.state.out = self.state.context.data.clone();
                self.status = MachineStatus::ExitedOk;
            }
            return true;
        } else if self.state.context.address == rip160_address {
            unimplemented!();
        } else if self.state.context.address == sha256_address {
            unimplemented!();
        } else if self.state.context.address == ecrec_address {
            unimplemented!();
        } else {
            return false;
        }
    }

    /// Step an instruction in the PC. The eval result is refected by
    /// the runtime status, and it will only return an error if
    /// there're accounts or blockhashes to be committed to this
    /// runtime for it to run. In that case, the state of the current
    /// runtime will not be affected.
    pub fn step(&mut self) -> Result<(), RequireError> {
        match &self.status {
            &MachineStatus::Running => (),
            _ => panic!(),
        }

        if self.step_precompiled() {
            return Ok(());
        }

        if self.state.depth >= 2 {
            self.status = MachineStatus::ExitedErr(MachineError::CallstackOverflow);
            return Ok(());
        }

        if self.pc.is_end() {
            self.status = MachineStatus::ExitedOk;
            return Ok(());
        }

        match self.check() {
            Ok(()) => (),
            Err(EvalError::Machine(error)) => {
                self.status = MachineStatus::ExitedErr(error);
                return Ok(());
            },
            Err(EvalError::Require(error)) => {
                return Err(error);
            },
        };

        let instruction = self.pc.peek().unwrap();
        let position = self.pc.position();
        let memory_cost = memory_cost(instruction, &self.state);
        let memory_gas = memory_gas(memory_cost);
        let gas_cost = gas_cost(instruction, &self.state);
        let gas_stipend = gas_stipend(instruction, &self.state);
        let gas_refund = gas_refund(instruction, &self.state);
        let after_gas = self.state.context.gas_limit - memory_gas - self.state.used_gas - gas_cost + gas_stipend;

        match extra_check_opcode(instruction, &self.state, gas_stipend, after_gas) {
            Ok(()) => (),
            Err(EvalError::Machine(error)) => {
                self.status = MachineStatus::ExitedErr(error);
                return Ok(());
            },
            Err(EvalError::Require(error)) => {
                return Err(error);
            },
        }

        if self.state.context.gas_limit < memory_gas + self.state.used_gas + gas_cost - gas_stipend {
            self.status = MachineStatus::ExitedErr(MachineError::EmptyGas);
            return Ok(());
        }

        let instruction = self.pc.read().unwrap();
        let result = run_opcode((instruction, position),
                                &mut self.state, gas_stipend, after_gas);

        self.state.used_gas = self.state.used_gas + gas_cost - gas_stipend;
        self.state.memory_cost = memory_cost;
        self.state.refunded_gas = self.state.refunded_gas + gas_refund;

        match result {
            None => Ok(()),
            Some(Control::Jump(dest)) => {
                self.pc.jump(dest.into()).unwrap();
                Ok(())
            },
            Some(Control::InvokeCall(context, (from, len))) => {
                self.status = MachineStatus::InvokeCall(context, (from, len));
                Ok(())
            },
            Some(Control::InvokeCreate(context)) => {
                self.status = MachineStatus::InvokeCreate(context);
                Ok(())
            },
            Some(Control::Stop) => {
                self.status = MachineStatus::ExitedOk;
                Ok(())
            },
        }
    }

    /// Get the runtime state.
    pub fn state(&self) -> &State<M> {
        &self.state
    }

    /// Get the current runtime status.
    pub fn status(&self) -> MachineStatus {
        self.status.clone()
    }
}
<|endoftext|>


<commit_before>use super::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::{Date, Datelike, Local, NaiveDate, TimeZone};
use config::Config;
use error::BackendError;
use input;
use reqwest::header::{Accept, Authorization, Bearer, ContentType, Headers};
use reqwest::{Client, Response};
use serde_json as json;
use std::io;
use std::process::{Command, ExitStatus};

const LOGIN_URL: &str =
    "https://anilist.co/api/v2/oauth/authorize?client_id=427&response_type=token";

const API_URL: &str = "https://graphql.anilist.co";

macro_rules! send_query {
    ($backend:expr, $query_str:expr, {$($vars:tt)*}, $($response_root:expr)=>*) => {{
        let vars = json!({
            $($vars)*
        });

        let json = $backend.send_json_request($query_str, &vars)?;
        json$([$response_root])*.clone()
    }};
}

pub struct Anilist {
    client: Client,
    user_id: u32,
    access_token: String,
}

impl Anilist {
    fn send_request(
        &self,
        query_str: &str,
        variables: &json::Value,
    ) -> Result<Response, BackendError> {
        let body = json!({
            "query": query_str,
            "variables": variables,
        }).to_string();

        let mut headers = Headers::new();
        headers.set(ContentType::json());
        headers.set(Accept::json());
        headers.set(Authorization(Bearer {
            token: self.access_token.to_owned(),
        }));

        let response = self
            .client
            .post(API_URL)
            .headers(headers)
            .body(body)
            .send()?;

        Ok(response)
    }

    fn send_json_request(
        &self,
        query_str: &str,
        variables: &json::Value,
    ) -> Result<json::Value, BackendError> {
        let text = self.send_request(query_str, variables)?.text()?;
        let json: json::Value = json::from_str(&text)?;

        if json["errors"] != json::Value::Null {
            // TODO: add error chaining
            let err = &json["errors"][0];

            let msg = err["message"].as_str().unwrap_or("unknown error");
            let status_code = err["status"].as_u64().unwrap_or(0) as u32;

            return Err(BackendError::BadResponse(status_code, msg.into()));
        }

        Ok(json)
    }

    fn request_user_id(&self) -> Result<u32, BackendError> {
        let resp = send_query!(self,
            r#"
                query {
                    Viewer {
                        id
                    }
                }
            "#,
            {},
            "data" => "Viewer" => "id"
        );

        let id = json::from_value(resp)?;
        Ok(id)
    }

    fn prompt_for_access_token(open_url: bool) -> Result<String, BackendError> {
        if open_url {
            try_open_url(LOGIN_URL);
        }

        println!(
            "please authorize your account in the opened browser tab and paste the code below:"
        );

        let token = input::read_line()?;
        Ok(token)
    }

    fn login(&mut self, is_first_launch: bool, config: &mut Config) -> Result<(), BackendError> {
        let mut times_token_incorrect = 0;

        loop {
            match self.request_user_id() {
                Ok(user_id) => {
                    self.user_id = user_id;
                    break;
                }
                // As bad as checking for a specific error via its message is, the API does not provide
                // anything else to narrow it down to an invalid token error
                Err(BackendError::BadResponse(400, ref msg))
                    if msg.to_lowercase() == "invalid token" =>
                {
                    times_token_incorrect += 1;
                    println!("\ninvalid access token");

                    let should_open_url = !is_first_launch && times_token_incorrect <= 1;
                    let token = Anilist::prompt_for_access_token(should_open_url)?;

                    self.access_token = token;
                }
                Err(err) => return Err(err),
            }
        }

        if times_token_incorrect > 0 {
            config.user.encode_access_token(&self.access_token);
        }

        Ok(())
    }
}

impl SyncBackend for Anilist {
    fn name() -> &'static str {
        "AniList"
    }

    fn max_score(&self) -> u8 {
        // TODO: add support for other scoring types
        10
    }

    fn init(config: &mut Config) -> Result<Anilist, BackendError> {
        let is_first_launch = config.user.access_token.is_none();

        let access_token = if is_first_launch {
            let token = Anilist::prompt_for_access_token(true)?;
            config.user.encode_access_token(&token);
            token
        } else {
            config.user.decode_access_token()?
        };

        let mut anilist = Anilist {
            client: Client::new(),
            user_id: 0,
            access_token,
        };

        anilist.login(is_first_launch, config)?;

        Ok(anilist)
    }

    fn search_by_name(&self, name: &str) -> Result<Vec<AnimeInfo>, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($name: String) {
                    Page (page: 1, perPage: 30) {
                        media (search: $name, type: ANIME) {
                            id
                            title {
                                romaji
                            }
                            episodes
                        }
                    }
                }
            "#,
            { "name": name },
            "data" => "Page" => "media"
        );

        use self::json::Value;
        let mut series = Vec::new();

        match resp {
            Value::Array(ref entries) => {
                for entry in entries {
                    let series_info: MediaData = json::from_value(entry.clone())?;
                    series.push(series_info.into());
                }
            }
            _ => return Err(BackendError::InvalidJsonResponse),
        }

        Ok(series)
    }

    fn get_series_info_by_id(&self, id: u32) -> Result<AnimeInfo, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($id: Int) {
                    Media (id: $id) {
                        id
                        title {
                            romaji
                        }
                        episodes
                    }
                }
            "#,
            { "id": id },
            "data" => "Media"
        );

        let info: MediaData = json::from_value(resp)?;
        Ok(info.into())
    }

    fn get_list_entry(&self, info: AnimeInfo) -> Result<Option<AnimeEntry>, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($id: Int, $userID: Int) {
                    MediaList(mediaId: $id, userId: $userID) {
                        progress
                        status
                        score
                        startedAt {
                            year
                            month
                            day
                        }
                        completedAt {
                            year
                            month
                            day
                        }
                    }
                }
            "#,
            { "id": info.id, "userID": self.user_id },
            "data" => "MediaList"
        );

        match resp {
            json::Value::Null => Ok(None),
            _ => {
                let media_entry: MediaListEntry = json::from_value(resp)?;
                Ok(Some(media_entry.into_generic_entry(info)))
            }
        }
    }

    fn update_list_entry(&self, entry: &AnimeEntry) -> Result<(), BackendError> {
        send_query!(self,
            r#"
                mutation ($mediaId: Int, $watched_eps: Int, $score: Float, $status: MediaListStatus, $start_date: FuzzyDateInput, $finish_date: FuzzyDateInput) {
                    SaveMediaListEntry (mediaId: $mediaId, progress: $watched_eps, score: $score, status: $status, startedAt: $start_date, completedAt: $finish_date) {
                        mediaId
                    }
                }
            "#,
            {
                "mediaId": entry.info.id,
                "watched_eps": entry.watched_episodes,
                "score": entry.score,
                "status": MediaStatus::from(entry.status.clone()),
                "start_date": MediaDate::from_date(entry.start_date),
                "finish_date": MediaDate::from_date(entry.finish_date),
            },
        );

        Ok(())
    }
}

fn open_url(url: &str) -> io::Result<ExitStatus> {
    #[cfg(target_os = "windows")]
    const LAUNCH_PROGRAM: &str = "start";
    #[cfg(target_os = "macos")]
    const LAUNCH_PROGRAM: &str = "open";
    #[cfg(target_os = "linux")]
    const LAUNCH_PROGRAM: &str = "xdg-open";

    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
    compile_error!("support for opening URL's not implemented for this platform");

    Command::new(LAUNCH_PROGRAM).arg(url).status()
}

fn try_open_url(url: &str) {
    match open_url(url) {
        Ok(status) if status.success() => (),
        result => {
            eprintln!(
                "failed to open URL in default browser. please open it manually: {}",
                url
            );

            if let Err(err) = result {
                eprintln!("error message: {}", err);
            }
        }
    }
}

#[derive(Deserialize)]
struct MediaData {
    id: u32,
    title: Title,
    episodes: Option<u32>,
}

#[derive(Deserialize)]
struct Title {
    romaji: String,
}

impl Into<AnimeInfo> for MediaData {
    fn into(self) -> AnimeInfo {
        AnimeInfo {
            id: self.id,
            title: self.title.romaji,
            episodes: self.episodes,
        }
    }
}

#[derive(Deserialize)]
struct MediaListEntry {
    progress: u32,
    status: MediaStatus,
    score: f32,
    #[serde(rename = "startedAt")]
    start_date: MediaDate,
    #[serde(rename = "completedAt")]
    finish_date: MediaDate,
}

impl MediaListEntry {
    fn into_generic_entry(self, info: AnimeInfo) -> AnimeEntry {
        AnimeEntry {
            info,
            watched_episodes: self.progress,
            score: self.score,
            status: self.status.into(),
            start_date: self.start_date.into_date(),
            finish_date: self.finish_date.into_date(),
        }
    }
}

#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
enum MediaStatus {
    #[serde(rename = "CURRENT")]
    Current,
    #[serde(rename = "COMPLETED")]
    Completed,
    #[serde(rename = "PAUSED")]
    Paused,
    #[serde(rename = "DROPPED")]
    Dropped,
    #[serde(rename = "PLANNING")]
    Planning,
    #[serde(rename = "REPEATING")]
    Repeating,
}

impl Into<Status> for MediaStatus {
    fn into(self) -> Status {
        match self {
            MediaStatus::Current => Status::Watching,
            MediaStatus::Completed => Status::Completed,
            MediaStatus::Paused => Status::OnHold,
            MediaStatus::Dropped => Status::Dropped,
            MediaStatus::Planning => Status::PlanToWatch,
            MediaStatus::Repeating => Status::Rewatching,
        }
    }
}

impl From<Status> for MediaStatus {
    fn from(status: Status) -> MediaStatus {
        match status {
            Status::Watching => MediaStatus::Current,
            Status::Completed => MediaStatus::Completed,
            Status::OnHold => MediaStatus::Paused,
            Status::Dropped => MediaStatus::Dropped,
            Status::PlanToWatch => MediaStatus::Planning,
            Status::Rewatching => MediaStatus::Repeating,
        }
    }
}

#[derive(Serialize, Deserialize)]
struct MediaDate {
    year: Option<i32>,
    month: Option<u32>,
    day: Option<u32>,
}

impl MediaDate {
    fn into_date(self) -> Option<Date<Local>> {
        match (self.year, self.month, self.day) {
            (Some(year), Some(month), Some(day)) => Some(Local.ymd(year, month, day)),
            _ => None,
        }
    }

    fn from_date(date: Option<Date<Local>>) -> MediaDate {
        match date {
            Some(date) => MediaDate {
                year: Some(date.year()),
                month: Some(date.month()),
                day: Some(date.day()),
            },
            None => MediaDate {
                year: None,
                month: None,
                day: None,
            },
        }
    }
}

impl From<NaiveDate> for MediaDate {
    fn from(date: NaiveDate) -> MediaDate {
        MediaDate {
            year: Some(date.year()),
            month: Some(date.month()),
            day: Some(date.day()),
        }
    }
}
<commit_msg>Fix get_list_entry() returning error when the entry isn't on the user's anime list<commit_after>use super::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::{Date, Datelike, Local, NaiveDate, TimeZone};
use config::Config;
use error::BackendError;
use input;
use reqwest::header::{Accept, Authorization, Bearer, ContentType, Headers};
use reqwest::{Client, Response};
use serde_json as json;
use std::io;
use std::process::{Command, ExitStatus};

const LOGIN_URL: &str =
    "https://anilist.co/api/v2/oauth/authorize?client_id=427&response_type=token";

const API_URL: &str = "https://graphql.anilist.co";

macro_rules! send_query {
    ($backend:expr, $query_str:expr, {$($vars:tt)*}, $($response_root:expr)=>*) => {{
        let vars = json!({
            $($vars)*
        });

        $backend.send_json_request($query_str, &vars).map(|json| json$([$response_root])*.clone())
    }};
}

pub struct Anilist {
    client: Client,
    user_id: u32,
    access_token: String,
}

impl Anilist {
    fn send_request(
        &self,
        query_str: &str,
        variables: &json::Value,
    ) -> Result<Response, BackendError> {
        let body = json!({
            "query": query_str,
            "variables": variables,
        }).to_string();

        let mut headers = Headers::new();
        headers.set(ContentType::json());
        headers.set(Accept::json());
        headers.set(Authorization(Bearer {
            token: self.access_token.to_owned(),
        }));

        let response = self
            .client
            .post(API_URL)
            .headers(headers)
            .body(body)
            .send()?;

        Ok(response)
    }

    fn send_json_request(
        &self,
        query_str: &str,
        variables: &json::Value,
    ) -> Result<json::Value, BackendError> {
        let text = self.send_request(query_str, variables)?.text()?;
        let json: json::Value = json::from_str(&text)?;

        if json["errors"] != json::Value::Null {
            // TODO: add error chaining
            let err = &json["errors"][0];

            let msg = err["message"].as_str().unwrap_or("unknown error");
            let status_code = err["status"].as_u64().unwrap_or(0) as u32;

            return Err(BackendError::BadResponse(status_code, msg.into()));
        }

        Ok(json)
    }

    fn request_user_id(&self) -> Result<u32, BackendError> {
        let resp = send_query!(self,
            r#"
                query {
                    Viewer {
                        id
                    }
                }
            "#,
            {},
            "data" => "Viewer" => "id"
        )?;

        let id = json::from_value(resp)?;
        Ok(id)
    }

    fn prompt_for_access_token(open_url: bool) -> Result<String, BackendError> {
        if open_url {
            try_open_url(LOGIN_URL);
        }

        println!(
            "please authorize your account in the opened browser tab and paste the code below:"
        );

        let token = input::read_line()?;
        Ok(token)
    }

    fn login(&mut self, is_first_launch: bool, config: &mut Config) -> Result<(), BackendError> {
        let mut times_token_incorrect = 0;

        loop {
            match self.request_user_id() {
                Ok(user_id) => {
                    self.user_id = user_id;
                    break;
                }
                // As bad as checking for a specific error via its message is, the API does not provide
                // anything else to narrow it down to an invalid token error
                Err(BackendError::BadResponse(400, ref msg))
                    if msg.to_lowercase() == "invalid token" =>
                {
                    times_token_incorrect += 1;
                    println!("\ninvalid access token");

                    let should_open_url = !is_first_launch && times_token_incorrect <= 1;
                    let token = Anilist::prompt_for_access_token(should_open_url)?;

                    self.access_token = token;
                }
                Err(err) => return Err(err),
            }
        }

        if times_token_incorrect > 0 {
            config.user.encode_access_token(&self.access_token);
        }

        Ok(())
    }
}

impl SyncBackend for Anilist {
    fn name() -> &'static str {
        "AniList"
    }

    fn max_score(&self) -> u8 {
        // TODO: add support for other scoring types
        10
    }

    fn init(config: &mut Config) -> Result<Anilist, BackendError> {
        let is_first_launch = config.user.access_token.is_none();

        let access_token = if is_first_launch {
            let token = Anilist::prompt_for_access_token(true)?;
            config.user.encode_access_token(&token);
            token
        } else {
            config.user.decode_access_token()?
        };

        let mut anilist = Anilist {
            client: Client::new(),
            user_id: 0,
            access_token,
        };

        anilist.login(is_first_launch, config)?;

        Ok(anilist)
    }

    fn search_by_name(&self, name: &str) -> Result<Vec<AnimeInfo>, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($name: String) {
                    Page (page: 1, perPage: 30) {
                        media (search: $name, type: ANIME) {
                            id
                            title {
                                romaji
                            }
                            episodes
                        }
                    }
                }
            "#,
            { "name": name },
            "data" => "Page" => "media"
        )?;

        use self::json::Value;
        let mut series = Vec::new();

        match resp {
            Value::Array(ref entries) => {
                for entry in entries {
                    let series_info: MediaData = json::from_value(entry.clone())?;
                    series.push(series_info.into());
                }
            }
            _ => return Err(BackendError::InvalidJsonResponse),
        }

        Ok(series)
    }

    fn get_series_info_by_id(&self, id: u32) -> Result<AnimeInfo, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($id: Int) {
                    Media (id: $id) {
                        id
                        title {
                            romaji
                        }
                        episodes
                    }
                }
            "#,
            { "id": id },
            "data" => "Media"
        )?;

        let info: MediaData = json::from_value(resp)?;
        Ok(info.into())
    }

    fn get_list_entry(&self, info: AnimeInfo) -> Result<Option<AnimeEntry>, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($id: Int, $userID: Int) {
                    MediaList(mediaId: $id, userId: $userID) {
                        progress
                        status
                        score
                        startedAt {
                            year
                            month
                            day
                        }
                        completedAt {
                            year
                            month
                            day
                        }
                    }
                }
            "#,
            { "id": info.id, "userID": self.user_id },
            "data" => "MediaList"
        );

        match resp {
            Ok(entry_json) => {
                let media_entry: MediaListEntry = json::from_value(entry_json)?;
                Ok(Some(media_entry.into_generic_entry(info)))
            }
            Err(BackendError::BadResponse(404, _)) => Ok(None),
            Err(err) => Err(err),
        }
    }

    fn update_list_entry(&self, entry: &AnimeEntry) -> Result<(), BackendError> {
        send_query!(self,
            r#"
                mutation ($mediaId: Int, $watched_eps: Int, $score: Float, $status: MediaListStatus, $start_date: FuzzyDateInput, $finish_date: FuzzyDateInput) {
                    SaveMediaListEntry (mediaId: $mediaId, progress: $watched_eps, score: $score, status: $status, startedAt: $start_date, completedAt: $finish_date) {
                        mediaId
                    }
                }
            "#,
            {
                "mediaId": entry.info.id,
                "watched_eps": entry.watched_episodes,
                "score": entry.score,
                "status": MediaStatus::from(entry.status.clone()),
                "start_date": MediaDate::from_date(entry.start_date),
                "finish_date": MediaDate::from_date(entry.finish_date),
            },
        )?;

        Ok(())
    }
}

fn open_url(url: &str) -> io::Result<ExitStatus> {
    #[cfg(target_os = "windows")]
    const LAUNCH_PROGRAM: &str = "start";
    #[cfg(target_os = "macos")]
    const LAUNCH_PROGRAM: &str = "open";
    #[cfg(target_os = "linux")]
    const LAUNCH_PROGRAM: &str = "xdg-open";

    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
    compile_error!("support for opening URL's not implemented for this platform");

    Command::new(LAUNCH_PROGRAM).arg(url).status()
}

fn try_open_url(url: &str) {
    match open_url(url) {
        Ok(status) if status.success() => (),
        result => {
            eprintln!(
                "failed to open URL in default browser. please open it manually: {}",
                url
            );

            if let Err(err) = result {
                eprintln!("error message: {}", err);
            }
        }
    }
}

#[derive(Deserialize)]
struct MediaData {
    id: u32,
    title: Title,
    episodes: Option<u32>,
}

#[derive(Deserialize)]
struct Title {
    romaji: String,
}

impl Into<AnimeInfo> for MediaData {
    fn into(self) -> AnimeInfo {
        AnimeInfo {
            id: self.id,
            title: self.title.romaji,
            episodes: self.episodes,
        }
    }
}

#[derive(Deserialize)]
struct MediaListEntry {
    progress: u32,
    status: MediaStatus,
    score: f32,
    #[serde(rename = "startedAt")]
    start_date: MediaDate,
    #[serde(rename = "completedAt")]
    finish_date: MediaDate,
}

impl MediaListEntry {
    fn into_generic_entry(self, info: AnimeInfo) -> AnimeEntry {
        AnimeEntry {
            info,
            watched_episodes: self.progress,
            score: self.score,
            status: self.status.into(),
            start_date: self.start_date.into_date(),
            finish_date: self.finish_date.into_date(),
        }
    }
}

#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
enum MediaStatus {
    #[serde(rename = "CURRENT")]
    Current,
    #[serde(rename = "COMPLETED")]
    Completed,
    #[serde(rename = "PAUSED")]
    Paused,
    #[serde(rename = "DROPPED")]
    Dropped,
    #[serde(rename = "PLANNING")]
    Planning,
    #[serde(rename = "REPEATING")]
    Repeating,
}

impl Into<Status> for MediaStatus {
    fn into(self) -> Status {
        match self {
            MediaStatus::Current => Status::Watching,
            MediaStatus::Completed => Status::Completed,
            MediaStatus::Paused => Status::OnHold,
            MediaStatus::Dropped => Status::Dropped,
            MediaStatus::Planning => Status::PlanToWatch,
            MediaStatus::Repeating => Status::Rewatching,
        }
    }
}

impl From<Status> for MediaStatus {
    fn from(status: Status) -> MediaStatus {
        match status {
            Status::Watching => MediaStatus::Current,
            Status::Completed => MediaStatus::Completed,
            Status::OnHold => MediaStatus::Paused,
            Status::Dropped => MediaStatus::Dropped,
            Status::PlanToWatch => MediaStatus::Planning,
            Status::Rewatching => MediaStatus::Repeating,
        }
    }
}

#[derive(Serialize, Deserialize)]
struct MediaDate {
    year: Option<i32>,
    month: Option<u32>,
    day: Option<u32>,
}

impl MediaDate {
    fn into_date(self) -> Option<Date<Local>> {
        match (self.year, self.month, self.day) {
            (Some(year), Some(month), Some(day)) => Some(Local.ymd(year, month, day)),
            _ => None,
        }
    }

    fn from_date(date: Option<Date<Local>>) -> MediaDate {
        match date {
            Some(date) => MediaDate {
                year: Some(date.year()),
                month: Some(date.month()),
                day: Some(date.day()),
            },
            None => MediaDate {
                year: None,
                month: None,
                day: None,
            },
        }
    }
}

impl From<NaiveDate> for MediaDate {
    fn from(date: NaiveDate) -> MediaDate {
        MediaDate {
            year: Some(date.year()),
            month: Some(date.month()),
            day: Some(date.day()),
        }
    }
}
<|endoftext|>


<commit_before>//! Backend using the pure-rust termion library.
//!
//! Requires the `termion-backend` feature.
#![cfg(feature = "termion")]

extern crate termion;

use self::termion::color as tcolor;
use self::termion::event::Event as TEvent;
use self::termion::event::Key as TKey;
use self::termion::event::MouseButton as TMouseButton;
use self::termion::event::MouseEvent as TMouseEvent;
use self::termion::input::{MouseTerminal, TermRead};
use self::termion::raw::{IntoRawMode, RawTerminal};
use self::termion::screen::AlternateScreen;
use self::termion::style as tstyle;
use crossbeam_channel::{self, Receiver, Sender};
use libc;
use signal_hook::iterator::Signals;

use backend;
use event::{Event, Key, MouseButton, MouseEvent};
use theme;
use vec::Vec2;

use std::cell::Cell;
use std::io::{Stdout, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;

pub struct Backend {
    terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
    current_style: Cell<theme::ColorPair>,
}

struct InputParser {
    // Inner state required to parse input
    last_button: Option<MouseButton>,

    event_due: bool,
    requests: Sender<()>,
    input: Receiver<TEvent>,
}

impl InputParser {
    // Creates a non-blocking abstraction over the usual blocking input
    fn new() -> Self {
        let (input_sender, input_receiver) = crossbeam_channel::bounded(0);
        let (request_sender, request_receiver) = crossbeam_channel::bounded(0);

        // This thread will stop after an event when `InputParser` is dropped.
        thread::spawn(move || {
            let stdin = ::std::io::stdin();
            let stdin = stdin.lock();
            let mut events = stdin.events();

            for _ in request_receiver {
                let event: Result<TEvent, ::std::io::Error> =
                    events.next().unwrap();
                input_sender.send(event.unwrap());
            }
        });

        InputParser {
            last_button: None,
            input: input_receiver,
            requests: request_sender,
            event_due: false,
        }
    }

    /// We pledge to want input.
    ///
    /// If we were already expecting input, this is a NO-OP.
    fn request(&mut self) {
        if !self.event_due {
            self.requests.send(());
            self.event_due = true;
        }
    }

    fn peek(&mut self) -> Option<Event> {
        self.request();

        let timeout = ::std::time::Duration::from_millis(10);

        let input = select! {
            recv(self.input, input) => {
                input
            }
            recv(crossbeam_channel::after(timeout)) => return None,
        };

        // We got what we came for.
        self.event_due = false;
        Some(self.map_key(input.unwrap()))
    }

    fn next_event(&mut self) -> Event {
        self.request();

        let input = self.input.recv().unwrap();
        self.event_due = false;
        self.map_key(input)
    }

    fn map_key(&mut self, event: TEvent) -> Event {
        match event {
            TEvent::Unsupported(bytes) => Event::Unknown(bytes),
            TEvent::Key(TKey::Esc) => Event::Key(Key::Esc),
            TEvent::Key(TKey::Backspace) => Event::Key(Key::Backspace),
            TEvent::Key(TKey::Left) => Event::Key(Key::Left),
            TEvent::Key(TKey::Right) => Event::Key(Key::Right),
            TEvent::Key(TKey::Up) => Event::Key(Key::Up),
            TEvent::Key(TKey::Down) => Event::Key(Key::Down),
            TEvent::Key(TKey::Home) => Event::Key(Key::Home),
            TEvent::Key(TKey::End) => Event::Key(Key::End),
            TEvent::Key(TKey::PageUp) => Event::Key(Key::PageUp),
            TEvent::Key(TKey::PageDown) => Event::Key(Key::PageDown),
            TEvent::Key(TKey::Delete) => Event::Key(Key::Del),
            TEvent::Key(TKey::Insert) => Event::Key(Key::Ins),
            TEvent::Key(TKey::F(i)) if i < 12 => Event::Key(Key::from_f(i)),
            TEvent::Key(TKey::F(j)) => Event::Unknown(vec![j]),
            TEvent::Key(TKey::Char('\n')) => Event::Key(Key::Enter),
            TEvent::Key(TKey::Char('\t')) => Event::Key(Key::Tab),
            TEvent::Key(TKey::Char(c)) => Event::Char(c),
            TEvent::Key(TKey::Ctrl('c')) => Event::Exit,
            TEvent::Key(TKey::Ctrl(c)) => Event::CtrlChar(c),
            TEvent::Key(TKey::Alt(c)) => Event::AltChar(c),
            TEvent::Mouse(TMouseEvent::Press(btn, x, y)) => {
                let position = (x - 1, y - 1).into();

                let event = match btn {
                    TMouseButton::Left => MouseEvent::Press(MouseButton::Left),
                    TMouseButton::Middle => {
                        MouseEvent::Press(MouseButton::Middle)
                    }
                    TMouseButton::Right => {
                        MouseEvent::Press(MouseButton::Right)
                    }
                    TMouseButton::WheelUp => MouseEvent::WheelUp,
                    TMouseButton::WheelDown => MouseEvent::WheelDown,
                };

                if let MouseEvent::Press(btn) = event {
                    self.last_button = Some(btn);
                }

                Event::Mouse {
                    event,
                    position,
                    offset: Vec2::zero(),
                }
            }
            TEvent::Mouse(TMouseEvent::Release(x, y))
                if self.last_button.is_some() =>
            {
                let event = MouseEvent::Release(self.last_button.unwrap());
                let position = (x - 1, y - 1).into();
                Event::Mouse {
                    event,
                    position,
                    offset: Vec2::zero(),
                }
            }
            TEvent::Mouse(TMouseEvent::Hold(x, y))
                if self.last_button.is_some() =>
            {
                let event = MouseEvent::Hold(self.last_button.unwrap());
                let position = (x - 1, y - 1).into();
                Event::Mouse {
                    event,
                    position,
                    offset: Vec2::zero(),
                }
            }
            _ => Event::Unknown(vec![]),
        }
    }
}

trait Effectable {
    fn on(&self);
    fn off(&self);
}

impl Effectable for theme::Effect {
    fn on(&self) {
        match *self {
            theme::Effect::Simple => (),
            theme::Effect::Reverse => print!("{}", tstyle::Invert),
            theme::Effect::Bold => print!("{}", tstyle::Bold),
            theme::Effect::Italic => print!("{}", tstyle::Italic),
            theme::Effect::Underline => print!("{}", tstyle::Underline),
        }
    }

    fn off(&self) {
        match *self {
            theme::Effect::Simple => (),
            theme::Effect::Reverse => print!("{}", tstyle::NoInvert),
            theme::Effect::Bold => print!("{}", tstyle::NoBold),
            theme::Effect::Italic => print!("{}", tstyle::NoItalic),
            theme::Effect::Underline => print!("{}", tstyle::NoUnderline),
        }
    }
}

impl Backend {
    pub fn init() -> Box<backend::Backend> {
        print!("{}", termion::cursor::Hide);

        // TODO: lock stdout
        let terminal = AlternateScreen::from(MouseTerminal::from(
            ::std::io::stdout().into_raw_mode().unwrap(),
        ));

        let c = Backend {
            terminal: terminal,
            current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)),
        };

        Box::new(c)
    }

    fn apply_colors(&self, colors: theme::ColorPair) {
        with_color(&colors.front, |c| print!("{}", tcolor::Fg(c)));
        with_color(&colors.back, |c| print!("{}", tcolor::Bg(c)));
    }
}

impl backend::Backend for Backend {
    fn finish(&mut self) {
        print!("{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1));
        print!(
            "{}[49m{}[39m{}",
            27 as char,
            27 as char,
            termion::clear::All
        );
    }

    fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair {
        let current_style = self.current_style.get();

        if current_style != color {
            self.apply_colors(color);
            self.current_style.set(color);
        }

        return current_style;
    }

    fn set_effect(&self, effect: theme::Effect) {
        effect.on();
    }

    fn unset_effect(&self, effect: theme::Effect) {
        effect.off();
    }

    fn has_colors(&self) -> bool {
        // TODO: color support detection?
        true
    }

    fn screen_size(&self) -> Vec2 {
        let (x, y) = termion::terminal_size().unwrap_or((1, 1));
        (x, y).into()
    }

    fn clear(&self, color: theme::Color) {
        self.apply_colors(theme::ColorPair {
            front: color,
            back: color,
        });
        print!("{}", termion::clear::All);
    }

    fn refresh(&mut self) {
        self.terminal.flush().unwrap();
    }

    fn print_at(&self, pos: Vec2, text: &str) {
        print!(
            "{}{}",
            termion::cursor::Goto(1 + pos.x as u16, 1 + pos.y as u16),
            text
        );
    }

    fn start_input_thread(
        &mut self, event_sink: Sender<Option<Event>>,
        input_request: Receiver<backend::InputRequest>,
    ) {
        let running = Arc::new(AtomicBool::new(true));

        #[cfg(unix)]
        {
            backend::resize::start_resize_thread(
                Signals::new(&[libc::SIGWINCH]).unwrap(),
                event_sink.clone(),
                input_request.clone(),
                Arc::clone(&running),
                None,
            );
        }

        let mut parser = InputParser::new();
        thread::spawn(move || {
            for req in input_request {
                match req {
                    backend::InputRequest::Peek => {
                        event_sink.send(parser.peek());
                    }
                    backend::InputRequest::Block => {
                        event_sink.send(Some(parser.next_event()));
                    }
                }
            }
            running.store(false, Ordering::Relaxed);
        });
    }
}

fn with_color<F, R>(clr: &theme::Color, f: F) -> R
where
    F: FnOnce(&tcolor::Color) -> R,
{
    match *clr {
        theme::Color::TerminalDefault => f(&tcolor::Reset),
        theme::Color::Dark(theme::BaseColor::Black) => f(&tcolor::Black),
        theme::Color::Dark(theme::BaseColor::Red) => f(&tcolor::Red),
        theme::Color::Dark(theme::BaseColor::Green) => f(&tcolor::Green),
        theme::Color::Dark(theme::BaseColor::Yellow) => f(&tcolor::Yellow),
        theme::Color::Dark(theme::BaseColor::Blue) => f(&tcolor::Blue),
        theme::Color::Dark(theme::BaseColor::Magenta) => f(&tcolor::Magenta),
        theme::Color::Dark(theme::BaseColor::Cyan) => f(&tcolor::Cyan),
        theme::Color::Dark(theme::BaseColor::White) => f(&tcolor::White),

        theme::Color::Light(theme::BaseColor::Black) => f(&tcolor::LightBlack),
        theme::Color::Light(theme::BaseColor::Red) => f(&tcolor::LightRed),
        theme::Color::Light(theme::BaseColor::Green) => f(&tcolor::LightGreen),
        theme::Color::Light(theme::BaseColor::Yellow) => {
            f(&tcolor::LightYellow)
        }
        theme::Color::Light(theme::BaseColor::Blue) => f(&tcolor::LightBlue),
        theme::Color::Light(theme::BaseColor::Magenta) => {
            f(&tcolor::LightMagenta)
        }
        theme::Color::Light(theme::BaseColor::Cyan) => f(&tcolor::LightCyan),
        theme::Color::Light(theme::BaseColor::White) => f(&tcolor::LightWhite),

        theme::Color::Rgb(r, g, b) => f(&tcolor::Rgb(r, g, b)),
        theme::Color::RgbLowRes(r, g, b) => {
            f(&tcolor::AnsiValue::rgb(r, g, b))
        }
    }
}
<commit_msg>Do not compile signal_hook on Windows<commit_after>//! Backend using the pure-rust termion library.
//!
//! Requires the `termion-backend` feature.
#![cfg(feature = "termion")]

extern crate termion;

use self::termion::color as tcolor;
use self::termion::event::Event as TEvent;
use self::termion::event::Key as TKey;
use self::termion::event::MouseButton as TMouseButton;
use self::termion::event::MouseEvent as TMouseEvent;
use self::termion::input::{MouseTerminal, TermRead};
use self::termion::raw::{IntoRawMode, RawTerminal};
use self::termion::screen::AlternateScreen;
use self::termion::style as tstyle;
use crossbeam_channel::{self, Receiver, Sender};
use libc;

#[cfg(unix)]
use signal_hook::iterator::Signals;

use backend;
use event::{Event, Key, MouseButton, MouseEvent};
use theme;
use vec::Vec2;

use std::cell::Cell;
use std::io::{Stdout, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;

pub struct Backend {
    terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
    current_style: Cell<theme::ColorPair>,
}

struct InputParser {
    // Inner state required to parse input
    last_button: Option<MouseButton>,

    event_due: bool,
    requests: Sender<()>,
    input: Receiver<TEvent>,
}

impl InputParser {
    // Creates a non-blocking abstraction over the usual blocking input
    fn new() -> Self {
        let (input_sender, input_receiver) = crossbeam_channel::bounded(0);
        let (request_sender, request_receiver) = crossbeam_channel::bounded(0);

        // This thread will stop after an event when `InputParser` is dropped.
        thread::spawn(move || {
            let stdin = ::std::io::stdin();
            let stdin = stdin.lock();
            let mut events = stdin.events();

            for _ in request_receiver {
                let event: Result<TEvent, ::std::io::Error> =
                    events.next().unwrap();
                input_sender.send(event.unwrap());
            }
        });

        InputParser {
            last_button: None,
            input: input_receiver,
            requests: request_sender,
            event_due: false,
        }
    }

    /// We pledge to want input.
    ///
    /// If we were already expecting input, this is a NO-OP.
    fn request(&mut self) {
        if !self.event_due {
            self.requests.send(());
            self.event_due = true;
        }
    }

    fn peek(&mut self) -> Option<Event> {
        self.request();

        let timeout = ::std::time::Duration::from_millis(10);

        let input = select! {
            recv(self.input, input) => {
                input
            }
            recv(crossbeam_channel::after(timeout)) => return None,
        };

        // We got what we came for.
        self.event_due = false;
        Some(self.map_key(input.unwrap()))
    }

    fn next_event(&mut self) -> Event {
        self.request();

        let input = self.input.recv().unwrap();
        self.event_due = false;
        self.map_key(input)
    }

    fn map_key(&mut self, event: TEvent) -> Event {
        match event {
            TEvent::Unsupported(bytes) => Event::Unknown(bytes),
            TEvent::Key(TKey::Esc) => Event::Key(Key::Esc),
            TEvent::Key(TKey::Backspace) => Event::Key(Key::Backspace),
            TEvent::Key(TKey::Left) => Event::Key(Key::Left),
            TEvent::Key(TKey::Right) => Event::Key(Key::Right),
            TEvent::Key(TKey::Up) => Event::Key(Key::Up),
            TEvent::Key(TKey::Down) => Event::Key(Key::Down),
            TEvent::Key(TKey::Home) => Event::Key(Key::Home),
            TEvent::Key(TKey::End) => Event::Key(Key::End),
            TEvent::Key(TKey::PageUp) => Event::Key(Key::PageUp),
            TEvent::Key(TKey::PageDown) => Event::Key(Key::PageDown),
            TEvent::Key(TKey::Delete) => Event::Key(Key::Del),
            TEvent::Key(TKey::Insert) => Event::Key(Key::Ins),
            TEvent::Key(TKey::F(i)) if i < 12 => Event::Key(Key::from_f(i)),
            TEvent::Key(TKey::F(j)) => Event::Unknown(vec![j]),
            TEvent::Key(TKey::Char('\n')) => Event::Key(Key::Enter),
            TEvent::Key(TKey::Char('\t')) => Event::Key(Key::Tab),
            TEvent::Key(TKey::Char(c)) => Event::Char(c),
            TEvent::Key(TKey::Ctrl('c')) => Event::Exit,
            TEvent::Key(TKey::Ctrl(c)) => Event::CtrlChar(c),
            TEvent::Key(TKey::Alt(c)) => Event::AltChar(c),
            TEvent::Mouse(TMouseEvent::Press(btn, x, y)) => {
                let position = (x - 1, y - 1).into();

                let event = match btn {
                    TMouseButton::Left => MouseEvent::Press(MouseButton::Left),
                    TMouseButton::Middle => {
                        MouseEvent::Press(MouseButton::Middle)
                    }
                    TMouseButton::Right => {
                        MouseEvent::Press(MouseButton::Right)
                    }
                    TMouseButton::WheelUp => MouseEvent::WheelUp,
                    TMouseButton::WheelDown => MouseEvent::WheelDown,
                };

                if let MouseEvent::Press(btn) = event {
                    self.last_button = Some(btn);
                }

                Event::Mouse {
                    event,
                    position,
                    offset: Vec2::zero(),
                }
            }
            TEvent::Mouse(TMouseEvent::Release(x, y))
                if self.last_button.is_some() =>
            {
                let event = MouseEvent::Release(self.last_button.unwrap());
                let position = (x - 1, y - 1).into();
                Event::Mouse {
                    event,
                    position,
                    offset: Vec2::zero(),
                }
            }
            TEvent::Mouse(TMouseEvent::Hold(x, y))
                if self.last_button.is_some() =>
            {
                let event = MouseEvent::Hold(self.last_button.unwrap());
                let position = (x - 1, y - 1).into();
                Event::Mouse {
                    event,
                    position,
                    offset: Vec2::zero(),
                }
            }
            _ => Event::Unknown(vec![]),
        }
    }
}

trait Effectable {
    fn on(&self);
    fn off(&self);
}

impl Effectable for theme::Effect {
    fn on(&self) {
        match *self {
            theme::Effect::Simple => (),
            theme::Effect::Reverse => print!("{}", tstyle::Invert),
            theme::Effect::Bold => print!("{}", tstyle::Bold),
            theme::Effect::Italic => print!("{}", tstyle::Italic),
            theme::Effect::Underline => print!("{}", tstyle::Underline),
        }
    }

    fn off(&self) {
        match *self {
            theme::Effect::Simple => (),
            theme::Effect::Reverse => print!("{}", tstyle::NoInvert),
            theme::Effect::Bold => print!("{}", tstyle::NoBold),
            theme::Effect::Italic => print!("{}", tstyle::NoItalic),
            theme::Effect::Underline => print!("{}", tstyle::NoUnderline),
        }
    }
}

impl Backend {
    pub fn init() -> Box<backend::Backend> {
        print!("{}", termion::cursor::Hide);

        // TODO: lock stdout
        let terminal = AlternateScreen::from(MouseTerminal::from(
            ::std::io::stdout().into_raw_mode().unwrap(),
        ));

        let c = Backend {
            terminal: terminal,
            current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)),
        };

        Box::new(c)
    }

    fn apply_colors(&self, colors: theme::ColorPair) {
        with_color(&colors.front, |c| print!("{}", tcolor::Fg(c)));
        with_color(&colors.back, |c| print!("{}", tcolor::Bg(c)));
    }
}

impl backend::Backend for Backend {
    fn finish(&mut self) {
        print!("{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1));
        print!(
            "{}[49m{}[39m{}",
            27 as char,
            27 as char,
            termion::clear::All
        );
    }

    fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair {
        let current_style = self.current_style.get();

        if current_style != color {
            self.apply_colors(color);
            self.current_style.set(color);
        }

        return current_style;
    }

    fn set_effect(&self, effect: theme::Effect) {
        effect.on();
    }

    fn unset_effect(&self, effect: theme::Effect) {
        effect.off();
    }

    fn has_colors(&self) -> bool {
        // TODO: color support detection?
        true
    }

    fn screen_size(&self) -> Vec2 {
        let (x, y) = termion::terminal_size().unwrap_or((1, 1));
        (x, y).into()
    }

    fn clear(&self, color: theme::Color) {
        self.apply_colors(theme::ColorPair {
            front: color,
            back: color,
        });
        print!("{}", termion::clear::All);
    }

    fn refresh(&mut self) {
        self.terminal.flush().unwrap();
    }

    fn print_at(&self, pos: Vec2, text: &str) {
        print!(
            "{}{}",
            termion::cursor::Goto(1 + pos.x as u16, 1 + pos.y as u16),
            text
        );
    }

    fn start_input_thread(
        &mut self, event_sink: Sender<Option<Event>>,
        input_request: Receiver<backend::InputRequest>,
    ) {
        let running = Arc::new(AtomicBool::new(true));

        #[cfg(unix)]
        {
            backend::resize::start_resize_thread(
                Signals::new(&[libc::SIGWINCH]).unwrap(),
                event_sink.clone(),
                input_request.clone(),
                Arc::clone(&running),
                None,
            );
        }

        let mut parser = InputParser::new();
        thread::spawn(move || {
            for req in input_request {
                match req {
                    backend::InputRequest::Peek => {
                        event_sink.send(parser.peek());
                    }
                    backend::InputRequest::Block => {
                        event_sink.send(Some(parser.next_event()));
                    }
                }
            }
            running.store(false, Ordering::Relaxed);
        });
    }
}

fn with_color<F, R>(clr: &theme::Color, f: F) -> R
where
    F: FnOnce(&tcolor::Color) -> R,
{
    match *clr {
        theme::Color::TerminalDefault => f(&tcolor::Reset),
        theme::Color::Dark(theme::BaseColor::Black) => f(&tcolor::Black),
        theme::Color::Dark(theme::BaseColor::Red) => f(&tcolor::Red),
        theme::Color::Dark(theme::BaseColor::Green) => f(&tcolor::Green),
        theme::Color::Dark(theme::BaseColor::Yellow) => f(&tcolor::Yellow),
        theme::Color::Dark(theme::BaseColor::Blue) => f(&tcolor::Blue),
        theme::Color::Dark(theme::BaseColor::Magenta) => f(&tcolor::Magenta),
        theme::Color::Dark(theme::BaseColor::Cyan) => f(&tcolor::Cyan),
        theme::Color::Dark(theme::BaseColor::White) => f(&tcolor::White),

        theme::Color::Light(theme::BaseColor::Black) => f(&tcolor::LightBlack),
        theme::Color::Light(theme::BaseColor::Red) => f(&tcolor::LightRed),
        theme::Color::Light(theme::BaseColor::Green) => f(&tcolor::LightGreen),
        theme::Color::Light(theme::BaseColor::Yellow) => {
            f(&tcolor::LightYellow)
        }
        theme::Color::Light(theme::BaseColor::Blue) => f(&tcolor::LightBlue),
        theme::Color::Light(theme::BaseColor::Magenta) => {
            f(&tcolor::LightMagenta)
        }
        theme::Color::Light(theme::BaseColor::Cyan) => f(&tcolor::LightCyan),
        theme::Color::Light(theme::BaseColor::White) => f(&tcolor::LightWhite),

        theme::Color::Rgb(r, g, b) => f(&tcolor::Rgb(r, g, b)),
        theme::Color::RgbLowRes(r, g, b) => {
            f(&tcolor::AnsiValue::rgb(r, g, b))
        }
    }
}
<|endoftext|>


<commit_before>#![feature(macro_rules)]
extern crate string_telephone;

mod test_shared;

/**
 * Test we can start listening
 */
#[test]
fn create_server() {
    unimplemented!()
}

/**
 * Test we get empty polls when we have no clients
 */
#[test]
fn empty_poll() {
    unimplemented!()
}

/**
 * Test we reject bad connection attempts
 */
#[test]
fn bad_client_attempt() {
    unimplemented!()
}

/**
 * Test we can take multiple clients
 */
#[test]
fn multiple_clients() {
    unimplemented!()
}

/**
 * Test we can cull old clients
 */
#[test]
fn cull() {
    unimplemented!()
}

/**
 * Test we can send to one
 */
#[test]
fn send_to_one() {
    unimplemented!()
}

/**
 * Test we can't send to folks who aren't connected
 */
#[test]
fn send_to_disconnected() {
    unimplemented!()
}

/**
 * Test we can send to multiple folks
 */
#[test]
fn send_to_many() {
    unimplemented!()
}

/**
 * Test we can send to everyone connected
 */
#[test]
fn send_to_all() {
    unimplemented!()
}
<commit_msg>Basic Create server test<commit_after>#![feature(macro_rules)]
extern crate string_telephone;

use string_telephone::{ConnectionConfig, ClientConnectionConfig, Server, Packet, PollEmpty, PacketConnect, PacketMessage, PacketDisconnect, PollDisconnected};
use std::io::net::ip::{Ipv4Addr, SocketAddr};
use std::time::duration::Duration;

mod test_shared;

fn generate_settings(port: u16, protocol_id: u32) -> (SocketAddr, ConnectionConfig<Vec<u8>>) {
    let my_addr = SocketAddr{ ip: Ipv4Addr(127, 0, 0, 1), port: port };
    let settings = ConnectionConfig::new(protocol_id, Duration::seconds(10), test_shared::deserializer, test_shared::serializer);
    (my_addr, settings)
}

/**
 * Test we can start listening
 */
#[test]
fn create_server() {
    let socket = 64000;
    let (my_addr, settings) = generate_settings(socket, 121);
    match Server::new(my_addr, settings) {
        Ok(server) => (), //passed
        Err(t) => fail!("Failed to create a server - {}", t)
    };
}

/**
 * Test we get empty polls when we have no clients
 */
#[test]
fn empty_poll() {
    unimplemented!()
}

/**
 * Test we reject bad connection attempts
 */
#[test]
fn bad_client_attempt() {
    unimplemented!()
}

/**
 * Test we can take multiple clients
 */
#[test]
fn multiple_clients() {
    unimplemented!()
}

/**
 * Test we can cull old clients
 */
#[test]
fn cull() {
    unimplemented!()
}

/**
 * Test we can send to one
 */
#[test]
fn send_to_one() {
    unimplemented!()
}

/**
 * Test we can't send to folks who aren't connected
 */
#[test]
fn send_to_disconnected() {
    unimplemented!()
}

/**
 * Test we can send to multiple folks
 */
#[test]
fn send_to_many() {
    unimplemented!()
}

/**
 * Test we can send to everyone connected
 */
#[test]
fn send_to_all() {
    unimplemented!()
}
<|endoftext|>


<commit_before>use directories::ProjectDirs;
use gettextrs::gettext;
use ron;

use std::fs::{create_dir_all, File};
use std::io::Write;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::sync::{Arc, RwLock};

use super::{TLD, SLD};

const CONFIG_FILENAME: &str = "config.ron";

lazy_static! {
    pub static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::new()));
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct UI {
    pub width: i32,
    pub height: i32,
    pub is_chapters_list_hidden: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct Media {
    pub is_gl_disable: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct GlobalConfig {
    pub ui: UI,
    pub media: Media,
}

pub struct Config {
    path: PathBuf,
    last: GlobalConfig,
    current: GlobalConfig,
}

impl Config {
    fn new() -> Config {
        let project_dirs = ProjectDirs::from(TLD, SLD, env!("CARGO_PKG_NAME"));
        let config_dir = project_dirs.config_dir();
        create_dir_all(&config_dir).unwrap();
        let path = config_dir.join(CONFIG_FILENAME).to_owned();

        let last = match File::open(&path) {
            Ok(config_file) => {
                let config: Result<GlobalConfig, ron::de::Error> = ron::de::from_reader(config_file);
                match config {
                    Ok(config) => {
                        debug!("read config: {:?}", config);
                        config
                    }
                    Err(err) => {
                        error!("{}",
                            &gettext("couldn't load configuration: {}")
                                .replacen("{}", &format!("{:?}", err), 1),
                        );
                        GlobalConfig::default()
                    }
                }
            }
            Err(_) => GlobalConfig::default(),
        };

        Config {
            path,
            current: last.clone(),
            last,
        }
    }

    pub fn save(&mut self) {
        if self.last == self.current {
            // unchanged => don't save
            return;
        }

        match File::create(&self.path) {
            Ok(mut config_file) => {
                match ron::ser::to_string_pretty(
                    &self.current,
                    ron::ser::PrettyConfig::default(),
                ) {
                    Ok(config_str) => match config_file.write_all(config_str.as_bytes()) {
                        Ok(()) => {
                            self.last = self.current.clone();
                            debug!("saved config: {:?}", self.current);
                        }
                        Err(err) => {
                            error!("{}",
                                &gettext("couldn't write configuration: {}")
                                    .replacen("{}", &format!("{:?}", err), 1),
                            );
                        }
                    }
                    Err(err) => {
                        error!("{}",
                            &gettext("couldn't serialize configuration: {}")
                                .replacen("{}", &format!("{:?}", err), 1),
                        );
                    }
                }
            }
            Err(err) => {
                error!("{}",
                    &gettext("couldn't create configuration file: {}")
                        .replacen("{}", &format!("{:?}", err), 1),
                );
            }
        }
    }
}

impl Deref for Config {
    type Target = GlobalConfig;

    fn deref(&self) -> &Self::Target {
        &self.current
    }
}

impl DerefMut for Config {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.current
    }
}<commit_msg>Config: remove unneeded Arc (#122)<commit_after>use directories::ProjectDirs;
use gettextrs::gettext;
use ron;

use std::fs::{create_dir_all, File};
use std::io::Write;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::sync::RwLock;

use super::{TLD, SLD};

const CONFIG_FILENAME: &str = "config.ron";

lazy_static! {
    pub static ref CONFIG: RwLock<Config> = RwLock::new(Config::new());
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct UI {
    pub width: i32,
    pub height: i32,
    pub is_chapters_list_hidden: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct Media {
    pub is_gl_disable: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct GlobalConfig {
    pub ui: UI,
    pub media: Media,
}

pub struct Config {
    path: PathBuf,
    last: GlobalConfig,
    current: GlobalConfig,
}

impl Config {
    fn new() -> Config {
        let project_dirs = ProjectDirs::from(TLD, SLD, env!("CARGO_PKG_NAME"));
        let config_dir = project_dirs.config_dir();
        create_dir_all(&config_dir).unwrap();
        let path = config_dir.join(CONFIG_FILENAME).to_owned();

        let last = match File::open(&path) {
            Ok(config_file) => {
                let config: Result<GlobalConfig, ron::de::Error> = ron::de::from_reader(config_file);
                match config {
                    Ok(config) => {
                        debug!("read config: {:?}", config);
                        config
                    }
                    Err(err) => {
                        error!("{}",
                            &gettext("couldn't load configuration: {}")
                                .replacen("{}", &format!("{:?}", err), 1),
                        );
                        GlobalConfig::default()
                    }
                }
            }
            Err(_) => GlobalConfig::default(),
        };

        Config {
            path,
            current: last.clone(),
            last,
        }
    }

    pub fn save(&mut self) {
        if self.last == self.current {
            // unchanged => don't save
            return;
        }

        match File::create(&self.path) {
            Ok(mut config_file) => {
                match ron::ser::to_string_pretty(
                    &self.current,
                    ron::ser::PrettyConfig::default(),
                ) {
                    Ok(config_str) => match config_file.write_all(config_str.as_bytes()) {
                        Ok(()) => {
                            self.last = self.current.clone();
                            debug!("saved config: {:?}", self.current);
                        }
                        Err(err) => {
                            error!("{}",
                                &gettext("couldn't write configuration: {}")
                                    .replacen("{}", &format!("{:?}", err), 1),
                            );
                        }
                    }
                    Err(err) => {
                        error!("{}",
                            &gettext("couldn't serialize configuration: {}")
                                .replacen("{}", &format!("{:?}", err), 1),
                        );
                    }
                }
            }
            Err(err) => {
                error!("{}",
                    &gettext("couldn't create configuration file: {}")
                        .replacen("{}", &format!("{:?}", err), 1),
                );
            }
        }
    }
}

impl Deref for Config {
    type Target = GlobalConfig;

    fn deref(&self) -> &Self::Target {
        &self.current
    }
}

impl DerefMut for Config {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.current
    }
}<|endoftext|>


<commit_before><commit_msg>Drop url builder for now<commit_after><|endoftext|>


<commit_before><commit_msg>[clippy] unified identical match<commit_after><|endoftext|>