From b475854578d36c51aa7b30043de28ff2e10ab8ad Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Mon, 30 Oct 2023 21:55:57 +0100 Subject: [PATCH 1/3] add file proxy --- Cargo.lock | 8 +++--- Cargo.toml | 2 +- ffplayout-api/src/api/routes.rs | 46 ++++++++++++++++++++++++++++----- ffplayout-api/src/main.rs | 15 +++++------ 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4929b47f..08e3b438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1112,7 +1112,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "ffplayout" -version = "0.20.0-beta3" +version = "0.20.0-beta4" dependencies = [ "chrono", "clap", @@ -1134,7 +1134,7 @@ dependencies = [ [[package]] name = "ffplayout-api" -version = "0.20.0-beta3" +version = "0.20.0-beta4" dependencies = [ "actix-files", "actix-multipart", @@ -1167,7 +1167,7 @@ dependencies = [ [[package]] name = "ffplayout-lib" -version = "0.20.0-beta3" +version = "0.20.0-beta4" dependencies = [ "chrono", "crossbeam-channel", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "tests" -version = "0.20.0-beta3" +version = "0.20.0-beta4" dependencies = [ "chrono", "crossbeam-channel", diff --git a/Cargo.toml b/Cargo.toml index 4ad5672f..87bda8ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"] resolver = "2" [workspace.package] -version = "0.20.0-beta3" +version = "0.20.0-beta4" license = "GPL-3.0" repository = "https://github.com/ffplayout/ffplayout" authors = ["Jonathan Baecker "] diff --git a/ffplayout-api/src/api/routes.rs b/ffplayout-api/src/api/routes.rs index 776ab6fb..8d718061 100644 --- a/ffplayout-api/src/api/routes.rs +++ b/ffplayout-api/src/api/routes.rs @@ -8,11 +8,20 @@ /// /// For all endpoints an (Bearer) authentication is required.\ /// `{id}` represent the channel id, and at default is 1. -use std::{collections::HashMap, env, fs, path::PathBuf}; +use std::{collections::HashMap, env, fs, path::PathBuf, sync::{Arc, Mutex}}; +use actix_files; use actix_multipart::Multipart; -use actix_web::{delete, get, http::StatusCode, patch, post, put, web, HttpResponse, Responder}; +use actix_web::{ + delete, get, + http::{ + header::{ContentDisposition, DispositionType}, + StatusCode, + }, + patch, post, put, web, Error, HttpRequest, HttpResponse, Responder, +}; use actix_web_grants::{permissions::AuthDetails, proc_macro::has_any_role}; + use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, SaltString}, Argon2, PasswordHasher, PasswordVerifier, @@ -37,7 +46,7 @@ use crate::utils::{ }, naive_date_time_from_str, playlist::{delete_playlist, generate_playlist, read_playlist, write_playlist}, - playout_config, read_log_file, read_playout_config, Role, + read_log_file, read_playout_config, Role, }; use crate::{ auth::{create_jwt, Claims}, @@ -438,6 +447,7 @@ async fn update_playout_config( pool: web::Data>, id: web::Path, data: web::Json, + config: web::Data>>, ) -> Result { if let Ok(channel) = handles::select_channel(&pool.into_inner(), &id).await { if let Ok(f) = std::fs::OpenOptions::new() @@ -446,6 +456,7 @@ async fn update_playout_config( .open(channel.config_path) { serde_yaml::to_writer(f, &data).unwrap(); + *config.lock().unwrap() = data.into_inner(); return Ok("Update playout config success."); } else { @@ -765,8 +776,10 @@ pub async fn gen_playlist( pool: web::Data>, params: web::Path<(i32, String)>, data: Option>, + global_config: web::Data>>, ) -> Result { - let (mut config, channel) = playout_config(&pool.into_inner(), ¶ms.0).await?; + let channel = handles::select_channel(&pool.into_inner(), ¶ms.0).await?; + let mut config = global_config.lock().unwrap(); config.general.generate = Some(vec![params.1.clone()]); if let Some(obj) = data { @@ -785,7 +798,7 @@ pub async fn gen_playlist( config.general.template = obj.template.clone(); } - match generate_playlist(config, channel.name).await { + match generate_playlist(config.to_owned(), channel.name).await { Ok(playlist) => Ok(web::Json(playlist)), Err(e) => Err(e), } @@ -919,6 +932,23 @@ async fn save_file( upload(&pool.into_inner(), *id, payload, &obj.path, false).await } +#[get("/file/{id}/{filename:.*}")] +#[has_any_role("Role::Admin", "Role::User", type = "Role")] +async fn get_file(req: HttpRequest, config: web::Data>>,) -> Result { + let storage_path = &config.lock().unwrap().storage.path; + let file_path = req.match_info().query("filename"); + let (path, _, _) = norm_abs_path(storage_path, file_path); + println!("{path:?}"); + + let file = actix_files::NamedFile::open(path)?; + Ok(file + .use_last_modified(true) + .set_content_disposition(ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![], + })) +} + /// **Import playlist** /// /// Import text/m3u file and convert it to a playlist @@ -935,11 +965,12 @@ async fn import_playlist( id: web::Path, payload: Multipart, obj: web::Query, + global_config: web::Data>>, ) -> Result { let file = obj.file.file_name().unwrap_or_default(); let path = env::temp_dir().join(file); - let (config, _) = playout_config(&pool.clone().into_inner(), &id).await?; let channel = handles::select_channel(&pool.clone().into_inner(), &id).await?; + let config = global_config.lock().unwrap(); upload(&pool.into_inner(), *id, payload, &path, true).await?; import_file(&config, &obj.date, Some(channel.name), &path)?; @@ -977,8 +1008,9 @@ async fn get_program( pool: web::Data>, id: web::Path, obj: web::Query, + global_config: web::Data>>, ) -> Result { - let (config, _) = playout_config(&pool.clone().into_inner(), &id).await?; + let config = global_config.lock().unwrap(); let start_sec = config.playlist.start_sec.unwrap(); let mut days = 0; let mut program = vec![]; diff --git a/ffplayout-api/src/main.rs b/ffplayout-api/src/main.rs index 11b6820d..cce05395 100644 --- a/ffplayout-api/src/main.rs +++ b/ffplayout-api/src/main.rs @@ -1,4 +1,4 @@ -use std::{path::Path, process::exit}; +use std::{path::Path, process::exit, sync::{Arc, Mutex}}; use actix_files::Files; use actix_web::{dev::ServiceRequest, middleware, web, App, Error, HttpMessage, HttpServer}; @@ -14,14 +14,7 @@ pub mod utils; use api::{ auth, - routes::{ - add_channel, add_dir, add_preset, add_user, control_playout, del_playlist, delete_preset, - file_browser, gen_playlist, get_all_channels, get_channel, get_log, get_playlist, - get_playout_config, get_presets, get_program, get_user, import_playlist, login, - media_current, media_last, media_next, move_rename, patch_channel, process_control, remove, - remove_channel, save_file, save_playlist, send_text_message, update_playout_config, - update_preset, update_user, - }, + routes::*, }; use db::{db_pool, models::LoginUser}; use utils::{args_parse::Args, control::ProcessControl, db_path, init_config, run_args, Role}; @@ -92,6 +85,7 @@ async fn main() -> std::io::Result<()> { let addr = ip_port[0]; let port = ip_port[1].parse::().unwrap(); let engine_process = web::Data::new(ProcessControl::new()); + let global_config = Arc::new(Mutex::new(config)); info!("running ffplayout API, listen on {conn}"); @@ -99,9 +93,11 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { let auth = HttpAuthentication::bearer(validator); let db_pool = web::Data::new(pool.clone()); + let global = web::Data::new(global_config.clone()); App::new() .app_data(db_pool) + .app_data(global) .app_data(engine_process.clone()) .wrap(middleware::Logger::default()) .service(login) @@ -138,6 +134,7 @@ async fn main() -> std::io::Result<()> { .service(move_rename) .service(remove) .service(save_file) + .service(get_file) .service(import_playlist) .service(get_program), ) From 14a86fedbcc2800d81cde2a4d229307d6d266ef1 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Mon, 30 Oct 2023 23:03:53 +0100 Subject: [PATCH 2/3] no auth for file route, get config from channel id, (revert to old state) --- docs/api.md | 8 +++++++ ffplayout-api/src/api/routes.rs | 40 +++++++++++++++++-------------- ffplayout-api/src/main.rs | 12 +++------- ffplayout-api/src/utils/errors.rs | 6 +++++ 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/docs/api.md b/docs/api.md index 38c76b11..c05574d4 100644 --- a/docs/api.md +++ b/docs/api.md @@ -328,6 +328,14 @@ curl -X PUT http://127.0.0.1:8787/api/file/1/upload/ -H 'Authorization: Bearer < -F "file=@file.mp4" ``` +**Get File** + +Can be used for preview video files + +```BASH +curl -X GET http://127.0.0.1:8787/file/1/path/to/file.mp4 +``` + **Import playlist** Import text/m3u file and convert it to a playlist diff --git a/ffplayout-api/src/api/routes.rs b/ffplayout-api/src/api/routes.rs index 8d718061..7a4e8180 100644 --- a/ffplayout-api/src/api/routes.rs +++ b/ffplayout-api/src/api/routes.rs @@ -8,7 +8,7 @@ /// /// For all endpoints an (Bearer) authentication is required.\ /// `{id}` represent the channel id, and at default is 1. -use std::{collections::HashMap, env, fs, path::PathBuf, sync::{Arc, Mutex}}; +use std::{collections::HashMap, env, fs, path::PathBuf}; use actix_files; use actix_multipart::Multipart; @@ -18,7 +18,7 @@ use actix_web::{ header::{ContentDisposition, DispositionType}, StatusCode, }, - patch, post, put, web, Error, HttpRequest, HttpResponse, Responder, + patch, post, put, web, HttpRequest, HttpResponse, Responder, }; use actix_web_grants::{permissions::AuthDetails, proc_macro::has_any_role}; @@ -46,7 +46,7 @@ use crate::utils::{ }, naive_date_time_from_str, playlist::{delete_playlist, generate_playlist, read_playlist, write_playlist}, - read_log_file, read_playout_config, Role, + playout_config, read_log_file, read_playout_config, Role, }; use crate::{ auth::{create_jwt, Claims}, @@ -447,7 +447,6 @@ async fn update_playout_config( pool: web::Data>, id: web::Path, data: web::Json, - config: web::Data>>, ) -> Result { if let Ok(channel) = handles::select_channel(&pool.into_inner(), &id).await { if let Ok(f) = std::fs::OpenOptions::new() @@ -456,7 +455,6 @@ async fn update_playout_config( .open(channel.config_path) { serde_yaml::to_writer(f, &data).unwrap(); - *config.lock().unwrap() = data.into_inner(); return Ok("Update playout config success."); } else { @@ -776,10 +774,8 @@ pub async fn gen_playlist( pool: web::Data>, params: web::Path<(i32, String)>, data: Option>, - global_config: web::Data>>, ) -> Result { - let channel = handles::select_channel(&pool.into_inner(), ¶ms.0).await?; - let mut config = global_config.lock().unwrap(); + let (mut config, channel) = playout_config(&pool.into_inner(), ¶ms.0).await?; config.general.generate = Some(vec![params.1.clone()]); if let Some(obj) = data { @@ -932,15 +928,25 @@ async fn save_file( upload(&pool.into_inner(), *id, payload, &obj.path, false).await } +/// **Get File** +/// +/// Can be used for preview video files +/// +/// ```BASH +/// curl -X GET http://127.0.0.1:8787/file/1/path/to/file.mp4 +/// ``` #[get("/file/{id}/{filename:.*}")] -#[has_any_role("Role::Admin", "Role::User", type = "Role")] -async fn get_file(req: HttpRequest, config: web::Data>>,) -> Result { - let storage_path = &config.lock().unwrap().storage.path; +async fn get_file( + pool: web::Data>, + req: HttpRequest, +) -> Result { + let id: i32 = req.match_info().query("id").parse()?; + let (config, _) = playout_config(&pool.into_inner(), &id).await?; + let storage_path = config.storage.path; let file_path = req.match_info().query("filename"); - let (path, _, _) = norm_abs_path(storage_path, file_path); - println!("{path:?}"); - + let (path, _, _) = norm_abs_path(&storage_path, file_path); let file = actix_files::NamedFile::open(path)?; + Ok(file .use_last_modified(true) .set_content_disposition(ContentDisposition { @@ -965,12 +971,11 @@ async fn import_playlist( id: web::Path, payload: Multipart, obj: web::Query, - global_config: web::Data>>, ) -> Result { let file = obj.file.file_name().unwrap_or_default(); let path = env::temp_dir().join(file); + let (config, _) = playout_config(&pool.clone().into_inner(), &id).await?; let channel = handles::select_channel(&pool.clone().into_inner(), &id).await?; - let config = global_config.lock().unwrap(); upload(&pool.into_inner(), *id, payload, &path, true).await?; import_file(&config, &obj.date, Some(channel.name), &path)?; @@ -1008,9 +1013,8 @@ async fn get_program( pool: web::Data>, id: web::Path, obj: web::Query, - global_config: web::Data>>, ) -> Result { - let config = global_config.lock().unwrap(); + let (config, _) = playout_config(&pool.clone().into_inner(), &id).await?; let start_sec = config.playlist.start_sec.unwrap(); let mut days = 0; let mut program = vec![]; diff --git a/ffplayout-api/src/main.rs b/ffplayout-api/src/main.rs index cce05395..904f3881 100644 --- a/ffplayout-api/src/main.rs +++ b/ffplayout-api/src/main.rs @@ -1,4 +1,4 @@ -use std::{path::Path, process::exit, sync::{Arc, Mutex}}; +use std::{path::Path, process::exit}; use actix_files::Files; use actix_web::{dev::ServiceRequest, middleware, web, App, Error, HttpMessage, HttpServer}; @@ -12,10 +12,7 @@ pub mod api; pub mod db; pub mod utils; -use api::{ - auth, - routes::*, -}; +use api::{auth, routes::*}; use db::{db_pool, models::LoginUser}; use utils::{args_parse::Args, control::ProcessControl, db_path, init_config, run_args, Role}; @@ -85,7 +82,6 @@ async fn main() -> std::io::Result<()> { let addr = ip_port[0]; let port = ip_port[1].parse::().unwrap(); let engine_process = web::Data::new(ProcessControl::new()); - let global_config = Arc::new(Mutex::new(config)); info!("running ffplayout API, listen on {conn}"); @@ -93,11 +89,9 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { let auth = HttpAuthentication::bearer(validator); let db_pool = web::Data::new(pool.clone()); - let global = web::Data::new(global_config.clone()); App::new() .app_data(db_pool) - .app_data(global) .app_data(engine_process.clone()) .wrap(middleware::Logger::default()) .service(login) @@ -134,10 +128,10 @@ async fn main() -> std::io::Result<()> { .service(move_rename) .service(remove) .service(save_file) - .service(get_file) .service(import_playlist) .service(get_program), ) + .service(get_file) .service(Files::new("/", public_path()).index_file("index.html")) }) .bind((addr, port))? diff --git a/ffplayout-api/src/utils/errors.rs b/ffplayout-api/src/utils/errors.rs index 675f9622..15189bfd 100644 --- a/ffplayout-api/src/utils/errors.rs +++ b/ffplayout-api/src/utils/errors.rs @@ -64,6 +64,12 @@ impl From for ServiceError { } } +impl From for ServiceError { + fn from(err: std::num::ParseIntError) -> ServiceError { + ServiceError::BadRequest(err.to_string()) + } +} + impl From for ServiceError { fn from(err: actix_web::error::BlockingError) -> ServiceError { ServiceError::BadRequest(err.to_string()) From d046f89fd48b3d9528479cc345e8455156fd75ca Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Mon, 30 Oct 2023 23:05:19 +0100 Subject: [PATCH 3/3] preview file from storage path --- ffplayout-frontend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffplayout-frontend b/ffplayout-frontend index 50bee93c..d00ffcab 160000 --- a/ffplayout-frontend +++ b/ffplayout-frontend @@ -1 +1 @@ -Subproject commit 50bee93c8555b14181864a654239f7e68c50cafb +Subproject commit d00ffcabeaafc98169e6fe3ad163e226460048f4