diff --git a/Cargo.lock b/Cargo.lock index 0989a22..cb4d888 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,10 +23,6 @@ name = "accesskit" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" -dependencies = [ - "enumn", - "serde", -] [[package]] name = "accesskit_consumer" @@ -117,7 +113,6 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", - "serde", "version_check", "zerocopy", ] @@ -131,6 +126,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-activity" version = "0.5.2" @@ -202,6 +218,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c90a406b4495d129f00461241616194cb8a032c8d1c53c657f0961d5f8e0498" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + [[package]] name = "async-executor" version = "1.11.0" @@ -415,6 +447,71 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "base64", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite 0.20.1", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -447,9 +544,6 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -dependencies = [ - "serde", -] [[package]] name = "block" @@ -527,6 +621,27 @@ dependencies = [ "piper", ] +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -827,27 +942,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dispatch" version = "0.2.0" @@ -885,7 +979,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10" dependencies = [ "bytemuck", - "serde", ] [[package]] @@ -896,7 +989,6 @@ checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2" dependencies = [ "bytemuck", "cocoa", - "directories-next", "document-features", "egui", "egui-winit", @@ -912,8 +1004,6 @@ dependencies = [ "percent-encoding", "raw-window-handle 0.5.2", "raw-window-handle 0.6.1", - "ron", - "serde", "static_assertions", "thiserror", "wasm-bindgen", @@ -935,8 +1025,6 @@ dependencies = [ "epaint", "log", "nohash-hasher", - "ron", - "serde", ] [[package]] @@ -950,7 +1038,6 @@ dependencies = [ "egui", "log", "raw-window-handle 0.6.1", - "serde", "smithay-clipboard", "web-time", "webbrowser", @@ -979,7 +1066,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f" dependencies = [ "bytemuck", - "serde", ] [[package]] @@ -1003,17 +1089,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "enumn" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "env_logger" version = "0.10.2" @@ -1041,7 +1116,6 @@ dependencies = [ "log", "nohash-hasher", "parking_lot 0.12.2", - "serde", ] [[package]] @@ -1125,6 +1199,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ewebsock" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6177769715c6ec5a324acee995183b22721ea23c58e49af14a828eadec85d120" +dependencies = [ + "document-features", + "js-sys", + "log", + "tungstenite 0.21.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "eyre" version = "0.6.12" @@ -1453,11 +1542,37 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1486,6 +1601,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a80870ee775a6b862265eb04a8d61d1b1ef8e4c423154a52ba6a1e3ff24836c" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.1.0" @@ -1497,32 +1623,84 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyperdeck-monitor" version = "0.1.0" dependencies = [ + "axum", "color-eyre", + "futures", "futures-util", "serde", "serde_json", "tokio", - "tokio-tungstenite", + "tokio-stream", + "tokio-tungstenite 0.21.0", "tokio-util", + "tower", + "tower-http", "tracing", "tracing-subscriber", "url", + "uuid", ] [[package]] @@ -1532,9 +1710,11 @@ dependencies = [ "eframe", "egui", "env_logger", + "ewebsock", "hrtime", "log", "serde", + "serde_json", "wasm-bindgen-futures", "wasm-timer", ] @@ -1579,6 +1759,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -1586,7 +1776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1609,6 +1799,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "iri-string" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f5f6c2df22c009ac44f6f1499308e7a3ac7ba42cd2378475cc691510e1eef1b" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -1705,16 +1905,6 @@ dependencies = [ "redox_syscall 0.4.1", ] -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.5.0", - "libc", -] - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1767,6 +1957,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.2" @@ -1800,6 +1996,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2047,7 +2259,7 @@ version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "libredox 0.0.2", + "libredox", ] [[package]] @@ -2147,6 +2359,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -2350,17 +2582,6 @@ dependencies = [ "bitflags 2.5.0", ] -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom", - "libredox 0.1.3", - "thiserror", -] - [[package]] name = "regex" version = "1.10.4" @@ -2405,18 +2626,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" -[[package]] -name = "ron" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" -dependencies = [ - "base64", - "bitflags 2.5.0", - "serde", - "serde_derive", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2450,6 +2659,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.17" @@ -2499,15 +2714,25 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -2519,6 +2744,18 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2671,6 +2908,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "tempfile" version = "3.10.1" @@ -2767,6 +3010,30 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.20.1", +] + [[package]] name = "tokio-tungstenite" version = "0.21.0" @@ -2776,19 +3043,23 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.21.0", ] [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", + "futures-util", + "hashbrown 0.14.5", "pin-project-lite", + "slab", "tokio", ] @@ -2804,7 +3075,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -2815,17 +3086,81 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap", + "indexmap 2.2.6", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "hdrhistogram", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "async-compression", + "base64", + "bitflags 2.5.0", + "bytes", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body", + "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2891,12 +3226,37 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.21.0" @@ -2906,7 +3266,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 1.1.0", "httparse", "log", "rand", @@ -2933,6 +3293,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -2977,6 +3346,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "valuable" version = "0.1.0" @@ -3005,6 +3384,15 @@ dependencies = [ "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]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3723,6 +4111,34 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zvariant" version = "3.15.2" diff --git a/Cargo.toml b/Cargo.toml index 6e685ab..5600746 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = [ "monitor", - "frontend" + "frontend", ] resolver = "2" diff --git a/frontend/.gitignore b/frontend/.gitignore index 0a9d784..3324001 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,3 +1,6 @@ +# Copied during build (🤮) +src/websocket.rs + # trunk output folder dist diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index a406532..d196880 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -9,12 +9,13 @@ eframe = { version = "0.27.0", default-features = false, features = [ "accesskit", # Make egui comptaible with screen readers. "default_fonts", # Embed the default egui fonts. "glow", # Use the glow rendering backend. Alternative: "wgpu". - "persistence", # Enable restoring app state when restarting the app. ] } log = "0.4" serde = { version = "1", features = ["derive"] } hrtime = "0.2.0" wasm-timer = "0.2.5" +ewebsock = "0.5.0" +serde_json = "1.0.117" # native: diff --git a/frontend/build.rs b/frontend/build.rs new file mode 100644 index 0000000..c284fe4 --- /dev/null +++ b/frontend/build.rs @@ -0,0 +1,7 @@ +fn main() { + // Sorry, I know it's horrible but I don't have the spoons to make it right. + const API_DEFINITION_FILE: &str = "../monitor/src/api/message.rs"; + println!("cargo:rerun-if-changed={API_DEFINITION_FILE}"); + std::fs::copy(API_DEFINITION_FILE, "./src/websocket.rs") + .expect("Failed to copy API definition"); +} diff --git a/frontend/src/app.rs b/frontend/src/app.rs index f959524..7866030 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -1,35 +1,38 @@ use std::{ + collections::VecDeque, fmt::Display, + mem, net::{IpAddr, Ipv4Addr}, }; -use egui::{Color32, RichText, Sense, Stroke, Vec2}; +use egui::{Button, Color32, RichText, Sense, Stroke, Vec2}; +use ewebsock::{Options, WsReceiver, WsSender}; use wasm_timer::Instant; -#[derive(serde::Deserialize, serde::Serialize)] -#[serde(default)] +use crate::websocket::{AddHyperdeckRequest, ClientRequest}; + pub struct HyperdeckMonitorApp { - #[serde(skip)] blink: bool, - #[serde(skip)] last_blink_change: Instant, - #[serde(skip)] new_hyperdeck_ip: String, - - #[serde(skip)] new_hyperdeck_name: String, - - #[serde(skip)] + new_hyperdeck_port: String, hyperdecks: Vec, + websocket_message_queue: VecDeque, + ws_sender: WsSender, + ws_receiver: WsReceiver, } impl Default for HyperdeckMonitorApp { fn default() -> Self { + let (ws_sender, ws_receiver) = + ewebsock::connect("ws://127.0.0.1:9681/ws", Options::default()).unwrap(); Self { blink: false, last_blink_change: Instant::now(), new_hyperdeck_ip: "".to_owned(), new_hyperdeck_name: "".to_owned(), + new_hyperdeck_port: 9993.to_string(), hyperdecks: vec![ Hyperdeck { name: "Test Hyperdeck 1".to_string(), @@ -52,27 +55,20 @@ impl Default for HyperdeckMonitorApp { }], }, ], + websocket_message_queue: VecDeque::new(), + ws_sender, + ws_receiver, } } } -impl HyperdeckMonitorApp { - pub fn new(cc: &eframe::CreationContext<'_>) -> Self { - // Load previous app state (if any) - if let Some(storage) = cc.storage { - return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); - } - - Default::default() - } -} - impl eframe::App for HyperdeckMonitorApp { - fn save(&mut self, storage: &mut dyn eframe::Storage) { - eframe::set_value(storage, eframe::APP_KEY, self); - } - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + if let Some(message) = self.websocket_message_queue.pop_front() { + self.ws_sender.send(ewebsock::WsMessage::Text( + serde_json::to_string(&message).expect("Could not serialize message"), + )); + } egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::menu::bar(ui, |ui| { egui::widgets::global_dark_light_mode_buttons(ui); @@ -80,7 +76,13 @@ impl eframe::App for HyperdeckMonitorApp { }); egui::CentralPanel::default().show(ctx, |ui| { - add_hyperdeck_panel(ui, &mut self.new_hyperdeck_ip, &mut self.new_hyperdeck_name); + add_hyperdeck_panel( + ui, + &mut self.new_hyperdeck_name, + &mut self.new_hyperdeck_ip, + &mut self.new_hyperdeck_port, + &mut self.websocket_message_queue, + ); ui.separator(); ui.vertical(|ui| { @@ -105,8 +107,10 @@ impl eframe::App for HyperdeckMonitorApp { fn add_hyperdeck_panel( ui: &mut egui::Ui, - new_hyperdeck_ip: &mut String, new_hyperdeck_name: &mut String, + new_hyperdeck_ip: &mut String, + new_hyperdeck_port: &mut String, + message_queue: &mut VecDeque, ) { ui.heading("Add hyperdeck"); ui.horizontal(|ui| { @@ -114,8 +118,19 @@ fn add_hyperdeck_panel( ui.text_edit_singleline(new_hyperdeck_name); ui.label("IP"); ui.text_edit_singleline(new_hyperdeck_ip); - if ui.button("Add").clicked() { - // Do Something + ui.label("Port"); + ui.text_edit_singleline(new_hyperdeck_port); + let button_enabled = new_hyperdeck_ip.parse::().is_ok() + && !new_hyperdeck_name.is_empty() + && new_hyperdeck_port.parse::().is_ok(); + if ui.add_enabled(button_enabled, Button::new("Add")).clicked() { + message_queue.push_back(ClientRequest::AddHyperdeck(AddHyperdeckRequest { + name: mem::take(new_hyperdeck_name), + ip: mem::take(new_hyperdeck_ip), + port: mem::replace(new_hyperdeck_port, "9993".to_string()) + .parse::() + .unwrap(), + })); } }); } diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index d62abbd..040d615 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -1,2 +1,4 @@ mod app; +mod websocket; + pub use app::HyperdeckMonitorApp; diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 28b0bda..2f35a49 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -11,7 +11,7 @@ fn main() -> eframe::Result<()> { eframe::run_native( "eframe template", native_options, - Box::new(|cc| Box::new(hyperdeck_monitor_gui::HyperdeckMonitorApp::new(cc))), + Box::new(|_| Box::new(hyperdeck_monitor_gui::HyperdeckMonitorApp::default())), ) } @@ -26,7 +26,7 @@ fn main() { .start( "the_canvas_id", // Where to draw to. web_options, - Box::new(|cc| Box::new(hyperdeck_monitor_gui::HyperdeckMonitorApp::new(cc))), + Box::new(|_| Box::new(hyperdeck_monitor_gui::HyperdeckMonitorApp::default())), ) .await .expect("failed to start eframe"); diff --git a/monitor/Cargo.toml b/monitor/Cargo.toml index 7ed586c..e8a66e6 100644 --- a/monitor/Cargo.toml +++ b/monitor/Cargo.toml @@ -6,13 +6,19 @@ edition = "2021" default-run = "hyperdeck-monitor" [dependencies] +axum = { version = "0.6.10", features = ["macros", "ws"] } color-eyre = "0.6.3" +futures = { version = "0.3.25" } futures-util = "0.3.30" serde = { version = "1.0.199", features = ["derive"] } serde_json = "1.0.116" tokio = { version = "1.37.0", features = ["full"] } +tokio-stream = { version = "0.1.12", features = ["sync"] } tokio-tungstenite = "0.21.0" -tokio-util = "0.7.10" +tokio-util = { version = "0.7.11", features = ["full"] } +tower = { version = "0.4.13", features = ["full"] } +tower-http = { version = "0.4.0", features = ["full"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } url = "2.5.0" +uuid = { version = "1.2.2", features = ["serde", "v4"] } diff --git a/monitor/index.ts b/monitor/index.ts index b89390a..9f4834d 100644 --- a/monitor/index.ts +++ b/monitor/index.ts @@ -1,10 +1,35 @@ +import { Hyperdeck, Commands } from 'hyperdeck-connection'; import WebSocket from 'ws'; +interface WrappedHyperdeck { + ip: String, + port: number, + hyperdeck: Hyperdeck +} + +const hyperdecks: Map = new Map() + +enum WebSocketMessageType { + AddHyperdeck = "add_hyperdeck" +} + +type WebSocketMessage = { + type: WebSocketMessageType.AddHyperdeck, + id: string, + ip: string, + port: number +} + const wss = new WebSocket.Server({ port: 7867 }); wss.on('connection', function connection(ws) { ws.on('message', function message(data) { - console.log('received: %s', data); + try { + const message = JSON.parse(data.toString()) as Partial; + handle_message(message) + } catch (_err) { + return; + } }); ws.send(JSON.stringify({ @@ -12,3 +37,55 @@ wss.on('connection', function connection(ws) { message: "Hello" })); }); + +function exhaustiveMatch(_never: never) { + return; +} + +function handle_message(message: Partial) { + console.log(JSON.stringify(message)); + if (message.type === undefined) return; + + switch (message.type) { + case WebSocketMessageType.AddHyperdeck: + if (message.id === undefined) return; + if (message.ip === undefined) return; + if (message.port === undefined) return; + if (isNaN(message.port)) return; + if (message.port <= 0) return; + + console.log("Adding hyperdeck"); + + const newHyperdeck = new Hyperdeck() + + // hyperdecks.set(message.id, { + // ip: message.ip, + // port: message.port, + // hyperdeck: newHyperdeck + // }); + + newHyperdeck.on('connected', (info) => { + console.log(JSON.stringify(info)) + + newHyperdeck.sendCommand(new Commands.TransportInfoCommand()).then((transportInfo) => { + console.log(JSON.stringify(transportInfo)) + }) + }) + + newHyperdeck.on('notify.slot', function (state) { + console.log(JSON.stringify(state)) // catch the slot state change. + }) + newHyperdeck.on('notify.transport', function (state) { + console.log(JSON.stringify(state)) // catch the transport state change. + }) + newHyperdeck.on('error', (err) => { + console.log('Hyperdeck error', JSON.stringify(err)) + }) + + newHyperdeck.connect(message.ip, message.port) + + break; + default: + exhaustiveMatch(message.type) + } +} diff --git a/monitor/src/api.rs b/monitor/src/api.rs new file mode 100644 index 0000000..07b1859 --- /dev/null +++ b/monitor/src/api.rs @@ -0,0 +1,172 @@ +use axum::extract::ws::Message; +use axum::extract::{State, WebSocketUpgrade}; +use axum::response::Html; +use axum::{ + body::Bytes, + extract::Path, + http::{header, HeaderValue, Method}, + response::IntoResponse, + routing::get, + Router, +}; +use message::{ClientRequest, HyperdeckMonitorState, ServerEvent}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + net::{Ipv4Addr, SocketAddr}, + sync::Arc, + time::Duration, +}; +use tokio::sync::{Mutex, RwLock}; +use tower::ServiceBuilder; +use tower_http::timeout::TimeoutLayer; +use tower_http::ServiceBuilderExt; +use tower_http::{ + cors::{Any, CorsLayer}, + trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}, + LatencyUnit, +}; +use tracing::info; +use uuid::Uuid; + +pub mod message; +mod ws; + +#[derive(Debug, Clone)] +pub struct Client { + pub sender: Option>, +} + +type Clients = Arc>>; + +pub async fn initialize_api( + mut state_rx: tokio::sync::broadcast::Receiver, + client_request_tx: tokio::sync::mpsc::UnboundedSender, +) { + info!("Initializing API"); + + let clients: Clients = Default::default(); + + let state = Arc::new(RwLock::new(state_rx.recv().await.unwrap())); + + let state_clients = clients.clone(); + let state_loop = state.clone(); + tokio::spawn(async move { + loop { + if let Ok(hyperdeck_monitor_state) = state_rx.recv().await { + let mut state = state_loop.write().await; + *state = hyperdeck_monitor_state.clone(); + + let clients = state_clients.lock().await; + let state_json = serde_json::to_string(&ServerEvent::HyperdeckMonitorState( + hyperdeck_monitor_state.into(), + )) + .unwrap(); + for (_, client) in clients.iter() { + if let Some(sender) = &client.sender { + let message: Message = Message::Text(state_json.clone()); + let _ = sender.send(message); + } + } + } + } + }); + + let app_state = AppState { + state, + client_request_tx, + clients, + port: 9681, + }; + + let addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, app_state.port)); + info!("Listening on {}", addr); + // TODO: This could fail, need to figure out how to get a result from this + let _ = axum::Server::bind(&addr) + .serve(app(app_state).into_make_service()) + .await; +} + +#[derive(Clone)] +struct AppState { + state: Arc>, + client_request_tx: tokio::sync::mpsc::UnboundedSender, + clients: Clients, + port: u16, +} + +fn app(state: AppState) -> Router { + let sensitive_headers: Arc<[_]> = vec![header::AUTHORIZATION, header::COOKIE].into(); + let middleware = ServiceBuilder::new() + // Mark the `Authorization` and `Cookie` headers as sensitive so it doesn't show in logs + .sensitive_request_headers(sensitive_headers.clone()) + // Add high level tracing/logging to all requests + .layer( + TraceLayer::new_for_http() + .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| { + tracing::trace!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk") + }) + .make_span_with(DefaultMakeSpan::new().include_headers(true)) + .on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)), + ) + .sensitive_response_headers(sensitive_headers) + // Set a timeout + .layer(TimeoutLayer::new(Duration::from_secs(10))) + // Box the response body so it implements `Default` which is required by axum + .map_response_body(axum::body::boxed) + // Compress responses + .compression() + // Set a `Content-Type` if there isn't one already. + .insert_response_header_if_not_present( + header::CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ); + + let cors = CorsLayer::new() + .allow_methods(vec![ + Method::GET, + Method::POST, + Method::PUT, + Method::DELETE, + Method::OPTIONS, + ]) + .allow_headers(Any) + .allow_origin(Any) + .allow_credentials(false); + + Router::new() + .route("/", get(get_index)) + .route("/ws", get(upgrade_ws)) + .layer(middleware) + .layer(cors) + .with_state(state) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct WebSocketUpgradeRequest {} + +async fn get_index() -> Html { + Html(format!("Hello!")) +} + +#[axum::debug_handler] +async fn upgrade_ws(state: State, ws: WebSocketUpgrade) -> impl IntoResponse { + info!("New client websocket connection"); + let client_id = uuid::Uuid::new_v4(); + state + .clients + .lock() + .await + .insert(client_id.clone(), Client { sender: None }); + let client = state.clients.lock().await.get(&client_id).cloned().unwrap(); + ws.on_upgrade(move |socket| { + ws::client_connection( + state.client_request_tx.clone(), + socket, + client_id, + state.state.clone(), + state.clients.clone(), + client, + ) + }) +} diff --git a/monitor/src/api/message.rs b/monitor/src/api/message.rs new file mode 100644 index 0000000..b4ddf48 --- /dev/null +++ b/monitor/src/api/message.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "type")] +pub enum ClientRequest { + AddHyperdeck(AddHyperdeckRequest), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AddHyperdeckRequest { + pub name: String, + pub ip: String, + pub port: u16, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "type")] +pub enum ServerEvent { + HyperdeckMonitorState(HyperdeckMonitorState), +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct HyperdeckMonitorState { + pub hyperdecks: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HyperdeckState { + pub name: String, + pub ip: String, + pub port: u16, +} diff --git a/monitor/src/api/ws.rs b/monitor/src/api/ws.rs new file mode 100644 index 0000000..408b6db --- /dev/null +++ b/monitor/src/api/ws.rs @@ -0,0 +1,84 @@ +use std::{future, sync::Arc}; + +use super::message::{ClientRequest, HyperdeckMonitorState}; +use crate::api::ServerEvent; +use axum::extract::ws::{Message, WebSocket}; +use futures::StreamExt; +use tokio::sync::RwLock; +use tokio_stream::wrappers::BroadcastStream; +use tracing::{debug, error, log::info}; +use uuid::Uuid; + +use super::{Client, Clients}; + +pub async fn client_connection( + client_request_tx: tokio::sync::mpsc::UnboundedSender, + ws: WebSocket, + id: Uuid, + state: Arc>, + clients: Clients, + mut client: Client, +) { + let (client_ws_sender, mut client_ws_rcv) = ws.split(); + let (client_sender, client_rcv) = tokio::sync::broadcast::channel::(10); + let client_rcv = BroadcastStream::new(client_rcv); + + tokio::task::spawn( + client_rcv + .filter(|msg| future::ready(msg.is_ok())) + .map(|msg| Ok(msg.unwrap())) + .forward(client_ws_sender), + ); + + let current_state = state.read().await.clone(); + let state_json = + serde_json::to_string(&ServerEvent::HyperdeckMonitorState(current_state.into())).unwrap(); + client_sender.send(Message::Text(state_json.clone())).ok(); + + client.sender = Some(client_sender); + clients.lock().await.insert(id, client); + + info!("{} connected", id); + + while let Some(result) = client_ws_rcv.next().await { + let msg = match result { + Ok(msg) => msg, + Err(e) => { + error!("error resolving ws message for id: {}: {}", id.clone(), e); + break; + } + }; + client_msg(client_request_tx.clone(), &id, msg).await; + } + + clients.lock().await.remove(&id); + info!("{} disconnected", id); +} + +async fn client_msg( + client_request_tx: tokio::sync::mpsc::UnboundedSender, + id: &Uuid, + msg: Message, +) { + debug!("received message from {}: {:?}", id, msg); + let message = match msg.into_text() { + Ok(v) => v, + Err(err) => { + error!("error: {:?}", err); + return; + } + }; + + if message == "ping" || message == "ping\n" { + return; + } + + let client_request: super::message::ClientRequest = match serde_json::from_str(&message) { + Ok(v) => v, + Err(_) => { + return; + } + }; + + let _ = client_request_tx.send(client_request); +} diff --git a/monitor/src/main.rs b/monitor/src/main.rs index 86da7e9..f9b5709 100644 --- a/monitor/src/main.rs +++ b/monitor/src/main.rs @@ -1,5 +1,6 @@ -use std::{net::IpAddr, time::Duration}; +use std::{process::Stdio, time::Duration}; +use api::message::{AddHyperdeckRequest, ClientRequest, HyperdeckMonitorState, HyperdeckState}; use color_eyre::Report; use futures_util::{ pin_mut, select, @@ -9,9 +10,14 @@ use futures_util::{ use serde::{Deserialize, Serialize}; use tokio::net::TcpStream; use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream}; -use tokio_util::sync::CancellationToken; +use tokio_util::{ + codec::{FramedRead, LinesCodec}, + sync::CancellationToken, +}; use tracing_subscriber::EnvFilter; +mod api; + #[tokio::main] async fn main() { setup_logging().expect("Failed to setup logging"); @@ -20,35 +26,169 @@ async fn main() { let cancel = CancellationToken::new(); let node_process = run_node_process(cancel.clone()).fuse(); - let (ws_message_tx, ws_message_rx) = tokio::sync::mpsc::unbounded_channel(); - let (commands_tx, commands_rx) = tokio::sync::mpsc::unbounded_channel(); + let (node_ws_message_tx, node_ws_message_rx) = tokio::sync::mpsc::unbounded_channel(); + let (node_commands_tx, node_commands_rx) = tokio::sync::mpsc::unbounded_channel(); let state = AppState::default(); - let ws_process = talk_to_node_ws(state, ws_message_tx, commands_rx, cancel.clone()).fuse(); + let node_ws_communication = + talk_to_node_ws(state, node_ws_message_tx, node_commands_rx, cancel.clone()).fuse(); + + let (state_tx, state_rx) = tokio::sync::broadcast::channel(1); + let (client_request_tx, client_request_rx) = tokio::sync::mpsc::unbounded_channel(); + let api = api::initialize_api(state_rx, client_request_tx).fuse(); + + let hyperdeck_monitor = run( + node_commands_tx, + node_ws_message_rx, + state_tx, + client_request_rx, + cancel.clone(), + ) + .fuse(); pin_mut!(node_process); - pin_mut!(ws_process); + pin_mut!(node_ws_communication); + pin_mut!(api); + pin_mut!(hyperdeck_monitor); select! { _ = node_process => {}, - _ = ws_process => {}, + _ = node_ws_communication => {}, + _ = api => {}, + _ = hyperdeck_monitor => {}, _ = cancel.cancelled().fuse() => {} }; cancel.cancel(); } +async fn run( + mut node_commands_tx: tokio::sync::mpsc::UnboundedSender, + mut node_ws_message_rx: tokio::sync::mpsc::UnboundedReceiver, + mut state_tx: tokio::sync::broadcast::Sender, + mut client_request_rx: tokio::sync::mpsc::UnboundedReceiver, + cancel: CancellationToken, +) { + let mut state = HyperdeckMonitorState::default(); + let _ = state_tx.send(state.clone()); + + let mut ping_interval = tokio::time::interval(Duration::from_millis(500)); + ping_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + + while !cancel.is_cancelled() { + let state_modified = select! { + _ = ping_interval.tick().fuse() => { + // TODO: Ping node, check it's still alive + false + }, + message_from_node = node_ws_message_rx.recv().fuse() => { + if let Some(msg) = message_from_node { + handle_message_from_node(msg, &mut node_commands_tx, &mut state).await + } else { + false + } + }, + message_from_client = client_request_rx.recv().fuse() => { + if let Some(msg) = message_from_client { + handle_message_from_client(msg, &mut node_commands_tx, &mut state).await + } else { + false + } + } + }; + + if state_modified { + let _ = state_tx.send(state.clone()); + } + } +} + +async fn handle_message_from_node( + msg: NodeWsMessageReceived, + node_commands_tx: &mut tokio::sync::mpsc::UnboundedSender, + state: &mut HyperdeckMonitorState, +) -> bool { + match msg { + NodeWsMessageReceived::Log { message } => { + tracing::info!("[NODE] {message}"); + false + } + } +} + +async fn handle_message_from_client( + msg: ClientRequest, + node_commands_tx: &mut tokio::sync::mpsc::UnboundedSender, + state: &mut HyperdeckMonitorState, +) -> bool { + match msg { + ClientRequest::AddHyperdeck(AddHyperdeckRequest { name, ip, port }) => { + tracing::info!("Adding hyperdeck"); + let id = uuid::Uuid::new_v4(); + state.hyperdecks.insert( + id.to_string(), + HyperdeckState { + name, + ip: ip.clone(), + port, + }, + ); + let _ = node_commands_tx.send(NodeWsCommand::AddHyperdeck(AddHyperdeckCommand { + id: id.to_string(), + ip, + port, + })); + true + } + } +} + async fn run_node_process(cancel: CancellationToken) { while !cancel.is_cancelled() { + // Back-off in case we are immediately crashing in a loop. + tokio::time::sleep(Duration::from_secs(1)).await; + let result = tokio::process::Command::new("node") .arg("monitor/index.js") - .output() - .await; - if let Ok(output) = result { - if !output.status.success() { - let err = String::from_utf8(output.stderr).unwrap_or("Unknown".to_string()); - tracing::error!("Node process exited with error: {}", err); - // Back-off in case we are immediately crashing in a loop. - tokio::time::sleep(Duration::from_secs(1)).await; + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn(); + match result { + Ok(mut child_process) => { + let Some(raw_stdout) = child_process.stdout.take() else { + let _ = child_process.kill().await; + continue; + }; + + let Some(raw_stderr) = child_process.stderr.take() else { + let _ = child_process.kill().await; + continue; + }; + + let mut stdout = FramedRead::new(raw_stdout, LinesCodec::new()) + .map(|data| data.expect("Could not read stdout")); + let mut stderr = FramedRead::new(raw_stderr, LinesCodec::new()) + .map(|data| data.expect("Could not read stderr")); + + while !cancel.is_cancelled() { + select! { + line = stdout.next().fuse() => { + if let Some(line) = line { + tracing::info!("[NODE] {line}"); + } + } + line = stderr.next().fuse() => { + if let Some(line) = line { + tracing::error!("[NODE] {line}"); + } + } + } + } + + let _ = child_process.kill().await; + } + Err(err) => { + tracing::error!("Error running Node child process: {err}"); } } } @@ -56,9 +196,15 @@ async fn run_node_process(cancel: CancellationToken) { #[derive(Default)] struct AppState {} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] enum NodeWsCommand { + #[serde(rename = "ping")] Ping, + #[serde(rename = "add_hyperdeck")] AddHyperdeck(AddHyperdeckCommand), + #[serde(rename = "remove_hyperdeck")] RemoveHyperdeck(RemoveHyperdeckCommand), } @@ -69,16 +215,18 @@ enum NodeWsMessageReceived { Log { message: String }, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct AddHyperdeckCommand { - ip: IpAddr, + id: String, + ip: String, + port: u16, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct RemoveHyperdeckCommand { - ip: IpAddr, + id: String, } async fn talk_to_node_ws( @@ -126,28 +274,13 @@ async fn handle_outbound_messages( mut socket_tx: SplitSink>, Message>, ) { while let Some(command) = commands_rx.recv().await { - match command { - NodeWsCommand::Ping => { - let _ = socket_tx - .send(tokio_tungstenite::tungstenite::Message::Ping(vec![])) - .await; - } - NodeWsCommand::AddHyperdeck(command) => { - let _ = socket_tx - .send(tokio_tungstenite::tungstenite::Message::Text( - serde_json::to_string(&command) - .expect("Could not serialize AddHyperdeck command"), - )) - .await; - } - NodeWsCommand::RemoveHyperdeck(command) => { - let _ = socket_tx - .send(tokio_tungstenite::tungstenite::Message::Text( - serde_json::to_string(&command) - .expect("Could not serialize RemoveHyperdeck command"), - )) - .await; - } + if let Err(err) = socket_tx + .send(tokio_tungstenite::tungstenite::Message::Text( + serde_json::to_string(&command).expect("Could not serialize command"), + )) + .await + { + tracing::error!("Error sending command to Node proccess: {err}"); } } } @@ -161,11 +294,7 @@ async fn handle_inbound_messages( match message { Ok(tokio_tungstenite::tungstenite::Message::Text(text)) => { if let Ok(received) = serde_json::from_str::(&text) { - match received { - NodeWsMessageReceived::Log { message } => { - tracing::info!("Message from Node process: {message}"); - } - } + let _ = ws_message_tx.send(received); } } Ok(tokio_tungstenite::tungstenite::Message::Pong(_)) => {}