diff --git a/Cargo.lock b/Cargo.lock index 334757c6..830e0c4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "asynchronous-codec" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" dependencies = [ "bytes", "futures-sink", @@ -1134,7 +1134,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "ffplayout" -version = "0.20.0-beta4" +version = "0.20.0-beta5" dependencies = [ "chrono", "clap", @@ -1156,7 +1156,7 @@ dependencies = [ [[package]] name = "ffplayout-api" -version = "0.20.0-beta4" +version = "0.20.0-beta5" dependencies = [ "actix-files", "actix-multipart", @@ -1192,7 +1192,7 @@ dependencies = [ [[package]] name = "ffplayout-lib" -version = "0.20.0-beta4" +version = "0.20.0-beta5" dependencies = [ "chrono", "crossbeam-channel", @@ -3189,7 +3189,7 @@ dependencies = [ [[package]] name = "tests" -version = "0.20.0-beta4" +version = "0.20.0-beta5" dependencies = [ "chrono", "crossbeam-channel", @@ -3755,8 +3755,9 @@ checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" [[package]] name = "zeromq" -version = "0.3.3" -source = "git+https://github.com/zeromq/zmq.rs.git#7baeeffde9e4cb9741d1841cfdee5f00f354b578" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db35fbc7d9082d39a85c9831ec5dc7b7b135038d2f00bb5ff2a4c0275893da1" dependencies = [ "async-std", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 87bda8ea..86e02ee8 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-beta4" +version = "0.20.0-beta5" license = "GPL-3.0" repository = "https://github.com/ffplayout/ffplayout" authors = ["Jonathan Baecker "] diff --git a/ffplayout-api/build.rs b/ffplayout-api/build.rs index 7cd0aa07..25730784 100644 --- a/ffplayout-api/build.rs +++ b/ffplayout-api/build.rs @@ -1,11 +1,19 @@ +use std::{env, path::Path}; + use static_files::NpmBuild; fn main() -> std::io::Result<()> { - NpmBuild::new("../ffplayout-frontend") - .install()? - .run("generate")? - .target("../ffplayout-frontend/.output/public") - .change_detection() - .to_resource_dir() - .build() + let gen_path = Path::new(&env::var("OUT_DIR").unwrap()).join("generated.rs"); + + if Ok("release".to_owned()) == env::var("PROFILE") || !gen_path.is_file() { + NpmBuild::new("../ffplayout-frontend") + .install()? + .run("generate")? + .target("../ffplayout-frontend/.output/public") + .change_detection() + .to_resource_dir() + .build() + } else { + Ok(()) + } } diff --git a/ffplayout-api/src/api/auth.rs b/ffplayout-api/src/api/auth.rs index 0ea64001..4aa68bec 100644 --- a/ffplayout-api/src/api/auth.rs +++ b/ffplayout-api/src/api/auth.rs @@ -4,7 +4,7 @@ use chrono::{Duration, Utc}; use jsonwebtoken::{self, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; -use crate::utils::GlobalSettings; +use crate::utils::{GlobalSettings, Role}; // Token lifetime const JWT_EXPIRATION_DAYS: i64 = 7; @@ -13,12 +13,12 @@ const JWT_EXPIRATION_DAYS: i64 = 7; pub struct Claims { pub id: i32, pub username: String, - pub role: String, + pub role: Role, exp: i64, } impl Claims { - pub fn new(id: i32, username: String, role: String) -> Self { + pub fn new(id: i32, username: String, role: Role) -> Self { Self { id, username, diff --git a/ffplayout-api/src/api/routes.rs b/ffplayout-api/src/api/routes.rs index 7a4e8180..30b65dee 100644 --- a/ffplayout-api/src/api/routes.rs +++ b/ffplayout-api/src/api/routes.rs @@ -170,7 +170,7 @@ pub async fn login(pool: web::Data>, credentials: web::Json) { let role = handles::select_role(&conn, &user.role_id.unwrap_or_default()) .await - .unwrap_or_else(|_| "guest".to_string()); + .unwrap_or(Role::Guest); let claims = Claims::new(user.id, user.username.clone(), role.clone()); if let Ok(token) = create_jwt(claims) { @@ -340,7 +340,7 @@ async fn get_channel( /// curl -X GET http://127.0.0.1:8787/api/channels -H "Authorization: Bearer " /// ``` #[get("/channels")] -#[has_any_role("Role::Admin", type = "Role")] +#[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn get_all_channels(pool: web::Data>) -> Result { if let Ok(channel) = handles::select_all_channels(&pool.into_inner()).await { return Ok(web::Json(channel)); diff --git a/ffplayout-api/src/db/handles.rs b/ffplayout-api/src/db/handles.rs index f7ed6003..0b8bd8b5 100644 --- a/ffplayout-api/src/db/handles.rs +++ b/ffplayout-api/src/db/handles.rs @@ -13,12 +13,7 @@ use crate::db::{ db_pool, models::{Channel, TextPreset, User}, }; -use crate::utils::{db_path, local_utc_offset, GlobalSettings}; - -#[derive(Debug, sqlx::FromRow)] -struct Role { - name: String, -} +use crate::utils::{db_path, local_utc_offset, GlobalSettings, Role}; async fn create_schema(conn: &Pool) -> Result { let query = "PRAGMA foreign_keys = ON; @@ -217,11 +212,11 @@ pub async fn select_last_channel(conn: &Pool) -> Result, id: &i32) -> Result { +pub async fn select_role(conn: &Pool, id: &i32) -> Result { let query = "SELECT name FROM roles WHERE id = $1"; let result: Role = sqlx::query_as(query).bind(id).fetch_one(conn).await?; - Ok(result.name) + Ok(result) } pub async fn select_login(conn: &Pool, user: &str) -> Result { diff --git a/ffplayout-api/src/db/models.rs b/ffplayout-api/src/db/models.rs index a1984625..02c428b6 100644 --- a/ffplayout-api/src/db/models.rs +++ b/ffplayout-api/src/db/models.rs @@ -25,6 +25,7 @@ pub struct User { #[serde(skip_serializing)] pub channel_id: Option, #[sqlx(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub token: Option, } diff --git a/ffplayout-api/src/main.rs b/ffplayout-api/src/main.rs index b1050b62..19bcc5d5 100644 --- a/ffplayout-api/src/main.rs +++ b/ffplayout-api/src/main.rs @@ -16,7 +16,7 @@ pub mod utils; use api::{auth, routes::*}; use db::{db_pool, models::LoginUser}; -use utils::{args_parse::Args, control::ProcessControl, db_path, init_config, run_args, Role}; +use utils::{args_parse::Args, control::ProcessControl, db_path, init_config, run_args}; use ffplayout_lib::utils::{init_logging, PlayoutConfig}; @@ -29,7 +29,7 @@ async fn validator( // We just get permissions from JWT match auth::decode_jwt(credentials.token()).await { Ok(claims) => { - req.attach(vec![Role::set_role(&claims.role)]); + req.attach(vec![claims.role]); req.extensions_mut() .insert(LoginUser::new(claims.id, claims.username)); diff --git a/ffplayout-api/src/utils/mod.rs b/ffplayout-api/src/utils/mod.rs index fa5bbac3..062c9a64 100644 --- a/ffplayout-api/src/utils/mod.rs +++ b/ffplayout-api/src/utils/mod.rs @@ -1,9 +1,11 @@ use std::{ env, error::Error, + fmt, fs::{self, File}, io::{stdin, stdout, Write}, path::Path, + str::FromStr, }; use chrono::{format::ParseErrorKind, prelude::*}; @@ -11,9 +13,9 @@ use faccess::PathExt; use once_cell::sync::OnceCell; use path_clean::PathClean; use rpassword::read_password; -use serde::{de, Deserialize, Deserializer}; +use serde::{de, Deserialize, Deserializer, Serialize}; use simplelog::*; -use sqlx::{Pool, Sqlite}; +use sqlx::{sqlite::SqliteRow, FromRow, Pool, Row, Sqlite}; pub mod args_parse; pub mod channels; @@ -30,7 +32,7 @@ use crate::db::{ use crate::utils::{args_parse::Args, errors::ServiceError}; use ffplayout_lib::utils::{time_to_sec, PlayoutConfig}; -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum Role { Admin, User, @@ -47,6 +49,51 @@ impl Role { } } +impl FromStr for Role { + type Err = String; + + fn from_str(input: &str) -> Result { + match input { + "admin" => Ok(Self::Admin), + "user" => Ok(Self::User), + _ => Ok(Self::Guest), + } + } +} + +impl fmt::Display for Role { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Admin => write!(f, "admin"), + Self::User => write!(f, "user"), + Self::Guest => write!(f, "guest"), + } + } +} + +impl<'r> sqlx::decode::Decode<'r, ::sqlx::Sqlite> for Role +where + &'r str: sqlx::decode::Decode<'r, sqlx::Sqlite>, +{ + fn decode( + value: >::ValueRef, + ) -> Result> { + let value = <&str as sqlx::decode::Decode>::decode(value)?; + + Ok(value.parse()?) + } +} + +impl FromRow<'_, SqliteRow> for Role { + fn from_row(row: &SqliteRow) -> sqlx::Result { + match row.get("name") { + "admin" => Ok(Self::Admin), + "user" => Ok(Self::User), + _ => Ok(Self::Guest), + } + } +} + #[derive(Debug, sqlx::FromRow)] pub struct GlobalSettings { pub secret: String, diff --git a/ffplayout-engine/Cargo.toml b/ffplayout-engine/Cargo.toml index 08989b7e..c5533c37 100644 --- a/ffplayout-engine/Cargo.toml +++ b/ffplayout-engine/Cargo.toml @@ -26,7 +26,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" simplelog = { version = "0.12", features = ["paris"] } tiny_http = { version = "0.12", default-features = false } -zeromq = { git = "https://github.com/zeromq/zmq.rs.git", default-features = false, features = [ +zeromq = { version = "0.3", default-features = false, features = [ "async-std-runtime", "tcp-transport", ] } diff --git a/ffplayout-engine/src/input/playlist.rs b/ffplayout-engine/src/input/playlist.rs index d585c869..d8becd28 100644 --- a/ffplayout-engine/src/input/playlist.rs +++ b/ffplayout-engine/src/input/playlist.rs @@ -277,8 +277,9 @@ impl CurrentProgram { // de-instance node to preserve original values in list let mut node_clone = nodes[index].clone(); + node_clone.seek = time_sec + - (node_clone.begin.unwrap() - *self.playout_stat.time_shift.lock().unwrap()); - node_clone.seek = time_sec - node_clone.begin.unwrap(); self.current_node = handle_list_init( &self.config, node_clone, diff --git a/ffplayout-engine/src/output/hls.rs b/ffplayout-engine/src/output/hls.rs index 2da18f7c..6c2042ee 100644 --- a/ffplayout-engine/src/output/hls.rs +++ b/ffplayout-engine/src/output/hls.rs @@ -144,6 +144,7 @@ pub fn write_hls( let config_clone = config.clone(); let ff_log_format = format!("level+{}", config.logging.ffmpeg_level.to_lowercase()); let play_stat = playout_stat.clone(); + let play_stat2 = playout_stat.clone(); let proc_control_c = proc_control.clone(); let get_source = source_generator( @@ -177,12 +178,15 @@ pub fn write_hls( ); if config.task.enable { - let task_config = config.clone(); - let task_node = node.clone(); - let server_running = proc_control.server_is_running.load(Ordering::SeqCst); - if config.task.path.is_file() { - thread::spawn(move || task_runner::run(task_config, task_node, server_running)); + let task_config = config.clone(); + let task_node = node.clone(); + let server_running = proc_control.server_is_running.load(Ordering::SeqCst); + let stat = play_stat2.clone(); + + thread::spawn(move || { + task_runner::run(task_config, task_node, stat, server_running) + }); } else { error!( "{:?} executable not exists!", diff --git a/ffplayout-engine/src/output/mod.rs b/ffplayout-engine/src/output/mod.rs index f05b6a5a..de1e2b5e 100644 --- a/ffplayout-engine/src/output/mod.rs +++ b/ffplayout-engine/src/output/mod.rs @@ -45,6 +45,7 @@ pub fn player( let mut buffer = [0; 65088]; let mut live_on = false; let playlist_init = playout_stat.list_init.clone(); + let play_stat = playout_stat.clone(); // get source iterator let get_source = source_generator( @@ -111,12 +112,15 @@ pub fn player( ); if config.task.enable { - let task_config = config.clone(); - let task_node = node.clone(); - let server_running = proc_control.server_is_running.load(Ordering::SeqCst); - if config.task.path.is_file() { - thread::spawn(move || task_runner::run(task_config, task_node, server_running)); + let task_config = config.clone(); + let task_node = node.clone(); + let server_running = proc_control.server_is_running.load(Ordering::SeqCst); + let stat = play_stat.clone(); + + thread::spawn(move || { + task_runner::run(task_config, task_node, stat, server_running) + }); } else { error!( "{:?} executable not exists!", diff --git a/ffplayout-engine/src/rpc/server.rs b/ffplayout-engine/src/rpc/server.rs index 2a838757..ec29627b 100644 --- a/ffplayout-engine/src/rpc/server.rs +++ b/ffplayout-engine/src/rpc/server.rs @@ -376,11 +376,17 @@ fn control_text( /// media info: get infos about current clip fn media_current( config: &PlayoutConfig, + playout_stat: &PlayoutStatus, play_control: &PlayerControl, proc: &ProcessControl, ) -> Response>> { if let Some(media) = play_control.current_media.lock().unwrap().clone() { - let data_map = get_data_map(config, media, proc.server_is_running.load(Ordering::SeqCst)); + let data_map = get_data_map( + config, + media, + playout_stat, + proc.server_is_running.load(Ordering::SeqCst), + ); return json_response(data_map); }; @@ -389,14 +395,18 @@ fn media_current( } /// media info: get infos about next clip -fn media_next(config: &PlayoutConfig, play_control: &PlayerControl) -> Response>> { +fn media_next( + config: &PlayoutConfig, + playout_stat: &PlayoutStatus, + play_control: &PlayerControl, +) -> Response>> { let index = play_control.current_index.load(Ordering::SeqCst); let current_list = play_control.current_list.lock().unwrap(); if index < current_list.len() { let media = current_list[index].clone(); - let data_map = get_data_map(config, media, false); + let data_map = get_data_map(config, media, playout_stat, false); return json_response(data_map); } @@ -405,14 +415,18 @@ fn media_next(config: &PlayoutConfig, play_control: &PlayerControl) -> Response< } /// media info: get infos about last clip -fn media_last(config: &PlayoutConfig, play_control: &PlayerControl) -> Response>> { +fn media_last( + config: &PlayoutConfig, + playout_stat: &PlayoutStatus, + play_control: &PlayerControl, +) -> Response>> { let index = play_control.current_index.load(Ordering::SeqCst); let current_list = play_control.current_list.lock().unwrap(); if index > 1 && index - 2 < current_list.len() { let media = current_list[index - 2].clone(); - let data_map = get_data_map(config, media, false); + let data_map = get_data_map(config, media, playout_stat, false); return json_response(data_map); } @@ -464,13 +478,18 @@ fn build_response( } else if let Some(media_value) = data.get("media").and_then(|m| m.as_str()) { match media_value { "current" => { - let _ = request.respond(media_current(config, play_control, proc_control)); + let _ = request.respond(media_current( + config, + playout_stat, + play_control, + proc_control, + )); } "next" => { - let _ = request.respond(media_next(config, play_control)); + let _ = request.respond(media_next(config, playout_stat, play_control)); } "last" => { - let _ = request.respond(media_last(config, play_control)); + let _ = request.respond(media_last(config, playout_stat, play_control)); } _ => (), } diff --git a/ffplayout-engine/src/utils/mod.rs b/ffplayout-engine/src/utils/mod.rs index 7c7e0484..beff97ed 100644 --- a/ffplayout-engine/src/utils/mod.rs +++ b/ffplayout-engine/src/utils/mod.rs @@ -16,7 +16,7 @@ use ffplayout_lib::{ filter::Filters, utils::{ config::Template, get_sec, parse_log_level_filter, sec_to_time, time_to_sec, Media, - OutputMode::*, PlayoutConfig, ProcessMode::*, + OutputMode::*, PlayoutConfig, PlayoutStatus, ProcessMode::*, }, vec_strings, }; @@ -258,10 +258,13 @@ pub fn get_media_map(media: Media) -> Value { pub fn get_data_map( config: &PlayoutConfig, media: Media, + playout_stat: &PlayoutStatus, server_is_running: bool, ) -> Map { let mut data_map = Map::new(); - let begin = media.begin.unwrap_or(0.0); + let current_time = get_sec(); + let shift = *playout_stat.time_shift.lock().unwrap(); + let begin = media.begin.unwrap_or(0.0) - shift; data_map.insert("play_mode".to_string(), json!(config.processing.mode)); data_map.insert("ingest_runs".to_string(), json!(server_is_running)); @@ -269,7 +272,7 @@ pub fn get_data_map( data_map.insert("start_sec".to_string(), json!(begin)); if begin > 0.0 { - let played_time = get_sec() - begin; + let played_time = current_time - begin; let remaining_time = media.out - played_time; data_map.insert("start_time".to_string(), json!(sec_to_time(begin))); diff --git a/ffplayout-engine/src/utils/task_runner.rs b/ffplayout-engine/src/utils/task_runner.rs index 006c6f0f..db18c277 100644 --- a/ffplayout-engine/src/utils/task_runner.rs +++ b/ffplayout-engine/src/utils/task_runner.rs @@ -3,10 +3,11 @@ use std::process::Command; use simplelog::*; use crate::utils::get_data_map; -use ffplayout_lib::utils::{config::PlayoutConfig, Media}; +use ffplayout_lib::utils::{config::PlayoutConfig, Media, PlayoutStatus}; -pub fn run(config: PlayoutConfig, node: Media, server_running: bool) { - let obj = serde_json::to_string(&get_data_map(&config, node, server_running)).unwrap(); +pub fn run(config: PlayoutConfig, node: Media, playout_stat: PlayoutStatus, server_running: bool) { + let obj = + serde_json::to_string(&get_data_map(&config, node, &playout_stat, server_running)).unwrap(); trace!("Run task: {obj}"); match Command::new(config.task.path).arg(obj).spawn() { diff --git a/ffplayout-frontend b/ffplayout-frontend index 0898bcb2..8f615c35 160000 --- a/ffplayout-frontend +++ b/ffplayout-frontend @@ -1 +1 @@ -Subproject commit 0898bcb2413408cca47707db36f00c74ce55c456 +Subproject commit 8f615c358290b263244f52d10f6783ac39c6948c