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

rewrite library using restson

parent 2b5a4f62
......@@ -4,3 +4,5 @@
# These are backup files generated by rustfmt
**/*.rs.bk
.buildconfig
\ No newline at end of file
This diff is collapsed.
......@@ -6,14 +6,7 @@ version = "0.0.1"
description = "Rust API wrapper for radio-browser.info"
[dependencies]
reqwest = "0.8.5"
serde = "1.0.43"
serde_json = "1.0"
serde_derive = "1.0.43"
restson = "^0.3.0"
serde = "^1.0"
serde_derive = "^1.0"
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::collections::HashMap;
use std::sync::mpsc::Sender;
......@@ -13,133 +5,81 @@ use std::sync::mpsc::channel;
use std::thread;
use std::rc::Rc;
use std::cell::RefCell;
use restson::{RestClient, Error};
#[derive(Deserialize)]
pub struct StationUrlResult{
url: String,
}
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,
}
use {Codec, Country, Language, State, Station, Stats, Tag};
use {CodecResponse, CountryResponse, LanguageResponse, StateResponse, StationResponse, TagResponse};
use PlayableStationUrl;
pub struct Client {
current_search_id: Rc<RefCell<u64>>,
rest_client: RestClient,
}
impl Client {
pub fn new() -> Client {
Client {
current_search_id: Rc::new(RefCell::new(0)),
}
}
pub fn create_reqwest_client() -> reqwest::Client{
let proxy: Option<String> = match env::var("http_proxy") {
Ok(proxy) => Some(proxy),
Err(_) => None,
pub fn new(base_url: &str) -> Client {
let mut rest_client = match RestClient::new(base_url){
Ok(rc) => rc,
Err(err) => panic!("Could not create rest_client!")
};
match proxy {
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(),
}
Client { rest_client }
}
pub fn get_all_languages(&self) -> Vec<Country>{
let url = format!("{}{}", BASE_URL, LANGUAGES);
Self::send_get_request(url).unwrap().json().unwrap()
pub fn get_station_by_id(&mut self, id: u32) -> Result<Station, Error>{
match self.rest_client.get(id)? {
StationResponse::Station(station) => Ok(station),
StationResponse::Stations(mut stations) => Ok(stations.pop().unwrap()),
}
pub fn get_all_countries(&self) -> Vec<Country>{
let url = format!("{}{}", BASE_URL, LANGUAGES);
Self::send_get_request(url).unwrap().json().unwrap()
}
pub fn get_all_states(&self) -> Vec<Country>{
let url = format!("{}{}", BASE_URL, STATES);
Self::send_get_request(url).unwrap().json().unwrap()
pub fn get_all_stations(&mut self) -> Result<Vec<Station>, Error>{
match self.rest_client.get(())? {
StationResponse::Stations(stations) => Ok(stations),
_ => Err(Error::InvalidValue),
}
pub fn get_all_tags(&self) -> Vec<Country>{
let url = format!("{}{}", BASE_URL, TAGS);
Self::send_get_request(url).unwrap().json().unwrap()
}
pub fn get_station_by_id(&self, id: i32) -> Result<Station,&str> {
let url = format!("{}{}{}", BASE_URL, STATION_BY_ID, id);
let mut result : Vec<Station> = Self::send_get_request(url).unwrap().json().unwrap();
if result.len() > 0 {
Ok(result.remove(0))
}else {
Err("ID points to an empty station")
pub fn get_all_codecs(&mut self) -> Result<Vec<Codec>, Error>{
match self.rest_client.get(())? {
CodecResponse::Codecs(codecs) => Ok(codecs),
_ => Err(Error::InvalidValue),
}
}
pub fn get_playable_station_url(station: &Station) -> String{
let url = format!("{}{}{}", BASE_URL, PLAYABLE_STATION_URL, station.id);
let result: StationUrlResult = Self::send_get_request(url).unwrap().json().unwrap();
result.url
pub fn get_all_countries(&mut self) -> Result<Vec<Country>, Error>{
match self.rest_client.get(())? {
CountryResponse::Countries(countries) => Ok(countries),
_ => Err(Error::InvalidValue),
}
}
pub fn search(&mut self, params: HashMap<String, String>, sender: Sender<ClientUpdate>){
// Generate a new search ID. It is possible, that the old thread is still running,
// while a new one already have started. With this ID we can check, if the search request is still up-to-date.
*self.current_search_id.borrow_mut() += 1;
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);
pub fn get_all_languages(&mut self) -> Result<Vec<Language>, Error>{
match self.rest_client.get(())? {
LanguageResponse::Languages(languages) => Ok(languages),
_ => Err(Error::InvalidValue),
}
}
match search_receiver.try_recv(){
Ok(stations) => {
sender.send(ClientUpdate::NewStations(stations));
Continue(false)
pub fn get_all_states(&mut self) -> Result<Vec<State>, Error>{
match self.rest_client.get(())? {
StateResponse::States(states) => Ok(states),
_ => Err(Error::InvalidValue),
}
Err(_) => Continue(true),
}
});
pub fn get_all_tags(&mut self) -> Result<Vec<Tag>, Error>{
match self.rest_client.get(())? {
TagResponse::Tags(tags) => Ok(tags),
_ => Err(Error::InvalidValue),
}
}
fn send_post_request(url: String, params: HashMap<String, String>) -> Result<reqwest::Response, reqwest::Error>{
debug!("Post request -> {:?} ({:?})", url, params);
let client = Self::create_reqwest_client();
client.post(&url).form(&params).send()
pub fn get_stats(&mut self) -> Result<Stats, Error>{
self.rest_client.get(())
}
fn send_get_request(url: String) -> Result<reqwest::Response, reqwest::Error>{
debug!("Get request -> {:?}", url);
let client = Self::create_reqwest_client();
client.get(&url).send()
pub fn get_playable_station_url(&mut self, id: u32) -> Result<String, Error>{
let result: PlayableStationUrl = self.rest_client.get(id)?;
Ok(result.url)
}
}
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 name: String,
pub value: String,
pub stationcount: String,
}
#[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 name: String,
pub value: String,
pub stationcount: String,
}
#[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 log;
#[macro_use] extern crate failure;
extern crate serde;
extern crate serde_json;
extern crate reqwest;
extern crate gtk; // TODO: Don't require gtk
extern crate glib;
extern crate restson;
mod station;
mod country;
mod codec;
mod state;
mod language;
mod tag;
mod stats;
mod client;
pub mod error;
pub mod client;
pub mod station;
pub mod country;
pub mod state;
pub mod language;
pub mod tag;
pub mod stats;
pub use station::Station as Station;
use station::PlayableStationUrl as PlayableStationUrl;
pub use country::Country as Country;
pub use codec::Codec as Codec;
pub use state::State as State;
pub use language::Language as Language;
pub use tag::Tag as Tag;
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 name: String,
pub value: String,
pub country: String,
pub stationcount: String,
}
#[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 name: String,
pub language: String,
......@@ -28,10 +30,40 @@ pub struct Station {
pub clicktrend: String,
}
impl Station{}
impl PartialEq for Station {
fn eq(&self, other: &Station) -> bool {
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 stations: String,
pub stations_broken: String,
......@@ -9,18 +11,8 @@ pub struct Stats {
pub countries: String,
}
impl Stats{
pub fn print(&self){
println!("Stations: {} ({} broken)\nTags: {}\n\
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);
impl RestPath<()> for Stats {
fn get_path(_: ()) -> Result<String,Error> {
Ok(format!("webservice/json/stats"))
}
}
#[derive(Deserialize)]
use restson::{RestClient,RestPath,Error};
#[derive(Serialize, Deserialize, Debug)]
pub struct Tag {
pub name: String,
pub value: String,
pub stationcount: String,
}
#[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