diff --git a/api/src/category.rs b/api/src/category.rs new file mode 100644 index 0000000..5389c52 --- /dev/null +++ b/api/src/category.rs @@ -0,0 +1,81 @@ +use base64; +use serde::Serialize; +use std::fs::DirEntry; +use std::{io, env}; +use rocket::serde::json::Json; + +#[derive(Serialize)] +pub struct Category { + name: String, + thumbnail: Option +} + +#[derive(Serialize)] +struct List { + categories: Vec +} + + + +fn get_category_metadata(entry: DirEntry) -> io::Result<(String, Option)> { + use std::io::Read; + let name = entry.file_name().to_string_lossy().to_string(); + let mut p = entry.path(); p.push(".thumbnail.jpg"); + if p.is_file() { + let mut buf = Vec::new(); + let mut file = std::fs::File::open(p)?; + file.read_to_end(&mut buf)?; + + let nail = base64::encode(buf).to_string(); + return match nail.len() { + 0 => Ok((name, None)), + _ => Ok((name, Some(nail))) + }; + } + return Ok((String::new(), None)); +} + +fn get_category_dirs(path: &str) -> std::io::Result> { + let path = std::path::Path::new(path); + if !path.is_dir() { + panic!("<{:?}> is not a valid directory", path); + } + + let mut ret: Vec = Vec::new(); + for entry in std::fs::read_dir(path)? { + if let Ok(entry) = entry { + ret.push(entry) + } + } + return Ok(ret); +} + +#[get("/categories")] +pub fn list() -> Json> { + // WARN: misconfigured servers are just going to get shafted and serve up + // a tonne of 500's + let dir = match env::var("CLIPS_DIR") { + Ok(val) => val, + Err(_) => "/media/clips/".to_string() + }; + match get_category_dirs(&dir) { + Ok(entries) => { + let mut cats: Vec = Vec::new(); + for ent in entries { + match get_category_metadata(ent) { + Ok((name, thumbnail)) => { + cats.push(Category {name, thumbnail}); + }, + _ => continue + } + + } + return Json(cats); + }, + Err(e) => { + eprintln!("ERROR: {}", e); + } + } + Json(Vec::new()) +} + diff --git a/api/src/main.rs b/api/src/main.rs index 6a69399..aa2c46d 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -4,6 +4,7 @@ use std::env; use rocket_dyn_templates::Template; mod page; +mod category; mod video; #[tokio::main] @@ -16,7 +17,7 @@ async fn main() { */ let _ = rocket::build() .mount("/", routes![page::home, page::category, page::video, page::files]) - .mount("/api", routes![video::list_categories]) + .mount("/api", routes![category::list, video::list]) .attach(Template::fairing()) .launch().await; } diff --git a/api/src/page.rs b/api/src/page.rs index c3e0196..afba484 100644 --- a/api/src/page.rs +++ b/api/src/page.rs @@ -18,7 +18,7 @@ pub async fn home() -> Template { #[get("/category/")] pub async fn category(cat: String) -> Template { let mut h: HashMap<&'static str, &'static str> = HashMap::new(); - h.insert("script_src", "/js/category.js"); + h.insert("script_src", "category.js"); h.insert("page", "category"); return Template::render("list", &h); diff --git a/api/src/video.rs b/api/src/video.rs index 8ff9fed..cebd620 100644 --- a/api/src/video.rs +++ b/api/src/video.rs @@ -1,81 +1,89 @@ -use base64; -use serde::Serialize; +use std::env; use std::fs::DirEntry; -use std::{io, env}; +use serde::Serialize; use rocket::serde::json::Json; #[derive(Serialize)] -pub struct Category { +pub struct VideoPreview { name: String, thumbnail: Option } -#[derive(Serialize)] -struct List { - categories: Vec -} - - - -fn get_category_metadata(entry: DirEntry) -> io::Result<(String, Option)> { - use std::io::Read; - let name = entry.file_name().to_string_lossy().to_string(); - let mut p = entry.path(); p.push(".thumbnail.jpg"); - if p.is_file() { - let mut buf = Vec::new(); - let mut file = std::fs::File::open(p)?; - file.read_to_end(&mut buf)?; - - let nail = base64::encode(buf).to_string(); - return match nail.len() { - 0 => Ok((name, None)), - _ => Ok((name, Some(nail))) - }; - } - return Ok((String::new(), None)); -} - -fn get_category_dirs(path: &str) -> std::io::Result> { +fn vid_file_entries(path: &str) -> std::io::Result> { let path = std::path::Path::new(path); if !path.is_dir() { panic!("<{:?}> is not a valid directory", path); } - - let mut ret: Vec = Vec::new(); - for entry in std::fs::read_dir(path)? { - if let Ok(entry) = entry { - ret.push(entry) + let mut entries: Vec = Vec::new(); + for ent in path.read_dir()? { + if let Ok(ent) = ent { + let name = ent.file_name().into_string().unwrap(); + if name.ends_with("mkv") || name.ends_with("mp4") || name.ends_with("webm") { + entries.push(ent); + } } } - return Ok(ret); + return Ok(entries); } -#[get("/categories")] -pub async fn list_categories() -> Json> { - // WARN: misconfigured servers are just going to get shafted and serve up - // a tonne of 500's - let dir = match env::var("CLIPS_DIR") { +pub fn get_thumbnail(clips_dir: &str, vid_file: &str) -> std::io::Result> { + use std::io::Read; + // Contruct the full path to the video file itself + // NOTE: thumbnail file names are basically file.mkv.jpg + let path = { + let mut s = clips_dir.to_string(); + s.push_str("/thumbnails/"); + s.push_str(vid_file); + s.push_str(".jpg"); + s + }; + let path = std::path::Path::new(&path); + + if !path.is_file() { + println!("File {:?} not found", path); + return Ok(None) + } + println!("Found file {:?}", path); + + // Read into a vec so that base64 can deal with it + let mut buf = Vec::new(); + let mut file = std::fs::File::open(path)?; + file.read_to_end(&mut buf)?; + + let nail = base64::encode(buf).to_string(); + return match nail.len() { + 0 => Ok(None), + _ => Ok(Some(nail)) + }; +} + +#[get("/category/")] +pub fn list(cat: String) -> Json> { + // Strip out any and all '..' from the string + // Doing this to avoid any directory traversal memes + let cat = cat.replace("..",""); + + // Target the directory of clips that we're looking for + let mut clips_dir = match env::var("CLIPS_DIR") { Ok(val) => val, Err(_) => "/media/clips/".to_string() - }; - match get_category_dirs(&dir) { - Ok(entries) => { - let mut cats: Vec = Vec::new(); - for ent in entries { - match get_category_metadata(ent) { - Ok((name, thumbnail)) => { - cats.push(Category {name, thumbnail}); - }, - _ => continue - } - - } - return Json(cats); - }, - Err(e) => { - eprintln!("ERROR: {}", e); - } + }; + clips_dir.push('/'); clips_dir.push_str(&cat); + let thumbs_dir = clips_dir.clone(); + + let mut json: Vec = Vec::new(); + // Grab direntires to all the target files + let vid_files = vid_file_entries(&clips_dir).unwrap(); + for vf in vid_files { + // jfc this is rarted + let name = vf.file_name().to_string_lossy().to_string(); + let thumbnail = match get_thumbnail(&thumbs_dir, &name) { + Ok(val) => val, + _ => None + }; + json.push(VideoPreview { name, thumbnail }) } - Json(Vec::new()) + + return Json(json) }