Files
ImageApi/src/main.rs
Cameron Cordes c90987d709 Check extensions case-invariant
Ran clippy and linted some of the logic.
2020-07-16 20:16:17 -04:00

242 lines
7.2 KiB
Rust

#[macro_use]
extern crate diesel;
extern crate rayon;
use actix_files::NamedFile;
use actix_web::web::{HttpRequest, HttpResponse, Json};
use actix_web::{get, post, web, App, HttpServer, Responder};
use chrono::{Duration, Utc};
use data::{LoginRequest, ThumbnailRequest};
use jsonwebtoken::{encode, EncodingKey, Header};
use rayon::prelude::*;
use serde::Serialize;
use std::path::{Path, PathBuf};
use crate::data::{Claims, CreateAccountRequest, Token};
use crate::database::{create_user, get_user, user_exists};
use crate::files::list_files;
use crate::video::create_playlist;
mod data;
mod database;
mod files;
mod video;
#[post("/register")]
async fn register(user: Json<CreateAccountRequest>) -> impl Responder {
if !user.username.is_empty() && user.password.len() > 5 && user.password == user.confirmation {
if user_exists(&user.username) {
HttpResponse::BadRequest()
} else if let Some(_user) = create_user(&user.username, &user.password) {
HttpResponse::Ok()
} else {
HttpResponse::InternalServerError()
}
} else {
HttpResponse::BadRequest()
}
}
#[post("/login")]
async fn login(creds: Json<LoginRequest>) -> impl Responder {
println!("Logging in: {}", creds.username);
if let Some(user) = get_user(&creds.username, &creds.password) {
let claims = Claims {
sub: user.id.to_string(),
exp: (Utc::now() + Duration::days(3)).timestamp(),
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret("secret_token".as_ref()),
)
.unwrap();
HttpResponse::Ok().json(Token { token: &token })
} else {
HttpResponse::NotFound().finish()
}
}
#[post("/photos")]
async fn list_photos(_claims: Claims, req: Json<ThumbnailRequest>) -> impl Responder {
println!("{}", req.path);
let path = &req.path;
if let Some(path) = is_valid_path(path) {
let files = list_files(path).unwrap_or_default();
let photos = &files
.iter()
.filter(|f| !f.extension().unwrap_or_default().is_empty())
.map(|f| f.to_str().unwrap().to_string())
.collect::<Vec<String>>();
let dirs = &files
.iter()
.filter(|f| f.extension().unwrap_or_default().is_empty())
.map(|f| f.to_str().unwrap().to_string())
.collect::<Vec<String>>();
HttpResponse::Ok().json(PhotosResponse { photos, dirs })
} else {
HttpResponse::BadRequest().finish()
}
}
#[derive(Serialize)]
struct PhotosResponse<'a> {
photos: &'a [String],
dirs: &'a [String],
}
fn is_valid_path(path: &str) -> Option<PathBuf> {
match path {
path if path.contains("..") => None,
path => {
let path = PathBuf::from(path);
if path.is_relative() {
let mut full_path = PathBuf::from(dotenv::var("BASE_PATH").unwrap());
full_path.push(path);
Some(full_path)
} else {
None
}
}
}
}
#[get("/image")]
async fn get_image(
_claims: Claims,
request: HttpRequest,
req: web::Query<ThumbnailRequest>,
) -> impl Responder {
if let Some(path) = is_valid_path(&req.path) {
if req.size.is_some() {
let thumbs = dotenv::var("THUMBNAILS").unwrap();
let thumb_path = Path::new(&thumbs).join(path.file_name().unwrap());
println!("{:?}", thumb_path);
if let Ok(file) = NamedFile::open(&thumb_path) {
file.into_response(&request).unwrap()
} else {
HttpResponse::NotFound().finish()
}
} else if let Ok(file) = NamedFile::open(path) {
file.into_response(&request).unwrap()
} else {
HttpResponse::NotFound().finish()
}
} else {
HttpResponse::BadRequest().finish()
}
}
#[post("/video/generate")]
async fn generate_video(_claims: Claims, body: web::Json<ThumbnailRequest>) -> impl Responder {
let filename = PathBuf::from(&body.path);
if let Some(name) = filename.file_stem() {
let filename = name.to_str().expect("Filename should convert to string");
let playlist = format!("tmp/{}.m3u8", filename);
if let Some(path) = is_valid_path(&body.path) {
create_playlist(&path.to_str().unwrap(), &playlist);
} else {
return HttpResponse::BadRequest().finish();
}
HttpResponse::Ok().json(playlist)
} else {
HttpResponse::BadRequest().finish()
}
}
#[get("/video/stream")]
async fn stream_video(request: HttpRequest, path: web::Query<ThumbnailRequest>) -> impl Responder {
let playlist = &path.path;
println!("Playlist: {}", playlist);
if let Ok(file) = NamedFile::open(playlist) {
file.into_response(&request).unwrap()
} else {
HttpResponse::NotFound().finish()
}
}
#[get("/video/{path}")]
async fn get_video_part(request: HttpRequest, path: web::Path<ThumbnailRequest>) -> impl Responder {
let part = &path.path;
println!("Video part: {}", part);
if let Ok(file) = NamedFile::open(String::from("tmp/") + part) {
file.into_response(&request).unwrap()
} else {
HttpResponse::NotFound().finish()
}
}
async fn create_thumbnails() {
let thumbs = &dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined");
let thumbnail_directory: &Path = Path::new(thumbs);
let t: Vec<_> = thumbnail_directory
.read_dir()
.expect("Error reading thumbnail directory")
.collect();
if !t.is_empty() {
println!("Skipping thumbs");
return;
}
let images = PathBuf::from(dotenv::var("BASE_PATH").unwrap());
walkdir::WalkDir::new(images)
.into_iter()
.collect::<Vec<Result<_, _>>>()
.into_par_iter()
.filter_map(|entry| entry.ok())
.filter(|entry| {
println!("{:?}", entry.path());
if let Some(ext) = entry.path().extension().and_then(|ext| {
ext.to_str().map(|ext| ext.to_lowercase())
}) {
ext == "jpg" || ext == "jpeg" || ext == "png"
} else {
false
}
})
.map(|entry| (image::open(entry.path()), entry.path().to_path_buf()))
.filter(|(img, _)| img.is_ok())
.map(|(img, path)| (img.unwrap(), path))
.map(|(image, path)| (image.thumbnail(200, 200), path))
.map(|(image, path)| {
let thumb = Path::new(thumbnail_directory).join(path.file_name().unwrap());
println!("{:?}", thumb);
image.save(thumb).expect("Failure saving thumbnail");
})
.for_each(drop);
println!("Finished");
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
create_thumbnails().await;
HttpServer::new(|| {
App::new()
.service(register)
.service(login)
.service(list_photos)
.service(get_image)
.service(generate_video)
.service(stream_video)
.service(get_video_part)
})
.bind("192.168.10.23:8088")?
.bind("localhost:8088")?
.run()
.await
}