195 lines
5.9 KiB
Rust

use axum::{
extract::State,
http::{header::*, Method, StatusCode},
response::IntoResponse,
routing::{get, post},
Json, Router,
};
use mpd::Client;
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", post(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 duration: Option<u64>,
pub elapsed: Option<u64>,
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() {
let mut info = SongInfo {
artist: current.artist,
title: current.title,
duration: None,
elapsed: None,
tags: current.tags,
};
if let Ok(status) = client.status() {
if let Some(time) = status.time {
info.duration = Some(time.1.as_secs());
info.elapsed = Some(time.0.as_secs());
}
}
return (StatusCode::OK, Json(Some(info)));
} 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(),
})),
)
}