217 lines
6.7 KiB
Rust
217 lines
6.7 KiB
Rust
mod logging;
|
|
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use axum::body::BodyDataStream;
|
|
use axum::extract::{Request, State};
|
|
use axum::http::StatusCode;
|
|
use axum::Router;
|
|
use axum::routing::post;
|
|
use dotenv::{dotenv, var};
|
|
use futures::TryStreamExt;
|
|
use log::LevelFilter;
|
|
use systemd_journal_logger::JournalLog;
|
|
use tokio::fs::File;
|
|
use tokio::{fs, io};
|
|
use tokio::io::BufWriter;
|
|
use tokio_util::io::StreamReader;
|
|
use urlencoding::decode;
|
|
use crate::logging::Logging;
|
|
|
|
#[derive(Clone)]
|
|
struct App {
|
|
log: Logging,
|
|
directories: HashMap<String, String>
|
|
}
|
|
|
|
impl App {
|
|
pub fn init_directories(&mut self) {
|
|
let root_dir = match var("ROOT_DIRECTORY") {
|
|
Ok(dir) => {
|
|
self.log.info(format!("ROOT_DIRECTORY set to '{dir}'"));
|
|
dir
|
|
}
|
|
Err(e) => {
|
|
self.log.error(format!("ROOT_DIRECTORY not set: {e}. Aborting."));
|
|
panic!("ROOT_DIRECTORY not set: {e}. Aborting.");
|
|
}
|
|
};
|
|
|
|
let novel_dir = match var("NOVEL_DIRECTORY") {
|
|
Ok(dir) => {
|
|
self.log.info(format!("NOVEL_DIRECTORY set to '{root_dir}/{dir}'"));
|
|
format!("{root_dir}/{dir}")
|
|
}
|
|
Err(e) => {
|
|
self.log.error(format!("NOVEL_DIRECTORY not set: {e}. Defaulting to '{root_dir}/novels'."));
|
|
format!("{root_dir}/novels")
|
|
}
|
|
};
|
|
|
|
self.directories.insert("Novel".to_owned(), novel_dir);
|
|
|
|
let manga_dir = match var("MANGA_DIRECTORY") {
|
|
Ok(dir) => {
|
|
self.log.info(format!("MANGA_DIRECTORY set to '{root_dir}/{dir}'"));
|
|
format!("{root_dir}/{dir}")
|
|
}
|
|
Err(e) => {
|
|
self.log.error(format!("MANGA_DIRECTORY not set: {e}. Defaulting to '{root_dir}/manga'."));
|
|
format!("{root_dir}/manga")
|
|
}
|
|
};
|
|
|
|
self.directories.insert("Manga".to_owned(), manga_dir);
|
|
|
|
let hentai_dir = match var("HENTAI_DIRECTORY") {
|
|
Ok(dir) => {
|
|
self.log.info(format!("HENTAI_DIRECTORY set to '{root_dir}/{dir}'"));
|
|
format!("{root_dir}/{dir}")
|
|
}
|
|
Err(e) => {
|
|
self.log.error(format!("HENTAI_DIRECTORY not set: {e}. Defaulting to '{root_dir}/hentai'."));
|
|
format!("{root_dir}/hentai")
|
|
}
|
|
};
|
|
|
|
self.directories.insert("Hentai".to_owned(), hentai_dir);
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
dotenv().expect("Failed to init dotenv");
|
|
|
|
JournalLog::new()
|
|
.expect("Systemd-Logger crate error")
|
|
.install()
|
|
.expect("Systemd-Logger crate error");
|
|
|
|
match var("LOG_LEVEL") {
|
|
Ok(level) => {
|
|
match level.as_str() {
|
|
"debug" => log::set_max_level(LevelFilter::Debug),
|
|
"info" => log::set_max_level(LevelFilter::Info),
|
|
_ => log::set_max_level(LevelFilter::Info),
|
|
}
|
|
}
|
|
_ => log::set_max_level(LevelFilter::Info),
|
|
}
|
|
|
|
let mut app = App {
|
|
log: Logging::new(None),
|
|
directories: HashMap::new(),
|
|
};
|
|
|
|
app.init_directories();
|
|
|
|
let api = Router::new()
|
|
.route("/upload", post(|State(mut state): State<App>, request: Request| async move {
|
|
upload_file(&mut state, request).await;
|
|
}))
|
|
.with_state(app);
|
|
|
|
let listener = tokio::net::TcpListener::bind("[::]:3000").await.unwrap();
|
|
axum::serve(listener, api).await.unwrap();
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct FilePath {
|
|
format: String,
|
|
series: String,
|
|
volume: String,
|
|
extension: String
|
|
}
|
|
|
|
impl FilePath {
|
|
fn new() -> Self {
|
|
Self {
|
|
format: "".to_owned(),
|
|
series: "".to_owned(),
|
|
volume: "".to_owned(),
|
|
extension: "".to_owned()
|
|
}
|
|
}
|
|
|
|
fn check_valid(&self) -> bool {
|
|
if self.format == "" || self.series == "" || self.volume == "" || self.extension == "" {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
fn to_pathbuf(&self) -> PathBuf {
|
|
Path::new(format!("{}/{}/{}/{}.{}", self.format, self.series, self.volume, self.volume, self.extension).as_str()).to_path_buf()
|
|
}
|
|
}
|
|
|
|
async fn upload_file(state: &mut App, request: Request) {
|
|
let params_raw: Vec<&str> = request.uri().query().unwrap().split('&').collect();
|
|
let mut file = FilePath::new();
|
|
params_raw.iter().for_each(|param| {
|
|
let split: Vec<&str> = param.split('=').collect();
|
|
state.log.info(format!("Parsing Parameter Key-Value Pair '{param}'"));
|
|
match split[0] {
|
|
"format" => {
|
|
file.format.clone_from(state.directories.get(split[1]).expect("Assume Valid Format Was Provided"));
|
|
},
|
|
"series" => {
|
|
file.series = decode(split[1]).expect("UTF-8").to_string();
|
|
},
|
|
"volume" => {
|
|
file.volume = decode(split[1]).expect("UTF-8").to_string();
|
|
},
|
|
k => {
|
|
state.log.warn(format!("Parameter {k} is not known and will be ignored"));
|
|
}
|
|
}
|
|
});
|
|
|
|
let content_type = request.headers().get("Content-Type").expect("Content Type Should Have Been provided").to_str().expect("Content Type Should Be String");
|
|
|
|
file.extension = match content_type {
|
|
"application/epub+zip" => "epub".to_owned(),
|
|
"application/comic+zip" => "cbz".to_owned(),
|
|
"application/pdf" => "pdf".to_owned(),
|
|
ct => {
|
|
state.log.error(format!("Invalid Content Type '{ct}' Provided, Aborting"));
|
|
panic!("Invalid Content Type '{ct}'")
|
|
}
|
|
};
|
|
|
|
println!("{:#?}", file);
|
|
|
|
if !file.check_valid() {
|
|
//return Err((StatusCode::BAD_REQUEST, "Format not specified".to_owned()));
|
|
}
|
|
|
|
let pathbuf = file.to_pathbuf();
|
|
state.log.info(format!("File Path '{}'", pathbuf.clone().display()));
|
|
|
|
let file_stream = request.into_body().into_data_stream();
|
|
if let Err(e) = stream_to_file(&pathbuf, file_stream).await {
|
|
state.log.error(format!("{}: {}", e.0, e.1));
|
|
};
|
|
}
|
|
|
|
async fn stream_to_file(path: &PathBuf, stream: BodyDataStream) -> Result<(), (StatusCode, String)>
|
|
{
|
|
if !Path::exists(path.parent().unwrap()) {
|
|
fs::create_dir_all(path.parent().unwrap()).await.expect("Unable to Create Path");
|
|
}
|
|
|
|
async {
|
|
let body_with_io_error = stream.map_err(|err| io::Error::new(io::ErrorKind::Other, err));
|
|
let body_reader = StreamReader::new(body_with_io_error);
|
|
futures::pin_mut!(body_reader);
|
|
|
|
let mut file = BufWriter::new(File::create(path).await?);
|
|
|
|
io::copy(&mut body_reader, &mut file).await?;
|
|
|
|
Ok::<_, io::Error>(())
|
|
}
|
|
.await
|
|
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))
|
|
}
|