feat(media_server): Made the module into a lib and started a new binary crate

This commit is contained in:
kale 2024-08-05 00:46:48 +02:00
parent 37d047c22f
commit 4c4593f56e
Signed by: kalmenn
GPG key ID: F500055C44BC3834
7 changed files with 62 additions and 54 deletions

View file

@ -1,13 +1,14 @@
[package]
name = "server"
description = "The binary that should be run to both host a personal library and serve media files"
version = "0.1.0"
edition = "2021"
[dependencies]
media_server = { path = "../media_server/" }
warp = "0.3.7"
tokio = { version = "1.37.0", features = ["full"] }
futures-util = "0.3.30"
clap = { version = "4.5.4", features = ["derive"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
bytes = "1.6.0"

View file

@ -1,39 +0,0 @@
use std::borrow::Cow;
use hyper::StatusCode;
use tracing::warn;
use warp::{
hyper,
reject::{Reject, Rejection},
reply::Response,
};
#[derive(Debug)]
pub struct InternalServerError(pub Cow<'static, str>);
impl Reject for InternalServerError {}
#[derive(Debug)]
pub struct NotFoundError(pub Cow<'static, str>);
impl Reject for NotFoundError {}
pub async fn handle_rejection(err: Rejection) -> Result<Response, std::convert::Infallible> {
let (status_code, body): (StatusCode, Cow<'static, str>) =
if err.is_not_found() || err.find::<NotFoundError>().is_some() {
(
StatusCode::NOT_FOUND,
err.find::<NotFoundError>()
.map_or("".into(), |reason| reason.0.clone()),
)
} else if let Some(InternalServerError(reason)) = err.find::<InternalServerError>() {
(StatusCode::INTERNAL_SERVER_ERROR, reason.clone())
} else {
warn!("unhandled rejection: {:?}", err);
(StatusCode::INTERNAL_SERVER_ERROR, "".into())
};
Ok(hyper::Response::builder()
.header("Content-Type", "text/plain")
.status(status_code)
.body(body.into())
.expect("building the response should never fail"))
}

View file

@ -1,7 +1,4 @@
pub mod error;
pub mod media_server;
use error::handle_rejection;
use media_server::error::handle_rejection;
use std::path::PathBuf;
use clap::Parser as _;

View file

@ -1,89 +0,0 @@
use crate::error::{InternalServerError, NotFoundError};
use hyper::{body, Body};
use std::path::Path;
use bytes::Bytes;
use tokio::io::AsyncReadExt;
use tracing::{debug, span, warn, Instrument, Level};
use warp::{
filters::{any::any, path::param},
http::HeaderMap,
hyper,
reject::Rejection,
Filter,
};
pub fn serve_local_tracks(
music_dir: &'_ Path,
) -> impl Filter<Extract = (warp::reply::Response,), Error = Rejection> + Clone + '_ {
any()
.map(move || music_dir)
.and(warp::header::headers_cloned())
.and(param())
.and_then(handle_file_request)
}
pub async fn handle_file_request(
music_dir: &Path,
_headers: HeaderMap,
// TODO: request files based off of their MusicBrainz Identifier instead
track_name: String,
) -> Result<warp::reply::Response, Rejection> {
let mut location = music_dir.to_owned();
// FIXME: for now, file paths need to be URL safe to be able to be requested
location.push(track_name);
let (Ok(file), Some(file_name)) = (
tokio::fs::File::options().read(true).open(&location).await,
location.file_name().map(|name| name.to_string_lossy()),
) else {
return Err(NotFoundError(
format!("The requested song could not be found on disk. Tried loading {location:?}")
.into(),
)
.into());
};
// TODO: handle range requests
let (sender, body) = Body::channel();
tokio::task::spawn(stream_file(sender, file).instrument(span!(
Level::DEBUG,
"stream_file",
file_name = file_name.as_ref()
)));
if let Ok(response) = hyper::Response::builder()
.header("Content-Type", "audio/mpeg") // TODO: Infer from filetype
// .header("Accept-Ranges", "bytes") // TODO: handle range requests
.body(body)
{
Ok(response)
} else {
Err(InternalServerError("Failed to build Response".into()).into())
}
}
pub async fn stream_file(mut dest: body::Sender, mut file: tokio::fs::File) {
debug!("Starting stream");
let mut buf = [0u8; 512];
loop {
let bytes_read = match file.read(&mut buf).await {
Ok(0) => break debug!("Done streaming the file"),
Ok(bytes_read) => bytes_read,
Err(err) => break warn!("Couldn't read part of the file. Got err: {err}"),
};
let res = dest
.send_data(Bytes::copy_from_slice(&buf[..bytes_read]))
.await;
if res.is_err() {
warn!("Connection dropped by peer");
break;
}
}
}