1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
//! ## Icecast Server Status
//! Decode server status from an [icecast server](https://icecast.org/docs/icecast-trunk/server_stats/).
//!
//! ### Usage example
//! ```no_run
//! use icecast_stats::fetch;
//! 
//! let url_str = "https://stream.example.com:8000/somestream";
//! let stats = fetch(url_str).unwrap();
//! println!("{:#?}", stats);
//! ```
//! 
//! ### Example for a full icecast stats json file
//! ```json
//! {
//!     "icestats": {
//!         "admin": "icemaster@localhost",
//!         "host": "localhost",
//!         "location": "Earth",
//!         "server_id": "Icecast 2.4.4",
//!         "server_start": "Fri, 09 Apr 2021 21:49:50 +0200",
//!         "server_start_iso8601": "2021-04-09T21:49:50+0200",
//!         "source": {
//!             "audio_bitrate": 128000,
//!             "audio_channels": 1,
//!             "audio_samplerate": 48000,
//!             "bitrate": 128,
//!             "genre": "various",
//!             "ice-bitrate": 128,
//!             "listener_peak": 1,
//!             "listeners": 0,
//!             "listenurl": "http://localhost:8000/test2",
//!             "server_description": "Unspecified description",
//!             "server_name": "Unspecified name",
//!             "server_type": "application/ogg",
//!             "stream_start": "Fri, 09 Apr 2021 21:49:52 +0200",
//!             "stream_start_iso8601": "2021-04-09T21:49:52+0200",
//!             "subtype": "Vorbis",
//!             "dummy": null
//!         }
//!     }
//! }
//! ```

mod icecast_stats;
mod icecast_source;

use reqwest::blocking::get;
use std::error::Error;
use url::ParseError;
pub use url::Url;

pub use crate::icecast_stats::IcecastStats;
pub use crate::icecast_stats::IcecastStatsRoot;
pub use crate::icecast_source::IcecastStatsSource;

/// Try to generate an icecast status url from any url to the same server
/// 
/// Example:
/// ```rust
/// use icecast_stats::Url;
/// use icecast_stats::generate_icecast_stats_url;
/// 
/// let url_str = "https://stream.example.com:8000/somestream";
/// let url = Url::parse(url_str).unwrap();
/// let stats_url = generate_icecast_stats_url(url).unwrap();
/// assert!(stats_url.to_string().eq("https://stream.example.com:8000/status-json.xsl"));
/// ```
pub fn generate_icecast_stats_url(base: Url) -> Result<Url, Box<dyn Error>> {
    let host: String = base.host().ok_or(ParseError::EmptyHost)?.to_string();
    let newu = match base.scheme() {
        "http" => Url::parse(&format!(
            "http://{hostname}:{port}/status-json.xsl",
            hostname = host,
            port = base.port().unwrap_or(80)
        )),
        "https" => Url::parse(&format!(
            "https://{hostname}:{port}/status-json.xsl",
            hostname = host,
            port = base.port().unwrap_or(443)
        )),
        _ => Err(ParseError::EmptyHost),
    }?;
    Ok(newu)
}

/// Fetch icecast status information from server
/// 
/// This is a shorthand for
/// * generate_icecast_stats_url()
/// * downloading json
/// * parsing json
/// 
/// Example:
/// ```no_run
/// use icecast_stats::fetch;
/// 
/// let url_str = "https://stream.example.com:8000/somestream";
/// let stats = fetch(url_str).unwrap();
/// ```
pub fn fetch(url: &str) -> Result<IcecastStats, Box<dyn Error>> {
    let base_url = Url::parse(url)?;
    let url = generate_icecast_stats_url(base_url)?;
    let resp = get(url.to_string())?;
    let j: icecast_stats::IcecastStatsRoot = resp.json()?;
    Ok(j.icestats)
}