diff --git a/.github/workflows/tip.yml b/.github/workflows/tip.yml new file mode 100644 index 0000000..c8e5102 --- /dev/null +++ b/.github/workflows/tip.yml @@ -0,0 +1,78 @@ +name: Continuous master build + +on: + push: + branches: + - master + +jobs: + clippy: + if: "!contains(github.event.head_commit.message, 'skip ci')" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: target/ + key: cargo-clippy-cache-${{ matrix.arch.target }} + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2023-01-08 + override: true + components: clippy + - uses: actions-rs/cargo@v1 + with: + command: clippy + + build: + if: "!contains(github.event.head_commit.message, 'skip ci')" + strategy: + matrix: + arch: + - { name: 'x86_64', os: 'ubuntu-20.04', target: 'x86_64-unknown-linux-gnu', cross: false } + runs-on: ${{ matrix.arch.os }} + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + target/ + key: cargo-build-cache-${{ matrix.arch.target }} + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2023-01-08 + override: true + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --release --all-features --target=${{ matrix.arch.target }} + use-cross: ${{ matrix.arch.cross }} + env: + JQ_LIB_DIR: /usr/lib/x86_64-linux-gnu + - name: Rename artifact + run: | + mv target/${{ matrix.arch.target }}/release/deckshot target/${{ matrix.arch.target }}/release/deckshot-tip-${{ matrix.arch.name }} + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: deckshot-tip-${{ matrix.arch.name }} + path: | + target/${{ matrix.arch.target }}/release/deckshot-tip-${{ matrix.arch.name }} + + package: + runs-on: ubuntu-20.04 + needs: [clippy, build] + steps: + - name: Download artifacts + uses: actions/download-artifact@v2 + with: + path: target/out + - name: Create release + uses: marvinpinto/action-automatic-releases@latest + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + automatic_release_tag: tip + title: Development build + prerelease: true + files: target/out/*/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61a9ab5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/deckshot.yml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..3d01f6f --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,4 @@ +fn_args_density = "Compressed" +merge_imports = true +max_width = 200 +tab_spaces = 2 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dd07fe3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2312 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[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 = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "async-compression" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "attohttpc" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262c3f7f5d61249d8c00e5546e2685cd15ebeeb1bc0f3cc5449350a1cb07319e" +dependencies = [ + "http", + "log", + "rustls", + "serde", + "serde_json", + "url 2.3.0", + "webpki", + "webpki-roots", + "wildmatch", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "aws-creds" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeeee1a5defa63cba39097a510dfe63ef53658fc8995202a610f6a8a4d03639" +dependencies = [ + "attohttpc", + "dirs", + "rust-ini", + "serde", + "serde-xml-rs", + "thiserror", + "time", + "url 2.3.0", +] + +[[package]] +name = "aws-region" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92a8af5850d0ea0916ca3e015ab86951ded0bf4b70fd27896e81ae1dfb0af37" +dependencies = [ + "thiserror", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[package]] +name = "cfg-if" +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", + "num-integer", + "num-traits", + "serde", + "winapi", +] + +[[package]] +name = "clap" +version = "4.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +dependencies = [ + "bitflags", + "clap_lex", + "is-terminal", + "strsim", + "termcolor", +] + +[[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 = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cxx" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deckshot" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "dropbox-sdk", + "futures", + "google-drive3", + "kvlogger", + "log", + "notify", + "oauth2", + "onedrive-api", + "pickledb", + "rand", + "reqwest", + "rust-s3", + "serde", + "serde_json", + "serde_yaml", + "tokio", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "dropbox-sdk" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e11f3120100fbcb94b79d22013a190ff9fd914f1c2039dd5c884d2e309e476" +dependencies = [ + "atty", + "base64", + "log", + "ring", + "serde", + "serde_json", + "thiserror", + "ureq", + "url 2.3.0", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[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 = "filetime" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.42.0", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding 2.1.0", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "google-apis-common" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0d0bf11c1a1c1c304a7379fa173659bda32fdd2f042c5af5a066f547cb2fef" +dependencies = [ + "base64", + "chrono", + "http", + "hyper", + "itertools", + "mime", + "serde", + "serde_json", + "serde_with", + "tokio", + "tower-service", + "url 1.7.2", + "yup-oauth2", +] + +[[package]] +name = "google-drive3" +version = "5.0.2-beta-1+20220225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad99a15b0f384457adff8894423b0df1e90fdbb9bc02e35d0fe1d1fae03484d6" +dependencies = [ + "anyhow", + "google-apis-common", + "http", + "hyper", + "hyper-rustls", + "itertools", + "mime", + "serde", + "serde_json", + "tokio", + "tower-service", + "url 1.7.2", +] + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +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 = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[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 = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "ipnet" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[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 = "kqueue" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "kvlogger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093ab3f3923ff92ae168b639e5e7f25954183b9bb9a2bbb8fe95b56c7b47192c" +dependencies = [ + "colored", + "env_logger", + "indexmap", + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "maybe-async" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6007f9dad048e0a224f27ca599d669fca8cfa0dac804725aab542b2eb032bce6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.42.0", +] + +[[package]] +name = "notify" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" +dependencies = [ + "bitflags", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "winapi", +] + +[[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]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "oauth2" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf26a72311c087f8c5ba617c96fac67a5c04f430e716ac8d8ab2de62e23368" +dependencies = [ + "base64", + "chrono", + "getrandom", + "http", + "rand", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror", + "url 2.3.0", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "onedrive-api" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d4d8d88caaff1f965ba863a7ed0975a1c9bfd43dca240fe4c12a3e6bd0f8b0" +dependencies = [ + "bytes", + "reqwest", + "serde", + "serde_json", + "strum", + "thiserror", + "url 2.3.0", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown", +] + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pickledb" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53a5ade47760e8cc4986bdc5e72daeffaaaee64cbc374f9cfe0a00c1cd87b1f" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "reqwest" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +dependencies = [ + "async-compression", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding 2.1.0", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url 2.3.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[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 = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rust-s3" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6009d9d4cf910505534d62d380a0aa305805a2af0b5c3ad59a3024a0715b847" +dependencies = [ + "async-trait", + "aws-creds", + "aws-region", + "base64", + "cfg-if", + "hex", + "hmac", + "http", + "log", + "maybe-async", + "md5", + "percent-encoding 2.1.0", + "reqwest", + "serde", + "serde-xml-rs", + "serde_derive", + "sha2", + "thiserror", + "time", + "tokio", + "tokio-stream", + "url 2.3.0", +] + +[[package]] +name = "rustix" +version = "0.36.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + +[[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 = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-xml-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa" +dependencies = [ + "log", + "serde", + "thiserror", + "xml-rs", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341" +dependencies = [ + "serde", +] + +[[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 = "serde_with" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "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 = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "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 = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "libc", + "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.42.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[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.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733b5ad78377302af52c0dbcb2623d78fe50e4b3bf215948ff29e9ee031d8566" +dependencies = [ + "base64", + "log", + "once_cell", + "rustls", + "url 2.3.0", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + +[[package]] +name = "url" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +dependencies = [ + "form_urlencoded", + "idna 0.2.3", + "percent-encoding 2.1.0", + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[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-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "wildmatch" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee583bdc5ff1cf9db20e9db5bb3ff4c3089a8f6b8b31aff265c9aba85812db86" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yup-oauth2" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cb398cca4dedd0203666d7c3e7a089d14557be759efd57ab75f067949276e7" +dependencies = [ + "anyhow", + "async-trait", + "base64", + "futures", + "http", + "hyper", + "hyper-rustls", + "itertools", + "log", + "percent-encoding 2.1.0", + "rustls", + "rustls-pemfile", + "seahash", + "serde", + "serde_json", + "time", + "tokio", + "tower-service", + "url 2.3.0", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d522c14 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "deckshot" +version = "0.1.0" +authors = ["Antoine POPINEAU"] +description = "Automatic screenshot uploader for the Steam Deck" +license = "MIT" + +edition = "2021" + +[profile.release] +strip = "debuginfo" +opt-level = "s" +lto = "thin" + +[dependencies] +anyhow = "1.0.58" +async-trait = "0.1.60" +clap = "4.0.32" +dropbox-sdk = { version = "0.15.0", default-features = false, features = ["default_client", "dbx_files"] } +futures = "0.3.25" +google-drive3 = "5.0.2-beta-1" +kvlogger = "0.5.0" +log = "*" +notify = "5.0.0" +oauth2 = "4.3.0" +onedrive-api = { version = "0.9.0", default-features = false } +pickledb = "0.5.1" +reqwest = { version = "0.11.13", default-features = false, features = ["rustls-tls"] } +rust-s3 = {version = "0.32.3", default-features = false, features = ["tokio-rustls-tls"] } +serde = "^1.0" +serde_json = "^1.0" +serde_yaml = "0.9.16" +tokio = { version = "^1.23", features = ["macros", "rt-multi-thread", "sync", "io-util"] } + +rand = "0.8.5" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..57b1896 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Antoine POPINEAU + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b18c76 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# Deckshot + +**UNDER DEVELOPEMENT** + +Deckshot is a service to be run on your Steam Deck that will automatically upload any screenshot taken while it is running. So far, it supports the following remote services: + + * S3 + * Google Drive + * Dropbox + * Microsoft OneDrive + +It will try and find the name of the game played while the screenshot was taken and place it in an appropriately named folder. If the name of the game cannot be determined (network issue, non-Steam game, GameScope), the screenshot will be uploaded to an `UNKNOWN GAME` folder. + +It maintains an internal (and simple) database storing the screenshots that failed to upload (for example, if you were offline), and will regularly retry those uploads. + +## Installation + +```shell +# Install with +deck$ curl -sL https://raw.githubusercontent.com/apognu/deckshot/master/contrib/install.sh | sh + +# Uninstall with +deck$ curl -sL https://raw.githubusercontent.com/apognu/deckshot/master/contrib/uninstall.sh | sh +``` + +## Configuration + +Deckshot is confiured by editing `/home/deck/.local/share/deckshot/deckshot.yml`. The only required configuration settings are the one defining the uploader to use. When you edit the configuration, restart deckshot by issuing the following command: + +```shell +deck$ systemctl --user restart deckshot +``` + +The upload providers requiring an interactive authentication process (open a browser for instance) can be configured by running the `auth` command once the configuration file is updated accordingly. + +```shell +deck$ /home/deck/.local/share/deckshot/deckshot auth +``` + +Here are the required parameters per uploader: + +### S3 / Minio + +As of this writing, only path-based S3 access is supported. Credentials with `s3:PutObject` allowed are required. + +```yaml +uploader: + kind: S3 + endpoint: + region: + access_key_id: + secret_access_key: + bucket: +``` + +### Google Drive + +The Google Drive integration requires a service account created from the [Google Developers console](https://console.cloud.google.com/iam-admin/serviceaccounts/create), in the form of a JSON private key. The service account itself does not require permission, but will need to autorize it to access a folder in your Google drive. + +To do so, note the service account email address (`xxx@yyy@iam.gserviceaccount.com`), and share the folder with that email address, with write permissions. Also note the folder ID from the URL, you will need to indicate it in the configuration. + +```yaml +uploader: + kind: GoogleDrive + private_key_file: + folder: +``` + +### Dropbox + +The Dropbox integration requires that you create a Dropbox OAuth2 application from [Dropbox's Developers console](https://www.dropbox.com/developers/apps/create), once it is created, note down the `App key` shown in the "OAuth 2" section, and give it the `files.content.write` scope from the Permissions tab. + +Depending on the appliction's access type you chose when creating it, the value of the `folder` parameters will either be appended to the application's folder, or will start at your Dropbox's root. + +```yaml +uploader: + kind: Dropbox + client_id: + folder: +``` + +Now, run the authentication process with `deckshot auth` and follow the instructions before restarting deckshot. + +### OneDrive + +The OneDrive integration requires the creation of an OAuth2 application on the [Azure portal](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade). From there, go to "New registration", and create a "Personal Microsoft accounts only" account. You can choose whichever `Web` Redirect URI you want, like `http://localhost:8080/redirect` (the loading of this page will fail anyway, we will retrieve the code from the URL). When the application is created, generate a client secret by going to "Certificates and secrets" and clicking "New client secret". Finally, give the application the `Files.ReadWrite` permission by going into "API permissions" (this is a "Microsoft Graph" delegated permission). + +Report the values for the "Application (client) ID" (in the "Overview" tab), the client secret and the redirect URI in the configuration file. + +```yaml +uploader: + client_id: + client_secret: + redirect_uri: + folder: +``` diff --git a/contrib/deckshot.service b/contrib/deckshot.service new file mode 100644 index 0000000..9838ced --- /dev/null +++ b/contrib/deckshot.service @@ -0,0 +1,11 @@ +[Unit] +Description=Deckshot Screenshot Uploader + +[Service] +Type=simple +Restart=always +WorkingDirectory=/home/deck/.local/share/deckshot +ExecStart=/home/deck/.local/share/deckshot/deckshot + +[Install] +WantedBy=default.target diff --git a/contrib/deckshot.yml b/contrib/deckshot.yml new file mode 100644 index 0000000..e05cf39 --- /dev/null +++ b/contrib/deckshot.yml @@ -0,0 +1,21 @@ +uploader: + # kind: S3 + # endpoint: + # region: + # access_key_id: + # secret_access_key: + # bucket: + + # kind: GoogleDrive + # private_key_file: + # folder: + + # kind: Dropbox + # token: + # folder: + + # kind: OneDrive + # client_id: + # client_secret: + # redirect_uri: + # folder: diff --git a/contrib/install.sh b/contrib/install.sh new file mode 100644 index 0000000..e88fda1 --- /dev/null +++ b/contrib/install.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +if [ "$(id -nu)" != 'deck' ]; then + echo "ERROR: this should be run as the 'deck' user on a Steam Deck." >&2 + exit 1 +fi + +DECKSHOT_DIR='/home/deck/.local/share/deckshot' +CONFIG_FILE="${DECKSHOT_DIR}/deckshot.yml" + +systemctl --user stop deckshot || true + +mkdir -p /home/deck/.local/share/deckshot + +curl -sL https://github.com/apognu/deckshot/releases/download/tip/deckshot-tip-x86_64 > ${DECKSHOT_DIR}/deckshot +curl -sL https://raw.githubusercontent.com/apognu/deckshot/master/contrib/deckshot.service > /home/deck/.config/systemd/user/deckshot.service + +if [ ! -f $CONFIG_FILE ]; then + curl -sL https://raw.githubusercontent.com/apognu/deckshot/master/contrib/deckshot.yml > ${CONFIG_FILE} + chmod 0600 $CONFIG_FILE +fi + +chmod u+x ${DECKSHOT_DIR}/deckshot + +systemctl --user daemon-reload +systemctl --user enable --now deckshot diff --git a/contrib/uninstall.sh b/contrib/uninstall.sh new file mode 100644 index 0000000..bab9422 --- /dev/null +++ b/contrib/uninstall.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -e + +if [ "$(id -nu)" != 'deck' ]; then + echo "ERROR: this should be run as the 'deck' user on a Steam Deck." >&2 + exit 1 +fi + +DECKSHOT_DIR='/home/deck/.local/share/deckshot' + +systemctl --user disable --now deckshot + +rm -rf $DECKSHOT_DIR +rm /home/deck/.config/systemd/user/deckshot.service + +systemctl --user daemon-reload diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..a55da2c --- /dev/null +++ b/src/config.rs @@ -0,0 +1,68 @@ +use std::{path::PathBuf, sync::Arc}; + +use anyhow::Context; +use serde::Deserialize; + +use crate::uploaders::{ + dropbox::{DropboxConfig, DropboxUploader}, + gdrive::{GoogleDriveConfig, GoogleDriveUploader}, + noop::NoopUploader, + onedrive::{OneDriveConfig, OneDriveUploader}, + s3::{S3Config, S3Uploader}, + Uploader, +}; + +#[derive(Clone, Deserialize)] +#[serde(tag = "kind")] +pub enum UploaderKind { + Noop, + S3(S3Config), + GoogleDrive(GoogleDriveConfig), + Dropbox(DropboxConfig), + OneDrive(OneDriveConfig), +} + +#[derive(Clone, Deserialize)] +pub struct Config { + #[serde(default = "default_deckshot_path")] + pub deckshot_path: PathBuf, + #[serde(default = "default_screenshot_path")] + pub screenshots_path: PathBuf, + pub uploader: UploaderKind, + #[serde(default = "default_retrier_interval")] + pub retrier_interval: u64, +} + +fn default_deckshot_path() -> PathBuf { + "/home/deck/.local/share/deckshot".into() +} + +fn default_screenshot_path() -> PathBuf { + "/home/deck/.local/share/Steam/userdata".into() +} + +const fn default_retrier_interval() -> u64 { + 60 +} + +impl Config { + pub async fn uploader(&self) -> Result>, anyhow::Error> { + let uploader: Box = match self.uploader { + UploaderKind::Noop => Box::new(NoopUploader::build()?), + UploaderKind::S3(ref config) => Box::new(S3Uploader::build(config.clone())?), + UploaderKind::GoogleDrive(ref config) => Box::new(GoogleDriveUploader::build(config.clone()).await?), + UploaderKind::Dropbox(ref config) => Box::new(DropboxUploader::build(self, config.clone()).await?), + UploaderKind::OneDrive(ref config) => Box::new(OneDriveUploader::build(self, config.clone()).await?), + }; + + Ok(Arc::new(uploader)) + } +} + +pub fn read_config(path: Option<&PathBuf>) -> Result { + let default: PathBuf = default_deckshot_path().join("deckshot.yml"); + let path = path.unwrap_or(&default); + let file = std::fs::File::open(path).context("could not open configuration file")?; + + serde_yaml::from_reader::<_, Config>(file).context("could not parse configuration file") +} diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..9f2ef83 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,57 @@ +use std::sync::Arc; + +use anyhow::Context; +use pickledb::PickleDb; +use tokio::{ + fs::{create_dir_all, File}, + io::{AsyncReadExt, AsyncWriteExt}, + sync::Mutex, +}; + +use crate::config::Config; + +pub fn init_db(config: &Config) -> Result>, anyhow::Error> { + let mut db = match load_db(config) { + Ok(db) => db, + Err(_) => create_db(config), + }; + + if !db.lexists("screenshots") { + db.lcreate("screenshots").context("could not create database list")?; + } + + Ok(Arc::new(Mutex::new(db))) +} + +fn create_db(config: &Config) -> PickleDb { + PickleDb::new(config.deckshot_path.join("deckshot.db"), pickledb::PickleDbDumpPolicy::AutoDump, pickledb::SerializationMethod::Json) +} + +fn load_db(config: &Config) -> Result { + Ok(PickleDb::load( + config.deckshot_path.join("deckshot.db"), + pickledb::PickleDbDumpPolicy::AutoDump, + pickledb::SerializationMethod::Json, + )?) +} + +pub async fn save_token(config: &Config, key: &str, value: &str) -> Result<(), anyhow::Error> { + create_dir_all(config.deckshot_path.join("credentials")).await?; + + let mut file = File::create(config.deckshot_path.join("credentials").join(key)).await?; + file.write_all(value.as_bytes()).await?; + + Ok(()) +} + +pub async fn load_token(config: &Config, key: &str) -> Result { + let mut file = File::open(config.deckshot_path.join("credentials").join(key)) + .await + .context("could not load token, did you run 'deckshot auth'?")?; + + let mut value = String::new(); + + file.read_to_string(&mut value).await?; + + Ok(value) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b7326ed --- /dev/null +++ b/src/main.rs @@ -0,0 +1,164 @@ +#![feature(async_closure, let_chains)] + +#[macro_use] +extern crate async_trait; + +mod config; +mod database; +mod steam; +mod uploaders; + +use std::{ + env, + path::{Path, PathBuf}, + sync::{mpsc::channel, Arc}, + time::Duration, +}; + +use anyhow::{anyhow, Context}; +use clap::{arg, value_parser, Command}; +use kvlogger::*; +use notify::{ + event::{AccessKind, AccessMode}, + Event, EventKind, RecursiveMode, Watcher, +}; +use pickledb::PickleDb; +use tokio::sync::Mutex; + +use crate::{steam::GameScreenshot, uploaders::Uploader}; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "deckshot=info"); + } + + KvLoggerBuilder::default().init()?; + + let args = Command::new("deckshot") + .arg(arg!(-c --config "Location of configuration file").required(false).value_parser(value_parser!(PathBuf))) + .subcommand(Command::new("auth").about("Launch an interactive authentication process")) + .get_matches(); + + let config = config::read_config(args.get_one::("config"))?; + let uploader = config.uploader().await.context("could not build uploader configuration")?; + + if args.subcommand_matches("auth").is_some() { + uploader.auth().await?; + + return Ok(()); + } + + let (tx, rx) = channel::(); + + let mut watcher = notify::recommended_watcher(move |event| { + if let Ok(event) = event { + let _ = tx.send(event); + } + })?; + + watcher + .watch(Path::new(&config.screenshots_path), RecursiveMode::Recursive) + .context("could not watch screenshot directory")?; + + let db = database::init_db(&config).context("could not initialize database")?; + + tokio::spawn({ + let db = db.clone(); + let uploader = uploader.clone(); + + async move { + loop { + let mut paths: Vec = Vec::new(); + + { + let db = db.lock().await; + let data = db.liter("screenshots"); + + for item in data { + if let Some(path) = item.get_item::() { + kvlog!(Info, "uploading failed screenshot", { + "path" => path + }); + + paths.push(path); + } + } + } + + for (index, path) in paths.iter().enumerate() { + match upload(&**uploader, db.clone(), path).await { + Ok(screenshot) => { + kvlog!(Info, "screenshot uploaded", { + "path" => screenshot.path.display(), + "game" => screenshot.game_name().await + }); + } + + Err(err) => { + kvlog!(Error, "could not upload screenshot", { + "error" => format!("{err:#}") + }); + } + } + + db.lock().await.lpop::("screenshots", index); + } + + tokio::time::sleep(Duration::from_secs(config.retrier_interval)).await; + } + } + }); + + while let Ok(event) = rx.recv() { + if event.kind == EventKind::Access(AccessKind::Close(AccessMode::Write)) { + for path in event.paths { + let lossy_path = path.as_os_str().to_string_lossy(); + + if lossy_path.ends_with(".jpg") && !lossy_path.contains("thumbnail") { + match upload(&**uploader, db.clone(), path).await { + Ok(screenshot) => { + kvlog!(Info, "screenshot uploaded", { + "path" => screenshot.path.display(), + "game" => screenshot.game_name().await + }); + } + + Err(err) => { + kvlog!(Error, "could not upload screenshot", { + "error" => format!("{err:#}") + }); + } + } + } + } + } + } + + Ok(()) +} + +async fn upload

(uploader: &dyn Uploader, db: Arc>, path: P) -> Result +where + P: AsRef, +{ + match uploader.upload(path.as_ref().into()).await { + Ok(screenshot) => Ok(screenshot), + + Err(err) => match save(db, path).await { + Ok(()) => Err(err), + Err(save_err) => Err(save_err.context(err)), + }, + } +} + +async fn save

(db: Arc>, path: P) -> Result<(), anyhow::Error> +where + P: AsRef, +{ + let mut db = db.lock().await; + + db.ladd("screenshots", &path.as_ref().to_string_lossy()).ok_or_else(|| anyhow!("could not save screenshot"))?; + + Ok(()) +} diff --git a/src/steam.rs b/src/steam.rs new file mode 100644 index 0000000..a525283 --- /dev/null +++ b/src/steam.rs @@ -0,0 +1,55 @@ +use std::{ + collections::HashMap, + ffi::OsStr, + path::{Path, PathBuf}, +}; + +use anyhow::anyhow; +use serde::Deserialize; + +#[derive(Deserialize)] +struct GameInformationResponse { + data: GameInformation, +} + +#[derive(Deserialize)] +struct GameInformation { + name: String, +} + +pub struct GameScreenshot { + pub game_id: u64, + pub path: PathBuf, +} + +impl GameScreenshot { + pub fn file_name(&self) -> Result<&OsStr, anyhow::Error> { + self.path.file_name().ok_or_else(|| anyhow!("could not determine file name")) + } + + pub async fn game_name(&self) -> String { + let url = format!("https://store.steampowered.com/api/appdetails?appids={}", self.game_id); + + if let Ok(response) = reqwest::get(url).await { + if let Ok(payload) = response.json::>().await { + if let Some(game) = payload.get(&self.game_id) { + return game.data.name.clone(); + } + } + } + + "UNKNOWN_GAME".to_string() + } + + pub async fn dest_name(&self) -> Result { + Ok(Path::new(&self.game_name().await).join(self.file_name()?)) + } +} + +impl From<&Path> for GameScreenshot { + fn from(path: &Path) -> Self { + let game_id = path.iter().nth(10).unwrap_or_default().to_string_lossy().parse::().unwrap_or_default(); + + GameScreenshot { game_id, path: path.to_owned() } + } +} diff --git a/src/uploaders/dropbox.rs b/src/uploaders/dropbox.rs new file mode 100644 index 0000000..003bb7d --- /dev/null +++ b/src/uploaders/dropbox.rs @@ -0,0 +1,76 @@ +use dropbox_sdk::{ + default_client::{NoauthDefaultClient, UserAuthDefaultClient}, + files::{self, UploadArg}, + oauth2::{Authorization, AuthorizeUrlBuilder, Oauth2Type, PkceCode}, +}; +use serde::Deserialize; +use tokio::{fs::File, io::AsyncReadExt}; + +use crate::{ + config::Config, + database::{load_token, save_token}, + uploaders::prompt_authorization_code, + GameScreenshot, Uploader, +}; + +pub struct DropboxUploader { + config: Config, + client_id: String, + folder: Option, +} + +#[derive(Clone, Deserialize)] +pub struct DropboxConfig { + pub client_id: String, + pub folder: Option, +} + +impl DropboxUploader { + pub async fn build(deckshot: &Config, config: DropboxConfig) -> Result { + Ok(DropboxUploader { + config: deckshot.clone(), + client_id: config.client_id.clone(), + folder: config.folder, + }) + } +} + +#[async_trait] +impl Uploader for DropboxUploader { + async fn upload(&self, screenshot: GameScreenshot) -> Result { + let token = load_token(&self.config, "dropbox").await?; + let auth = Authorization::load(self.client_id.clone(), &token).unwrap(); + let client = UserAuthDefaultClient::new(auth); + + let dest = if let Some(ref folder) = self.folder { + format!("/{}/{}", folder, screenshot.dest_name().await?.to_string_lossy()) + } else { + format!("/{}", screenshot.dest_name().await?.to_string_lossy()) + }; + + let mut file = File::open(&screenshot.path).await?; + let mut buffer = Vec::new(); + + file.read_to_end(&mut buffer).await?; + + let args = UploadArg::new(dest); + + files::upload(&client, &args, &buffer)??; + + Ok(screenshot) + } + + async fn auth(&self) -> Result<(), anyhow::Error> { + let pkce = PkceCode::new(); + let flow = Oauth2Type::PKCE(pkce); + let url = AuthorizeUrlBuilder::new(&self.client_id, &flow).build(); + let code = prompt_authorization_code(url.as_str())?; + + let mut auth = Authorization::from_auth_code(self.client_id.clone(), flow, code, None); + auth.obtain_access_token(NoauthDefaultClient::default())?; + + save_token(&self.config, "dropbox", &auth.save().unwrap()).await?; + + Ok(()) + } +} diff --git a/src/uploaders/gdrive.rs b/src/uploaders/gdrive.rs new file mode 100644 index 0000000..97dec58 --- /dev/null +++ b/src/uploaders/gdrive.rs @@ -0,0 +1,77 @@ +use std::fs::File; + +use anyhow::Context; +use google_drive3::{ + api::{DriveHub, File as RemoteFile}, + client::oauth2 as helpers, + hyper::{self, client::HttpConnector}, + hyper_rustls::{self, HttpsConnector}, + oauth2, +}; +use serde::Deserialize; + +use crate::{GameScreenshot, Uploader}; + +pub struct GoogleDriveUploader { + hub: DriveHub>, + folder: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct GoogleDriveConfig { + pub private_key_file: String, + pub folder: String, +} + +impl GoogleDriveUploader { + pub async fn build(config: GoogleDriveConfig) -> Result { + let pkey = helpers::read_service_account_key(config.private_key_file).await?; + let auth = oauth2::ServiceAccountAuthenticator::builder(pkey).build().await.context("could not parse private key")?; + + let hub = DriveHub::new( + hyper::Client::builder().build(hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_or_http().enable_http1().enable_http2().build()), + auth, + ); + + Ok(GoogleDriveUploader { hub, folder: config.folder }) + } +} + +#[async_trait] +impl Uploader for GoogleDriveUploader { + async fn upload(&self, screenshot: GameScreenshot) -> Result { + let filename = screenshot.file_name()?; + let game = screenshot.game_name().await; + + let (_, folders) = self + .hub + .files() + .list() + .q(&format!("mimeType = 'application/vnd.google-apps.folder' and '{}' in parents and name = '{game}'", self.folder)) + .doit() + .await + .context("could not find game folder")?; + + let folder = if let Some(folders) = folders.files && !folders.is_empty() { + folders[0].clone() + } else { + let remote = RemoteFile { name: Some(game), parents: Some(vec![self.folder.clone()]), mime_type: Some("application/vnd.google-apps.folder".to_string()), ..Default::default() }; + + let (_, file) = self.hub.files().create(remote).upload(std::io::empty(), "application/vnd.google-apps.folder".parse().unwrap()).await.context("could not create game folder")?; + + file + }; + + let file = File::open(&screenshot.path)?; + + let remote = RemoteFile { + parents: Some(vec![folder.id.unwrap()]), + name: Some(filename.to_string_lossy().into_owned()), + ..Default::default() + }; + + self.hub.files().create(remote).upload(file, "image/jpeg".parse().unwrap()).await.context("could not upload file")?; + + Ok(screenshot) + } +} diff --git a/src/uploaders/mod.rs b/src/uploaders/mod.rs new file mode 100644 index 0000000..7006d05 --- /dev/null +++ b/src/uploaders/mod.rs @@ -0,0 +1,33 @@ +pub mod dropbox; +pub mod gdrive; +pub mod noop; +pub mod onedrive; +pub mod s3; + +use std::io::{self, Write}; + +use anyhow::Context; + +use crate::GameScreenshot; + +#[async_trait] +pub trait Uploader: Sync + Send { + async fn upload(&self, screenshot: GameScreenshot) -> Result; + + async fn auth(&self) -> Result<(), anyhow::Error> { + unimplemented!(); + } +} + +pub fn prompt_authorization_code(authorize_url: &str) -> Result { + println!("Open the following URL into your Web browser to authenticate, then input the generated code:"); + println!("{authorize_url}"); + + let mut code = String::new(); + + print!("Code: "); + io::stdout().flush()?; + io::stdin().read_line(&mut code).context("could not read code")?; + + Ok(code.trim().to_string()) +} diff --git a/src/uploaders/noop.rs b/src/uploaders/noop.rs new file mode 100644 index 0000000..21d5699 --- /dev/null +++ b/src/uploaders/noop.rs @@ -0,0 +1,17 @@ +use crate::{GameScreenshot, Uploader}; + +#[derive(Clone)] +pub struct NoopUploader; + +impl NoopUploader { + pub fn build() -> Result { + Ok(NoopUploader) + } +} + +#[async_trait] +impl Uploader for NoopUploader { + async fn upload(&self, _screenshot: GameScreenshot) -> Result { + unimplemented!(); + } +} diff --git a/src/uploaders/onedrive.rs b/src/uploaders/onedrive.rs new file mode 100644 index 0000000..d2e174b --- /dev/null +++ b/src/uploaders/onedrive.rs @@ -0,0 +1,121 @@ +use oauth2::{ + basic::BasicClient, reqwest::async_http_client, AccessToken, AuthType, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, RefreshToken, Scope, TokenResponse, TokenUrl, +}; +use onedrive_api::{option::DriveItemPutOption, ConflictBehavior, DriveLocation, FileName, ItemLocation}; +use serde::Deserialize; +use tokio::{fs::File, io::AsyncReadExt}; + +use crate::{ + config::Config, + database::{load_token, save_token}, + uploaders::prompt_authorization_code, + GameScreenshot, Uploader, +}; + +pub struct OneDriveUploader { + config: Config, + client: BasicClient, + folder: Option, +} + +#[derive(Clone, Deserialize)] +pub struct OneDriveConfig { + pub client_id: String, + pub client_secret: String, + pub redirect_uri: String, + pub folder: Option, +} + +impl OneDriveUploader { + pub async fn build(deckshot: &Config, config: OneDriveConfig) -> Result { + let client = BasicClient::new( + ClientId::new(config.client_id.clone()), + Some(ClientSecret::new(config.client_secret.clone())), + AuthUrl::new("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize".to_string())?, + Some(TokenUrl::new("https://login.microsoftonline.com/consumers/oauth2/v2.0/token".to_string())?), + ) + .set_auth_type(AuthType::RequestBody) + .set_redirect_uri(RedirectUrl::new(config.redirect_uri.clone())?); + + Ok(OneDriveUploader { + config: deckshot.clone(), + client, + folder: config.folder, + }) + } + + async fn try_upload(&self, screenshot: GameScreenshot) -> Result { + let token = AccessToken::new(load_token(&self.config, "onedrive-access-token").await?); + + let game = screenshot.game_name().await; + let filename = FileName::new(screenshot.file_name()?.to_str().unwrap()).unwrap(); + + let mut file = File::open(&screenshot.path).await?; + let mut buffer = Vec::new(); + + file.read_to_end(&mut buffer).await?; + + let drive = onedrive_api::OneDrive::new(token.secret(), DriveLocation::me()); + + if let Some(ref folder) = self.folder { + drive + .create_folder_with_option( + ItemLocation::root(), + FileName::new(folder).unwrap(), + DriveItemPutOption::new().conflict_behavior(ConflictBehavior::Replace), + ) + .await?; + } + + let root_folder_path = self.folder.clone().map(|path| format!("/{path}")).unwrap_or_else(|| "/".to_string()); + let root_folder = ItemLocation::from_path(&root_folder_path).unwrap(); + + let folder = drive + .create_folder_with_option(root_folder, FileName::new(&game).unwrap(), DriveItemPutOption::new().conflict_behavior(ConflictBehavior::Replace)) + .await?; + + let folder_id = folder.id.unwrap(); + let item = ItemLocation::child_of_id(&folder_id, filename); + + drive.upload_small(item, buffer).await?; + + Ok(screenshot) + } +} + +#[async_trait] +impl Uploader for OneDriveUploader { + async fn upload(&self, screenshot: GameScreenshot) -> Result { + match self.try_upload(screenshot).await { + Ok(screenshot) => Ok(screenshot), + + Err(err) => { + if err.downcast_ref::().is_some() { + let refresh = RefreshToken::new(load_token(&self.config, "onedrive-refresh-token").await?); + let tokens = self.client.exchange_refresh_token(&refresh).request_async(async_http_client).await?; + + save_token(&self.config, "onedrive-access-token", tokens.access_token().secret()).await?; + } + + Err(err) + } + } + } + + async fn auth(&self) -> Result<(), anyhow::Error> { + let (url, _) = self + .client + .authorize_url(CsrfToken::new_random) + .add_scope(Scope::new("offline_access".to_string())) + .add_scope(Scope::new("Files.ReadWrite".to_string())) + .url(); + + let code = prompt_authorization_code(url.as_str())?; + let tokens = self.client.exchange_code(AuthorizationCode::new(code)).request_async(async_http_client).await?; + + save_token(&self.config, "onedrive-access-token", tokens.access_token().secret()).await?; + save_token(&self.config, "onedrive-refresh-token", tokens.refresh_token().unwrap().secret()).await?; + + Ok(()) + } +} diff --git a/src/uploaders/s3.rs b/src/uploaders/s3.rs new file mode 100644 index 0000000..f0c8d80 --- /dev/null +++ b/src/uploaders/s3.rs @@ -0,0 +1,48 @@ +use anyhow::Context; +use s3::{creds::Credentials, Bucket, Region}; +use serde::Deserialize; +use tokio::{fs::File, io::BufReader}; + +use crate::{GameScreenshot, Uploader}; + +pub struct S3Uploader { + bucket: Bucket, +} + +#[derive(Clone, Deserialize)] +pub struct S3Config { + pub endpoint: String, + pub region: Option, + pub access_key_id: String, + pub secret_access_key: String, + pub bucket: String, +} + +impl S3Uploader { + pub fn build(config: S3Config) -> Result { + let bucket = Bucket::new( + &config.bucket, + Region::Custom { + region: config.region.unwrap_or_default(), + endpoint: config.endpoint, + }, + Credentials::new(Some(&config.access_key_id), Some(&config.secret_access_key), None, None, None)?, + )? + .with_path_style(); + + Ok(S3Uploader { bucket }) + } +} + +#[async_trait] +impl Uploader for S3Uploader { + async fn upload(&self, screenshot: GameScreenshot) -> Result { + let dest = screenshot.dest_name().await?; + let file = File::open(&screenshot.path).await?; + let mut reader = BufReader::new(file); + + self.bucket.put_object_stream(&mut reader, dest.to_string_lossy()).await.context("could not upload screenshot")?; + + Ok(screenshot) + } +}