added station actions

This commit is contained in:
Alex 2022-05-06 23:18:52 +02:00
parent 5ba1aa492e
commit adf4c5a49f
9 changed files with 255 additions and 51 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "radiobrowser" name = "radiobrowser"
version = "0.2.0" version = "0.3.0"
authors = ["segler_alex <segler_alex@web.de>"] authors = ["segler_alex <segler_alex@web.de>"]
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -16,15 +16,19 @@ async-std = { version = "1.11.0", features = ["attributes", "tokio1"] }
async-std-resolver = "0.21.2" async-std-resolver = "0.21.2"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
futures = { version = "0.3.21" } futures = { version = "0.3.21" }
log = { version = "0.4.16" } log = { version = "0.4.17" }
rand = { version = "0.8.5" } rand = { version = "0.8.5" }
reqwest = { version = "0.11.10", features = ["json"] } reqwest = { version = "0.11.10", features = ["json"] }
serde = { version = "1.0.136", features = ["derive"] } serde = { version = "1.0.137", features = ["derive"] }
[features] [features]
default = ["blocking"] default = []
blocking = [] blocking = []
[[bin]]
name = "test"
required-features = ["blocking"]
[badges] [badges]
# The `maintenance` table indicates the status of the maintenance of # The `maintenance` table indicates the status of the maintenance of
# the crate. This may be used by a registry, but is currently not # the crate. This may be used by a registry, but is currently not

View file

@ -1,12 +1,45 @@
# Radiobrowser Lib Rust # Radiobrowser Lib Rust
Client library for radio-browser.info and other radio-browser-rust servers Client library for radio-browser.info and other radio-browser-rust servers
## Getting started ## Features
- [x] Async / Blocking API
- [x] Clean query api with builder pattern
- [x] Countries, languages, tags, stations, serverconfig
- [x] Server statistics
- [x] Station actions: click, vote
- [ ] Add streams
## Getting started (Blocking)
### Example:
It needs to have the feature "blocking" enabled.
Cargo.toml entry:
```toml
radiobrowser = { version = "*", features = ["blocking"] }
```
```rust
use radiobrowser::blocking::RadioBrowserAPI;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let api = RadioBrowserAPI::new()?;
let servers = RadioBrowserAPI::get_servers()?;
println!("Servers: {:?}", servers);
let status = RadioBrowserAPI::get_server_status()?;
println!("Status: {:?}", status);
let countries = api.get_countries().send()?;
println!("Countries: {:?}", countries);
let stations = api.get_stations().name("jazz").send()?;
println!("Stations: {:?}", stations);
Ok(())
}
```
## Getting started (Async)
Cargo.toml entry Cargo.toml entry
```toml ```toml
radiobrowser = "*" radiobrowser = "*"
``` ```
Example: ### Example:
```rust ```rust
use radiobrowser::RadioBrowserAPI; use radiobrowser::RadioBrowserAPI;
use radiobrowser::StationOrder; use radiobrowser::StationOrder;

View file

