use crate::client::{AuthenticatedClient, DeviceClient};
use crate::episode::EpisodeActionType;
use crate::error::Error;
use crate::subscription::Podcast;
use chrono::naive::NaiveDateTime;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use url::Url;
#[serde(rename_all = "lowercase")]
#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum DeviceType {
Desktop,
Laptop,
Mobile,
Server,
Other,
}
#[derive(Deserialize, Serialize, Debug, Clone, Eq)]
pub struct Device {
pub id: String,
pub caption: String,
#[serde(rename(serialize = "type", deserialize = "type"))]
pub device_type: DeviceType,
pub subscriptions: u16,
}
#[derive(Serialize)]
pub(crate) struct DeviceData {
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) caption: Option<String>,
#[serde(rename(serialize = "type", deserialize = "type"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) device_type: Option<DeviceType>,
}
#[derive(Serialize, Deserialize)]
pub struct EpisodeUpdate {
pub title: String,
pub url: Url,
pub podcast_title: String,
pub podcast_url: Url,
pub description: String,
pub website: Url,
pub mygpo_link: Url,
pub released: NaiveDateTime,
pub status: Option<EpisodeActionType>,
}
#[derive(Serialize, Deserialize)]
pub struct DeviceUpdates {
pub add: Vec<Podcast>,
pub rem: Vec<Url>,
pub updates: Vec<EpisodeUpdate>,
pub timestamp: u64,
}
pub trait UpdateDeviceData {
fn update_device_data<T: Into<Option<String>>, U: Into<Option<DeviceType>>>(
&self,
caption: T,
device_type: U,
) -> Result<(), Error>;
}
pub trait ListDevices {
fn list_devices(&self) -> Result<Vec<Device>, Error>;
}
pub trait GetDeviceUpdates {
fn get_device_updates(&self, since: u64, include_actions: bool)
-> Result<DeviceUpdates, Error>;
}
impl UpdateDeviceData for DeviceClient {
fn update_device_data<T: Into<Option<String>>, U: Into<Option<DeviceType>>>(
&self,
caption: T,
device_type: U,
) -> Result<(), Error> {
let input = DeviceData {
caption: caption.into(),
device_type: device_type.into(),
};
self.post(
&format!(
"https://gpodder.net/api/2/devices/{}/{}.json",
self.authenticated_client.username, self.device_id
),
&input,
)?;
Ok(())
}
}
impl ListDevices for AuthenticatedClient {
fn list_devices(&self) -> Result<Vec<Device>, Error> {
Ok(self
.get(&format!(
"https://gpodder.net/api/2/devices/{}.json",
self.username
))?
.json()?)
}
}
impl ListDevices for DeviceClient {
fn list_devices(&self) -> Result<Vec<Device>, Error> {
self.as_ref().list_devices()
}
}
impl GetDeviceUpdates for DeviceClient {
fn get_device_updates(
&self,
since: u64,
include_actions: bool,
) -> Result<DeviceUpdates, Error> {
let mut query_parameters: Vec<&(&str, &str)> = Vec::new();
let since_string = since.to_string();
let query_parameter_since = ("since", since_string.as_ref());
query_parameters.push(&query_parameter_since);
let include_actions_string = include_actions.to_string();
let query_parameter_include_actions = ("include_actions", include_actions_string.as_ref());
query_parameters.push(&query_parameter_include_actions);
Ok(self
.get_with_query(
&format!(
"https://gpodder.net/api/2/updates/{}/{}.json",
self.authenticated_client.username, self.device_id
),
&query_parameters,
)?
.json()?)
}
}
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Ord for Device {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl PartialOrd for Device {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} (id={})", self.device_type, self.caption, self.id)
}
}
#[cfg(test)]
mod tests {
use super::{Device, DeviceType};
use std::cmp::Ordering;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[test]
fn equal_device_means_equal_hash() {
let device1 = Device {
id: String::from("abcdef"),
caption: String::from("gPodder on my Lappy"),
device_type: DeviceType::Laptop,
subscriptions: 27,
};
let device2 = Device {
id: String::from("abcdef"),
caption: String::from("unnamed"),
device_type: DeviceType::Other,
subscriptions: 1,
};
assert_eq!(device1, device2);
assert_eq!(device1.partial_cmp(&device2), Some(Ordering::Equal));
let mut hasher1 = DefaultHasher::new();
device1.hash(&mut hasher1);
let mut hasher2 = DefaultHasher::new();
device2.hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish());
}
#[test]
fn not_equal_devices_have_non_equal_ordering() {
let device1 = Device {
id: String::from("abcdef"),
caption: String::from("gPodder on my Lappy"),
device_type: DeviceType::Laptop,
subscriptions: 27,
};
let device2 = Device {
id: String::from("phone-au90f923023.203f9j23f"),
caption: String::from("My Phone"),
device_type: DeviceType::Mobile,
subscriptions: 5,
};
assert_ne!(device1, device2);
assert_eq!(device1.partial_cmp(&device2), Some(Ordering::Less));
let mut hasher1 = DefaultHasher::new();
device1.hash(&mut hasher1);
let mut hasher2 = DefaultHasher::new();
device2.hash(&mut hasher2);
assert_ne!(hasher1.finish(), hasher2.finish());
}
#[test]
fn display() {
let device = Device {
id: String::from("abcdef"),
caption: String::from("gPodder on my Lappy"),
device_type: DeviceType::Laptop,
subscriptions: 27,
};
assert_eq!(
"Laptop gPodder on my Lappy (id=abcdef)".to_owned(),
format!("{}", device)
);
}
}