diff --git a/.gitignore b/.gitignore index ea8c4bf..955b37c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/store diff --git a/Cargo.lock b/Cargo.lock index 752db5d..c24344e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,7 +180,7 @@ dependencies = [ "serde_urlencoded", "smallvec", "socket2", - "time", + "time 0.3.17", "url", ] @@ -237,6 +237,15 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -255,6 +264,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.3" @@ -285,6 +303,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + [[package]] name = "bytes" version = "1.3.0" @@ -315,6 +339,74 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "clap" +version = "4.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -328,10 +420,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" dependencies = [ "percent-encoding", - "time", + "time 0.3.17", "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -360,6 +458,50 @@ dependencies = [ "typenum", ] +[[package]] +name = "cxx" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "decky_api" version = "0.1.0" @@ -380,13 +522,22 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer", + "block-buffer 0.10.3", "crypto-common", ] @@ -399,6 +550,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "flate2" version = "1.0.25" @@ -472,7 +644,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -500,6 +672,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -509,6 +687,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.8" @@ -532,6 +725,30 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "idna" version = "0.3.0" @@ -552,6 +769,28 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.4" @@ -567,6 +806,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -579,6 +827,21 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" + [[package]] name = "local-channel" version = "0.1.3" @@ -645,7 +908,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -655,7 +918,35 @@ version = "0.1.0" dependencies = [ "actix-cors", "actix-web", + "bytes", + "chrono", + "clap", "decky_api", + "log", + "serde", + "serde_json", + "sha256", + "simplelog", + "ureq", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", ] [[package]] @@ -664,7 +955,16 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ "libc", ] @@ -674,6 +974,18 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + [[package]] name = "parking_lot" version = "0.12.1" @@ -727,6 +1039,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.47" @@ -801,6 +1137,21 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -810,6 +1161,32 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.11" @@ -822,6 +1199,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "semver" version = "1.0.14" @@ -879,7 +1272,30 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.6", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha256" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e334db67871c14c18fc066ad14af13f9fdf5f9a91c61af432d1e3a39c8c6a141" +dependencies = [ + "hex", + "sha2", ] [[package]] @@ -891,6 +1307,17 @@ dependencies = [ "libc", ] +[[package]] +name = "simplelog" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786" +dependencies = [ + "log", + "termcolor", + "time 0.3.17", +] + [[package]] name = "slab" version = "0.4.7" @@ -916,6 +1343,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.105" @@ -927,6 +1366,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.17" @@ -934,6 +1393,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", + "libc", + "num_threads", "serde", "time-core", "time-macros", @@ -1049,6 +1510,37 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" +dependencies = [ + "base64", + "chunked_transfer", + "flate2", + "log", + "once_cell", + "rustls", + "serde", + "serde_json", + "url", + "webpki", + "webpki-roots", +] + [[package]] name = "url" version = "2.3.1" @@ -1066,12 +1558,101 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +dependencies = [ + "webpki", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1088,6 +1669,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 9f82f13..c7b8e16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,28 @@ edition = "2021" [dependencies] decky_api = { version = "0.1.0", path = "./decky_api" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +bytes = "1.3" +sha256 = "1.1" + +# logging +log = "0.4" +simplelog = "0.12" # web framework actix-web = "4.2" actix-cors = "0.6" +# proxy storage impl +ureq = { version = "2.5", features = ["json"] } + +# cache storage impl +chrono = { version = "0.4" } + +# cli +clap = { version = "4.0", features = ["derive"] } + [workspace] include = [ "decky_api" diff --git a/decky_api/Cargo.toml b/decky_api/Cargo.toml index b5cab14..f2457bb 100644 --- a/decky_api/Cargo.toml +++ b/decky_api/Cargo.toml @@ -7,4 +7,3 @@ edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } -#serde_json = { version = "1.0" } diff --git a/decky_api/src/store_plugin.rs b/decky_api/src/store_plugin.rs index 24ebaf7..6298068 100644 --- a/decky_api/src/store_plugin.rs +++ b/decky_api/src/store_plugin.rs @@ -1,32 +1,16 @@ -/*export interface StorePluginVersion { - name: string; - hash: string; - artifact: string | undefined | null; -} - -export interface StorePlugin { - id: number; - name: string; - versions: StorePluginVersion[]; - author: string; - description: string; - tags: string[]; - image_url: string; -}*/ - use serde::{Serialize, Deserialize}; pub type StorePluginList = Vec; #[derive(Serialize, Deserialize, Clone)] pub struct StorePlugin { - id: usize, - name: String, - versions: Vec, - author: String, - description: String, - tags: Vec, - image_url: String, + pub id: usize, + pub name: String, + pub versions: Vec, + pub author: String, + pub description: String, + pub tags: Vec, + pub image_url: String, } #[derive(Serialize, Deserialize, Clone)] diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..f6e0ab2 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,43 @@ +use clap::{Parser, Subcommand, Args}; + +/// An alternative plugin store +#[derive(Parser, Debug, Clone)] +#[command(author, version, about, long_about = None, propagate_version = true)] +pub struct CliArgs { + /// Proxy offerings from another store + #[arg(name = "store", long)] + pub proxy_store: Option, + /// Proxy main store offerings + #[arg(name = "proxy", short, long)] + pub proxy: bool, + /// Cache results for a period + #[arg(name = "cache", long)] + pub cache_duration: Option, + /// Storage adapter + #[command(subcommand)] + pub storage: StorageArgs, +} + +impl CliArgs { + pub fn get() -> Self { + Self::parse() + } +} + +#[derive(Subcommand, Debug, Clone)] +pub enum StorageArgs { + /// Use default storage settings (filesystem) + Default, + /// Use the filesystem + Filesystem(FilesystemArgs), +} + +#[derive(Args, Debug, Clone)] +pub struct FilesystemArgs { + #[arg(name = "folder", default_value_t = {"./store".into()})] + pub root: String, + #[arg(name = "domain", default_value_t = {"http://localhost:22252".into()})] + pub domain_root: String, + #[arg(name = "stats", long)] + pub enable_stats: bool, +} diff --git a/src/main.rs b/src/main.rs index f5306ea..cecb22b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,12 @@ +mod cli; mod consts; mod not_decky; +mod storage; -use actix_web::{get, App, HttpResponse, HttpServer, Responder}; +use crate::storage::IStorageWrap; + +use actix_web::{get, web, App, HttpResponse, HttpServer, Responder}; +use simplelog::{LevelFilter, WriteLogger}; #[get("/version_info")] async fn hello() -> impl Responder { @@ -10,17 +15,45 @@ async fn hello() -> impl Responder { #[actix_web::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| { + let args = cli::CliArgs::get(); + let log_filepath = std::path::Path::new("/tmp").join(format!("{}.log", consts::PACKAGE_NAME)); + WriteLogger::init( + LevelFilter::Debug, + Default::default(), + std::fs::File::create(&log_filepath).unwrap(), + ) + .unwrap(); + + println!("Logging to {}", log_filepath.display()); + + HttpServer::new(move || { let cors = actix_cors::Cors::default() .allowed_origin("https://steamloopback.host") .allow_any_header() .expose_any_header(); + let storage_data: Box = match &args.storage { + cli::StorageArgs::Default => storage::FileStorage::new( + "./store".into(), + "http://192.168.0.128:22252".into(), + true, + ).wrap(args.clone()), + cli::StorageArgs::Filesystem(fs) => storage::FileStorage::new( + fs.root.clone().into(), + fs.domain_root.clone().into(), + fs.enable_stats, + ).wrap(args.clone()), + }; + App::new() .wrap(cors) + .app_data(web::Data::new(storage_data)) .service(hello) .service(not_decky::decky_index) .service(not_decky::decky_plugins) + .service(not_decky::decky_artifact) + .service(not_decky::decky_image) + .service(not_decky::decky_statistics) }) .bind(("0.0.0.0", 22252))? .run() diff --git a/src/not_decky/artifact.rs b/src/not_decky/artifact.rs new file mode 100644 index 0000000..479345b --- /dev/null +++ b/src/not_decky/artifact.rs @@ -0,0 +1,9 @@ +use actix_web::{get, web, Responder}; + +use crate::storage::IStorage; + +#[get("/plugins/{name}/{version}/{hash}.zip")] +pub async fn decky_artifact(data: web::Data>, path: web::Path<(String, String, String)>) -> actix_web::Result { + let zip = data.get_artifact(&path.0, &path.1, &path.2).map_err(|e| actix_web::error::ErrorNotFound(e.to_string()))?; + Ok(zip) +} diff --git a/src/not_decky/image.rs b/src/not_decky/image.rs new file mode 100644 index 0000000..b4c945e --- /dev/null +++ b/src/not_decky/image.rs @@ -0,0 +1,9 @@ +use actix_web::{get, web, Responder}; + +use crate::storage::IStorage; + +#[get("/plugins/{name}.png")] +pub async fn decky_image(data: web::Data>, path: web::Path) -> actix_web::Result { + let zip = data.get_image(&path).map_err(|e| actix_web::error::ErrorNotFound(e.to_string()))?; + Ok(zip) +} diff --git a/src/not_decky/mod.rs b/src/not_decky/mod.rs index 653179a..b359f42 100644 --- a/src/not_decky/mod.rs +++ b/src/not_decky/mod.rs @@ -1,5 +1,11 @@ +mod artifact; +mod image; mod index; mod plugins; +mod stats; +pub use artifact::decky_artifact; +pub use image::decky_image; pub use index::decky_index; pub use plugins::decky_plugins; +pub use stats::decky_statistics; diff --git a/src/not_decky/plugins.rs b/src/not_decky/plugins.rs index 944db0a..3ad1e95 100644 --- a/src/not_decky/plugins.rs +++ b/src/not_decky/plugins.rs @@ -1,9 +1,11 @@ use decky_api::StorePluginList; -use actix_web::{get, web::Json, Responder}; +use actix_web::{get, web, Responder}; + +use crate::storage::IStorage; #[get("/plugins")] -pub async fn decky_plugins() -> impl Responder { - let plugins: Vec = Vec::new(); - Json(plugins) +pub async fn decky_plugins(data: actix_web::web::Data>) -> impl Responder { + let plugins: StorePluginList = data.plugins(); + web::Json(plugins) } diff --git a/src/not_decky/stats.rs b/src/not_decky/stats.rs new file mode 100644 index 0000000..1842974 --- /dev/null +++ b/src/not_decky/stats.rs @@ -0,0 +1,11 @@ +use std::collections::HashMap; + +use actix_web::{get, web, Responder}; + +use crate::storage::IStorage; + +#[get("/stats")] +pub async fn decky_statistics(data: actix_web::web::Data>) -> impl Responder { + let plugins: HashMap = data.get_statistics(); + web::Json(plugins) +} diff --git a/src/storage/cache.rs b/src/storage/cache.rs new file mode 100644 index 0000000..9cb8448 --- /dev/null +++ b/src/storage/cache.rs @@ -0,0 +1,102 @@ +use std::sync::{RwLock, atomic::{AtomicI64, Ordering}}; +use std::collections::HashMap; + +use decky_api::StorePluginList; +use bytes::Bytes; +use chrono::Utc; + +use super::IStorage; + +struct Cached { + expiry: AtomicI64, + value: RwLock, + ttl: i64 +} + +impl Cached { + fn new(value: T, duration: i64) -> Self { + Self { + expiry: AtomicI64::new(Utc::now().timestamp() + duration), + value: RwLock::new(value), + ttl: duration, + } + } + + fn get T>(&self, getter: F) -> T { + let now = Utc::now().timestamp(); + let expiry = self.expiry.load(Ordering::Acquire); + if expiry < now { + // refresh required + let new_value = getter(); + let new_expiry = now + self.ttl; + let mut write_lock = self.value.write().expect("Failed to acquire cache write lock"); + self.expiry.store(new_expiry, Ordering::Release); + *write_lock = new_value.clone(); + new_value + } else { + // cache is still good + self.value.read().expect("Failed to acquire cache read lock").clone() + } + } + + fn refresh(&self, new_value: T) { + let new_expiry = Utc::now().timestamp() + self.ttl; + let mut write_lock = self.value.write().expect("Failed to acquire cache write lock"); + self.expiry.store(new_expiry, Ordering::Release); + *write_lock = new_value; + } +} + +pub struct CachedStorage { + fallback: S, + plugins_cache: Cached, + statistics_cache: Cached>, + artifacts_cache: Cached>, + images_cache: Cached>, +} + +impl CachedStorage { + pub fn new(duration: i64, inner: S) -> Self { + Self { + plugins_cache: Cached::new(inner.plugins(), duration), + statistics_cache: Cached::new(inner.get_statistics(), duration), + artifacts_cache: Cached::new(HashMap::new(), duration), + images_cache: Cached::new(HashMap::new(), duration), + fallback: inner, + } + } +} + +impl IStorage for CachedStorage { + fn plugins(&self) -> StorePluginList { + self.plugins_cache.get(|| self.fallback.plugins()) + } + + fn get_artifact(&self, name: &str, version: &str, hash: &str) -> Result { + let mut cached = self.artifacts_cache.get(|| HashMap::new()); + if let Some(bytes) = cached.get(hash) { + Ok(bytes.to_owned()) + } else { + let new_artifact = self.fallback.get_artifact(name, version, hash)?; + cached.insert(hash.to_owned(), new_artifact.clone()); + self.artifacts_cache.refresh(cached); + Ok(new_artifact) + } + } + + fn get_image(&self, name: &str) -> Result { + let mut cached = self.images_cache.get(|| HashMap::new()); + if let Some(bytes) = cached.get(name) { + Ok(bytes.to_owned()) + } else { + let new_image = self.fallback.get_image(name)?; + cached.insert(name.to_owned(), new_image.clone()); + self.images_cache.refresh(cached); + Ok(new_image) + } + } + + fn get_statistics(&self) -> std::collections::HashMap { + self.statistics_cache.get(|| self.fallback.get_statistics()) + } +} diff --git a/src/storage/filesystem.rs b/src/storage/filesystem.rs new file mode 100644 index 0000000..a7a00ae --- /dev/null +++ b/src/storage/filesystem.rs @@ -0,0 +1,197 @@ +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::RwLock; +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; + +use decky_api::{StorePlugin, StorePluginList, StorePluginVersion}; + +use serde::{Serialize, Deserialize}; + +use super::IStorage; + +#[derive(Serialize, Deserialize, Clone)] +pub struct PluginMetadata { + id: usize, + author: String, + description: String, + tags: Vec, +} + +impl PluginMetadata { + fn complete(self, name: String, versions: Vec, image: String) -> StorePlugin { + StorePlugin { + id: self.id, + name: name, + versions: versions, + author: self.author, + description: self.description, + tags: self.tags, + image_url: image, + } + } +} + +pub struct FileStorage { + stats: Option>>, // TODO collect hit counts on actions + root: PathBuf, + domain_root: String, +} + +impl FileStorage { + pub fn new(root: PathBuf, domain_root: String, enable_stats: bool) -> Self { + Self { + root: root, + domain_root: domain_root, + stats: if enable_stats { Some(RwLock::new(HashMap::new())) } else { None }, + } + } + + fn plugins_path(&self) -> PathBuf { + self.root.join("plugins") + } + + fn plugin_json_path(&self, plugin_root: impl AsRef) -> PathBuf { + plugin_root.as_ref().join("plugin.json") + } + + fn plugin_root_path(&self, plugin_name: &str) -> PathBuf { + self.plugins_path().join(plugin_name) + } + + fn plugin_artifact_path(&self, plugin_name: &str, version_name: &str, _hash: &str) -> PathBuf { + self.plugin_root_path(plugin_name) + .join(format!("{}.zip", version_name)) + } + + fn plugin_image_path(&self, plugin_name: &str) -> PathBuf { + self.plugin_root_path(plugin_name) + .join(format!("image.png")) + } + + fn read_all_plugins(&self) -> std::io::Result { + let plugins = self.plugins_path(); + let dir_reader = plugins.read_dir()?; + let mut results = Vec::with_capacity(dir_reader.size_hint().1.unwrap_or(32)); + for entry in dir_reader { + let entry = entry?; + if entry.file_type()?.is_dir() { + results.push(self.read_single_plugin(&entry.path())?); + } + } + // build stats counters + if let Some(stats) = &self.stats { + let mut lock = stats.write().expect("Couldn't acquire stats write lock"); + for plugin in &results { + for version in &plugin.versions { + if !lock.contains_key(&version.hash) { + lock.insert(version.hash.clone(), AtomicU64::new(0)); + } + } + } + } + Ok(results) + } + + fn read_single_plugin(&self, path: &PathBuf) -> std::io::Result { + let plugin_name = path.file_name().unwrap().to_string_lossy().into_owned(); + let json_path = self.plugin_json_path(path); + let plugin_info: PluginMetadata = match serde_json::from_reader(File::open(&json_path)?) { + Ok(x) => x, + Err(e) => { + log::error!("`{}` JSON err: {}", json_path.display(), e); + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)) + } + }; + // find plugin versions + let dir_reader = path.read_dir()?; + let mut versions = Vec::with_capacity(dir_reader.size_hint().1.unwrap_or(4)); + for entry in dir_reader { + let entry = entry?; + if entry.file_type()?.is_file() { + let entry_path = entry.path(); + let extension = entry_path.extension().unwrap().to_string_lossy().into_owned(); + if extension == "zip" { + let version_name = entry_path.file_stem().unwrap().to_string_lossy().into_owned(); + let hash_str = sha256::try_digest(entry_path.as_ref())?; + let artifact_url = format!("{}/plugins/{}/{}/{}.zip", self.domain_root, plugin_name, version_name, hash_str); + versions.push(StorePluginVersion { + name: version_name, + hash: hash_str, + artifact: Some(artifact_url) + }); + } + } + } + let image_url = format!("{}/plugins/{}.png", self.domain_root, plugin_name); + Ok( + plugin_info.complete( + plugin_name, + versions, + image_url, + ) + ) + } +} + +impl IStorage for FileStorage { + fn plugins(&self) -> StorePluginList { + match self.read_all_plugins() { + Err(e) => { + log::error!("Plugins read error: {}", e); + vec![] + }, + Ok(x) => x + } + } + + fn get_artifact(&self, name: &str, version: &str, hash: &str) -> Result { + let path = self.plugin_artifact_path(name, version, hash); + log::debug!("Opening artifact path: {}", path.display()); + let mut file = File::open(path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + if let Some(stats) = &self.stats { + let lock = stats.read().expect("Failed to acquire stats read lock"); + if let Some(counter) = lock.get(hash) { + counter.fetch_add(1, Ordering::SeqCst); + } + } + Ok(buffer.into()) + } + + fn get_image(&self, name: &str) -> Result { + let path = self.plugin_image_path(name); + log::debug!("Opening image path: {}", path.display()); + let mut file = File::open(path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + Ok(buffer.into()) + } + + fn get_statistics(&self) -> std::collections::HashMap { + if let Some(stats) = &self.stats { + if let Ok(plugins) = self.read_all_plugins() { + let lock = stats.read().expect("Failed to acquire stats read lock"); + let mut map = std::collections::HashMap::with_capacity(64); + for plugin in plugins { + let mut total = 0; + for version in plugin.versions { + if let Some(count) = lock.get(&version.hash) { + let count_val = count.load(Ordering::SeqCst); + total += count_val; + map.insert(format!("{} {}", plugin.name, version.name), count_val); + } + } + map.insert(format!("{}", plugin.name), total); + } + map + } else { + std::collections::HashMap::with_capacity(0) + } + } else { + std::collections::HashMap::with_capacity(0) + } + } +} diff --git a/src/storage/interface.rs b/src/storage/interface.rs new file mode 100644 index 0000000..86bb0f8 --- /dev/null +++ b/src/storage/interface.rs @@ -0,0 +1,50 @@ +pub trait IStorage { + fn plugins(&self) -> decky_api::StorePluginList; + + fn get_artifact(&self, _name: &str, _version: &str, _hash: &str) -> Result { + Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Artifact downloading not supported")) + } + + fn get_image(&self, _name: &str) -> Result { + Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Image downloading not supported")) + } + + fn get_statistics(&self) -> std::collections::HashMap { + std::collections::HashMap::with_capacity(0) + } +} + +pub trait IStorageWrap: Sized + IStorage { + fn wrap(self, conf: crate::cli::CliArgs) -> Box; +} + +impl IStorageWrap for X { + fn wrap(self, conf: crate::cli::CliArgs) -> Box { + let proxy = if let Some(store) = conf.proxy_store { + Some(store) + } else if conf.proxy { + Some("https://plugins.deckbrew.xyz".to_owned()) + } else { + None + }; + match (proxy, conf.cache_duration) { + (Some(proxy), Some(cache_dur)) => Box::new( + super::CachedStorage::new( + cache_dur, + super::ProxiedStorage::new(proxy, self), + ) + ), + (Some(proxy), None) => Box::new(super::ProxiedStorage::new(proxy, self)), + (None, Some(cache_dur)) => Box::new(super::CachedStorage::new(cache_dur, self)), + (None, None) => Box::new(self), + } + } +} + +pub struct EmptyStorage; + +impl IStorage for EmptyStorage { + fn plugins(&self) -> decky_api::StorePluginList { + Vec::new() + } +} diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 0000000..73da24b --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,9 @@ +mod cache; +mod filesystem; +mod interface; +mod proxy; + +pub use cache::CachedStorage; +pub use filesystem::FileStorage; +pub use interface::{IStorage, EmptyStorage, IStorageWrap}; +pub use proxy::ProxiedStorage; diff --git a/src/storage/proxy.rs b/src/storage/proxy.rs new file mode 100644 index 0000000..a013a12 --- /dev/null +++ b/src/storage/proxy.rs @@ -0,0 +1,74 @@ +use decky_api::{StorePluginList, StorePluginVersion}; + +use super::IStorage; + +pub struct ProxiedStorage { + store_url: String, + fallback: S, + agent: ureq::Agent, +} + +impl ProxiedStorage { + pub fn new(target_store: String, inner: S) -> Self { + Self { + store_url: target_store, + fallback: inner, + agent: ureq::Agent::new(), + } + } + + fn plugins_url(&self) -> String { + format!("{}/plugins", self.store_url) + } + + fn default_artifact_url(ver: &StorePluginVersion) -> String { + format!("https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/{}.zip", ver.hash) + } + + fn proxy_plugins(&self) -> StorePluginList { + let url = self.plugins_url(); + match self.agent.get(&url).call() { + Err(e) => { + log::error!("Plugins proxy error for {}: {}", url, e); + vec![] + }, + Ok(resp) => { + match resp.into_json::() { + Err(e) => { + log::error!("Plugins json error for {}: {}", url, e); + vec![] + } + Ok(x) => x, + } + } + } + } +} + +impl IStorage for ProxiedStorage { + fn plugins(&self) -> StorePluginList { + let mut proxy = self.proxy_plugins(); + for plugin in &mut proxy { + for version in &mut plugin.versions { + if version.artifact.is_none() { + version.artifact = Some(Self::default_artifact_url(version)); + } + } + } + let fallback = self.fallback.plugins(); + proxy.extend_from_slice(&fallback); + proxy + } + + fn get_artifact(&self, name: &str, version: &str, hash: &str) -> Result { + self.fallback.get_artifact(name, version, hash) + } + + fn get_image(&self, name: &str) -> Result { + self.fallback.get_image(name) + } + + fn get_statistics(&self) -> std::collections::HashMap { + self.fallback.get_statistics() + } +}