187 lines
5.5 KiB
Rust
187 lines
5.5 KiB
Rust
|
use axum::{
|
||
|
extract::DefaultBodyLimit,
|
||
|
extract::State,
|
||
|
http::{header::*, Method, StatusCode},
|
||
|
response::IntoResponse,
|
||
|
routing::{get, post},
|
||
|
Json, Router,
|
||
|
};
|
||
|
use mpd::Client;
|
||
|
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||
|
use serde_json::json;
|
||
|
use std::sync::Arc;
|
||
|
use tower_http::cors::CorsLayer;
|
||
|
|
||
|
use crate::Context;
|
||
|
use crate::{
|
||
|
config,
|
||
|
config::{Station, StationId, StationStatus},
|
||
|
};
|
||
|
|
||
|
pub fn routes(state: Arc<Context>) -> Router {
|
||
|
let cors = CorsLayer::new()
|
||
|
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
|
||
|
.allow_headers(vec![ORIGIN, AUTHORIZATION, ACCEPT, CONTENT_TYPE, COOKIE])
|
||
|
.allow_origin([
|
||
|
"http://localhost:54605".parse().unwrap(),
|
||
|
"http://localhost:5173".parse().unwrap(),
|
||
|
])
|
||
|
.allow_credentials(true);
|
||
|
|
||
|
Router::new()
|
||
|
.route("/healthcheck", get(healthcheck))
|
||
|
.route("/stations", get(stations))
|
||
|
.route("/status", get(status))
|
||
|
.layer(cors)
|
||
|
.fallback(fallback)
|
||
|
.with_state(state)
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, serde::Serialize)]
|
||
|
pub enum Playback {
|
||
|
Stopped,
|
||
|
Playing,
|
||
|
Paused,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, serde::Serialize)]
|
||
|
pub struct SongInfo {
|
||
|
pub artist: Option<String>,
|
||
|
pub title: Option<String>,
|
||
|
pub tags: Vec<(String, String)>,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, serde::Serialize)]
|
||
|
pub struct StationInfo {
|
||
|
pub id: StationId,
|
||
|
pub name: String,
|
||
|
pub url: Option<String>,
|
||
|
pub status: config::StationStatus,
|
||
|
pub location: Option<String>,
|
||
|
pub genre: Option<String>,
|
||
|
pub playback: Option<Playback>,
|
||
|
}
|
||
|
|
||
|
impl Default for StationInfo {
|
||
|
fn default() -> Self {
|
||
|
StationInfo {
|
||
|
id: StationId(String::from("station")),
|
||
|
name: String::from("Station"),
|
||
|
url: None,
|
||
|
status: config::StationStatus::Receive,
|
||
|
location: None,
|
||
|
genre: None,
|
||
|
playback: None,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<Station> for StationInfo {
|
||
|
fn from(station: Station) -> Self {
|
||
|
StationInfo {
|
||
|
id: station.id,
|
||
|
name: station.name,
|
||
|
url: station.url,
|
||
|
status: station.status,
|
||
|
location: station.location,
|
||
|
genre: station.genre,
|
||
|
..StationInfo::default()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub async fn stations(State(state): State<Arc<Context>>) -> (StatusCode, Json<Vec<StationInfo>>) {
|
||
|
match &state.config.stations {
|
||
|
Some(stations) => {
|
||
|
let mut stations_info: Vec<StationInfo> = Vec::with_capacity(stations.len());
|
||
|
|
||
|
for station in stations {
|
||
|
stations_info.push(match station.status {
|
||
|
StationStatus::Online | StationStatus::Offline => {
|
||
|
StationInfo::from(station.clone())
|
||
|
}
|
||
|
StationStatus::Receive => {
|
||
|
let connection =
|
||
|
Client::connect(format!("{}:{}", station.host, station.port));
|
||
|
|
||
|
if let Ok(mut client) = connection {
|
||
|
let mut info = StationInfo::from(station.clone());
|
||
|
|
||
|
if let Ok(status) = client.status() {
|
||
|
info.playback = match status.state {
|
||
|
mpd::State::Play => Some(Playback::Playing),
|
||
|
mpd::State::Stop => Some(Playback::Stopped),
|
||
|
mpd::State::Pause => Some(Playback::Paused),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
info
|
||
|
} else {
|
||
|
StationInfo::from(station.clone())
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
(StatusCode::OK, Json(stations_info))
|
||
|
}
|
||
|
None => (StatusCode::OK, Json(Vec::new())),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||
|
pub struct GetSongInfo {
|
||
|
pub id: StationId,
|
||
|
}
|
||
|
|
||
|
pub async fn status(
|
||
|
State(state): State<Arc<Context>>,
|
||
|
Json(body): Json<GetSongInfo>,
|
||
|
) -> (StatusCode, Json<Option<SongInfo>>) {
|
||
|
match &state.config.stations {
|
||
|
Some(stations) => {
|
||
|
for station in stations {
|
||
|
if station.id.0.eq(&body.id.0) {
|
||
|
let connection = Client::connect(format!("{}:{}", station.host, station.port));
|
||
|
|
||
|
if let Ok(mut client) = connection {
|
||
|
if let Ok(Some(current)) = client.currentsong() {
|
||
|
return (
|
||
|
StatusCode::OK,
|
||
|
Json(Some(SongInfo {
|
||
|
artist: current.artist,
|
||
|
title: current.title,
|
||
|
tags: current.tags,
|
||
|
})),
|
||
|
);
|
||
|
} else {
|
||
|
return (StatusCode::OK, Json(None));
|
||
|
}
|
||
|
} else {
|
||
|
return (StatusCode::OK, Json(None));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
(StatusCode::OK, Json(None))
|
||
|
}
|
||
|
None => (StatusCode::OK, Json(None)),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub async fn healthcheck() -> impl IntoResponse {
|
||
|
(
|
||
|
StatusCode::OK,
|
||
|
Json(json!({
|
||
|
"status": StatusCode::OK.to_string(),
|
||
|
})),
|
||
|
)
|
||
|
}
|
||
|
|
||
|
pub async fn fallback() -> impl IntoResponse {
|
||
|
(
|
||
|
StatusCode::NOT_FOUND,
|
||
|
Json(json!({
|
||
|
"status": StatusCode::NOT_FOUND.to_string(),
|
||
|
})),
|
||
|
)
|
||
|
}
|