Simple working server (from voxa-server)
This commit is contained in:
Generated
+814
@@ -0,0 +1,814 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[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.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
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 = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
|
||||
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 = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.176"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "3.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"ureq-proto",
|
||||
"utf-8",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "voxa-node"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"chrono",
|
||||
"once_cell",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "voxa-node"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
anyhow = "1.0.100"
|
||||
base64 = "0.22.1"
|
||||
sha1 = "0.10.6"
|
||||
once_cell = "1.21.3"
|
||||
chrono = "0.4.42"
|
||||
ureq = "3.1.2"
|
||||
rusqlite = "0.37.0"
|
||||
@@ -0,0 +1,30 @@
|
||||
#[macro_export]
|
||||
macro_rules! export_plugin {
|
||||
($p:expr) => {
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn load_plugin() -> $crate::utils::plugin::DynPlugin {
|
||||
$p
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! logger {
|
||||
(const $i:ident $name:expr) => {
|
||||
pub const $i: $crate::once_cell::sync::Lazy<$crate::utils::logger::Logger> =
|
||||
$crate::once_cell::sync::Lazy::new(|| $crate::utils::logger::Logger::new($name));
|
||||
};
|
||||
|
||||
($i:ident $name:expr) => {
|
||||
pub const $i: $crate::once_cell::sync::Lazy<$crate::utils::logger::Logger> =
|
||||
$crate::once_cell::sync::Lazy::new(|| $crate::utils::logger::Logger::new($name));
|
||||
};
|
||||
|
||||
(const $name:expr) => {
|
||||
$crate::once_cell::sync::Lazy::new(|| $crate::utils::logger::Logger::new($name))
|
||||
};
|
||||
|
||||
($name:expr) => {
|
||||
$crate::logger::utils::Logger::new($name)
|
||||
};
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
mod macros;
|
||||
mod requests;
|
||||
mod server;
|
||||
mod types;
|
||||
mod utils;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub use anyhow::Context as ErrorContext;
|
||||
pub use anyhow::Result;
|
||||
pub use once_cell;
|
||||
|
||||
use crate::server::ServerConfig;
|
||||
use crate::utils::vfs;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let root = PathBuf::from("");
|
||||
let config: ServerConfig = vfs::read_config(&root.join("config.json"))?;
|
||||
config.build(&root).run()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{ErrorContext, logger};
|
||||
use crate::{server::Server, utils::client::Client};
|
||||
|
||||
logger!(LOGGER "Auth");
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AuthApiRes {
|
||||
user_id: u32,
|
||||
}
|
||||
|
||||
pub fn auth(_server: &Arc<Server>, client: &mut Client, token: &str) -> crate::Result<u32> {
|
||||
let mut res = ureq::get(format!(
|
||||
"http://localhost:3000/api/auth?intents=server&token={token}"
|
||||
))
|
||||
.call()
|
||||
.context("Failed to authenticate")?;
|
||||
let api_res: AuthApiRes = serde_json::from_str(&res.body_mut().read_to_string()?)?;
|
||||
client.set_uuid(api_res.user_id);
|
||||
LOGGER.info(format!("{} successfully authenticated", api_res.user_id));
|
||||
Ok(api_res.user_id)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{server::Server, types, utils::client::Client};
|
||||
|
||||
crate::logger!(LOGGER "Message Manager");
|
||||
|
||||
pub fn send(
|
||||
server: &Arc<Server>,
|
||||
client: &Client,
|
||||
channel_id: &str,
|
||||
contents: &str,
|
||||
) -> crate::Result<()> {
|
||||
LOGGER.info(format!("SendMessage to {channel_id}: {contents}"));
|
||||
|
||||
if contents.is_empty() {
|
||||
client.send(types::ResponseError::InvalidRequest(format!(
|
||||
"Invalid message: empty message"
|
||||
)))?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let msg = server.db.insert_message(
|
||||
&channel_id,
|
||||
client.get_uuid()?,
|
||||
&contents,
|
||||
chrono::Utc::now().timestamp(),
|
||||
)?;
|
||||
|
||||
let server = server.clone();
|
||||
|
||||
for c in server.clients.lock().unwrap().iter() {
|
||||
let c = c.clone();
|
||||
let server = server.clone();
|
||||
let msg = msg.clone();
|
||||
std::thread::spawn(move || {
|
||||
server
|
||||
.wrap_err(&c, c.send(types::ServerMessage::MessageCreate(msg)))
|
||||
.expect("Failed to broadcast");
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn edit(
|
||||
_server: &Arc<Server>,
|
||||
_client: &Client,
|
||||
message_id: usize,
|
||||
new_contents: &str,
|
||||
) -> crate::Result<()> {
|
||||
LOGGER.info(format!("EditMessage {message_id}: {new_contents}"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete(_server: &Arc<Server>, _client: &Client, message_id: usize) -> crate::Result<()> {
|
||||
LOGGER.info(format!("DeleteMessage {message_id}"));
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
pub mod auth;
|
||||
pub mod message;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
server::Server,
|
||||
types::{ClientMessage, WsMessage},
|
||||
utils::client::Client,
|
||||
};
|
||||
|
||||
impl Server {
|
||||
pub fn call_request(
|
||||
self: &Arc<Self>,
|
||||
req: &WsMessage<ClientMessage>,
|
||||
client: &Client,
|
||||
) -> crate::Result<()> {
|
||||
match req {
|
||||
WsMessage::Message(req) => match req {
|
||||
ClientMessage::SendMessage {
|
||||
channel_id,
|
||||
contents,
|
||||
} => {
|
||||
message::send(self, client, channel_id, contents)?;
|
||||
}
|
||||
|
||||
ClientMessage::EditMessage {
|
||||
message_id,
|
||||
new_contents,
|
||||
} => message::edit(self, client, *message_id, new_contents)?,
|
||||
|
||||
ClientMessage::DeleteMessage { message_id } => {
|
||||
message::delete(self, client, *message_id)?
|
||||
}
|
||||
},
|
||||
|
||||
WsMessage::Binary(b) => {
|
||||
Self::LOGGER.info(format!("Binary message: {b:?}"));
|
||||
}
|
||||
|
||||
WsMessage::String(s) => {
|
||||
Self::LOGGER.info(format!("String message: {s}"));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
net::{TcpListener, TcpStream},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
requests, types,
|
||||
utils::{self, client::Client, database},
|
||||
};
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
server_name: String,
|
||||
server_id: String,
|
||||
port: u16,
|
||||
channels: Vec<types::data::Channel>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Server {
|
||||
pub root: PathBuf,
|
||||
pub config: ServerConfig,
|
||||
pub clients: Mutex<HashSet<Client>>,
|
||||
pub db: utils::database::Database,
|
||||
}
|
||||
|
||||
impl Default for ServerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: 7080,
|
||||
server_name: format!("Server Name"),
|
||||
server_id: format!("offline-server"),
|
||||
channels: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
pub fn build(self, root: &Path) -> Arc<Server> {
|
||||
Server::new_config(root, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Server {
|
||||
crate::logger!(LOGGER "Server");
|
||||
|
||||
pub fn new(root: &Path) -> Arc<Self> {
|
||||
Self::new_config(root, ServerConfig::default())
|
||||
}
|
||||
|
||||
pub fn new_config(root: &Path, config: ServerConfig) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
db: database::Database::new(&config).unwrap(),
|
||||
root: root.to_path_buf(),
|
||||
config,
|
||||
clients: Mutex::new(HashSet::new()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(self: &Arc<Self>) -> crate::Result<()> {
|
||||
// Start server
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", self.config.port))?;
|
||||
Self::LOGGER.info(format!("Server listening at 0.0.0.0:{}", self.config.port));
|
||||
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(stream) => {
|
||||
std::thread::spawn({
|
||||
let srv = self.clone();
|
||||
|
||||
move || {
|
||||
let Some(client) = Self::LOGGER
|
||||
.extract(srv.init_client(stream), "Failed to initialize client")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
Self::LOGGER.extract(
|
||||
srv.wrap_err(&client, srv.handle_client(&client)),
|
||||
"Client handler failed",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
Self::LOGGER.error(format!("Connection failed: {e}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_client(self: &Arc<Self>, stream: TcpStream) -> anyhow::Result<Client> {
|
||||
Self::LOGGER.info(format!("New connection: {}", stream.peer_addr()?));
|
||||
// Initialize client
|
||||
let mut client = Client::new(stream)?;
|
||||
|
||||
// Initialize handshake
|
||||
self.wrap_err(
|
||||
&client,
|
||||
client.send(types::handshake::ServerDetails {
|
||||
name: self.config.server_name.clone(),
|
||||
id: self.config.server_id.clone(),
|
||||
version: format!("0.0.1"),
|
||||
}),
|
||||
)?;
|
||||
|
||||
match self.wrap_err(&client, client.read_t::<types::handshake::ClientDetails>())? {
|
||||
Some(types::WsMessage::Message(types::handshake::ClientDetails {
|
||||
auth_token,
|
||||
last_message,
|
||||
..
|
||||
})) => {
|
||||
let auth_res = requests::auth::auth(self, &mut client, &auth_token);
|
||||
let uuid = self.wrap_err(&client, auth_res)?;
|
||||
self.wrap_err(
|
||||
&client,
|
||||
client.send(types::ServerMessage::Authenticated {
|
||||
uuid,
|
||||
messages: if let Some(i) = last_message {
|
||||
self.wrap_err(&client, self.db.get_messages_after_id(i))?
|
||||
} else {
|
||||
self.wrap_err(&client, self.db.get_messages_after_id(0))?
|
||||
},
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
Some(v) => {
|
||||
self.wrap_err(
|
||||
&client,
|
||||
client.send(types::ResponseError::InvalidHandshake(format!(
|
||||
"Invalid handshake: {v:?}"
|
||||
))),
|
||||
)?;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
// Insert to the set of all connected clients
|
||||
self.clients.lock().unwrap().insert(client.clone());
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
fn handle_client(self: &Arc<Self>, client: &Client) -> anyhow::Result<()> {
|
||||
// The main req/res loop
|
||||
loop {
|
||||
let req = client.read()?;
|
||||
if let Some(r) = &req {
|
||||
self.wrap_err(&client, self.call_request(r, &client))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When there is a error it removes the client
|
||||
pub fn wrap_err<T, E: std::fmt::Display>(
|
||||
self: &Arc<Self>,
|
||||
client: &Client,
|
||||
res: std::result::Result<T, E>,
|
||||
) -> std::result::Result<T, E> {
|
||||
if let Err(e) = &res {
|
||||
self.clients.lock().unwrap().remove(&client);
|
||||
if client
|
||||
.send(types::ResponseError::InternalError(e.to_string()))
|
||||
.is_err()
|
||||
{}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Messages sent *from the client* (user’s app) to the server
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "params", rename_all = "snake_case")]
|
||||
pub enum ClientMessage {
|
||||
/// Send a message to a channel
|
||||
SendMessage {
|
||||
channel_id: String,
|
||||
contents: String,
|
||||
},
|
||||
|
||||
/// Edit a message (if allowed)
|
||||
EditMessage {
|
||||
message_id: usize,
|
||||
new_contents: String,
|
||||
},
|
||||
|
||||
/// Delete a message (if allowed)
|
||||
DeleteMessage { message_id: usize },
|
||||
}
|
||||
|
||||
/// Messages sent *from the server* to the client
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "params", rename_all = "snake_case")]
|
||||
pub enum ServerMessage {
|
||||
/// Successful authentication
|
||||
Authenticated {
|
||||
uuid: u32,
|
||||
messages: Vec<data::Message>,
|
||||
},
|
||||
|
||||
TempMessage {
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// A new message in a channel
|
||||
MessageCreate(data::Message),
|
||||
|
||||
/// A message was edited
|
||||
MessageUpdate(data::Message),
|
||||
|
||||
/// A message was deleted
|
||||
MessageDelete {
|
||||
channel_id: String,
|
||||
message_id: usize,
|
||||
},
|
||||
|
||||
/// Presence updates
|
||||
PresenceUpdate {
|
||||
user_id: String,
|
||||
status: String,
|
||||
},
|
||||
|
||||
/// Typing indicator
|
||||
Typing {
|
||||
user_id: String,
|
||||
channel_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "error", content = "message", rename_all = "snake_case")]
|
||||
pub enum ResponseError {
|
||||
InvalidRequest(String),
|
||||
InvalidHandshake(String),
|
||||
Unauthorized(String),
|
||||
NotFound(String),
|
||||
InternalError(String),
|
||||
}
|
||||
|
||||
/// WebSocket wrapper
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WsMessage<T: Serialize + for<'de> Deserialize<'de>> {
|
||||
Message(T),
|
||||
Binary(Vec<u8>),
|
||||
String(String),
|
||||
}
|
||||
|
||||
/// Shared data structures
|
||||
pub mod data {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Message {
|
||||
pub id: i64,
|
||||
pub channel_id: String,
|
||||
pub from: u32,
|
||||
pub contents: String,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Channel {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub kind: ChannelKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChannelKind {
|
||||
Text,
|
||||
Voice,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod handshake {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerDetails {
|
||||
pub version: String,
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClientDetails {
|
||||
pub version: String,
|
||||
pub auth_token: String,
|
||||
pub last_message: Option<usize>,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
use std::{
|
||||
hash::{Hash, Hasher},
|
||||
io::{self, Read, Write},
|
||||
net::{SocketAddr, TcpStream},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::{ClientMessage, WsMessage};
|
||||
|
||||
pub mod handshake {
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as Base64;
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::TcpStream;
|
||||
|
||||
const WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
pub fn handle_websocket_handshake(stream: &mut TcpStream) -> std::io::Result<()> {
|
||||
let mut reader = BufReader::new(stream.try_clone()?);
|
||||
let mut request_line = String::new();
|
||||
reader.read_line(&mut request_line)?;
|
||||
|
||||
if !request_line.starts_with("GET") {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Invalid HTTP method",
|
||||
));
|
||||
}
|
||||
|
||||
let mut headers = HashMap::new();
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
line.clear();
|
||||
let bytes = reader.read_line(&mut line)?;
|
||||
if bytes == 0 || line == "\r\n" {
|
||||
break;
|
||||
}
|
||||
if let Some((k, v)) = line.split_once(':') {
|
||||
headers.insert(k.trim().to_lowercase(), v.trim().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let key = headers.get("sec-websocket-key").ok_or_else(|| {
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing Sec-WebSocket-Key")
|
||||
})?;
|
||||
|
||||
if headers.get("upgrade").map(|v| v.to_lowercase()) != Some("websocket".to_string()) {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Missing or invalid Upgrade header",
|
||||
));
|
||||
}
|
||||
|
||||
if !headers
|
||||
.get("connection")
|
||||
.map(|v| v.to_lowercase().contains("upgrade"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Missing or invalid Connection header",
|
||||
));
|
||||
}
|
||||
|
||||
// Optional: validate Sec-WebSocket-Version == 13 (most common)
|
||||
if let Some(ver) = headers.get("sec-websocket-version") {
|
||||
if ver.trim() != "13" {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Unsupported Sec-WebSocket-Version",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Compute accept key
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(key.as_bytes());
|
||||
hasher.update(WS_GUID.as_bytes());
|
||||
let hash = hasher.finalize();
|
||||
let accept_key = Base64.encode(hash);
|
||||
|
||||
// Note: include Sec-WebSocket-Protocol handling if you support subprotocols
|
||||
let response = format!(
|
||||
"HTTP/1.1 101 Switching Protocols\r\n\
|
||||
Upgrade: websocket\r\n\
|
||||
Connection: Upgrade\r\n\
|
||||
Sec-WebSocket-Accept: {}\r\n\
|
||||
Sec-WebSocket-Version: 13\r\n\r\n",
|
||||
accept_key
|
||||
);
|
||||
|
||||
stream.write_all(response.as_bytes())?;
|
||||
stream.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client(TcpStream, Option<u32>);
|
||||
|
||||
impl Client {
|
||||
/// Create a client with no timeouts
|
||||
pub fn new(mut stream: TcpStream) -> crate::Result<Self> {
|
||||
handshake::handle_websocket_handshake(&mut stream)?;
|
||||
Ok(Client(stream, None))
|
||||
}
|
||||
|
||||
/// Create a client and set read/write timeouts (useful in prod)
|
||||
pub fn with_timeouts(
|
||||
mut stream: TcpStream,
|
||||
read_timeout: Option<Duration>,
|
||||
write_timeout: Option<Duration>,
|
||||
) -> crate::Result<Self> {
|
||||
if let Some(t) = read_timeout {
|
||||
stream.set_read_timeout(Some(t))?;
|
||||
}
|
||||
if let Some(t) = write_timeout {
|
||||
stream.set_write_timeout(Some(t))?;
|
||||
}
|
||||
handshake::handle_websocket_handshake(&mut stream)?;
|
||||
Ok(Client(stream, None))
|
||||
}
|
||||
|
||||
/// Send a close frame and flush. `code` is a WebSocket close code (e.g., 1000 normal).
|
||||
pub fn send_close(&self, code: u16, reason: &str) -> crate::Result<()> {
|
||||
let mut stream = self.0.try_clone()?;
|
||||
|
||||
// control frames must be <= 125 bytes
|
||||
let mut payload = Vec::new();
|
||||
payload.extend_from_slice(&code.to_be_bytes());
|
||||
payload.extend_from_slice(reason.as_bytes());
|
||||
if payload.len() > 125 {
|
||||
return Err(anyhow!("close reason too long").into());
|
||||
}
|
||||
|
||||
let mut frame = Vec::with_capacity(2 + payload.len());
|
||||
frame.push(0x88); // FIN=1, opcode=0x8 (Close)
|
||||
frame.push(payload.len() as u8); // server->client MUST NOT mask
|
||||
frame.extend_from_slice(&payload);
|
||||
|
||||
stream.write_all(&frame)?;
|
||||
stream.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a pong with given payload (control frames must be <=125)
|
||||
fn send_pong(&self, payload: &[u8]) -> crate::Result<()> {
|
||||
let mut stream = self.0.try_clone()?;
|
||||
|
||||
if payload.len() > 125 {
|
||||
return Err(anyhow!("pong payload too long").into());
|
||||
}
|
||||
let mut frame = Vec::with_capacity(2 + payload.len());
|
||||
frame.push(0x8A); // FIN=1, opcode=0xA (Pong)
|
||||
frame.push(payload.len() as u8);
|
||||
frame.extend_from_slice(payload);
|
||||
stream.write_all(&frame)?;
|
||||
stream.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a text/binary frame (server->client must NOT mask)
|
||||
pub fn send<T: Serialize>(&self, m: T) -> crate::Result<()> {
|
||||
let mut stream = self.0.try_clone()?;
|
||||
|
||||
let payload = serde_json::to_string(&m)?;
|
||||
let payload_bytes = payload.as_bytes();
|
||||
let len = payload_bytes.len();
|
||||
|
||||
let mut header = Vec::new();
|
||||
header.push(0x81); // FIN=1, opcode=0x1 (text)
|
||||
|
||||
if len < 126 {
|
||||
header.push(len as u8);
|
||||
} else if len <= 65535 {
|
||||
header.push(126);
|
||||
header.extend_from_slice(&(len as u16).to_be_bytes());
|
||||
} else {
|
||||
header.push(127);
|
||||
header.extend_from_slice(&(len as u64).to_be_bytes());
|
||||
}
|
||||
|
||||
stream.write_all(&header)?;
|
||||
stream.write_all(payload_bytes)?;
|
||||
stream.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read a full WebSocket message, handling fragmentation and control frames.
|
||||
///
|
||||
/// Returns:
|
||||
/// - Ok(Some(WsMessage)) on an application message (text/binary)
|
||||
/// - Ok(None) if the connection should be closed (close received / read EOF)
|
||||
/// - Err on protocol or IO errors.
|
||||
pub fn read_t<T: Serialize + for<'de> Deserialize<'de>>(
|
||||
&self,
|
||||
) -> crate::Result<Option<WsMessage<T>>> {
|
||||
let mut stream = self.0.try_clone()?;
|
||||
|
||||
let mut message_payload = Vec::new();
|
||||
|
||||
loop {
|
||||
// read the 2-byte header
|
||||
let mut header = [0u8; 2];
|
||||
if let Err(e) = stream.read_exact(&mut header) {
|
||||
if e.kind() == io::ErrorKind::UnexpectedEof || e.kind() == io::ErrorKind::BrokenPipe
|
||||
{
|
||||
return Ok(None); // treat EOF as closed
|
||||
}
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
let fin = header[0] & 0x80 != 0;
|
||||
let opcode = header[0] & 0x0F;
|
||||
let masked = header[1] & 0x80 != 0;
|
||||
let mut payload_len = (header[1] & 0x7F) as u64;
|
||||
|
||||
// Extended payload lengths
|
||||
if payload_len == 126 {
|
||||
let mut ext_len = [0u8; 2];
|
||||
stream.read_exact(&mut ext_len)?;
|
||||
payload_len = u16::from_be_bytes(ext_len) as u64;
|
||||
} else if payload_len == 127 {
|
||||
let mut ext_len = [0u8; 8];
|
||||
stream.read_exact(&mut ext_len)?;
|
||||
payload_len = u64::from_be_bytes(ext_len);
|
||||
}
|
||||
|
||||
// Mask key (client→server MUST be masked)
|
||||
let mut mask = [0u8; 4];
|
||||
if masked {
|
||||
stream.read_exact(&mut mask)?;
|
||||
} else {
|
||||
let _ = self.send_close(1002, "Client frames must be masked");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Control frame checks
|
||||
if matches!(opcode, 0x8 | 0x9 | 0xA) {
|
||||
if payload_len > 125 {
|
||||
let _ = self.send_close(1002, "Control frame too large");
|
||||
return Ok(None);
|
||||
}
|
||||
if !fin {
|
||||
let _ = self.send_close(1002, "Control frames must not be fragmented");
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// Read payload + unmask
|
||||
let mut payload = vec![0u8; payload_len as usize];
|
||||
if payload_len > 0 {
|
||||
stream.read_exact(&mut payload)?;
|
||||
for i in 0..payload.len() {
|
||||
payload[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
|
||||
match opcode {
|
||||
0x0 | 0x1 | 0x2 => {
|
||||
// Continuation / Text / Binary
|
||||
message_payload.extend(payload);
|
||||
if fin {
|
||||
break; // got full message
|
||||
} else {
|
||||
continue; // wait for more fragments
|
||||
}
|
||||
}
|
||||
0x8 => {
|
||||
// Close
|
||||
let (code, reason) = if payload.len() >= 2 {
|
||||
let code = u16::from_be_bytes([payload[0], payload[1]]);
|
||||
let reason = if payload.len() > 2 {
|
||||
String::from_utf8_lossy(&payload[2..]).into_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
(code, reason)
|
||||
} else {
|
||||
(1000, String::new())
|
||||
};
|
||||
let _ = self.send_close(code, &reason);
|
||||
return Ok(None);
|
||||
}
|
||||
0x9 => {
|
||||
// Ping → respond with Pong
|
||||
let _ = self.send_pong(&payload);
|
||||
continue;
|
||||
}
|
||||
0xA => {
|
||||
// Pong → ignore
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
let _ = self.send_close(1002, "Unsupported opcode");
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try parsing JSON into ClientMessage
|
||||
let message = match String::from_utf8(message_payload.clone()) {
|
||||
Ok(text) => match serde_json::from_str(&text) {
|
||||
Ok(msg) => WsMessage::Message(msg),
|
||||
Err(_) => WsMessage::String(text),
|
||||
},
|
||||
Err(_) => WsMessage::Binary(message_payload),
|
||||
};
|
||||
|
||||
Ok(Some(message))
|
||||
}
|
||||
|
||||
/// Read a full WebSocket message, handling fragmentation and control frames.
|
||||
///
|
||||
/// Returns:
|
||||
/// - Ok(Some(WsMessage)) on an application message (text/binary)
|
||||
/// - Ok(None) if the connection should be closed (close received / read EOF)
|
||||
/// - Err on protocol or IO errors.
|
||||
pub fn read(&self) -> crate::Result<Option<WsMessage<ClientMessage>>> {
|
||||
self.read_t()
|
||||
}
|
||||
|
||||
pub fn get_uuid(&self) -> crate::Result<u32> {
|
||||
self.1.ok_or(anyhow!("Invalid UUID"))
|
||||
}
|
||||
|
||||
pub fn set_uuid(&mut self, uuid: u32) {
|
||||
self.1 = Some(uuid)
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> crate::Result<SocketAddr> {
|
||||
Ok(self.0.peer_addr().unwrap_or(self.0.local_addr()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Client {
|
||||
fn clone(&self) -> Self {
|
||||
Client(self.0.try_clone().expect("failed to clone TcpStream"), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Client {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.addr().unwrap() == other.addr().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Client {}
|
||||
|
||||
impl Hash for Client {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.addr().unwrap().hash(state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
use crate::{server::ServerConfig, types::data::Message};
|
||||
use rusqlite::{Connection, Result, params};
|
||||
|
||||
pub struct Database(Connection);
|
||||
|
||||
// General use case
|
||||
impl Database {
|
||||
pub fn new(_config: &ServerConfig) -> Option<Self> {
|
||||
let conn = Connection::open("main.db").ok()?;
|
||||
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS chat (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
channel_id TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
contents TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
Some(Database(conn))
|
||||
}
|
||||
}
|
||||
|
||||
// For chat messages
|
||||
impl Database {
|
||||
/// Insert a message into the DB
|
||||
pub fn insert_message(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
user_id: u32,
|
||||
contents: &str,
|
||||
timestamp: i64,
|
||||
) -> Result<Message> {
|
||||
self.0.execute(
|
||||
"INSERT INTO chat (channel_id, user_id, contents, timestamp)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
params![channel_id, user_id, contents, timestamp],
|
||||
)?;
|
||||
|
||||
let id = self.0.last_insert_rowid();
|
||||
|
||||
Ok(Message {
|
||||
id,
|
||||
channel_id: channel_id.to_string(),
|
||||
from: user_id,
|
||||
contents: contents.to_string(),
|
||||
timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete a message from the DB
|
||||
pub fn delete_message(&self, message_id: usize) -> Result<()> {
|
||||
self.0
|
||||
.execute("DELETE FROM chat WHERE id = ?1;", params![message_id])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a message from the DB
|
||||
pub fn edit_message(&self, message_id: usize, contents: &str) -> Result<()> {
|
||||
self.0.execute(
|
||||
"UPDATE table_name
|
||||
SET contents = ?2
|
||||
WHERE id = ?1;
|
||||
",
|
||||
params![message_id, contents],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a message by its ID
|
||||
pub fn get_message_by_id(&self, message_id: usize) -> Result<Option<Message>> {
|
||||
let mut stmt = self.0.prepare(
|
||||
"SELECT id, channel_id, user_id, contents, timestamp
|
||||
FROM chat
|
||||
WHERE id = ?1",
|
||||
)?;
|
||||
|
||||
let mut rows = stmt.query_map(params![message_id], |row| {
|
||||
Ok((
|
||||
row.get::<_, i64>(0)?, // id
|
||||
row.get::<_, String>(1)?, // channel_id
|
||||
row.get::<_, u32>(2)?, // user_id
|
||||
row.get::<_, String>(3)?, // contents
|
||||
row.get::<_, i64>(4)?, // timestamp
|
||||
))
|
||||
})?;
|
||||
|
||||
if let Some(row) = rows.next() {
|
||||
let (id, channel_id, user_id, contents, timestamp) = row?;
|
||||
return Ok(Some(Message {
|
||||
id,
|
||||
channel_id,
|
||||
from: user_id,
|
||||
contents,
|
||||
timestamp,
|
||||
}));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get all messages with an ID greater than the given one
|
||||
pub fn get_messages_after_id(&self, message_id: usize) -> Result<Vec<Message>> {
|
||||
let mut stmt = self.0.prepare(
|
||||
"SELECT id, channel_id, user_id, contents, timestamp
|
||||
FROM chat
|
||||
WHERE id > ?1
|
||||
ORDER BY id ASC",
|
||||
)?;
|
||||
|
||||
let rows = stmt.query_map(params![message_id], |row| {
|
||||
Ok(Message {
|
||||
id: row.get::<_, i64>(0)?,
|
||||
channel_id: row.get::<_, String>(1)?,
|
||||
from: row.get::<_, u32>(2)?,
|
||||
contents: row.get::<_, String>(3)?,
|
||||
timestamp: row.get::<_, i64>(4)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut messages = Vec::new();
|
||||
for row in rows {
|
||||
messages.push(row?);
|
||||
}
|
||||
|
||||
Ok(messages)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Database {}
|
||||
unsafe impl Sync for Database {}
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
pub struct Logger {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
pub fn new<T: Display>(name: T) -> Self {
|
||||
Logger {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn info<T: Display>(&self, message: T) {
|
||||
println!("\x1b[32mINFO\x1b[0m ({}) › {}", self.name, message);
|
||||
}
|
||||
|
||||
pub fn warn<T: Display>(&self, message: T) {
|
||||
println!("\x1b[33mWARN\x1b[0m ({}) › {}", self.name, message);
|
||||
}
|
||||
|
||||
pub fn error<T: Display>(&self, message: T) {
|
||||
println!("\x1b[31mERROR\x1b[0m ({}) › {}", self.name, message);
|
||||
}
|
||||
|
||||
pub fn extract<T, E: Display, D: Display>(&self, v: Result<T, E>, m: D) -> Option<T> {
|
||||
match v {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
self.error(format!("{m}: {e}"));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_panic<T, E: Display, D: Display>(&self, v: Result<T, E>, m: D) -> T {
|
||||
match v {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
self.error(format!("{m}: {e}"));
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod client;
|
||||
pub mod database;
|
||||
pub mod logger;
|
||||
pub mod vfs;
|
||||
@@ -0,0 +1,84 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
logger! {
|
||||
const LOGGER "File"
|
||||
}
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::logger;
|
||||
|
||||
pub fn dir(path: &Path) -> crate::Result<()> {
|
||||
if !path.exists() {
|
||||
LOGGER.info(format!("Directory {path:?} does not exist, creating it"));
|
||||
fs::create_dir_all(path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(path: &Path, default_content: &str) -> crate::Result<String> {
|
||||
if !path.exists() {
|
||||
LOGGER.info(format!(
|
||||
"File {path:?} does not exist, creating it with default contents"
|
||||
));
|
||||
write(path, default_content)?;
|
||||
return Ok(default_content.to_string());
|
||||
}
|
||||
|
||||
Ok(fs::read_to_string(path)?)
|
||||
}
|
||||
|
||||
pub fn read_bytes<'a>(path: &Path, default_content: Vec<u8>) -> crate::Result<Vec<u8>> {
|
||||
if !path.exists() {
|
||||
LOGGER.info(format!(
|
||||
"File {path:?} does not exist, creating it with default contents"
|
||||
));
|
||||
write_bytes(path, &default_content)?;
|
||||
return Ok(default_content);
|
||||
}
|
||||
|
||||
Ok(fs::read(path)?)
|
||||
}
|
||||
|
||||
pub fn read_config<T: Default + Serialize + for<'de> Deserialize<'de>>(
|
||||
path: &Path,
|
||||
) -> crate::Result<T> {
|
||||
if !path.exists() {
|
||||
LOGGER.info(format!(
|
||||
"File {path:?} does not exist, creating it with default contents"
|
||||
));
|
||||
let default = T::default();
|
||||
write_config(path, &default)?;
|
||||
return Ok(default);
|
||||
}
|
||||
|
||||
let read = fs::read_to_string(path)?;
|
||||
Ok(serde_json::from_str::<T>(&read)?)
|
||||
}
|
||||
|
||||
pub fn write(path: &Path, content: &str) -> crate::Result<()> {
|
||||
dir(path.parent().ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidFilename,
|
||||
"File doesn't have a parent assigned, example: `config/config.json`",
|
||||
))?)?;
|
||||
fs::write(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_bytes(path: &Path, content: &[u8]) -> crate::Result<()> {
|
||||
dir(path.parent().ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidFilename,
|
||||
"File doesn't have a parent assigned, example: `config/config.json`",
|
||||
))?)?;
|
||||
fs::write(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_config<T: Serialize>(path: &Path, content: &T) -> crate::Result<()> {
|
||||
dir(path.parent().ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidFilename,
|
||||
"File doesn't have a parent assigned, example: `config/config.json`",
|
||||
))?)?;
|
||||
fs::write(path, &serde_json::to_string_pretty(content)?)?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user