195 lines
5.9 KiB
Rust
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(),
|
|
})),
|
|
)
|
|
}
|