diff --git a/CHANGELOG.md b/CHANGELOG.md index 207820fe2..97a8ab5d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ The playlist user can login with his credentials and can select the desired groups for his playlist. - Added `user_config_dir` to `config.yml`. It is the storage path for user configurations (f.e. bouquets). - New Filter field `input` can be used along `name`, `group`, `title`, `url` and `type`. Input is a `regexp` filter. `input ~ "provider\-\d+"` +- New option `use_user_db` in `api-proxy.yml`. The Playlist Users are stored inside the config file `api-proxy.yml`. When you set this option to `true` +the user are stored in a db file. This is a better choice if you have a lot of users. If you have only a few let it defaultto `false` # 2.2.1 (2025-02-14) - Added more info to `/status`. diff --git a/Cargo.lock b/Cargo.lock index cccd482ac..c60e51637 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,9 +358,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" [[package]] name = "arrayref" @@ -426,13 +426,23 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" -version = "1.3.3" +version = "2.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" dependencies = [ + "bincode_derive", "serde", ] +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -458,15 +468,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "memmap2", ] [[package]] @@ -534,9 +545,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.13" +version = "1.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" dependencies = [ "jobserver", "libc", @@ -571,9 +582,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.29" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", "clap_derive", @@ -581,9 +592,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.29" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", @@ -725,9 +736,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.79+curl-8.12.0" +version = "0.4.80+curl-8.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a9bbeeb3996717ef1248018db20d1b0b5ba7165bff60e3b5135b4f4c2b37a5" +checksum = "55f7df2eac63200c3ab25bde3b2268ef2ee56af3d238e76d61f01c3c49bff734" dependencies = [ "cc", "libc", @@ -902,9 +913,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -951,9 +962,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", @@ -1155,9 +1166,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ "atomic-waker", "bytes", @@ -1262,7 +1273,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.7", + "h2 0.4.8", "http 1.2.0", "http-body", "httparse", @@ -1608,9 +1619,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libnghttp2-sys" @@ -1686,9 +1697,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "m3u-filter" @@ -1746,6 +1757,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -1764,9 +1784,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -1785,9 +1805,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", @@ -1860,9 +1880,9 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "openssl" -version = "0.10.70" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -1901,9 +1921,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.105" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", @@ -1955,9 +1975,9 @@ checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" [[package]] name = "pem" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64 0.22.1", "serde", @@ -2142,9 +2162,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" dependencies = [ "cfg_aliases", "libc", @@ -2181,8 +2201,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.17", + "rand_core 0.9.2", + "zerocopy 0.8.20", ] [[package]] @@ -2202,7 +2222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.2", ] [[package]] @@ -2216,19 +2236,19 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.17", + "zerocopy 0.8.20", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags 2.8.0", ] @@ -2280,7 +2300,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.7", + "h2 0.4.8", "http 1.2.0", "http-body", "http-body-util", @@ -2322,15 +2342,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -2524,18 +2543,18 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -2544,9 +2563,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" dependencies = [ "itoa", "memchr", @@ -2650,9 +2669,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" @@ -2664,12 +2683,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2742,9 +2755,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.16.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand 2.3.0", @@ -2977,9 +2990,9 @@ checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" @@ -2995,9 +3008,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "unidecode" @@ -3088,6 +3101,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + [[package]] name = "waker-fn" version = "1.2.0" @@ -3506,11 +3525,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.17" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" dependencies = [ - "zerocopy-derive 0.8.17", + "zerocopy-derive 0.8.20", ] [[package]] @@ -3526,9 +3545,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.17" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" dependencies = [ "proc-macro2", "quote", @@ -3586,27 +3605,27 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.14+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index acd6b57ef..4352ff3b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ mime = "0.3" log = "0.4" env_logger = "0.11" rustelebot = "0.3" -bincode = "1.3" +bincode = { version = "2.0.0-rc.3", features = ["std", "serde"] } rand = "0.9" rpassword = "7.3" flate2 = "1" diff --git a/src/api/endpoints/v1_api.rs b/src/api/endpoints/v1_api.rs index f99837b77..53cc4e23e 100644 --- a/src/api/endpoints/v1_api.rs +++ b/src/api/endpoints/v1_api.rs @@ -12,13 +12,14 @@ use crate::api::endpoints::user_api::user_api_register; use crate::api::model::app_state::AppState; use crate::api::model::config::{ServerConfig, ServerInputConfig, ServerSourceConfig, ServerTargetConfig}; use crate::api::model::request::PlaylistRequest; -use crate::auth::authenticator::{validator_admin}; +use crate::auth::authenticator::validator_admin; use crate::m3u_filter_error::M3uFilterError; use crate::model::api_proxy::{ApiProxyConfig, ApiProxyServerInfo, TargetUser}; use crate::model::config::{validate_targets, Config, ConfigDto, ConfigInput, ConfigInputOptions, ConfigSource, ConfigTarget, InputType}; use crate::processing::processor::playlist; -use crate::utils::network::request::sanitize_sensitive_info; +use crate::repository::user_repository::store_api_user; use crate::utils::file::config_reader; +use crate::utils::network::request::sanitize_sensitive_info; use crate::utils::network::{m3u, xtream}; fn intern_save_config_api_proxy(backup_dir: &str, api_proxy: &ApiProxyConfig, file_path: &str) -> Option { @@ -56,18 +57,25 @@ async fn save_config_api_proxy_user( return HttpResponse::BadRequest().json(json!({"error": err.to_string()})); } if usernames.contains(&credential.username) { - return HttpResponse::BadRequest().json(json!({"error": format!("Duplicate usewrname {}", &credential.username)})); + return HttpResponse::BadRequest().json(json!({"error": format!("Duplicate username {}", &credential.username)})); } usernames.insert(&credential.username); } } + if let Some(api_proxy) = app_state.config.t_api_proxy.write().as_mut() { - let backup_dir = app_state.config.backup_dir.as_ref().unwrap().as_str(); api_proxy.user = users; - if let Some(err) = intern_save_config_api_proxy(backup_dir, api_proxy, app_state.config.t_api_proxy_file_path.as_str()) { - return HttpResponse::InternalServerError().json(json!({"error": err.to_string()})); - } api_proxy.user.iter_mut().flat_map(|t| &mut t.credentials).for_each(|c| c.prepare(true)); + if api_proxy.use_user_db { + if let Err(err) = store_api_user(&app_state.config, &api_proxy.user) { + return HttpResponse::InternalServerError().json(json!({"error": err.to_string()})); + } + } else { + let backup_dir = app_state.config.backup_dir.as_ref().unwrap().as_str(); + if let Some(err) = intern_save_config_api_proxy(backup_dir, api_proxy, app_state.config.t_api_proxy_file_path.as_str()) { + return HttpResponse::InternalServerError().json(json!({"error": err.to_string()})); + } + } } HttpResponse::Ok().finish() } @@ -227,7 +235,7 @@ async fn config( messaging: config.messaging.clone(), video: config.video.clone(), sources: config.sources.iter().map(map_source).collect(), - api_proxy: config_reader::read_api_proxy(app_state.config.t_api_proxy_file_path.as_str(), false), + api_proxy: config_reader::read_api_proxy(config, app_state.config.t_api_proxy_file_path.as_str(), false), }; let mut result = match config_reader::read_config(app_state.config.t_config_path.as_str(), @@ -261,7 +269,5 @@ pub fn v1_api_register(web_auth_enabled: bool) -> impl Fn(&mut web::ServiceConfi .route("/playlist/update", web::post().to(playlist_update)) .route("/file/download", web::post().to(download_api::queue_download_file)) .route("/file/download/info", web::get().to(download_api::download_file_info))); - - } } diff --git a/src/m3u_filter_error.rs b/src/m3u_filter_error.rs index f50dfdde3..58cdb9296 100644 --- a/src/m3u_filter_error.rs +++ b/src/m3u_filter_error.rs @@ -118,9 +118,9 @@ impl Display for M3uFilterError { impl Error for M3uFilterError {} pub fn to_io_error(err: E) -> std::io::Error -where E: std::error::Error { - std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) -} +where + E: std::error::Error, +{ std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) } pub fn str_to_io_error(err: &str) -> std::io::Error { std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) diff --git a/src/model/api_proxy.rs b/src/model/api_proxy.rs index fb78b3784..1554c1b42 100644 --- a/src/model/api_proxy.rs +++ b/src/model/api_proxy.rs @@ -1,12 +1,15 @@ -use std::collections::HashSet; -use std::fmt::Display; -use std::str::FromStr; -use chrono::{Local}; -use crate::m3u_filter_error::{create_m3u_filter_error_result, info_err, M3uFilterError, M3uFilterErrorKind}; -use crate::utils::file::config_reader; -use enum_iterator::Sequence; -use log::debug; use crate::api::model::app_state::AppState; +use crate::m3u_filter_error::{create_m3u_filter_error_result, info_err, M3uFilterError, M3uFilterErrorKind}; +use crate::model::config::Config; +use crate::repository::user_repository::{get_api_user_db_path, load_api_user, merge_api_user}; +use crate::utils::file::config_reader; +use chrono::Local; +use enum_iterator::Sequence; +use log::{debug}; +use std::collections::{HashSet}; +use std::fmt::Display; +use std::fs; +use std::str::FromStr; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq)] pub enum ProxyType { @@ -282,30 +285,84 @@ impl ApiProxyServerInfo { pub struct ApiProxyConfig { pub server: Vec, pub user: Vec, + #[serde(default)] + pub use_user_db: bool, + } impl ApiProxyConfig { - pub fn prepare(&mut self, resolve_var: bool) -> Result<(), M3uFilterError> { - let mut usernames = HashSet::new(); - let mut tokens = HashSet::new(); - let mut errors = Vec::new(); - if self.server.is_empty() { - errors.push("No server info defined".to_string()); - } else { - let mut name_set = HashSet::new(); - for server in &self.server { - if server.name.trim().is_empty() { - errors.push("Server info name is empty ".to_owned()); - } else if name_set.contains(server.name.as_str()) { - errors.push(format!( - "Non unique server info name found {}", - &server.name - )); + // we have the option to store user in the config file or in the user_db + // When we switch from one to other we need to migrate the existing data. + fn migrate_api_user(&mut self, cfg: &Config, errors: &mut Vec) { + if self.use_user_db { + // we have user defined in config file. + // we migrate them to the db and delete them from the config file + if !&self.user.is_empty() { + if let Err(err) = merge_api_user(cfg, &self.user) { + errors.push(err.to_string()); } else { - name_set.insert(server.name.clone()); + let api_proxy_file = cfg.t_api_proxy_file_path.as_str(); + let backup_dir = cfg.backup_dir.as_ref().unwrap().as_str(); + self.user = vec![]; + if let Err(err) = config_reader::save_api_proxy(api_proxy_file, backup_dir, &self) { + errors.push(format!("Error saving api proxy file: {err}")); + } + } + } + match load_api_user(cfg) { + Ok(users) => { + self.user = users; + } + Err(err) => { + println!("{err}"); + errors.push(err.to_string()) + }, + }; + } else { + let user_db_path = get_api_user_db_path(cfg); + if user_db_path.exists() { + // we cant have user defined in db file. + // we need to load them and save them into the config file + if let Ok(stored_users) = load_api_user(cfg) { + for stored_user in stored_users { + if let Some(target_user) = self.user.iter_mut().find(|t| t.target == stored_user.target) { + for stored_credential in stored_user.credentials.iter() { + if target_user.credentials.iter().find(|&c| c.username == stored_credential.username).is_none() { + target_user.credentials.push(stored_credential.clone()); + }; + } + } else { + self.user.push(stored_user); + } + } + } + let api_proxy_file = cfg.t_api_proxy_file_path.as_str(); + let backup_dir = cfg.backup_dir.as_ref().unwrap().as_str(); + if let Err(err) = config_reader::save_api_proxy(api_proxy_file, backup_dir, &self) { + errors.push(format!("Error saving api proxy file: {err}")); + } else { + let _ = fs::remove_file(&user_db_path); } } } + } + + fn prepare_server_config(&mut self, errors: &mut Vec) { + let mut name_set = HashSet::new(); + for server in &self.server { + if server.name.trim().is_empty() { + errors.push("Server info name is empty ".to_owned()); + } else if name_set.contains(server.name.as_str()) { + errors.push(format!("Non unique server info name found {}", &server.name)); + } else { + name_set.insert(server.name.clone()); + } + } + } + + fn prepare_target_user(&mut self, resolve_var: bool, errors: &mut Vec) { + let mut usernames = HashSet::new(); + let mut tokens = HashSet::new(); for target_user in &mut self.user { for user in &mut target_user.credentials { user.prepare(resolve_var); @@ -325,9 +382,7 @@ impl ApiProxyConfig { } if let Some(server_info_name) = &user.server { - if !&self - .server - .iter() + if !&self.server.iter() .any(|server_info| server_info.name.eq(server_info_name)) { errors.push(format!( @@ -338,6 +393,17 @@ impl ApiProxyConfig { } } } + } + + pub fn prepare(&mut self, cfg: &Config, resolve_var: bool) -> Result<(), M3uFilterError> { + let mut errors = Vec::new(); + if self.server.is_empty() { + errors.push("No server info defined".to_string()); + } else { + self.prepare_server_config(&mut errors); + } + self.prepare_target_user(resolve_var, &mut errors); + self.migrate_api_user(cfg, &mut errors); if errors.is_empty() { Ok(()) } else { diff --git a/src/processing/playlist_watch.rs b/src/processing/playlist_watch.rs index ef327c7fe..9f4478dbe 100644 --- a/src/processing/playlist_watch.rs +++ b/src/processing/playlist_watch.rs @@ -4,6 +4,7 @@ use log::{error, info}; use crate::messaging::{MsgKind, send_message}; use crate::model::config::Config; use crate::model::playlist::PlaylistGroup; +use crate::utils::bincode_utils::{bincode_deserialize, bincode_serialize}; use crate::utils::file::file_utils; use crate::utils::file::file_utils::sanitize_filename; @@ -76,13 +77,13 @@ fn handle_watch_notification(cfg: &Config, added: &BTreeSet, removed: &B fn load_watch_tree(path: &Path) -> Option> { std::fs::read(path).map_or(None, |encoded| { - let decoded: BTreeSet = bincode::deserialize(&encoded[..]).unwrap(); + let decoded = bincode_deserialize(&encoded[..]).ok()?; Some(decoded) }) } fn save_watch_tree(path: &Path, tree: &BTreeSet) -> std::io::Result<()> { - let encoded: Vec = bincode::serialize(&tree).unwrap(); + let encoded: Vec = bincode_serialize(&tree)?; std::fs::write(path, encoded) } diff --git a/src/processing/processor/xtream_series.rs b/src/processing/processor/xtream_series.rs index 1a997c51f..fdcb2b904 100644 --- a/src/processing/processor/xtream_series.rs +++ b/src/processing/processor/xtream_series.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use std::time::Instant; use log::{info, log_enabled, Level}; use crate::model::xtream::{XtreamSeriesEpisode, XtreamSeriesInfoEpisode}; +use crate::utils::bincode_utils::bincode_serialize; use crate::utils::file::file_utils::file_writer; const TAG_SERIES_INFO_LAST_MODIFIED: &str = "last_modified"; @@ -42,7 +43,7 @@ fn write_series_episode_record_to_wal_file( episode: &XtreamSeriesInfoEpisode, ) -> std::io::Result<()> { let series_episode = XtreamSeriesEpisode::from(episode); - if let Ok(content_bytes) = bincode::serialize(&series_episode) { + if let Ok(content_bytes) = bincode_serialize(&series_episode) { writer.write_all(&provider_id.to_le_bytes())?; let len = u32::try_from(content_bytes.len()).unwrap(); writer.write_all(&len.to_le_bytes())?; diff --git a/src/repository/bplustree.rs b/src/repository/bplustree.rs index 528df9dc5..0796d4aee 100644 --- a/src/repository/bplustree.rs +++ b/src/repository/bplustree.rs @@ -3,7 +3,6 @@ use std::io::{self, BufReader, Read, Seek, SeekFrom, Write}; use std::marker::PhantomData; use std::mem::size_of; use std::path::Path; - use crate::m3u_filter_error::{str_to_io_error, to_io_error}; use crate::utils::file::file_utils::{file_reader, file_writer, open_read_write_file, rename_or_copy}; use log::error; @@ -11,6 +10,7 @@ use ruzstd::decoding::StreamingDecoder; use ruzstd::encoding::{compress_to_vec, CompressionLevel}; use serde::{Deserialize, Serialize}; use tempfile::NamedTempFile; +use crate::utils::bincode_utils::{bincode_deserialize, bincode_serialize}; const BLOCK_SIZE: usize = 4096; const BINCODE_OVERHEAD: usize = 8; @@ -39,22 +39,6 @@ fn u32_from_bytes(bytes: &[u8]) -> io::Result { Ok(u32::from_le_bytes(bytes.try_into().map_err(to_io_error)?)) } -#[inline] -fn bincode_serialize(value: &T) -> io::Result> -where - T: ?Sized + serde::Serialize, -{ - bincode::serialize(value).map_err(to_io_error) -} - -#[inline] -fn bincode_deserialize(value: &[u8]) -> io::Result -where - T: for<'a> serde::Deserialize<'a>, -{ - bincode::deserialize(value).map_err(to_io_error) -} - fn get_entry_index_upper_bound(keys: &[K], key: &K) -> usize where diff --git a/src/repository/indexed_document.rs b/src/repository/indexed_document.rs index e41bd1bfb..b0ab19515 100644 --- a/src/repository/indexed_document.rs +++ b/src/repository/indexed_document.rs @@ -10,6 +10,7 @@ use log::error; use serde::{Deserialize, Serialize}; use tempfile::NamedTempFile; use crate::m3u_filter_error::{str_to_io_error, to_io_error}; +use crate::utils::bincode_utils::{bincode_deserialize, bincode_serialize}; use crate::utils::file::file_utils::{create_new_file_for_read_write, file_reader, file_writer, open_read_write_file, open_readonly_file, rename_or_copy}; const BLOCK_SIZE: usize = 4096; @@ -163,7 +164,7 @@ where where T: ?Sized + serde::Serialize, { - let encoded_bytes = bincode::serialize(doc).map_err(|_| Error::new(ErrorKind::InvalidData, "Failed to serialize document"))?; + let encoded_bytes = bincode_serialize(doc).map_err(|_| Error::new(ErrorKind::InvalidData, "Failed to serialize document"))?; let mut new_record_appended = false; // do i need to change the index and set the new offset if let Some(&offset) = self.index_tree.query(&doc_id) { self.main_file.seek(SeekFrom::Start(u64::from(offset)))?; @@ -266,7 +267,7 @@ where let buf_size = IndexedDocument::read_content_size(&mut self.main_file)?; let mut buffer: Vec = vec![0; buf_size]; self.main_file.read_exact(&mut buffer)?; - if let Ok(item) = bincode::deserialize::(&buffer) { + if let Ok(item) = bincode_deserialize::(&buffer) { return Ok(item); } } @@ -351,7 +352,7 @@ where // read content self.main_file.read_exact(&mut self.t_buffer[0..buf_size])?; // deserialize buffer - match bincode::deserialize::(&self.t_buffer[0..buf_size]) { + match bincode_deserialize::(&self.t_buffer[0..buf_size]) { Ok(value) => Ok(Some(value)), Err(err) => { self.failed = true; @@ -399,7 +400,7 @@ impl IndexedDocumentDirectAccess { let buf_size = IndexedDocument::read_content_size(&mut main_file)?; let mut buffer: Vec = vec![0; buf_size]; main_file.read_exact(&mut buffer)?; - if let Ok(item) = bincode::deserialize::(&buffer) { + if let Ok(item) = bincode_deserialize::(&buffer) { return Ok(item); } } diff --git a/src/repository/user_repository.rs b/src/repository/user_repository.rs index f26e4401f..c36defac8 100644 --- a/src/repository/user_repository.rs +++ b/src/repository/user_repository.rs @@ -1,16 +1,86 @@ -use std::collections::HashSet; +use crate::model::api_proxy::{ProxyUserCredentials, TargetUser}; use crate::model::config::Config; +use crate::model::playlist::XtreamCluster; use crate::model::playlist_categories::{PlaylistCategoriesDto, PlaylistCategoryDto}; +use crate::repository::bplustree::BPlusTree; use crate::utils::file::file_utils; use crate::utils::json_utils::json_write_documents_to_file; use log::error; +use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; -use crate::model::playlist::XtreamCluster; const USER_LIVE_BOUQUET: &str = "live_bouquet.json"; const USER_VOD_BOUQUET: &str = "vod_bouquet.json"; const USER_SERIES_BOUQUET: &str = "series_bouquet.json"; + +// This is a Helper class to store all user into one Database file. +// For the Config files we keep the old structure where a user is assigned to a target. +// But for storing inside one db file it is easier to store the target next to the user. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +struct StoredProxyUserCredentials { + pub target: String, + pub user: ProxyUserCredentials, +} + +pub fn get_api_user_db_path(cfg: &Config) -> PathBuf { + PathBuf::from(&cfg.t_config_path).join("api_user.db") +} + + +fn add_target_user_to_user_tree(target_users: &[TargetUser], user_tree: &mut BPlusTree) { + for target_user in target_users { + for user in &target_user.credentials { + let store_user = StoredProxyUserCredentials { + target: target_user.target.clone(), + user: user.clone(), + }; + user_tree.insert(user.username.clone(), store_user); + } + } +} + +pub fn merge_api_user(cfg: &Config, target_users: &[TargetUser]) -> Result { + let path = get_api_user_db_path(&cfg); + let _lock = cfg.file_locks.read_lock(&path); + let mut user_tree: BPlusTree = BPlusTree::load(&path).unwrap_or_else(|_| BPlusTree::new()); + drop(_lock); + add_target_user_to_user_tree(target_users, &mut user_tree); + let _lock = cfg.file_locks.write_lock(&path); + user_tree.store(&path) +} + +pub fn store_api_user(cfg: &Config, target_users: &[TargetUser]) -> Result { + let mut user_tree = BPlusTree::::new(); + add_target_user_to_user_tree(target_users, &mut user_tree); + let path = get_api_user_db_path(&cfg); + let _lock = cfg.file_locks.write_lock(&path); + user_tree.store(&path) +} + +pub fn load_api_user(cfg: &Config) -> Result, std::io::Error> { + let path = get_api_user_db_path(&cfg); + let _lock = cfg.file_locks.read_lock(&path); + let user_tree = BPlusTree::::load(&path)?; + drop(_lock); + let mut target_users: HashMap = HashMap::new(); + for (_uname, user_wrapper) in user_tree.iter() { + match target_users.entry(user_wrapper.target.clone()) { + std::collections::hash_map::Entry::Occupied(mut entry) => { + let target = entry.get_mut(); + target.credentials.push(user_wrapper.user.clone()); + } + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(TargetUser { + target: user_wrapper.target.clone(), + credentials: vec![user_wrapper.user.clone()], + }); + } + } + } + Ok(target_users.into_values().collect()) +} + pub fn get_user_storage_path(cfg: &Config, username: &str) -> Option { cfg.user_config_dir.as_ref().and_then(|ucd| file_utils::get_file_path(ucd, Some(std::path::PathBuf::from(username)))) } @@ -68,7 +138,7 @@ pub async fn load_user_bouquet_as_json(cfg: &Config, username: &str) -> Option Option> { if let Some(storage_path) = get_user_storage_path(cfg, username) { if storage_path.exists() { - let content = load_user_bouquet_from_file(& match cluster { + let content = load_user_bouquet_from_file(&match cluster { XtreamCluster::Live => user_get_live_bouquet_path(&storage_path), XtreamCluster::Video => user_get_vod_bouquet_path(&storage_path), XtreamCluster::Series => user_get_series_bouquet_path(&storage_path), diff --git a/src/repository/xtream_repository.rs b/src/repository/xtream_repository.rs index 9fa41aa19..d3d8dc79e 100644 --- a/src/repository/xtream_repository.rs +++ b/src/repository/xtream_repository.rs @@ -24,6 +24,7 @@ use crate::repository::xtream_playlist_iterator::XtreamPlaylistIterator; use crate::utils::file::file_utils::open_readonly_file; use crate::utils::hash_utils::generate_playlist_uuid; use crate::utils::json_utils::{get_u32_from_serde_value, json_iter_array, json_write_documents_to_file}; +use crate::utils::bincode_utils::{bincode_deserialize}; pub static COL_CAT_LIVE: &str = "cat_live"; pub static COL_CAT_SERIES: &str = "cat_series"; @@ -922,7 +923,7 @@ pub async fn xtream_update_input_series_episodes_record_from_wal_file( if reader.read_exact(&mut buffer[0..len]).is_err() { break; } - match bincode::deserialize(&buffer[0..len]) { + match bincode_deserialize(&buffer[0..len]) { Ok(episode) => { tree_record_index.insert(provider_id, episode); } diff --git a/src/utils/bincode_utils.rs b/src/utils/bincode_utils.rs new file mode 100644 index 000000000..cb8beaf1d --- /dev/null +++ b/src/utils/bincode_utils.rs @@ -0,0 +1,21 @@ +use std::io; +use crate::m3u_filter_error::to_io_error; + +#[inline] +pub fn bincode_serialize(value: &T) -> io::Result> +where + T: ?Sized + serde::Serialize, +{ + bincode::serde::encode_to_vec(value, bincode::config::legacy()).map_err(to_io_error) +} + +#[inline] +pub fn bincode_deserialize(value: &[u8]) -> io::Result +where + T: for<'a> serde::Deserialize<'a>, +{ + match bincode::serde::decode_from_slice(value, bincode::config::legacy()) { + Ok((instance, _size)) => Ok(instance), + Err(e) => Err(to_io_error(e)), + } +} diff --git a/src/utils/file/config_reader.rs b/src/utils/file/config_reader.rs index 1e9dea170..f2519573c 100644 --- a/src/utils/file/config_reader.rs +++ b/src/utils/file/config_reader.rs @@ -36,7 +36,7 @@ pub fn read_mappings(args_mapping: Option, cfg: &mut Config) -> Result, cfg: &mut Config) -> Option { let api_proxy_config_file: String = args_api_proxy_config.unwrap_or_else(|| file_utils::get_default_api_proxy_config_path(cfg.t_config_path.as_str())); api_proxy_config_file.clone_into(&mut cfg.t_api_proxy_file_path); - let api_proxy_config = read_api_proxy(api_proxy_config_file.as_str(), true); + let api_proxy_config = read_api_proxy(cfg, api_proxy_config_file.as_str(), true); match api_proxy_config { None => { warn!("cant read api_proxy_config file: {}", api_proxy_config_file.as_str()); @@ -90,12 +90,12 @@ pub fn read_mapping(mapping_file: &str) -> Result, M3uFilterErr Ok(None) } -pub fn read_api_proxy(api_proxy_file: &str, resolve_var: bool) -> Option { +pub fn read_api_proxy(config: &Config, api_proxy_file: &str, resolve_var: bool) -> Option { file_utils::open_file(&std::path::PathBuf::from(api_proxy_file)).map_or(None, |file| { let mapping: Result = serde_yaml::from_reader(file); match mapping { Ok(mut result) => { - match result.prepare(resolve_var) { + match result.prepare(config, resolve_var) { Err(err) => { exit!("cant read api-proxy-config file: {}", err); } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d156014b8..0eb4e331b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,6 +7,7 @@ pub mod hash_utils; pub mod compression; pub(crate) mod file; pub(crate) mod network; +pub mod bincode_utils; #[macro_export] macro_rules! debug_if_enabled {