control panel works more now, added install script to add services and compile

asyncversion
Anton Franzluebbers 2022-07-10 15:16:44 -04:00
parent 04cb9e29d9
commit 4c44c3531b
29 changed files with 1099 additions and 1345 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ main.pdb
.idea/ .idea/
restarts.log restarts.log
nohup.out nohup.out
server.log
control_panel.log

84
Cargo.lock generated
View File

@ -2,18 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "VelNetServerRust"
version = "0.2.0"
dependencies = [
"async-notify",
"async-std",
"chrono",
"futures",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "async-attributes" name = "async-attributes"
version = "1.1.2" version = "1.1.2"
@ -205,7 +193,7 @@ dependencies = [
"libc", "libc",
"num-integer", "num-integer",
"num-traits", "num-traits",
"time", "time 0.1.44",
"winapi", "winapi",
] ]
@ -468,6 +456,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.10.0" version = "1.10.0"
@ -560,6 +557,17 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "simplelog"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786"
dependencies = [
"log",
"termcolor",
"time 0.3.11",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.5" version = "0.4.5"
@ -587,6 +595,15 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.44" version = "0.1.44"
@ -598,6 +615,24 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "time"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
dependencies = [
"itoa",
"libc",
"num_threads",
"time-macros",
]
[[package]]
name = "time-macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@ -614,6 +649,20 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "velnet_server"
version = "0.2.0"
dependencies = [
"async-notify",
"async-std",
"chrono",
"futures",
"log",
"serde",
"serde_json",
"simplelog",
]
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
@ -733,6 +782,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View File

@ -1,5 +1,5 @@
[package] [package]
name = "VelNetServerRust" name = "velnet_server"
version = "0.2.0" version = "0.2.0"
edition = "2021" edition = "2021"
@ -12,3 +12,5 @@ serde_json = "1.0"
async-std = { version = "1.6", features = ["attributes"]} async-std = { version = "1.6", features = ["attributes"]}
futures = "*" futures = "*"
async-notify = "*" async-notify = "*"
simplelog = "^0.12.0"
log = "*"

5
config.json Normal file
View File

@ -0,0 +1,5 @@
{
"port": 5000,
"tcp_timeout": 30,
"log_file": "server.log"
}

View File

@ -1,4 +0,0 @@
{
"port":5000,
"tcp_timeout":30
}

240
control-panel/Cargo.lock generated
View File

@ -19,6 +19,29 @@ dependencies = [
"tokio-util 0.7.0", "tokio-util 0.7.0",
] ]
[[package]]
name = "actix-files"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04dcf7654254676d434b0285e2298d577ed4826f67f536e7a39bb0f64721164"
dependencies = [
"actix-http",
"actix-service",
"actix-utils",
"actix-web",
"askama_escape",
"bitflags",
"bytes",
"derive_more",
"futures-core",
"http-range",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
]
[[package]] [[package]]
name = "actix-http" name = "actix-http"
version = "3.0.0" version = "3.0.0"
@ -62,8 +85,8 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
dependencies = [ dependencies = [
"quote", "quote 1.0.15",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -176,9 +199,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc" checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc"
dependencies = [ dependencies = [
"actix-router", "actix-router",
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -222,6 +245,12 @@ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
] ]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -333,16 +362,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "control-panel"
version = "0.1.0"
dependencies = [
"actix-web",
"handlebars",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@ -388,6 +407,17 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "default-env"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f753eb82d29277e79efc625e84aecacfd4851ee50e05a8573a4740239a77bfd3"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"syn 0.15.44",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.17" version = "0.99.17"
@ -395,10 +425,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"rustc_version", "rustc_version",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -420,6 +450,12 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "either"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.30" version = "0.8.30"
@ -569,6 +605,12 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -589,6 +631,12 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.6.0" version = "1.6.0"
@ -622,6 +670,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.1" version = "1.0.1"
@ -715,6 +772,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.4.4" version = "0.4.4"
@ -849,9 +916,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -883,13 +950,22 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
dependencies = [
"unicode-xid 0.1.0",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.36" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid 0.2.2",
] ]
[[package]] [[package]]
@ -898,13 +974,22 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
dependencies = [
"proc-macro2 0.4.30",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.15" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.36",
] ]
[[package]] [[package]]
@ -972,6 +1057,12 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustversion"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.9" version = "1.0.9"
@ -1014,9 +1105,9 @@ version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"syn", "syn 1.0.86",
] ]
[[package]] [[package]]
@ -1074,6 +1165,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simplelog"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786"
dependencies = [
"log",
"termcolor",
"time",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.5" version = "0.4.5"
@ -1096,15 +1198,66 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum_macros"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b"
dependencies = [
"heck",
"proc-macro2 1.0.36",
"quote 1.0.15",
"rustversion",
"syn 1.0.86",
]
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"unicode-xid 0.1.0",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.86" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.36",
"quote", "quote 1.0.15",
"unicode-xid", "unicode-xid 0.2.2",
]
[[package]]
name = "systemctl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3134cbab79000bee5ed4d71331d2c9071db897f8369e3e3089b6be85cc781d1"
dependencies = [
"default-env",
"itertools",
"strum",
"strum_macros",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
] ]
[[package]] [[package]]
@ -1218,6 +1371,15 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.7" version = "0.3.7"
@ -1233,6 +1395,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@ -1251,6 +1419,20 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "velnet_control_panel"
version = "0.2.0"
dependencies = [
"actix-files",
"actix-web",
"handlebars",
"log",
"serde",
"serde_json",
"simplelog",
"systemctl",
]
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "control-panel" name = "velnet_control_panel"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -10,3 +10,7 @@ actix-web = "4"
handlebars = { version = "4.2.1", features = ["dir_source"] } handlebars = { version = "4.2.1", features = ["dir_source"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
simplelog = "^0.12.0"
log = "*"
systemctl = "0.1.7"
actix-files = "0.6.1"

View File

@ -0,0 +1,4 @@
#!/bin/bash
cd ..
sudo -u "$1" bash -c "~/.cargo/bin/cargo build --release"

View File

@ -1,5 +1,8 @@
{ {
"port": 8080, "port": 8080,
"log_file": "/home/ubuntu/VelNetServerRust2/nohup.out", "user": "ntsfranz",
"server_dir": "/home/ubuntu/VelNetServerRust2/" "server_log_file": "../server.log",
"control_panel_log_file": "control_panel.log",
"server_dir": "../",
"handlebars_dev_mode": true
} }

View File

@ -1 +1 @@
sudo -u ubuntu bash -c "git pull" sudo -u "$1" bash -c "git pull"

View File

@ -1 +1,3 @@
sudo -u ubuntu bash -c "onefetch > onefetch.out" #!/bin/bash
sudo -u "$1" bash -c "~/.cargo/bin/onefetch > onefetch.out"

View File

@ -9,18 +9,22 @@ use serde_json::json;
use std::io; use std::io;
use std::io::{prelude::*, BufReader}; use std::io::{prelude::*, BufReader};
use std::fs; use std::fs;
use std::fs::File; use std::fs::{File, OpenOptions};
use std::path::Path; use std::path::Path;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use std::process::{Command, Stdio}; use std::process::{Command};
use std::env;
use std::cmp; use std::cmp;
use std::error::Error;
use simplelog;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct Config { struct ControlPanelConfig {
port: u16, port: u16,
log_file: String, user: String,
server_log_file: String,
control_panel_log_file: String,
server_dir: String, server_dir: String,
handlebars_dev_mode: bool,
} }
@ -38,15 +42,12 @@ fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
#[get("/")] #[get("/")]
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse { async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
let config = read_config_file().unwrap();
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
//read the log file //read the log file
let log_file = lines_from_file(config.log_file); let log_file = lines_from_file(config.server_log_file);
let restarts_log = lines_from_file("../restarts.log"); // let restarts_log = lines_from_file(config.restarts_log_file);
let uptime = Command::new("uptime") let uptime = Command::new("uptime")
.output() .output()
@ -54,6 +55,7 @@ async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
let _onefetch = Command::new("sh") let _onefetch = Command::new("sh")
.arg("onefetch_file.sh") .arg("onefetch_file.sh")
.arg(config.user)
.output() .output()
.expect("failed"); .expect("failed");
@ -61,10 +63,10 @@ async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
let data = json!({ let data = json!({
"log_output": &log_file[(cmp::max((log_file.len() as i64) - 1000, 0) as usize)..], "log_output": &log_file[(cmp::max((log_file.len() as i64) - 1000, 0) as usize)..],
"restarts_output": &restarts_log[(cmp::max((restarts_log.len() as i64) - 1000, 0) as usize)..], // "restarts_output": &restarts_log[(cmp::max((restarts_log.len() as i64) - 1000, 0) as usize)..],
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)), "uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
//"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout)) //"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout))
"onefetch": onefetch "onefetch": onefetch.trim_end()
}); });
let body = hb.render("index", &data).unwrap(); let body = hb.render("index", &data).unwrap();
@ -75,21 +77,33 @@ async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
async fn restart_server() -> HttpResponse { async fn restart_server() -> HttpResponse {
// Restart the process // Restart the process
let _output = Command::new("sh") // let _output = Command::new("sh")
.arg("../run.sh") // .arg("../restart_server.sh")
.stdin(Stdio::null()) // .stdin(Stdio::null())
//.stdout(Stdio::null()) // .spawn();
//.stderr(Stdio::null()) // let _output = Command::new("systemd")
.spawn(); // .arg("restart")
// .arg("velnet")
// .stdin(Stdio::null())
// .spawn();
HttpResponse::Ok().body("DONE") systemctl::restart("velnet.service").unwrap();
let ret = systemctl::status("velnet.service").unwrap();
HttpResponse::Ok().body(ret)
} }
#[get("/git_pull")] #[get("/git_pull")]
async fn git_pull() -> HttpResponse { async fn git_pull() -> HttpResponse {
//read the config file
let config = read_config_file().unwrap();
let output = Command::new("sh") let output = Command::new("sh")
.arg("git_pull.sh") .arg("git_pull.sh")
.arg(config.user)
.output() .output()
.expect("failed to execute process"); .expect("failed to execute process");
@ -101,37 +115,63 @@ async fn git_pull() -> HttpResponse {
async fn compile() -> HttpResponse { async fn compile() -> HttpResponse {
//read the config file //read the config file
let file = fs::read_to_string("config.json").unwrap(); let config = read_config_file().unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
let orig_dir = std::env::current_dir().unwrap(); log::debug!("before");
let root = Path::new(&config.server_dir); let output = Command::new("sh")
let _new_dir = env::set_current_dir(&root); .arg("compile_server.sh")
.arg(config.user)
print!("before");
let output = Command::new("cargo")
.arg("build")
.arg("--release")
.output() .output()
.expect("failed to execute process"); .expect("failed to execute process");
print!("after"); log::debug!("after");
let _new_dir = env::set_current_dir(orig_dir);
HttpResponse::Ok().body(output.stdout) HttpResponse::Ok().body(output.stdout)
} }
fn read_config_file() -> Result<ControlPanelConfig, Box<dyn Error>> {
// Open the file in read-only mode with buffer.
let file = File::open("config.json")?;
let reader = BufReader::new(file);
let config = serde_json::from_reader(reader)?;
Ok(config)
}
#[actix_web::main] #[actix_web::main]
async fn main() -> io::Result<()> { async fn main() -> io::Result<()> {
//read the config file
let config: ControlPanelConfig = read_config_file().unwrap();
let f = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(config.control_panel_log_file)
.unwrap();
simplelog::CombinedLogger::init(
vec![
simplelog::TermLogger::new(simplelog::LevelFilter::Info, simplelog::Config::default(), simplelog::TerminalMode::Mixed, simplelog::ColorChoice::Auto),
simplelog::WriteLogger::new(simplelog::LevelFilter::Debug, simplelog::Config::default(), f),
]
).unwrap();
log::info!("Starting control panel server.");
let mut handlebars = Handlebars::new(); let mut handlebars = Handlebars::new();
handlebars.set_dev_mode(true); handlebars.set_dev_mode(config.handlebars_dev_mode);
handlebars.register_templates_directory(".hbs", "./static/templates").unwrap(); handlebars.register_templates_directory(".hbs", "./static/templates").unwrap();
let handlebars_ref = web::Data::new(handlebars); let handlebars_ref = web::Data::new(handlebars);
log::info!("http://127.0.0.1:{}", config.port);
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.wrap(error_handlers()) .wrap(error_handlers())
@ -140,8 +180,9 @@ async fn main() -> io::Result<()> {
.service(restart_server) .service(restart_server)
.service(git_pull) .service(git_pull)
.service(compile) .service(compile)
.service(actix_files::Files::new("/static", "./static"))
}) })
.bind(("127.0.0.1", 8080))? .bind(("0.0.0.0", config.port))?
.run() .run()
.await .await
} }

View File

@ -0,0 +1,223 @@
use actix_web::body::BoxBody;
use actix_web::dev::ServiceResponse;
use actix_web::http::header::ContentType;
use actix_web::http::StatusCode;
use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::{get, web, App, HttpResponse, HttpServer, Result};
use handlebars::Handlebars;
use serde_json::json;
use std::io;
use std::io::{prelude::*, BufReader};
use std::fs;
use std::fs::File;
use std::path::Path;
use serde::{Serialize, Deserialize};
use std::process::{Command, Stdio};
use std::env;
use std::cmp;
#[derive(Serialize, Deserialize)]
struct Config {
port: u16,
log_file: String,
server_dir: String,
}
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
let file = File::open(filename).expect("no such file");
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
}
#[get("/")]
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
//read the log file
let log_file = lines_from_file(config.log_file);
let restarts_log = lines_from_file("../restarts.log");
let uptime = Command::new("uptime")
.output()
.expect("failed to execute process");
let _onefetch = Command::new("sh")
.arg("onefetch_file.sh")
.output()
.expect("failed");
let onefetch = fs::read_to_string("onefetch.out").unwrap();
let data = json!({
"log_output": log_file,
"restarts_output": restarts_log,
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
//"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout))
"onefetch": onefetch
});
let body = hb.render("index", &data).unwrap();
HttpResponse::Ok().body(body)
}
#[get("/nohup.out")]
async fn nohup() -> HttpResponse {
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
//read the log file
let log_file = lines_from_file(config.log_file);
// get the last 100 lines
let all_lines = log_file.as_slice()[cmp::max(log_file.len()-100, 0)..].to_vec().join("\n");
HttpResponse::Ok().body(all_lines)
}
#[get("/restarts.log2")]
async fn restarts() -> HttpResponse {
//read the log file
let log_file = lines_from_file("../restarts.log");
// get the last 100 lines
let all_lines = log_file.as_slice()[cmp::max(log_file.len()-100, 0)..].to_vec().join("\n");
HttpResponse::Ok().body(all_lines)
}
#[get("/restart_server")]
async fn restart_server() -> HttpResponse {
// Restart the process
let _output = Command::new("sh")
.arg("../run.sh")
.stdin(Stdio::null())
//.stdout(Stdio::null())
//.stderr(Stdio::null())
.spawn();
HttpResponse::Ok().body("DONE")
}
#[get("/git_pull")]
async fn git_pull() -> HttpResponse {
let output = Command::new("git")
.arg("pull")
.output()
.expect("failed to execute process");
HttpResponse::Ok().body(output.stdout)
}
#[get("/compile")]
async fn compile() -> HttpResponse {
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
let orig_dir = std::env::current_dir().unwrap();
let root = Path::new(&config.server_dir);
let _new_dir = env::set_current_dir(&root);
print!("before");
let output = Command::new("cargo")
.arg("build")
.arg("--release")
.output()
.expect("failed to execute process");
print!("after");
let _new_dir = env::set_current_dir(orig_dir);
HttpResponse::Ok().body(output.stdout)
}
#[actix_web::main]
async fn main() -> io::Result<()> {
let mut handlebars = Handlebars::new();
handlebars.set_dev_mode(true);
handlebars.register_templates_directory(".hbs", "./static/templates").unwrap();
let handlebars_ref = web::Data::new(handlebars);
HttpServer::new(move || {
App::new()
.wrap(error_handlers())
.app_data(handlebars_ref.clone())
.service(index)
.service(restart_server)
.service(git_pull)
.service(compile)
.service(nohup)
.service(restarts)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
// Custom error handlers, to return HTML responses when an error occurs.
fn error_handlers() -> ErrorHandlers<BoxBody> {
ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
}
// Error handler for a 404 Page not found error.
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
let response = get_error_response(&res, "Page not found");
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
res.into_parts().0,
response.map_into_left_body(),
)))
}
// Generic error handler.
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse<BoxBody> {
let request = res.request();
// Provide a fallback to a simple plain text response in case an error occurs during the
// rendering of the error page.
let fallback = |e: &str| {
HttpResponse::build(res.status())
.content_type(ContentType::plaintext())
.body(e.to_string())
};
let hb = request
.app_data::<web::Data<Handlebars>>()
.map(|t| t.get_ref());
match hb {
Some(hb) => {
let data = json!({
"error": error,
"status_code": res.status().as_str()
});
let body = hb.render("error", &data);
match body {
Ok(body) => HttpResponse::build(res.status())
.content_type(ContentType::html())
.body(body),
Err(_) => fallback(error),
}
}
None => fallback(error),
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#b91d47</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M605 1500 l600 -600 97 0 98 0 0 600 0 600 -100 0 -100 0 0 -452 0
-453 -453 453 -452 452 -145 0 -145 0 600 -600z"/>
<path d="M1500 2000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
<path d="M2200 1500 l0 -600 398 2 397 3 3 98 3 97 -301 0 -300 0 -2 498 -3
497 -97 3 -98 3 0 -601z"/>
<path d="M1500 1500 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
<path d="M1500 1000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 977 B

View File

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@ -1,15 +1,24 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png">
<link rel="manifest" href="/static/favicons/site.webmanifest">
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#b91d47">
<meta name="theme-color" content="#ffffff">
<title>VelNet Control Panel</title> <title>VelNet Control Panel</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<style> <style>
.bottom-scroller { .bottom-scroller {
height: 30em; height: 40em;
overflow: auto; overflow: auto;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
@ -22,97 +31,81 @@
</head> </head>
<body> <body>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<img src="https://vel.engr.uga.edu/wp-content/uploads/elementor/thumbs/4dLogo-1-oaxk7d2mcrutzo0dhqah6qlgpp2c1tvihad1dchrc0.png" style="float: right;"> <img src="https://vel.engr.uga.edu/wp-content/uploads/elementor/thumbs/4dLogo-1-oaxk7d2mcrutzo0dhqah6qlgpp2c1tvihad1dchrc0.png"
<h1 class="title"> style="float: right;" alt="vel logo">
VelNet Control Panel <h1 class="title">
</h1> VelNet Control Panel
<p class="subtitle"> </h1>
Log output and utilites for <strong>VelNet</strong> <p class="subtitle">
</p> Log output and utilities for <strong>VelNet</strong>
</div> </p>
</div>
</section> </section>
<section> <section>
<div class="container"> <div class="container">
<div class="block"> <div class="block">
<button class="button" id="restart-button">Restart Server</button> <button class="button" id="restart-button">Restart Server</button>
<button class="button" id="pull-button">Git Pull</button> <button class="button" id="pull-button">Git Pull</button>
<button class="button" id="compile-button">Compile</button> <button class="button" id="compile-button">Compile</button>
</div>
<div class="block">Uptime: {{uptime}}</div>
<pre style="font-size: 0.8em;">{{onefetch}}</pre>
</div>
</section>
<section class="section">
<div class="container">
<nav class="panel">
<p class="panel-heading">
Server Log
<code>server.log</code>
<!-- <label for="debug_checkbox">DEBUG</label> -->
<!-- <input id="debug_checkbox" type="checkbox"> -->
</p>
<div class="content bottom-scroller">
<code class="log-output">
{{#each log_output}}
<div class="panel-block">
{{this}}
</div>
{{/each}}
</code>
</div> </div>
<div class="block">Uptime: {{uptime}}</div>
<pre>{{onefetch}}</pre>
</div>
</section>
<section class="section"> </nav>
<div class="container">
<nav class="panel">
<p class="panel-heading">
Server Log
<code>nohup.out</code>
</p>
<div class="content bottom-scroller"> </div>
<code class="log-output">
<div>
{{#each log_output}}
<div class="panel-block">
{{this}}
</div>
{{/each}}
</div>
</code>
</div>
</nav> </section>
<nav class="panel">
<p class="panel-heading">
Server Restarts
<code>restarts.log</code>
</p>
<div class="content bottom-scroller">
<code class="log-output">
<div>
{{#each restarts_output}}
<div class="panel-block">
{{this}}
</div>
{{/each}}
</div>
</code>
</div>
</nav>
</div>
</section>
<script>
"use strict";
document.getElementById('restart-button').addEventListener('click', c=> {
fetch('/restart_server').then(r=> {
setTimeout(location.reload(), 1000);
});
<script>
"use strict";
document.getElementById('restart-button').addEventListener('click', _ => {
fetch('/restart_server').then(_ => {
setTimeout(location.reload(), 1000);
}); });
document.getElementById('pull-button').addEventListener('click', c=> {
fetch('/git_pull').then(r=> {
location.reload();
});
});
document.getElementById('pull-button').addEventListener('click', _ => {
fetch('/git_pull').then(_ => {
location.reload();
}); });
document.getElementById('compile-button').addEventListener('click', c=> {
fetch('/compile').then(r=> {
location.reload();
});
});
document.getElementById('compile-button').addEventListener('click', _ => {
fetch('/compile').then(_ => {
location.reload();
}); });
</script>
});
</script>
</body> </body>
</html> </html>

35
install.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
echo "Building VelNet Server..."
cargo build --release
(
cd control-panel || exit
echo "Building VelNet Control Panel..."
cargo build --release
)
echo "Copying systemd configs..."
(
sudo cp velnet.service /etc/systemd/system/
service="/etc/systemd/system/velnet.service"
wd="$(pwd)"
exe="$(pwd)/target/release/velnet_server"
sudo sed -i -e "s+WorkingDirectory=+WorkingDirectory=$wd+g" "$service"
sudo sed -i -e "s+ExecStart=+ExecStart=$exe+g" "$service"
systemctl enable velnet
systemctl restart velnet
)
(
cd control-panel || exit
sudo cp ../velnet.service /etc/systemd/system/velnet-control-panel.service
service="/etc/systemd/system/velnet-control-panel.service"
wd="$(pwd)"
exe="$(pwd)/target/release/velnet_control_panel"
sudo sed -i -e "s+WorkingDirectory=+WorkingDirectory=$wd+g" "$service"
sudo sed -i -e "s+ExecStart=+ExecStart=$exe+g" "$service"
systemctl enable velnet-control-panel
systemctl restart velnet-control-panel
)
echo "'velnet' and 'velnet-control-panel' systemd services enabled."

15
run.sh
View File

@ -1,15 +0,0 @@
#!/bin/bash
BASEDIR=$(dirname "$0")
echo $(date +"%Y-%m-%dT%T.%3N%z") >> $BASEDIR/restarts.log
echo "Before: " >> $BASEDIR/restarts.log
pgrep -f "VelNetServerRust -v2" >> $BASEDIR/restarts.log
#pgrep -f "VelNetServerRust -v2" | xargs sudo kill >> $BASEDIRE/restarts.log
#kill $(pgrep -f "VelNetServerRust -v2")
pkill -f "VelNetServerRust -v2"
echo "\nAfter: " >> $BASEDIR/restarts.log
pgrep -f "VelNetServerRust -v2" >> $BASEDIR/restarts.log
echo "\n"
(cd $BASEDIR && nohup ./target/release/VelNetServerRust -v2 >> nohup.out &)
# nohup "$BASEDIR/target/release/VelNetServerRust" &
echo "Starting..." >> $BASEDIR/restarts.log

File diff suppressed because it is too large Load Diff

View File

@ -1,846 +0,0 @@
extern crate chrono;
extern crate serde;
extern crate serde_json;
use std::io::prelude::*;
use std::thread;
use std::net::{TcpListener, TcpStream,UdpSocket,IpAddr,SocketAddr};
use std::collections::HashMap;
use std::sync::{Arc,RwLock};
use std::sync::mpsc;
use std::sync::mpsc::{SyncSender,Receiver};
use chrono::Local;
use std::fs;
use std::time;
use serde::{Serialize, Deserialize};
enum ToClientTCPMessageType {
LoggedIn = 0,
RoomList = 1,
PlayerJoined = 2,
DataMessage = 3,
MasterMessage = 4,
YouJoined = 5,
PlayerLeft = 6,
YouLeft = 7,
RoomData = 8
}
enum FromClientTCPMessageType {
LogIn = 0,
GetRooms = 1,
JoinRoom = 2,
SendMessageOthersUnbuffered = 3,
SendMessageAllUnbuffered = 4,
SendMessageGroupUnbuffered = 5,
CreateGroup = 6,
SendMessageOthersBuffered = 7,
SendMessageAllBuffered = 8,
GetRoomData =9
}
enum ToClientUDPMessageType {
Connected = 0,
DataMessage = ToClientTCPMessageType::DataMessage as isize
}
enum FromClientUDPMessageType {
Connect = 0,
SendMesssageOthersUnbuffered = FromClientTCPMessageType::SendMessageOthersUnbuffered as isize,
SendMessageAllUnbuffered = FromClientTCPMessageType::SendMessageAllUnbuffered as isize,
SendMessageGroupUnbuffered = FromClientTCPMessageType::SendMessageGroupUnbuffered as isize
}
struct Client {
logged_in: Arc<RwLock<bool>>,
id: u32,
username: Arc<RwLock<String>>,
application: Arc<RwLock<String>>,
room: Arc<RwLock<Option<Arc<Room>>>>,
sender: SyncSender<Vec<u8>>,
rooms_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,
clients_mutex: Arc<RwLock<HashMap<u32,Arc<Client>>>>,
groups: Arc<RwLock<HashMap<String,Vec<Arc<Client>>>>>,
ip: Arc<RwLock<IpAddr>>,
port: Arc<RwLock<u16>>
}
struct Room {
name: String,
clients: RwLock<HashMap<u32,Arc<Client>>>,
master_client: Arc<RwLock<Arc<Client>>>
}
#[derive(Serialize, Deserialize)]
struct Config {
port: u16,
tcp_timeout: u64,
tcp_send_buffer: usize
}
fn read_u8(stream: &mut TcpStream) -> u8 {
let mut buf = [0; 1];
stream.read_exact(&mut buf).unwrap();
return buf[0];
}
fn read_u32(stream: &mut TcpStream) -> u32 {
let mut buf:[u8;4] = [0; 4];
stream.read_exact(&mut buf).unwrap();
let size = u32::from_be_bytes(buf);
return size;
}
fn _read_string(stream: &mut TcpStream) -> String {
let size = read_u32(stream);
let mut string_bytes = vec![0;size as usize];
stream.read_exact(&mut string_bytes).unwrap();
return String::from_utf8(string_bytes).unwrap();
}
fn read_short_string(stream: &mut TcpStream) -> String {
let size = read_u8(stream);
let mut string_bytes = vec![0;size as usize];
stream.read_exact(&mut string_bytes).unwrap();
return String::from_utf8(string_bytes).unwrap();
}
fn read_vec(stream: &mut TcpStream) -> Vec<u8> {
let message_size = read_u32(stream);
let mut message = vec![0u8;message_size as usize];
stream.read_exact(&mut message).unwrap();
return message;
}
//this is in response to someone asking to login (this is where usernames and passwords would be processed, in theory)
fn read_login_message(stream: &mut TcpStream, client: &Arc<Client>) {
//byte,shortstring,byte,shortstring
let username = read_short_string(stream);
let application = read_short_string(stream);
println!("{}: Got application {} and userid {}",Local::now().format("%Y-%m-%d %H:%M:%S"),application,username);
let mut client_user = client.username.write().unwrap();
*client_user = username;
let mut client_application = client.application.write().unwrap();
*client_application = application;
let mut client_loggedin = client.logged_in.write().unwrap();
*client_loggedin = true;
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::LoggedIn as u8);
write_buf.extend_from_slice(&(client.id).to_be_bytes()); //send the client the id
client.sender.try_send(write_buf).unwrap();
}
//this is in response to a request for rooms.
fn read_rooms_message(_stream: &mut TcpStream, client: &Arc<Client>){
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::RoomList as u8);
//first we need to get the room names
let rooms = client.rooms_mutex.read().unwrap();
let mut rooms_vec = vec![];
for (k,v) in rooms.iter() {
let app_name = client.application.read().unwrap();
if !k.starts_with(&app_name.to_string()) {
continue;
}
let clients = v.clients.read().unwrap();
let mut iter = k.chars();
iter.by_ref().nth(app_name.len());
let application_stripped_room = iter.as_str();
let room_string = format!("{}:{}",application_stripped_room,clients.len());
rooms_vec.push(room_string);
}
let rooms_message = rooms_vec.join(",");
let message_bytes = rooms_message.as_bytes();
let message_len = message_bytes.len() as u32;
write_buf.extend_from_slice(&(message_len).to_be_bytes());
write_buf.extend_from_slice(message_bytes);
client.sender.try_send(write_buf).unwrap();
}
fn read_roomdata_message(stream: &mut TcpStream, client: &Arc<Client>){
//type, room_name
//will respond with type, numclients u32, id1 u32, name_len u8, name_bytes ...
//read the room name and append the client application
let short_room_name = read_short_string(stream);
let application = client.application.read().unwrap().to_string();
let room_name = format!("{}_{}", application, short_room_name);
//we need to access the rooms list
let rooms = client.rooms_mutex.read().unwrap();
if rooms.contains_key(&room_name) {
let room = rooms.get(&room_name).unwrap();
//form and send the message
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::RoomData as u8);
let roomname_bytes = short_room_name.as_bytes();
write_buf.push(roomname_bytes.len() as u8);
write_buf.extend_from_slice(&roomname_bytes);
let clients = room.clients.read().unwrap();
write_buf.extend_from_slice(&(clients.len() as u32).to_be_bytes());
for (_k,c) in clients.iter() {
//write out the client id (u32) and the username (short string)
write_buf.extend_from_slice(&(c.id).to_be_bytes());
let username = c.username.read().unwrap();
let username_bytes = username.as_bytes();
write_buf.push(username_bytes.len() as u8);
write_buf.extend_from_slice(&username_bytes);
}
client.sender.try_send(write_buf).unwrap();
}
}
fn send_client_master_message(to: &Arc<Client>, master_id: u32){
//2u8, person_id_u32, room_name_len_u8, room_name_bytes
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::MasterMessage as u8);
write_buf.extend_from_slice(&(master_id).to_be_bytes()); //send everyone that the client id joined the room
let res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
fn send_client_join_message(to: &Arc<Client>, from: u32, room: &str){
//2u8, person_id_u32, room_name_len_u8, room_name_bytes
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::PlayerJoined as u8);
write_buf.extend_from_slice(&(from).to_be_bytes()); //send everyone that the client id joined the room
write_buf.push(room.as_bytes().len() as u8);
write_buf.extend_from_slice(room.as_bytes());
let res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
fn send_you_joined_message(to: &Arc<Client>, in_room: Vec<u32>, room: &str){
//you_joined_u8, ids_len_u32, id_list_array_u32, room_name_len_u8, room_name_bytes
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::YouJoined as u8);
write_buf.extend_from_slice(&(in_room.len() as u32).to_be_bytes());
for id in in_room {
write_buf.extend_from_slice(&(id).to_be_bytes());
}
write_buf.push(room.as_bytes().len() as u8);
write_buf.extend_from_slice(room.as_bytes());
let res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
fn send_you_left_message(to: &Arc<Client>, room: &str){
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::YouLeft as u8);
write_buf.push(room.as_bytes().len() as u8);
write_buf.extend_from_slice(room.as_bytes());
let res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
fn send_client_left_message(to: &Arc<Client>, from: u32, room: &str){
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::PlayerLeft as u8);
write_buf.extend_from_slice(&(from).to_be_bytes()); //send everyone that the client id left the room
write_buf.push(room.as_bytes().len() as u8);
write_buf.extend_from_slice(room.as_bytes());
let res = to.sender.try_send(write_buf);
match res {
Ok(_) => (),
Err(_) => ()
}
}
//helper function, because clients leave room in multiple places
fn client_leave_room(client: &Arc<Client>, send_to_client: bool){
//first remove the client from the room they are in
{
let room = client.room.read().unwrap(); //I need to get the room, because I'll be modifying the clients in it
if room.is_some(){
{
let mut change_master = false;
let mut new_master_id = 0;
{
println!("{}: {}: Client {} in room, leaving",Local::now().format("%Y-%m-%d %H:%M:%S"), client.application.read().unwrap().to_string(),client.id);
}
let room = room.as_ref().unwrap();
//may have to choose a new master
{
let clients = room.clients.read().unwrap();
let master_client = room.master_client.read().unwrap();
if master_client.id == client.id {
//change the master
change_master = true;
}
for (_k,v) in clients.iter() {
if !send_to_client && v.id == client.id{
continue;
}else if v.id == client.id {
send_you_left_message(v, &room.name);
}else{
send_client_left_message(v, client.id, &room.name);
}
}
}
{
let mut clients = room.clients.write().unwrap();
clients.remove(&client.id); //remove the client from that list in the room
}
let clients = room.clients.read().unwrap();
//if the room is empty, destroy it as well
if clients.len() == 0 {
let mut rooms = client.rooms_mutex.write().unwrap();
rooms.remove(&room.name);
{
println!("{}: {}: Destroyed room {}",Local::now().format("%Y-%m-%d %H:%M:%S"), client.application.read().unwrap().to_string(), &room.name)
}
}else if change_master{
for (_k,v) in clients.iter() {
if v.id != client.id {
new_master_id = v.id;
break;
}
}
{
println!("{}: {}: Changing master to {}",Local::now().format("%Y-%m-%d %H:%M:%S"),client.application.read().unwrap().to_string(), new_master_id);
}
for (_k,v) in clients.iter() {
send_client_master_message(&v, new_master_id);
}
{
let mut master_client = room.master_client.write().unwrap();
*master_client = clients.get(&new_master_id).unwrap().clone();
}
}
}
}
}
{
let mut room = client.room.write().unwrap();
*room = Option::None;
}
}
fn read_join_message(stream: &mut TcpStream, client: &Arc<Client>){
//byte,shortstring
let short_room_name = read_short_string(stream);
let application = client.application.read().unwrap().to_string();
let extended_room_name = format!("{}_{}", application, short_room_name);
//if the client is in a room, leave it
let mut leave_room = false;
{
let room = client.room.read().unwrap(); //must release this mutex before calling into a function that uses it
if room.as_ref().is_some(){
leave_room = true;
}
}
if leave_room {
client_leave_room(client, true);
}
if short_room_name.trim() == "" || short_room_name == "-1" {
return;
}
//join room_name
{
{
let mut rooms = client.rooms_mutex.write().unwrap();
if !rooms.contains_key(&extended_room_name) { //new room, must create it
let map: HashMap<u32, Arc<Client>> = HashMap::new();
let r = Arc::new(Room {
name: extended_room_name.to_string(),
clients: RwLock::new(map),
master_client: Arc::new(RwLock::new(client.clone())) //client is the master, since they joined first
});
rooms.insert(String::from(&extended_room_name),r);
println!("{}: {}: New room {} created",Local::now().format("%Y-%m-%d %H:%M:%S"), application,&extended_room_name);
}
//the room is guaranteed to exist now, so this call can't fail
let room_to_join = &rooms[&extended_room_name];
let mut clients = room_to_join.clients.write().unwrap();
clients.insert(client.id,client.clone());
println!("{}: {}: Client {} joined {}",Local::now().format("%Y-%m-%d %H:%M:%S"), application, client.id,&extended_room_name);
let mut room = client.room.write().unwrap();
*room = Some(room_to_join.clone()); //we create an option and assign it back to the room
}
//once the client is in the room, it can't suddenly die, so we can release the write lock, and can use the extended_room name without issue
{
let rooms = client.rooms_mutex.read().unwrap();
let clients = rooms[&extended_room_name].clients.read().unwrap(); //only need a read lock now
//send a join message to everyone in the room (except the client)
for (_k,v) in clients.iter() {
if v.id != client.id {
send_client_join_message(v, client.id, &short_room_name);
}
}
//send a join message to the client that has all of the ids in the room
let mut ids_in_room = vec![];
for (_k,v) in clients.iter() {
ids_in_room.push(v.id);
}
send_you_joined_message(client, ids_in_room, &short_room_name);
}
let room = client.room.read().unwrap();
//tell the client who the master is
let master_client = room.as_ref().unwrap().master_client.read().unwrap();
send_client_master_message(client, master_client.id);
}
}
// function send_message_to_clients_dictionary(clients: message: &Vec<u8>, include_sender: bool){
// }
fn send_room_message(sender: &Arc<Client>, message: &Vec<u8>, include_sender: bool, ordered: bool){
//this message is 3u8, sender_id_u32, message_len_u32, message_bytes
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
write_buf.extend_from_slice(&sender.id.to_be_bytes());
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
write_buf.extend_from_slice(message);
//println!("sending {} bytes from {}",message.len(),sender.id);
{
if !ordered {
let room = sender.room.read().unwrap();
if room.is_some() {
let clients = room.as_ref().unwrap().clients.read().unwrap();
for (_k,v) in clients.iter(){
if !include_sender && v.id == sender.id {
continue;
}
match v.sender.try_send(write_buf.clone()){
Ok(_) => (),
Err(x) => println!("{}: {}: Error sending to client {}: {}",Local::now().format("%Y-%m-%d %H:%M:%S"), v.application.read().unwrap().to_string(),v.id,x)
} //this sometimes fails.
}
}
}else{ //I'm bad at rust, so I don't know how else to do this other than repeat the code above because the types are so different
let room = sender.room.write().unwrap();
if room.is_some() {
let clients = room.as_ref().unwrap().clients.read().unwrap();
for (_k,v) in clients.iter(){
if !include_sender && v.id == sender.id {
continue;
}
match v.sender.try_send(write_buf.clone()){
Ok(_) => (),
Err(x) => println!("{}: {}: Error sending to client {}: {}",Local::now().format("%Y-%m-%d %H:%M:%S"),v.application.read().unwrap().to_string(),v.id,x)
} //this sometimes fails.
}
}
}
}
}
fn send_group_message(sender: &Arc<Client>, message: &Vec<u8>, group: &String){
let mut write_buf = vec![];
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
write_buf.extend_from_slice(&sender.id.to_be_bytes());
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
write_buf.extend_from_slice(message);
//get the list of client ids for this group
let groups = sender.groups.read().unwrap();
if groups.contains_key(group) {
let group = groups.get(group).unwrap();
for c in group {
//there may be a leftover when a client leaves...will fix itself
match c.sender.try_send(write_buf.clone()) {
Ok(_) => (),
Err(_) => ()
}
}
}
}
fn read_send_message(stream: &mut TcpStream, client: &Arc<Client>, message_type: u8){
//4 byte length, array
//this is a message for everyone in the room (maybe)
let to_send = read_vec(stream);
if message_type == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 {
send_room_message(client,&to_send,false,false);
}else if message_type == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 {
send_room_message(client,&to_send,true,false);
} else if message_type == FromClientTCPMessageType::SendMessageOthersBuffered as u8 { //ordered
send_room_message(client,&to_send,false,true);
}else if message_type == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //ordered
send_room_message(client,&to_send,true,true);
}else if message_type == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 {
let group = read_short_string(stream);
send_group_message(client,&to_send, &group);
}
}
fn read_group_message(stream: &mut TcpStream, client: &Arc<Client>){
let group = read_short_string(stream);
let id_bytes = read_vec(stream);
let num = id_bytes.len();
let mut groups = client.groups.write().unwrap();
let clients = client.clients_mutex.read().unwrap();
let mut group_clients = vec![];
let mut i = 0;
loop {
if i >= num {
break;
}
let mut slice = [0u8;4];
slice[0] = id_bytes[i];
slice[1] = id_bytes[i+1];
slice[2] = id_bytes[i+2];
slice[3] = id_bytes[i+3]; //probably a better way to do this
let id = u32::from_be_bytes(slice);
match clients.get(&id) {
Some(client) => {group_clients.push(client.clone());},
None => () //not there, so don't add it
}
i = i + 4;
}
//delete the group if it exists
if groups.contains_key(&group) {
groups.remove(&group); //ensures the client references go away
}
groups.insert(group.clone(),group_clients);
}
fn client_read_thread(mut stream: TcpStream, mut client: Arc<Client>) {
let mut read_buf:[u8;1] = [0; 1];
//messages come through as a 1 byte type identifier, that can be one of 0 (login) 1 (get rooms), 2 (join/leave room) 3 (send message to room), 4 (send message to room including me), 5 (send message to group), 6 (establish group)
loop {
//read exactly 1 byte
stream.read_exact(&mut read_buf).unwrap();
//println!("Got a message {}",read_buf[0]);
let t = read_buf[0];
if t == FromClientTCPMessageType::LogIn as u8 { //[0:u8][username.length():u8][username:shortstring][password.length():u8][password:shortstring]
read_login_message(&mut stream, &mut client);
} else if t == FromClientTCPMessageType::GetRooms as u8 {//[1:u8]
read_rooms_message(&mut stream, &mut client);
} else if t == FromClientTCPMessageType::GetRoomData as u8 {
read_roomdata_message(&mut stream, &mut client);
} else if t == FromClientTCPMessageType::JoinRoom as u8 {//[2:u8][roomname.length():u8][roomname:shortstring]
read_join_message(&mut stream, &mut client);
} else if t == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 ||
t == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 ||
t == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 ||
t == FromClientTCPMessageType::SendMessageOthersBuffered as u8 ||
t == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //others,all,group[t:u8][message.length():i32][message:u8array]
read_send_message(&mut stream, &client, t);
} else if t == FromClientTCPMessageType::CreateGroup as u8 { //[t:u8][list.lengthbytes:i32][clients:i32array]
read_group_message(&mut stream, &client);
} else {
//die...not correct protocol
println!("Incorrect protocol, killing");
return;
}
std::io::stdout().flush().unwrap();
}
}
fn client_write_thread(mut stream: TcpStream, rx: Receiver<Vec<u8>> ) {
//wait on messages in my queue
loop {
let m = rx.recv().unwrap();
//println!("Sending a message {}",m.len());
if m.len() == 1{
break;
}
stream.write(&m).unwrap();
}
}
fn handle_client(stream: TcpStream, client_id: u32, clients_mutex: Arc<RwLock<HashMap<u32,Arc<Client>>>>, rooms_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,tcp_timeout: u64,tcp_send_buffer:usize){
stream.set_nodelay(true).unwrap();
stream.set_read_timeout(Some(time::Duration::new(tcp_timeout,0))).unwrap();
stream.set_write_timeout(Some(time::Duration::new(tcp_timeout,0))).unwrap();
println!("{}: Accepted new connection, assigned client id {}",Local::now().format("%Y-%m-%d %H:%M:%S"),client_id);
let (tx, rx) = mpsc::sync_channel(tcp_send_buffer); //the server is very fast. However, if the clients throttle the sending, this buffer may overflow, and when it does, data is lost
//create a new client structure and add it to the list of clients
let client = Arc::new(Client{
id: client_id,
username: Arc::new(RwLock::new(String::from(""))),
logged_in: Arc::new(RwLock::new(false)),
room: Arc::new(RwLock::new(Option::None)),
application: Arc::new(RwLock::new(String::from(""))),
sender: tx,
rooms_mutex: rooms_mutex.clone(),
clients_mutex: clients_mutex.clone(),
groups: Arc::new(RwLock::new(HashMap::new())),
ip: Arc::new(RwLock::new(stream.peer_addr().unwrap().ip())),
port: Arc::new(RwLock::new(0))
});
{
let mut clients = clients_mutex.write().unwrap();
clients.insert(client_id, client.clone());
}
let read_clone = stream.try_clone().expect("clone failed");
let read_client = client.clone();
let read_handle = thread::spawn(move ||{client_read_thread(read_clone, read_client)});
let write_handle = thread::spawn(move ||{client_write_thread(stream, rx)});
//handle writing to the thread as needed
match read_handle.join(){
Ok(_)=>(),
Err(_)=>()
}
match client.sender.try_send(vec![0]){ //force send thread to exit
Ok(_)=>(),
Err(_)=>()
}
match write_handle.join() {
Ok(_)=>(),
Err(_)=>()
}
println!("{}: {}: Client {} left",Local::now().format("%Y-%m-%d %H:%M:%S"),client.application.read().unwrap().to_string(),client_id);
//now we can kill the client.
{
//make sure we remove the client from all rooms
client_leave_room(&client, false);
let mut clients = clients_mutex.write().unwrap();
clients.remove(&client_id);
}
}
fn tcp_listen(client_mutex: Arc<RwLock<HashMap<u32, Arc<Client>>>>, room_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,port:u16,tcp_timeout:u64,tcp_send_buffer:usize){
println!("{}: Started TCP Listener",Local::now().format("%Y-%m-%d %H:%M:%S"));
let listener = TcpListener::bind(format!("0.0.0.0:{}",port)).expect("could not bind port");
let mut next_client_id = 0;
// accept connections and process them serially
for stream in listener.incoming() {
let client_mutex = Arc::clone(&client_mutex);
let room_mutex = Arc::clone(&room_mutex);
thread::spawn(move || {handle_client(stream.unwrap(), next_client_id, client_mutex, room_mutex,tcp_timeout,tcp_send_buffer)});
next_client_id+=1;
}
println!("{}: Ended TCP Listener",Local::now().format("%Y-%m-%d %H:%M:%S"));
}
fn udp_listen(client_mutex: Arc<RwLock<HashMap<u32, Arc<Client>>>>, _room_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,port:u16){
let mut buf = [0u8;1024];
let s = UdpSocket::bind(format!("0.0.0.0:{}",port)).unwrap();
println!("{}: UDP Thread Started",Local::now().format("%Y-%m-%d %H:%M:%S"));
loop {
let res = s.recv_from(&mut buf);
match res {
Ok(_) => (),
Err(_) => continue
}
let (packet_size,addr) = res.unwrap();
let t = buf[0];
if packet_size >= 5{
//get the client id, which has to be sent with every udp message, because you don't know where udp messages are coming from
let client_id_bytes = [buf[1],buf[2],buf[3],buf[4]];
let client_id = u32::from_be_bytes(client_id_bytes);
if t == FromClientUDPMessageType::Connect as u8 { //1 byte, 0. Nothing else. This is just to establish the udp port, Echos back the same thing sent
//connect message, respond back
{
let clients = client_mutex.read().unwrap();
if clients.contains_key(&client_id){
let client = clients.get(&client_id).unwrap();
let mut port = client.port.write().unwrap();
*port = addr.port(); //set the udp port to send data to
buf[0] = ToClientUDPMessageType::Connected as u8;
match s.send_to(&buf,addr) {
Ok(_)=>(),
Err(_)=>()
}
}
}
} else if t == FromClientUDPMessageType::SendMesssageOthersUnbuffered as u8 { //[3:u8][from:i32][contents:u8array] note that it must fit into the packet of 1024 bytes
{
let clients = client_mutex.read().unwrap();
if clients.contains_key(&client_id){
let client = clients.get(&client_id).unwrap();
let room_option = client.room.read().unwrap();
let room = room_option.as_ref().unwrap();
let room_clients = room.clients.read().unwrap(); //we finally got to the room!
buf[0] = ToClientUDPMessageType::DataMessage as u8; //technically unecessary, unless we change this number
for (_k,v) in room_clients.iter() {
if v.id != client_id{
let ip = v.ip.read().unwrap();
let port = v.port.read().unwrap();
match s.send_to(&buf,SocketAddr::new(*ip, *port)) {
Ok(_)=> (),
Err(_) => ()
}
}
}
}
}
} else if t == FromClientUDPMessageType::SendMessageAllUnbuffered as u8 { //see above
{
let clients = client_mutex.read().unwrap();
if clients.contains_key(&client_id){
let client = clients.get(&client_id).unwrap();
let room_option = client.room.read().unwrap();
let room = room_option.as_ref().unwrap();
let room_clients = room.clients.read().unwrap(); //we finally got to the room!
buf[0] = ToClientUDPMessageType::DataMessage as u8; //messages are always 3s, even though this came in as 4
for (_k,v) in room_clients.iter() {
let ip = v.ip.read().unwrap();
let port = v.port.read().unwrap();
match s.send_to(&buf,SocketAddr::new(*ip, *port)) {
Ok(_)=> (),
Err(_) => ()
}
}
}
}
} else if t == FromClientUDPMessageType::SendMessageGroupUnbuffered as u8 { //[5:byte][from:i32][group.length():u8][message:u8array]
//this one is a little different, because we don't send the group in the message, so we have to formulate another message (like a 3 message)
//send a message to a group
//read the group name
let group_name_size = buf[5];
let message_vec = buf[6..packet_size].to_vec();
let (group_name_bytes, message_bytes) = message_vec.split_at(group_name_size as usize);
let group_name = String::from_utf8(group_name_bytes.to_vec()).unwrap();
let clients = client_mutex.read().unwrap();
if clients.contains_key(&client_id){
let client = clients.get(&client_id).unwrap();
let groups = client.groups.read().unwrap();
if groups.contains_key(&group_name) {
let clients = groups.get(&group_name).unwrap();
//we need to form a new message without the group name
let mut message_to_send = vec![];
message_to_send.push(ToClientUDPMessageType::DataMessage as u8);
message_to_send.extend([buf[1],buf[2],buf[3],buf[4]]);
message_to_send.extend(message_bytes);
for v in clients.iter() {
let ip = v.ip.read().unwrap();
let port = v.port.read().unwrap();
match s.send_to(&message_to_send,SocketAddr::new(*ip, *port)) {
Ok(_)=> (),
Err(_) => ()
}
}
}
}
}
}
}
println!("{}: UDP Thread Ended",Local::now().format("%Y-%m-%d %H:%M:%S"));
}
fn main_save() {
println!("{}: VelNet Server Starting",Local::now().format("%Y-%m-%d %H:%M:%S"));
//read the config file
let foo = fs::read_to_string("config.txt").unwrap();
let config: Config = serde_json::from_str(&foo).unwrap();
println!("{}",config.port);
let clients: HashMap<u32, Arc<Client>> = HashMap::new();
let rooms: HashMap<String, Arc<Room>> = HashMap::new();
let client_mutex = Arc::new(RwLock::new(clients));
let room_mutex = Arc::new(RwLock::new(rooms));
//start the UDP thread
let udp_clients = Arc::clone(&client_mutex);
let udp_rooms = Arc::clone(&room_mutex);
let udp_handle = thread::spawn(move ||{udp_listen(udp_clients, udp_rooms, config.port);});
//start the TCP thread
tcp_listen(client_mutex, room_mutex,config.port,config.tcp_timeout,config.tcp_send_buffer);
udp_handle.join().unwrap();
println!("{}: VelNet Ended", Local::now().format("%Y-%m-%d %H:%M:%S"));
}

13
velnet.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=VelNet Server
Requires=network.target
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=
ExecStart=
[Install]
WantedBy=multi-user.target