Merging with prototype
This commit is contained in:
commit
ad50f83cf7
28 changed files with 2784 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
*.test
|
||||
target
|
710
Cargo.lock
generated
Normal file
710
Cargo.lock
generated
Normal file
|
@ -0,0 +1,710 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
||||
|
||||
[[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.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[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-epoch"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"once_cell",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "harsh-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"harsh_common",
|
||||
"sled",
|
||||
"telecomande",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "harsh_common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "harsh_server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"harsh_common",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sled",
|
||||
"telecomande",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[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.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
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.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
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 = "ryu"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sled"
|
||||
version = "0.34.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"fs2",
|
||||
"fxhash",
|
||||
"libc",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "telecomande"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e2911385f0e73674cd9a881c3121b27a78bbc1a4f25fce42989e85c6b571679"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
||||
|
||||
[[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-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",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[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_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[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_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
3
Cargo.toml
Normal file
3
Cargo.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[workspace]
|
||||
members = ["harsh-client", "harsh-server", "harsh-common"]
|
||||
|
18
README.md
Normal file
18
README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Harsh
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
An enbeded Harmony server implementation written in rust.
|
||||
|
||||
> **Warning**
|
||||
> This project is in early design phase, it is non conforming and very few features are implemented.
|
||||
|
||||
## Usage
|
||||
|
||||
To launch the server, use the `start-server.sh` script
|
||||
|
||||
To launch the debug client, use the `start-client.sh` script
|
BIN
assets/icon_256.png
Normal file
BIN
assets/icon_256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
assets/icon_512.png
Normal file
BIN
assets/icon_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
1
harsh-client/.gitignore
vendored
Normal file
1
harsh-client/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
12
harsh-client/Cargo.toml
Normal file
12
harsh-client/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "harsh-client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
sled = "0.34.7"
|
||||
telecomande = "1.2.2"
|
||||
tokio = { version = "1.20.1", features = ["full"] }
|
||||
harsh_common = { path = "../harsh-common" }
|
192
harsh-client/src/commands.rs
Normal file
192
harsh-client/src/commands.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
use harsh_common::ClientRequest;
|
||||
|
||||
pub enum Command {
|
||||
Help,
|
||||
Request(ClientRequest),
|
||||
}
|
||||
|
||||
pub fn parse(input: &str) -> Option<Command> {
|
||||
let mut parts = smart_split(input).into_iter();
|
||||
let command = match parts.next()?.as_str() {
|
||||
"help" => return Some(Command::Help),
|
||||
"ping" => {
|
||||
let rest = parts.collect::<Box<[_]>>();
|
||||
let content = rest.join(" ");
|
||||
ClientRequest::new_ping(content)
|
||||
}
|
||||
"chanls" => ClientRequest::new_channel_list(),
|
||||
"chanadd" => {
|
||||
let name = parts.next()?;
|
||||
ClientRequest::new_channel_create(name)
|
||||
}
|
||||
"chandel" => {
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
ClientRequest::new_channel_delete(id)
|
||||
}
|
||||
"changname" => {
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
ClientRequest::new_channel_get_name(id)
|
||||
}
|
||||
"chansname" => {
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
let name = parts.next()?;
|
||||
ClientRequest::new_channel_set_name(id, name)
|
||||
}
|
||||
"msgls" => {
|
||||
let channel_id = parts.next()?.parse().ok()?;
|
||||
ClientRequest::new_message_list(channel_id)
|
||||
}
|
||||
"msgadd" => {
|
||||
let channel_id = parts.next()?.parse().ok()?;
|
||||
let content = parts.next()?;
|
||||
ClientRequest::new_message_create(channel_id, content)
|
||||
}
|
||||
"msgdel" => {
|
||||
let channel_id = parts.next()?.parse().ok()?;
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
ClientRequest::new_message_delete(channel_id, id)
|
||||
}
|
||||
"msggcont" => {
|
||||
let channel_id = parts.next()?.parse().ok()?;
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
ClientRequest::new_message_get_content(channel_id, id)
|
||||
}
|
||||
"msgscont" => {
|
||||
let channel_id = parts.next()?.parse().ok()?;
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
let content = parts.next()?;
|
||||
ClientRequest::new_message_set_content(channel_id, id, content)
|
||||
}
|
||||
"usrls" => ClientRequest::new_user_list(),
|
||||
"usradd" => {
|
||||
let name = parts.next()?;
|
||||
let pass = parts.next()?;
|
||||
ClientRequest::new_user_create(name, pass)
|
||||
}
|
||||
"usrdel" => {
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
ClientRequest::new_user_delete(id)
|
||||
}
|
||||
"usrgname" => {
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
ClientRequest::new_user_get_name(id)
|
||||
}
|
||||
"usrsname" => {
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
let name = parts.next()?;
|
||||
ClientRequest::new_user_set_name(id, name)
|
||||
}
|
||||
"usrspass" => {
|
||||
let id = parts.next()?.parse().ok()?;
|
||||
let pass = parts.next()?;
|
||||
ClientRequest::new_user_set_pass(id, pass)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(Command::Request(command))
|
||||
}
|
||||
|
||||
pub const CMDS: &'static [Description] = &[
|
||||
// all commands
|
||||
Description::new("help", &[], "returns a help message"),
|
||||
Description::new(
|
||||
"ping",
|
||||
&["content"],
|
||||
"sends a ping with the specified content",
|
||||
),
|
||||
Description::new("chanls", &[], "list channels"),
|
||||
Description::new("chanadd", &["name"], "creates a new channel"),
|
||||
Description::new("chandel", &["id"], "delete a channel by its id"),
|
||||
Description::new("changname", &["id"], "get a channel's name"),
|
||||
Description::new("chansname", &["id", "name"], "set a channel's name"),
|
||||
Description::new("msgls", &["channel_id"], "list messages"),
|
||||
Description::new("msgadd", &["channel_id", "content"], "create a message"),
|
||||
Description::new("msgdel", &["channel_id", "id"], "delete a message"),
|
||||
Description::new("msggcont", &["channel_id", "id"], "get a message's content"),
|
||||
Description::new(
|
||||
"msgscont",
|
||||
&["channel_id", "id", "content"],
|
||||
"set a message's content",
|
||||
),
|
||||
Description::new("usrls", &[], "list users"),
|
||||
Description::new("usradd", &["name", "pass"], "add a user"),
|
||||
Description::new("usrdel", &["id"], "delete a user"),
|
||||
Description::new("usrgname", &["id"], "get a user name"),
|
||||
Description::new("usrsname", &["id", "name"], "set a user name"),
|
||||
Description::new("usrspass", &["id", "pass"], "set a user pass"),
|
||||
];
|
||||
|
||||
pub fn smart_split(input: &str) -> Vec<String> {
|
||||
let input = input.trim();
|
||||
let mut result = Vec::new();
|
||||
|
||||
let mut capturing = false;
|
||||
let mut ignoring = false;
|
||||
let mut current = String::new();
|
||||
for char in input.chars() {
|
||||
let char: char = char;
|
||||
if ignoring {
|
||||
current.push(char);
|
||||
ignoring = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
match char {
|
||||
'\\' => ignoring = true,
|
||||
'"' => capturing = !capturing,
|
||||
' ' if !capturing => {
|
||||
result.push(current);
|
||||
current = String::new();
|
||||
}
|
||||
_ => current.push(char),
|
||||
}
|
||||
}
|
||||
result.push(current);
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split() {
|
||||
assert_eq!(
|
||||
smart_split("hello world"),
|
||||
vec!["hello".to_string(), "world".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
smart_split(r#""lorem ipsum" "dolor amit""#),
|
||||
vec!["lorem ipsum".to_string(), "dolor amit".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
smart_split(r#"lorem "ipsum do"lor "amit""#),
|
||||
vec![
|
||||
"lorem".to_string(),
|
||||
"ipsum dolor".to_string(),
|
||||
"amit".to_string()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
pub struct Description {
|
||||
name: &'static str,
|
||||
params: &'static [&'static str],
|
||||
desc: &'static str,
|
||||
}
|
||||
|
||||
impl Description {
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
params: &'static [&'static str],
|
||||
desc: &'static str,
|
||||
) -> Self {
|
||||
Self { name, desc, params }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help() {
|
||||
for &Description { name, params, desc } in CMDS {
|
||||
let mut usage = params.iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
||||
usage.insert(0, name.to_string());
|
||||
let usage = usage.join(" ");
|
||||
println!("{name}:\n\tusage:\t\t{usage}\n\tdescription:\t{desc}");
|
||||
}
|
||||
}
|
64
harsh-client/src/main.rs
Normal file
64
harsh-client/src/main.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::{
|
||||
io::{stdout, Write},
|
||||
process::exit,
|
||||
};
|
||||
|
||||
use harsh_common::ServerRequest;
|
||||
use tokio::{
|
||||
io::{stdin, AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
const ADDRESS: &'static str = "localhost:42069";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("[main/info] starting client ...");
|
||||
let stream = TcpStream::connect(ADDRESS).await.unwrap();
|
||||
println!("[main/info] connected to '{ADDRESS}'");
|
||||
let (reader, writer) = stream.into_split();
|
||||
tokio::spawn(async {
|
||||
let mut reader = BufReader::new(reader);
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
match reader.read_line(&mut line).await {
|
||||
Ok(0) => {
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
if let Some(parsed) = ServerRequest::try_parse(&line) {
|
||||
println!("[main/info] received '{parsed:?}'");
|
||||
}
|
||||
}
|
||||
println!("[main/info] connection closed, goodbye.");
|
||||
exit(0);
|
||||
});
|
||||
|
||||
let input_loop = tokio::spawn(async {
|
||||
let mut input = BufReader::new(stdin());
|
||||
let mut writer = writer;
|
||||
|
||||
loop {
|
||||
print!("$> ");
|
||||
stdout().lock().flush().unwrap();
|
||||
let mut line = String::new();
|
||||
input.read_line(&mut line).await.unwrap();
|
||||
let input = commands::parse(&line);
|
||||
match input {
|
||||
None => println!("[main/warn] failed to parse command"),
|
||||
Some(commands::Command::Help) => commands::help(),
|
||||
Some(commands::Command::Request(cmd)) => {
|
||||
println!("[main/info] sending..");
|
||||
writer.write_all(cmd.serialize().as_bytes()).await.unwrap();
|
||||
writer.write_all(b"\n").await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
println!("[main/info] awaiting input ...");
|
||||
input_loop.await.unwrap();
|
||||
}
|
||||
|
||||
mod commands;
|
2
harsh-common/.gitignore
vendored
Normal file
2
harsh-common/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
10
harsh-common/Cargo.toml
Normal file
10
harsh-common/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "harsh_common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
serde_json = "1.0.83"
|
355
harsh-common/src/client.rs
Normal file
355
harsh-common/src/client.rs
Normal file
|
@ -0,0 +1,355 @@
|
|||
#[derive(Debug)]
|
||||
pub struct Ping {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelList {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelCreate {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelDelete {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelGetName {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelSetName {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MessageList {
|
||||
pub channel_id: u64,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MessageCreate {
|
||||
pub channel_id: u64,
|
||||
pub content: String,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MessageDelete {
|
||||
pub channel_id: u64,
|
||||
pub id: u64,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MessageGetContent {
|
||||
pub channel_id: u64,
|
||||
pub id: u64,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MessageSetContent {
|
||||
pub channel_id: u64,
|
||||
pub id: u64,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserList {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserCreate {
|
||||
pub name: String,
|
||||
pub pass: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserDelete {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserGetName {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserSetName {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserGetPass {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserSetPass {
|
||||
pub id: u64,
|
||||
pub pass: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientRequest {
|
||||
Ping(Ping),
|
||||
ChannelList(ChannelList),
|
||||
ChannelCreate(ChannelCreate),
|
||||
ChannelDelete(ChannelDelete),
|
||||
ChannelGetName(ChannelGetName),
|
||||
ChannelSetName(ChannelSetName),
|
||||
|
||||
MessageList(MessageList),
|
||||
MessageCreate(MessageCreate),
|
||||
MessageDelete(MessageDelete),
|
||||
MessageGetContent(MessageGetContent),
|
||||
MessageSetContent(MessageSetContent),
|
||||
|
||||
UserList(UserList),
|
||||
UserCreate(UserCreate),
|
||||
UserDelete(UserDelete),
|
||||
UserGetName(UserGetName),
|
||||
UserSetName(UserSetName),
|
||||
UserSetPass(UserSetPass),
|
||||
}
|
||||
|
||||
impl ClientRequest {
|
||||
pub fn new_ping(content: String) -> Self {
|
||||
Self::Ping(Ping { content })
|
||||
}
|
||||
|
||||
pub fn new_channel_list() -> Self {
|
||||
Self::ChannelList(ChannelList {})
|
||||
}
|
||||
|
||||
pub fn new_channel_create(name: String) -> Self {
|
||||
Self::ChannelCreate(ChannelCreate { name })
|
||||
}
|
||||
|
||||
pub fn new_channel_delete(channel_id: u64) -> Self {
|
||||
Self::ChannelDelete(ChannelDelete { id: channel_id })
|
||||
}
|
||||
|
||||
pub fn new_channel_get_name(channel_id: u64) -> Self {
|
||||
Self::ChannelGetName(ChannelGetName { id: channel_id })
|
||||
}
|
||||
|
||||
pub fn new_channel_set_name(channel_id: u64, name: String) -> Self {
|
||||
Self::ChannelSetName(ChannelSetName {
|
||||
id: channel_id,
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_message_list(channel_id: u64) -> Self {
|
||||
Self::MessageList(MessageList { channel_id })
|
||||
}
|
||||
pub fn new_message_create(channel_id: u64, content: String) -> Self {
|
||||
Self::MessageCreate(MessageCreate {
|
||||
channel_id,
|
||||
content,
|
||||
})
|
||||
}
|
||||
pub fn new_message_delete(channel_id: u64, id: u64) -> Self {
|
||||
Self::MessageDelete(MessageDelete { channel_id, id })
|
||||
}
|
||||
pub fn new_message_get_content(channel_id: u64, id: u64) -> Self {
|
||||
Self::MessageGetContent(MessageGetContent { channel_id, id })
|
||||
}
|
||||
pub fn new_message_set_content(channel_id: u64, id: u64, content: String) -> Self {
|
||||
Self::MessageSetContent(MessageSetContent {
|
||||
channel_id,
|
||||
id,
|
||||
content,
|
||||
})
|
||||
}
|
||||
pub fn new_user_list() -> Self {
|
||||
Self::UserList(UserList {})
|
||||
}
|
||||
pub fn new_user_create(name: String, pass: String) -> Self {
|
||||
Self::UserCreate(UserCreate { name, pass })
|
||||
}
|
||||
pub fn new_user_delete(id: u64) -> Self {
|
||||
Self::UserDelete(UserDelete { id })
|
||||
}
|
||||
pub fn new_user_get_name(id: u64) -> Self {
|
||||
Self::UserGetName(UserGetName { id })
|
||||
}
|
||||
pub fn new_user_set_name(id: u64, name: String) -> Self {
|
||||
Self::UserSetName(UserSetName { id, name })
|
||||
}
|
||||
pub fn new_user_set_pass(id: u64, pass: String) -> Self {
|
||||
Self::UserSetPass(UserSetPass { id, pass })
|
||||
}
|
||||
|
||||
pub fn try_parse(line: &str) -> Option<Self> {
|
||||
use repr::Command::*;
|
||||
let command: repr::Command = serde_json::from_str(line).ok()?;
|
||||
let mapped = match command {
|
||||
ping { content } => Self::Ping(Ping { content }),
|
||||
channel_list {} => Self::ChannelList(ChannelList {}),
|
||||
channel_create { name } => Self::ChannelCreate(ChannelCreate { name }),
|
||||
channel_delete { id: channel_id } => {
|
||||
Self::ChannelDelete(ChannelDelete { id: channel_id })
|
||||
}
|
||||
channel_get_name { id: channel_id } => {
|
||||
Self::ChannelGetName(ChannelGetName { id: channel_id })
|
||||
}
|
||||
channel_set_name {
|
||||
id: channel_id,
|
||||
name,
|
||||
} => Self::ChannelSetName(ChannelSetName {
|
||||
id: channel_id,
|
||||
name,
|
||||
}),
|
||||
message_list { channel_id } => Self::MessageList(MessageList { channel_id }),
|
||||
message_create {
|
||||
channel_id,
|
||||
content,
|
||||
} => Self::MessageCreate(MessageCreate {
|
||||
channel_id,
|
||||
content,
|
||||
}),
|
||||
message_delete { id, channel_id } => {
|
||||
Self::MessageDelete(MessageDelete { id, channel_id })
|
||||
}
|
||||
message_get_content { id, channel_id } => {
|
||||
Self::MessageGetContent(MessageGetContent { id, channel_id })
|
||||
}
|
||||
message_set_content {
|
||||
id,
|
||||
channel_id,
|
||||
content,
|
||||
} => Self::MessageSetContent(MessageSetContent {
|
||||
content,
|
||||
id,
|
||||
channel_id,
|
||||
}),
|
||||
user_list {} => Self::UserList(UserList {}),
|
||||
user_create { name, pass } => Self::UserCreate(UserCreate { name, pass }),
|
||||
user_delete { id } => Self::UserDelete(UserDelete { id }),
|
||||
user_get_name { id } => Self::UserGetName(UserGetName { id }),
|
||||
user_set_name { id, name } => Self::UserSetName(UserSetName { id, name }),
|
||||
user_set_pass { id, pass } => Self::UserSetPass(UserSetPass { id, pass }),
|
||||
};
|
||||
Some(mapped)
|
||||
}
|
||||
|
||||
pub fn serialize(self) -> String {
|
||||
use repr::Command::*;
|
||||
let mapped = match self {
|
||||
Self::Ping(Ping { content }) => ping { content },
|
||||
Self::ChannelList(ChannelList {}) => repr::Command::channel_list {},
|
||||
Self::ChannelCreate(ChannelCreate { name }) => channel_create { name },
|
||||
Self::ChannelDelete(ChannelDelete { id: channel_id }) => {
|
||||
channel_delete { id: channel_id }
|
||||
}
|
||||
Self::ChannelGetName(ChannelGetName { id: channel_id }) => {
|
||||
channel_get_name { id: channel_id }
|
||||
}
|
||||
Self::ChannelSetName(ChannelSetName {
|
||||
id: channel_id,
|
||||
name,
|
||||
}) => channel_set_name {
|
||||
id: channel_id,
|
||||
name,
|
||||
},
|
||||
Self::MessageList(MessageList { channel_id }) => message_list { channel_id },
|
||||
Self::MessageCreate(MessageCreate {
|
||||
channel_id,
|
||||
content,
|
||||
}) => message_create {
|
||||
channel_id,
|
||||
content,
|
||||
},
|
||||
Self::MessageDelete(MessageDelete { id, channel_id }) => {
|
||||
message_delete { id, channel_id }
|
||||
}
|
||||
Self::MessageGetContent(MessageGetContent { id, channel_id }) => {
|
||||
message_get_content { id, channel_id }
|
||||
}
|
||||
Self::MessageSetContent(MessageSetContent {
|
||||
content,
|
||||
id,
|
||||
channel_id,
|
||||
}) => message_set_content {
|
||||
id,
|
||||
channel_id,
|
||||
content,
|
||||
},
|
||||
Self::UserList(UserList {}) => user_list {},
|
||||
Self::UserCreate(UserCreate { name, pass }) => user_create { name, pass },
|
||||
Self::UserDelete(UserDelete { id }) => user_delete { id },
|
||||
Self::UserGetName(UserGetName { id }) => user_get_name { id },
|
||||
Self::UserSetName(UserSetName { id, name }) => user_set_name { id, name },
|
||||
Self::UserSetPass(UserSetPass { id, pass }) => user_set_pass { id, pass },
|
||||
};
|
||||
serde_json::to_string(&mapped).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
mod repr {
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Command {
|
||||
ping {
|
||||
content: String,
|
||||
},
|
||||
channel_list {},
|
||||
channel_create {
|
||||
name: String,
|
||||
},
|
||||
channel_delete {
|
||||
id: u64,
|
||||
},
|
||||
channel_get_name {
|
||||
id: u64,
|
||||
},
|
||||
channel_set_name {
|
||||
id: u64,
|
||||
name: String,
|
||||
},
|
||||
message_list {
|
||||
channel_id: u64,
|
||||
},
|
||||
message_create {
|
||||
channel_id: u64,
|
||||
content: String,
|
||||
},
|
||||
message_delete {
|
||||
channel_id: u64,
|
||||
id: u64,
|
||||
},
|
||||
message_get_content {
|
||||
channel_id: u64,
|
||||
id: u64,
|
||||
},
|
||||
message_set_content {
|
||||
channel_id: u64,
|
||||
id: u64,
|
||||
content: String,
|
||||
},
|
||||
user_list {},
|
||||
user_create {
|
||||
name: String,
|
||||
pass: String,
|
||||
},
|
||||
user_delete {
|
||||
id: u64,
|
||||
},
|
||||
user_get_name {
|
||||
id: u64,
|
||||
},
|
||||
user_set_name {
|
||||
id: u64,
|
||||
name: String,
|
||||
},
|
||||
user_set_pass {
|
||||
id: u64,
|
||||
pass: String,
|
||||
},
|
||||
}
|
||||
}
|
14
harsh-common/src/lib.rs
Normal file
14
harsh-common/src/lib.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = 2 + 2;
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
|
||||
pub use client::ClientRequest;
|
||||
pub mod client;
|
||||
|
||||
pub use server::ServerRequest;
|
||||
pub mod server;
|
393
harsh-common/src/server.rs
Normal file
393
harsh-common/src/server.rs
Normal file
|
@ -0,0 +1,393 @@
|
|||
#[derive(Debug)]
|
||||
pub struct Pong {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelList {
|
||||
pub channels: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelGetName {
|
||||
pub id: u64,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelCreate {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelDelete {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelSetName {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MessageList {
|
||||
pub channel_id: u64,
|
||||
pub messages: Vec<u64>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MessageCreate {
|
||||
pub channel_id: u64,
|
||||
pub id: u64,
|
||||
pub content: String,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MessageDelete {
|
||||
pub channel_id: u64,
|
||||
pub id: u64,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MessageGetContent {
|
||||
pub channel_id: u64,
|
||||
pub id: u64,
|
||||
pub content: Option<String>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MessageSetContent {
|
||||
pub channel_id: u64,
|
||||
pub id: u64,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserList {
|
||||
pub users: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserCreate {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserDelete {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserGetName {
|
||||
pub id: u64,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserSetName {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserSetPass {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerRequest {
|
||||
Pong(Pong),
|
||||
|
||||
ChannelCreate(ChannelCreate),
|
||||
ChannelDelete(ChannelDelete),
|
||||
ChannelList(ChannelList),
|
||||
ChannelGetName(ChannelGetName),
|
||||
ChannelSetName(ChannelSetName),
|
||||
|
||||
MessageList(MessageList),
|
||||
MessageCreate(MessageCreate),
|
||||
MessageDelete(MessageDelete),
|
||||
MessageGetContent(MessageGetContent),
|
||||
MessageSetContent(MessageSetContent),
|
||||
|
||||
UserList(UserList),
|
||||
UserCreate(UserCreate),
|
||||
UserDelete(UserDelete),
|
||||
UserGetName(UserGetName),
|
||||
UserSetName(UserSetName),
|
||||
UserSetPass(UserSetPass),
|
||||
}
|
||||
|
||||
impl ServerRequest {
|
||||
pub fn new_pong(content: String) -> Self {
|
||||
Self::Pong(Pong { content })
|
||||
}
|
||||
|
||||
pub fn new_channel_list(channels: Vec<u64>) -> Self {
|
||||
Self::ChannelList(ChannelList { channels })
|
||||
}
|
||||
|
||||
pub fn new_channel_get_name(id: u64, name: Option<String>) -> Self {
|
||||
Self::ChannelGetName(ChannelGetName { name, id })
|
||||
}
|
||||
|
||||
pub fn new_channel_create(id: u64, name: String) -> Self {
|
||||
Self::ChannelCreate(ChannelCreate { id, name })
|
||||
}
|
||||
|
||||
pub fn new_channel_delete(id: u64) -> Self {
|
||||
Self::ChannelDelete(ChannelDelete { id })
|
||||
}
|
||||
|
||||
pub fn new_channel_set_name(id: u64, name: String) -> Self {
|
||||
Self::ChannelSetName(ChannelSetName { id, name })
|
||||
}
|
||||
|
||||
pub fn new_message_list(channel_id: u64, messages: Vec<u64>) -> Self {
|
||||
Self::MessageList(MessageList {
|
||||
channel_id,
|
||||
messages,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_message_create(channel_id: u64, id: u64, content: String) -> Self {
|
||||
Self::MessageCreate(MessageCreate {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
})
|
||||
}
|
||||
pub fn new_message_delete(channel_id: u64, id: u64) -> Self {
|
||||
Self::MessageDelete(MessageDelete { channel_id, id })
|
||||
}
|
||||
|
||||
pub fn new_message_get_content(channel_id: u64, id: u64, content: Option<String>) -> Self {
|
||||
Self::MessageGetContent(MessageGetContent {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_message_set_content(channel_id: u64, id: u64, content: String) -> Self {
|
||||
Self::MessageSetContent(MessageSetContent {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_user_list(users: Vec<u64>) -> Self {
|
||||
Self::UserList(UserList { users })
|
||||
}
|
||||
|
||||
pub fn new_user_create(id: u64, name: String) -> Self {
|
||||
Self::UserCreate(UserCreate { id, name })
|
||||
}
|
||||
|
||||
pub fn new_user_delete(id: u64) -> Self {
|
||||
Self::UserDelete(UserDelete { id })
|
||||
}
|
||||
|
||||
pub fn new_user_get_name(id: u64, name: Option<String>) -> Self {
|
||||
Self::UserGetName(UserGetName { id, name })
|
||||
}
|
||||
|
||||
pub fn new_user_set_name(id: u64, name: String) -> Self {
|
||||
Self::UserSetName(UserSetName { id, name })
|
||||
}
|
||||
|
||||
pub fn new_user_set_pass(id: u64) -> Self {
|
||||
Self::UserSetPass(UserSetPass { id })
|
||||
}
|
||||
|
||||
pub fn try_parse(line: &str) -> Option<Self> {
|
||||
use repr::Command::*;
|
||||
let command: repr::Command = serde_json::from_str(line).ok()?;
|
||||
let mapped = match command {
|
||||
pong { content } => Self::Pong(Pong { content }),
|
||||
channel_list { channels } => Self::ChannelList(ChannelList { channels }),
|
||||
channel_get_name { id, name } => Self::ChannelGetName(ChannelGetName { id, name }),
|
||||
channel_create { id, name } => Self::ChannelCreate(ChannelCreate { id, name }),
|
||||
channel_set_name { id, name } => Self::ChannelSetName(ChannelSetName { id, name }),
|
||||
channel_delete { id } => Self::ChannelDelete(ChannelDelete { id }),
|
||||
message_list {
|
||||
channel_id,
|
||||
messages,
|
||||
} => Self::MessageList(MessageList {
|
||||
channel_id,
|
||||
messages,
|
||||
}),
|
||||
message_create {
|
||||
channel_id,
|
||||
id,
|
||||
content,
|
||||
} => Self::MessageCreate(MessageCreate {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
}),
|
||||
message_delete { channel_id, id } => {
|
||||
Self::MessageDelete(MessageDelete { channel_id, id })
|
||||
}
|
||||
message_get_content {
|
||||
channel_id,
|
||||
id,
|
||||
content,
|
||||
} => Self::MessageGetContent(MessageGetContent {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
}),
|
||||
message_set_content {
|
||||
channel_id,
|
||||
id,
|
||||
content,
|
||||
} => Self::MessageSetContent(MessageSetContent {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
}),
|
||||
user_list { users } => Self::UserList(UserList { users }),
|
||||
user_create { id, name } => Self::UserCreate(UserCreate { id, name }),
|
||||
user_delete { id } => Self::UserDelete(UserDelete { id }),
|
||||
user_get_name { id, name } => Self::UserGetName(UserGetName { id, name }),
|
||||
user_set_name { id, name } => Self::UserSetName(UserSetName { id, name }),
|
||||
user_set_pass { id } => Self::UserSetPass(UserSetPass { id }),
|
||||
};
|
||||
Some(mapped)
|
||||
}
|
||||
|
||||
pub fn serialize(self) -> String {
|
||||
use repr::Command::*;
|
||||
let mapped = match self {
|
||||
Self::Pong(Pong { content }) => pong { content },
|
||||
Self::ChannelList(ChannelList { channels }) => channel_list { channels },
|
||||
Self::ChannelGetName(ChannelGetName { id, name }) => channel_get_name { id, name },
|
||||
Self::ChannelCreate(ChannelCreate { id, name }) => channel_create { id, name },
|
||||
Self::ChannelSetName(ChannelSetName { id, name }) => channel_set_name { id, name },
|
||||
Self::ChannelDelete(ChannelDelete { id }) => channel_delete { id },
|
||||
|
||||
Self::MessageList(MessageList {
|
||||
channel_id,
|
||||
messages,
|
||||
}) => message_list {
|
||||
channel_id,
|
||||
messages,
|
||||
},
|
||||
Self::MessageCreate(MessageCreate {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
}) => message_create {
|
||||
channel_id,
|
||||
id,
|
||||
content,
|
||||
},
|
||||
Self::MessageDelete(MessageDelete { channel_id, id }) => {
|
||||
message_delete { channel_id, id }
|
||||
}
|
||||
Self::MessageGetContent(MessageGetContent {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
}) => message_get_content {
|
||||
channel_id,
|
||||
id,
|
||||
content,
|
||||
},
|
||||
Self::MessageSetContent(MessageSetContent {
|
||||
channel_id,
|
||||
content,
|
||||
id,
|
||||
}) => message_set_content {
|
||||
channel_id,
|
||||
id,
|
||||
content,
|
||||
},
|
||||
Self::UserList(UserList { users }) => user_list { users },
|
||||
Self::UserCreate(UserCreate { id, name }) => user_create { id, name },
|
||||
Self::UserDelete(UserDelete { id }) => user_delete { id },
|
||||
Self::UserGetName(UserGetName { id, name }) => user_get_name { id, name },
|
||||
Self::UserSetName(UserSetName { id, name }) => user_set_name { id, name },
|
||||
Self::UserSetPass(UserSetPass { id }) => user_set_pass { id },
|
||||
};
|
||||
|
||||
serde_json::to_string(&mapped).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
mod repr {
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Command {
|
||||
pong {
|
||||
content: String,
|
||||
},
|
||||
channel_list {
|
||||
channels: Vec<u64>,
|
||||
},
|
||||
channel_get_name {
|
||||
id: u64,
|
||||
name: Option<String>,
|
||||
},
|
||||
channel_create {
|
||||
id: u64,
|
||||
name: String,
|
||||
},
|
||||
channel_delete {
|
||||
id: u64,
|
||||
},
|
||||
channel_set_name {
|
||||
id: u64,
|
||||
name: String,
|
||||
},
|
||||
message_list {
|
||||
channel_id: u64,
|
||||
messages: Vec<u64>,
|
||||
},
|
||||
message_create {
|
||||
channel_id: u64,
|
||||
id: u64,
|
||||
content: String,
|
||||
},
|
||||
message_delete {
|
||||
channel_id: u64,
|
||||
id: u64,
|
||||
},
|
||||
message_get_content {
|
||||
channel_id: u64,
|
||||
id: u64,
|
||||
content: Option<String>,
|
||||
},
|
||||
message_set_content {
|
||||
channel_id: u64,
|
||||
id: u64,
|
||||
content: String,
|
||||
},
|
||||
user_list {
|
||||
users: Vec<u64>,
|
||||
},
|
||||
user_create {
|
||||
id: u64,
|
||||
name: String,
|
||||
},
|
||||
user_delete {
|
||||
id: u64,
|
||||
},
|
||||
user_get_name {
|
||||
id: u64,
|
||||
name: Option<String>,
|
||||
},
|
||||
user_set_name {
|
||||
id: u64,
|
||||
name: String,
|
||||
},
|
||||
user_set_pass {
|
||||
id: u64,
|
||||
},
|
||||
}
|
||||
}
|
1
harsh-server/.gitignore
vendored
Normal file
1
harsh-server/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
17
harsh-server/Cargo.toml
Normal file
17
harsh-server/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "harsh_server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
#sleded = "1.0"
|
||||
sled = "0.34"
|
||||
telecomande = "1.2"
|
||||
tokio = { version = "1.20", features = ["full"] }
|
||||
harsh_common = { path = "../harsh-common/" }
|
||||
chrono = "0.4"
|
||||
rand = "0.8.5"
|
225
harsh-server/src/gateway.rs
Normal file
225
harsh-server/src/gateway.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
use harsh_common::{client, server, ClientRequest, ServerRequest};
|
||||
use telecomande::{Processor, Remote};
|
||||
|
||||
use crate::{Addr, Id, SessionCmd, SessionProc, StorageCmd, StorageProc};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GatewayCmd {
|
||||
Request(Addr, String),
|
||||
ClosedConnection(Addr),
|
||||
}
|
||||
|
||||
pub struct GatewayProc {
|
||||
sessions: Remote<SessionProc>,
|
||||
storage: Remote<StorageProc>,
|
||||
}
|
||||
|
||||
impl GatewayProc {
|
||||
async fn handle_request(&mut self, address: Addr, request: ClientRequest) {
|
||||
use client as c;
|
||||
use ClientRequest::*;
|
||||
match request {
|
||||
Ping(c::Ping { content }) => self.on_ping(content, address),
|
||||
|
||||
ChannelCreate(c::ChannelCreate { name }) => self.on_channel_create(name).await,
|
||||
ChannelDelete(c::ChannelDelete { id }) => self.on_channel_delete(id),
|
||||
ChannelList(c::ChannelList {}) => self.on_channel_list(address).await,
|
||||
ChannelGetName(c::ChannelGetName { id }) => self.on_channel_get_name(id, address).await,
|
||||
ChannelSetName(c::ChannelSetName { id, name }) => self.on_channel_set_name(id, name),
|
||||
|
||||
MessageList(c::MessageList { channel_id }) => {
|
||||
self.on_message_list(channel_id, address).await
|
||||
}
|
||||
MessageCreate(c::MessageCreate {
|
||||
channel_id,
|
||||
content,
|
||||
}) => self.on_message_create(channel_id, content).await,
|
||||
MessageDelete(c::MessageDelete { channel_id, id }) => {
|
||||
self.on_message_delete(channel_id, id)
|
||||
}
|
||||
MessageGetContent(c::MessageGetContent { channel_id, id }) => {
|
||||
self.on_message_get_content(channel_id, id, address).await
|
||||
}
|
||||
|
||||
MessageSetContent(c::MessageSetContent {
|
||||
channel_id,
|
||||
id,
|
||||
content,
|
||||
}) => {
|
||||
self.on_message_set_content(channel_id, id, content);
|
||||
}
|
||||
|
||||
// TODO: user
|
||||
UserList(c::UserList {}) => {
|
||||
let (cmd, rec) = StorageCmd::new_user_list();
|
||||
self.storage.send(cmd).unwrap();
|
||||
let result = rec.await.unwrap().iter().map(Id::to_u64).collect();
|
||||
let request = ServerRequest::new_user_list(result);
|
||||
let command = SessionCmd::new_send(address, request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
UserCreate(c::UserCreate { name, pass }) => {
|
||||
let (cmd, rec) = StorageCmd::new_user_create(name.clone(), pass);
|
||||
self.storage.send(cmd).unwrap();
|
||||
let id = rec.await.unwrap();
|
||||
let request = ServerRequest::new_user_create(id.into(), name);
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
UserDelete(c::UserDelete { id }) => {
|
||||
let command = StorageCmd::new_user_delete(id.into());
|
||||
self.storage.send(command).unwrap();
|
||||
let request = ServerRequest::new_user_delete(id.into());
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
UserGetName(c::UserGetName { id }) => {
|
||||
let (cmd, rec) = StorageCmd::new_user_get_name(id.into());
|
||||
self.storage.send(cmd).unwrap();
|
||||
let name = rec.await.unwrap();
|
||||
let request = ServerRequest::new_user_get_name(id.into(), name);
|
||||
let command = SessionCmd::new_send(address, request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
UserSetName(c::UserSetName { id, name }) => {
|
||||
let command = StorageCmd::new_user_set_name(id.into(), name.clone());
|
||||
self.storage.send(command).unwrap();
|
||||
let request = ServerRequest::new_user_set_name(id.into(), name);
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
UserSetPass(c::UserSetPass { id, pass }) => {
|
||||
let command = StorageCmd::new_user_set_pass(id.into(), pass);
|
||||
self.storage.send(command).unwrap();
|
||||
let request = ServerRequest::new_user_set_pass(id.into());
|
||||
let command = SessionCmd::new_send(address, request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(sessions: Remote<SessionProc>, storage: Remote<StorageProc>) -> Self {
|
||||
Self { sessions, storage }
|
||||
}
|
||||
|
||||
fn on_ping(&mut self, content: String, address: Addr) {
|
||||
println!("[gateway/PING] '{content:?}'");
|
||||
let request = ServerRequest::Pong(server::Pong { content });
|
||||
let command = SessionCmd::new_send(address, request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
async fn on_channel_create(&mut self, name: String) {
|
||||
let (cmd, rec) = StorageCmd::new_channel_create(name.clone());
|
||||
self.storage.send(cmd).unwrap();
|
||||
let id = rec.await.unwrap().to_u64();
|
||||
let request = ServerRequest::new_channel_create(id, name);
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
fn on_channel_delete(&mut self, id: u64) {
|
||||
let command = StorageCmd::new_channel_delete(id.into());
|
||||
self.storage.send(command).unwrap();
|
||||
let request = ServerRequest::new_channel_delete(id);
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
async fn on_channel_list(&mut self, address: Addr) {
|
||||
let (cmd, rec) = StorageCmd::new_channel_list();
|
||||
self.storage.send(cmd).unwrap();
|
||||
let channels = rec.await.unwrap().iter().map(|id| id.to_u64()).collect();
|
||||
let request = ServerRequest::new_channel_list(channels);
|
||||
let command = SessionCmd::new_send(address, request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
async fn on_channel_get_name(&mut self, id: u64, address: Addr) {
|
||||
let (cmd, rec) = StorageCmd::new_channel_get_name(id.into());
|
||||
self.storage.send(cmd).unwrap();
|
||||
let name = rec.await.unwrap();
|
||||
let request = ServerRequest::new_channel_get_name(id, name);
|
||||
let command = SessionCmd::new_send(address, request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
fn on_channel_set_name(&mut self, id: u64, name: String) {
|
||||
let command = StorageCmd::new_channel_set_name(id.into(), name.clone());
|
||||
self.storage.send(command).unwrap();
|
||||
let request = ServerRequest::new_channel_set_name(id, name);
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
async fn on_message_list(&mut self, channel_id: u64, address: Addr) {
|
||||
let (cmd, rec) = StorageCmd::new_message_list(channel_id.into());
|
||||
self.storage.send(cmd).unwrap();
|
||||
let messages = rec.await.unwrap().iter().map(Id::to_u64).collect();
|
||||
let request = ServerRequest::new_message_list(channel_id, messages);
|
||||
let command = SessionCmd::new_send(address, request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
async fn on_message_create(&mut self, channel_id: u64, content: String) {
|
||||
let (cmd, rec) = StorageCmd::new_message_create(channel_id.into(), content.clone());
|
||||
self.storage.send(cmd).unwrap();
|
||||
let id = rec.await.unwrap();
|
||||
let request = ServerRequest::new_message_create(channel_id, id.to_u64(), content);
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
fn on_message_delete(&mut self, channel_id: u64, id: u64) {
|
||||
let command = StorageCmd::new_message_delete(channel_id.into(), id.into());
|
||||
self.storage.send(command).unwrap();
|
||||
let request = ServerRequest::new_message_delete(channel_id, id);
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
async fn on_message_get_content(&mut self, channel_id: u64, id: u64, address: Addr) {
|
||||
let (cmd, rec) = StorageCmd::new_message_get_content(channel_id.into(), id.into());
|
||||
self.storage.send(cmd).unwrap();
|
||||
let request = ServerRequest::new_message_get_content(channel_id, id, rec.await.unwrap());
|
||||
let command = SessionCmd::new_send(address, request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
|
||||
fn on_message_set_content(&mut self, channel_id: u64, id: u64, content: String) {
|
||||
let command =
|
||||
StorageCmd::new_message_set_content(channel_id.into(), id.into(), content.clone());
|
||||
self.storage.send(command).unwrap();
|
||||
let request = ServerRequest::new_message_set_content(channel_id, id, content);
|
||||
let command = SessionCmd::new_broadcast(request);
|
||||
self.sessions.send(command).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[telecomande::async_trait]
|
||||
impl Processor for GatewayProc {
|
||||
type Command = GatewayCmd;
|
||||
type Error = ();
|
||||
async fn handle(&mut self, command: Self::Command) -> Result<(), Self::Error> {
|
||||
match command {
|
||||
GatewayCmd::Request(address, request) => {
|
||||
if let Some(request) = ClientRequest::try_parse(&request) {
|
||||
println!("[session/info] received command '{request:?}'");
|
||||
self.handle_request(address, request).await;
|
||||
} else {
|
||||
println!("[session/warn] failed to parse command");
|
||||
}
|
||||
}
|
||||
GatewayCmd::ClosedConnection(address) => self
|
||||
.sessions
|
||||
.send(SessionCmd::RemoveSession(address))
|
||||
.unwrap(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
44
harsh-server/src/main.rs
Normal file
44
harsh-server/src/main.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use telecomande::{Executor, SimpleExecutor};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
const ADDRESS: &'static str = "localhost:42069";
|
||||
const DB_PATH: &'static str = "./db.test";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("[main/info] starting server ...");
|
||||
let sessions = SimpleExecutor::new(SessionProc::default()).spawn();
|
||||
println!("[main/info] spawned sessions");
|
||||
let storage = SimpleExecutor::new(StorageProc::new(DB_PATH)).spawn();
|
||||
println!("[main/info] spawned storage");
|
||||
let gateway =
|
||||
SimpleExecutor::new(GatewayProc::new(sessions.remote(), storage.remote())).spawn();
|
||||
println!("[main/info] spawned gateway");
|
||||
|
||||
let listener = TcpListener::bind(ADDRESS).await.unwrap();
|
||||
println!("[main/info] listening on '{ADDRESS}' ...");
|
||||
|
||||
let client_handler = sessions.remote();
|
||||
loop {
|
||||
let (stream, address) = listener.accept().await.unwrap();
|
||||
client_handler
|
||||
.send(sessions::SessionCmd::AddSession(
|
||||
stream,
|
||||
address,
|
||||
gateway.remote(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
mod utils;
|
||||
pub use utils::{Addr, Id};
|
||||
|
||||
mod gateway;
|
||||
pub use gateway::{GatewayCmd, GatewayProc};
|
||||
|
||||
mod sessions;
|
||||
pub use sessions::{SessionCmd, SessionProc};
|
||||
|
||||
mod storage;
|
||||
pub use storage::{StorageCmd, StorageProc};
|
109
harsh-server/src/sessions.rs
Normal file
109
harsh-server/src/sessions.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
use harsh_common::ServerRequest;
|
||||
use telecomande::{Processor, Remote};
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
net::{
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
TcpStream,
|
||||
},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{gateway, Addr};
|
||||
#[derive(Debug)]
|
||||
pub enum SessionCmd {
|
||||
AddSession(TcpStream, SocketAddr, Remote<gateway::GatewayProc>),
|
||||
RemoveSession(Addr),
|
||||
Send(Addr, String),
|
||||
Broadcast(String),
|
||||
}
|
||||
|
||||
impl SessionCmd {
|
||||
pub fn new_send(address: Addr, request: ServerRequest) -> Self {
|
||||
let content = request.serialize();
|
||||
Self::Send(address, content)
|
||||
}
|
||||
|
||||
pub fn new_broadcast(request: ServerRequest) -> Self {
|
||||
let content = request.serialize();
|
||||
Self::Broadcast(content)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SessionProc {
|
||||
clients: HashMap<Addr, (OwnedWriteHalf, JoinHandle<()>)>,
|
||||
}
|
||||
|
||||
impl SessionProc {
|
||||
fn add_client(
|
||||
&mut self,
|
||||
stream: TcpStream,
|
||||
address: Addr,
|
||||
remote: Remote<gateway::GatewayProc>,
|
||||
) {
|
||||
let (reader, writer) = stream.into_split();
|
||||
let handle = tokio::spawn(session(address.clone(), reader, remote));
|
||||
self.clients.insert(address, (writer, handle));
|
||||
}
|
||||
}
|
||||
|
||||
#[telecomande::async_trait]
|
||||
impl Processor for SessionProc {
|
||||
type Command = SessionCmd;
|
||||
|
||||
type Error = ();
|
||||
|
||||
async fn handle(&mut self, command: Self::Command) -> Result<(), Self::Error> {
|
||||
match command {
|
||||
SessionCmd::AddSession(stream, address, remote) => {
|
||||
println!("[sessions/info] new connection from '{address:?}'");
|
||||
let address = Addr::new(address);
|
||||
self.add_client(stream, address, remote)
|
||||
}
|
||||
SessionCmd::RemoveSession(address) => {
|
||||
println!("[sessions/info] closed connection from '{address:?}'");
|
||||
if let Some((_writer, handle)) = self.clients.remove(&address) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
}
|
||||
SessionCmd::Send(address, content) => {
|
||||
if let Some((client, _)) = self.clients.get_mut(&address) {
|
||||
println!("[session/info] sending '{content}' to '{address:?}'");
|
||||
client.write_all(content.as_bytes()).await.unwrap();
|
||||
client.write_all(b"\n").await.unwrap();
|
||||
} else {
|
||||
eprintln!("failed to find session with address '{address:?}'")
|
||||
}
|
||||
}
|
||||
SessionCmd::Broadcast(content) => {
|
||||
for (client, _) in self.clients.values_mut() {
|
||||
println!("[session/info] broadcasting '{content}'");
|
||||
client.write_all(content.as_bytes()).await.unwrap();
|
||||
client.write_all(b"\n").await.unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn session(address: Addr, reader: OwnedReadHalf, remote: Remote<gateway::GatewayProc>) {
|
||||
let mut reader = BufReader::new(reader);
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
match reader.read_line(&mut line).await {
|
||||
Err(error) => eprintln!("[session/error] {error}"),
|
||||
Ok(0) => break,
|
||||
_ => (),
|
||||
}
|
||||
remote
|
||||
.send(gateway::GatewayCmd::Request(address.clone(), line.clone()))
|
||||
.unwrap();
|
||||
}
|
||||
remote
|
||||
.send(gateway::GatewayCmd::ClosedConnection(address))
|
||||
.unwrap();
|
||||
}
|
341
harsh-server/src/storage.rs
Normal file
341
harsh-server/src/storage.rs
Normal file
|
@ -0,0 +1,341 @@
|
|||
use sled::Db;
|
||||
use telecomande::Processor;
|
||||
use tokio::sync::oneshot::{self, Receiver, Sender};
|
||||
|
||||
use crate::Id;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StorageCmd {
|
||||
ChannelList(Sender<Vec<Id>>),
|
||||
ChannelCreate(String, Sender<Id>),
|
||||
ChannelDelete(Id),
|
||||
ChannelGetName(Id, Sender<Option<String>>),
|
||||
ChannelSetName(Id, String),
|
||||
MessageList(Id, Sender<Vec<Id>>),
|
||||
MessageCreate(Id, String, Sender<Id>),
|
||||
MessageDelete(Id, Id),
|
||||
MessageGetContent(Id, Id, Sender<Option<String>>),
|
||||
MessageSetContent(Id, Id, String),
|
||||
UserList(Sender<Vec<Id>>),
|
||||
UserCreate(String, String, Sender<Id>),
|
||||
UserDelete(Id),
|
||||
UserGetName(Id, Sender<Option<String>>),
|
||||
UserSetName(Id, String),
|
||||
UserGetPass(Id, Sender<Option<String>>),
|
||||
UserSetPass(Id, String),
|
||||
}
|
||||
|
||||
impl StorageCmd {
|
||||
pub fn new_channel_list() -> (Self, Receiver<Vec<Id>>) {
|
||||
let (s, r) = oneshot::channel();
|
||||
(Self::ChannelList(s), r)
|
||||
}
|
||||
|
||||
pub fn new_channel_create(name: impl ToString) -> (Self, Receiver<Id>) {
|
||||
let (s, r) = oneshot::channel();
|
||||
(Self::ChannelCreate(name.to_string(), s), r)
|
||||
}
|
||||
|
||||
pub fn new_channel_delete(id: Id) -> Self {
|
||||
Self::ChannelDelete(id)
|
||||
}
|
||||
|
||||
pub fn new_channel_get_name(id: Id) -> (Self, Receiver<Option<String>>) {
|
||||
let (s, r) = oneshot::channel();
|
||||
(Self::ChannelGetName(id, s), r)
|
||||
}
|
||||
|
||||
pub fn new_channel_set_name(id: Id, name: String) -> Self {
|
||||
Self::ChannelSetName(id, name)
|
||||
}
|
||||
|
||||
pub fn new_message_list(channel_id: Id) -> (Self, Receiver<Vec<Id>>) {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let cmd = Self::MessageList(channel_id, sender);
|
||||
(cmd, receiver)
|
||||
}
|
||||
|
||||
pub fn new_message_create(channel_id: Id, content: String) -> (Self, Receiver<Id>) {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let cmd = Self::MessageCreate(channel_id, content, sender);
|
||||
(cmd, receiver)
|
||||
}
|
||||
|
||||
pub fn new_message_delete(channel_id: Id, id: Id) -> Self {
|
||||
Self::MessageDelete(channel_id, id)
|
||||
}
|
||||
|
||||
pub fn new_message_get_content(channel_id: Id, id: Id) -> (Self, Receiver<Option<String>>) {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let cmd = Self::MessageGetContent(channel_id, id, sender);
|
||||
(cmd, receiver)
|
||||
}
|
||||
|
||||
pub fn new_message_set_content(channel_id: Id, id: Id, content: String) -> Self {
|
||||
Self::MessageSetContent(channel_id, id, content)
|
||||
}
|
||||
|
||||
pub fn new_user_list() -> (Self, Receiver<Vec<Id>>) {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let cmd = Self::UserList(sender);
|
||||
(cmd, receiver)
|
||||
}
|
||||
|
||||
pub fn new_user_create(name: String, pass: String) -> (Self, Receiver<Id>) {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let cmd = Self::UserCreate(name, pass, sender);
|
||||
(cmd, receiver)
|
||||
}
|
||||
|
||||
pub fn new_user_delete(id: Id) -> Self {
|
||||
Self::UserDelete(id)
|
||||
}
|
||||
|
||||
pub fn new_user_get_name(id: Id) -> (Self, Receiver<Option<String>>) {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let cmd = Self::UserGetName(id, sender);
|
||||
(cmd, receiver)
|
||||
}
|
||||
|
||||
pub fn new_user_set_name(id: Id, name: String) -> Self {
|
||||
Self::UserSetName(id, name)
|
||||
}
|
||||
|
||||
pub fn new_user_get_pass(id: Id) -> (Self, Receiver<Option<String>>) {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let cmd = Self::UserGetPass(id, sender);
|
||||
(cmd, receiver)
|
||||
}
|
||||
|
||||
pub fn new_user_set_pass(id: Id, pass: String) -> Self {
|
||||
Self::UserSetPass(id, pass)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StorageProc {
|
||||
base: Db,
|
||||
}
|
||||
|
||||
impl StorageProc {
|
||||
pub fn new<S>(path: S) -> Self
|
||||
where
|
||||
S: ToString,
|
||||
{
|
||||
let path = path.to_string();
|
||||
let base = sled::open(path).unwrap();
|
||||
Self { base }
|
||||
}
|
||||
|
||||
fn get<S, T>(&self, path: S) -> Option<T>
|
||||
where
|
||||
S: ToString,
|
||||
T: SerDeser,
|
||||
{
|
||||
let path = path.to_string();
|
||||
println!("[storage/info] setting entry at '{path}'");
|
||||
T::read(&self.base, path)
|
||||
}
|
||||
fn set<S, T>(&self, path: S, item: T)
|
||||
where
|
||||
S: ToString,
|
||||
T: SerDeser,
|
||||
{
|
||||
let path = path.to_string();
|
||||
println!("[storage/info] getting entry at '{path}'");
|
||||
item.write(&self.base, path)
|
||||
}
|
||||
|
||||
fn list(&self, path: impl ToString) -> Vec<Id> {
|
||||
let path = path.to_string();
|
||||
println!("[storage/info] listing entries in '{path}'");
|
||||
let db = &self.base;
|
||||
list(db, path)
|
||||
}
|
||||
|
||||
// firsts (x)
|
||||
// lasts (x)
|
||||
// from (id, x)
|
||||
// to (id, x)
|
||||
|
||||
fn remove(&self, path: impl ToString) {
|
||||
let path = path.to_string();
|
||||
println!("[storage/info] removing entry at '{path}'");
|
||||
self.base.remove(path).unwrap();
|
||||
}
|
||||
|
||||
async fn handle_command(&mut self, command: StorageCmd) {
|
||||
use StorageCmd::*;
|
||||
match command {
|
||||
//
|
||||
// Channel
|
||||
//
|
||||
ChannelList(sender) => self.on_channel_list(sender),
|
||||
ChannelCreate(name, sender) => self.on_channel_create(name, sender),
|
||||
ChannelDelete(id) => self.on_channel_remove(id),
|
||||
ChannelGetName(id, sender) => self.on_channel_get_name(id, sender),
|
||||
ChannelSetName(id, name) => self.on_channel_set_name(id, name),
|
||||
// ChannelGetParent / Set
|
||||
|
||||
//
|
||||
// User
|
||||
//
|
||||
MessageList(channel_id, sender) => self.on_message_list(channel_id, sender),
|
||||
MessageCreate(channel_id, content, sender) => {
|
||||
self.on_message_create(channel_id, content, sender)
|
||||
}
|
||||
MessageDelete(channel_id, id) => self.on_message_delete(channel_id, id),
|
||||
MessageGetContent(channel_id, id, sender) => {
|
||||
self.on_message_get_content(channel_id, id, sender)
|
||||
}
|
||||
MessageSetContent(channel_id, id, content) => {
|
||||
self.on_message_set_content(channel_id, id, content)
|
||||
}
|
||||
|
||||
//
|
||||
// User
|
||||
//
|
||||
UserList(sender) => {
|
||||
let users = self.list("/users/");
|
||||
sender.send(users).unwrap();
|
||||
}
|
||||
|
||||
UserCreate(name, pass, sender) => {
|
||||
let user = User::new(name, pass);
|
||||
let id = user.get_id();
|
||||
self.set(format!("/users/{id}"), user);
|
||||
sender.send(id).unwrap();
|
||||
}
|
||||
|
||||
UserDelete(id) => {
|
||||
self.remove(format!("/users/{id}"));
|
||||
}
|
||||
|
||||
UserGetName(id, sender) => {
|
||||
let user = self.get::<_, User>(format!("/users/{id}"));
|
||||
let name = user.map(|u| u.get_name().to_string());
|
||||
sender.send(name).unwrap();
|
||||
}
|
||||
|
||||
UserSetName(id, name) => {
|
||||
let path = format!("/users/{id}");
|
||||
if let Some(mut user) = self.get::<_, User>(&path) {
|
||||
user.set_name(name);
|
||||
self.set(path, user);
|
||||
}
|
||||
}
|
||||
|
||||
UserGetPass(id, sender) => {
|
||||
let user = self.get::<_, User>(format!("/users/{id}"));
|
||||
let name = user.map(|u| u.get_pass().to_string());
|
||||
sender.send(name).unwrap();
|
||||
}
|
||||
|
||||
UserSetPass(id, pass) => {
|
||||
let path = format!("/users/{id}");
|
||||
if let Some(mut user) = self.get::<_, User>(&path) {
|
||||
user.set_pass(pass);
|
||||
self.set(path, user);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Channels
|
||||
//
|
||||
fn on_channel_list(&mut self, sender: Sender<Vec<Id>>) {
|
||||
let results = self.list("/channels/");
|
||||
sender.send(results).unwrap();
|
||||
}
|
||||
|
||||
fn on_channel_create(&mut self, name: String, sender: Sender<Id>) {
|
||||
let item = Channel::new(name);
|
||||
let id = item.get_id();
|
||||
self.set(format!("/channels/{id}"), item);
|
||||
sender.send(id).unwrap();
|
||||
}
|
||||
|
||||
fn on_channel_remove(&mut self, id: Id) {
|
||||
for message_id in self.list(format!("/messages/{id}/")) {
|
||||
self.remove(format!("/messages/{id}/{message_id}"))
|
||||
}
|
||||
self.remove(format!("/channels/{id}"))
|
||||
}
|
||||
|
||||
fn on_channel_get_name(&mut self, id: Id, sender: Sender<Option<String>>) {
|
||||
let channel = self.get::<_, Channel>(format!("/channels/{id}"));
|
||||
let name = channel.map(|channel| channel.get_name().to_string());
|
||||
sender.send(name).unwrap();
|
||||
}
|
||||
|
||||
fn on_channel_set_name(&mut self, id: Id, name: String) {
|
||||
let path = format!("/channels/{id}");
|
||||
if let Some(mut channel) = self.get::<_, Channel>(&path) {
|
||||
channel.set_name(name);
|
||||
self.set(path, channel);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Messages
|
||||
//
|
||||
fn on_message_list(&mut self, channel_id: Id, sender: Sender<Vec<Id>>) {
|
||||
let items = self.list(format!("/messages/{channel_id}/"));
|
||||
sender.send(items).unwrap();
|
||||
}
|
||||
|
||||
fn on_message_create(&mut self, channel_id: Id, content: String, sender: Sender<Id>) {
|
||||
let message = Message::new(content);
|
||||
let id = message.get_id();
|
||||
self.set(format!("/messages/{channel_id}/{id}"), message);
|
||||
sender.send(id).unwrap();
|
||||
}
|
||||
|
||||
fn on_message_delete(&mut self, channel_id: Id, id: Id) {
|
||||
self.remove(format!("/messages/{channel_id}/{id}"));
|
||||
}
|
||||
|
||||
fn on_message_get_content(&mut self, channel_id: Id, id: Id, sender: Sender<Option<String>>) {
|
||||
let message = self.get::<_, Message>(format!("/messages/{channel_id}/{id}"));
|
||||
let content = message.map(|m| m.get_content().to_string());
|
||||
sender.send(content).unwrap()
|
||||
}
|
||||
|
||||
fn on_message_set_content(&mut self, channel_id: Id, id: Id, content: String) {
|
||||
let path = format!("/messages/{channel_id}/{id}");
|
||||
if let Some(mut message) = self.get::<_, Message>(&path) {
|
||||
message.set_content(content);
|
||||
self.set(path, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[telecomande::async_trait]
|
||||
impl Processor for StorageProc {
|
||||
type Command = StorageCmd;
|
||||
|
||||
type Error = ();
|
||||
|
||||
async fn handle(&mut self, command: Self::Command) -> Result<(), Self::Error> {
|
||||
self.handle_command(command).await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod models;
|
||||
pub use models::{Channel, Message, SerDeser, User};
|
||||
|
||||
fn list(db: &Db, path: String) -> Vec<Id> {
|
||||
let len = path.len();
|
||||
db.scan_prefix(path)
|
||||
.filter_map(move |result| -> Option<Id> {
|
||||
let (key, _) = result.ok()?;
|
||||
let string = String::from_utf8(key.iter().cloned().collect()).unwrap();
|
||||
let suffix = &string[len..];
|
||||
Id::from_string(suffix)
|
||||
})
|
||||
.collect() // TODO: turn into iterator with limits
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
116
harsh-server/src/storage/models.rs
Normal file
116
harsh-server/src/storage/models.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use sled::Db;
|
||||
|
||||
use crate::Id;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Channel {
|
||||
id: Id,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new(name: String) -> Self {
|
||||
let id = Id::from_now();
|
||||
Self { id, name }
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
id: Id,
|
||||
name: String,
|
||||
pass: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(name: String, pass: String) -> Self {
|
||||
let id = Id::from_now();
|
||||
Self { id, name, pass }
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
pub fn get_pass(&self) -> &str {
|
||||
&self.pass
|
||||
}
|
||||
|
||||
pub fn set_pass(&mut self, pass: String) {
|
||||
self.pass = pass
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Message {
|
||||
id: Id,
|
||||
content: String,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new(content: String) -> Self {
|
||||
let id = Id::from_now();
|
||||
Self { id, content }
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
pub fn get_content(&self) -> &str {
|
||||
&self.content
|
||||
}
|
||||
pub fn set_content(&mut self, content: String) {
|
||||
self.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SerDeser: Serialize + DeserializeOwned {
|
||||
fn ser(&self) -> Vec<u8>;
|
||||
fn deser(input: &[u8]) -> Option<Self>;
|
||||
fn read(db: &Db, path: String) -> Option<Self>;
|
||||
fn write(&self, db: &Db, path: String);
|
||||
}
|
||||
|
||||
impl<T> SerDeser for T
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
{
|
||||
fn ser(&self) -> Vec<u8> {
|
||||
serde_json::to_vec(self).unwrap()
|
||||
}
|
||||
|
||||
fn deser(input: &[u8]) -> Option<Self> {
|
||||
serde_json::from_slice(input).ok()
|
||||
}
|
||||
|
||||
fn read(db: &Db, path: String) -> Option<Self> {
|
||||
let bytes = db.get(path).unwrap()?;
|
||||
Self::deser(&bytes)
|
||||
}
|
||||
|
||||
fn write(&self, db: &Db, path: String) {
|
||||
let bytes = self.ser();
|
||||
db.insert(path, bytes).unwrap();
|
||||
}
|
||||
}
|
64
harsh-server/src/storage/tests.rs
Normal file
64
harsh-server/src/storage/tests.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use super::*;
|
||||
#[test]
|
||||
fn test_list() {
|
||||
let db = sled::open("/tmp/test-db").unwrap();
|
||||
db.insert("/some/path/123", b"hello1").unwrap();
|
||||
db.insert("/some/path/1234", b"hello2").unwrap();
|
||||
db.insert("/some/path/12345", b"hello3").unwrap();
|
||||
let results = list(&db, "/some/path/".to_string());
|
||||
assert_eq!(
|
||||
results,
|
||||
vec![
|
||||
Id::from_string("123").unwrap(),
|
||||
Id::from_string("1234").unwrap(),
|
||||
Id::from_string("12345").unwrap()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_channels() {
|
||||
use telecomande::{Executor, SimpleExecutor};
|
||||
// cleaning;
|
||||
std::fs::remove_dir_all("/tmp/db-test").ok();
|
||||
|
||||
// instantiation
|
||||
let store = SimpleExecutor::new(StorageProc::new("/tmp/db-test")).spawn();
|
||||
let remote = store.remote();
|
||||
|
||||
// insertion
|
||||
let (cmd, rec) = StorageCmd::new_channel_create("a-channel");
|
||||
remote.send(cmd).unwrap();
|
||||
let id = rec.await.unwrap();
|
||||
|
||||
// query all
|
||||
let (cmd, rec) = StorageCmd::new_channel_list();
|
||||
remote.send(cmd).unwrap();
|
||||
let result = rec.await.unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let first = result[0];
|
||||
assert_eq!(first, id);
|
||||
|
||||
// query property
|
||||
let (cmd, rec) = StorageCmd::new_channel_get_name(id);
|
||||
remote.send(cmd).unwrap();
|
||||
let result = rec.await.unwrap();
|
||||
assert_eq!(result.unwrap(), "a-channel".to_string());
|
||||
|
||||
// insertion
|
||||
let (cmd, rec) = StorageCmd::new_channel_create("b-channel");
|
||||
remote.send(cmd).unwrap();
|
||||
let id2 = rec.await.unwrap();
|
||||
|
||||
// query all
|
||||
let (cmd, rec) = StorageCmd::new_channel_list();
|
||||
remote.send(cmd).unwrap();
|
||||
let result = rec.await.unwrap();
|
||||
assert_eq!(result.len(), 2);
|
||||
|
||||
// query property
|
||||
let (cmd, rec) = StorageCmd::new_channel_get_name(id2);
|
||||
remote.send(cmd).unwrap();
|
||||
let result = rec.await.unwrap();
|
||||
assert_eq!(result.unwrap(), "b-channel".to_string());
|
||||
}
|
76
harsh-server/src/utils.rs
Normal file
76
harsh-server/src/utils.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use std::{fmt::Display, net::SocketAddr};
|
||||
|
||||
use rand::random;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Addr(String);
|
||||
|
||||
impl Addr {
|
||||
pub fn new(address: SocketAddr) -> Self {
|
||||
let string = format!("{address:?}");
|
||||
Self(string)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Id(u64);
|
||||
|
||||
impl Id {
|
||||
pub fn from_now() -> Self {
|
||||
let ms = chrono::Utc::now().timestamp_millis() as u64;
|
||||
let total = (ms * 1000) + rand_range(1000);
|
||||
Self(total)
|
||||
}
|
||||
|
||||
pub fn from_string(input: &str) -> Option<Self> {
|
||||
let inner: u64 = input.parse().ok()?;
|
||||
Some(Self(inner))
|
||||
}
|
||||
|
||||
pub fn from_u64(input: u64) -> Self {
|
||||
Self(input)
|
||||
}
|
||||
|
||||
pub fn to_u64(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Id {
|
||||
fn from(input: u64) -> Self {
|
||||
Self::from_u64(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for u64 {
|
||||
fn from(input: Id) -> Self {
|
||||
input.to_u64()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_convertion() {
|
||||
let id = Id::from_now();
|
||||
let str = id.to_string();
|
||||
assert_eq!(id, Id::from_string(&str).unwrap());
|
||||
}
|
||||
|
||||
fn rand_range(n: u64) -> u64 {
|
||||
let random: u64 = random();
|
||||
random % n
|
||||
}
|
||||
|
||||
impl Display for Id {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let inner = self.0;
|
||||
let padded = format!("{inner:0>20}"); // pads to the left to make 20 chars of length
|
||||
f.write_str(&padded)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn length_of_max() {
|
||||
assert_eq!(u64::MAX, 18446744073709551615_u64);
|
||||
assert_eq!(20, "18446744073709551615".len())
|
||||
}
|
4
start-client.sh
Executable file
4
start-client.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd harsh-client;
|
||||
cargo run;
|
4
start-server.sh
Executable file
4
start-server.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd harsh-server;
|
||||
cargo run;
|
3
watch-client.sh
Executable file
3
watch-client.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
nodemon "harsh-client/src" "harsh-server/src" "harsh-common/src" -x "./start-client.sh" -e "rs"
|
3
watch-server.sh
Executable file
3
watch-server.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
nodemon "harsh-client/src" "harsh-server/src" "harsh-common/src" -x "./start-server.sh" -e "rs"
|
Loading…
Add table
Add a link
Reference in a new issue