Merge pull request 'feature/logging' (#5) from feature/logging into master
All checks were successful
Core Repos/ImageApi/pipeline/head This commit looks good
All checks were successful
Core Repos/ImageApi/pipeline/head This commit looks good
Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
41
Cargo.lock
generated
41
Cargo.lock
generated
@@ -390,6 +390,17 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -856,6 +867,19 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"humantime",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fake-simd"
|
name = "fake-simd"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1169,6 +1193,12 @@ version = "1.3.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1213,10 +1243,12 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"hmac",
|
"hmac",
|
||||||
"image",
|
"image",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
|
"log",
|
||||||
"notify",
|
"notify",
|
||||||
"path-absolutize",
|
"path-absolutize",
|
||||||
"rayon",
|
"rayon",
|
||||||
@@ -2200,6 +2232,15 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
|
|||||||
@@ -29,3 +29,5 @@ rayon = "1.3"
|
|||||||
notify = "4.0"
|
notify = "4.0"
|
||||||
tokio = "0.2"
|
tokio = "0.2"
|
||||||
path-absolutize = "3.0.6"
|
path-absolutize = "3.0.6"
|
||||||
|
log="0.4"
|
||||||
|
env_logger="0.8"
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ They should be defined where the binary is located or above it in an `.env` file
|
|||||||
- `THUMBNAILS` is a path where generated thumbnails should be stored
|
- `THUMBNAILS` is a path where generated thumbnails should be stored
|
||||||
- `BIND_URL` is the url and port to bind to (typically your own IP address)
|
- `BIND_URL` is the url and port to bind to (typically your own IP address)
|
||||||
- `SECRET_KEY` is the *hopefully* random string to sign Tokens with
|
- `SECRET_KEY` is the *hopefully* random string to sign Tokens with
|
||||||
|
- `RUST_LOG` is one of `off, error, warn, info, debug, trace`, from least to most noisy [error is default]
|
||||||
|
|
||||||
|
|||||||
44
src/auth.rs
Normal file
44
src/auth.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use actix_web::web::{HttpResponse, Json};
|
||||||
|
use actix_web::{post, Responder};
|
||||||
|
use chrono::{Duration, Utc};
|
||||||
|
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use crate::data::LoginRequest;
|
||||||
|
use crate::data::{secret_key, Claims, CreateAccountRequest, Token};
|
||||||
|
use crate::database::{create_user, get_user, user_exists};
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
debug!("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(5)).timestamp(),
|
||||||
|
};
|
||||||
|
let token = encode(
|
||||||
|
&Header::default(),
|
||||||
|
&claims,
|
||||||
|
&EncodingKey::from_secret(secret_key().as_bytes()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
HttpResponse::Ok().json(Token { token: &token })
|
||||||
|
} else {
|
||||||
|
HttpResponse::NotFound().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use log::error;
|
||||||
|
|
||||||
use actix_web::error::ErrorUnauthorized;
|
use actix_web::error::ErrorUnauthorized;
|
||||||
use actix_web::{dev, http::header, Error, FromRequest, HttpRequest};
|
use actix_web::{dev, http::header, Error, FromRequest, HttpRequest};
|
||||||
use futures::future::{err, ok, Ready};
|
use futures::future::{err, ok, Ready};
|
||||||
@@ -11,14 +13,18 @@ pub struct Token<'a> {
|
|||||||
pub token: &'a str,
|
pub token: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Claims {
|
pub struct Claims {
|
||||||
pub sub: String,
|
pub sub: String,
|
||||||
pub exp: i64,
|
pub exp: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn secret_key() -> String {
|
pub fn secret_key() -> String {
|
||||||
dotenv::var("SECRET_KEY").expect("SECRET_KEY env not set!")
|
if cfg!(test) {
|
||||||
|
String::from("test_key")
|
||||||
|
} else {
|
||||||
|
dotenv::var("SECRET_KEY").expect("SECRET_KEY env not set!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Claims {
|
impl FromStr for Claims {
|
||||||
@@ -34,7 +40,7 @@ impl FromStr for Claims {
|
|||||||
) {
|
) {
|
||||||
Ok(data) => Ok(data.claims),
|
Ok(data) => Ok(data.claims),
|
||||||
Err(other) => {
|
Err(other) => {
|
||||||
println!("DecodeError: {}", other);
|
error!("DecodeError: {}", other);
|
||||||
Err(other)
|
Err(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,3 +91,39 @@ pub struct CreateAccountRequest {
|
|||||||
pub struct AddFavoriteRequest {
|
pub struct AddFavoriteRequest {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Claims;
|
||||||
|
use jsonwebtoken::errors::ErrorKind;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_token_from_claims() {
|
||||||
|
let claims = Claims {
|
||||||
|
exp: 16136164790, // 2481-ish
|
||||||
|
sub: String::from("9"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let c = Claims::from_str(
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5IiwiZXhwIjoxNjEzNjE2NDc5MH0.9wwK4l8vhvq55YoueEljMbN_5uVTaAsGLLRPr0AuymE")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(claims.sub, c.sub);
|
||||||
|
assert_eq!(claims.exp, c.exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_expired_token() {
|
||||||
|
let err = Claims::from_str(
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5IiwiZXhwIjoxNn0.eZnfaNfiD54VMbphIqeBICeG9SzAtwNXntLwtTBihjY");
|
||||||
|
|
||||||
|
match err.unwrap_err().into_kind() {
|
||||||
|
ErrorKind::ExpiredSignature => assert!(true),
|
||||||
|
kind => {
|
||||||
|
println!("Unexpected error: {:?}", kind);
|
||||||
|
assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
109
src/main.rs
109
src/main.rs
@@ -2,74 +2,40 @@
|
|||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
extern crate rayon;
|
extern crate rayon;
|
||||||
|
|
||||||
|
use crate::auth::login;
|
||||||
|
use futures::stream::StreamExt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc};
|
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use actix::{Actor, Addr};
|
use actix::{Actor, Addr};
|
||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
use actix_multipart as mp;
|
use actix_multipart as mp;
|
||||||
use actix_web::{App, get, HttpServer, post, Responder, web};
|
|
||||||
use actix_web::web::{HttpRequest, HttpResponse, Json};
|
use actix_web::web::{HttpRequest, HttpResponse, Json};
|
||||||
use chrono::{Duration, Utc};
|
use actix_web::{get, post, web, App, HttpServer, Responder};
|
||||||
use futures::stream::StreamExt;
|
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
|
||||||
use notify::{DebouncedEvent, RecursiveMode, watcher, Watcher};
|
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use data::{AddFavoriteRequest, LoginRequest, ThumbnailRequest};
|
use data::{AddFavoriteRequest, ThumbnailRequest};
|
||||||
|
use log::{debug, error, info};
|
||||||
|
|
||||||
use crate::data::{Claims, CreateAccountRequest, secret_key, Token};
|
use crate::data::Claims;
|
||||||
use crate::database::{add_favorite, create_user, get_favorites, get_user, user_exists};
|
use crate::database::{add_favorite, get_favorites};
|
||||||
use crate::files::{is_valid_path, list_files};
|
use crate::files::{is_valid_path, list_files};
|
||||||
use crate::video::*;
|
use crate::video::*;
|
||||||
|
|
||||||
|
mod auth;
|
||||||
mod data;
|
mod data;
|
||||||
mod database;
|
mod database;
|
||||||
mod files;
|
mod files;
|
||||||
mod video;
|
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(5)).timestamp(),
|
|
||||||
};
|
|
||||||
let token = encode(
|
|
||||||
&Header::default(),
|
|
||||||
&claims,
|
|
||||||
&EncodingKey::from_secret(secret_key().as_bytes()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
HttpResponse::Ok().json(Token { token: &token })
|
|
||||||
} else {
|
|
||||||
HttpResponse::NotFound().finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/photos")]
|
#[post("/photos")]
|
||||||
async fn list_photos(_claims: Claims, req: Json<ThumbnailRequest>) -> impl Responder {
|
async fn list_photos(_claims: Claims, req: Json<ThumbnailRequest>) -> impl Responder {
|
||||||
println!("{}", req.path);
|
info!("{}", req.path);
|
||||||
|
|
||||||
let path = &req.path;
|
let path = &req.path;
|
||||||
if let Some(path) = is_valid_path(path) {
|
if let Some(path) = is_valid_path(path) {
|
||||||
@@ -89,6 +55,7 @@ async fn list_photos(_claims: Claims, req: Json<ThumbnailRequest>) -> impl Respo
|
|||||||
|
|
||||||
HttpResponse::Ok().json(PhotosResponse { photos, dirs })
|
HttpResponse::Ok().json(PhotosResponse { photos, dirs })
|
||||||
} else {
|
} else {
|
||||||
|
error!("Bad photos request: {}", req.path);
|
||||||
HttpResponse::BadRequest().finish()
|
HttpResponse::BadRequest().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +80,7 @@ async fn get_image(
|
|||||||
.expect("Error stripping prefix");
|
.expect("Error stripping prefix");
|
||||||
let thumb_path = Path::new(&thumbs).join(relative_path);
|
let thumb_path = Path::new(&thumbs).join(relative_path);
|
||||||
|
|
||||||
println!("{:?}", thumb_path);
|
debug!("{:?}", thumb_path);
|
||||||
if let Ok(file) = NamedFile::open(&thumb_path) {
|
if let Ok(file) = NamedFile::open(&thumb_path) {
|
||||||
file.into_response(&request).unwrap()
|
file.into_response(&request).unwrap()
|
||||||
} else {
|
} else {
|
||||||
@@ -125,6 +92,7 @@ async fn get_image(
|
|||||||
HttpResponse::NotFound().finish()
|
HttpResponse::NotFound().finish()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
error!("Bad photos request: {}", req.path);
|
||||||
HttpResponse::BadRequest().finish()
|
HttpResponse::BadRequest().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,9 +105,9 @@ async fn upload_image(_: Claims, mut payload: mp::Multipart) -> impl Responder {
|
|||||||
|
|
||||||
while let Some(Ok(mut part)) = payload.next().await {
|
while let Some(Ok(mut part)) = payload.next().await {
|
||||||
if let Some(content_type) = part.content_disposition() {
|
if let Some(content_type) = part.content_disposition() {
|
||||||
println!("{:?}", content_type);
|
debug!("{:?}", content_type);
|
||||||
if let Some(filename) = content_type.get_filename() {
|
if let Some(filename) = content_type.get_filename() {
|
||||||
println!("Name: {:?}", filename);
|
debug!("Name: {:?}", filename);
|
||||||
file_name = Some(filename.to_string());
|
file_name = Some(filename.to_string());
|
||||||
|
|
||||||
while let Some(Ok(data)) = part.next().await {
|
while let Some(Ok(data)) = part.next().await {
|
||||||
@@ -163,9 +131,11 @@ async fn upload_image(_: Claims, mut payload: mp::Multipart) -> impl Responder {
|
|||||||
let mut file = File::create(full_path).unwrap();
|
let mut file = File::create(full_path).unwrap();
|
||||||
file.write_all(&file_content).unwrap();
|
file.write_all(&file_content).unwrap();
|
||||||
} else {
|
} else {
|
||||||
|
error!("File already exists: {:?}", full_path);
|
||||||
return HttpResponse::BadRequest().body("File already exists");
|
return HttpResponse::BadRequest().body("File already exists");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
error!("Invalid path for upload: {:?}", full_path);
|
||||||
return HttpResponse::BadRequest().body("Path was not valid");
|
return HttpResponse::BadRequest().body("Path was not valid");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -187,7 +157,8 @@ async fn generate_video(
|
|||||||
let playlist = format!("tmp/{}.m3u8", filename);
|
let playlist = format!("tmp/{}.m3u8", filename);
|
||||||
if let Some(path) = is_valid_path(&body.path) {
|
if let Some(path) = is_valid_path(&body.path) {
|
||||||
if let Ok(child) = create_playlist(&path.to_str().unwrap(), &playlist) {
|
if let Ok(child) = create_playlist(&path.to_str().unwrap(), &playlist) {
|
||||||
data.stream_manager.do_send(ProcessMessage(playlist.clone(), child));
|
data.stream_manager
|
||||||
|
.do_send(ProcessMessage(playlist.clone(), child));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return HttpResponse::BadRequest().finish();
|
return HttpResponse::BadRequest().finish();
|
||||||
@@ -195,6 +166,7 @@ async fn generate_video(
|
|||||||
|
|
||||||
HttpResponse::Ok().json(playlist)
|
HttpResponse::Ok().json(playlist)
|
||||||
} else {
|
} else {
|
||||||
|
error!("Unable to get file name: {:?}", filename);
|
||||||
HttpResponse::BadRequest().finish()
|
HttpResponse::BadRequest().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +178,7 @@ async fn stream_video(
|
|||||||
path: web::Query<ThumbnailRequest>,
|
path: web::Query<ThumbnailRequest>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let playlist = &path.path;
|
let playlist = &path.path;
|
||||||
println!("Playlist: {}", playlist);
|
debug!("Playlist: {}", playlist);
|
||||||
|
|
||||||
// Extract video playlist dir to dotenv
|
// Extract video playlist dir to dotenv
|
||||||
if !playlist.starts_with("tmp") && is_valid_path(playlist) != None {
|
if !playlist.starts_with("tmp") && is_valid_path(playlist) != None {
|
||||||
@@ -225,11 +197,12 @@ async fn get_video_part(
|
|||||||
path: web::Path<ThumbnailRequest>,
|
path: web::Path<ThumbnailRequest>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let part = &path.path;
|
let part = &path.path;
|
||||||
println!("Video part: {}", part);
|
debug!("Video part: {}", part);
|
||||||
|
|
||||||
if let Ok(file) = NamedFile::open(String::from("tmp/") + part) {
|
if let Ok(file) = NamedFile::open(String::from("tmp/") + part) {
|
||||||
file.into_response(&request).unwrap()
|
file.into_response(&request).unwrap()
|
||||||
} else {
|
} else {
|
||||||
|
error!("Video part not found: tmp/{}", part);
|
||||||
HttpResponse::NotFound().finish()
|
HttpResponse::NotFound().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,6 +213,7 @@ async fn favorites(claims: Claims) -> impl Responder {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|favorite| favorite.path)
|
.map(|favorite| favorite.path)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
HttpResponse::Ok().json(PhotosResponse {
|
HttpResponse::Ok().json(PhotosResponse {
|
||||||
photos: &favorites,
|
photos: &favorites,
|
||||||
dirs: &Vec::new(),
|
dirs: &Vec::new(),
|
||||||
@@ -250,8 +224,10 @@ async fn favorites(claims: Claims) -> impl Responder {
|
|||||||
async fn post_add_favorite(claims: Claims, body: web::Json<AddFavoriteRequest>) -> impl Responder {
|
async fn post_add_favorite(claims: Claims, body: web::Json<AddFavoriteRequest>) -> impl Responder {
|
||||||
if let Ok(user_id) = claims.sub.parse::<i32>() {
|
if let Ok(user_id) = claims.sub.parse::<i32>() {
|
||||||
add_favorite(user_id, body.path.clone());
|
add_favorite(user_id, body.path.clone());
|
||||||
|
debug!("Adding favorite \"{}\" for userid: {}", user_id, body.path);
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
} else {
|
} else {
|
||||||
|
error!("Unable to parse sub as i32: {}", claims.sub);
|
||||||
HttpResponse::BadRequest()
|
HttpResponse::BadRequest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,8 +243,9 @@ fn create_thumbnails() {
|
|||||||
.collect::<Vec<Result<_, _>>>()
|
.collect::<Vec<Result<_, _>>>()
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.filter_map(|entry| entry.ok())
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| entry.file_type().is_file())
|
||||||
.filter(|entry| {
|
.filter(|entry| {
|
||||||
println!("{:?}", entry.path());
|
debug!("{:?}", entry.path());
|
||||||
if let Some(ext) = entry
|
if let Some(ext) = entry
|
||||||
.path()
|
.path()
|
||||||
.extension()
|
.extension()
|
||||||
@@ -279,12 +256,15 @@ fn create_thumbnails() {
|
|||||||
let thumb_path = Path::new(thumbnail_directory).join(relative_path);
|
let thumb_path = Path::new(thumbnail_directory).join(relative_path);
|
||||||
std::fs::create_dir_all(&thumb_path.parent().unwrap())
|
std::fs::create_dir_all(&thumb_path.parent().unwrap())
|
||||||
.expect("Error creating directory");
|
.expect("Error creating directory");
|
||||||
|
|
||||||
|
debug!("Generating video thumbnail: {:?}", thumb_path);
|
||||||
generate_video_thumbnail(entry.path(), &thumb_path);
|
generate_video_thumbnail(entry.path(), &thumb_path);
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "nef"
|
ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "nef"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
error!("Unable to get extension for file: {:?}", entry.path());
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -295,7 +275,12 @@ fn create_thumbnails() {
|
|||||||
!thumb_path.exists()
|
!thumb_path.exists()
|
||||||
})
|
})
|
||||||
.map(|entry| (image::open(entry.path()), entry.path().to_path_buf()))
|
.map(|entry| (image::open(entry.path()), entry.path().to_path_buf()))
|
||||||
.filter(|(img, _)| img.is_ok())
|
.filter(|(img, path)| {
|
||||||
|
if let Err(e) = img {
|
||||||
|
error!("Unable to open image: {:?}. {}", path, e);
|
||||||
|
}
|
||||||
|
img.is_ok()
|
||||||
|
})
|
||||||
.map(|(img, path)| (img.unwrap(), path))
|
.map(|(img, path)| (img.unwrap(), path))
|
||||||
.map(|(image, path)| (image.thumbnail(200, u32::MAX), path))
|
.map(|(image, path)| (image.thumbnail(200, u32::MAX), path))
|
||||||
.map(|(image, path)| {
|
.map(|(image, path)| {
|
||||||
@@ -303,15 +288,18 @@ fn create_thumbnails() {
|
|||||||
let thumb_path = Path::new(thumbnail_directory).join(relative_path);
|
let thumb_path = Path::new(thumbnail_directory).join(relative_path);
|
||||||
std::fs::create_dir_all(&thumb_path.parent().unwrap())
|
std::fs::create_dir_all(&thumb_path.parent().unwrap())
|
||||||
.expect("There was an issue creating directory");
|
.expect("There was an issue creating directory");
|
||||||
println!("{:?}", thumb_path);
|
debug!("Saving thumbnail: {:?}", thumb_path);
|
||||||
image.save(thumb_path).expect("Failure saving thumbnail");
|
image.save(thumb_path).expect("Failure saving thumbnail");
|
||||||
})
|
})
|
||||||
.for_each(drop);
|
.for_each(drop);
|
||||||
|
|
||||||
println!("Finished");
|
debug!("Finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
|
dotenv::dotenv().ok();
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
create_thumbnails();
|
create_thumbnails();
|
||||||
|
|
||||||
std::thread::spawn(|| {
|
std::thread::spawn(|| {
|
||||||
@@ -322,11 +310,10 @@ fn main() -> std::io::Result<()> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let ev = wrx.recv_timeout(std::time::Duration::from_secs(10));
|
let ev = wrx.recv();
|
||||||
if let Ok(event) = ev {
|
if let Ok(event) = ev {
|
||||||
match event {
|
match event {
|
||||||
DebouncedEvent::Create(_) => create_thumbnails(),
|
DebouncedEvent::Create(_) | DebouncedEvent::Rename(_, _) => create_thumbnails(),
|
||||||
DebouncedEvent::Rename(_, _) => create_thumbnails(),
|
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -353,9 +340,9 @@ fn main() -> std::io::Result<()> {
|
|||||||
.service(post_add_favorite)
|
.service(post_add_favorite)
|
||||||
.app_data(app_data.clone())
|
.app_data(app_data.clone())
|
||||||
})
|
})
|
||||||
.bind(dotenv::var("BIND_URL").unwrap())?
|
.bind(dotenv::var("BIND_URL").unwrap())?
|
||||||
.bind("localhost:8088")?
|
.bind("localhost:8088")?
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
system.run()
|
system.run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::path::Path;
|
|||||||
use std::process::{Child, Command, ExitStatus, Stdio};
|
use std::process::{Child, Command, ExitStatus, Stdio};
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
use log::{debug, trace};
|
||||||
|
|
||||||
// ffmpeg -i test.mp4 -c:v h264 -flags +cgop -g 30 -hls_time 3 out.m3u8
|
// ffmpeg -i test.mp4 -c:v h264 -flags +cgop -g 30 -hls_time 3 out.m3u8
|
||||||
// ffmpeg -i "filename.mp4" -preset veryfast -c:v libx264 -f hls -hls_list_size 100 -hls_time 2 -crf 24 -vf scale=1080:-2,setsar=1:1 attempt/vid_out.m3u8
|
// ffmpeg -i "filename.mp4" -preset veryfast -c:v libx264 -f hls -hls_list_size 100 -hls_time 2 -crf 24 -vf scale=1080:-2,setsar=1:1 attempt/vid_out.m3u8
|
||||||
@@ -23,11 +24,11 @@ impl Handler<ProcessMessage> for StreamActor {
|
|||||||
type Result = Result<ExitStatus>;
|
type Result = Result<ExitStatus>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: ProcessMessage, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: ProcessMessage, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
println!("Message received");
|
trace!("Message received");
|
||||||
let mut process = msg.1;
|
let mut process = msg.1;
|
||||||
let result = process.wait();
|
let result = process.wait();
|
||||||
|
|
||||||
println!(
|
debug!(
|
||||||
"Finished waiting for: {:?}. Code: {:?}",
|
"Finished waiting for: {:?}. Code: {:?}",
|
||||||
msg.0,
|
msg.0,
|
||||||
result
|
result
|
||||||
@@ -40,7 +41,7 @@ impl Handler<ProcessMessage> for StreamActor {
|
|||||||
|
|
||||||
pub fn create_playlist(video_path: &str, playlist_file: &str) -> Result<Child> {
|
pub fn create_playlist(video_path: &str, playlist_file: &str) -> Result<Child> {
|
||||||
if Path::new(playlist_file).exists() {
|
if Path::new(playlist_file).exists() {
|
||||||
println!("Playlist already exists: {}", playlist_file);
|
debug!("Playlist already exists: {}", playlist_file);
|
||||||
return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists));
|
return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user