@ -1,3 +1,6 @@
use crate::ApiStationClickResult;
use crate::ApiStationVoteResult;
use crate::ApiStatus;
use crate::external::post_api; use crate::external::post_api;
use crate::ApiConfig; use crate::ApiConfig;
use crate::CountrySearchBuilder; use crate::CountrySearchBuilder;
@ -18,6 +21,21 @@ use async_std_resolver::proto::rr::RecordType;
use async_std_resolver::proto::xfer::DnsRequestOptions; use async_std_resolver::proto::xfer::DnsRequestOptions;
use async_std_resolver::{config, resolver}; use async_std_resolver::{config, resolver};
/// RadioBrowser client for async communication
///
/// It uses crate:async_std
///
/// Example
/// ```rust
/// use std::error::Error;
/// use radiobrowser::RadioBrowserAPI;
/// #[async_std::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// let mut api = RadioBrowserAPI::new().await?;
/// Ok(())
/// }
/// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RadioBrowserAPI { pub struct RadioBrowserAPI {
servers: Vec<String>, servers: Vec<String>,
@ -25,6 +43,9 @@ pub struct RadioBrowserAPI {
} }
impl RadioBrowserAPI { impl RadioBrowserAPI {
/// Create a new instance of a radiobrowser api client.
/// It will fetch a list of radiobrowser server with get_servers()
/// and save it internally.
pub async fn new() -> Result<Self, Box<dyn Error>> { pub async fn new() -> Result<Self, Box<dyn Error>> {
Ok(RadioBrowserAPI { Ok(RadioBrowserAPI {
servers: RadioBrowserAPI::get_servers().await?, servers: RadioBrowserAPI::get_servers().await?,
@ -54,6 +75,20 @@ impl RadioBrowserAPI {
Ok(self.post_api("/json/config").await?) Ok(self.post_api("/json/config").await?)
} }
pub async fn get_server_status(&mut self) -> Result<ApiStatus, Box<dyn Error>> {
Ok(self.post_api("/json/stats").await?)
}
/// Add a click to a station found by stationuuid
pub async fn station_click<P: AsRef<str>>(&mut self, stationuuid: P) -> Result<ApiStationClickResult, Box<dyn Error>> {
Ok(self.post_api(format!("/json/url/{}",stationuuid.as_ref())).await?)
}
/// Add a vote to a station found by a stationuuid
pub async fn station_vote<P: AsRef<str>>(&mut self, stationuuid: P) -> Result<ApiStationVoteResult, Box<dyn Error>> {
Ok(self.post_api(format!("/json/vote/{}",stationuuid.as_ref())).await?)
}
pub fn get_stations(&self) -> StationSearchBuilder { pub fn get_stations(&self) -> StationSearchBuilder {
StationSearchBuilder::new(self.clone()) StationSearchBuilder::new(self.clone())
} }

38
src/bin/test-async.rs Normal file
View file

@ -0,0 +1,38 @@
use futures::join;
use radiobrowser::RadioBrowserAPI;
use radiobrowser::StationOrder;
use std::error::Error;
#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
let mut api = RadioBrowserAPI::new().await?;
let countries = api.get_countries().send();
let languages = api.get_languages().send();
let tags = api.get_tags().filter("jazz").send();
let stations = api
.get_stations()
.name("jazz")
.reverse(true)
.order(StationOrder::Clickcount)
.send();
let mut api2 = api.clone();
let config = api.get_server_config();
let stats = api2.get_server_status();
let (stations, config, countries, languages, tags, stats) = join!(stations, config, countries, languages, tags, stats);
println!("Config: {:#?}", config?);
println!("Status: {:#?}", stats?);
println!("Countries found: {}", countries?.len());
println!("Languages found: {}", languages?.len());
let tags = tags?;
println!("Tags found: {}", tags.len());
println!("{:?}", tags);
let stations = stations?;
println!("Stations found: {}", stations.len());
let vote_result = api.station_vote(&stations[0].stationuuid).await?;
println!("Stations voted result: {:?}", vote_result);
let click_result = api.station_click(&stations[0].stationuuid).await?;
println!("Stations clicked result: {:?}", click_result);
Ok(())
}

View file

@ -1,45 +1,21 @@
/*
use futures::join;
use radiobrowser::RadioBrowserAPI;
use radiobrowser::StationOrder;
use std::error::Error;
#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
let mut api = RadioBrowserAPI::new().await?;
let countries = api.get_countries().send();
let languages = api.get_languages().send();
let tags = api.get_tags().filter("jazz").send();
let stations = api
.get_stations()
.name("jazz")
.reverse(true)
.order(StationOrder::Clickcount)
.send();
let config = api.get_server_config();
let (stations, config, countries, languages, tags) = join!(stations, config, countries, languages, tags);
println!("Config: {:#?}", config?);
println!("Countries found: {}", countries?.len());
println!("Languages found: {}", languages?.len());
let tags = tags?;
println!("Tags found: {}", tags.len());
println!("{:?}", tags);
println!("Stations found: {}", stations?.len());
Ok(())
}
*/
use radiobrowser::blocking::RadioBrowserAPI; use radiobrowser::blocking::RadioBrowserAPI;
use std::error::Error; use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let api = RadioBrowserAPI::new()?; let mut api = RadioBrowserAPI::new()?;
let servers = RadioBrowserAPI::get_servers()?; let servers = RadioBrowserAPI::get_servers()?;
println!("Servers: {:?}", servers); println!("Servers: {:?}", servers);
let status = api.get_server_status()?;
println!("Status: {:#?}", status);
let config = api.get_server_config()?;
println!("Config: {:#?}", config);
let countries = api.get_countries().send()?; let countries = api.get_countries().send()?;
println!("Countries: {:?}", countries); println!("Countries: {:?}", countries.len());
let stations = api.get_stations().name("jazz").send()?; let stations = api.get_stations().name("jazz").send()?;
println!("Stations: {:?}", stations); println!("Stations with name containing 'jazz': {:?}", stations.len());
let vote_result = api.station_vote(&stations[0].stationuuid)?;
println!("Stations voted result: {:?}", vote_result);
let click_result = api.station_click(&stations[0].stationuuid)?;
println!("Stations clicked result: {:?}", click_result);
Ok(()) Ok(())
} }

View file

@ -2,6 +2,10 @@ use crate::blocking::stationsearchbuilder::StationSearchBuilder;
use crate::blocking::CountrySearchBuilder; use crate::blocking::CountrySearchBuilder;
use crate::blocking::LanguageSearchBuilder; use crate::blocking::LanguageSearchBuilder;
use crate::blocking::TagSearchBuilder; use crate::blocking::TagSearchBuilder;
use crate::ApiStationClickResult;
use crate::ApiStationVoteResult;
use crate::ApiStatus;
use crate::ApiConfig;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use std::collections::HashMap; use std::collections::HashMap;
@ -20,6 +24,24 @@ impl RadioBrowserAPI {
.map(|api| RadioBrowserAPI { api }) .map(|api| RadioBrowserAPI { api })
} }
pub fn get_server_status(&mut self) -> Result<ApiStatus, Box<dyn Error>> {
task::block_on(async { self.api.get_server_status().await })
}
pub fn get_server_config(&mut self) -> Result<ApiConfig, Box<dyn Error>> {
task::block_on(async { self.api.get_server_config().await })
}
/// Add a click to a station found by stationuuid
pub fn station_click<P: AsRef<str>>(&mut self, stationuuid: P) -> Result<ApiStationClickResult, Box<dyn Error>> {
task::block_on(async { self.api.station_click(stationuuid).await })
}
/// Add a vote to a station found by a stationuuid
pub fn station_vote<P: AsRef<str>>(&mut self, stationuuid: P) -> Result<ApiStationVoteResult, Box<dyn Error>> {
task::block_on(async { self.api.station_vote(stationuuid).await })
}
pub fn get_stations(&self) -> StationSearchBuilder { pub fn get_stations(&self) -> StationSearchBuilder {
StationSearchBuilder::new(self.api.get_stations()) StationSearchBuilder::new(self.api.get_stations())
} }

View file

@ -1,10 +1,11 @@
pub mod api; mod api;
pub mod countrysearchbuilder; mod countrysearchbuilder;
pub mod languagesearchbuilder; mod languagesearchbuilder;
pub mod stationsearchbuilder; mod stationsearchbuilder;
pub mod tagsearchbuilder; mod tagsearchbuilder;
pub use api::RadioBrowserAPI; pub use api::RadioBrowserAPI;
pub use countrysearchbuilder::CountrySearchBuilder; pub use countrysearchbuilder::CountrySearchBuilder;
pub use languagesearchbuilder::LanguageSearchBuilder; pub use languagesearchbuilder::LanguageSearchBuilder;
pub use tagsearchbuilder::TagSearchBuilder; pub use tagsearchbuilder::TagSearchBuilder;
pub use stationsearchbuilder::StationSearchBuilder;

View file

@ -51,6 +51,7 @@
//! ``` //! ```
mod api; mod api;
#[doc()]
#[cfg(feature = "blocking")] #[cfg(feature = "blocking")]
pub mod blocking; pub mod blocking;
mod external; mod external;
@ -66,7 +67,13 @@ pub use structs::ApiCountry;
pub use structs::ApiLanguage; pub use structs::ApiLanguage;
pub use structs::ApiStation; pub use structs::ApiStation;
pub use structs::ApiStreamingServer; pub use structs::ApiStreamingServer;
pub use structs::ApiStationClick;
pub use structs::ApiStationHistory;
pub use structs::ApiTag; pub use structs::ApiTag;
pub use structs::ApiStatus;
pub use structs::ApiStationAddResult;
pub use structs::ApiStationClickResult;
pub use structs::ApiStationVoteResult;
pub use stationsearchbuilder::StationSearchBuilder; pub use stationsearchbuilder::StationSearchBuilder;
pub use stationsearchbuilder::StationOrder; pub use stationsearchbuilder::StationOrder;
pub use countrysearchbuilder::CountrySearchBuilder; pub use countrysearchbuilder::CountrySearchBuilder;

View file

@ -1,7 +1,48 @@
use serde::Deserialize;
use chrono::DateTime; use chrono::DateTime;
use chrono::Utc; use chrono::Utc;
use serde::Deserialize;
/// Radiobrowser status and statistical information of single server.
#[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiStatus {
pub supported_version: u32,
pub software_version: Option<String>,
pub status: String,
pub stations: u64,
pub stations_broken: u64,
pub tags: u64,
pub clicks_last_hour: u64,
pub clicks_last_day: u64,
pub languages: u64,
pub countries: u64,
}
#[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiStationAddResult {
pub ok: bool,
pub message: String,
pub uuid: String,
}
#[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiStationVoteResult {
pub ok: bool,
pub message: String,
}
#[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiStationClickResult {
pub ok: bool,
pub message: String,
}
#[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiCodec {
name: String,
stationcount: u64,
}
/// A single station entry
#[derive(PartialEq, Deserialize, Debug)] #[derive(PartialEq, Deserialize, Debug)]
pub struct ApiStation { pub struct ApiStation {
pub changeuuid: String, pub changeuuid: String,
@ -42,6 +83,51 @@ pub struct ApiStation {
pub has_extended_info: Option<bool>, pub has_extended_info: Option<bool>,
} }
/// A single historical entry for a station
#[derive(PartialEq, Deserialize, Debug)]
pub struct ApiStationHistory {
pub changeuuid: String,
pub stationuuid: String,
pub name: String,
pub url: String,
pub homepage: String,
pub favicon: String,
pub tags: String,
pub country: String,
pub countrycode: String,
pub state: String,
pub language: String,
pub languagecodes: Option<String>,
pub votes: i32,
pub lastchangetime: String,
pub lastchangetime_iso8601: Option<DateTime<Utc>>,
pub geo_lat: Option<f64>,
pub geo_long: Option<f64>,
}
/// A click event for a station
#[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiStationClick {
pub stationuuid: String,
pub clickuuid: String,
pub clicktimestamp_iso8601: Option<DateTime<Utc>>,
pub clicktimestamp: String,
}
/// A single step of a check action for a station
#[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiStationCheckStep {
pub stepuuid: String,
pub parent_stepuuid: Option<String>,
pub checkuuid: String,
pub stationuuid: String,
pub url: String,
pub urltype: Option<String>,
pub error: Option<String>,
pub creation_iso8601: DateTime<Utc>,
}
/// A single country
#[derive(PartialEq, Eq, Deserialize, Debug)] #[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiCountry { pub struct ApiCountry {
pub name: String, pub name: String,
@ -49,6 +135,7 @@ pub struct ApiCountry {
pub stationcount: u32, pub stationcount: u32,
} }
/// A single language
#[derive(PartialEq, Eq, Deserialize, Debug)] #[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiLanguage { pub struct ApiLanguage {
pub name: String, pub name: String,
@ -56,13 +143,14 @@ pub struct ApiLanguage {
pub stationcount: u32, pub stationcount: u32,
} }
/// A single tag
#[derive(PartialEq, Eq, Deserialize, Debug)] #[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiTag { pub struct ApiTag {
pub name: String, pub name: String,
pub stationcount: u32, pub stationcount: u32,
} }
#[derive(PartialEq, Deserialize, Debug)] #[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiStreamingServer { pub struct ApiStreamingServer {
pub uuid: String, pub uuid: String,
pub url: String, pub url: String,
@ -71,7 +159,7 @@ pub struct ApiStreamingServer {
pub error: Option<String>, pub error: Option<String>,
} }
#[derive(Debug, Deserialize)] #[derive(PartialEq, Eq, Deserialize, Debug)]
pub struct ApiConfig { pub struct ApiConfig {
pub check_enabled: bool, pub check_enabled: bool,
pub prometheus_exporter_enabled: bool, pub prometheus_exporter_enabled: bool,