// Copyright 2015, 2016 Ethcore (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see . use rustc_serialize::json::Json; use std::thread; use std::io::Read; use std::time::Duration; use std::str::FromStr; use std::sync::mpsc; use hyper::client::{Handler, Request, Response, Client}; use hyper::{Next, Encoder, Decoder}; use hyper::net::HttpStream; #[derive(Debug)] pub struct PriceInfo { pub ethusd: f32, } pub struct SetPriceHandler { set_price: F, channel: mpsc::Sender<()>, } impl Drop for SetPriceHandler { fn drop(&mut self) { let _ = self.channel.send(()); } } impl Handler for SetPriceHandler { fn on_request(&mut self, _: &mut Request) -> Next { trace!(target: "miner", "price_info: on_request"); Next::read().timeout(Duration::from_secs(3)) } fn on_request_writable(&mut self, _: &mut Encoder) -> Next { trace!(target: "miner", "price_info: on_request_writable"); Next::read().timeout(Duration::from_secs(3)) } fn on_response(&mut self, _: Response) -> Next { trace!(target: "miner", "price_info: on_response"); Next::read().timeout(Duration::from_secs(3)) } fn on_response_readable(&mut self, r: &mut Decoder) -> Next { trace!(target: "miner", "price_info: on_response_readable!"); let mut body = String::new(); let _ = r.read_to_string(&mut body).ok() .and_then(|_| Json::from_str(&body).ok()) .and_then(|json| json.find_path(&["result", "ethusd"]) .and_then(|obj| match *obj { Json::String(ref s) => Some((self.set_price)(PriceInfo { ethusd: FromStr::from_str(s).unwrap() })), _ => None, })); Next::end() } } impl PriceInfo { pub fn get(set_price: F) -> Result<(), ()> { // TODO: Handle each error type properly let client = try!(Client::new().map_err(|_| ())); trace!(target: "miner", "Starting price info request..."); thread::spawn(move || { trace!(target: "miner", "Inside thread..."); let (tx, rx) = mpsc::channel(); let _ = client.request(FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice").unwrap(), SetPriceHandler { set_price: set_price, channel: tx, }).ok().and_then(|_| rx.recv().ok()); client.close(); }); Ok(()) } } //#[ignore] #[test] fn should_get_price_info() { use std::sync::{Condvar, Mutex, Arc}; use std::time::Duration; use util::log::init_log; init_log(); let done = Arc::new((Mutex::new(PriceInfo { ethusd: 0f32 }), Condvar::new())); let rdone = done.clone(); trace!(target: "miner", "price_info: getting price_info"); PriceInfo::get(move |price| { let mut p = rdone.0.lock().unwrap(); *p = price; rdone.1.notify_one(); }).unwrap(); let p = done.1.wait_timeout(done.0.lock().unwrap(), Duration::from_millis(10000)).unwrap(); assert!(!p.1.timed_out()); assert!(p.0.ethusd != 0f32); }