Revenons sur les devices
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,3 +21,4 @@ OLD-GO-CODE/
|
|||||||
xxx
|
xxx
|
||||||
xx
|
xx
|
||||||
all.txt
|
all.txt
|
||||||
|
pmo_src.txt
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
"makefile.configureOnOpen": false,
|
"makefile.configureOnOpen": false,
|
||||||
"git.enabled": false
|
"git.enabled": false,
|
||||||
|
"claude-code.environmentVariables": [
|
||||||
|
|
||||||
|
]
|
||||||
}
|
}
|
||||||
480
Cargo.lock
generated
480
Cargo.lock
generated
@@ -233,7 +233,7 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
"windows-link",
|
"windows-link 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -414,7 +414,17 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-link",
|
"windows-link 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -577,6 +587,15 @@ version = "2.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc"
|
checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encoding_rs"
|
||||||
|
version = "0.8.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -594,6 +613,22 @@ dependencies = [
|
|||||||
"typeid",
|
"typeid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -623,6 +658,21 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@@ -908,6 +958,39 @@ dependencies = [
|
|||||||
"pin-utils",
|
"pin-utils",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.27.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -916,14 +999,24 @@ version = "0.1.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
|
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"ipnet",
|
||||||
|
"libc",
|
||||||
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"socket2",
|
||||||
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
"windows-registry",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1095,6 +1188,22 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipnet"
|
||||||
|
version = "2.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iri-string"
|
||||||
|
version = "0.7.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
@@ -1142,6 +1251,12 @@ dependencies = [
|
|||||||
"zlib-rs",
|
"zlib-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -1221,6 +1336,23 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.1"
|
version = "0.50.1"
|
||||||
@@ -1254,6 +1386,50 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.73"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"foreign-types",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"openssl-macros",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1301,6 +1477,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pmoconfig"
|
name = "pmoconfig"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1350,6 +1532,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pmoconfig",
|
"pmoconfig",
|
||||||
"pmodidl",
|
"pmodidl",
|
||||||
|
"reqwest",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -1502,6 +1685,60 @@ version = "0.8.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.12.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-core",
|
||||||
|
"h2",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-rustls",
|
||||||
|
"hyper-tls",
|
||||||
|
"hyper-util",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-embed"
|
name = "rust-embed"
|
||||||
version = "8.7.2"
|
version = "8.7.2"
|
||||||
@@ -1542,6 +1779,52 @@ version = "0.1.26"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.61.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.103.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
@@ -1563,12 +1846,44 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "2.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -1741,6 +2056,12 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.106"
|
version = "2.0.106"
|
||||||
@@ -1757,6 +2078,9 @@ name = "sync_wrapper"
|
|||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
@@ -1769,6 +2093,40 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.17"
|
version = "2.0.17"
|
||||||
@@ -1838,6 +2196,26 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-native-tls"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.26.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||||
|
dependencies = [
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
@@ -1908,6 +2286,24 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"iri-string",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -1982,6 +2378,12 @@ dependencies = [
|
|||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "try-lock"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typeid"
|
name = "typeid"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -2012,6 +2414,12 @@ version = "0.2.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.7"
|
version = "2.5.7"
|
||||||
@@ -2131,6 +2539,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@@ -2147,6 +2561,15 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "want"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||||
|
dependencies = [
|
||||||
|
"try-lock",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.1+wasi-snapshot-preview1"
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
@@ -2291,9 +2714,9 @@ checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
"windows-link",
|
"windows-link 0.2.0",
|
||||||
"windows-result",
|
"windows-result 0.4.0",
|
||||||
"windows-strings",
|
"windows-strings 0.5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2318,19 +2741,54 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-registry"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.1.3",
|
||||||
|
"windows-result 0.3.4",
|
||||||
|
"windows-strings 0.4.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.1.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2339,7 +2797,7 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2366,7 +2824,7 @@ version = "0.61.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2514,6 +2972,12 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use pmoupnp::{mediarenderer::avtransport::actions::{SETAVTRANSPORTURI}, server::{
|
use pmoupnp::{mediarenderer::avtransport::{actions::SETAVTRANSPORTURI, AVTTRANSPORT}, server::{
|
||||||
logs::{log_dump, log_sse, LogState, SseLayer}, ServerBuilder, Webapp
|
logs::{log_dump, log_sse, LogState, SseLayer}, ServerBuilder, Webapp
|
||||||
}, UpnpObject}; // ton module pmoupnp::server
|
}, UpnpObject}; // ton module pmoupnp::server
|
||||||
use tracing_subscriber::Registry;
|
use tracing_subscriber::Registry;
|
||||||
@@ -47,7 +47,8 @@ async fn main() {
|
|||||||
|
|
||||||
server.add_redirect("/", "/app").await;
|
server.add_redirect("/", "/app").await;
|
||||||
|
|
||||||
info!("{}",SETAVTRANSPORTURI.to_markdown());
|
info!("{}",AVTTRANSPORT.to_markdown());
|
||||||
|
info!("{}",AVTTRANSPORT.scpd_xml());
|
||||||
|
|
||||||
server.start().await;
|
server.start().await;
|
||||||
server.wait().await;
|
server.wait().await;
|
||||||
|
|||||||
9290
pmo_src.txt
Normal file
9290
pmo_src.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ thiserror = "2.0.16"
|
|||||||
xmltree = "0.11.0"
|
xmltree = "0.11.0"
|
||||||
get_if_addrs = "0.5.3"
|
get_if_addrs = "0.5.3"
|
||||||
axum = "0.8.4"
|
axum = "0.8.4"
|
||||||
tokio = { version = "1.35", features = ["rt-multi-thread", "macros", "sync", "time"] }
|
tokio = { version = "1.35", features = ["rt-multi-thread", "macros", "sync", "time", "signal"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
@@ -37,3 +37,4 @@ utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
|||||||
validator = { version = "0.20.0", features = ["derive"] }
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
bevy_reflect = "0.17.1"
|
bevy_reflect = "0.17.1"
|
||||||
bevy_reflect_derive = "0.17.1"
|
bevy_reflect_derive = "0.17.1"
|
||||||
|
reqwest = "0.12.23"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use xmltree::{Element, XMLNode};
|
|||||||
use crate::actions::Action;
|
use crate::actions::Action;
|
||||||
use crate::actions::Argument;
|
use crate::actions::Argument;
|
||||||
use crate::actions::ArgumentSet;
|
use crate::actions::ArgumentSet;
|
||||||
|
use crate::actions::ArgInstanceSet;
|
||||||
use crate::actions::ActionInstance;
|
use crate::actions::ActionInstance;
|
||||||
use crate::UpnpInstance;
|
use crate::UpnpInstance;
|
||||||
use crate::UpnpObject;
|
use crate::UpnpObject;
|
||||||
@@ -13,7 +14,7 @@ use crate::UpnpTypedInstance;
|
|||||||
use crate::UpnpObjectType;
|
use crate::UpnpObjectType;
|
||||||
|
|
||||||
impl UpnpObject for ActionInstance {
|
impl UpnpObject for ActionInstance {
|
||||||
fn to_xml_element(&self) -> Element {
|
fn to_xml_element(&self) -> Element {
|
||||||
let mut elem = Element::new("action");
|
let mut elem = Element::new("action");
|
||||||
|
|
||||||
// <name>
|
// <name>
|
||||||
@@ -21,8 +22,8 @@ fn to_xml_element(&self) -> Element {
|
|||||||
name_elem.children.push(XMLNode::Text(self.get_name().clone()));
|
name_elem.children.push(XMLNode::Text(self.get_name().clone()));
|
||||||
elem.children.push(XMLNode::Element(name_elem));
|
elem.children.push(XMLNode::Element(name_elem));
|
||||||
|
|
||||||
// déplacer tous les enfants de args_elem dans un nouvel Element
|
// Utiliser le set d'instances d'arguments
|
||||||
let args_container = self.arguments_set().to_xml_element();
|
let args_container = self.arguments.to_xml_element();
|
||||||
elem.children.push(XMLNode::Element(args_container));
|
elem.children.push(XMLNode::Element(args_container));
|
||||||
|
|
||||||
elem
|
elem
|
||||||
@@ -40,12 +41,23 @@ impl UpnpInstance for ActionInstance {
|
|||||||
type Model = Action;
|
type Model = Action;
|
||||||
|
|
||||||
fn new(action: &Action) -> Self {
|
fn new(action: &Action) -> Self {
|
||||||
|
// Créer les instances d'arguments
|
||||||
|
let mut arguments = ArgInstanceSet::new();
|
||||||
|
|
||||||
|
for arg_model in action.arguments().all() {
|
||||||
|
let arg_instance = Arc::new(crate::actions::ArgumentInstance::new(&*arg_model));
|
||||||
|
if let Err(e) = arguments.insert(arg_instance) {
|
||||||
|
tracing::error!("Failed to insert argument instance: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
object: UpnpObjectType {
|
object: UpnpObjectType {
|
||||||
name: action.get_name().clone(),
|
name: action.get_name().clone(),
|
||||||
object_type: "ActionInstance".to_string(),
|
object_type: "ActionInstance".to_string(),
|
||||||
},
|
},
|
||||||
model: action.clone(),
|
model: action.clone(),
|
||||||
|
arguments, // ⬅️ Set d'instances, pas le modèle !
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,13 +72,65 @@ impl UpnpTypedInstance for ActionInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ActionInstance {
|
impl ActionInstance {
|
||||||
|
/// Retourne une instance d'argument par son nom.
|
||||||
|
///
|
||||||
pub fn arguments(&self, name: &str) -> Option<Arc<Argument>> {
|
/// # Arguments
|
||||||
self.model.arguments.get_by_name(name)
|
///
|
||||||
|
/// * `name` - Nom de l'argument à rechercher
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `Some(Arc<ArgumentInstance>)` si trouvé, `None` sinon.
|
||||||
|
pub fn argument(&self, name: &str) -> Option<Arc<crate::actions::ArgumentInstance>> {
|
||||||
|
self.arguments.get_by_name(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arguments_set(&self) -> &ArgumentSet {
|
/// Retourne le set d'instances d'arguments.
|
||||||
&self.model.arguments
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Référence vers le `ArgInstanceSet` contenant toutes les instances.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// for arg_instance in action_instance.arguments_set().all() {
|
||||||
|
/// println!("Argument: {}", arg_instance.get_name());
|
||||||
|
/// if let Some(var) = arg_instance.get_variable_instance() {
|
||||||
|
/// println!(" Variable: {} = {}", var.get_name(), var.value());
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn arguments_set(&self) -> &ArgInstanceSet {
|
||||||
|
&self.arguments // ⬅️ Retourne les INSTANCES, pas les modèles !
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::actions::Action;
|
||||||
|
use crate::UpnpInstance;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_action_instance_creation() {
|
||||||
|
let action = Action::new("Play".to_string());
|
||||||
|
let instance = ActionInstance::new(&action);
|
||||||
|
|
||||||
|
assert_eq!(instance.get_name(), "Play");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_action_instance_has_argument_instances() {
|
||||||
|
let action = Action::new("Play".to_string());
|
||||||
|
let instance = ActionInstance::new(&action);
|
||||||
|
|
||||||
|
// Vérifier que arguments_set() retourne bien des instances
|
||||||
|
assert!(instance.arguments_set().all().iter().all(|arg| {
|
||||||
|
// Chaque argument doit être une ArgumentInstance
|
||||||
|
arg.get_model(); // Cette méthode existe seulement sur les instances
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use xmltree::{Element, XMLNode};
|
use xmltree::{Element, XMLNode};
|
||||||
|
|
||||||
use crate::actions::ActionSet;
|
use crate::actions::{ActionInstanceSet, ActionSet};
|
||||||
use crate::UpnpObject;
|
use crate::{UpnpModel, UpnpObject};
|
||||||
|
|
||||||
impl UpnpObject for ActionSet {
|
impl UpnpObject for ActionSet {
|
||||||
fn to_xml_element(&self) -> Element {
|
fn to_xml_element(&self) -> Element {
|
||||||
@@ -17,3 +17,7 @@ impl UpnpObject for ActionSet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl UpnpModel for ActionSet {
|
||||||
|
type Instance = ActionInstanceSet;
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
use std::{collections::HashMap, sync::{Arc, RwLock}};
|
||||||
|
|
||||||
use xmltree::Element;
|
use xmltree::Element;
|
||||||
|
|
||||||
use crate::{actions::{Argument, ArgumentInstance}, UpnpInstance, UpnpObject, UpnpObjectType, UpnpTyped, UpnpTypedInstance};
|
use crate::{actions::{ActionInstanceSet, ActionSet, Argument, ArgumentInstance}, state_variables::StateVarInstance, UpnpInstance, UpnpObject, UpnpObjectType, UpnpTyped, UpnpTypedInstance};
|
||||||
|
|
||||||
|
|
||||||
impl UpnpObject for ArgumentInstance {
|
impl UpnpObject for ArgumentInstance {
|
||||||
@@ -15,28 +17,240 @@ impl UpnpTyped for ArgumentInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implémentation de [`UpnpTypedInstance`] pour [`ArgumentInstance`].
|
||||||
|
///
|
||||||
|
/// Cette implémentation permet d'accéder au modèle [`Argument`] depuis l'instance
|
||||||
|
/// via la méthode [`get_model()`](UpnpTypedInstance::get_model).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::UpnpTypedInstance;
|
||||||
|
///
|
||||||
|
/// let arg_instance = ArgumentInstance::new(&arg_model);
|
||||||
|
///
|
||||||
|
/// // Accéder au modèle
|
||||||
|
/// let model = arg_instance.get_model();
|
||||||
|
/// println!("Direction: in={}, out={}", model.is_in(), model.is_out());
|
||||||
|
/// println!("Related variable: {}", model.state_variable().get_name());
|
||||||
|
/// ```
|
||||||
impl UpnpTypedInstance for ArgumentInstance {
|
impl UpnpTypedInstance for ArgumentInstance {
|
||||||
|
/// Retourne une référence vers le modèle [`Argument`].
|
||||||
|
///
|
||||||
|
/// Permet d'accéder aux métadonnées statiques définies dans le modèle :
|
||||||
|
/// - Direction de l'argument (in/out)
|
||||||
|
/// - Variable d'état associée
|
||||||
|
/// - Nom et type
|
||||||
fn get_model(&self) -> &Self::Model {
|
fn get_model(&self) -> &Self::Model {
|
||||||
&self.model
|
&self.model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Implémentation de [`UpnpInstance`] pour [`ArgumentInstance`].
|
||||||
|
///
|
||||||
|
/// Cette implémentation fournit le constructeur standard qui crée une instance
|
||||||
|
/// **non liée** d'un argument. La liaison à une [`StateVarInstance`] doit être
|
||||||
|
/// effectuée séparément via [`bind_variable`](ArgumentInstance::bind_variable).
|
||||||
|
///
|
||||||
|
/// # Processus de construction en deux phases
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// Phase 1 (new) Phase 2 (bind_variable)
|
||||||
|
/// ┌─────────────────┐ ┌──────────────────────┐
|
||||||
|
/// │ ArgumentInstance│ │ StateVarInstance │
|
||||||
|
/// │ │ │ │
|
||||||
|
/// │ model: Arc<...> │────>│ Liaison établie │
|
||||||
|
/// │ variable: None │ │ variable: Some(...) │
|
||||||
|
/// └─────────────────┘ └──────────────────────┘
|
||||||
|
/// ↓ ↓
|
||||||
|
/// Création bind_variable(&var)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Pourquoi deux phases ?
|
||||||
|
///
|
||||||
|
/// 1. **Ordre de création** : Les modèles (`Argument`) existent avant les instances
|
||||||
|
/// 2. **Validation différée** : Les dépendances sont vérifiées après instanciation
|
||||||
|
/// 3. **Découplage** : Permet de créer des arguments même si les variables n'existent pas encore
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::actions::{Argument, ArgumentInstance};
|
||||||
|
/// use pmoupnp::UpnpInstance;
|
||||||
|
///
|
||||||
|
/// let arg_model = Argument::new_in("InstanceID".to_string(), instance_id_var);
|
||||||
|
///
|
||||||
|
/// // Création de l'instance - Phase 1
|
||||||
|
/// let arg_instance = ArgumentInstance::new(&arg_model);
|
||||||
|
///
|
||||||
|
/// // À ce stade, l'instance existe mais n'est pas encore liée
|
||||||
|
/// assert_eq!(arg_instance.get_name(), "InstanceID");
|
||||||
|
/// assert!(arg_instance.get_variable_instance().is_none());
|
||||||
|
///
|
||||||
|
/// // La liaison se fera plus tard via bind_variable()
|
||||||
|
/// ```
|
||||||
impl UpnpInstance for ArgumentInstance {
|
impl UpnpInstance for ArgumentInstance {
|
||||||
type Model = Argument;
|
type Model = Argument;
|
||||||
|
|
||||||
|
/// Crée une nouvelle instance d'argument depuis son modèle.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `from` - Référence vers le modèle [`Argument`] définissant cet argument
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Une nouvelle `ArgumentInstance` avec :
|
||||||
|
/// - Nom copié depuis le modèle
|
||||||
|
/// - Référence vers le modèle (clone)
|
||||||
|
/// - `variable_instance` initialisé à `None` (liaison non établie)
|
||||||
|
///
|
||||||
|
/// # État initial
|
||||||
|
///
|
||||||
|
/// L'instance créée n'est **pas encore liée** à une variable d'état.
|
||||||
|
/// Pour établir la liaison, appelez [`bind_variable`](ArgumentInstance::bind_variable).
|
||||||
|
///
|
||||||
|
/// # Thread-safety
|
||||||
|
///
|
||||||
|
/// L'instance retournée est thread-safe et peut être partagée via `Arc`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::UpnpInstance;
|
||||||
|
///
|
||||||
|
/// // Création depuis un modèle
|
||||||
|
/// let instance = ArgumentInstance::new(&arg_model);
|
||||||
|
///
|
||||||
|
/// // L'instance hérite des propriétés du modèle
|
||||||
|
/// assert_eq!(instance.get_name(), arg_model.get_name());
|
||||||
|
/// assert_eq!(instance.is_in(), arg_model.is_in());
|
||||||
|
///
|
||||||
|
/// // Mais n'a pas encore de valeur runtime
|
||||||
|
/// assert!(instance.get_variable_instance().is_none());
|
||||||
|
/// ```
|
||||||
fn new(from: &Argument) -> Self {
|
fn new(from: &Argument) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
// Copie des métadonnées depuis le modèle
|
||||||
object: UpnpObjectType {
|
object: UpnpObjectType {
|
||||||
name: from.get_name().clone(),
|
name: from.get_name().clone(),
|
||||||
object_type: "UpnpInstance".to_string(),
|
object_type: "ArgumentInstance".to_string(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Clone du modèle pour référence future
|
||||||
model: from.clone(),
|
model: from.clone(),
|
||||||
variable_instance: None,
|
|
||||||
}
|
// Initialisation à None - sera lié plus tard via bind_variable()
|
||||||
|
// Arc<RwLock<...>> permet la modification thread-safe post-construction
|
||||||
|
variable_instance: Arc::new(RwLock::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Méthodes de liaison et d'accès
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
impl ArgumentInstance {
|
||||||
|
/// Lie cet argument à une instance de variable d'état.
|
||||||
|
///
|
||||||
|
/// Cette méthode établit la connexion entre l'argument et sa variable d'état,
|
||||||
|
/// permettant l'accès aux valeurs runtime lors de l'exécution d'actions.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `var_instance` - Instance de la variable d'état à lier
|
||||||
|
///
|
||||||
|
/// # Thread-safety
|
||||||
|
///
|
||||||
|
/// Cette méthode acquiert un **write lock** sur `variable_instance` et peut
|
||||||
|
/// bloquer si d'autres threads lisent actuellement la valeur.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panique si le lock est empoisonné (poisoned), ce qui ne devrait jamais
|
||||||
|
/// arriver dans un usage normal.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use std::sync::Arc;
|
||||||
|
///
|
||||||
|
/// let arg_instance = ArgumentInstance::new(&arg_model);
|
||||||
|
/// let var_instance = Arc::new(StateVarInstance::new(&state_var));
|
||||||
|
///
|
||||||
|
/// // Établir la liaison
|
||||||
|
/// arg_instance.bind_variable(var_instance.clone());
|
||||||
|
///
|
||||||
|
/// // Vérifier que la liaison est établie
|
||||||
|
/// assert!(arg_instance.get_variable_instance().is_some());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// Cette méthode peut être appelée plusieurs fois pour changer la variable liée,
|
||||||
|
/// bien que ce ne soit généralement pas recommandé dans un usage normal.
|
||||||
|
pub fn bind_variable(&self, var_instance: Arc<StateVarInstance>) {
|
||||||
|
let mut var = self.variable_instance.write().unwrap();
|
||||||
|
*var = Some(var_instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retourne l'instance de variable d'état liée, si elle existe.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `Some(Arc<StateVarInstance>)` si une variable est liée
|
||||||
|
/// - `None` si aucune liaison n'a été établie via [`bind_variable`](Self::bind_variable)
|
||||||
|
///
|
||||||
|
/// # Thread-safety
|
||||||
|
///
|
||||||
|
/// Cette méthode acquiert un **read lock** sur `variable_instance`.
|
||||||
|
/// Plusieurs threads peuvent lire simultanément sans blocage.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panique si le lock est empoisonné (poisoned).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// // Vérifier si la liaison existe
|
||||||
|
/// if let Some(var) = arg_instance.get_variable_instance() {
|
||||||
|
/// println!("Variable liée : {}", var.get_name());
|
||||||
|
/// println!("Valeur actuelle : {}", var.value());
|
||||||
|
/// } else {
|
||||||
|
/// println!("Aucune variable liée");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Usage dans l'exécution d'actions
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// async fn execute_action(action: &ActionInstance) -> Result<(), ActionError> {
|
||||||
|
/// for arg in action.arguments_set().all() {
|
||||||
|
/// if let Some(var) = arg.get_variable_instance() {
|
||||||
|
/// // Utiliser var.value() pour lire/écrire
|
||||||
|
/// println!("Paramètre {} = {}", arg.get_name(), var.value());
|
||||||
|
/// } else {
|
||||||
|
/// return Err(ActionError::UnboundArgument(arg.get_name().to_string()));
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn get_variable_instance(&self) -> Option<Arc<StateVarInstance>> {
|
||||||
|
self.variable_instance.read().unwrap().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl UpnpInstance for ActionInstanceSet {
|
||||||
|
type Model = ActionSet;
|
||||||
|
|
||||||
|
fn new(_: &ActionSet) -> Self {
|
||||||
|
Self {
|
||||||
|
objects: RwLock::new(HashMap::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
mod errors;
|
mod errors;
|
||||||
|
|
||||||
mod action_methods;
|
|
||||||
mod action_instance;
|
mod action_instance;
|
||||||
mod action_set_methods;
|
|
||||||
mod action_instance_set;
|
mod action_instance_set;
|
||||||
mod argument_methods;
|
mod action_methods;
|
||||||
mod arg_set_methods;
|
mod action_set_methods;
|
||||||
mod arg_inst_set_methods;
|
mod arg_inst_set_methods;
|
||||||
mod arg_instance_methods;
|
mod arg_instance_methods;
|
||||||
|
mod arg_set_methods;
|
||||||
|
mod argument_methods;
|
||||||
|
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
use std::sync::Arc;
|
UpnpObjectSet, UpnpObjectType,
|
||||||
use crate::{state_variables::{StateVarInstance, StateVariable}, UpnpObjectSet, UpnpObjectType};
|
state_variables::{StateVarInstance, StateVariable},
|
||||||
|
};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
pub use errors::ActionError;
|
pub use errors::ActionError;
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ pub type ActionSet = UpnpObjectSet<Action>;
|
|||||||
pub struct ActionInstance {
|
pub struct ActionInstance {
|
||||||
object: UpnpObjectType,
|
object: UpnpObjectType,
|
||||||
model: Action,
|
model: Action,
|
||||||
|
arguments: ArgInstanceSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ActionInstanceSet = UpnpObjectSet<ActionInstance>;
|
pub type ActionInstanceSet = UpnpObjectSet<ActionInstance>;
|
||||||
@@ -43,12 +46,71 @@ pub struct Argument {
|
|||||||
|
|
||||||
pub type ArgumentSet = UpnpObjectSet<Argument>;
|
pub type ArgumentSet = UpnpObjectSet<Argument>;
|
||||||
|
|
||||||
|
/// Instance d'un argument d'action UPnP.
|
||||||
|
///
|
||||||
|
/// Un `ArgumentInstance` représente un argument concret utilisé lors de l'exécution
|
||||||
|
/// d'une action. Contrairement au modèle [`Argument`] qui définit la structure,
|
||||||
|
/// l'instance maintient une liaison dynamique vers une [`StateVarInstance`] qui
|
||||||
|
/// contient la valeur runtime.
|
||||||
|
///
|
||||||
|
/// # Cycle de vie
|
||||||
|
///
|
||||||
|
/// 1. **Création** : Instanciation via [`UpnpInstance::new`] avec `variable_instance = None`
|
||||||
|
/// 2. **Liaison** : Association à une [`StateVarInstance`] via [`bind_variable`](Self::bind_variable)
|
||||||
|
/// 3. **Utilisation** : Accès à la valeur runtime via [`get_variable_instance`](Self::get_variable_instance)
|
||||||
|
///
|
||||||
|
/// # Pourquoi `variable_instance` est optionnel ?
|
||||||
|
///
|
||||||
|
/// La liaison ne peut pas être faite dans le constructeur car :
|
||||||
|
/// - Les `StateVarInstance` sont créées **après** les modèles
|
||||||
|
/// - Les `ActionInstance` sont créées **avant** que toutes les variables soient disponibles
|
||||||
|
/// - La validation des dépendances se fait en deux phases
|
||||||
|
///
|
||||||
|
/// # Thread-safety
|
||||||
|
///
|
||||||
|
/// Le champ `variable_instance` est protégé par un `RwLock` pour permettre :
|
||||||
|
/// - La liaison après création (write lock)
|
||||||
|
/// - L'accès concurrent en lecture (read lock)
|
||||||
|
/// - L'utilisation dans un contexte multi-thread
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use pmoupnp::actions::{Argument, ArgumentInstance};
|
||||||
|
/// use pmoupnp::state_variables::StateVarInstance;
|
||||||
|
/// use std::sync::Arc;
|
||||||
|
///
|
||||||
|
/// // Phase 1 : Créer l'instance (sans liaison)
|
||||||
|
/// let arg_model = Argument::new_in("Volume".to_string(), volume_var);
|
||||||
|
/// let arg_instance = ArgumentInstance::new(&arg_model);
|
||||||
|
/// assert!(arg_instance.get_variable_instance().is_none());
|
||||||
|
///
|
||||||
|
/// // Phase 2 : Lier à une variable d'état
|
||||||
|
/// let var_instance = Arc::new(StateVarInstance::new(&volume_var));
|
||||||
|
/// arg_instance.bind_variable(var_instance.clone());
|
||||||
|
/// assert!(arg_instance.get_variable_instance().is_some());
|
||||||
|
///
|
||||||
|
/// // Phase 3 : Utiliser la valeur runtime
|
||||||
|
/// if let Some(var) = arg_instance.get_variable_instance() {
|
||||||
|
/// println!("Current value: {}", var.value());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ArgumentInstance {
|
pub struct ArgumentInstance {
|
||||||
|
/// Métadonnées de l'objet UPnP
|
||||||
object: UpnpObjectType,
|
object: UpnpObjectType,
|
||||||
|
|
||||||
|
/// Référence vers le modèle définissant la structure
|
||||||
model: Argument,
|
model: Argument,
|
||||||
variable_instance: Option<Arc<StateVarInstance>>,
|
|
||||||
|
/// Liaison optionnelle vers l'instance de variable d'état.
|
||||||
|
///
|
||||||
|
/// - `None` : Pas encore liée (état initial après construction)
|
||||||
|
/// - `Some(Arc<StateVarInstance>)` : Liée et prête à l'emploi
|
||||||
|
///
|
||||||
|
/// Protégée par `RwLock` pour permettre la liaison post-construction
|
||||||
|
/// et l'accès concurrent en lecture.
|
||||||
|
variable_instance: Arc<RwLock<Option<Arc<StateVarInstance>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ArgInstanceSet = UpnpObjectSet<ArgumentInstance>;
|
pub type ArgInstanceSet = UpnpObjectSet<ArgumentInstance>;
|
||||||
|
|||||||
0
pmoupnp/src/devices/mod.rs
Normal file
0
pmoupnp/src/devices/mod.rs
Normal file
@@ -4,7 +4,7 @@ mod object_set;
|
|||||||
pub mod actions;
|
pub mod actions;
|
||||||
pub mod mediarenderer;
|
pub mod mediarenderer;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
// pub mod services;
|
pub mod services;
|
||||||
pub mod state_variables;
|
pub mod state_variables;
|
||||||
pub mod value_ranges;
|
pub mod value_ranges;
|
||||||
pub mod variable_types;
|
pub mod variable_types;
|
||||||
@@ -27,6 +27,7 @@ pub struct UpnpObjectSet<T: UpnpTypedObject> {
|
|||||||
objects: RwLock<HashMap<String, Arc<T>>>,
|
objects: RwLock<HashMap<String, Arc<T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum UpnpObjectSetError {
|
pub enum UpnpObjectSetError {
|
||||||
AlreadyExists(String),
|
AlreadyExists(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::A_ARG_TYPE_INSTANCE_ID;
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static GETDEVICECAPABILITIES = "GetDeviceCapabilities" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::{A_ARG_TYPE_INSTANCE_ID, NUMBEROFTRACKS, CURRENTTRACK, AVTRANSPORTURI, AVTRANSPORTURIMETADATA, AVTRANSPORTNEXTURI, AVTRANSPORTNEXTURIMETADATA};
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static GETMEDIAINFO = "GetMediaInfo" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
out "NrTracks" => NUMBEROFTRACKS,
|
||||||
|
out "CurrentTrack" => CURRENTTRACK,
|
||||||
|
out "CurrentURI" => AVTRANSPORTURI,
|
||||||
|
out "CurrentURIMetaData" => AVTRANSPORTURIMETADATA,
|
||||||
|
out "NextURI" => AVTRANSPORTNEXTURI,
|
||||||
|
out "NextURIMetaData" => AVTRANSPORTNEXTURIMETADATA,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::{A_ARG_TYPE_INSTANCE_ID, CURRENTTRACK, CURRENTTRACKDURATION, AVTRANSPORTURI, AVTRANSPORTURIMETADATA, RELATIVETIMEPOSITION, ABSOLUTETIMEPOSITION};
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static GETPOSITIONINFO = "GetPositionInfo" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
out "Track" => CURRENTTRACK,
|
||||||
|
out "TrackDuration" => CURRENTTRACKDURATION,
|
||||||
|
out "TrackURI" => AVTRANSPORTURI,
|
||||||
|
out "TrackMetaData" => AVTRANSPORTURIMETADATA,
|
||||||
|
out "RelTime" => RELATIVETIMEPOSITION,
|
||||||
|
out "AbsTime" => ABSOLUTETIMEPOSITION,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::{A_ARG_TYPE_INSTANCE_ID, TRANSPORTSTATE, TRANSPORTSTATUS};
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static GETTRANSPORTINFO = "GetTransportInfo" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
out "CurrentTransportState" => TRANSPORTSTATE,
|
||||||
|
out "CurrentTransportStatus" => TRANSPORTSTATUS,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::A_ARG_TYPE_INSTANCE_ID;
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static GETTRANSPORTSETTINGS = "GetTransportSettings" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,28 @@
|
|||||||
|
mod getdevicecapabilities;
|
||||||
|
mod getmediainfo;
|
||||||
|
mod getpositioninfo;
|
||||||
|
mod gettransportinfo;
|
||||||
|
mod gettransportsettings;
|
||||||
|
mod next;
|
||||||
|
mod pause;
|
||||||
mod play;
|
mod play;
|
||||||
mod stop;
|
mod previous;
|
||||||
|
mod seek;
|
||||||
|
mod setavtransportnexturi;
|
||||||
mod setavtransporturi;
|
mod setavtransporturi;
|
||||||
|
mod stop;
|
||||||
|
|
||||||
|
pub use getdevicecapabilities::GETDEVICECAPABILITIES;
|
||||||
|
pub use getmediainfo::GETMEDIAINFO;
|
||||||
|
pub use getpositioninfo::GETPOSITIONINFO;
|
||||||
|
pub use gettransportinfo::GETTRANSPORTINFO;
|
||||||
|
pub use gettransportsettings::GETTRANSPORTSETTINGS;
|
||||||
|
pub use next::NEXT;
|
||||||
|
pub use pause::PAUSE;
|
||||||
pub use play::PLAY;
|
pub use play::PLAY;
|
||||||
pub use stop::STOP;
|
pub use previous::PREVIOUS;
|
||||||
|
pub use seek::SEEK;
|
||||||
|
pub use setavtransportnexturi::SETNEXTAVTRANSPORTURI;
|
||||||
pub use setavtransporturi::SETAVTRANSPORTURI;
|
pub use setavtransporturi::SETAVTRANSPORTURI;
|
||||||
|
pub use stop::STOP;
|
||||||
|
|
||||||
|
|||||||
8
pmoupnp/src/mediarenderer/avtransport/actions/next.rs
Normal file
8
pmoupnp/src/mediarenderer/avtransport/actions/next.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::A_ARG_TYPE_INSTANCE_ID;
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static NEXT = "Next" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
8
pmoupnp/src/mediarenderer/avtransport/actions/pause.rs
Normal file
8
pmoupnp/src/mediarenderer/avtransport/actions/pause.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::A_ARG_TYPE_INSTANCE_ID;
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static PAUSE = "Pause" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::A_ARG_TYPE_INSTANCE_ID;
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static PREVIOUS = "Previous" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
10
pmoupnp/src/mediarenderer/avtransport/actions/seek.rs
Normal file
10
pmoupnp/src/mediarenderer/avtransport/actions/seek.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::{A_ARG_TYPE_INSTANCE_ID, A_ARG_TYPE_SEEKMODE, CURRENTTRACKDURATION};
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static SEEK = "Seek" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
in "Unit" => A_ARG_TYPE_SEEKMODE,
|
||||||
|
in "Target" => CURRENTTRACKDURATION,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
use crate::mediarenderer::avtransport::variables::{A_ARG_TYPE_INSTANCE_ID, AVTRANSPORTNEXTURI, AVTRANSPORTNEXTURIMETADATA};
|
||||||
|
use crate::define_action;
|
||||||
|
|
||||||
|
define_action! {
|
||||||
|
pub static SETNEXTAVTRANSPORTURI = "SetNextAVTransportURI" {
|
||||||
|
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
in "NextURI" => AVTRANSPORTNEXTURI,
|
||||||
|
in "NextURIMetaData" => AVTRANSPORTNEXTURIMETADATA,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,55 @@
|
|||||||
|
use crate::define_service;
|
||||||
|
|
||||||
pub mod variables;
|
pub mod variables;
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
|
|
||||||
|
use actions::{
|
||||||
|
GETDEVICECAPABILITIES, GETMEDIAINFO, GETPOSITIONINFO, GETTRANSPORTINFO,
|
||||||
|
GETTRANSPORTSETTINGS, NEXT, PAUSE, PLAY, PREVIOUS, SEEK,
|
||||||
|
SETNEXTAVTRANSPORTURI, SETAVTRANSPORTURI, STOP
|
||||||
|
};
|
||||||
|
use variables::{
|
||||||
|
ABSOLUTETIMEPOSITION, AVTRANSPORTNEXTURI, AVTRANSPORTNEXTURIMETADATA,
|
||||||
|
AVTRANSPORTURI, AVTRANSPORTURIMETADATA, A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
A_ARG_TYPE_PLAY_SPEED, A_ARG_TYPE_SEEKMODE, CURRENTTRACK,
|
||||||
|
CURRENTTRACKDURATION, NUMBEROFTRACKS, RELATIVETIMEPOSITION, SEEKMODE,
|
||||||
|
TRANSPORTPLAYSPEED, TRANSPORTSTATE, TRANSPORTSTATUS
|
||||||
|
};
|
||||||
|
|
||||||
|
define_service! {
|
||||||
|
pub static AVTTRANSPORT = "AVTransport" {
|
||||||
|
variables: [
|
||||||
|
ABSOLUTETIMEPOSITION,
|
||||||
|
A_ARG_TYPE_INSTANCE_ID,
|
||||||
|
A_ARG_TYPE_PLAY_SPEED,
|
||||||
|
A_ARG_TYPE_SEEKMODE,
|
||||||
|
AVTRANSPORTNEXTURI,
|
||||||
|
AVTRANSPORTNEXTURIMETADATA,
|
||||||
|
AVTRANSPORTURI,
|
||||||
|
AVTRANSPORTURIMETADATA,
|
||||||
|
CURRENTTRACK,
|
||||||
|
CURRENTTRACKDURATION,
|
||||||
|
NUMBEROFTRACKS,
|
||||||
|
RELATIVETIMEPOSITION,
|
||||||
|
SEEKMODE,
|
||||||
|
TRANSPORTPLAYSPEED,
|
||||||
|
TRANSPORTSTATE,
|
||||||
|
TRANSPORTSTATUS,
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
GETDEVICECAPABILITIES,
|
||||||
|
GETMEDIAINFO,
|
||||||
|
GETPOSITIONINFO,
|
||||||
|
GETTRANSPORTINFO,
|
||||||
|
GETTRANSPORTSETTINGS,
|
||||||
|
NEXT,
|
||||||
|
PAUSE,
|
||||||
|
PLAY,
|
||||||
|
PREVIOUS,
|
||||||
|
SEEK,
|
||||||
|
SETNEXTAVTRANSPORTURI,
|
||||||
|
SETAVTRANSPORTURI,
|
||||||
|
STOP,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::state_variables::StateVariable;
|
||||||
|
use crate::variable_types::{StateValue, StateVarType};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
pub static A_ARG_TYPE_SEEKMODE: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
|
let mut sv = StateVariable::new(StateVarType::UI4, "A_ARG_TYPE_SeekMode".to_string());
|
||||||
|
|
||||||
|
sv.extend_allowed_values(&[
|
||||||
|
StateValue::String("TRACK_NR".to_string()),
|
||||||
|
StateValue::String("REL_TIME".to_string()),
|
||||||
|
StateValue::String("ABS_TIME".to_string()),
|
||||||
|
]).expect("Cannot set default value");
|
||||||
|
|
||||||
|
Arc::new(sv)
|
||||||
|
});
|
||||||
@@ -7,3 +7,7 @@ use once_cell::sync::Lazy;
|
|||||||
pub static AVTRANSPORTURI: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
pub static AVTRANSPORTURI: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
Arc::new(StateVariable::new(StateVarType::String, "AVTransportURI".to_string()))
|
Arc::new(StateVariable::new(StateVarType::String, "AVTransportURI".to_string()))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub static AVTRANSPORTNEXTURI: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
|
Arc::new(StateVariable::new(StateVarType::String, "AVTransportNextURI".to_string()))
|
||||||
|
});
|
||||||
|
|||||||
@@ -6,15 +6,6 @@ use bevy_reflect::Reflect;
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use pmodidl::{DIDLLite, MediaMetadataParser};
|
use pmodidl::{DIDLLite, MediaMetadataParser};
|
||||||
|
|
||||||
// func _AVTransportURIMetaDataParser(value string) (interface{}, error) {
|
|
||||||
// log.Debug("[avtransport] Parsing AVTransport)")
|
|
||||||
// didl, err := pmodidl.Parse(value)
|
|
||||||
// if err != nil {
|
|
||||||
// return value, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return didl, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn avtransporturimetadataparser(value: &str) -> Result<Box<dyn Reflect>, StateVariableError> {
|
fn avtransporturimetadataparser(value: &str) -> Result<Box<dyn Reflect>, StateVariableError> {
|
||||||
// Parse DIDL-Lite
|
// Parse DIDL-Lite
|
||||||
@@ -31,3 +22,10 @@ pub static AVTRANSPORTURIMETADATA: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Ar
|
|||||||
sv.set_value_parser(Arc::new(avtransporturimetadataparser)).expect("Failed to set parser");
|
sv.set_value_parser(Arc::new(avtransporturimetadataparser)).expect("Failed to set parser");
|
||||||
Arc::new(sv)
|
Arc::new(sv)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub static AVTRANSPORTNEXTURIMETADATA: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
|
let mut sv = StateVariable::new(StateVarType::String, "AVTransportNextURIMetaData".to_string());
|
||||||
|
|
||||||
|
sv.set_value_parser(Arc::new(avtransporturimetadataparser)).expect("Failed to set parser");
|
||||||
|
Arc::new(sv)
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::state_variables::StateVariable;
|
|
||||||
use crate::variable_types::StateVarType;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
pub static CURRENTTRACKDURATION: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
|
||||||
Arc::new(StateVariable::new(StateVarType::String, "CurrentTrackDuration".to_string()))
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
mod a_arg_type_instanceid;
|
mod a_arg_type_instanceid;
|
||||||
mod a_arg_type_playspeed;
|
mod a_arg_type_playspeed;
|
||||||
|
mod a_arg_type_seekmode;
|
||||||
mod avtransporturi;
|
mod avtransporturi;
|
||||||
mod avtransporturimetadata;
|
mod avtransporturimetadata;
|
||||||
mod currenttrackduration;
|
mod track;
|
||||||
|
mod trackduration;
|
||||||
mod seekmode;
|
mod seekmode;
|
||||||
mod transportplayspeed;
|
mod transportplayspeed;
|
||||||
mod transportstate;
|
mod transportstate;
|
||||||
@@ -10,9 +12,16 @@ mod transportstatus;
|
|||||||
|
|
||||||
pub use a_arg_type_instanceid::A_ARG_TYPE_INSTANCE_ID;
|
pub use a_arg_type_instanceid::A_ARG_TYPE_INSTANCE_ID;
|
||||||
pub use a_arg_type_playspeed::A_ARG_TYPE_PLAY_SPEED;
|
pub use a_arg_type_playspeed::A_ARG_TYPE_PLAY_SPEED;
|
||||||
|
pub use a_arg_type_seekmode::A_ARG_TYPE_SEEKMODE;
|
||||||
pub use avtransporturi::AVTRANSPORTURI;
|
pub use avtransporturi::AVTRANSPORTURI;
|
||||||
|
pub use avtransporturi::AVTRANSPORTNEXTURI;
|
||||||
pub use avtransporturimetadata::AVTRANSPORTURIMETADATA;
|
pub use avtransporturimetadata::AVTRANSPORTURIMETADATA;
|
||||||
pub use currenttrackduration::CURRENTTRACKDURATION;
|
pub use avtransporturimetadata::AVTRANSPORTNEXTURIMETADATA;
|
||||||
|
pub use track::CURRENTTRACK;
|
||||||
|
pub use track::NUMBEROFTRACKS;
|
||||||
|
pub use trackduration::CURRENTTRACKDURATION;
|
||||||
|
pub use trackduration::ABSOLUTETIMEPOSITION;
|
||||||
|
pub use trackduration::RELATIVETIMEPOSITION;
|
||||||
pub use seekmode::SEEKMODE;
|
pub use seekmode::SEEKMODE;
|
||||||
pub use transportplayspeed::TRANSPORTPLAYSPEED;
|
pub use transportplayspeed::TRANSPORTPLAYSPEED;
|
||||||
pub use transportstate::TRANSPORTSTATE;
|
pub use transportstate::TRANSPORTSTATE;
|
||||||
|
|||||||
22
pmoupnp/src/mediarenderer/avtransport/variables/track.rs
Normal file
22
pmoupnp/src/mediarenderer/avtransport/variables/track.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::state_variables::StateVariable;
|
||||||
|
use crate::variable_types::StateVarType;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
pub static CURRENTTRACK: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
|
let mut sv = StateVariable::new(StateVarType::String, "CurrentTrack".to_string());
|
||||||
|
|
||||||
|
sv.set_send_notification();
|
||||||
|
|
||||||
|
Arc::new(sv)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static NUMBEROFTRACKS: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
|
let mut sv = StateVariable::new(StateVarType::String, "NumberOfTracks".to_string());
|
||||||
|
|
||||||
|
sv.set_send_notification();
|
||||||
|
|
||||||
|
Arc::new(sv)
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::state_variables::StateVariable;
|
||||||
|
use crate::variable_types::StateVarType;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
pub static CURRENTTRACKDURATION: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
|
let mut sv = StateVariable::new(StateVarType::String, "CurrentTrackDuration".to_string());
|
||||||
|
|
||||||
|
sv.set_send_notification();
|
||||||
|
|
||||||
|
Arc::new(sv)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static ABSOLUTETIMEPOSITION: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
|
let mut sv = StateVariable::new(StateVarType::String, "AbsoluteTimePosition".to_string());
|
||||||
|
|
||||||
|
sv.set_send_notification();
|
||||||
|
|
||||||
|
Arc::new(sv)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static RELATIVETIMEPOSITION: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
|
let mut sv = StateVariable::new(StateVarType::String, "RelativeTimePosition".to_string());
|
||||||
|
|
||||||
|
sv.set_send_notification();
|
||||||
|
|
||||||
|
Arc::new(sv)
|
||||||
|
});
|
||||||
|
|
||||||
@@ -7,7 +7,6 @@ use once_cell::sync::Lazy;
|
|||||||
pub static TRANSPORTSTATE: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
pub static TRANSPORTSTATE: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
let mut sv = StateVariable::new(StateVarType::String, "TransportState".to_string());
|
let mut sv = StateVariable::new(StateVarType::String, "TransportState".to_string());
|
||||||
|
|
||||||
sv.push_allowed_value(&StateValue::String("NO_MEDIA_PRESENT".to_string())).expect("Cannot add allowed value");
|
|
||||||
sv.extend_allowed_values(&[
|
sv.extend_allowed_values(&[
|
||||||
StateValue::String("STOPPED".to_string()),
|
StateValue::String("STOPPED".to_string()),
|
||||||
StateValue::String("PLAYING".to_string()),
|
StateValue::String("PLAYING".to_string()),
|
||||||
@@ -18,6 +17,8 @@ pub static TRANSPORTSTATE: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateV
|
|||||||
StateValue::String("NO_MEDIA_PRESENT".to_string()),
|
StateValue::String("NO_MEDIA_PRESENT".to_string()),
|
||||||
]).expect("Cannt set default value");
|
]).expect("Cannt set default value");
|
||||||
|
|
||||||
|
sv.set_send_notification();
|
||||||
|
|
||||||
Arc::new(sv)
|
Arc::new(sv)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ use once_cell::sync::Lazy;
|
|||||||
pub static TRANSPORTSTATUS: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
pub static TRANSPORTSTATUS: Lazy<Arc<StateVariable>> = Lazy::new(|| -> Arc<StateVariable> {
|
||||||
let mut sv = StateVariable::new(StateVarType::String, "TransportStatus".to_string());
|
let mut sv = StateVariable::new(StateVarType::String, "TransportStatus".to_string());
|
||||||
|
|
||||||
sv.push_allowed_value(&StateValue::String("OK".to_string()))
|
|
||||||
.expect("Cannot add allowed value");
|
|
||||||
sv.extend_allowed_values(&[
|
sv.extend_allowed_values(&[
|
||||||
StateValue::String("OK".to_string()),
|
StateValue::String("OK".to_string()),
|
||||||
StateValue::String("ERROR_OCCURRED".to_string()),
|
StateValue::String("ERROR_OCCURRED".to_string()),
|
||||||
])
|
])
|
||||||
.expect("Cannt set default value");
|
.expect("Cannt set default value");
|
||||||
|
|
||||||
|
sv.set_send_notification();
|
||||||
|
|
||||||
Arc::new(sv)
|
Arc::new(sv)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc};
|
|||||||
|
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use crate::{UpnpDeepClone, UpnpObjectSet, UpnpObjectSetError, UpnpTypedObject};
|
use crate::{UpnpDeepClone, UpnpObjectSet, UpnpObjectSetError, UpnpSet, UpnpTypedObject};
|
||||||
|
|
||||||
/// Implémentation du clonage profond pour `UpnpObjectSet`.
|
/// Implémentation du clonage profond pour `UpnpObjectSet`.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,15 +1,48 @@
|
|||||||
|
//! Erreurs du module services.
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Erreurs liées aux services UPnP.
|
||||||
|
///
|
||||||
|
/// Cette énumération couvre toutes les erreurs possibles lors de la manipulation
|
||||||
|
/// de services UPnP, incluant les erreurs de validation, de configuration et d'exécution.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ServiceError {
|
pub enum ServiceError {
|
||||||
#[error("Action error: {0}")]
|
/// Erreur générale du service.
|
||||||
|
#[error("Service error: {0}")]
|
||||||
GeneralError(String),
|
GeneralError(String),
|
||||||
|
|
||||||
#[error("Argument error: {0}")]
|
/// Erreur de validation (paramètres invalides).
|
||||||
ArgumentError(String),
|
#[error("Validation error: {0}")]
|
||||||
|
ValidationError(String),
|
||||||
|
|
||||||
|
/// Erreur lors d'une opération sur un ensemble (Set).
|
||||||
#[error("Set operation error: {0}")]
|
#[error("Set operation error: {0}")]
|
||||||
SetError(String),
|
SetError(String),
|
||||||
|
|
||||||
|
/// Erreur liée à une action.
|
||||||
|
#[error("Action error: {0}")]
|
||||||
|
ActionError(String),
|
||||||
|
|
||||||
|
/// Erreur liée à une variable d'état.
|
||||||
|
#[error("State variable error: {0}")]
|
||||||
|
StateVariableError(String),
|
||||||
|
|
||||||
|
/// Erreur de configuration.
|
||||||
|
#[error("Configuration error: {0}")]
|
||||||
|
ConfigError(String),
|
||||||
|
|
||||||
|
/// Erreur réseau ou HTTP.
|
||||||
|
#[error("Network error: {0}")]
|
||||||
|
NetworkError(String),
|
||||||
|
|
||||||
|
/// Erreur de sérialisation XML.
|
||||||
|
#[error("XML serialization error: {0}")]
|
||||||
|
XmlError(String),
|
||||||
|
|
||||||
|
/// Erreur lors du traitement SOAP.
|
||||||
|
#[error("SOAP error: {0}")]
|
||||||
|
SoapError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for ServiceError {
|
impl From<std::io::Error> for ServiceError {
|
||||||
@@ -17,3 +50,13 @@ impl From<std::io::Error> for ServiceError {
|
|||||||
ServiceError::GeneralError(format!("IO error: {}", err))
|
ServiceError::GeneralError(format!("IO error: {}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::UpnpObjectSetError> for ServiceError {
|
||||||
|
fn from(err: crate::UpnpObjectSetError) -> Self {
|
||||||
|
match err {
|
||||||
|
crate::UpnpObjectSetError::AlreadyExists(name) => {
|
||||||
|
ServiceError::SetError(format!("Object already exists: {}", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
pmoupnp/src/services/macros.rs
Normal file
109
pmoupnp/src/services/macros.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/// Macro pour définir facilement un service UPnP avec ses variables et actions.
|
||||||
|
///
|
||||||
|
/// Cette macro simplifie la création de services UPnP statiques en générant
|
||||||
|
/// automatiquement le code nécessaire pour initialiser un service avec ses
|
||||||
|
/// variables d'état et ses actions.
|
||||||
|
///
|
||||||
|
/// # Syntaxe
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// define_service! {
|
||||||
|
/// pub static SERVICE_NAME = "ServiceName" {
|
||||||
|
/// variables: [
|
||||||
|
/// VARIABLE1,
|
||||||
|
/// VARIABLE2,
|
||||||
|
/// ],
|
||||||
|
/// actions: [
|
||||||
|
/// ACTION1,
|
||||||
|
/// ACTION2,
|
||||||
|
/// ]
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `SERVICE_NAME` : Nom de la constante statique Rust
|
||||||
|
/// - `"ServiceName"` : Nom du service UPnP (chaîne littérale)
|
||||||
|
/// - `variables:` : Section listant les références aux variables d'état
|
||||||
|
/// - `actions:` : Section listant les références aux actions
|
||||||
|
///
|
||||||
|
/// # Type de retour
|
||||||
|
///
|
||||||
|
/// La macro génère une `Lazy<Arc<Service>>` qui sera initialisée lors du premier accès.
|
||||||
|
///
|
||||||
|
/// # Prérequis
|
||||||
|
///
|
||||||
|
/// Les variables et actions référencées doivent être définies comme `Lazy<Arc<T>>`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use once_cell::sync::Lazy;
|
||||||
|
/// use std::sync::Arc;
|
||||||
|
///
|
||||||
|
/// // Définir les variables et actions ailleurs
|
||||||
|
/// pub static TRANSPORT_STATE: Lazy<Arc<StateVariable>> = ...;
|
||||||
|
/// pub static PLAY: Lazy<Arc<Action>> = ...;
|
||||||
|
/// pub static STOP: Lazy<Arc<Action>> = ...;
|
||||||
|
///
|
||||||
|
/// // Définir le service
|
||||||
|
/// define_service! {
|
||||||
|
/// pub static AVTRANSPORT = "AVTransport" {
|
||||||
|
/// variables: [
|
||||||
|
/// TRANSPORT_STATE,
|
||||||
|
/// TRANSPORT_URI,
|
||||||
|
/// ],
|
||||||
|
/// actions: [
|
||||||
|
/// PLAY,
|
||||||
|
/// STOP,
|
||||||
|
/// PAUSE,
|
||||||
|
/// ]
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Utilisation
|
||||||
|
/// fn main() {
|
||||||
|
/// let service = &*AVTRANSPORT;
|
||||||
|
/// println!("Service: {}", service.name());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Notes d'implémentation
|
||||||
|
///
|
||||||
|
/// - Les `Arc<StateVariable>` et `Arc<Action>` sont clonés
|
||||||
|
/// - Le service est wrappé dans un `Arc`
|
||||||
|
/// - Initialisation paresseuse via `Lazy` (thread-safe)
|
||||||
|
/// - Utilise `.expect()` pour les erreurs d'ajout
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! define_service {
|
||||||
|
(pub static $name:ident = $service_name:literal {
|
||||||
|
variables: [
|
||||||
|
$($var:expr),* $(,)?
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
$($action:expr),* $(,)?
|
||||||
|
]
|
||||||
|
}) => {
|
||||||
|
pub static $name: once_cell::sync::Lazy<std::sync::Arc<$crate::services::Service>> =
|
||||||
|
once_cell::sync::Lazy::new(|| {
|
||||||
|
use $crate::UpnpTyped;
|
||||||
|
|
||||||
|
let mut svc = $crate::services::Service::new($service_name.to_string());
|
||||||
|
|
||||||
|
$(
|
||||||
|
svc.add_variable(std::sync::Arc::clone(&*$var))
|
||||||
|
.expect(&format!("Cannot add variable {} to service {}",
|
||||||
|
(*$var).get_name(), svc.name()));
|
||||||
|
)*
|
||||||
|
|
||||||
|
$(
|
||||||
|
svc.add_action(std::sync::Arc::clone(&*$action))
|
||||||
|
.expect(&format!("Cannot add action {} to service {}",
|
||||||
|
(*$action).get_name(), svc.name()));
|
||||||
|
)*
|
||||||
|
|
||||||
|
std::sync::Arc::new(svc)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
710
pmoupnp/src/services/service_instance.rs
Normal file
710
pmoupnp/src/services/service_instance.rs
Normal file
@@ -0,0 +1,710 @@
|
|||||||
|
//! Implémentation de ServiceInstance.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Mutex, RwLock},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
extract::{Request, State},
|
||||||
|
http::{HeaderMap, StatusCode},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
body::Body,
|
||||||
|
};
|
||||||
|
use tokio::time;
|
||||||
|
use tracing::{info, warn, error};
|
||||||
|
use xmltree::{Element, XMLNode, EmitterConfig};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
services::{Service, ServiceError},
|
||||||
|
actions::{ActionInstance, ActionInstanceSet},
|
||||||
|
state_variables::{StateVarInstance, StateVarInstanceSet, UpnpVariable},
|
||||||
|
UpnpObject, UpnpInstance, UpnpTyped, UpnpTypedInstance, UpnpObjectType,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Méthodes HTTP pour les événements UPnP.
|
||||||
|
pub const METHOD_SUBSCRIBE: &str = "SUBSCRIBE";
|
||||||
|
pub const METHOD_UNSUBSCRIBE: &str = "UNSUBSCRIBE";
|
||||||
|
|
||||||
|
/// Instance de service UPnP.
|
||||||
|
///
|
||||||
|
/// Représente une instance concrète d'un service UPnP, attachée à un device.
|
||||||
|
/// Gère l'exécution des actions, les notifications d'événements et les abonnements.
|
||||||
|
///
|
||||||
|
/// # Fonctionnalités
|
||||||
|
///
|
||||||
|
/// - Exécution d'actions via SOAP
|
||||||
|
/// - Gestion des abonnements aux événements (SUBSCRIBE/UNSUBSCRIBE)
|
||||||
|
/// - Notifications automatiques des changements d'état
|
||||||
|
/// - Génération de la description SCPD
|
||||||
|
///
|
||||||
|
/// # Cycle de vie
|
||||||
|
///
|
||||||
|
/// 1. Création via [`Service::create_instance`](crate::UpnpModel::create_instance)
|
||||||
|
/// 2. Enregistrement des URLs avec [`register_urls`](Self::register_urls)
|
||||||
|
/// 3. Démarrage du notifier avec [`start_notifier`](Self::start_notifier)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use pmoupnp::services::Service;
|
||||||
|
/// # use pmoupnp::server::Server;
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # #[tokio::main]
|
||||||
|
/// # async fn main() {
|
||||||
|
/// let service = Service::new("AVTransport".to_string());
|
||||||
|
/// let instance = service.create_instance();
|
||||||
|
///
|
||||||
|
/// // Enregistrer les endpoints
|
||||||
|
/// let mut server = Server::new("test", "http://localhost:8080", 8080);
|
||||||
|
/// instance.register_urls(&mut server).await.unwrap();
|
||||||
|
///
|
||||||
|
/// // Démarrer les notifications
|
||||||
|
/// let _handle = instance.start_notifier(Duration::from_secs(5));
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServiceInstance {
|
||||||
|
/// Métadonnées de l'objet
|
||||||
|
object: UpnpObjectType,
|
||||||
|
|
||||||
|
/// Référence vers le modèle
|
||||||
|
model: Arc<Service>,
|
||||||
|
|
||||||
|
/// Identifiant du service
|
||||||
|
identifier: String,
|
||||||
|
|
||||||
|
/// Device parent (optionnel)
|
||||||
|
device: Option<Arc<DeviceStub>>,
|
||||||
|
|
||||||
|
/// Variables d'état instanciées
|
||||||
|
statevariables: StateVarInstanceSet,
|
||||||
|
|
||||||
|
/// Actions instanciées
|
||||||
|
actions: ActionInstanceSet,
|
||||||
|
|
||||||
|
/// Abonnés aux événements (SID -> Callback URL)
|
||||||
|
subscribers: Arc<RwLock<HashMap<String, String>>>,
|
||||||
|
|
||||||
|
/// Buffer des changements en attente de notification
|
||||||
|
changed_buffer: Arc<Mutex<HashMap<String, String>>>,
|
||||||
|
|
||||||
|
/// Compteurs de séquence par abonné
|
||||||
|
seqid: Arc<Mutex<HashMap<String, u32>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stub temporaire pour DeviceInstance
|
||||||
|
// TODO: Remplacer par la vraie implémentation quand le module devices sera créé
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DeviceStub {
|
||||||
|
name: String,
|
||||||
|
udn: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceStub {
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_route(&self) -> String {
|
||||||
|
format!("/device/{}", self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn udn(&self) -> &str {
|
||||||
|
&self.udn
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn server_base_url(&self) -> String {
|
||||||
|
"http://localhost:8080".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ServiceInstance {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("ServiceInstance")
|
||||||
|
.field("object", &self.object)
|
||||||
|
.field("identifier", &self.identifier)
|
||||||
|
.field("device", &self.device)
|
||||||
|
.field("statevariables", &self.statevariables)
|
||||||
|
.field("actions", &self.actions)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpnpTyped for ServiceInstance {
|
||||||
|
fn as_upnp_object_type(&self) -> &UpnpObjectType {
|
||||||
|
&self.object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpnpInstance for ServiceInstance {
|
||||||
|
type Model = Service;
|
||||||
|
|
||||||
|
fn new(model: &Service) -> Self {
|
||||||
|
// Phase 1 : Créer les instances de variables d'état
|
||||||
|
let mut statevariables = StateVarInstanceSet::new();
|
||||||
|
for v in model.variables() {
|
||||||
|
if let Err(e) = statevariables.insert(Arc::new(StateVarInstance::new(&*v))) {
|
||||||
|
error!("Failed to insert state variable: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2 : Créer les instances d'actions avec validation
|
||||||
|
let mut actions = ActionInstanceSet::new();
|
||||||
|
for a in model.actions() {
|
||||||
|
// Vérifier que toutes les variables référencées existent
|
||||||
|
let mut missing_vars = Vec::new();
|
||||||
|
|
||||||
|
for arg in a.arguments().all() {
|
||||||
|
let related_var_name = arg.state_variable().get_name();
|
||||||
|
if statevariables.get_by_name(related_var_name).is_none() {
|
||||||
|
missing_vars.push(related_var_name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !missing_vars.is_empty() {
|
||||||
|
error!(
|
||||||
|
"Action '{}' references missing state variables: {:?}",
|
||||||
|
a.get_name(),
|
||||||
|
missing_vars
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer l'instance d'action
|
||||||
|
let action_instance = Arc::new(ActionInstance::new(&*a));
|
||||||
|
|
||||||
|
// ✅ Phase 3 : ACTIVER le binding des arguments aux variables d'instance
|
||||||
|
for arg_instance in action_instance.arguments_set().all() {
|
||||||
|
let var_name = arg_instance.get_model().state_variable().get_name();
|
||||||
|
if let Some(var_instance) = statevariables.get_by_name(var_name) {
|
||||||
|
// ✅ Activer cette ligne (déjà présente dans ArgumentInstance)
|
||||||
|
arg_instance.bind_variable(var_instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = actions.insert(action_instance) {
|
||||||
|
error!("Failed to insert action '{}': {:?}", a.get_name(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
object: UpnpObjectType {
|
||||||
|
name: model.name().to_string(),
|
||||||
|
object_type: "ServiceInstance".to_string(),
|
||||||
|
},
|
||||||
|
model: Arc::new(model.clone()),
|
||||||
|
identifier: model.identifier().to_string(),
|
||||||
|
device: None,
|
||||||
|
statevariables,
|
||||||
|
actions,
|
||||||
|
subscribers: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
changed_buffer: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
seqid: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpnpTypedInstance for ServiceInstance {
|
||||||
|
fn get_model(&self) -> &Self::Model {
|
||||||
|
&self.model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpnpObject for ServiceInstance {
|
||||||
|
fn to_xml_element(&self) -> Element {
|
||||||
|
let mut elem = Element::new("service");
|
||||||
|
|
||||||
|
let mut service_type = Element::new("serviceType");
|
||||||
|
service_type.children.push(XMLNode::Text(self.service_type()));
|
||||||
|
elem.children.push(XMLNode::Element(service_type));
|
||||||
|
|
||||||
|
let mut service_id = Element::new("serviceId");
|
||||||
|
service_id.children.push(XMLNode::Text(self.service_id()));
|
||||||
|
elem.children.push(XMLNode::Element(service_id));
|
||||||
|
|
||||||
|
let mut scpd_url = Element::new("SCPDURL");
|
||||||
|
scpd_url.children.push(XMLNode::Text(self.scpd_url()));
|
||||||
|
elem.children.push(XMLNode::Element(scpd_url));
|
||||||
|
|
||||||
|
let mut control_url = Element::new("controlURL");
|
||||||
|
control_url.children.push(XMLNode::Text(self.control_url()));
|
||||||
|
elem.children.push(XMLNode::Element(control_url));
|
||||||
|
|
||||||
|
let mut event_sub_url = Element::new("eventSubURL");
|
||||||
|
event_sub_url.children.push(XMLNode::Text(self.event_sub_url()));
|
||||||
|
elem.children.push(XMLNode::Element(event_sub_url));
|
||||||
|
|
||||||
|
elem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceInstance {
|
||||||
|
/// Retourne l'identifiant du service.
|
||||||
|
pub fn identifier(&self) -> &str {
|
||||||
|
&self.identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne le type de service UPnP.
|
||||||
|
///
|
||||||
|
/// Format: `urn:schemas-upnp-org:service:{name}:{version}`
|
||||||
|
pub fn service_type(&self) -> String {
|
||||||
|
self.model.service_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne l'ID de service UPnP.
|
||||||
|
///
|
||||||
|
/// Format: `urn:upnp-org:serviceId:{identifier}`
|
||||||
|
pub fn service_id(&self) -> String {
|
||||||
|
format!("urn:upnp-org:serviceId:{}", self.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Raccourci pour obtenir une variable d'état par nom
|
||||||
|
pub fn get_variable(&self, name: &str) -> Option<Arc<StateVarInstance>> {
|
||||||
|
self.statevariables.get_by_name(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Raccourci pour obtenir une action par nom
|
||||||
|
pub fn get_action(&self, name: &str) -> Option<Arc<ActionInstance>> {
|
||||||
|
self.actions.get_by_name(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne la route de base du service.
|
||||||
|
pub fn base_route(&self) -> String {
|
||||||
|
match &self.device {
|
||||||
|
Some(device) => format!("{}/service/{}", device.base_route(), self.get_name()),
|
||||||
|
None => format!("/service/{}", self.get_name()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne l'URL de contrôle SOAP.
|
||||||
|
pub fn control_url(&self) -> String {
|
||||||
|
format!("{}/control", self.base_route())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne l'URL de souscription aux événements.
|
||||||
|
pub fn event_sub_url(&self) -> String {
|
||||||
|
format!("{}/event", self.base_route())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne l'URL de la description SCPD.
|
||||||
|
pub fn scpd_url(&self) -> String {
|
||||||
|
format!("{}/desc.xml", self.base_route())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne l'USN (Unique Service Name).
|
||||||
|
pub fn usn(&self) -> String {
|
||||||
|
match &self.device {
|
||||||
|
Some(device) => format!("uuid:{}::urn:{}", device.udn(), self.service_type()),
|
||||||
|
None => format!("uuid::urn:{}", self.service_type()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne les variables d'état.
|
||||||
|
pub fn statevariables(&self) -> &StateVarInstanceSet {
|
||||||
|
&self.statevariables
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne les actions.
|
||||||
|
pub fn actions(&self) -> &ActionInstanceSet {
|
||||||
|
&self.actions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enregistre les routes UPnP dans le serveur Axum.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Retourne une erreur si l'enregistrement des routes échoue.
|
||||||
|
pub async fn register_urls(&self, server: &mut crate::server::Server) -> Result<(), ServiceError> {
|
||||||
|
info!(
|
||||||
|
"✅ Service description for {}:{} available at : {}{}",
|
||||||
|
self.device.as_ref().map(|d| d.name()).unwrap_or("unknown"),
|
||||||
|
self.get_name(),
|
||||||
|
self.device.as_ref().map(|d| d.server_base_url()).unwrap_or_default(),
|
||||||
|
self.scpd_url(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handler SCPD
|
||||||
|
let instance_scpd = self.clone();
|
||||||
|
server.add_handler(&self.scpd_url(), move || {
|
||||||
|
let instance = instance_scpd.clone();
|
||||||
|
async move { instance.scpd_handler().await }
|
||||||
|
}).await;
|
||||||
|
|
||||||
|
// Handler control
|
||||||
|
let instance_control = self.clone();
|
||||||
|
server.add_post_handler_with_state(
|
||||||
|
&self.control_url(),
|
||||||
|
control_handler,
|
||||||
|
instance_control,
|
||||||
|
).await;
|
||||||
|
|
||||||
|
// Handler événements
|
||||||
|
let instance_event = self.clone();
|
||||||
|
server.add_handler_with_state(
|
||||||
|
&self.event_sub_url(),
|
||||||
|
event_sub_handler,
|
||||||
|
instance_event,
|
||||||
|
).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Génère l'élément XML SCPD.
|
||||||
|
pub fn scpd_element(&self) -> Element {
|
||||||
|
let mut elem = Element::new("scpd");
|
||||||
|
elem.attributes.insert(
|
||||||
|
"xmlns".to_string(),
|
||||||
|
"urn:schemas-upnp-org:service-1-0".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// specVersion
|
||||||
|
let mut spec = Element::new("specVersion");
|
||||||
|
let mut major = Element::new("major");
|
||||||
|
major.children.push(XMLNode::Text("1".to_string()));
|
||||||
|
spec.children.push(XMLNode::Element(major));
|
||||||
|
|
||||||
|
let mut minor = Element::new("minor");
|
||||||
|
minor.children.push(XMLNode::Text("0".to_string()));
|
||||||
|
spec.children.push(XMLNode::Element(minor));
|
||||||
|
|
||||||
|
elem.children.push(XMLNode::Element(spec));
|
||||||
|
|
||||||
|
// actionList
|
||||||
|
if !self.actions.all().is_empty() {
|
||||||
|
elem.children.push(XMLNode::Element(
|
||||||
|
self.actions.to_xml_element()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceStateTable
|
||||||
|
if !self.statevariables.all().is_empty() {
|
||||||
|
elem.children.push(XMLNode::Element(
|
||||||
|
self.statevariables.to_xml_element()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
elem
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler pour la description SCPD.
|
||||||
|
async fn scpd_handler(&self) -> Response {
|
||||||
|
let elem = self.scpd_element();
|
||||||
|
|
||||||
|
let config = EmitterConfig::new()
|
||||||
|
.perform_indent(true)
|
||||||
|
.indent_string(" ");
|
||||||
|
|
||||||
|
let mut xml_output = Vec::new();
|
||||||
|
if let Err(e) = elem.write_with_config(&mut xml_output, config) {
|
||||||
|
error!("Failed to serialize SCPD XML: {}", e);
|
||||||
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut xml = String::from_utf8_lossy(&xml_output).to_string();
|
||||||
|
|
||||||
|
// Ajouter l'en-tête XML
|
||||||
|
xml.insert_str(0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[(axum::http::header::CONTENT_TYPE, "text/xml; charset=\"utf-8\"")],
|
||||||
|
xml,
|
||||||
|
).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ajoute un abonné aux événements.
|
||||||
|
pub async fn add_subscriber(&self, sid: String, callback: String) {
|
||||||
|
let mut subscribers = self.subscribers.write().unwrap();
|
||||||
|
subscribers.insert(sid, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renouvelle un abonnement.
|
||||||
|
pub async fn renew_subscriber(&self, sid: &str, timeout: &str) {
|
||||||
|
info!("♻️ Renewed SID {} for timeout {}", sid, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Supprime un abonné.
|
||||||
|
pub async fn remove_subscriber(&self, sid: &str) {
|
||||||
|
let mut subscribers = self.subscribers.write().unwrap();
|
||||||
|
subscribers.remove(sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Envoie l'événement initial à un nouvel abonné.
|
||||||
|
pub async fn send_initial_event(&self, sid: String) {
|
||||||
|
let callback = {
|
||||||
|
let subscribers = self.subscribers.read().unwrap();
|
||||||
|
subscribers.get(&sid).cloned()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(callback) = callback {
|
||||||
|
let mut changed = HashMap::new();
|
||||||
|
for sv in self.statevariables.all() {
|
||||||
|
if sv.is_sending_notification() {
|
||||||
|
changed.insert(sv.get_name().to_string(), sv.value().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let callback = callback.trim().trim_matches(|c| c == '<' || c == '>');
|
||||||
|
|
||||||
|
let mut body = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">"#.to_string();
|
||||||
|
for (name, val) in changed {
|
||||||
|
body.push_str(&format!("<e:property><{0}>{1}</{0}></e:property>", name, val));
|
||||||
|
}
|
||||||
|
body.push_str("</e:propertyset>");
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
match client
|
||||||
|
.request(reqwest::Method::from_bytes(b"NOTIFY").unwrap(), callback)
|
||||||
|
.header("Content-Type", r#"text/xml; charset="utf-8"#)
|
||||||
|
.header("NT", "upnp:event")
|
||||||
|
.header("NTS", "upnp:propchange")
|
||||||
|
.header("SID", &sid)
|
||||||
|
.header("SEQ", "0")
|
||||||
|
.body(body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(resp) => {
|
||||||
|
info!("✅ Initial event sent to {}, status={}", callback, resp.status());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to send initial event to {}: {}", callback, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marque un changement à notifier.
|
||||||
|
pub fn event_to_be_sent(&self, name: String, value: String) {
|
||||||
|
let mut buffer = self.changed_buffer.lock().unwrap();
|
||||||
|
buffer.insert(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Récupère le prochain numéro de séquence pour un abonné.
|
||||||
|
fn next_seq(&self, sid: &str) -> String {
|
||||||
|
let mut seqid = self.seqid.lock().unwrap();
|
||||||
|
let counter = seqid.entry(sid.to_string()).or_insert(0);
|
||||||
|
*counter += 1;
|
||||||
|
counter.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notifie tous les abonnés des changements.
|
||||||
|
pub async fn notify_subscribers(&self) {
|
||||||
|
let subscribers_copy = {
|
||||||
|
let subscribers = self.subscribers.read().unwrap();
|
||||||
|
if subscribers.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
subscribers.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let changed = {
|
||||||
|
let mut buffer = self.changed_buffer.lock().unwrap();
|
||||||
|
if buffer.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::mem::take(&mut *buffer)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (sid, callback) in subscribers_copy {
|
||||||
|
let changed_clone = changed.clone();
|
||||||
|
let seq = self.next_seq(&sid);
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let callback = callback.trim().trim_matches(|c| c == '<' || c == '>');
|
||||||
|
|
||||||
|
let mut body = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">"#.to_string();
|
||||||
|
for (name, val) in changed_clone {
|
||||||
|
body.push_str(&format!("<e:property><{0}>{1}</{0}></e:property>", name, val));
|
||||||
|
}
|
||||||
|
body.push_str("</e:propertyset>");
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
match client
|
||||||
|
.request(reqwest::Method::from_bytes(b"NOTIFY").unwrap(), callback)
|
||||||
|
.header("Content-Type", r#"text/xml; charset="utf-8"#)
|
||||||
|
.header("NT", "upnp:event")
|
||||||
|
.header("NTS", "upnp:propchange")
|
||||||
|
.header("SID", &sid)
|
||||||
|
.header("SEQ", seq)
|
||||||
|
.body(body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
info!("✅ Notified subscriber {} of changes", callback);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to notify subscriber {}: {}", callback, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Démarre le notifier périodique.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `interval` - Intervalle entre les notifications
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Un handle vers la tâche tokio du notifier.
|
||||||
|
pub fn start_notifier(&self, interval: Duration) -> tokio::task::JoinHandle<()> {
|
||||||
|
let instance = self.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut ticker = time::interval(interval);
|
||||||
|
info!("✅ Starting notifier every {:?}", interval);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
ticker.tick().await;
|
||||||
|
instance.notify_subscribers().await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler Axum pour les événements (SUBSCRIBE/UNSUBSCRIBE).
|
||||||
|
async fn event_sub_handler(
|
||||||
|
State(instance): State<ServiceInstance>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
req: Request<Body>,
|
||||||
|
) -> Response {
|
||||||
|
info!("📡 Event Subscription request for {}", instance.get_name());
|
||||||
|
|
||||||
|
let method = req.method().as_str();
|
||||||
|
let sid = headers.get("SID").and_then(|v| v.to_str().ok()).unwrap_or("");
|
||||||
|
let timeout = headers.get("Timeout").and_then(|v| v.to_str().ok()).unwrap_or("");
|
||||||
|
let callback = headers.get("Callback").and_then(|v| v.to_str().ok()).unwrap_or("");
|
||||||
|
|
||||||
|
match method {
|
||||||
|
METHOD_SUBSCRIBE => {
|
||||||
|
let (response_sid, response_timeout) = if sid.is_empty() {
|
||||||
|
// Nouvelle souscription
|
||||||
|
let new_sid = format!("uuid:{}", uuid::Uuid::new_v4());
|
||||||
|
if !callback.is_empty() {
|
||||||
|
instance.add_subscriber(new_sid.clone(), callback.to_string()).await;
|
||||||
|
}
|
||||||
|
let timeout_val = if timeout.is_empty() {
|
||||||
|
"Second-1800"
|
||||||
|
} else {
|
||||||
|
timeout
|
||||||
|
};
|
||||||
|
info!("🔒 New subscription: SID={}, Callback={}, Timeout={}", new_sid, callback, timeout_val);
|
||||||
|
|
||||||
|
let sid_clone = new_sid.clone();
|
||||||
|
let instance_clone = instance.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
instance_clone.send_initial_event(sid_clone).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
(new_sid, timeout_val.to_string())
|
||||||
|
} else {
|
||||||
|
// Renouvellement
|
||||||
|
instance.renew_subscriber(sid, timeout).await;
|
||||||
|
info!("♻️ Renew subscription: SID={}, Timeout={}", sid, timeout);
|
||||||
|
(sid.to_string(), timeout.to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
axum::http::header::HeaderName::from_static("sid"),
|
||||||
|
axum::http::HeaderValue::from_str(&response_sid).unwrap()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
axum::http::header::HeaderName::from_static("timeout"),
|
||||||
|
axum::http::HeaderValue::from_str(&response_timeout).unwrap()
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).into_response()
|
||||||
|
}
|
||||||
|
METHOD_UNSUBSCRIBE => {
|
||||||
|
if !sid.is_empty() {
|
||||||
|
instance.remove_subscriber(sid).await;
|
||||||
|
info!("❌ Unsubscribe SID={}", sid);
|
||||||
|
}
|
||||||
|
StatusCode::OK.into_response()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("Unsupported EventSub method: {}", method);
|
||||||
|
StatusCode::METHOD_NOT_ALLOWED.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler Axum pour le contrôle SOAP.
|
||||||
|
async fn control_handler(
|
||||||
|
State(instance): State<ServiceInstance>,
|
||||||
|
body: String,
|
||||||
|
) -> Response {
|
||||||
|
info!("📡 Control request for {}", instance.get_name());
|
||||||
|
|
||||||
|
// TODO: Parser le SOAP et appeler l'action correspondante
|
||||||
|
|
||||||
|
let response_xml = format!(
|
||||||
|
r#"<?xml version="1.0"?>
|
||||||
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
|
||||||
|
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||||
|
<s:Body>
|
||||||
|
<u:Response xmlns:u="{}">
|
||||||
|
</u:Response>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>"#,
|
||||||
|
instance.service_type()
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[(axum::http::header::CONTENT_TYPE, "text/xml; charset=\"utf-8\"")],
|
||||||
|
response_xml,
|
||||||
|
).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::services::Service;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_instance_creation() {
|
||||||
|
let service = Service::new("AVTransport".to_string());
|
||||||
|
let instance = ServiceInstance::new(&service);
|
||||||
|
|
||||||
|
assert_eq!(instance.get_name(), "AVTransport");
|
||||||
|
assert_eq!(instance.identifier(), "AVTransport");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_urls() {
|
||||||
|
let service = Service::new("AVTransport".to_string());
|
||||||
|
let instance = ServiceInstance::new(&service);
|
||||||
|
|
||||||
|
assert_eq!(instance.base_route(), "/service/AVTransport");
|
||||||
|
assert_eq!(instance.control_url(), "/service/AVTransport/control");
|
||||||
|
assert_eq!(instance.event_sub_url(), "/service/AVTransport/event");
|
||||||
|
assert_eq!(instance.scpd_url(), "/service/AVTransport/desc.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_type() {
|
||||||
|
let mut service = Service::new("AVTransport".to_string());
|
||||||
|
service.set_version(2).unwrap();
|
||||||
|
let instance = ServiceInstance::new(&service);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
instance.service_type(),
|
||||||
|
"urn:schemas-upnp-org:service:AVTransport:2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
pmoupnp/src/services/service_methods.rs
Normal file
57
pmoupnp/src/services/service_methods.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//! Implémentation des traits UPnP pour Service.
|
||||||
|
|
||||||
|
use xmltree::{Element, XMLNode};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
services::{Service, ServiceInstance},
|
||||||
|
UpnpObject, UpnpModel, UpnpTyped, UpnpObjectType,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl std::fmt::Display for Service {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "Service({}:{})", self.name(), self.version())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpnpTyped for Service {
|
||||||
|
fn as_upnp_object_type(&self) -> &UpnpObjectType {
|
||||||
|
&self.object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpnpObject for Service {
|
||||||
|
fn to_xml_element(&self) -> Element {
|
||||||
|
let mut elem = Element::new("service");
|
||||||
|
|
||||||
|
// serviceType
|
||||||
|
let mut service_type = Element::new("serviceType");
|
||||||
|
service_type.children.push(XMLNode::Text(self.service_type()));
|
||||||
|
elem.children.push(XMLNode::Element(service_type));
|
||||||
|
|
||||||
|
// serviceId
|
||||||
|
let mut service_id = Element::new("serviceId");
|
||||||
|
service_id.children.push(XMLNode::Text(self.service_id()));
|
||||||
|
elem.children.push(XMLNode::Element(service_id));
|
||||||
|
|
||||||
|
// SCPDURL
|
||||||
|
let mut SCPDURL = Element::new("SCPDURL");
|
||||||
|
SCPDURL.children.push(XMLNode::Text(self.scpd_url()));
|
||||||
|
elem.children.push(XMLNode::Element(SCPDURL));
|
||||||
|
|
||||||
|
// controlURL
|
||||||
|
let mut controlURL = Element::new("controlURL");
|
||||||
|
controlURL.children.push(XMLNode::Text(self.control_url()));
|
||||||
|
elem.children.push(XMLNode::Element(controlURL));
|
||||||
|
|
||||||
|
// SCPDURL
|
||||||
|
let mut eventSubURL = Element::new("eventSubURL");
|
||||||
|
eventSubURL.children.push(XMLNode::Text(self.event_url()));
|
||||||
|
elem.children.push(XMLNode::Element(eventSubURL));
|
||||||
|
|
||||||
|
elem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpnpModel for Service {
|
||||||
|
type Instance = ServiceInstance;
|
||||||
|
}
|
||||||
@@ -1,25 +1,32 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cat << EOF
|
echo "# Debut des sources des crates "
|
||||||
Est-ce que ces packages te semblent fonctionnel.
|
echo
|
||||||
Il ne s'agit pas de l'enrichir de nouvelle fonctionnalité, ni de l'optimiser d'avantage. Juste de le finaliser en detectant vrais bug. Ce package doit être thread safe, uniquement stéréo.
|
|
||||||
Ne signale que les bugs conduisant à un disfonctionnement
|
|
||||||
|
|
||||||
Regénère des version complètes et corrigées des fichiers nécessaires
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo ============== Debut des sources des packages ===============
|
|
||||||
|
|
||||||
for package in $*; do
|
for package in $*; do
|
||||||
find $package -type f \( -name '*.rs' -o -name '*.toml' \) -print0 |
|
find $package -type f \( -name '*.rs' -o -name '*.go' \
|
||||||
while IFS= read -r -d '' file; do
|
-o -name '*.js' -o -name '*.ts' \
|
||||||
echo "------- $file ------"
|
-o -name '*.vue' -o -name '*.css' \
|
||||||
|
-o -name '*.toml' \) -print \
|
||||||
|
| grep -v 'webapp/[^s]' \
|
||||||
|
| while read -r file; do
|
||||||
|
case "$file" in
|
||||||
|
*.rs) language="rust" ;;
|
||||||
|
*.go) language="go" ;;
|
||||||
|
*.js) lanquage="javascript" ;;
|
||||||
|
*.ts) language="typescript" ;;
|
||||||
|
*.vue) language="vue" ;;
|
||||||
|
*.css) language="css" ;;
|
||||||
|
*.toml) language="toml" ;;
|
||||||
|
*) language="" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "## fichier: \`$file\`"
|
||||||
|
echo
|
||||||
|
echo "\`\`\`$language"
|
||||||
cat $file
|
cat $file
|
||||||
echo "-----------------"
|
echo "\`\`\`"
|
||||||
|
echo
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ============== Fin des sources des packages ===============
|
|
||||||
|
|
||||||
echo ============== Analyse des bugs par chatgpt ===============
|
|
||||||
|
|||||||
Reference in New Issue
Block a user