|
@@ -1,19 +1,36 @@
|
|
use crate::{
|
|
use crate::{
|
|
cache::Cache,
|
|
cache::Cache,
|
|
error::NtitledError,
|
|
error::NtitledError,
|
|
- htmx::{DirHtmx, FileHtmx, SubtitleResponseHtmx},
|
|
|
|
|
|
+ htmx::{DirHtmx, DownloadButtonHtmx, FileHtmx, SubtitleResponseHtmx},
|
|
ost::{data::SubtitleResponse, hash::compute_hash},
|
|
ost::{data::SubtitleResponse, hash::compute_hash},
|
|
FileType, State,
|
|
FileType, State,
|
|
};
|
|
};
|
|
use axum::{
|
|
use axum::{
|
|
http::{header, StatusCode},
|
|
http::{header, StatusCode},
|
|
- response::IntoResponse,
|
|
|
|
|
|
+ response::{IntoResponse, Redirect},
|
|
|
|
+ routing::{get, get_service},
|
|
|
|
+ Router,
|
|
};
|
|
};
|
|
use htmxpress::HtmxElement;
|
|
use htmxpress::HtmxElement;
|
|
use minijinja::context;
|
|
use minijinja::context;
|
|
use serde::Deserialize;
|
|
use serde::Deserialize;
|
|
use std::{ffi::OsStr, fmt::Write, path::Path};
|
|
use std::{ffi::OsStr, fmt::Write, path::Path};
|
|
-use tracing::info;
|
|
|
|
|
|
+use tower_http::services::ServeDir;
|
|
|
|
+use tracing::{debug, info};
|
|
|
|
+
|
|
|
|
+pub fn router(state: State) -> Router {
|
|
|
|
+ let router = axum::Router::new()
|
|
|
|
+ .route("/", get(|| async { Redirect::permanent("/dir/root") }))
|
|
|
|
+ .route("/dir/*key", get(get_directory))
|
|
|
|
+ .route("/subtitles", get(search_subtitles))
|
|
|
|
+ .route("/download/dir", get(download_subtitles_dir))
|
|
|
|
+ .route("/download", get(download_subtitles))
|
|
|
|
+ .with_state(state);
|
|
|
|
+
|
|
|
|
+ let router_static = Router::new().fallback(get_service(ServeDir::new("assets")));
|
|
|
|
+
|
|
|
|
+ router.merge(router_static)
|
|
|
|
+}
|
|
|
|
|
|
pub async fn get_directory(
|
|
pub async fn get_directory(
|
|
mut state: axum::extract::State<State>,
|
|
mut state: axum::extract::State<State>,
|
|
@@ -38,12 +55,21 @@ pub async fn get_directory(
|
|
.fold(String::new(), |mut acc, (i, entry)| {
|
|
.fold(String::new(), |mut acc, (i, entry)| {
|
|
match entry {
|
|
match entry {
|
|
FileType::Directory(mut dir) => {
|
|
FileType::Directory(mut dir) => {
|
|
|
|
+ let _ = write!(acc, r#"<div class="dir-container">"#);
|
|
|
|
+
|
|
let Some((_, path)) = dir.path.split_once(&state.base_path) else {
|
|
let Some((_, path)) = dir.path.split_once(&state.base_path) else {
|
|
return acc;
|
|
return acc;
|
|
};
|
|
};
|
|
|
|
+ let download_path = dir.path.clone();
|
|
dir.path = path[1..].to_string();
|
|
dir.path = path[1..].to_string();
|
|
- let dir: DirHtmx = dir.into();
|
|
|
|
|
|
+
|
|
|
|
+ let dl_button = DownloadButtonHtmx::new(download_path);
|
|
|
|
+ let _ = write!(acc, "{}", dl_button.to_htmx());
|
|
|
|
+
|
|
|
|
+ let dir = DirHtmx::new(dir);
|
|
let _ = write!(acc, "{}", dir.to_htmx());
|
|
let _ = write!(acc, "{}", dir.to_htmx());
|
|
|
|
+
|
|
|
|
+ let _ = write!(acc, "</div>");
|
|
}
|
|
}
|
|
FileType::File(mut file) => {
|
|
FileType::File(mut file) => {
|
|
let Some((_, path)) = file.path.split_once(&state.base_path) else {
|
|
let Some((_, path)) = file.path.split_once(&state.base_path) else {
|
|
@@ -103,6 +129,7 @@ pub async fn search_subtitles(
|
|
|
|
|
|
Ok(response)
|
|
Ok(response)
|
|
}
|
|
}
|
|
|
|
+
|
|
#[derive(Debug, Deserialize)]
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct DownloadQuery {
|
|
pub struct DownloadQuery {
|
|
file_id: String,
|
|
file_id: String,
|
|
@@ -141,3 +168,61 @@ pub async fn download_subtitles(
|
|
|
|
|
|
Ok(String::from("Successfully downloaded subtitles"))
|
|
Ok(String::from("Successfully downloaded subtitles"))
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+#[derive(Debug, Deserialize)]
|
|
|
|
+pub struct DownloadDirQuery {
|
|
|
|
+ full_path: String,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+pub async fn download_subtitles_dir(
|
|
|
|
+ mut state: axum::extract::State<State>,
|
|
|
|
+ query: axum::extract::Query<DownloadDirQuery>,
|
|
|
|
+) -> Result<String, NtitledError> {
|
|
|
|
+ let files = state.scan_dir_contents(&query.full_path)?;
|
|
|
|
+ for file in files {
|
|
|
|
+ let FileType::File(mut file) = file else {
|
|
|
|
+ continue;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ if file.has_subs {
|
|
|
|
+ debug!("Subs exist, skipping {}", file.name);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let path = &file.path;
|
|
|
|
+
|
|
|
|
+ info!("Computing hash for {}", path);
|
|
|
|
+
|
|
|
|
+ let hash = compute_hash(path)?;
|
|
|
|
+
|
|
|
|
+ info!("Searching subtitles for {}", file.name);
|
|
|
|
+ let search = state.client.search(&file.name, Some(&hash)).await?;
|
|
|
|
+
|
|
|
|
+ let mut srt_files = search.data.into_iter().map(SubtitleResponse::from);
|
|
|
|
+
|
|
|
|
+ let Some(srt) = srt_files.find(|file| file.hashmatch) else {
|
|
|
|
+ info!("No hash match found, skipping {}", file.name);
|
|
|
|
+ continue;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ if srt.files.is_empty() {
|
|
|
|
+ info!("No files found from search, skipping {}", file.name);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let ext = Path::new(&file.path).extension().and_then(OsStr::to_str);
|
|
|
|
+ let srt_path = ext
|
|
|
|
+ .map(|ext| file.path.replace(&format!(".{ext}"), ".srt"))
|
|
|
|
+ .unwrap_or(format!("{}.srt", file.path));
|
|
|
|
+
|
|
|
|
+ state
|
|
|
|
+ .client
|
|
|
|
+ .download_subtitles(srt.files.first().unwrap().file_id, &srt_path)
|
|
|
|
+ .await?;
|
|
|
|
+
|
|
|
|
+ file.has_subs = true;
|
|
|
|
+ state.cache.set_file_meta(&file).unwrap();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Ok(String::from("Done :)"))
|
|
|
|
+}
|