Split routes up into smaller modules

This commit is contained in:
Sijmen 2023-07-13 00:35:54 +02:00
parent 3900a5c5c0
commit b8416b29dd
Signed by: vijfhoek
GPG key ID: DAF7821E067D9C48
8 changed files with 237 additions and 206 deletions

6
src/crate_version.rs Normal file
View file

@ -0,0 +1,6 @@
#[macro_export]
macro_rules! crate_version {
() => {
env!("CARGO_PKG_VERSION")
};
}

View file

@ -1,157 +1,6 @@
use std::{collections::HashMap, path::Path};
use askama::Template;
use percent_encoding::percent_decode_str;
use serde::Deserialize;
mod mpd; mod mpd;
mod routes;
macro_rules! crate_version { mod crate_version;
() => {
env!("CARGO_PKG_VERSION")
};
}
#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate;
#[derive(Deserialize, Default)]
#[serde(default)]
struct IndexQuery {
path: String,
}
async fn get_index(_req: tide::Request<()>) -> tide::Result {
Ok(askama_tide::into_response(&IndexTemplate))
}
#[derive(Template)]
#[template(path = "queue.html")]
struct QueueTemplate {
queue: Vec<mpd::QueueItem>,
}
async fn get_queue(_req: tide::Request<()>) -> tide::Result {
let queue = mpd::Mpd::connect().await?.playlist().await?;
let template = QueueTemplate { queue };
Ok(template.into())
}
#[derive(Template)]
#[template(path = "player.html")]
struct PlayerTemplate<'a> {
song: Option<&'a HashMap<String, String>>,
name: Option<String>,
state: &'a str,
elapsed: f32,
duration: f32,
}
async fn get_player(_req: tide::Request<()>) -> tide::Result {
let mut mpd = mpd::Mpd::connect().await?;
let song = mpd.command("currentsong").await?.into_hashmap();
let status = mpd.command("status").await?.into_hashmap();
let elapsed = status
.get("elapsed")
.and_then(|e| e.parse().ok())
.unwrap_or(0.0);
let duration = status
.get("duration")
.and_then(|e| e.parse().ok())
.unwrap_or(1.0);
let mut template = PlayerTemplate {
song: if song.is_empty() { None } else { Some(&song) },
name: None,
state: &status["state"],
elapsed,
duration,
};
if !song.is_empty() {
let name = song.get("Title").unwrap_or(&song["file"]).to_string();
template.name = Some(name);
}
Ok(template.into())
}
#[derive(Template)]
#[template(path = "browser.html")]
struct BrowserTemplate {
path: Vec<String>,
entries: Vec<mpd::Entry>,
}
async fn get_browser(req: tide::Request<()>) -> tide::Result {
let query: IndexQuery = req.query()?;
let path = percent_decode_str(&query.path).decode_utf8_lossy();
let entries = mpd::Mpd::connect().await?.ls(&path).await?;
let template = BrowserTemplate {
path: Path::new(&*path)
.iter()
.map(|s| s.to_string_lossy().to_string())
.collect(),
entries,
};
Ok(template.into())
}
#[derive(Deserialize)]
struct PostQueueQuery {
path: String,
#[serde(default)]
replace: bool,
#[serde(default)]
next: bool,
#[serde(default)]
play: bool,
}
async fn post_queue(req: tide::Request<()>) -> tide::Result {
let query: PostQueueQuery = req.query()?;
let path = percent_decode_str(&query.path).decode_utf8_lossy();
let mut mpd = mpd::Mpd::connect().await?;
if query.replace {
mpd.clear().await?;
}
if query.next {
mpd.add_pos(&path, "+0").await?;
} else {
mpd.add(&path).await?;
}
if query.play {
mpd.play().await?;
}
Ok("".into())
}
#[derive(Deserialize)]
struct DeleteQueueQuery {
#[serde(default)]
id: Option<u32>,
}
async fn delete_queue(req: tide::Request<()>) -> tide::Result {
let query: DeleteQueueQuery = req.query()?;
let mut mpd = mpd::Mpd::connect().await?;
if let Some(id) = query.id {
mpd.command(&format!("deleteid {id}")).await?;
} else {
mpd.command("clear").await?;
}
Ok("".into())
}
async fn post_play(_req: tide::Request<()>) -> tide::Result { async fn post_play(_req: tide::Request<()>) -> tide::Result {
mpd::Mpd::connect().await?.command("play").await?; mpd::Mpd::connect().await?.command("play").await?;
@ -173,51 +22,6 @@ async fn post_next(_req: tide::Request<()>) -> tide::Result {
Ok("".into()) Ok("".into())
} }
#[derive(Deserialize, Debug)]
struct UpdateQueueBody {
from: u32,
to: u32,
}
async fn post_queue_move(mut req: tide::Request<()>) -> tide::Result {
let body: UpdateQueueBody = req.body_json().await?;
let mut mpd = mpd::Mpd::connect().await?;
mpd.command(&format!("move {} {}", body.from, body.to))
.await?;
Ok("".into())
}
async fn get_art(req: tide::Request<()>) -> tide::Result {
let query: IndexQuery = req.query()?;
let path = percent_decode_str(&query.path).decode_utf8_lossy();
let mut mpd = mpd::Mpd::connect().await?;
let resp = if let Ok(art) = mpd.albumart(&path).await {
let mime = infer::get(&art)
.map(|k| k.mime_type())
.unwrap_or("application/octet-stream");
tide::Response::builder(tide::StatusCode::Ok)
.body(art)
.content_type(mime)
.header("cache-control", "max-age=3600")
} else if let Ok(art) = mpd.readpicture(&path).await {
let mime = infer::get(&art)
.map(|k| k.mime_type())
.unwrap_or("application/octet-stream");
tide::Response::builder(tide::StatusCode::Ok)
.body(art)
.content_type(mime)
.header("cache-control", "max-age=3600")
} else {
tide::Response::builder(tide::StatusCode::NotFound)
};
Ok(resp.into())
}
async fn sse(_req: tide::Request<()>, sender: tide::sse::Sender) -> tide::Result<()> { async fn sse(_req: tide::Request<()>, sender: tide::sse::Sender) -> tide::Result<()> {
// Update everything on connect // Update everything on connect
sender.send("playlist", "", None).await?; sender.send("playlist", "", None).await?;
@ -242,17 +46,17 @@ async fn main() -> tide::Result<()> {
let mut app = tide::new(); let mut app = tide::new();
app.with(tide_tracing::TraceMiddleware::new()); app.with(tide_tracing::TraceMiddleware::new());
app.at("/").get(get_index); app.at("/").get(routes::index::get_index);
app.at("/queue").get(get_queue); app.at("/player").get(routes::player::get_player);
app.at("/player").get(get_player); app.at("/browser").get(routes::browser::get_browser);
app.at("/art").get(get_art); app.at("/art").get(routes::art::get_art);
app.at("/browser").get(get_browser);
app.at("/sse").get(tide::sse::endpoint(sse)); app.at("/sse").get(tide::sse::endpoint(sse));
app.at("/queue").post(post_queue); app.at("/queue").get(routes::queue::get_queue);
app.at("/queue").delete(delete_queue); app.at("/queue").post(routes::queue::post_queue);
app.at("/queue/move").post(post_queue_move); app.at("/queue").delete(routes::queue::delete_queue);
app.at("/queue/move").post(routes::queue::post_queue_move);
app.at("/play").post(post_play); app.at("/play").post(post_play);
app.at("/pause").post(post_pause); app.at("/pause").post(post_pause);

40
src/routes/art.rs Normal file
View file

@ -0,0 +1,40 @@
use serde::Deserialize;
use crate::mpd;
use percent_encoding::percent_decode_str;
#[derive(Deserialize, Default)]
#[serde(default)]
struct ArtQuery {
path: String,
}
pub async fn get_art(req: tide::Request<()>) -> tide::Result {
let query: ArtQuery = req.query()?;
let path = percent_decode_str(&query.path).decode_utf8_lossy();
let mut mpd = mpd::Mpd::connect().await?;
let resp = if let Ok(art) = mpd.albumart(&path).await {
let mime = infer::get(&art)
.map(|k| k.mime_type())
.unwrap_or("application/octet-stream");
tide::Response::builder(tide::StatusCode::Ok)
.body(art)
.content_type(mime)
.header("cache-control", "max-age=3600")
} else if let Ok(art) = mpd.readpicture(&path).await {
let mime = infer::get(&art)
.map(|k| k.mime_type())
.unwrap_or("application/octet-stream");
tide::Response::builder(tide::StatusCode::Ok)
.body(art)
.content_type(mime)
.header("cache-control", "max-age=3600")
} else {
tide::Response::builder(tide::StatusCode::NotFound)
};
Ok(resp.into())
}

34
src/routes/browser.rs Normal file
View file

@ -0,0 +1,34 @@
use askama::Template;
use serde::Deserialize;
use crate::mpd;
use percent_encoding::percent_decode_str;
use std::path::Path;
#[derive(Template)]
#[template(path = "browser.html")]
struct BrowserTemplate {
path: Vec<String>,
entries: Vec<mpd::Entry>,
}
#[derive(Deserialize, Default)]
#[serde(default)]
struct BrowserQuery {
path: String,
}
pub async fn get_browser(req: tide::Request<()>) -> tide::Result {
let query: BrowserQuery = req.query()?;
let path = percent_decode_str(&query.path).decode_utf8_lossy();
let entries = mpd::Mpd::connect().await?.ls(&path).await?;
let template = BrowserTemplate {
path: Path::new(&*path)
.iter()
.map(|s| s.to_string_lossy().to_string())
.collect(),
entries,
};
Ok(template.into())
}

17
src/routes/index.rs Normal file
View file

@ -0,0 +1,17 @@
use askama::Template;
use serde::Deserialize;
use crate::crate_version;
#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate;
#[derive(Deserialize, Default)]
#[serde(default)]
struct IndexQuery {
path: String,
}
pub async fn get_index(_req: tide::Request<()>) -> tide::Result {
Ok(askama_tide::into_response(&IndexTemplate))
}

5
src/routes/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod index;
pub mod queue;
pub mod player;
pub mod browser;
pub mod art;

43
src/routes/player.rs Normal file
View file

@ -0,0 +1,43 @@
use askama::Template;
use crate::mpd;
use std::collections::HashMap;
#[derive(Template)]
#[template(path = "player.html")]
struct PlayerTemplate<'a> {
song: Option<&'a HashMap<String, String>>,
name: Option<String>,
state: &'a str,
elapsed: f32,
duration: f32,
}
pub async fn get_player(_req: tide::Request<()>) -> tide::Result {
let mut mpd = mpd::Mpd::connect().await?;
let song = mpd.command("currentsong").await?.into_hashmap();
let status = mpd.command("status").await?.into_hashmap();
let elapsed = status
.get("elapsed")
.and_then(|e| e.parse().ok())
.unwrap_or(0.0);
let duration = status
.get("duration")
.and_then(|e| e.parse().ok())
.unwrap_or(1.0);
let mut template = PlayerTemplate {
song: if song.is_empty() { None } else { Some(&song) },
name: None,
state: &status["state"],
elapsed,
duration,
};
if !song.is_empty() {
let name = song.get("Title").unwrap_or(&song["file"]).to_string();
template.name = Some(name);
}
Ok(template.into())
}

82
src/routes/queue.rs Normal file
View file

@ -0,0 +1,82 @@
use askama::Template;
use crate::mpd;
use serde::Deserialize;
use percent_encoding::percent_decode_str;
#[derive(Template)]
#[template(path = "queue.html")]
struct QueueTemplate {
queue: Vec<mpd::QueueItem>,
}
pub async fn get_queue(_req: tide::Request<()>) -> tide::Result {
let queue = mpd::Mpd::connect().await?.playlist().await?;
let template = QueueTemplate { queue };
Ok(template.into())
}
#[derive(Deserialize)]
struct PostQueueQuery {
path: String,
#[serde(default)]
replace: bool,
#[serde(default)]
next: bool,
#[serde(default)]
play: bool,
}
pub async fn post_queue(req: tide::Request<()>) -> tide::Result {
let query: PostQueueQuery = req.query()?;
let path = percent_decode_str(&query.path).decode_utf8_lossy();
let mut mpd = mpd::Mpd::connect().await?;
if query.replace {
mpd.clear().await?;
}
if query.next {
mpd.add_pos(&path, "+0").await?;
} else {
mpd.add(&path).await?;
}
if query.play {
mpd.play().await?;
}
Ok("".into())
}
#[derive(Deserialize)]
struct DeleteQueueQuery {
#[serde(default)]
id: Option<u32>,
}
pub async fn delete_queue(req: tide::Request<()>) -> tide::Result {
let query: DeleteQueueQuery = req.query()?;
let mut mpd = mpd::Mpd::connect().await?;
if let Some(id) = query.id {
mpd.command(&format!("deleteid {id}")).await?;
} else {
mpd.command("clear").await?;
}
Ok("".into())
}
#[derive(Deserialize, Debug)]
struct UpdateQueueBody {
from: u32,
to: u32,
}
pub async fn post_queue_move(mut req: tide::Request<()>) -> tide::Result {
let body: UpdateQueueBody = req.body_json().await?;
let mut mpd = mpd::Mpd::connect().await?;
mpd.command(&format!("move {} {}", body.from, body.to))
.await?;
Ok("".into())
}