Use enum for Role everywhere, fix time shift, fix #433, get config also as normal user

This commit is contained in:
jb-alvarado 2023-11-02 13:40:56 +01:00
parent 809b649226
commit 7d3173533f
17 changed files with 145 additions and 61 deletions

17
Cargo.lock generated
View File

@ -565,9 +565,9 @@ dependencies = [
[[package]] [[package]]
name = "asynchronous-codec" name = "asynchronous-codec"
version = "0.6.2" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-sink", "futures-sink",
@ -1134,7 +1134,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]] [[package]]
name = "ffplayout" name = "ffplayout"
version = "0.20.0-beta4" version = "0.20.0-beta5"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
@ -1156,7 +1156,7 @@ dependencies = [
[[package]] [[package]]
name = "ffplayout-api" name = "ffplayout-api"
version = "0.20.0-beta4" version = "0.20.0-beta5"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-multipart", "actix-multipart",
@ -1192,7 +1192,7 @@ dependencies = [
[[package]] [[package]]
name = "ffplayout-lib" name = "ffplayout-lib"
version = "0.20.0-beta4" version = "0.20.0-beta5"
dependencies = [ dependencies = [
"chrono", "chrono",
"crossbeam-channel", "crossbeam-channel",
@ -3189,7 +3189,7 @@ dependencies = [
[[package]] [[package]]
name = "tests" name = "tests"
version = "0.20.0-beta4" version = "0.20.0-beta5"
dependencies = [ dependencies = [
"chrono", "chrono",
"crossbeam-channel", "crossbeam-channel",
@ -3755,8 +3755,9 @@ checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
[[package]] [[package]]
name = "zeromq" name = "zeromq"
version = "0.3.3" version = "0.3.4"
source = "git+https://github.com/zeromq/zmq.rs.git#7baeeffde9e4cb9741d1841cfdee5f00f354b578" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2db35fbc7d9082d39a85c9831ec5dc7b7b135038d2f00bb5ff2a4c0275893da1"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",

View File

@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "0.20.0-beta4" version = "0.20.0-beta5"
license = "GPL-3.0" license = "GPL-3.0"
repository = "https://github.com/ffplayout/ffplayout" repository = "https://github.com/ffplayout/ffplayout"
authors = ["Jonathan Baecker <jonbae77@gmail.com>"] authors = ["Jonathan Baecker <jonbae77@gmail.com>"]

View File

@ -1,6 +1,11 @@
use std::{env, path::Path};
use static_files::NpmBuild; use static_files::NpmBuild;
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
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") NpmBuild::new("../ffplayout-frontend")
.install()? .install()?
.run("generate")? .run("generate")?
@ -8,4 +13,7 @@ fn main() -> std::io::Result<()> {
.change_detection() .change_detection()
.to_resource_dir() .to_resource_dir()
.build() .build()
} else {
Ok(())
}
} }

View File

@ -4,7 +4,7 @@ use chrono::{Duration, Utc};
use jsonwebtoken::{self, DecodingKey, EncodingKey, Header, Validation}; use jsonwebtoken::{self, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::utils::GlobalSettings; use crate::utils::{GlobalSettings, Role};
// Token lifetime // Token lifetime
const JWT_EXPIRATION_DAYS: i64 = 7; const JWT_EXPIRATION_DAYS: i64 = 7;
@ -13,12 +13,12 @@ const JWT_EXPIRATION_DAYS: i64 = 7;
pub struct Claims { pub struct Claims {
pub id: i32, pub id: i32,
pub username: String, pub username: String,
pub role: String, pub role: Role,
exp: i64, exp: i64,
} }
impl Claims { impl Claims {
pub fn new(id: i32, username: String, role: String) -> Self { pub fn new(id: i32, username: String, role: Role) -> Self {
Self { Self {
id, id,
username, username,

View File

@ -170,7 +170,7 @@ pub async fn login(pool: web::Data<Pool<Sqlite>>, credentials: web::Json<User>)
{ {
let role = handles::select_role(&conn, &user.role_id.unwrap_or_default()) let role = handles::select_role(&conn, &user.role_id.unwrap_or_default())
.await .await
.unwrap_or_else(|_| "guest".to_string()); .unwrap_or(Role::Guest);
let claims = Claims::new(user.id, user.username.clone(), role.clone()); let claims = Claims::new(user.id, user.username.clone(), role.clone());
if let Ok(token) = create_jwt(claims) { 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 <TOKEN>" /// curl -X GET http://127.0.0.1:8787/api/channels -H "Authorization: Bearer <TOKEN>"
/// ``` /// ```
#[get("/channels")] #[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<Pool<Sqlite>>) -> Result<impl Responder, ServiceError> { async fn get_all_channels(pool: web::Data<Pool<Sqlite>>) -> Result<impl Responder, ServiceError> {
if let Ok(channel) = handles::select_all_channels(&pool.into_inner()).await { if let Ok(channel) = handles::select_all_channels(&pool.into_inner()).await {
return Ok(web::Json(channel)); return Ok(web::Json(channel));

View File

@ -13,12 +13,7 @@ use crate::db::{
db_pool, db_pool,
models::{Channel, TextPreset, User}, models::{Channel, TextPreset, User},
}; };
use crate::utils::{db_path, local_utc_offset, GlobalSettings}; use crate::utils::{db_path, local_utc_offset, GlobalSettings, Role};
#[derive(Debug, sqlx::FromRow)]
struct Role {
name: String,
}
async fn create_schema(conn: &Pool<Sqlite>) -> Result<SqliteQueryResult, sqlx::Error> { async fn create_schema(conn: &Pool<Sqlite>) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "PRAGMA foreign_keys = ON; let query = "PRAGMA foreign_keys = ON;
@ -217,11 +212,11 @@ pub async fn select_last_channel(conn: &Pool<Sqlite>) -> Result<i32, sqlx::Error
sqlx::query_scalar(query).fetch_one(conn).await sqlx::query_scalar(query).fetch_one(conn).await
} }
pub async fn select_role(conn: &Pool<Sqlite>, id: &i32) -> Result<String, sqlx::Error> { pub async fn select_role(conn: &Pool<Sqlite>, id: &i32) -> Result<Role, sqlx::Error> {
let query = "SELECT name FROM roles WHERE id = $1"; let query = "SELECT name FROM roles WHERE id = $1";
let result: Role = sqlx::query_as(query).bind(id).fetch_one(conn).await?; 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<Sqlite>, user: &str) -> Result<User, sqlx::Error> { pub async fn select_login(conn: &Pool<Sqlite>, user: &str) -> Result<User, sqlx::Error> {

View File

@ -25,6 +25,7 @@ pub struct User {
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub channel_id: Option<i32>, pub channel_id: Option<i32>,
#[sqlx(default)] #[sqlx(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub token: Option<String>, pub token: Option<String>,
} }

View File

@ -16,7 +16,7 @@ pub mod utils;
use api::{auth, routes::*}; use api::{auth, routes::*};
use db::{db_pool, models::LoginUser}; 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}; use ffplayout_lib::utils::{init_logging, PlayoutConfig};
@ -29,7 +29,7 @@ async fn validator(
// We just get permissions from JWT // We just get permissions from JWT
match auth::decode_jwt(credentials.token()).await { match auth::decode_jwt(credentials.token()).await {
Ok(claims) => { Ok(claims) => {
req.attach(vec![Role::set_role(&claims.role)]); req.attach(vec![claims.role]);
req.extensions_mut() req.extensions_mut()
.insert(LoginUser::new(claims.id, claims.username)); .insert(LoginUser::new(claims.id, claims.username));

View File

@ -1,9 +1,11 @@
use std::{ use std::{
env, env,
error::Error, error::Error,
fmt,
fs::{self, File}, fs::{self, File},
io::{stdin, stdout, Write}, io::{stdin, stdout, Write},
path::Path, path::Path,
str::FromStr,
}; };
use chrono::{format::ParseErrorKind, prelude::*}; use chrono::{format::ParseErrorKind, prelude::*};
@ -11,9 +13,9 @@ use faccess::PathExt;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use path_clean::PathClean; use path_clean::PathClean;
use rpassword::read_password; use rpassword::read_password;
use serde::{de, Deserialize, Deserializer}; use serde::{de, Deserialize, Deserializer, Serialize};
use simplelog::*; use simplelog::*;
use sqlx::{Pool, Sqlite}; use sqlx::{sqlite::SqliteRow, FromRow, Pool, Row, Sqlite};
pub mod args_parse; pub mod args_parse;
pub mod channels; pub mod channels;
@ -30,7 +32,7 @@ use crate::db::{
use crate::utils::{args_parse::Args, errors::ServiceError}; use crate::utils::{args_parse::Args, errors::ServiceError};
use ffplayout_lib::utils::{time_to_sec, PlayoutConfig}; use ffplayout_lib::utils::{time_to_sec, PlayoutConfig};
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Role { pub enum Role {
Admin, Admin,
User, User,
@ -47,6 +49,51 @@ impl Role {
} }
} }
impl FromStr for Role {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
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: <sqlx::Sqlite as sqlx::database::HasValueRef<'r>>::ValueRef,
) -> Result<Role, Box<dyn Error + 'static + Send + Sync>> {
let value = <&str as sqlx::decode::Decode<sqlx::Sqlite>>::decode(value)?;
Ok(value.parse()?)
}
}
impl FromRow<'_, SqliteRow> for Role {
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
match row.get("name") {
"admin" => Ok(Self::Admin),
"user" => Ok(Self::User),
_ => Ok(Self::Guest),
}
}
}
#[derive(Debug, sqlx::FromRow)] #[derive(Debug, sqlx::FromRow)]
pub struct GlobalSettings { pub struct GlobalSettings {
pub secret: String, pub secret: String,

View File

@ -26,7 +26,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
simplelog = { version = "0.12", features = ["paris"] } simplelog = { version = "0.12", features = ["paris"] }
tiny_http = { version = "0.12", default-features = false } 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", "async-std-runtime",
"tcp-transport", "tcp-transport",
] } ] }

View File

@ -277,8 +277,9 @@ impl CurrentProgram {
// de-instance node to preserve original values in list // de-instance node to preserve original values in list
let mut node_clone = nodes[index].clone(); 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.current_node = handle_list_init(
&self.config, &self.config,
node_clone, node_clone,

View File

@ -144,6 +144,7 @@ pub fn write_hls(
let config_clone = config.clone(); let config_clone = config.clone();
let ff_log_format = format!("level+{}", config.logging.ffmpeg_level.to_lowercase()); let ff_log_format = format!("level+{}", config.logging.ffmpeg_level.to_lowercase());
let play_stat = playout_stat.clone(); let play_stat = playout_stat.clone();
let play_stat2 = playout_stat.clone();
let proc_control_c = proc_control.clone(); let proc_control_c = proc_control.clone();
let get_source = source_generator( let get_source = source_generator(
@ -177,12 +178,15 @@ pub fn write_hls(
); );
if config.task.enable { if config.task.enable {
if config.task.path.is_file() {
let task_config = config.clone(); let task_config = config.clone();
let task_node = node.clone(); let task_node = node.clone();
let server_running = proc_control.server_is_running.load(Ordering::SeqCst); let server_running = proc_control.server_is_running.load(Ordering::SeqCst);
let stat = play_stat2.clone();
if config.task.path.is_file() { thread::spawn(move || {
thread::spawn(move || task_runner::run(task_config, task_node, server_running)); task_runner::run(task_config, task_node, stat, server_running)
});
} else { } else {
error!( error!(
"<bright-blue>{:?}</> executable not exists!", "<bright-blue>{:?}</> executable not exists!",

View File

@ -45,6 +45,7 @@ pub fn player(
let mut buffer = [0; 65088]; let mut buffer = [0; 65088];
let mut live_on = false; let mut live_on = false;
let playlist_init = playout_stat.list_init.clone(); let playlist_init = playout_stat.list_init.clone();
let play_stat = playout_stat.clone();
// get source iterator // get source iterator
let get_source = source_generator( let get_source = source_generator(
@ -111,12 +112,15 @@ pub fn player(
); );
if config.task.enable { if config.task.enable {
if config.task.path.is_file() {
let task_config = config.clone(); let task_config = config.clone();
let task_node = node.clone(); let task_node = node.clone();
let server_running = proc_control.server_is_running.load(Ordering::SeqCst); let server_running = proc_control.server_is_running.load(Ordering::SeqCst);
let stat = play_stat.clone();
if config.task.path.is_file() { thread::spawn(move || {
thread::spawn(move || task_runner::run(task_config, task_node, server_running)); task_runner::run(task_config, task_node, stat, server_running)
});
} else { } else {
error!( error!(
"<bright-blue>{:?}</> executable not exists!", "<bright-blue>{:?}</> executable not exists!",

View File

@ -376,11 +376,17 @@ fn control_text(
/// media info: get infos about current clip /// media info: get infos about current clip
fn media_current( fn media_current(
config: &PlayoutConfig, config: &PlayoutConfig,
playout_stat: &PlayoutStatus,
play_control: &PlayerControl, play_control: &PlayerControl,
proc: &ProcessControl, proc: &ProcessControl,
) -> Response<Cursor<Vec<u8>>> { ) -> Response<Cursor<Vec<u8>>> {
if let Some(media) = play_control.current_media.lock().unwrap().clone() { 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); return json_response(data_map);
}; };
@ -389,14 +395,18 @@ fn media_current(
} }
/// media info: get infos about next clip /// media info: get infos about next clip
fn media_next(config: &PlayoutConfig, play_control: &PlayerControl) -> Response<Cursor<Vec<u8>>> { fn media_next(
config: &PlayoutConfig,
playout_stat: &PlayoutStatus,
play_control: &PlayerControl,
) -> Response<Cursor<Vec<u8>>> {
let index = play_control.current_index.load(Ordering::SeqCst); let index = play_control.current_index.load(Ordering::SeqCst);
let current_list = play_control.current_list.lock().unwrap(); let current_list = play_control.current_list.lock().unwrap();
if index < current_list.len() { if index < current_list.len() {
let media = current_list[index].clone(); 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); 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 /// media info: get infos about last clip
fn media_last(config: &PlayoutConfig, play_control: &PlayerControl) -> Response<Cursor<Vec<u8>>> { fn media_last(
config: &PlayoutConfig,
playout_stat: &PlayoutStatus,
play_control: &PlayerControl,
) -> Response<Cursor<Vec<u8>>> {
let index = play_control.current_index.load(Ordering::SeqCst); let index = play_control.current_index.load(Ordering::SeqCst);
let current_list = play_control.current_list.lock().unwrap(); let current_list = play_control.current_list.lock().unwrap();
if index > 1 && index - 2 < current_list.len() { if index > 1 && index - 2 < current_list.len() {
let media = current_list[index - 2].clone(); 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); 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()) { } else if let Some(media_value) = data.get("media").and_then(|m| m.as_str()) {
match media_value { match media_value {
"current" => { "current" => {
let _ = request.respond(media_current(config, play_control, proc_control)); let _ = request.respond(media_current(
config,
playout_stat,
play_control,
proc_control,
));
} }
"next" => { "next" => {
let _ = request.respond(media_next(config, play_control)); let _ = request.respond(media_next(config, playout_stat, play_control));
} }
"last" => { "last" => {
let _ = request.respond(media_last(config, play_control)); let _ = request.respond(media_last(config, playout_stat, play_control));
} }
_ => (), _ => (),
} }

View File

@ -16,7 +16,7 @@ use ffplayout_lib::{
filter::Filters, filter::Filters,
utils::{ utils::{
config::Template, get_sec, parse_log_level_filter, sec_to_time, time_to_sec, Media, config::Template, get_sec, parse_log_level_filter, sec_to_time, time_to_sec, Media,
OutputMode::*, PlayoutConfig, ProcessMode::*, OutputMode::*, PlayoutConfig, PlayoutStatus, ProcessMode::*,
}, },
vec_strings, vec_strings,
}; };
@ -258,10 +258,13 @@ pub fn get_media_map(media: Media) -> Value {
pub fn get_data_map( pub fn get_data_map(
config: &PlayoutConfig, config: &PlayoutConfig,
media: Media, media: Media,
playout_stat: &PlayoutStatus,
server_is_running: bool, server_is_running: bool,
) -> Map<String, Value> { ) -> Map<String, Value> {
let mut data_map = Map::new(); 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("play_mode".to_string(), json!(config.processing.mode));
data_map.insert("ingest_runs".to_string(), json!(server_is_running)); 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)); data_map.insert("start_sec".to_string(), json!(begin));
if begin > 0.0 { if begin > 0.0 {
let played_time = get_sec() - begin; let played_time = current_time - begin;
let remaining_time = media.out - played_time; let remaining_time = media.out - played_time;
data_map.insert("start_time".to_string(), json!(sec_to_time(begin))); data_map.insert("start_time".to_string(), json!(sec_to_time(begin)));

View File

@ -3,10 +3,11 @@ use std::process::Command;
use simplelog::*; use simplelog::*;
use crate::utils::get_data_map; 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) { pub fn run(config: PlayoutConfig, node: Media, playout_stat: PlayoutStatus, server_running: bool) {
let obj = serde_json::to_string(&get_data_map(&config, node, server_running)).unwrap(); let obj =
serde_json::to_string(&get_data_map(&config, node, &playout_stat, server_running)).unwrap();
trace!("Run task: {obj}"); trace!("Run task: {obj}");
match Command::new(config.task.path).arg(obj).spawn() { match Command::new(config.task.path).arg(obj).spawn() {

@ -1 +1 @@
Subproject commit 0898bcb2413408cca47707db36f00c74ce55c456 Subproject commit 8f615c358290b263244f52d10f6783ac39c6948c