Commit e8bae970 authored by Felix Häcker's avatar Felix Häcker

rewrite library using restson

parent 2b5a4f62
...@@ -3,4 +3,6 @@ ...@@ -3,4 +3,6 @@
/target/ /target/
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
\ No newline at end of file
.buildconfig
\ No newline at end of file
This diff is collapsed.
...@@ -6,14 +6,7 @@ version = "0.0.1" ...@@ -6,14 +6,7 @@ version = "0.0.1"
description = "Rust API wrapper for radio-browser.info" description = "Rust API wrapper for radio-browser.info"
[dependencies] [dependencies]
reqwest = "0.8.5" restson = "^0.3.0"
serde = "1.0.43" serde = "^1.0"
serde_json = "1.0" serde_derive = "^1.0"
serde_derive = "1.0.43" log = "0.4"
log = "0.4" \ No newline at end of file
failure = "0.1.1"
glib = "0.5.0"
[dependencies.gtk]
version = "0.4.0"
features = ["v3_22"]
\ No newline at end of file
extern crate serde;
extern crate serde_json;
extern crate reqwest;
extern crate gtk;
use gtk::prelude::*;
use country::Country;
use station::Station;
use std::env; use std::env;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
...@@ -13,133 +5,81 @@ use std::sync::mpsc::channel; ...@@ -13,133 +5,81 @@ use std::sync::mpsc::channel;
use std::thread; use std::thread;
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use restson::{RestClient, Error};
#[derive(Deserialize)] use {Codec, Country, Language, State, Station, Stats, Tag};
pub struct StationUrlResult{ use {CodecResponse, CountryResponse, LanguageResponse, StateResponse, StationResponse, TagResponse};
url: String, use PlayableStationUrl;
}
const BASE_URL: &'static str = "https://www.radio-browser.info/webservice/";
const LANGUAGES: &'static str = "json/languages/";
const COUNTRIES: &'static str = "json/countries/";
const STATES: &'static str = "json/states/";
const TAGS: &'static str = "json/tags/";
const PLAYABLE_STATION_URL: &'static str = "v2/json/url/";
const STATION_BY_ID: &'static str = "json/stations/byid/";
const SEARCH: &'static str ="json/stations/search";
pub enum ClientUpdate {
NewStations(Vec<Station>),
Clear,
}
pub struct Client { pub struct Client {
current_search_id: Rc<RefCell<u64>>, rest_client: RestClient,
} }
impl Client { impl Client {
pub fn new() -> Client { pub fn new(base_url: &str) -> Client {
Client { let mut rest_client = match RestClient::new(base_url){
current_search_id: Rc::new(RefCell::new(0)), Ok(rc) => rc,
} Err(err) => panic!("Could not create rest_client!")
}
pub fn create_reqwest_client() -> reqwest::Client{
let proxy: Option<String> = match env::var("http_proxy") {
Ok(proxy) => Some(proxy),
Err(_) => None,
}; };
match proxy { Client { rest_client }
Some(proxy_address) => {
info!("Use Proxy: {}", proxy_address);
let proxy = reqwest::Proxy::all(&proxy_address).unwrap();
reqwest::Client::builder().proxy(proxy).build().unwrap()
},
None => reqwest::Client::new(),
}
} }
pub fn get_all_languages(&self) -> Vec<Country>{ pub fn get_station_by_id(&mut self, id: u32) -> Result<Station, Error>{
let url = format!("{}{}", BASE_URL, LANGUAGES); match self.rest_client.get(id)? {
Self::send_get_request(url).unwrap().json().unwrap() StationResponse::Station(station) => Ok(station),
StationResponse::Stations(mut stations) => Ok(stations.pop().unwrap()),
}
} }
pub fn get_all_countries(&self) -> Vec<Country>{ pub fn get_all_stations(&mut self) -> Result<Vec<Station>, Error>{
let url = format!("{}{}", BASE_URL, LANGUAGES); match self.rest_client.get(())? {
Self::send_get_request(url).unwrap().json().unwrap() StationResponse::Stations(stations) => Ok(stations),
_ => Err(Error::InvalidValue),
}
} }
pub fn get_all_states(&self) -> Vec<Country>{ pub fn get_all_codecs(&mut self) -> Result<Vec<Codec>, Error>{
let url = format!("{}{}", BASE_URL, STATES); match self.rest_client.get(())? {
Self::send_get_request(url).unwrap().json().unwrap() CodecResponse::Codecs(codecs) => Ok(codecs),
_ => Err(Error::InvalidValue),
}
} }
pub fn get_all_tags(&self) -> Vec<Country>{ pub fn get_all_countries(&mut self) -> Result<Vec<Country>, Error>{
let url = format!("{}{}", BASE_URL, TAGS); match self.rest_client.get(())? {
Self::send_get_request(url).unwrap().json().unwrap() CountryResponse::Countries(countries) => Ok(countries),
_ => Err(Error::InvalidValue),
}
} }
pub fn get_station_by_id(&self, id: i32) -> Result<Station,&str> { pub fn get_all_languages(&mut self) -> Result<Vec<Language>, Error>{
let url = format!("{}{}{}", BASE_URL, STATION_BY_ID, id); match self.rest_client.get(())? {
let mut result : Vec<Station> = Self::send_get_request(url).unwrap().json().unwrap(); LanguageResponse::Languages(languages) => Ok(languages),
_ => Err(Error::InvalidValue),
if result.len() > 0 {
Ok(result.remove(0))
}else {
Err("ID points to an empty station")
} }
} }
pub fn get_playable_station_url(station: &Station) -> String{ pub fn get_all_states(&mut self) -> Result<Vec<State>, Error>{
let url = format!("{}{}{}", BASE_URL, PLAYABLE_STATION_URL, station.id); match self.rest_client.get(())? {
let result: StationUrlResult = Self::send_get_request(url).unwrap().json().unwrap(); StateResponse::States(states) => Ok(states),
result.url _ => Err(Error::InvalidValue),
}
} }
pub fn search(&mut self, params: HashMap<String, String>, sender: Sender<ClientUpdate>){ pub fn get_all_tags(&mut self) -> Result<Vec<Tag>, Error>{
// Generate a new search ID. It is possible, that the old thread is still running, match self.rest_client.get(())? {
// while a new one already have started. With this ID we can check, if the search request is still up-to-date. TagResponse::Tags(tags) => Ok(tags),
*self.current_search_id.borrow_mut() += 1; _ => Err(Error::InvalidValue),
debug!("Start new search with ID {}", self.current_search_id.borrow()); }
sender.send(ClientUpdate::Clear);
// Do the actual search in a new thread
let (search_sender, search_receiver) = channel();
let url = format!("{}{}", BASE_URL, SEARCH);
thread::spawn(move || search_sender.send(Self::send_post_request(url, params).unwrap().json().unwrap())); //TODO: don't unwrap
// Start a loop, and wait for a message from the thread.
let current_search_id = self.current_search_id.clone();
let search_id = *self.current_search_id.borrow();
let sender = sender.clone();
gtk::timeout_add(100, move|| {
if search_id != *current_search_id.borrow() { // Compare with current search id
debug!("Search ID changed -> cancel this search loop. (This: {} <-> Current: {})", search_id, current_search_id.borrow());
return Continue(false);
}
match search_receiver.try_recv(){
Ok(stations) => {
sender.send(ClientUpdate::NewStations(stations));
Continue(false)
}
Err(_) => Continue(true),
}
});
} }
fn send_post_request(url: String, params: HashMap<String, String>) -> Result<reqwest::Response, reqwest::Error>{ pub fn get_stats(&mut self) -> Result<Stats, Error>{
debug!("Post request -> {:?} ({:?})", url, params); self.rest_client.get(())
let client = Self::create_reqwest_client();
client.post(&url).form(&params).send()
} }
fn send_get_request(url: String) -> Result<reqwest::Response, reqwest::Error>{ pub fn get_playable_station_url(&mut self, id: u32) -> Result<String, Error>{
debug!("Get request -> {:?}", url); let result: PlayableStationUrl = self.rest_client.get(id)?;
let client = Self::create_reqwest_client(); Ok(result.url)
client.get(&url).send()
} }
} }
use restson::{RestClient,RestPath,Error};
#[derive(Serialize, Deserialize, Debug)]
pub struct Codec {
pub value: String,
pub stationcount: String,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum CodecResponse {
Codecs(Vec<Codec>),
Codec(Codec),
}
impl RestPath<()> for CodecResponse {
fn get_path(_: ()) -> Result<String,Error> {
Ok(format!("webservice/json/codecs"))
}
}
#[derive(Deserialize)] use restson::{RestClient,RestPath,Error};
#[derive(Serialize, Deserialize, Debug)]
pub struct Country { pub struct Country {
pub name: String,
pub value: String, pub value: String,
pub stationcount: String, pub stationcount: String,
} }
\ No newline at end of file
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum CountryResponse {
Countries(Vec<Country>),
Country(Country),
}
impl RestPath<()> for CountryResponse {
fn get_path(_: ()) -> Result<String,Error> {
Ok(format!("webservice/json/countries"))
}
}
use reqwest;
use std::io;
#[derive(Fail, Debug)]
pub enum Error {
#[fail(display = "Reqwest error: {}", _0)]
RequestError(#[cause] reqwest::Error),
#[fail(display = "Input/Output error: {}", _0)]
IoError(#[cause] io::Error),
#[fail(display = "Unexpected server response: {}", _0)]
UnexpectedResponse(reqwest::StatusCode),
#[fail(display = "url error: {}", _0)]
UrlError(reqwest::UrlError),
#[fail(display = "Parse error")]
ParseError,
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Error::RequestError(err)
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::IoError(err)
}
}
impl From<reqwest::UrlError> for Error {
fn from(err: reqwest::UrlError) -> Self {
Error::UrlError(err)
}
}
#[derive(Deserialize)] use restson::{RestClient,RestPath,Error};
#[derive(Serialize, Deserialize, Debug)]
pub struct Language { pub struct Language {
pub name: String,
pub value: String, pub value: String,
pub stationcount: String, pub stationcount: String,
} }
\ No newline at end of file
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum LanguageResponse {
Languages(Vec<Language>),
Language(Language),
}
impl RestPath<()> for LanguageResponse {
fn get_path(_: ()) -> Result<String,Error> {
Ok(format!("webservice/json/languages"))
}
}
#[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_derive;
#[macro_use] extern crate log; #[macro_use] extern crate log;
#[macro_use] extern crate failure;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate restson;
extern crate reqwest;
extern crate gtk; // TODO: Don't require gtk mod station;
extern crate glib; mod country;
mod codec;
mod state;
mod language;
mod tag;
mod stats;
mod client;
pub mod error; pub use station::Station as Station;
pub mod client; use station::PlayableStationUrl as PlayableStationUrl;
pub mod station; pub use country::Country as Country;
pub mod country; pub use codec::Codec as Codec;
pub mod state; pub use state::State as State;
pub mod language; pub use language::Language as Language;
pub mod tag; pub use tag::Tag as Tag;
pub mod stats; pub use stats::Stats as Stats;
pub use client::Client as Client;
use station::StationResponse as StationResponse;
use country::CountryResponse as CountryResponse;
use codec::CodecResponse as CodecResponse;
use state::StateResponse as StateResponse;
use language::LanguageResponse as LanguageResponse;
use tag::TagResponse as TagResponse;
#[derive(Deserialize)] use restson::{RestClient,RestPath,Error};
#[derive(Serialize, Deserialize, Debug)]
pub struct State { pub struct State {
pub name: String,
pub value: String, pub value: String,
pub country: String,
pub stationcount: String, pub stationcount: String,
} }
\ No newline at end of file
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum StateResponse {
States(Vec<State>),
State(State),
}
impl RestPath<()> for StateResponse {
fn get_path(_: ()) -> Result<String,Error> {
Ok(format!("webservice/json/states"))
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq, Hash)] use restson::{RestClient,RestPath,Error};
#[derive(Serialize, Deserialize, Debug)]
pub struct Station { pub struct Station {
pub name: String, pub name: String,
pub language: String, pub language: String,
...@@ -28,10 +30,40 @@ pub struct Station { ...@@ -28,10 +30,40 @@ pub struct Station {
pub clicktrend: String, pub clicktrend: String,
} }
impl Station{}
impl PartialEq for Station { impl PartialEq for Station {
fn eq(&self, other: &Station) -> bool { fn eq(&self, other: &Station) -> bool {
self.id == other.id self.id == other.id
} }
} }
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum StationResponse {
Stations(Vec<Station>),
Station(Station),
}
impl RestPath<()> for StationResponse {
fn get_path(_: ()) -> Result<String,Error> {
Ok(format!("webservice/json/stations"))
}
}
impl RestPath<u32> for StationResponse {
fn get_path(param: u32) -> Result<String,Error> {
Ok(format!("webservice/json/stations/byid/{}", param))
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PlayableStationUrl {
pub name: String,
pub url: String,
}
impl RestPath<u32> for PlayableStationUrl {
fn get_path(param: u32) -> Result<String,Error> {
Ok(format!("webservice/v2/xml/url/{}", param))
}
}
#[derive(Deserialize)] use restson::{RestClient,RestPath,Error};
#[derive(Serialize, Deserialize, Debug)]
pub struct Stats { pub struct Stats {
pub stations: String, pub stations: String,
pub stations_broken: String, pub stations_broken: String,
...@@ -9,18 +11,8 @@ pub struct Stats { ...@@ -9,18 +11,8 @@ pub struct Stats {
pub countries: String, pub countries: String,
} }
impl Stats{ impl RestPath<()> for Stats {
pub fn print(&self){ fn get_path(_: ()) -> Result<String,Error> {
println!("Stations: {} ({} broken)\nTags: {}\n\ Ok(format!("webservice/json/stats"))
Languages: {}\n\
Countries: {}\n\
Clicks: {} last hour / {} last day",
self.stations,
self.stations_broken,
self.tags,
self.languages,
self.countries,
self.clicks_last_hour,
self.clicks_last_day);
} }
} }
\ No newline at end of file
#[derive(Deserialize)] use restson::{RestClient,RestPath,Error};
#[derive(Serialize, Deserialize, Debug)]
pub struct Tag { pub struct Tag {
pub name: String,
pub value: String, pub value: String,
pub stationcount: String, pub stationcount: String,
} }
\ No newline at end of file
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum TagResponse {
Tags(Vec<Tag>),
Tag(Tag),
}
impl RestPath<()> for TagResponse {
fn get_path(_: ()) -> Result<String,Error> {
Ok(format!("webservice/json/tags"))
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment