From abd78d89bf0acc989220094c412c031068644fba Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Mon, 19 Aug 2024 12:06:48 +0200 Subject: [PATCH 01/10] add features --- ffplayout/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffplayout/Cargo.toml b/ffplayout/Cargo.toml index 8751f0d8..2fce8180 100644 --- a/ffplayout/Cargo.toml +++ b/ffplayout/Cargo.toml @@ -24,7 +24,7 @@ argon2 = "0.5" chrono = { version = "0.4", default-features = false, features = ["clock", "std", "serde"] } clap = { version = "4.3", features = ["derive", "env"] } crossbeam-channel = "0.5" -derive_more = "1" +derive_more = { version = "1", features = ["display"] } faccess = "0.2" ffprobe = "0.4" flexi_logger = { version = "0.28", features = ["kv", "colors"] } From b63cdb988891326b52ab729cd3f21109f177d4d8 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Wed, 21 Aug 2024 10:20:54 +0200 Subject: [PATCH 02/10] check and set db permissions --- Cargo.lock | 109 +++++++++++++++++++++++------- Cargo.toml | 2 +- docs/install.md | 8 +-- ffplayout/Cargo.toml | 1 + ffplayout/src/main.rs | 2 +- ffplayout/src/utils/args_parse.rs | 61 ++++++++++++++++- 6 files changed, 148 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10a22acc..f89de5c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,6 +348,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -528,7 +534,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -649,6 +655,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "change-detection" version = "1.2.0" @@ -1097,7 +1109,7 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "ffplayout" -version = "0.24.0-beta1" +version = "0.24.0-beta2" dependencies = [ "actix-files", "actix-multipart", @@ -1122,6 +1134,7 @@ dependencies = [ "lexical-sort", "local-ip-address", "log", + "nix", "notify", "notify-debouncer-full", "num-traits", @@ -1195,12 +1208,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -1818,9 +1831,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.157" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -1960,6 +1973,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "0.8.11" @@ -2010,6 +2032,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -2530,9 +2564,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes", @@ -2568,7 +2602,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots", - "winreg", + "windows-registry", ] [[package]] @@ -3372,6 +3406,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "sysinfo" @@ -3402,7 +3439,7 @@ dependencies = [ [[package]] name = "tests" -version = "0.24.0-beta1" +version = "0.24.0-beta2" dependencies = [ "chrono", "crossbeam-channel", @@ -3687,15 +3724,15 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "unicode_categories" @@ -3974,7 +4011,7 @@ checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ "windows-implement", "windows-interface", - "windows-result", + "windows-result 0.1.2", "windows-targets 0.52.6", ] @@ -4000,6 +4037,17 @@ dependencies = [ "syn 2.0.75", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -4009,6 +4057,25 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4166,16 +4233,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 13cc0a08..0e53419f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ default-members = ["ffplayout", "tests"] resolver = "2" [workspace.package] -version = "0.24.0-beta1" +version = "0.24.0-beta2" license = "GPL-3.0" repository = "https://github.com/ffplayout/ffplayout" authors = ["Jonathan Baecker "] diff --git a/docs/install.md b/docs/install.md index d28a5296..d6bcca43 100644 --- a/docs/install.md +++ b/docs/install.md @@ -9,13 +9,13 @@ ffplayout provides ***.deb** and ***.rpm** packages, which makes it more easy to - `systemctl enable ffplayout` 5. initial defaults and add global admin user: - `sudo -u ffpu ffplayout -i` -6. use a revers proxy for SSL, Port is **8787**. -7. login with your browser, address without proxy would be: **http://[IP ADDRESS]:8787** +6. start ffplayout: + - `systemctl start ffplayout` +7. use a revers proxy for SSL, Port is **8787**. +8. login with your browser, address without proxy would be: **http://[IP ADDRESS]:8787** Default location for playlists and media files are: **/var/lib/ffplayout/**. -When playlists are created and the ffplayout output is configured, you can start the process: `systemctl start ffplayout`, or click start in frontend. - ### Manual Install ----- diff --git a/ffplayout/Cargo.toml b/ffplayout/Cargo.toml index 2fce8180..b9e5933b 100644 --- a/ffplayout/Cargo.toml +++ b/ffplayout/Cargo.toml @@ -36,6 +36,7 @@ lettre = { version = "0.11", features = ["builder", "rustls-tls", "smtp-transpor lexical-sort = "0.3" local-ip-address = "0.6" log = { version = "0.4", features = ["std", "serde", "kv", "kv_std", "kv_sval", "kv_serde"] } +nix = { version = "0.29", features = ["user", "fs"] } notify = "6.0" notify-debouncer-full = { version = "*", default-features = false } num-traits = "0.2" diff --git a/ffplayout/src/main.rs b/ffplayout/src/main.rs index 8655658c..e4dcc895 100644 --- a/ffplayout/src/main.rs +++ b/ffplayout/src/main.rs @@ -289,7 +289,7 @@ async fn main() -> std::io::Result<()> { playlist, Arc::new(AtomicBool::new(false)), ); - } else { + } else if !ARGS.init { error!("Run ffplayout with parameters! Run ffplayout -h for more information."); } } diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index 75cc0783..9afe96cf 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -1,6 +1,8 @@ use std::{ + fs, io::{stdin, stdout, Write}, - path::PathBuf, + path::{Path, PathBuf}, + process::exit, }; use clap::Parser; @@ -14,6 +16,7 @@ use crate::db::{ use crate::utils::{ advanced_config::AdvancedConfig, config::{OutputMode, PlayoutConfig}, + db_path, }; use crate::ARGS; @@ -202,6 +205,37 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { let mut error_code = -1; if args.init { + let uid = nix::unistd::Uid::current(); + let current_user = nix::unistd::User::from_uid(uid).unwrap_or_default(); + let process_user = nix::unistd::User::from_name("ffpu").unwrap_or_default(); + let mut fix_permission = false; + + #[cfg(target_family = "unix")] + if current_user != process_user { + let user_name = current_user.unwrap().name; + let mut fix_perm = String::new(); + + println!( + "\nYou run the initialization as user {}. Fix permissions after initialization?", + user_name + ); + + print!("Fix permission [Y/n]: "); + stdout().flush().unwrap(); + + stdin() + .read_line(&mut fix_perm) + .expect("Did not enter a yes or no?"); + + fix_permission = fix_perm.trim().to_lowercase().starts_with('y'); + + if fix_permission && user_name != "root" { + println!("You do not have permission to change DB file ownership! Run as proper process user or root."); + + exit(1); + } + } + let check_user = handles::select_users(pool).await; let mut storage = String::new(); @@ -292,7 +326,7 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { stdin() .read_line(&mut shared_store) - .expect("Did not enter a correct path?"); + .expect("Did not enter a yes or no?"); global.shared_storage = shared_store.trim().to_lowercase().starts_with('y'); @@ -308,7 +342,28 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { handles::update_channel(pool, 1, channel).await.unwrap(); }; - println!("Set global settings..."); + if fix_permission { + let db_path = Path::new(db_path().unwrap()).with_extension(""); + let user = process_user.unwrap(); + + let db = fs::canonicalize(db_path.with_extension("db")).unwrap(); + let shm = fs::canonicalize(db_path.with_extension("db-shm")).unwrap(); + let wal = fs::canonicalize(db_path.with_extension("db-wal")).unwrap(); + + nix::unistd::chown(&db, Some(user.uid), Some(user.gid)).expect("Change DB owner"); + + if shm.is_file() { + nix::unistd::chown(&shm, Some(user.uid), Some(user.gid)) + .expect("Change DB-SHM owner"); + } + + if wal.is_file() { + nix::unistd::chown(&wal, Some(user.uid), Some(user.gid)) + .expect("Change DB-WAL owner"); + } + } + + println!("\nSet global settings done..."); } if args.add { From a1cc272697820fd47ee77b7044d60bd26513963d Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Wed, 21 Aug 2024 12:18:15 +0200 Subject: [PATCH 03/10] update user channels, fix #721 --- ffplayout/src/api/routes.rs | 32 +++++++++++++------------------- ffplayout/src/db/handles.rs | 14 ++++++++++++++ frontend | 2 +- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/ffplayout/src/api/routes.rs b/ffplayout/src/api/routes.rs index 39fca012..06d0c9f2 100644 --- a/ffplayout/src/api/routes.rs +++ b/ffplayout/src/api/routes.rs @@ -309,6 +309,7 @@ async fn update_user( role: AuthDetails, user: web::ReqData, ) -> Result { + let channel_ids = data.channel_ids.clone().unwrap_or_default(); let mut fields = String::new(); if let Some(mail) = data.mail.clone() { @@ -319,21 +320,6 @@ async fn update_user( fields.push_str(&format!("mail = '{mail}'")); } - if let Some(channel_ids) = &data.channel_ids { - if !fields.is_empty() { - fields.push_str(", "); - } - - fields.push_str(&format!( - "channel_ids = '{}'", - channel_ids - .iter() - .map(|i| i.to_string()) - .collect::>() - .join(",") - )); - } - if !data.password.is_empty() { if !fields.is_empty() { fields.push_str(", "); @@ -354,11 +340,19 @@ async fn update_user( fields.push_str(&format!("password = '{password_hash}'")); } - if handles::update_user(&pool, *id, fields).await.is_ok() { - return Ok("Update Success"); - }; + handles::update_user(&pool, *id, fields).await?; - Err(ServiceError::InternalServerError) + let related_channels = handles::select_related_channels(&pool, Some(*id)).await?; + + for channel in related_channels { + if !channel_ids.contains(&channel.id) { + handles::delete_user_channel(&pool, *id, channel.id).await?; + } + } + + handles::insert_user_channel(&pool, *id, channel_ids).await?; + + Ok("Update Success") } /// **Add User** diff --git a/ffplayout/src/db/handles.rs b/ffplayout/src/db/handles.rs index 9cfa90b0..f9aedc28 100644 --- a/ffplayout/src/db/handles.rs +++ b/ffplayout/src/db/handles.rs @@ -94,6 +94,20 @@ pub async fn select_related_channels( Ok(results) } +pub async fn delete_user_channel( + conn: &Pool, + user_id: i32, + channel_id: i32, +) -> Result { + let query = "DELETE FROM user_channels WHERE user_id = $1 AND channel_id = $2"; + + sqlx::query(query) + .bind(user_id) + .bind(channel_id) + .execute(conn) + .await +} + pub async fn update_channel( conn: &Pool, id: i32, diff --git a/frontend b/frontend index 2045e47a..e9eff701 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit 2045e47ab5e9f42ff73e03a7c4c604821a73f7ca +Subproject commit e9eff70158431a69080bb192a5bb793b0475938a From aabc2b8df536af6a5b317b258f6d0b2cbea231cc Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Wed, 21 Aug 2024 12:18:55 +0200 Subject: [PATCH 04/10] update version --- frontend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend b/frontend index e9eff701..86b2b00e 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit e9eff70158431a69080bb192a5bb793b0475938a +Subproject commit 86b2b00e0e121f72ab7c2cc2fb28f2a110c4c762 From 568fcab859e7f68fee63a7118725aa67c0b01fe9 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 22 Aug 2024 11:01:02 +0200 Subject: [PATCH 05/10] move storage paths to channel --- Cargo.lock | 49 ++++++++++- ffplayout/src/api/routes.rs | 21 +++-- ffplayout/src/db/handles.rs | 22 +++-- ffplayout/src/db/models.rs | 15 ++-- ffplayout/src/main.rs | 2 +- ffplayout/src/player/controller.rs | 2 +- ffplayout/src/player/input/folder.rs | 2 +- ffplayout/src/player/input/mod.rs | 2 +- ffplayout/src/player/input/playlist.rs | 2 +- ffplayout/src/player/utils/folder.rs | 2 +- ffplayout/src/player/utils/import.rs | 4 +- ffplayout/src/player/utils/json_serializer.rs | 4 +- ffplayout/src/utils/args_parse.rs | 85 ++++++++++++------- ffplayout/src/utils/channels.rs | 16 ++++ ffplayout/src/utils/config.rs | 61 ++++++------- ffplayout/src/utils/files.rs | 14 +-- ffplayout/src/utils/generator.rs | 4 +- ffplayout/src/utils/playlist.rs | 8 +- ffplayout/src/utils/system.rs | 2 +- frontend | 2 +- migrations/00001_create_tables.sql | 11 ++- tests/src/engine_generator.rs | 4 +- tests/src/engine_playlist.rs | 16 ++-- 23 files changed, 225 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f89de5c0..60395bce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3256,15 +3256,15 @@ dependencies = [ [[package]] name = "stacker" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +checksum = "95a5daa25ea337c85ed954c0496e3bdd2c7308cc3b24cf7b50d04876654c579f" dependencies = [ "cc", "cfg-if", "libc", "psm", - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -4076,6 +4076,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4146,6 +4159,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4158,6 +4177,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4176,6 +4201,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4188,6 +4219,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4212,6 +4249,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/ffplayout/src/api/routes.rs b/ffplayout/src/api/routes.rs index 06d0c9f2..0e757e6c 100644 --- a/ffplayout/src/api/routes.rs +++ b/ffplayout/src/api/routes.rs @@ -478,10 +478,17 @@ async fn patch_channel( role: AuthDetails, user: web::ReqData, ) -> Result { - if handles::update_channel(&pool, *id, data.into_inner()) - .await - .is_ok() - { + let mut data = data.into_inner(); + + if !role.has_authority(&Role::GlobalAdmin) { + let channel = handles::select_channel(&pool, &id).await?; + + data.hls_path = channel.hls_path; + data.playlist_path = channel.playlist_path; + data.storage_path = channel.storage_path; + } + + if handles::update_channel(&pool, *id, data).await.is_ok() { return Ok("Update Success"); }; @@ -1022,7 +1029,7 @@ pub async fn gen_playlist( ) -> Result { let manager = controllers.lock().unwrap().get(params.0).unwrap(); manager.config.lock().unwrap().general.generate = Some(vec![params.1.clone()]); - let storage_path = manager.config.lock().unwrap().global.storage_path.clone(); + let storage_path = manager.config.lock().unwrap().channel.storage_path.clone(); if let Some(obj) = data { if let Some(paths) = &obj.paths { @@ -1264,7 +1271,7 @@ async fn get_file( let id: i32 = req.match_info().query("id").parse()?; let manager = controllers.lock().unwrap().get(id).unwrap(); let config = manager.config.lock().unwrap(); - let storage_path = config.global.storage_path.clone(); + let storage_path = config.channel.storage_path.clone(); let file_path = req.match_info().query("filename"); let (path, _, _) = norm_abs_path(&storage_path, file_path)?; let file = actix_files::NamedFile::open(path)?; @@ -1295,7 +1302,7 @@ async fn get_public( let absolute_path = if file_stem.ends_with(".ts") || file_stem.ends_with(".m3u8") { let manager = controllers.lock().unwrap().get(id).unwrap(); let config = manager.config.lock().unwrap(); - config.global.hls_path.join(public) + config.channel.hls_path.join(public) } else if public_path.is_absolute() { public_path.to_path_buf() } else { diff --git a/ffplayout/src/db/handles.rs b/ffplayout/src/db/handles.rs index f9aedc28..d6374b3e 100644 --- a/ffplayout/src/db/handles.rs +++ b/ffplayout/src/db/handles.rs @@ -40,7 +40,7 @@ pub async fn db_migrate(conn: &Pool) -> Result<&'static str, Box) -> Result { - let query = "SELECT id, secret, hls_path, logging_path, playlist_path, storage_path, shared_storage FROM global WHERE id = 1"; + let query = "SELECT id, secret, logging_path, playlist_root, public_root, storage_root, shared_storage FROM global WHERE id = 1"; sqlx::query_as(query).fetch_one(conn).await } @@ -49,14 +49,14 @@ pub async fn update_global( conn: &Pool, global: GlobalSettings, ) -> Result { - let query = "UPDATE global SET hls_path = $2, playlist_path = $3, storage_path = $4, logging_path = $5, shared_storage = $6 WHERE id = 1"; + let query = "UPDATE global SET logging_path = $2, playlist_root = $3, public_root = $4, storage_root = $5, shared_storage = $6 WHERE id = 1"; sqlx::query(query) .bind(global.id) - .bind(global.hls_path) - .bind(global.playlist_path) - .bind(global.storage_path) .bind(global.logging_path) + .bind(global.playlist_root) + .bind(global.public_root) + .bind(global.storage_root) .bind(global.shared_storage) .execute(conn) .await @@ -77,7 +77,7 @@ pub async fn select_related_channels( ) -> Result, sqlx::Error> { let query = match user_id { Some(id) => format!( - "SELECT c.id, c.name, c.preview_url, c.extra_extensions, c.active, c.last_date, c.time_shift FROM channels c + "SELECT c.id, c.name, c.preview_url, c.extra_extensions, c.active, c.hls_path, c.playlist_path, c.storage_path, c.last_date, c.time_shift FROM channels c left join user_channels uc on uc.channel_id = c.id left join user u on u.id = uc.user_id WHERE u.id = {id} ORDER BY c.id ASC;" @@ -114,13 +114,16 @@ pub async fn update_channel( channel: Channel, ) -> Result { let query = - "UPDATE channels SET name = $2, preview_url = $3, extra_extensions = $4 WHERE id = $1"; + "UPDATE channels SET name = $2, preview_url = $3, extra_extensions = $4, hls_path = $5, playlist_path = $6, storage_path = $7 WHERE id = $1"; sqlx::query(query) .bind(id) .bind(channel.name) .bind(channel.preview_url) .bind(channel.extra_extensions) + .bind(channel.hls_path) + .bind(channel.playlist_path) + .bind(channel.storage_path) .execute(conn) .await } @@ -152,11 +155,14 @@ pub async fn update_player( } pub async fn insert_channel(conn: &Pool, channel: Channel) -> Result { - let query = "INSERT INTO channels (name, preview_url, extra_extensions) VALUES($1, $2, $3)"; + let query = "INSERT INTO channels (name, preview_url, extra_extensions, hls_path, playlist_path, storage_path) VALUES($1, $2, $3, $4, $5, $6)"; let result = sqlx::query(query) .bind(channel.name) .bind(channel.preview_url) .bind(channel.extra_extensions) + .bind(channel.hls_path) + .bind(channel.playlist_path) + .bind(channel.storage_path) .execute(conn) .await?; diff --git a/ffplayout/src/db/models.rs b/ffplayout/src/db/models.rs index 445a4cf2..14876141 100644 --- a/ffplayout/src/db/models.rs +++ b/ffplayout/src/db/models.rs @@ -16,10 +16,10 @@ use crate::utils::config::PlayoutConfig; pub struct GlobalSettings { pub id: i32, pub secret: Option, - pub hls_path: String, pub logging_path: String, - pub playlist_path: String, - pub storage_path: String, + pub playlist_root: String, + pub public_root: String, + pub storage_root: String, pub shared_storage: bool, } @@ -32,10 +32,10 @@ impl GlobalSettings { Err(_) => GlobalSettings { id: 0, secret: None, - hls_path: String::new(), logging_path: String::new(), - playlist_path: String::new(), - storage_path: String::new(), + playlist_root: String::new(), + public_root: String::new(), + storage_root: String::new(), shared_storage: false, }, } @@ -240,6 +240,9 @@ pub struct Channel { pub preview_url: String, pub extra_extensions: String, pub active: bool, + pub hls_path: String, + pub playlist_path: String, + pub storage_path: String, pub last_date: Option, pub time_shift: f64, diff --git a/ffplayout/src/main.rs b/ffplayout/src/main.rs index e4dcc895..b7b1f225 100644 --- a/ffplayout/src/main.rs +++ b/ffplayout/src/main.rs @@ -263,7 +263,7 @@ async fn main() -> std::io::Result<()> { exit(1); }; } else if ARGS.validate { - let mut playlist_path = config.global.playlist_path.clone(); + let mut playlist_path = config.channel.playlist_path.clone(); let start_sec = config.playlist.start_sec.unwrap(); let date = get_date(false, start_sec, false); diff --git a/ffplayout/src/player/controller.rs b/ffplayout/src/player/controller.rs index 7fc7a272..a8507e43 100644 --- a/ffplayout/src/player/controller.rs +++ b/ffplayout/src/player/controller.rs @@ -337,7 +337,7 @@ pub fn start_channel(manager: ChannelManager) -> Result<(), ProcessError> { let channel_id = config.general.channel_id; drain_hls_path( - &config.global.hls_path, + &config.channel.hls_path, &config.output.output_cmd.clone().unwrap_or_default(), channel_id, )?; diff --git a/ffplayout/src/player/input/folder.rs b/ffplayout/src/player/input/folder.rs index 4355e255..0a297620 100644 --- a/ffplayout/src/player/input/folder.rs +++ b/ffplayout/src/player/input/folder.rs @@ -29,7 +29,7 @@ pub fn watchman( sources: Arc>>, ) { let id = config.general.channel_id; - let path = Path::new(&config.global.storage_path); + let path = Path::new(&config.channel.storage_path); if !path.exists() { error!("Folder path not exists: '{path:?}'"); diff --git a/ffplayout/src/player/input/mod.rs b/ffplayout/src/player/input/mod.rs index d59c7871..abd01cc3 100644 --- a/ffplayout/src/player/input/mod.rs +++ b/ffplayout/src/player/input/mod.rs @@ -28,7 +28,7 @@ pub fn source_generator(manager: ChannelManager) -> Box{:?}", - config.global.storage_path + config.channel.storage_path ); let config_clone = config.clone(); diff --git a/ffplayout/src/player/input/playlist.rs b/ffplayout/src/player/input/playlist.rs index 79e615a4..c3098314 100644 --- a/ffplayout/src/player/input/playlist.rs +++ b/ffplayout/src/player/input/playlist.rs @@ -317,7 +317,7 @@ impl CurrentProgram { if self.current_node.source.contains( &self .config - .global + .channel .storage_path .to_string_lossy() .to_string(), diff --git a/ffplayout/src/player/utils/folder.rs b/ffplayout/src/player/utils/folder.rs index e50af350..965bdb7f 100644 --- a/ffplayout/src/player/utils/folder.rs +++ b/ffplayout/src/player/utils/folder.rs @@ -40,7 +40,7 @@ impl FolderSource { path_list.push(path) } } else { - path_list.push(&config.global.storage_path) + path_list.push(&config.channel.storage_path) } for path in &path_list { diff --git a/ffplayout/src/player/utils/import.rs b/ffplayout/src/player/utils/import.rs index 3f86eb4b..eec55206 100644 --- a/ffplayout/src/player/utils/import.rs +++ b/ffplayout/src/player/utils/import.rs @@ -28,13 +28,13 @@ pub fn import_file( program: vec![], }; - let playlist_root = &config.global.playlist_path; + let playlist_root = &config.channel.playlist_path; if !playlist_root.is_dir() { return Err(Error::new( ErrorKind::Other, format!( "Playlist folder {:?} not exists!", - config.global.playlist_path, + config.channel.playlist_path, ), )); } diff --git a/ffplayout/src/player/utils/json_serializer.rs b/ffplayout/src/player/utils/json_serializer.rs index 2eed648f..690d4859 100644 --- a/ffplayout/src/player/utils/json_serializer.rs +++ b/ffplayout/src/player/utils/json_serializer.rs @@ -100,11 +100,11 @@ pub fn read_json( ) -> JsonPlaylist { let id = config.general.channel_id; let config_clone = config.clone(); - let mut playlist_path = config.global.playlist_path.clone(); + let mut playlist_path = config.channel.playlist_path.clone(); let start_sec = config.playlist.start_sec.unwrap(); let date = get_date(seek, start_sec, get_next); - if playlist_path.is_dir() || is_remote(&config.global.playlist_path.to_string_lossy()) { + if playlist_path.is_dir() || is_remote(&config.channel.playlist_path.to_string_lossy()) { let d: Vec<&str> = date.split('-').collect(); playlist_path = playlist_path .join(d[0]) diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index 9afe96cf..bdb09a94 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -113,16 +113,20 @@ pub struct Args { #[clap(long, env, help = "Log to console")] pub log_to_console: bool, - #[clap(long, env, help = "HLS output path")] - pub hls_path: Option, + #[clap(long, env, help = "Public (HLS) output path")] + pub public_root: Option, #[clap(long, env, help = "Playlist root path")] - pub playlist_path: Option, + pub playlist_root: Option, #[clap(long, env, help = "Storage root path")] - pub storage_path: Option, + pub storage_root: Option, - #[clap(long, env, help = "Share storage across channels")] + #[clap( + long, + env, + help = "Share storage root across channels, important for running in Container" + )] pub shared_storage: bool, #[clap(short, long, help = "Create admin user")] @@ -216,7 +220,7 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { let mut fix_perm = String::new(); println!( - "\nYou run the initialization as user {}. Fix permissions after initialization?", + "\nYou run the initialization as user {}.\nFix permissions after initialization?\n", user_name ); @@ -230,7 +234,7 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { fix_permission = fix_perm.trim().to_lowercase().starts_with('y'); if fix_permission && user_name != "root" { - println!("You do not have permission to change DB file ownership! Run as proper process user or root."); + println!("\nYou do not have permission to change DB file ownership!\nRun as proper process user or root."); exit(1); } @@ -246,10 +250,10 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { let mut global = GlobalSettings { id: 0, secret: None, - hls_path: String::new(), - playlist_path: String::new(), - storage_path: String::new(), logging_path: String::new(), + playlist_root: String::new(), + public_root: String::new(), + storage_root: String::new(), shared_storage: false, }; @@ -265,9 +269,9 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { .expect("Did not enter a correct path?"); if storage.trim().is_empty() { - global.storage_path = "/var/lib/ffplayout/tv-media".to_string(); + global.storage_root = "/var/lib/ffplayout/tv-media".to_string(); } else { - global.storage_path = storage + global.storage_root = storage .trim() .trim_matches(|c| c == '"' || c == '\'') .to_string(); @@ -281,9 +285,9 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { .expect("Did not enter a correct path?"); if playlist.trim().is_empty() { - global.playlist_path = "/var/lib/ffplayout/playlists".to_string(); + global.playlist_root = "/var/lib/ffplayout/playlists".to_string(); } else { - global.playlist_path = playlist + global.playlist_root = playlist .trim() .trim_matches(|c| c == '"' || c == '\'') .to_string(); @@ -305,7 +309,7 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { .to_string(); } - print!("HLS path [/usr/share/ffplayout/public]: "); + print!("Public (HLS) path [/usr/share/ffplayout/public]: "); stdout().flush().unwrap(); stdin() @@ -313,9 +317,9 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { .expect("Did not enter a correct path?"); if hls.trim().is_empty() { - global.hls_path = "/usr/share/ffplayout/public".to_string(); + global.public_root = "/usr/share/ffplayout/public".to_string(); } else { - global.hls_path = hls + global.public_root = hls .trim() .trim_matches(|c| c == '"' || c == '\'') .to_string(); @@ -335,13 +339,29 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { error_code = 1; }; - if !global.shared_storage { - let mut channel = handles::select_channel(pool, &1).await.unwrap(); - channel.preview_url = "http://127.0.0.1:8787/1/stream.m3u8".to_string(); + let mut channel = handles::select_channel(pool, &1).await.unwrap(); + channel.hls_path = global.public_root; + channel.playlist_path = global.playlist_root; + channel.storage_path = global.storage_root; - handles::update_channel(pool, 1, channel).await.unwrap(); + if global.shared_storage { + channel.preview_url = "http://127.0.0.1:8787/1/stream.m3u8".to_string(); + channel.hls_path = Path::new(&channel.hls_path) + .join("1") + .to_string_lossy() + .to_string(); + channel.playlist_path = Path::new(&channel.playlist_path) + .join("1") + .to_string_lossy() + .to_string(); + channel.storage_path = Path::new(&channel.storage_path) + .join("1") + .to_string_lossy() + .to_string(); }; + handles::update_channel(pool, 1, channel).await.unwrap(); + if fix_permission { let db_path = Path::new(db_path().unwrap()).with_extension(""); let user = process_user.unwrap(); @@ -399,9 +419,9 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { } if !args.init - && args.storage_path.is_some() - && args.playlist_path.is_some() - && args.hls_path.is_some() + && args.storage_root.is_some() + && args.playlist_root.is_some() + && args.public_root.is_some() && args.log_path.is_some() { error_code = 0; @@ -409,18 +429,19 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { let global = GlobalSettings { id: 0, secret: None, - hls_path: args.hls_path.unwrap(), - playlist_path: args.playlist_path.unwrap(), - storage_path: args.storage_path.unwrap(), logging_path: args.log_path.unwrap().to_string_lossy().to_string(), + playlist_root: args.playlist_root.unwrap(), + public_root: args.public_root.unwrap(), + storage_root: args.storage_root.unwrap(), shared_storage: args.shared_storage, }; - if let Err(e) = handles::update_global(pool, global.clone()).await { - eprintln!("{e}"); - error_code = 1; - } else { - println!("Update global paths..."); + match handles::update_global(pool, global.clone()).await { + Ok(_) => println!("Update global paths..."), + Err(e) => { + eprintln!("{e}"); + error_code = 1; + } }; } diff --git a/ffplayout/src/utils/channels.rs b/ffplayout/src/utils/channels.rs index de71fc65..7250fb9e 100644 --- a/ffplayout/src/utils/channels.rs +++ b/ffplayout/src/utils/channels.rs @@ -61,10 +61,26 @@ pub async fn create_channel( queue: Arc>>>>, target_channel: Channel, ) -> Result { + let global = handles::select_global(conn).await?; let mut channel = handles::insert_channel(conn, target_channel).await?; channel.preview_url = preview_url(&channel.preview_url, channel.id); + if global.shared_storage { + channel.hls_path = Path::new(&channel.hls_path) + .join(channel.id.to_string()) + .to_string_lossy() + .to_string(); + channel.playlist_path = Path::new(&channel.playlist_path) + .join(channel.id.to_string()) + .to_string_lossy() + .to_string(); + channel.storage_path = Path::new(&channel.storage_path) + .join(channel.id.to_string()) + .to_string_lossy() + .to_string(); + } + handles::update_channel(conn, channel.id, channel.clone()).await?; let output_param = "-c:v libx264 -crf 23 -x264-params keyint=50:min-keyint=25:scenecut=-1 -maxrate 1300k -bufsize 2600k -preset faster -tune zerolatency -profile:v Main -level 3.1 -c:a aac -ar 44100 -b:a 128k -flags +cgop -f hls -hls_time 6 -hls_list_size 600 -hls_flags append_list+delete_segments+omit_endlist -hls_segment_filename live/stream-%d.ts live/stream.m3u8".to_string(); diff --git a/ffplayout/src/utils/config.rs b/ffplayout/src/utils/config.rs index 3a2d0186..3975538a 100644 --- a/ffplayout/src/utils/config.rs +++ b/ffplayout/src/utils/config.rs @@ -26,12 +26,13 @@ pub const IMAGE_FORMAT: [&str; 21] = [ ]; // Some well known errors can be safely ignore -pub const FFMPEG_IGNORE_ERRORS: [&str; 12] = [ +pub const FFMPEG_IGNORE_ERRORS: [&str; 13] = [ "ac-tex damaged", "codec s302m, is muxed as a private data stream", "corrupt decoded frame in stream", "corrupt input packet in stream", "end mismatch left", + "Invalid mb type in I-frame at", "Packet corrupt", "Referenced QT chapter track not found", "skipped MB in I-frame at", @@ -151,13 +152,13 @@ pub struct Source { pub paths: Vec, } -/// Global Config +/// Channel Config /// /// This we init ones, when ffplayout is starting and use them globally in the hole program. #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct PlayoutConfig { #[serde(skip_serializing, skip_deserializing)] - pub global: Global, + pub channel: Channel, #[serde(skip_serializing, skip_deserializing)] pub advanced: AdvancedConfig, pub general: General, @@ -174,21 +175,21 @@ pub struct PlayoutConfig { } #[derive(Debug, Default, Clone, Deserialize, Serialize)] -pub struct Global { +pub struct Channel { + pub logging_path: PathBuf, pub hls_path: PathBuf, pub playlist_path: PathBuf, pub storage_path: PathBuf, - pub logging_path: PathBuf, pub shared_storage: bool, } -impl Global { +impl Channel { pub fn new(config: &models::GlobalSettings) -> Self { Self { - hls_path: PathBuf::from(config.hls_path.clone()), - playlist_path: PathBuf::from(config.playlist_path.clone()), - storage_path: PathBuf::from(config.storage_path.clone()), logging_path: PathBuf::from(config.logging_path.clone()), + hls_path: PathBuf::from(config.public_root.clone()), + playlist_path: PathBuf::from(config.playlist_root.clone()), + storage_path: PathBuf::from(config.storage_root.clone()), shared_storage: config.shared_storage, } } @@ -559,7 +560,7 @@ impl PlayoutConfig { .await .expect("Can't read advanced config"); - let mut global = Global::new(&global); + let mut channel = Channel::new(&global); let advanced = AdvancedConfig::new(adv_config); let general = General::new(&config); let mail = Mail::new(&config); @@ -571,42 +572,42 @@ impl PlayoutConfig { let task = Task::new(&config); let mut output = Output::new(&config); - if !global.shared_storage { - global.storage_path = global.storage_path.join(channel_id.to_string()); + if global.shared_storage { + channel.storage_path = channel.storage_path.join(channel_id.to_string()); } - if !global.storage_path.is_dir() { - tokio::fs::create_dir_all(&global.storage_path) + if !channel.storage_path.is_dir() { + tokio::fs::create_dir_all(&channel.storage_path) .await .unwrap_or_else(|_| { - panic!("Can't create storage folder: {:#?}", global.storage_path) + panic!("Can't create storage folder: {:#?}", channel.storage_path) }); } - let mut storage = Storage::new(&config, global.storage_path.clone()); + let mut storage = Storage::new(&config, channel.storage_path.clone()); if channel_id > 1 || !global.shared_storage { - global.playlist_path = global.playlist_path.join(channel_id.to_string()); - global.hls_path = global.hls_path.join(channel_id.to_string()); + channel.playlist_path = channel.playlist_path.join(channel_id.to_string()); + channel.hls_path = channel.hls_path.join(channel_id.to_string()); } - if !global.playlist_path.is_dir() { - tokio::fs::create_dir_all(&global.playlist_path) + if !channel.playlist_path.is_dir() { + tokio::fs::create_dir_all(&channel.playlist_path) .await .unwrap_or_else(|_| { - panic!("Can't create playlist folder: {:#?}", global.playlist_path) + panic!("Can't create playlist folder: {:#?}", channel.playlist_path) }); } - if !global.logging_path.is_dir() { - tokio::fs::create_dir_all(&global.logging_path) + if !channel.logging_path.is_dir() { + tokio::fs::create_dir_all(&channel.logging_path) .await .unwrap_or_else(|_| { - panic!("Can't create logging folder: {:#?}", global.logging_path) + panic!("Can't create logging folder: {:#?}", channel.logging_path) }); } - let (filler_path, _, _) = norm_abs_path(&global.storage_path, &config.storage_filler) + let (filler_path, _, _) = norm_abs_path(&channel.storage_path, &config.storage_filler) .expect("Can't get filler path"); storage.filler = filler_path; @@ -700,7 +701,7 @@ impl PlayoutConfig { for item in cmd.iter_mut() { if item.ends_with(".ts") || (item.ends_with(".m3u8") && item != "master.m3u8") { - if let Ok((hls_path, _, _)) = norm_abs_path(&global.hls_path, item) { + if let Ok((hls_path, _, _)) = norm_abs_path(&channel.hls_path, item) { let parent = hls_path.parent().expect("HLS parent path"); if !parent.is_dir() { @@ -728,7 +729,7 @@ impl PlayoutConfig { } Self { - global, + channel, advanced, general, mail, @@ -749,7 +750,7 @@ impl PlayoutConfig { &config .storage .filler - .strip_prefix(config.global.storage_path.clone()) + .strip_prefix(config.channel.storage_path.clone()) .unwrap_or(&config.storage.filler) .to_path_buf(), ); @@ -849,11 +850,11 @@ pub async fn get_config(pool: &Pool, channel_id: i32) -> Result Result { - let (path, _, _) = norm_abs_path(&config.global.storage_path, &path_obj.source)?; + let (path, _, _) = norm_abs_path(&config.channel.storage_path, &path_obj.source)?; if let Err(e) = fs::create_dir_all(&path).await { return Err(ServiceError::BadRequest(e.to_string())); @@ -281,8 +281,8 @@ pub async fn rename_file( config: &PlayoutConfig, move_object: &MoveObject, ) -> Result { - let (source_path, _, _) = norm_abs_path(&config.global.storage_path, &move_object.source)?; - let (mut target_path, _, _) = norm_abs_path(&config.global.storage_path, &move_object.target)?; + let (source_path, _, _) = norm_abs_path(&config.channel.storage_path, &move_object.source)?; + let (mut target_path, _, _) = norm_abs_path(&config.channel.storage_path, &move_object.target)?; if !source_path.exists() { return Err(ServiceError::BadRequest("Source file not exist!".into())); @@ -314,7 +314,7 @@ pub async fn remove_file_or_folder( config: &PlayoutConfig, source_path: &str, ) -> Result<(), ServiceError> { - let (source, _, _) = norm_abs_path(&config.global.storage_path, source_path)?; + let (source, _, _) = norm_abs_path(&config.channel.storage_path, source_path)?; if !source.exists() { return Err(ServiceError::BadRequest("Source does not exists!".into())); @@ -346,7 +346,7 @@ pub async fn remove_file_or_folder( } async fn valid_path(config: &PlayoutConfig, path: &str) -> Result { - let (test_path, _, _) = norm_abs_path(&config.global.storage_path, path)?; + let (test_path, _, _) = norm_abs_path(&config.channel.storage_path, path)?; if !test_path.is_dir() { return Err(ServiceError::BadRequest("Target folder not exists!".into())); diff --git a/ffplayout/src/utils/generator.rs b/ffplayout/src/utils/generator.rs index edd7dd2d..e8eb2c58 100644 --- a/ffplayout/src/utils/generator.rs +++ b/ffplayout/src/utils/generator.rs @@ -215,7 +215,7 @@ pub fn playlist_generator(manager: &ChannelManager) -> Result, } } }; - let playlist_root = &config.global.playlist_path; + let playlist_root = &config.channel.playlist_path; let mut playlists = vec![]; let mut date_range = vec![]; let mut from_template = false; @@ -224,7 +224,7 @@ pub fn playlist_generator(manager: &ChannelManager) -> Result, error!( target: Target::all(), channel = id; "Playlist folder {:?} not exists!", - config.global.playlist_path + config.channel.playlist_path ); } diff --git a/ffplayout/src/utils/playlist.rs b/ffplayout/src/utils/playlist.rs index 884767f3..bb40dafe 100644 --- a/ffplayout/src/utils/playlist.rs +++ b/ffplayout/src/utils/playlist.rs @@ -14,7 +14,7 @@ pub async fn read_playlist( date: String, ) -> Result { let d: Vec<&str> = date.split('-').collect(); - let mut playlist_path = config.global.playlist_path.clone(); + let mut playlist_path = config.channel.playlist_path.clone(); playlist_path = playlist_path .join(d[0]) @@ -34,7 +34,7 @@ pub async fn write_playlist( ) -> Result { let date = json_data.date.clone(); let d: Vec<&str> = date.split('-').collect(); - let mut playlist_path = config.global.playlist_path.clone(); + let mut playlist_path = config.channel.playlist_path.clone(); if !playlist_path .extension() @@ -93,7 +93,7 @@ pub fn generate_playlist(manager: ChannelManager) -> Result Result Result { let d: Vec<&str> = date.split('-').collect(); - let mut playlist_path = PathBuf::from(&config.global.playlist_path); + let mut playlist_path = PathBuf::from(&config.channel.playlist_path); playlist_path = playlist_path .join(d[0]) diff --git a/ffplayout/src/utils/system.rs b/ffplayout/src/utils/system.rs index 7852d928..23e235d5 100644 --- a/ffplayout/src/utils/system.rs +++ b/ffplayout/src/utils/system.rs @@ -118,7 +118,7 @@ pub fn stat(config: PlayoutConfig) -> SystemStat { for disk in &*disks { if disk.mount_point().to_string_lossy().len() > 1 - && config.global.storage_path.starts_with(disk.mount_point()) + && config.channel.storage_path.starts_with(disk.mount_point()) { storage.path = disk.name().to_string_lossy().to_string(); storage.total = disk.total_space(); diff --git a/frontend b/frontend index 86b2b00e..e183320e 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit 86b2b00e0e121f72ab7c2cc2fb28f2a110c4c762 +Subproject commit e183320e13bd972d632b841bde722fa7bbed70e5 diff --git a/migrations/00001_create_tables.sql b/migrations/00001_create_tables.sql index 9c380daa..25f3a83b 100644 --- a/migrations/00001_create_tables.sql +++ b/migrations/00001_create_tables.sql @@ -5,11 +5,11 @@ CREATE TABLE global ( id INTEGER PRIMARY KEY, secret TEXT NOT NULL, - hls_path TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public", logging_path TEXT NOT NULL DEFAULT "/var/log/ffplayout", - playlist_path TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists", - storage_path TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media", - shared_storage INTEGER NOT NULL DEFAULT 1, + playlist_root TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists", + public_root TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public", + storage_root TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media", + shared_storage INTEGER NOT NULL DEFAULT 0, UNIQUE (secret) ); @@ -27,6 +27,9 @@ CREATE TABLE preview_url TEXT NOT NULL, extra_extensions TEXT NOT NULL DEFAULT 'jpg,jpeg,png', active INTEGER NOT NULL DEFAULT 0, + hls_path TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public", + playlist_path TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists", + storage_path TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media", last_date TEXT, time_shift REAL NOT NULL DEFAULT 0 ); diff --git a/tests/src/engine_generator.rs b/tests/src/engine_generator.rs index bd4c2303..8d2bb3f9 100644 --- a/tests/src/engine_generator.rs +++ b/tests/src/engine_generator.rs @@ -104,7 +104,7 @@ fn test_generate_playlist_from_folder() { config.processing.mode = Playlist; config.storage.filler = "assets/".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); let playlist = generate_playlist(manager); @@ -149,7 +149,7 @@ fn test_generate_playlist_from_template() { config.processing.mode = Playlist; config.storage.filler = "assets/".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); let playlist = generate_playlist(manager); diff --git a/tests/src/engine_playlist.rs b/tests/src/engine_playlist.rs index 2948c8ee..e8bb8e42 100644 --- a/tests/src/engine_playlist.rs +++ b/tests/src/engine_playlist.rs @@ -66,7 +66,7 @@ fn test_gen_source() { config.playlist.start_sec = Some(0.0); config.playlist.length = "24:00:00".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into(); let mut valid_source_with_probe = Media::new(0, "assets/media_mix/av_sync.mp4", true); @@ -113,7 +113,7 @@ fn playlist_missing() { config.playlist.start_sec = Some(0.0); config.playlist.length = "24:00:00".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.output.mode = Null; config.output.output_count = 1; @@ -147,7 +147,7 @@ fn playlist_next_missing() { config.playlist.start_sec = Some(0.0); config.playlist.length = "24:00:00".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.output.mode = Null; config.output.output_count = 1; @@ -181,7 +181,7 @@ fn playlist_to_short() { config.playlist.start_sec = Some(21600.0); config.playlist.length = "24:00:00".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.output.mode = Null; config.output.output_count = 1; @@ -215,7 +215,7 @@ fn playlist_init_after_list_end() { config.playlist.start_sec = Some(21600.0); config.playlist.length = "24:00:00".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.output.mode = Null; config.output.output_count = 1; @@ -249,7 +249,7 @@ fn playlist_change_at_midnight() { config.playlist.start_sec = Some(0.0); config.playlist.length = "24:00:00".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.output.mode = Null; config.output.output_count = 1; @@ -283,7 +283,7 @@ fn playlist_change_before_midnight() { config.playlist.start_sec = Some(0.0); config.playlist.length = "24:00:00".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.output.mode = Null; config.output.output_count = 1; @@ -317,7 +317,7 @@ fn playlist_change_at_six() { config.playlist.start_sec = Some(21600.0); config.playlist.length = "24:00:00".into(); config.playlist.length_sec = Some(86400.0); - config.global.playlist_path = "assets/playlists".into(); + config.channel.playlist_path = "assets/playlists".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.output.mode = Null; config.output.output_count = 1; From 6b17222076923300441394e10733fb87e30fda9c Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 22 Aug 2024 11:19:35 +0200 Subject: [PATCH 06/10] add drop db argument --- ffplayout/src/main.rs | 22 +++++++++++++++++++++- ffplayout/src/utils/args_parse.rs | 9 ++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ffplayout/src/main.rs b/ffplayout/src/main.rs index b7b1f225..ae428d6a 100644 --- a/ffplayout/src/main.rs +++ b/ffplayout/src/main.rs @@ -2,7 +2,7 @@ use std::{ collections::HashSet, env, fs::File, - io, + io::{self, stdin, stdout, Write}, process::exit, sync::{atomic::AtomicBool, Arc, Mutex}, thread, @@ -14,6 +14,7 @@ use actix_web::{ }; use actix_web_grants::authorities::AttachAuthorities; use actix_web_httpauth::{extractors::bearer::BearerAuth, middleware::HttpAuthentication}; +use sqlx::{migrate::MigrateDatabase, Sqlite}; #[cfg(all(not(debug_assertions), feature = "embed_frontend"))] use actix_web_static_files::ResourceFiles; @@ -35,6 +36,7 @@ use ffplayout::{ utils::{ args_parse::run_args, config::get_config, + db_path, logging::{init_logging, MailQueue}, playlist::generate_playlist, }, @@ -289,6 +291,24 @@ async fn main() -> std::io::Result<()> { playlist, Arc::new(AtomicBool::new(false)), ); + } else if ARGS.drop_db { + let mut drop_answer = String::new(); + + print!("Drop Database [Y/n]: "); + stdout().flush().unwrap(); + + stdin() + .read_line(&mut drop_answer) + .expect("Did not enter a yes or no?"); + + let drop = drop_answer.trim().to_lowercase().starts_with('y'); + + if drop { + match Sqlite::drop_database(db_path().unwrap()).await { + Ok(_) => info!("Successfully dropped DB"), + Err(e) => error!("{e}"), + }; + } } else if !ARGS.init { error!("Run ffplayout with parameters! Run ffplayout -h for more information."); } diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index bdb09a94..5101c010 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -35,9 +35,16 @@ pub struct Args { #[clap(short, long, help = "Add a global admin user")] pub add: bool, - #[clap(long, env, help = "path to database file")] + #[clap(long, env, help = "Path to database file")] pub db: Option, + #[clap( + long, + env, + help = "Drop database. WARNING: this will delete all configurations!" + )] + pub drop_db: bool, + #[clap( short, long, From 629f23cf429e75a97d104012993692ad5daa249a Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 22 Aug 2024 11:36:23 +0200 Subject: [PATCH 07/10] fix tests --- tests/src/engine_cmd.rs | 4 ++-- tests/src/engine_generator.rs | 4 ++-- tests/src/engine_playlist.rs | 4 ++-- tests/src/lib_utils.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/src/engine_cmd.rs b/tests/src/engine_cmd.rs index c5babfba..6e4d2772 100644 --- a/tests/src/engine_cmd.rs +++ b/tests/src/engine_cmd.rs @@ -22,8 +22,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) { sqlx::query( r#" - UPDATE global SET hls_path = "assets/hls", logging_path = "assets/log", - playlist_path = "assets/playlists", storage_path = "assets/storage"; + UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage"; + UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage"; UPDATE configurations SET processing_width = 1024, processing_height = 576; "#, ) diff --git a/tests/src/engine_generator.rs b/tests/src/engine_generator.rs index 8d2bb3f9..151dada8 100644 --- a/tests/src/engine_generator.rs +++ b/tests/src/engine_generator.rs @@ -25,8 +25,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) { sqlx::query( r#" - UPDATE global SET hls_path = "assets/hls", logging_path = "assets/log", - playlist_path = "assets/playlists", storage_path = "assets/storage"; + UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage"; + UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage"; UPDATE configurations SET processing_width = 1024, processing_height = 576; "#, ) diff --git a/tests/src/engine_playlist.rs b/tests/src/engine_playlist.rs index e8bb8e42..b5ea36cf 100644 --- a/tests/src/engine_playlist.rs +++ b/tests/src/engine_playlist.rs @@ -24,8 +24,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) { sqlx::query( r#" - UPDATE global SET hls_path = "assets/hls", logging_path = "assets/log", - playlist_path = "assets/playlists", storage_path = "assets/storage"; + UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage"; + UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage"; UPDATE configurations SET processing_width = 1024, processing_height = 576; "#, ) diff --git a/tests/src/lib_utils.rs b/tests/src/lib_utils.rs index a6a3e2e5..2638d038 100644 --- a/tests/src/lib_utils.rs +++ b/tests/src/lib_utils.rs @@ -18,8 +18,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) { sqlx::query( r#" - UPDATE global SET hls_path = "assets/hls", logging_path = "assets/log", - playlist_path = "assets/playlists", storage_path = "assets/storage"; + UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage"; + UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage"; UPDATE configurations SET processing_width = 1024, processing_height = 576; "#, ) From cf6a3feb441f4cd9d36fba1a2c3ea5854b2ee669 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 22 Aug 2024 11:42:38 +0200 Subject: [PATCH 08/10] change owner on one unix family --- ffplayout/src/utils/args_parse.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index 5101c010..70613918 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -369,6 +369,7 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { handles::update_channel(pool, 1, channel).await.unwrap(); + #[cfg(target_family = "unix")] if fix_permission { let db_path = Path::new(db_path().unwrap()).with_extension(""); let user = process_user.unwrap(); From b4dbca4be3bdf9ad78a3af65131cd5e0e34a4d68 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 22 Aug 2024 12:01:39 +0200 Subject: [PATCH 09/10] fix windows build --- ffplayout/src/utils/args_parse.rs | 42 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index 70613918..3214c004 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -216,34 +216,40 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { let mut error_code = -1; if args.init { - let uid = nix::unistd::Uid::current(); - let current_user = nix::unistd::User::from_uid(uid).unwrap_or_default(); + #[cfg(target_family = "unix")] let process_user = nix::unistd::User::from_name("ffpu").unwrap_or_default(); + + #[cfg(target_family = "unix")] let mut fix_permission = false; #[cfg(target_family = "unix")] - if current_user != process_user { - let user_name = current_user.unwrap().name; - let mut fix_perm = String::new(); + { + let uid = nix::unistd::Uid::current(); + let current_user = nix::unistd::User::from_uid(uid).unwrap_or_default(); - println!( - "\nYou run the initialization as user {}.\nFix permissions after initialization?\n", - user_name - ); + if current_user != process_user { + let user_name = current_user.unwrap().name; + let mut fix_perm = String::new(); - print!("Fix permission [Y/n]: "); - stdout().flush().unwrap(); + println!( + "\nYou run the initialization as user {}.\nFix permissions after initialization?\n", + user_name + ); - stdin() - .read_line(&mut fix_perm) - .expect("Did not enter a yes or no?"); + print!("Fix permission [Y/n]: "); + stdout().flush().unwrap(); - fix_permission = fix_perm.trim().to_lowercase().starts_with('y'); + stdin() + .read_line(&mut fix_perm) + .expect("Did not enter a yes or no?"); - if fix_permission && user_name != "root" { - println!("\nYou do not have permission to change DB file ownership!\nRun as proper process user or root."); + fix_permission = fix_perm.trim().to_lowercase().starts_with('y'); - exit(1); + if fix_permission && user_name != "root" { + println!("\nYou do not have permission to change DB file ownership!\nRun as proper process user or root."); + + exit(1); + } } } From c58fc6fdd5681098dc3a5f1d1fe1a8e2a52fa531 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 22 Aug 2024 12:05:03 +0200 Subject: [PATCH 10/10] fix unused imports --- ffplayout/src/utils/args_parse.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index 3214c004..fb22ccb3 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -1,10 +1,11 @@ use std::{ - fs, io::{stdin, stdout, Write}, path::{Path, PathBuf}, - process::exit, }; +#[cfg(target_family = "unix")] +use std::{fs, process::exit}; + use clap::Parser; use rpassword::read_password; use sqlx::{Pool, Sqlite}; @@ -16,10 +17,12 @@ use crate::db::{ use crate::utils::{ advanced_config::AdvancedConfig, config::{OutputMode, PlayoutConfig}, - db_path, }; use crate::ARGS; +#[cfg(target_family = "unix")] +use crate::utils::db_path; + #[derive(Parser, Debug, Clone)] #[clap(version, about = "ffplayout - 24/7 broadcasting solution",