use std::{fs, path::Path}; use axum::{ response::Redirect, routing::{get, get_service}, Router, }; use minijinja::Environment; use ost::client::OSClient; use serde::Serialize; use tower_http::services::ServeDir; use tracing::info; pub mod error; pub mod ost; pub mod routes; #[derive(Debug, Clone)] pub struct State { client: OSClient, env: Environment<'static>, base_path: String, } lazy_static::lazy_static! { pub static ref INDEX: String = std::fs::read_to_string("assets/index.html").expect("missing template"); } lazy_static::lazy_static! { pub static ref FILE_CONTAINER: String = std::fs::read_to_string("assets/filecontainer.html").expect("missing template"); } #[tokio::main] async fn main() { dotenv::dotenv().expect("could not load env"); tracing_subscriber::fmt().init(); let client = OSClient::init().await; info!("Successfully loaded client"); let base_path = std::env::var("BASE_PATH").expect("base path not configured"); let mut env = Environment::new(); env.add_template("file_container", &FILE_CONTAINER) .expect("unable to add template"); env.add_template("index", &INDEX) .expect("unable to add template"); let state = State { client, env, base_path, }; let router = axum::Router::new() .route("/", get(|| async { Redirect::permanent("/dir/root") })) .route("/dir/*key", get(routes::get_directory)) .route("/subtitles", get(routes::search_subtitles)) .route("/guess", get(routes::guess_it)) .route("/download", get(routes::download_subtitles)) .with_state(state); let router_static = Router::new().fallback(get_service(ServeDir::new("assets"))); let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await.unwrap(); axum::serve(listener, router.merge(router_static)) .await .unwrap(); } pub fn scan_dir_contents(path: impl AsRef) -> std::io::Result> { let entries = fs::read_dir(path)? .filter_map(Result::ok) .collect::>(); let subs = entries .iter() .filter_map(|entry| { let path = entry.path(); let ext = path.extension()?.to_str()?; let (name, _) = path.file_name()?.to_str()?.split_once(ext)?; (ext == "srt").then_some(name.to_owned()) }) .collect::>(); let mut files: Vec<_> = entries .iter() .filter_map(|entry| { if entry.path().is_dir() { Some(FileType::Directory(Directory { name: entry.file_name().to_str().unwrap_or_default().to_string(), path: entry.path().to_str().unwrap_or_default().to_string(), })) // Skip existing subtitles } else if entry.path().extension().is_some_and(|ext| { ext.to_str().is_some_and(|ext| ext == "srt") || ext.to_str().is_some_and(|ext| ext == "smi") }) { None } else { let path = entry.path(); let ext = path.extension()?.to_str()?; let (name, _) = path.file_name()?.to_str()?.split_once(ext)?; Some(FileType::File(File { name: entry.file_name().to_str().unwrap_or_default().to_string(), path: entry.path().to_str().unwrap_or_default().to_string(), has_subs: subs.contains(&name.to_string()), })) } }) .collect(); files.sort(); Ok(files) } #[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] pub enum FileType { Directory(Directory), File(File), } #[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] pub struct File { name: String, path: String, /// `true` if a same file exists with a `.srt` extension found /// in the same directory has_subs: bool, } #[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)] pub struct Directory { name: String, path: String, }