Merge pull request #307 from euzu/feature/refactor_config

Feature/refactor config
This commit is contained in:
euzu
2025-06-19 17:49:37 +02:00
committed by GitHub
214 changed files with 5790 additions and 1195 deletions
+1
View File
@@ -1,3 +1,4 @@
dist
/resources/*.ts
/release
/target
Generated
+64 -58
View File
@@ -143,9 +143,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.24"
version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d615619615a650c571269c00dca41db04b9210037fa76ed8239f70404ab56985"
checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4"
dependencies = [
"brotli",
"flate2",
@@ -165,9 +165,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "axum"
@@ -368,9 +368,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
[[package]]
name = "cc"
version = "1.2.26"
version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"jobserver",
"libc",
@@ -995,7 +995,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasm-bindgen",
]
@@ -1457,9 +1457,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"log",
@@ -1470,9 +1470,9 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
@@ -1542,9 +1542,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.172"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "libnghttp2-sys"
@@ -1627,9 +1627,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "mime"
@@ -1664,7 +1664,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
@@ -1914,9 +1914,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
dependencies = [
"memchr",
"thiserror 2.0.12",
@@ -1925,9 +1925,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
dependencies = [
"pest",
"pest_generator",
@@ -1935,9 +1935,9 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
dependencies = [
"pest",
"pest_meta",
@@ -1948,11 +1948,10 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
dependencies = [
"once_cell",
"pest",
"sha2",
]
@@ -2061,15 +2060,15 @@ dependencies = [
[[package]]
name = "quanta"
version = "0.12.5"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e"
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
dependencies = [
"crossbeam-utils",
"libc",
"once_cell",
"raw-cpuid",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi 0.11.1+wasi-snapshot-preview1",
"web-sys",
"winapi",
]
@@ -2126,9 +2125,9 @@ dependencies = [
[[package]]
name = "quinn-udp"
version = "0.5.12"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842"
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
dependencies = [
"cfg_aliases",
"libc",
@@ -2149,9 +2148,9 @@ dependencies = [
[[package]]
name = "r-efi"
version = "5.2.0"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
@@ -2213,9 +2212,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.12"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [
"bitflags 2.9.1",
]
@@ -2251,9 +2250,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "reqwest"
version = "0.12.19"
version = "0.12.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119"
checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -2269,12 +2268,10 @@ dependencies = [
"hyper-rustls",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"quinn",
@@ -2400,9 +2397,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.27"
version = "0.23.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
dependencies = [
"once_cell",
"ring",
@@ -2579,6 +2576,17 @@ dependencies = [
"digest",
]
[[package]]
name = "shared"
version = "0.1.0"
dependencies = [
"bitflags 2.9.1",
"enum-iterator",
"rand",
"regex",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
@@ -2599,12 +2607,9 @@ dependencies = [
[[package]]
name = "slab"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
[[package]]
name = "sluice"
@@ -2662,9 +2667,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.102"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",
@@ -2984,9 +2989,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.29"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
@@ -3024,7 +3029,7 @@ version = "3.1.4"
dependencies = [
"arc-swap",
"axum",
"base64 0.21.7",
"base64 0.22.1",
"bincode",
"bitflags 2.9.1",
"blake3",
@@ -3065,6 +3070,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"shared",
"socket2",
"strsim",
"tempfile",
@@ -3220,9 +3226,9 @@ dependencies = [
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
@@ -3414,9 +3420,9 @@ dependencies = [
[[package]]
name = "windows-link"
version = "0.1.1"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-registry"
@@ -3645,18 +3651,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.25"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
+3 -69
View File
@@ -1,7 +1,6 @@
[package]
name = "tuliprox"
version = "3.1.4"
edition = "2021"
[workspace]
members = ["backend", "shared"]
resolver = "2"
[profile.release]
debug = false
@@ -11,68 +10,3 @@ codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = 'abort' # Abort on panic
strip = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive", "rc"] }
serde_yaml = "0.9"
serde_json = { version = "1", features = ["arbitrary_precision"] }
quick-xml = "0.37"
regex = "1.11"
clap = { version = "4", features = ["derive"] }
url = "2.5"
reqwest = { version = "0", features = ["blocking", "json", "stream", "rustls-tls"] }
chrono = "0.4"
cron = "0.15"
axum = { version = "0" , features = ["macros", "default"]}
tower = "0"
tower-http = { version = "0", features = ["cors", "auth", "fs", "compression-full"]}
tower_governor = { version = "0.7", features = ["axum"] }
jsonwebtoken = "9.3"
rust-argon2 = "2.1"
futures = "0.3"
path-clean = "1"
pest = "2.8"
pest_derive = "2.8"
enum-iterator = "2"
openssl = { version = "*", features = ["vendored"] } #https://docs.rs/openssl/0.10.34/openssl/#vendored
deunicode = "1.6.2"
mime = "0.3"
log = "0.4"
env_logger = "0.11"
rustelebot = "0.3"
bincode = { version = "2.0.1", features = ["std", "serde"] }
rand = "0.9"
rpassword = "7.4"
flate2 = "1"
blake3 = "1.8"
bytes = "1.10"
tokio-stream = { version = "0.1", features = ["sync"] }
tokio = { version = "1.45", features = ["rt-multi-thread", "parking_lot", "fs"] }
tokio-util = "0.7"
tempfile = "3.20"
ruzstd = "0"
filetime = "0.2"
zeroize = "1.8"
strsim = "0.11"
rphonetic = "3"
rayon = "1.10"
base64 = "0"
paste = "1"
bitflags = "2"
indexmap = "2"
notify = "8"
arc-swap = "1"
dashmap = "6"
hyper = "1"
hyper-util = "0"
socket2 = "0"
#[cfg(target_os = "macos")]
libc = "0"
#[cfg(target_os = "windows")]
winapi = { version = "0", features = ["psapi", "minwindef", "processthreadsapi"] }
# [target.'cfg(not(target_env = "msvc"))'.dependencies]
# tikv-jemallocator = { version = "*", features = ["profiling", "unprefixed_malloc_on_supported_platforms"] }
[build-dependencies]
vergen = { version = "9", features = ["build"] }
+4 -4
View File
@@ -1516,7 +1516,7 @@ Ease way to compile is a docker toolchain `cross`
```shell
rust install cross
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build --release --target x86_64-unknown-linux-musl
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target x86_64-unknown-linux-musl
```
#### Manual compile - install prerequisites
@@ -1527,7 +1527,7 @@ rustup target add x86_64-unknown-linux-musl
```
#### Build statically linked binary
```shell
cargo build --target x86_64-unknown-linux-musl --release
cargo build -p tuliprox --target x86_64-unknown-linux-musl --release
```
#### Dockerize
There is a Dockerfile in `docker` directory.
@@ -1645,7 +1645,7 @@ rustup toolchain install stable-x86_64-pc-windows-gnu
Compile it with:
```shell
cargo build --release --target x86_64-pc-windows-gnu
cargo build -p tuliprox --release --target x86_64-pc-windows-gnu
```
### Cross compile for raspberry pi 2/3/4
@@ -1654,7 +1654,7 @@ Ease way to compile is a docker toolchain `cross`
```shell
rust install cross
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build --release --target armv7-unknown-linux-musleabihf
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target armv7-unknown-linux-musleabihf
```
# Different Scenarios
+3752
View File
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
[package]
name = "tuliprox"
version = "3.1.4"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
shared = {version = "0", path = "../shared" }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_yaml = "0.9"
serde_json = { version = "1", features = ["arbitrary_precision"] }
quick-xml = "0.37"
regex = "1.11"
clap = { version = "4", features = ["derive"] }
url = "2.5"
reqwest = { version = "0", features = ["blocking", "json", "stream", "rustls-tls"] }
chrono = "0.4"
cron = "0.15"
axum = { version = "0" , features = ["macros", "default"]}
tower = "0"
tower-http = { version = "0", features = ["cors", "auth", "fs", "compression-full"]}
tower_governor = { version = "0.7", features = ["axum"] }
jsonwebtoken = "9.3"
rust-argon2 = "2.1"
futures = "0.3"
path-clean = "1"
pest = "2.8"
pest_derive = "2.8"
enum-iterator = "2"
openssl = { version = "*", features = ["vendored"] } #https://docs.rs/openssl/0.10.34/openssl/#vendored
deunicode = "1.6.2"
mime = "0.3"
log = "0.4"
env_logger = "0.11"
rustelebot = "0.3"
bincode = { version = "2.0.1", features = ["std", "serde"] }
rand = "0.9"
rpassword = "7.4"
flate2 = "1"
blake3 = "1.8"
bytes = "1.10"
tokio-stream = { version = "0.1", features = ["sync"] }
tokio = { version = "1.45", features = ["rt-multi-thread", "parking_lot", "fs"] }
tokio-util = "0.7"
tempfile = "3.20"
ruzstd = "0"
filetime = "0.2"
zeroize = "1.8"
strsim = "0.11"
rphonetic = "3"
rayon = "1.10"
base64 = "0"
paste = "1"
bitflags = "2"
indexmap = "2"
notify = "8"
arc-swap = "1"
dashmap = "6"
hyper = "1"
hyper-util = "0"
socket2 = "0"
#[cfg(target_os = "macos")]
libc = "0"
#[cfg(target_os = "windows")]
winapi = { version = "0", features = ["psapi", "minwindef", "processthreadsapi"] }
# [target.'cfg(not(target_env = "msvc"))'.dependencies]
# tikv-jemallocator = { version = "*", features = ["profiling", "unprefixed_malloc_on_supported_platforms"] }
[build-dependencies]
vergen = { version = "9", features = ["build"] }
View File
@@ -12,17 +12,16 @@ use crate::api::model::streams::provider_stream_factory::{create_provider_stream
use crate::api::model::streams::shared_stream_manager::SharedStreamManager;
use crate::api::model::streams::throttled_stream::ThrottledStream;
use crate::auth::Claims;
use crate::model::{ProxyUserCredentials, UserConnectionPermission};
use crate::model::{ConfigInput, ConfigTarget, InputFetchMethod, TargetType};
use crate::model::{PlaylistEntry, PlaylistItemType, XtreamCluster};
use crate::model::{ConfigTarget, ProxyUserCredentials};
use crate::model::{ConfigInput, InputFetchMethod};
use shared::model::{PlaylistEntry, PlaylistItemType, TargetType, UserConnectionPermission, XtreamCluster};
use crate::tools::atomic_once_flag::AtomicOnceFlag;
use crate::tools::lru_cache::LRUResourceCache;
use crate::utils::{DASH_EXT, HLS_EXT};
use crate::utils::default_grace_period_millis;
use shared::utils::{DASH_EXT, HLS_EXT};
use shared::utils::{default_grace_period_millis, human_readable_byte_size};
use crate::utils::create_new_file_for_write;
use crate::utils::request;
use crate::utils::request::{extract_extension_from_url, replace_url_extension, sanitize_sensitive_info};
use crate::utils::human_readable_byte_size;
use crate::utils::{debug_if_enabled, trace_if_enabled};
use crate::{BUILD_TIMESTAMP};
use axum::body::Body;
@@ -1,5 +1,5 @@
use crate::api::model::app_state::AppState;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::utils;
use crate::utils::is_directory;
use log::{debug, error, info};
@@ -1,5 +1,6 @@
use crate::model::{Config, ConfigInput, ConfigTarget, InputType, TargetType};
use crate::model::{M3uPlaylistItem, PlaylistGroup, PlaylistItemType, XtreamCluster};
use crate::model::{Config, ConfigInput, ConfigTarget, InputType};
use crate::model::{M3uPlaylistItem, PlaylistGroup};
use shared::model::{PlaylistItemType, TargetType, XtreamCluster};
use crate::repository::{m3u_repository, xtream_repository};
use crate::utils::{m3u, xtream};
use axum::response::IntoResponse;
@@ -12,7 +12,8 @@ use std::ops::Deref;
use std::sync::Arc;
use std::{fs};
use axum::response::IntoResponse;
use crate::tuliprox_error::to_io_error;
use shared::utils::bytes_to_megabytes;
use shared::error::to_io_error;
use crate::utils::request::create_client;
async fn download_file(active: Arc<RwLock<Option<FileDownload>>>, client: &reqwest::Client) -> Result<(), String> {
@@ -39,7 +40,7 @@ async fn download_file(active: Arc<RwLock<Option<FileDownload>>>, client: &reqwe
Err(err) => return Err(format!("Error while writing to file: {file_path_str} {err}"))
}
} else {
let megabytes = request::bytes_to_megabytes(downloaded);
let megabytes = bytes_to_megabytes(downloaded);
info!("Downloaded {file_path_str}, filesize: {megabytes}MB");
active.write().await.as_mut().unwrap().size = downloaded;
return Ok(());
@@ -1,8 +1,9 @@
use crate::api::model::app_state::HdHomerunAppState;
use crate::auth::AuthBasic;
use crate::model::ProxyUserCredentials;
use crate::model::{Config, ConfigTarget, TargetType};
use crate::model::{M3uPlaylistItem, PlaylistItemType, XtreamCluster, XtreamPlaylistItem};
use crate::model::{ConfigTarget, ProxyUserCredentials};
use crate::model::{Config};
use crate::model::{M3uPlaylistItem, XtreamPlaylistItem};
use shared::model::{PlaylistItemType, TargetType, XtreamCluster};
use crate::processing::parser::xtream::get_xtream_url;
use crate::repository::m3u_playlist_iterator::M3uPlaylistIterator;
use crate::repository::xtream_playlist_iterator::XtreamPlaylistIterator;
@@ -2,11 +2,11 @@ use crate::api::api_utils::{force_provider_stream_response, get_stream_alternati
use crate::api::api_utils::{try_option_bad_request};
use crate::api::model::app_state::AppState;
use crate::api::model::streams::provider_stream::{create_custom_video_stream_response, CustomVideoStreamType};
use crate::model::{ProxyUserCredentials, UserConnectionPermission};
use crate::model::{ProxyUserCredentials};
use crate::model::ConfigInput;
use crate::model::{PlaylistItemType, XtreamCluster};
use shared::model::{PlaylistItemType, UserConnectionPermission, XtreamCluster};
use crate::processing::parser::hls::{get_hls_session_token_and_url_from_token, rewrite_hls, RewriteHlsProps};
use crate::utils::HLS_EXT;
use shared::utils::HLS_EXT;
use crate::utils::request;
use crate::utils::request::{is_hls_url, replace_url_extension, sanitize_sensitive_info};
use axum::response::IntoResponse;
@@ -4,13 +4,11 @@ use crate::api::endpoints::xtream_api::{ApiStreamContext, ApiStreamRequest};
use crate::api::model::app_state::AppState;
use crate::api::model::request::UserApiRequest;
use crate::api::model::streams::provider_stream::{create_custom_video_stream_response, CustomVideoStreamType};
use crate::model::TargetType;
use crate::model::UserConnectionPermission;
use crate::model::{FieldGetAccessor, PlaylistEntry, PlaylistItemType, XtreamCluster};
use shared::model::{FieldGetAccessor, PlaylistEntry, PlaylistItemType, TargetType, UserConnectionPermission, XtreamCluster};
use crate::repository::m3u_repository::{m3u_get_item_for_stream_id, m3u_load_rewrite_playlist};
use crate::repository::storage_const;
use crate::utils::request::{extract_extension_from_url, sanitize_sensitive_info};
use crate::utils::HLS_EXT;
use shared::utils::HLS_EXT;
use axum::http::{HeaderMap, StatusCode};
use axum::response::IntoResponse;
use bytes::Bytes;
@@ -1,8 +1,8 @@
use crate::api::api_utils::{get_user_target_by_username, get_username_from_auth_header};
use crate::api::model::app_state::AppState;
use crate::auth::validator_user;
use crate::model::{Config, ConfigTarget, TargetType};
use crate::model::XtreamCluster;
use crate::model::{Config, ConfigTarget};
use shared::model::{TargetType, XtreamCluster};
use crate::model::PlaylistBouquetDto;
use crate::model::PlaylistXtreamCategory;
use crate::repository::user_repository::{load_user_bouquet_as_json, save_user_bouquet};
@@ -6,10 +6,10 @@ use crate::api::model::config::{ServerConfig, ServerInputConfig, ServerSourceCon
use crate::api::model::request::{PlaylistRequest, PlaylistRequestType};
use crate::auth::create_access_token;
use crate::auth::validator_admin;
use crate::tuliprox_error::TuliproxError;
use crate::model::StatusCheck;
use shared::error::TuliproxError;
use crate::model::{ConfigTarget, StatusCheck};
use crate::model::XtreamPlaylistItem;
use crate::model::{Config, ConfigDto, ConfigInput, ConfigInputOptions, ConfigSource, ConfigTarget, InputType};
use crate::model::{Config, ConfigInput, ConfigInputOptions, ConfigSource, InputType};
use crate::model::{ApiProxyConfig, ApiProxyServerInfo, ProxyUserCredentials, TargetUser};
use crate::processing::processor::playlist;
use crate::repository::user_repository::store_api_user;
@@ -21,6 +21,7 @@ use log::error;
use serde_json::json;
use std::collections::{BTreeMap, HashSet};
use std::sync::Arc;
use shared::model::ConfigDto;
fn intern_save_config_api_proxy(backup_dir: &str, api_proxy: &ApiProxyConfig, file_path: &str) -> Option<TuliproxError> {
match utils::save_api_proxy(file_path, backup_dir, api_proxy) {
@@ -8,7 +8,7 @@ use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc};
use tower::Service;
use crate::utils::CONSTANTS;
use shared::utils::CONSTANTS;
fn no_web_auth_token() -> impl axum::response::IntoResponse + Send {
axum::Json(HashMap::from([("token", "authorized")])).into_response()
@@ -12,8 +12,8 @@ use std::sync::Arc;
use crate::api::api_utils::{get_user_target, serve_file};
use crate::api::model::app_state::AppState;
use crate::api::model::request::UserApiRequest;
use crate::model::ProxyUserCredentials;
use crate::model::{Config, ConfigTarget, TargetOutput};
use crate::model::{ConfigTarget, ProxyUserCredentials, TargetOutput};
use crate::model::{Config};
use crate::repository::m3u_repository::m3u_get_epg_file_path;
use crate::repository::storage::get_target_storage_path;
use crate::repository::xtream_repository::{xtream_get_epg_file_path, xtream_get_storage_path};
@@ -9,22 +9,21 @@ use crate::api::model::app_state::AppState;
use crate::api::model::request::UserApiRequest;
use crate::api::model::streams::provider_stream::{create_custom_video_stream_response, CustomVideoStreamType};
use crate::api::model::xtream::XtreamAuthorizationResponse;
use crate::model::TargetType;
use crate::model::{get_backdrop_path_value, FieldGetAccessor, PlaylistEntry, PlaylistItemType, XtreamCluster, XtreamPlaylistItem};
use crate::model::{Config, ConfigInput, ConfigTarget};
use crate::model::{ProxyType, ProxyUserCredentials, UserConnectionPermission};
use crate::model::{get_backdrop_path_value, ConfigTarget, XtreamPlaylistItem};
use crate::model::{Config, ConfigInput};
use crate::model::{ProxyUserCredentials};
use crate::repository::playlist_repository::get_target_id_mapping;
use crate::repository::storage::{get_target_storage_path, hex_encode};
use crate::repository::storage::{get_target_storage_path};
use crate::repository::{storage_const, user_repository, xtream_repository};
use crate::tuliprox_error::create_tuliprox_error_result;
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use crate::utils::generate_playlist_uuid;
use shared::error::create_tuliprox_error_result;
use shared::error::info_err;
use shared::error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use crate::utils::{generate_playlist_uuid, hex_encode};
use crate::utils::get_u32_from_serde_value;
use crate::utils::request::{extract_extension_from_url, sanitize_sensitive_info};
use crate::utils::trace_if_enabled;
use crate::utils::xtream::create_vod_info_from_item;
use crate::utils::HLS_EXT;
use shared::utils::HLS_EXT;
use crate::utils::{request, xtream};
use crate::auth::Fingerprint;
use axum::http::{HeaderMap, StatusCode};
@@ -41,6 +40,7 @@ use std::fmt::{Display, Formatter};
use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;
use shared::model::{PlaylistItemType, XtreamCluster, FieldGetAccessor, PlaylistEntry, TargetType, UserConnectionPermission, ProxyType};
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)]
pub enum ApiStreamContext {
@@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use crate::api::model::provider_config::{ProviderConfig, ProviderConfigWrapper};
use crate::utils::{default_grace_period_millis, default_grace_period_timeout_secs};
use shared::utils::{default_grace_period_millis, default_grace_period_timeout_secs};
pub struct ProviderConnectionGuard {
// manager: Arc<ActiveProviderManager>,
@@ -1,13 +1,14 @@
use crate::model::Config;
use crate::model::{ProxyUserCredentials, UserConnectionPermission};
use crate::model::{ProxyUserCredentials};
use crate::utils::request::sanitize_sensitive_info;
use crate::utils::{current_time_secs, default_grace_period_millis, default_grace_period_timeout_secs};
use shared::utils::{current_time_secs, default_grace_period_millis, default_grace_period_timeout_secs};
use jsonwebtoken::get_current_timestamp;
use log::{debug, info};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
use shared::model::UserConnectionPermission;
const USER_CON_TTL: u64 = 10_800; // 3 hours
const USER_SESSION_LIMIT: usize = 50;
@@ -1,10 +1,11 @@
use tokio::sync::{Mutex};
use std::sync::Arc;
use shared::model::UserConnectionPermission;
use crate::api::model::active_provider_manager::ActiveProviderManager;
use crate::api::model::active_user_manager::ActiveUserManager;
use crate::api::model::download::DownloadQueue;
use crate::api::model::streams::shared_stream_manager::SharedStreamManager;
use crate::model::{Config, UserConnectionPermission, HdHomeRunDeviceConfig};
use crate::model::{Config, HdHomeRunDeviceConfig};
use crate::tools::lru_cache::LRUResourceCache;
#[derive(Clone)]
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::model::{ApiProxyConfig, ConfigApi, InputType, LogConfig, MessagingConfig, ProcessingOrder, ReverseProxyConfig, ScheduleConfig, VideoConfig, ConfigRename, ConfigSort, ConfigTargetOptions, TargetOutput, WebUiConfig, ProxyConfig, IpCheckConfig};
use shared::model::ProcessingOrder;
use crate::model::{ApiProxyConfig, ConfigApi, InputType, LogConfig, MessagingConfig, ReverseProxyConfig, ScheduleConfig, VideoConfig, ConfigSort, WebUiConfig, ProxyConfig, IpCheckConfig, ConfigTargetOptions, TargetOutput, ConfigRename};
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ServerInputConfig {
@@ -7,8 +7,8 @@ use deunicode::deunicode;
use serde::{Deserialize, Serialize};
use crate::model::{VideoDownloadConfig};
use crate::repository::storage::hash_string_as_hex;
use crate::utils::{CONSTANTS, FILENAME_TRIM_PATTERNS};
use crate::utils::hash_string_as_hex;
use shared::utils::{CONSTANTS, FILENAME_TRIM_PATTERNS};
/// File-Download information.
#[derive(Clone)]
@@ -2,7 +2,7 @@ use reqwest::{StatusCode};
use std::collections::{HashSet};
use std::str::FromStr;
use reqwest::header::HeaderMap;
use crate::utils::{filter_response_header};
use shared::utils::{filter_response_header};
pub fn get_response_headers(headers: &HeaderMap) -> Vec<(String, String)> {
let mut response_headers: Vec<(String, String)> = headers.iter()
@@ -6,7 +6,7 @@ use crate::api::model::app_state::AppState;
use crate::api::model::stream::BoxedProviderStream;
use crate::api::model::stream_error::StreamError;
use crate::api::model::streams::transport_stream_buffer::TransportStreamBuffer;
use crate::model::{ProxyUserCredentials, UserConnectionPermission};
use crate::model::{ProxyUserCredentials};
use bytes::Bytes;
use futures::Stream;
use log::{error, info};
@@ -16,6 +16,7 @@ use std::sync::{Arc, Mutex};
use std::task::{Poll, Waker};
use crate::api::model::streams::timed_client_stream::TimedClientStream;
use futures::{StreamExt};
use shared::model::UserConnectionPermission;
const INNER_STREAM: u8 = 0_u8;
const GRACE_BLOCK_STREAM: u8 = 1_u8;
@@ -1,7 +1,7 @@
use crate::api::api_utils::{HeaderFilter};
use crate::api::model::streams::custom_video_stream::CustomVideoStream;
use crate::model::{Config};
use crate::model::PlaylistItemType;
use shared::model::PlaylistItemType;
use log::{trace};
use reqwest::StatusCode;
use axum::response::IntoResponse;
@@ -6,11 +6,12 @@ use crate::api::model::streams::buffered_stream::BufferedStream;
use crate::api::model::streams::client_stream::ClientStream;
use crate::api::model::streams::provider_stream::{create_channel_unavailable_stream, get_header_filter_for_item_type};
use crate::api::model::streams::timed_client_stream::TimedClientStream;
use crate::model::PlaylistItemType;
use shared::model::PlaylistItemType;
use crate::model::{Config, DEFAULT_USER_AGENT};
use crate::tools::atomic_once_flag::AtomicOnceFlag;
use crate::utils::request::{classify_content_type, get_request_headers, sanitize_sensitive_info, MimeCategory};
use crate::utils::{debug_if_enabled, filter_request_header};
use crate::utils::{debug_if_enabled};
use shared::utils::{filter_request_header};
use futures::stream::{self};
use futures::{StreamExt, TryStreamExt};
use log::{debug, log_enabled, warn};
@@ -1,7 +1,8 @@
use crate::model::{ApiProxyServerInfo, ProxyUserCredentials, ProxyUserStatus};
use crate::model::{ApiProxyServerInfo, ProxyUserCredentials};
use chrono::{Duration, Local};
use serde::{Deserialize, Serialize};
use crate::utils::CONSTANTS;
use shared::model::ProxyUserStatus;
use shared::utils::CONSTANTS;
#[derive(Serialize, Deserialize, Clone)]
pub struct XtreamUserInfoResponse {
@@ -1,4 +1,4 @@
use crate::repository::storage::{hex_decode, hex_encode};
use crate::utils::{hex_decode, hex_encode};
use chrono::Utc;
use serde::{Deserialize, Serialize};
@@ -5,7 +5,7 @@ use crate::api::api_utils::get_username_from_auth_header;
use crate::model::WebAuthConfig;
use crate::api::model::app_state::AppState;
use crate::auth::AuthBearer;
use crate::tuliprox_error::to_io_error;
use shared::error::to_io_error;
const ROLE_ADMIN: &str = "ADMIN";
const ROLE_USER: &str = "USER";
@@ -1,6 +1,6 @@
use rand::{Rng};
use rand::distr::Alphanumeric;
use crate::tuliprox_error::str_to_io_error;
use shared::error::str_to_io_error;
fn generate_salt(length: usize) -> String {
let salt: String = rand::rng()
@@ -8,12 +8,12 @@ use pest::iterators::Pair;
use pest::Parser;
use std::cmp::Ordering;
use std::collections::HashMap;
use crate::model::{FieldGetAccessor, FieldSetAccessor, ItemField};
use crate::model::{PlaylistItem, PlaylistItemType};
use shared::model::{FieldGetAccessor, FieldSetAccessor, ItemField, PlaylistItemType};
use crate::model::{PlaylistItem};
use crate::tools::directed_graph::DirectedGraph;
use crate::tuliprox_error::{create_tuliprox_error_result, info_err};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use crate::utils::CONSTANTS;
use shared::error::{create_tuliprox_error_result, info_err};
use shared::error::{TuliproxError, TuliproxErrorKind};
use shared::utils::CONSTANTS;
pub fn get_field_value(pli: &PlaylistItem, field: ItemField) -> String {
let header = &pli.header;
@@ -2,8 +2,8 @@
use crate::foundation::filter::{PatternTemplate, TemplateValue, ValueAccessor};
use crate::foundation::mapper::EvalResult::{AnyValue, Failure, Named, Number, Undefined, Value};
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use crate::utils::Capitalize;
use shared::error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use shared::utils::Capitalize;
use log::{debug, trace};
use pest::iterators::{Pair, Pairs};
use pest::Parser;
+2 -2
View File
@@ -10,7 +10,7 @@ mod modules;
include_modules!();
use crate::auth::generate_password;
use crate::model::{Config, HealthcheckConfig, Healthcheck, ProcessTargets};
use crate::model::{Config, Healthcheck, HealthcheckConfig, ProcessTargets};
use crate::processing::processor::playlist;
use crate::utils::{config_file_reader, resolve_env_var};
use crate::utils::request::{create_client, set_sanitize_sensitive_info};
@@ -20,7 +20,7 @@ use log::{error, info};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::utils::logging::init_logger;
use crate::utils::init_logger;
#[derive(Parser)]
#[command(name = "tuliprox")]
+1 -12
View File
@@ -2,18 +2,7 @@ use std::sync::Arc;
use crate::model::{MessagingConfig};
use log::{debug, error};
use reqwest::{header};
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum MsgKind {
#[serde(rename = "info")]
Info,
#[serde(rename = "stats")]
Stats,
#[serde(rename = "error")]
Error,
#[serde(rename = "watch")]
Watch,
}
use shared::model::MsgKind;
fn is_enabled(kind: MsgKind, cfg: &MessagingConfig) -> bool {
cfg.notify_on.contains(&kind)
@@ -1,190 +1,17 @@
use crate::api::model::app_state::AppState;
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use crate::model::{Config, ClusterFlags};
use shared::error::{info_err, TuliproxError, TuliproxErrorKind};
use crate::model::{Config};
use crate::repository::user_repository::{backup_api_user_db_file, get_api_user_db_path, load_api_user, merge_api_user};
use crate::utils::{default_as_true, save_api_proxy};
use crate::utils::{save_api_proxy};
use shared::utils::{default_as_true};
use chrono::Local;
use enum_iterator::Sequence;
use log::debug;
use std::cmp::PartialEq;
use std::collections::HashSet;
use std::fmt::Display;
use std::fs;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::model::PlaylistItemType;
use shared::model::{ProxyType, ProxyUserStatus, UserConnectionPermission};
use crate::utils;
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum UserConnectionPermission {
Exhausted,
Allowed,
GracePeriod,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProxyType {
Reverse(Option<ClusterFlags>),
Redirect,
}
impl Default for ProxyType {
fn default() -> Self {
Self::Redirect
}
}
impl ProxyType {
const REVERSE: &'static str = "reverse";
const REDIRECT: &'static str = "redirect";
pub fn is_redirect(&self, item_type: PlaylistItemType) -> bool {
match self {
ProxyType::Reverse(Some(flags)) => {
if flags.is_empty() {
return false;
}
if flags.has_cluster(item_type) {
return false;
}
true
},
ProxyType::Reverse(None) => false,
ProxyType::Redirect => true
}
}
pub fn is_reverse(&self, item_type: PlaylistItemType) -> bool {
!self.is_redirect(item_type)
}
}
impl Display for ProxyType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Reverse(force_redirect) => {
if let Some(force) = force_redirect {
write!(f, "{}{force:?}", Self::REVERSE)
} else {
write!(f, "{}", Self::REVERSE)
}
}
Self::Redirect => write!(f, "{}", Self::REDIRECT),
}
}
}
impl FromStr for ProxyType {
type Err = TuliproxError;
fn from_str(s: &str) -> Result<Self, TuliproxError> {
if s == Self::REDIRECT {
return Ok(Self::Redirect);
}
if s == Self::REVERSE {
return Ok(Self::Reverse(None));
}
if s.starts_with(Self::REVERSE) {
let suffix = s.strip_prefix(Self::REVERSE).unwrap();
if let Ok(force_redirect) = ClusterFlags::try_from(suffix) {
if force_redirect.has_full_flags() {
return Ok(ProxyType::Reverse(None));
}
return Ok(Self::Reverse(Some(force_redirect)));
}
}
create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown ProxyType: {}", s)
}
}
impl<'de> Deserialize<'de> for ProxyType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw: String = Deserialize::deserialize(deserializer)?;
if raw == ProxyType::REDIRECT {
return Ok(ProxyType::Redirect);
} else if raw.starts_with(ProxyType::REVERSE) {
return ProxyType::from_str(raw.as_str()).map_err(serde::de::Error::custom);
}
Err(serde::de::Error::custom("Unknown proxy type"))
}
}
impl Serialize for ProxyType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
ProxyType::Redirect => serializer.serialize_str(ProxyType::REDIRECT),
ProxyType::Reverse(None) => serializer.serialize_str(ProxyType::REVERSE),
ProxyType::Reverse(Some(ref force_redirect)) => {
serializer.serialize_str(&format!("{}{}", ProxyType::REVERSE, force_redirect))
},
}
}
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq)]
pub enum ProxyUserStatus {
Active, // The account is in good standing and can stream content
Expired, // The account can no longer access content unless it is renewed.
Banned, // The account is temporarily or permanently disabled. Typically used for users who violate terms of service or abuse the system.
Trial, // The account is marked as a trial account.
Disabled, // The account is inactive or deliberately disabled by the administrator.
Pending,
}
impl Default for ProxyUserStatus {
fn default() -> Self {
Self::Active
}
}
impl ProxyUserStatus {
const ACTIVE: &'static str = "Active";
const EXPIRED: &'static str = "Expired";
const BANNED: &'static str = "Banned";
const TRIAL: &'static str = "Trial";
const DISABLED: &'static str = "Disabled";
const PENDING: &'static str = "Pending";
}
impl Display for ProxyUserStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
Self::Active => Self::ACTIVE,
Self::Expired => Self::EXPIRED,
Self::Banned => Self::BANNED,
Self::Trial => Self::TRIAL,
Self::Disabled => Self::DISABLED,
Self::Pending => Self::PENDING,
})
}
}
impl FromStr for ProxyUserStatus {
type Err = TuliproxError;
fn from_str(s: &str) -> Result<Self, TuliproxError> {
match s {
Self::ACTIVE => Ok(Self::Active),
Self::EXPIRED => Ok(Self::Expired),
Self::BANNED => Ok(Self::Banned),
Self::TRIAL => Ok(Self::Trial),
Self::DISABLED => Ok(Self::Disabled),
Self::PENDING => Ok(Self::Pending),
_ => create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown ProxyUserStatus: {}", s)
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProxyUserCredentials {
@@ -1,25 +1,18 @@
use arc_swap::{ArcSwapOption};
use enum_iterator::Sequence;
use std::collections::{HashSet};
use std::fmt::Display;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use log::{debug, error, warn};
use log::{debug, error};
use path_clean::PathClean;
use rand::Rng;
use crate::model::{ApiProxyConfig, ApiProxyServerInfo, Mappings, ProxyUserCredentials, SourcesConfig};
use crate::model::{ApiProxyConfig, ApiProxyServerInfo, CustomStreamResponse, Mappings, ProxyUserCredentials, ReverseProxyConfig, ScheduleConfig, SourcesConfig};
use crate::model::{ConfigInput, ConfigInputOptions, ConfigTarget, HdHomeRunConfig, IpCheckConfig, LogConfig, MessagingConfig, ProxyConfig, TargetOutput, VideoConfig, WebUiConfig};
use crate::tuliprox_error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use crate::utils::{default_connect_timeout_secs, default_grace_period_millis, default_grace_period_timeout_secs};
use crate::utils::{parse_to_kbps};
const STREAM_QUEUE_SIZE: usize = 1024; // mpsc channel holding messages. with 8192byte chunks and 2Mbit/s approx 8MB
use shared::error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use shared::utils::{default_connect_timeout_secs};
const CHANNEL_UNAVAILABLE: &str = "channel_unavailable.ts";
const USER_CONNECTIONS_EXHAUSTED: &str = "user_connections_exhausted.ts";
@@ -41,87 +34,8 @@ macro_rules! valid_property {
}
pub use valid_property;
use crate::api::model::streams::transport_stream_buffer::TransportStreamBuffer;
use crate::model::config_cache::CacheConfig;
use crate::utils;
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, Eq, PartialEq)]
pub enum ItemField {
#[serde(rename = "group")]
Group,
#[serde(rename = "name")]
Name,
#[serde(rename = "title")]
Title,
#[serde(rename = "url")]
Url,
#[serde(rename = "input")]
Input,
#[serde(rename = "type")]
Type,
#[serde(rename = "caption")]
Caption,
}
impl ItemField {
const GROUP: &'static str = "Group";
const NAME: &'static str = "Name";
const TITLE: &'static str = "Title";
const URL: &'static str = "Url";
const INPUT: &'static str = "Input";
const TYPE: &'static str = "Type";
const CAPTION: &'static str = "Caption";
pub fn as_str(&self) -> &'static str {
match *self {
Self::Group => Self::GROUP,
Self::Name => Self::NAME,
Self::Title => Self::TITLE,
Self::Url => Self::URL,
Self::Input => Self::INPUT,
Self::Type => Self::TYPE,
Self::Caption => Self::CAPTION,
}
}
}
impl Display for ItemField {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", match *self {
Self::Group => Self::GROUP,
Self::Name => Self::NAME,
Self::Title => Self::TITLE,
Self::Url => Self::URL,
Self::Input => Self::INPUT,
Self::Type => Self::TYPE,
Self::Caption => Self::CAPTION,
})
}
}
impl FromStr for ItemField {
type Err = TuliproxError;
fn from_str(s: &str) -> Result<Self, TuliproxError> {
if s.eq_ignore_ascii_case(Self::GROUP) {
Ok(Self::Group)
} else if s.eq_ignore_ascii_case(Self::NAME) {
Ok(Self::Name)
} else if s.eq_ignore_ascii_case(Self::TITLE) {
Ok(Self::Title)
} else if s.eq_ignore_ascii_case(Self::CAPTION) {
Ok(Self::Caption)
} else if s.eq_ignore_ascii_case(Self::URL) {
Ok(Self::Url)
} else if s.eq_ignore_ascii_case(Self::INPUT) {
Ok(Self::Input)
} else if s.eq_ignore_ascii_case(Self::TYPE) {
Ok(Self::Type)
} else {
create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown InputType: {}", s)
}
}
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum FilterMode {
@@ -148,213 +62,6 @@ impl ConfigApi {
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigDto {
#[serde(default)]
pub threads: u8,
pub api: ConfigApi,
#[serde(default)]
pub working_dir: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub backup_dir: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_config_dir: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mapping_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_stream_response_path: Option<String>,
#[serde(default)]
pub user_access_control: bool,
#[serde(default = "default_connect_timeout_secs")]
pub connect_timeout_secs: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sleep_timer_mins: Option<u32>,
#[serde(default)]
pub config_hot_reload: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video: Option<VideoConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schedules: Option<Vec<ScheduleConfig>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub messaging: Option<MessagingConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub log: Option<LogConfig>,
#[serde(default)]
pub update_on_boot: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub web_ui: Option<WebUiConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reverse_proxy: Option<ReverseProxyConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proxy: Option<ProxyConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ipcheck: Option<IpCheckConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hdhomerun: Option<HdHomeRunConfig>,
}
impl ConfigDto {
pub fn is_valid(&self) -> bool {
if self.api.host.is_empty() {
return false;
}
if let Some(video) = &self.video {
if let Some(download) = &video.download {
if let Some(episode_pattern) = &download.episode_pattern {
if !episode_pattern.is_empty() {
let re = regex::Regex::new(episode_pattern);
if re.is_err() {
return false;
}
}
}
}
}
true
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ScheduleConfig {
#[serde(default)]
pub schedule: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub targets: Option<Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct StreamBufferConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub size: usize,
}
impl StreamBufferConfig {
fn prepare(&mut self) {
if self.enabled && self.size == 0 {
self.size = STREAM_QUEUE_SIZE;
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct StreamConfig {
#[serde(default)]
pub retry: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub buffer: Option<StreamBufferConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub throttle: Option<String>,
#[serde(default = "default_grace_period_millis")]
pub grace_period_millis: u64,
#[serde(default = "default_grace_period_timeout_secs")]
pub grace_period_timeout_secs: u64,
#[serde(default)]
pub forced_retry_interval_secs: u32,
#[serde(default, skip)]
pub throttle_kbps: u64,
}
impl StreamConfig {
fn prepare(&mut self) -> Result<(), TuliproxError> {
if let Some(buffer) = self.buffer.as_mut() {
buffer.prepare();
}
if let Some(throttle) = &self.throttle {
self.throttle_kbps = parse_to_kbps(throttle).map_err(|err| TuliproxError::new(TuliproxErrorKind::Info, err))?;
}
if self.grace_period_millis > 0 {
if self.grace_period_timeout_secs == 0 {
let triple_ms = self.grace_period_millis * 3;
self.grace_period_timeout_secs = std::cmp::max(1, triple_ms.div_ceil(1000));
} else if self.grace_period_millis / 1000 > self.grace_period_timeout_secs {
return Err(TuliproxError::new(TuliproxErrorKind::Info, format!("Grace time period timeout {} sec should be more than grace time period {} ms", self.grace_period_timeout_secs, self.grace_period_millis)));
}
}
Ok(())
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct RateLimitConfig {
pub enabled: bool,
pub period_millis: u64,
pub burst_size: u32,
}
impl RateLimitConfig {
fn prepare(&self) -> Result<(), TuliproxError> {
if self.period_millis == 0 {
return Err(TuliproxError::new(TuliproxErrorKind::Info, "Rate limiter period can't be 0".to_string()));
}
if self.burst_size == 0 {
return Err(TuliproxError::new(TuliproxErrorKind::Info, "Rate limiter bust can't be 0".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ReverseProxyConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<StreamConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache: Option<CacheConfig>,
#[serde(default)]
pub resource_rewrite_disabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rate_limit: Option<RateLimitConfig>,
#[serde(default)]
pub disable_referer_header: bool,
}
impl ReverseProxyConfig {
fn prepare(&mut self, working_dir: &str) -> Result<(), TuliproxError> {
if let Some(stream) = self.stream.as_mut() {
stream.prepare()?;
}
if let Some(cache) = self.cache.as_mut() {
if cache.enabled && self.resource_rewrite_disabled {
warn!("The cache is disabled because resource rewrite is disabled");
cache.enabled = false;
}
cache.prepare(working_dir)?;
}
if let Some(rate_limit) = self.rate_limit.as_mut() {
if rate_limit.enabled {
rate_limit.prepare()?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct CustomStreamResponse {
#[serde(default, skip)]
pub channel_unavailable: Option<TransportStreamBuffer>,
#[serde(default, skip)]
pub user_connections_exhausted: Option<TransportStreamBuffer>, // user has no more connections
#[serde(default, skip)]
pub provider_connections_exhausted: Option<TransportStreamBuffer>, // provider limit reached, has no more connections
#[serde(default, skip)]
pub user_account_expired: Option<TransportStreamBuffer>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct Config {
@@ -1,8 +1,8 @@
use std::path::PathBuf;
use log::error;
use path_clean::PathClean;
use crate::tuliprox_error::{info_err, TuliproxError, TuliproxErrorKind};
use crate::utils::parse_size_base_2;
use shared::error::{info_err, TuliproxError, TuliproxErrorKind};
use shared::utils::parse_size_base_2;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
@@ -1,5 +1,5 @@
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use crate::utils::CONSTANTS;
use shared::error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use shared::utils::CONSTANTS;
use log::warn;
use regex::Regex;
@@ -1,6 +1,6 @@
use std::collections::HashSet;
use log::warn;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
use shared::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
fn default_friendly_name() -> String { String::from("TuliproxTV") }
fn default_manufacturer() -> String { String::from("Silicondust") }
fn default_model_name() -> String { String::from("HDTC-2US") }
@@ -1,7 +1,7 @@
use crate::tuliprox_error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind};
use crate::model::{EpgConfig};
use crate::utils::default_as_true;
use crate::utils::get_trimmed_string;
use shared::utils::default_as_true;
use shared::utils::get_trimmed_string;
use crate::utils::request::{get_base_url_from_str, get_credentials_from_url, get_credentials_from_url_str, sanitize_sensitive_info};
use enum_iterator::Sequence;
use log::{debug};
@@ -1,5 +1,5 @@
use regex::Regex;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
@@ -1,5 +1,5 @@
use crate::utils::default_as_true;
use shared::utils::default_as_true;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
@@ -1,4 +1,4 @@
use crate::messaging::MsgKind;
use shared::model::MsgKind;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
+47
View File
@@ -0,0 +1,47 @@
mod trakt;
mod base;
mod webui;
mod web_auth;
mod messaging;
mod hdhomerun;
mod ip_check;
mod source;
mod target;
mod sort;
mod video_download;
mod log;
mod input;
mod stream;
mod epg;
mod reverse_proxy;
mod cache;
mod rate_limit;
mod proxy;
mod schedule;
mod api_proxy;
mod rename;
mod healthcheck;
pub use base::*;
pub use api_proxy::*;
pub use webui::*;
pub use web_auth::*;
pub use messaging::*;
pub use hdhomerun::*;
pub use ip_check::*;
pub use source::*;
pub use target::*;
pub use sort::*;
pub use video_download::*;
pub use schedule::*;
pub use log::*;
pub use input::*;
pub use stream::*;
pub use epg::*;
pub use rate_limit::*;
pub use reverse_proxy::*;
pub use proxy::*;
pub use rename::*;
pub use trakt::*;
pub use healthcheck::*;
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
+21
View File
@@ -0,0 +1,21 @@
use shared::error::{TuliproxError, TuliproxErrorKind};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct RateLimitConfig {
pub enabled: bool,
pub period_millis: u64,
pub burst_size: u32,
}
impl RateLimitConfig {
pub(crate) fn prepare(&self) -> Result<(), TuliproxError> {
if self.period_millis == 0 {
return Err(TuliproxError::new(TuliproxErrorKind::Info, "Rate limiter period can't be 0".to_string()));
}
if self.burst_size == 0 {
return Err(TuliproxError::new(TuliproxErrorKind::Info, "Rate limiter burst can't be 0".to_string()));
}
Ok(())
}
}
@@ -1,6 +1,6 @@
use shared::model::ItemField;
use crate::foundation::filter::{apply_templates_to_pattern_single, PatternTemplate};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
use crate::model::ItemField;
use shared::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
+42
View File
@@ -0,0 +1,42 @@
use log::warn;
use shared::error::TuliproxError;
use crate::model::config::cache::CacheConfig;
use crate::model::{RateLimitConfig, StreamConfig};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ReverseProxyConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<StreamConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache: Option<CacheConfig>,
#[serde(default)]
pub resource_rewrite_disabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rate_limit: Option<RateLimitConfig>,
#[serde(default)]
pub disable_referer_header: bool,
}
impl ReverseProxyConfig {
pub(crate) fn prepare(&mut self, working_dir: &str) -> Result<(), TuliproxError> {
if let Some(stream) = self.stream.as_mut() {
stream.prepare()?;
}
if let Some(cache) = self.cache.as_mut() {
if cache.enabled && self.resource_rewrite_disabled {
warn!("The cache is disabled because resource rewrite is disabled");
cache.enabled = false;
}
cache.prepare(working_dir)?;
}
if let Some(rate_limit) = self.rate_limit.as_mut() {
if rate_limit.enabled {
rate_limit.prepare()?;
}
}
Ok(())
}
}
+9
View File
@@ -0,0 +1,9 @@
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ScheduleConfig {
#[serde(default)]
pub schedule: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub targets: Option<Vec<String>>,
}
@@ -1,7 +1,7 @@
use regex::Regex;
use crate::foundation::filter::{apply_templates_to_pattern, apply_templates_to_pattern_single, PatternTemplate, TemplateValue};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error, handle_tuliprox_error_result_list};
use crate::model::{ItemField};
use shared::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error, handle_tuliprox_error_result_list};
use shared::model::ItemField;
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum SortOrder {
@@ -1,9 +1,8 @@
use std::collections::{HashMap, HashSet};
use crate::foundation::filter::{prepare_templates, PatternTemplate};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, handle_tuliprox_error_result_list, create_tuliprox_error_result};
use crate::model::{ConfigInput, ProcessTargets};
use crate::model::config_target::ConfigTarget;
use crate::utils::default_as_default;
use shared::error::{TuliproxError, TuliproxErrorKind, handle_tuliprox_error_result_list, create_tuliprox_error_result};
use crate::model::{ConfigInput, ConfigTarget, ProcessTargets};
use shared::utils::default_as_default;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
+80
View File
@@ -0,0 +1,80 @@
use shared::utils::default_grace_period_millis;
use shared::utils::default_grace_period_timeout_secs;
use shared::error::{TuliproxError, TuliproxErrorKind};
use shared::info_err;
use shared::utils::parse_to_kbps;
use crate::api::model::streams::transport_stream_buffer::TransportStreamBuffer;
const STREAM_QUEUE_SIZE: usize = 1024; // mpsc channel holding messages. with 8192byte chunks and 2Mbit/s approx 8MB
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct StreamBufferConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub size: usize,
}
impl StreamBufferConfig {
fn prepare(&mut self) {
if self.enabled && self.size == 0 {
self.size = STREAM_QUEUE_SIZE;
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct StreamConfig {
#[serde(default)]
pub retry: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub buffer: Option<StreamBufferConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub throttle: Option<String>,
#[serde(default = "default_grace_period_millis")]
pub grace_period_millis: u64,
#[serde(default = "default_grace_period_timeout_secs")]
pub grace_period_timeout_secs: u64,
#[serde(default)]
pub forced_retry_interval_secs: u32,
#[serde(default, skip)]
pub throttle_kbps: u64,
}
impl StreamConfig {
pub(crate) fn prepare(&mut self) -> Result<(), TuliproxError> {
if let Some(buffer) = self.buffer.as_mut() {
buffer.prepare();
}
if let Some(throttle) = &self.throttle {
self.throttle_kbps = parse_to_kbps(throttle).map_err(|err| TuliproxError::new(TuliproxErrorKind::Info, err))?;
}
if self.grace_period_millis > 0 {
if self.grace_period_timeout_secs == 0 {
let triple_ms = self.grace_period_millis.saturating_mul(3);
self.grace_period_timeout_secs = std::cmp::max(1, triple_ms.div_ceil(1000));
} else if self.grace_period_millis / 1000 > self.grace_period_timeout_secs {
return Err(info_err!(format!("Grace time period timeout {} sec should be more than grace time period {} ms", self.grace_period_timeout_secs, self.grace_period_millis)));
}
}
Ok(())
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct CustomStreamResponse {
#[serde(default, skip)]
pub channel_unavailable: Option<TransportStreamBuffer>,
#[serde(default, skip)]
pub user_connections_exhausted: Option<TransportStreamBuffer>, // user has no more connections
#[serde(default, skip)]
pub provider_connections_exhausted: Option<TransportStreamBuffer>, // provider limit reached, has no more connections
#[serde(default, skip)]
pub user_account_expired: Option<TransportStreamBuffer>,
}
@@ -1,80 +1,14 @@
use crate::foundation::filter::{get_filter, Filter, PatternTemplate, ValueProvider};
use crate::tuliprox_error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind};
use crate::model::cluster_flags::ClusterFlags;
use crate::model::config_rename::ConfigRename;
use crate::model::config_sort::ConfigSort;
use crate::model::mapping::Mapping;
use crate::model::processing_order::ProcessingOrder;
use crate::model::PlaylistItemType;
use crate::model::trakt::TraktConfig;
use crate::utils::{default_as_default, default_as_true, default_resolve_delay_secs};
use enum_iterator::Sequence;
use std::fmt::Display;
use crate::model::config::trakt::TraktConfig;
use shared::error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind};
use shared::utils::{default_as_default, default_as_true, default_resolve_delay_secs};
use arc_swap::ArcSwapOption;
use shared::model::{ClusterFlags, ProcessingOrder, StrmExportStyle, TargetType};
use shared::model::PlaylistItemType;
use std::sync::Arc;
use arc_swap::{ArcSwapOption};
use crate::model::{ConfigRename, ConfigSort};
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq, Hash, Default)]
pub enum ExportStyle {
#[serde(rename = "kodi")]
#[default]
Kodi,
#[serde(rename = "plex")]
Plex,
#[serde(rename = "emby")]
Emby,
#[serde(rename = "jellyfin")]
Jellyfin,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq, Hash)]
pub enum TargetType {
#[serde(rename = "m3u")]
M3u,
#[serde(rename = "xtream")]
Xtream,
#[serde(rename = "strm")]
Strm,
#[serde(rename = "hdhomerun")]
HdHomeRun,
}
impl TargetType {
const M3U: &'static str = "M3u";
const XTREAM: &'static str = "Xtream";
const STRM: &'static str = "Strm";
const HDHOMERUN: &'static str = "HdHomeRun";
}
impl Display for TargetType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", match *self {
Self::M3u => Self::M3U,
Self::Xtream => Self::XTREAM,
Self::Strm => Self::STRM,
Self::HdHomeRun => Self::HDHOMERUN,
})
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq, Hash)]
enum HdHomeRunUseTargetType {
#[serde(rename = "m3u")]
M3u,
#[serde(rename = "xtream")]
Xtream,
}
impl TryFrom<TargetType> for HdHomeRunUseTargetType {
type Error = &'static str;
fn try_from(value: TargetType) -> Result<Self, Self::Error> {
match value {
TargetType::Xtream => Ok(Self::Xtream),
TargetType::M3u => Ok(Self::M3u),
_ => Err("Not allowed!"),
}
}
}
#[derive(Clone, Debug)]
pub struct ProcessTargets {
@@ -154,7 +88,7 @@ pub struct StrmTargetOutput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(default)]
pub style: ExportStyle,
pub style: StrmExportStyle,
#[serde(default)]
pub flat: bool,
#[serde(default)]
@@ -184,7 +118,7 @@ pub enum TargetOutput {
impl TargetOutput {
pub fn prepare(&mut self) {
match self {
match self {
TargetOutput::Xtream(output) => output.prepare(),
TargetOutput::M3u(_)
| TargetOutput::Strm(_)
@@ -257,7 +191,7 @@ impl ConfigTarget {
m3u_output.filename = m3u_output.filename.as_ref().map(|s| s.trim().to_string());
}
TargetOutput::Strm(strm_output) => {
strm_cnt +=1;
strm_cnt += 1;
strm_output.directory = strm_output.directory.trim().to_string();
if strm_output.directory.trim().is_empty() {
return create_tuliprox_error_result!(TuliproxErrorKind::Info, "directory is required for strm type: {}", self.name);
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::model::{PlaylistItem};
use crate::utils::trakt::normalize_title_for_matching;
use crate::utils::normalize_title_for_matching;
const TRAKT_API_KEY: &str = "0183a05ad97098d87287fe46da4ae286f434f32e8e951caad4cc147c947d79a3";
const TRAKT_API_VERSION: &str = "2";
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use regex::Regex;
use std::borrow::BorrowMut;
use std::collections::HashMap;
@@ -1,9 +1,9 @@
use crate::utils::default_as_true;
use shared::utils::default_as_true;
use std::fs::File;
use std::io::BufRead;
use std::path::PathBuf;
use crate::auth::UserCredential;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
use shared::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
use crate::utils;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -1,5 +1,5 @@
use crate::utils::default_as_true;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::utils::default_as_true;
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::WebAuthConfig;
const RESERVED_PATHS: &[&str] = &[
@@ -7,8 +7,8 @@ use log::{trace};
use crate::foundation::filter::{apply_templates_to_pattern_single, get_filter, prepare_templates, Filter, PatternTemplate};
use crate::foundation::mapper::MapperScript;
use crate::model::valid_property;
use crate::tuliprox_error::{create_tuliprox_error_result, info_err};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, info_err};
use shared::error::{TuliproxError, TuliproxErrorKind};
pub const COUNTER_FIELDS: &[&str] = &["name", "title", "caption", "chno"];
+19
View File
@@ -0,0 +1,19 @@
mod playlist;
mod mapping;
mod stats;
mod xmltv;
mod xtream;
mod healthcheck;
mod playlist_categories;
mod xtream_const;
mod config;
pub use self::playlist::*;
pub use self::mapping::*;
pub use self::stats::*;
pub use self::xmltv::*;
pub use self::xtream::*;
pub use self::healthcheck::*;
pub use self::playlist_categories::*;
pub use self::xtream_const::*;
pub use self::config::*;
@@ -1,28 +1,16 @@
use crate::model::xtream::{xtream_playlistitem_to_document, XtreamMappingOptions};
use crate::model::xtream_const;
use crate::model::ProxyUserCredentials;
use crate::model::TVGuide;
use crate::model::{ConfigInput, ConfigTargetOptions};
use crate::model::{TVGuide, ProxyUserCredentials, ConfigInput, ConfigTargetOptions};
use crate::utils::request::extract_extension_from_url;
use crate::utils::{generate_playlist_uuid, get_provider_id};
use crate::utils::{get_string_from_serde_value, get_u64_from_serde_value};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::borrow::Cow;
use std::cmp::PartialEq;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use shared::model::{PlaylistEntry, PlaylistItemType, UUIDType, XtreamCluster};
// https://de.wikipedia.org/wiki/M3U
// https://siptv.eu/howto/playlist.html
pub trait PlaylistEntry: Send + Sync {
fn get_virtual_id(&self) -> u32;
fn get_provider_id(&self) -> Option<u32>;
fn get_category_id(&self) -> Option<u32>;
fn get_provider_url(&self) -> String;
fn get_uuid(&self) -> UUIDType;
fn get_item_type(&self) -> PlaylistItemType;
}
#[derive(Debug, Clone)]
pub struct FetchedPlaylist<'a> { // Contains playlist for one input
@@ -42,120 +30,6 @@ impl FetchedPlaylist<'_> {
}
}
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default)]
#[repr(u8)]
pub enum XtreamCluster {
#[default]
Live = 1,
Video = 2,
Series = 3,
}
impl XtreamCluster {
pub const fn as_str(&self) -> &str {
match self {
Self::Live => "Live",
Self::Video => "Video",
Self::Series => "Series",
}
}
pub const fn as_stream_type(&self) -> &str {
match self {
Self::Live => "live",
Self::Video => "movie",
Self::Series => "series",
}
}
}
impl Display for XtreamCluster {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl TryFrom<PlaylistItemType> for XtreamCluster {
type Error = String;
fn try_from(item_type: PlaylistItemType) -> Result<Self, Self::Error> {
match item_type {
PlaylistItemType::Live | PlaylistItemType::LiveHls | PlaylistItemType::LiveDash | PlaylistItemType::LiveUnknown => Ok(Self::Live),
PlaylistItemType::Catchup | PlaylistItemType::Video => Ok(Self::Video),
PlaylistItemType::Series | PlaylistItemType::SeriesInfo => Ok(Self::Series),
}
}
}
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Default)]
#[repr(u8)]
pub enum PlaylistItemType {
#[default]
Live = 1,
Video = 2,
Series = 3, // xtream series description
SeriesInfo = 4, // xtream series info fetched for series description
Catchup = 5,
LiveUnknown = 6, // No Provider id
LiveHls = 7, // m3u8 entry
LiveDash = 8, // mpd
}
impl From<XtreamCluster> for PlaylistItemType {
fn from(xtream_cluster: XtreamCluster) -> Self {
match xtream_cluster {
XtreamCluster::Live => Self::Live,
XtreamCluster::Video => Self::Video,
XtreamCluster::Series => Self::SeriesInfo,
}
}
}
impl FromStr for PlaylistItemType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Live" => Ok(PlaylistItemType::Live),
"Video" => Ok(PlaylistItemType::Video),
"Series" => Ok(PlaylistItemType::Series),
"SeriesInfo" => Ok(PlaylistItemType::SeriesInfo),
"Catchup" => Ok(PlaylistItemType::Catchup),
"LiveUnknown" => Ok(PlaylistItemType::LiveUnknown),
"LiveHls" => Ok(PlaylistItemType::LiveHls),
"LiveDash" => Ok(PlaylistItemType::LiveDash),
_ => Err(format!("Invalid PlaylistItemType: {s}")),
}
}
}
impl PlaylistItemType {
const LIVE: &'static str = "live";
const VIDEO: &'static str = "video";
const SERIES: &'static str = "series";
const SERIES_INFO: &'static str = "series-info";
const CATCHUP: &'static str = "catchup";
}
impl Display for PlaylistItemType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
Self::Live | Self::LiveHls | Self::LiveDash | Self::LiveUnknown => Self::LIVE,
Self::Video => Self::VIDEO,
Self::Series => Self::SERIES,
Self::SeriesInfo => Self::SERIES_INFO,
Self::Catchup => Self::CATCHUP,
})
}
}
pub trait FieldGetAccessor {
fn get_field(&self, field: &str) -> Option<Cow<str>>;
}
pub trait FieldSetAccessor {
fn set_field(&mut self, field: &str, value: &str) -> bool;
}
pub type UUIDType = [u8; 32];
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PlaylistItemHeader {
@@ -252,7 +126,7 @@ macro_rules! to_m3u_resource_non_empty_fields {
macro_rules! generate_field_accessor_impl_for_playlist_item_header {
($($prop:ident),*;) => {
impl FieldGetAccessor for PlaylistItemHeader {
impl shared::model::FieldGetAccessor for PlaylistItemHeader {
fn get_field(&self, field: &str) -> Option<Cow<str>> {
let field = field.to_lowercase();
match field.as_str() {
@@ -267,7 +141,7 @@ macro_rules! generate_field_accessor_impl_for_playlist_item_header {
}
}
}
impl FieldSetAccessor for PlaylistItemHeader {
impl shared::model::FieldSetAccessor for PlaylistItemHeader {
fn set_field(&mut self, field: &str, value: &str) -> bool {
let field = field.to_lowercase();
let val = String::from(value);
@@ -379,7 +253,7 @@ impl PlaylistEntry for M3uPlaylistItem {
macro_rules! generate_field_accessor_impl_for_m3u_playlist_item {
($($prop:ident),*;) => {
impl FieldGetAccessor for M3uPlaylistItem {
impl shared::model::FieldGetAccessor for M3uPlaylistItem {
fn get_field(&self, field: &str) -> Option<Cow<str>> {
let field = field.to_lowercase();
match field.as_str() {
@@ -490,7 +364,7 @@ pub fn get_backdrop_path_value<'a>(field: &'a str, value: Option<&'a Value>) ->
macro_rules! generate_field_accessor_impl_for_xtream_playlist_item {
($($prop:ident),*;) => {
impl FieldGetAccessor for XtreamPlaylistItem {
impl shared::model::FieldGetAccessor for XtreamPlaylistItem {
fn get_field(&self, field: &str) -> Option<Cow<str>> {
let field = field.to_lowercase();
match field.as_str() {
@@ -1,14 +1,15 @@
use crate::model::{ProxyUserCredentials};
use crate::model::{Config, ConfigTarget, ClusterFlags, XtreamTargetOutput};
use crate::model::{PlaylistEntry, PlaylistItem, XtreamCluster, XtreamPlaylistItem};
use crate::model::serde_utils::{deserialize_as_option_rc_string, deserialize_as_rc_string, deserialize_as_string_array, deserialize_number_from_string};
use crate::model::{Config, ConfigTarget, XtreamTargetOutput};
use crate::model::{PlaylistItem,XtreamPlaylistItem};
use crate::utils::{deserialize_as_option_rc_string, deserialize_as_rc_string, deserialize_as_string_array, deserialize_number_from_string};
use crate::model::xtream_const;
use crate::utils::{get_non_empty_str, opt_string_or_number_u32, string_default_on_null, string_or_number_f64, string_or_number_u32};
use crate::utils::{opt_string_or_number_u32, string_default_on_null, string_or_number_f64, string_or_number_u32};
use shared::utils::{get_non_empty_str};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::iter::FromIterator;
use shared::model::{ClusterFlags, PlaylistEntry, XtreamCluster};
#[derive(Deserialize, Default)]
pub struct XtreamCategory {
@@ -8,7 +8,6 @@ macro_rules! include_modules {
extern crate pest_derive;
pub mod api;
pub mod auth;
pub mod tuliprox_error;
pub mod messaging;
pub mod model;
pub mod processing;
@@ -1,5 +1,5 @@
use crate::model::ProxyUserCredentials;
use crate::utils::{CONSTANTS, HLS_PREFIX};
use shared::utils::{CONSTANTS, HLS_PREFIX};
use crate::utils::{deobfuscate_text, obfuscate_text};
use std::str;
@@ -1,7 +1,24 @@
use std::borrow::BorrowMut;
use shared::model::{PlaylistItemType, XtreamCluster};
use crate::model::{Config, ConfigInput};
use crate::model::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, PlaylistItemType, XtreamCluster};
use crate::utils::{extract_id_from_url, get_title_group};
use crate::model::{PlaylistGroup, PlaylistItem, PlaylistItemHeader};
use crate::utils::{extract_id_from_url};
// other implementations like calculating text_distance on all titles took too much time
// we keep it now as simple as possible and less memory intensive.
fn get_title_group(text: &str) -> String {
let alphabetic_only: String = text.chars().map(|c| if c.is_alphanumeric() { c } else { ' ' }).collect();
let parts = alphabetic_only.split_whitespace();
let mut combination = String::new();
for p in parts {
combination = format!("{combination} {p}").trim().to_string();
if combination.len() > 2 {
return combination;
}
}
text.to_string()
}
#[inline]
fn token_value(stack: &mut String, it: &mut std::str::Chars) -> String {

Some files were not shown because too many files have changed in this diff Show More