aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock826
-rw-r--r--Cargo.toml51
-rw-r--r--src/audio_player.rs328
-rw-r--r--src/bot/master.rs363
-rw-r--r--src/bot/music.rs500
-rw-r--r--src/log_bridge.rs101
-rw-r--r--src/main.rs154
-rw-r--r--src/playlist.rs16
-rw-r--r--src/teamspeak/mod.rs245
-rw-r--r--src/web_server.rs34
-rw-r--r--src/web_server/api.rs17
-rw-r--r--src/web_server/bot_data.rs47
-rw-r--r--src/web_server/bot_executor.rs63
-rw-r--r--src/web_server/default.rs17
-rw-r--r--src/web_server/tmtu.rs17
-rw-r--r--src/youtube_dl.rs15
16 files changed, 1575 insertions, 1219 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b77fb08..2c2e037 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,42 +2,27 @@
# It is not intended for manual editing.
[[package]]
name = "actix"
-version = "0.9.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4af87564ff659dee8f9981540cac9418c45e910c8072fdedd643a262a38fcaf"
+checksum = "1be241f88f3b1e7e9a3fbe3b5a8a0f6915b5a1d7ee0d9a248d3376d01068cc60"
dependencies = [
- "actix-http",
"actix-rt",
"actix_derive",
"bitflags",
"bytes",
"crossbeam-channel",
"derive_more",
- "futures",
- "lazy_static",
+ "futures-channel",
+ "futures-util",
"log",
- "parking_lot 0.10.2",
+ "once_cell",
+ "parking_lot",
"pin-project",
"smallvec",
"tokio",
- "tokio-util 0.2.0",
- "trust-dns-proto 0.18.0-alpha.2",
- "trust-dns-resolver 0.18.0-alpha.2",
-]
-
-[[package]]
-name = "actix-codec"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"
-dependencies = [
- "bitflags",
- "bytes",
- "futures-core",
- "futures-sink",
- "log",
- "tokio",
- "tokio-util 0.2.0",
+ "tokio-util",
+ "trust-dns-proto",
+ "trust-dns-resolver",
]
[[package]]
@@ -53,35 +38,34 @@ dependencies = [
"log",
"pin-project",
"tokio",
- "tokio-util 0.3.1",
+ "tokio-util",
]
[[package]]
name = "actix-connect"
-version = "1.0.2"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c"
+checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc"
dependencies = [
- "actix-codec 0.2.0",
+ "actix-codec",
"actix-rt",
"actix-service",
- "actix-utils 1.0.6",
+ "actix-utils",
"derive_more",
"either",
- "futures",
+ "futures-util",
"http",
"log",
- "trust-dns-proto 0.18.0-alpha.2",
- "trust-dns-resolver 0.18.0-alpha.2",
+ "trust-dns-proto",
+ "trust-dns-resolver",
]
[[package]]
name = "actix-files"
-version = "0.2.2"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "193b22cb1f7b4ff12a4eb2415d6d19e47e44ea93e05930b30d05375ea29d3529"
+checksum = "5fc0a9181e93c91dc7eb401a0debaed5c8294e12019c307c72fd7a1731b672fc"
dependencies = [
- "actix-http",
"actix-service",
"actix-web",
"bitflags",
@@ -98,26 +82,25 @@ dependencies = [
[[package]]
name = "actix-http"
-version = "1.0.1"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c16664cc4fdea8030837ad5a845eb231fb93fc3c5c171edfefb52fad92ce9019"
+checksum = "05dd80ba8f27c4a34357c07e338c8f5c38f8520e6d626ca1727d8fecc41b0cab"
dependencies = [
- "actix-codec 0.2.0",
+ "actix-codec",
"actix-connect",
"actix-rt",
"actix-service",
"actix-threadpool",
- "actix-utils 1.0.6",
- "base64 0.11.0",
+ "actix-utils",
+ "base64",
"bitflags",
"brotli2",
"bytes",
- "chrono",
+ "cookie",
"copyless",
"derive_more",
"either",
"encoding_rs",
- "failure",
"flate2",
"futures-channel",
"futures-core",
@@ -127,6 +110,7 @@ dependencies = [
"http",
"httparse",
"indexmap",
+ "itoa",
"language-tags",
"lazy_static",
"log",
@@ -138,9 +122,9 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
- "sha1",
+ "sha-1",
"slab",
- "time 0.1.44",
+ "time 0.2.22",
]
[[package]]
@@ -187,10 +171,10 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e"
dependencies = [
- "actix-codec 0.3.0",
+ "actix-codec",
"actix-rt",
"actix-service",
- "actix-utils 2.0.0",
+ "actix-utils",
"futures-channel",
"futures-util",
"log",
@@ -212,6 +196,19 @@ dependencies = [
]
[[package]]
+name = "actix-slog"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c697a62a2f51c5c26af6b1dded0622f15bec690da191615947e0c1b2b7b75198"
+dependencies = [
+ "actix-web",
+ "chrono",
+ "futures",
+ "pin-project",
+ "slog",
+]
+
+[[package]]
name = "actix-testing"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -236,42 +233,20 @@ dependencies = [
"lazy_static",
"log",
"num_cpus",
- "parking_lot 0.11.0",
+ "parking_lot",
"threadpool",
]
[[package]]
name = "actix-tls"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4e5b4faaf105e9a6d389c606c298dcdb033061b00d532af9df56ff3a54995a8"
-dependencies = [
- "actix-codec 0.2.0",
- "actix-rt",
- "actix-service",
- "actix-utils 1.0.6",
- "derive_more",
- "either",
- "futures",
- "log",
-]
-
-[[package]]
-name = "actix-utils"
-version = "1.0.6"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcf8f5631bf01adec2267808f00e228b761c60c0584cc9fa0b5364f41d147f4e"
+checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb"
dependencies = [
- "actix-codec 0.2.0",
- "actix-rt",
+ "actix-codec",
"actix-service",
- "bitflags",
- "bytes",
- "either",
- "futures",
- "log",
- "pin-project",
- "slab",
+ "actix-utils",
+ "futures-util",
]
[[package]]
@@ -280,7 +255,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a"
dependencies = [
- "actix-codec 0.3.0",
+ "actix-codec",
"actix-rt",
"actix-service",
"bitflags",
@@ -296,11 +271,11 @@ dependencies = [
[[package]]
name = "actix-web"
-version = "2.0.0"
+version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3158e822461040822f0dbf1735b9c2ce1f95f93b651d7a7aded00b1efbb1f635"
+checksum = "c1b12fe25e11cd9ed2ef2e428427eb6178a1b363f3f7f0dab8278572f11b2da1"
dependencies = [
- "actix-codec 0.2.0",
+ "actix-codec",
"actix-http",
"actix-macros",
"actix-router",
@@ -310,31 +285,34 @@ dependencies = [
"actix-testing",
"actix-threadpool",
"actix-tls",
- "actix-utils 1.0.6",
+ "actix-utils",
"actix-web-codegen",
"awc",
"bytes",
"derive_more",
"encoding_rs",
- "futures",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
"fxhash",
"log",
"mime",
- "net2",
"pin-project",
"regex",
"serde",
"serde_json",
"serde_urlencoded",
- "time 0.1.44",
+ "socket2",
+ "time 0.2.22",
+ "tinyvec 1.0.1",
"url",
]
[[package]]
name = "actix-web-codegen"
-version = "0.2.2"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a71bf475cbe07281d0b3696abb48212db118e7e23219f13596ce865235ff5766"
+checksum = "750ca8fb60bbdc79491991650ba5d2ae7cd75f3fc00ead51390cfe9efda0d4d8"
dependencies = [
"proc-macro2",
"quote",
@@ -410,9 +388,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "0.7.13"
+version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
+checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
dependencies = [
"memchr",
]
@@ -428,9 +406,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.32"
+version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
+checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
[[package]]
name = "arc-swap"
@@ -471,9 +449,9 @@ dependencies = [
[[package]]
name = "askama_actix"
-version = "0.10.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddbc230d919b403e31373600425d49df06a19fb8af7a8964292ca7b5364db366"
+checksum = "20653490b9baa13ed557b0ea6dae1fc9bbc2349ace67efeaaffbaea1c76f518a"
dependencies = [
"actix-web",
"askama",
@@ -566,15 +544,15 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "awc"
-version = "1.0.1"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5"
+checksum = "150e00c06683ab44c5f97d033950e5d87a7a042d06d77f5eecb443cbd23d0575"
dependencies = [
- "actix-codec 0.2.0",
+ "actix-codec",
"actix-http",
"actix-rt",
"actix-service",
- "base64 0.11.0",
+ "base64",
"bytes",
"derive_more",
"futures-core",
@@ -589,12 +567,12 @@ dependencies = [
[[package]]
name = "backtrace"
-version = "0.3.51"
+version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1"
+checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
dependencies = [
"addr2line",
- "cfg-if",
+ "cfg-if 1.0.0",
"libc",
"miniz_oxide",
"object",
@@ -602,16 +580,22 @@ dependencies = [
]
[[package]]
-name = "base-x"
-version = "0.2.6"
+name = "barrage"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
+checksum = "756c265ddc2c445724b688455c42ec724590635fa71b47eaff7b260dc95fa7c9"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "loom",
+ "spinny",
+]
[[package]]
-name = "base64"
-version = "0.11.0"
+name = "base-x"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
+checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
[[package]]
name = "base64"
@@ -643,6 +627,15 @@ dependencies = [
]
[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
name = "block-cipher"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -684,16 +677,19 @@ dependencies = [
]
[[package]]
-name = "bumpalo"
-version = "3.4.0"
+name = "buf-min"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
+checksum = "b6ae7069aad07c7cdefe6a22a671f00650728bd2331a4cc62e1e5d0becdf9ca4"
+dependencies = [
+ "bytes",
+]
[[package]]
-name = "byte-slice-cast"
-version = "0.3.5"
+name = "bumpalo"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3"
+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "byteorder"
@@ -717,10 +713,25 @@ dependencies = [
]
[[package]]
+name = "cache-padded"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
+
+[[package]]
+name = "catty"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c8b5dc109078e97a1303c505ce1eeb4f8c4c5459d462040d143520828e80c12"
+dependencies = [
+ "spinning_top",
+]
+
+[[package]]
name = "cc"
-version = "1.0.60"
+version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c"
+checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
[[package]]
name = "cfg-if"
@@ -729,6 +740,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[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.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -758,15 +775,6 @@ dependencies = [
[[package]]
name = "cloudabi"
-version = "0.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "cloudabi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
@@ -785,6 +793,15 @@ dependencies = [
]
[[package]]
+name = "concurrent-queue"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
+dependencies = [
+ "cache-padded",
+]
+
+[[package]]
name = "const_fn"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -797,6 +814,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
+name = "cookie"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1373a16a4937bc34efec7b391f9c1500c30b8478a701a4f44c9165cc0475a6e0"
+dependencies = [
+ "percent-encoding",
+ "time 0.2.22",
+ "version_check 0.9.2",
+]
+
+[[package]]
name = "copyless"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -819,12 +847,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
[[package]]
+name = "cpuid-bool"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
+
+[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
+]
+
+[[package]]
+name = "crossbeam"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
+dependencies = [
+ "cfg-if 0.1.10",
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
]
[[package]]
@@ -838,13 +886,50 @@ dependencies = [
]
[[package]]
+name = "crossbeam-deque"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
+dependencies = [
+ "autocfg",
+ "cfg-if 0.1.10",
+ "crossbeam-utils",
+ "lazy_static",
+ "maybe-uninit",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
+dependencies = [
+ "cfg-if 0.1.10",
+ "crossbeam-utils",
+ "maybe-uninit",
+]
+
+[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
- "cfg-if",
+ "cfg-if 0.1.10",
"lazy_static",
]
@@ -938,7 +1023,7 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"dirs-sys",
]
@@ -989,7 +1074,7 @@ version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
]
[[package]]
@@ -1005,26 +1090,10 @@ dependencies = [
]
[[package]]
-name = "failure"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
-dependencies = [
- "backtrace",
- "failure_derive",
-]
-
-[[package]]
-name = "failure_derive"
-version = "0.1.8"
+name = "event-listener"
+version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "flakebi-ring"
@@ -1047,13 +1116,23 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]]
+name = "flume"
+version = "0.9.1"
+source = "git+https://github.com/zesterer/flume#9c907f78e0aa8bce39478dd16d49cf30ae7f3f47"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spinning_top",
+]
+
+[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1092,9 +1171,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futures"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
+checksum = "5d8e3078b7b2a8a671cb7a3d17b4760e4181ea243227776ba83fd043b4ca034e"
dependencies = [
"futures-channel",
"futures-core",
@@ -1107,9 +1186,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
+checksum = "a7a4d35f7401e948629c9c3d6638fb9bf94e0b2121e96c3b428cc4e631f3eb74"
dependencies = [
"futures-core",
"futures-sink",
@@ -1117,15 +1196,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
+checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b"
[[package]]
name = "futures-executor"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
+checksum = "cc709ca1da6f66143b8c9bec8e6260181869893714e9b5a490b169b0414144ab"
dependencies = [
"futures-core",
"futures-task",
@@ -1134,15 +1213,15 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
+checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c"
[[package]]
name = "futures-macro"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
+checksum = "f57ed14da4603b2554682e9f2ff3c65d7567b53188db96cb71538217fc64581b"
dependencies = [
"proc-macro-hack",
"proc-macro2",
@@ -1152,24 +1231,30 @@ dependencies = [
[[package]]
name = "futures-sink"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc"
+checksum = "0d8764258ed64ebc5d9ed185cf86a95db5cac810269c5d20ececb32e0088abbd"
[[package]]
name = "futures-task"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626"
+checksum = "4dd26820a9f3637f1302da8bceba3ff33adbe53464b54ca24d4e2d4f1db30f94"
dependencies = [
"once_cell",
]
[[package]]
+name = "futures-timer"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
+
+[[package]]
name = "futures-util"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
+checksum = "8a894a0acddba51a2d49a6f4263b1e64b8c579ece8af50fa86503d52cd1eea34"
dependencies = [
"futures-channel",
"futures-core",
@@ -1195,6 +1280,19 @@ dependencies = [
]
[[package]]
+name = "generator"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5e13c8f4607ff74f6d0fa37007cb95492531333f46bb9744f772d9e7830855c"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustc_version",
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1210,7 +1308,7 @@ version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
@@ -1301,12 +1399,12 @@ dependencies = [
[[package]]
name = "gstreamer"
-version = "0.16.3"
+version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "566e1062d60c9df234da5051c970426d8351c8f3afc909f632fad995df1af956"
+checksum = "da0bc44d8c3ce6c3dca434a3f256a851c55f0ea5c409413b5b406039989db6ff"
dependencies = [
"bitflags",
- "cfg-if",
+ "cfg-if 1.0.0",
"futures-channel",
"futures-core",
"futures-util",
@@ -1358,9 +1456,9 @@ dependencies = [
[[package]]
name = "gstreamer-audio"
-version = "0.16.3"
+version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0463453d581b3642f89e9afcc807851ea6e2a94e2df06ae977ebc7e11b0a18c"
+checksum = "54e05de1343acac0ca8236dc92a9ad06d054aa87616e7b9eb044b5b5f8f255e3"
dependencies = [
"array-init",
"bitflags",
@@ -1446,7 +1544,7 @@ dependencies = [
"indexmap",
"slab",
"tokio",
- "tokio-util 0.3.1",
+ "tokio-util",
"tracing",
]
@@ -1467,9 +1565,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.1.16"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
dependencies = [
"libc",
]
@@ -1603,7 +1701,7 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
]
[[package]]
@@ -1687,16 +1785,16 @@ checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
dependencies = [
"arrayvec",
"bitflags",
- "cfg-if",
+ "cfg-if 0.1.10",
"ryu",
"static_assertions",
]
[[package]]
name = "libc"
-version = "0.2.78"
+version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98"
+checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
[[package]]
name = "linked-hash-map"
@@ -1706,15 +1804,6 @@ checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "lock_api"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
-dependencies = [
- "scopeguard",
-]
-
-[[package]]
-name = "lock_api"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
@@ -1728,7 +1817,7 @@ version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"serde",
]
@@ -1752,7 +1841,7 @@ dependencies = [
"libc",
"log",
"log-mdc",
- "parking_lot 0.11.0",
+ "parking_lot",
"serde",
"serde-value",
"serde_derive",
@@ -1764,6 +1853,18 @@ dependencies = [
]
[[package]]
+name = "loom"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0e8460f2f2121162705187214720353c517b97bdfb3494c0b1e33d83ebe4bed"
+dependencies = [
+ "cfg-if 0.1.10",
+ "futures-util",
+ "generator",
+ "scoped-tls",
+]
+
+[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1797,6 +1898,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
+name = "memoffset"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1814,9 +1924,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9"
+checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
dependencies = [
"adler",
"autocfg",
@@ -1828,7 +1938,7 @@ version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"fuchsia-zircon",
"fuchsia-zircon-sys",
"iovec",
@@ -1916,7 +2026,7 @@ version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"libc",
"winapi 0.3.9",
]
@@ -2017,9 +2127,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.20.0"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
+checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
[[package]]
name = "omnom"
@@ -2049,7 +2159,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
dependencies = [
"bitflags",
- "cfg-if",
+ "cfg-if 0.1.10",
"foreign-types",
"lazy_static",
"libc",
@@ -2086,37 +2196,13 @@ dependencies = [
[[package]]
name = "parking_lot"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
-dependencies = [
- "lock_api 0.3.4",
- "parking_lot_core 0.7.2",
-]
-
-[[package]]
-name = "parking_lot"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
dependencies = [
"instant",
- "lock_api 0.4.1",
- "parking_lot_core 0.8.0",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
-dependencies = [
- "cfg-if",
- "cloudabi 0.0.3",
- "libc",
- "redox_syscall",
- "smallvec",
- "winapi 0.3.9",
+ "lock_api",
+ "parking_lot_core",
]
[[package]]
@@ -2125,8 +2211,8 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
dependencies = [
- "cfg-if",
- "cloudabi 0.1.0",
+ "cfg-if 0.1.10",
+ "cloudabi",
"instant",
"libc",
"redox_syscall",
@@ -2148,18 +2234,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
-version = "0.4.25"
+version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b9e280448854bd91559252582173b3bd1f8e094a0e644791c0628ca9b1f144f"
+checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "0.4.25"
+version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8c8b352676bc6a4c3d71970560b913cea444a7a921cc2e2d920225e4b91edaa"
+checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
dependencies = [
"proc-macro2",
"quote",
@@ -2180,9 +2266,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
-version = "0.3.18"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "pokebot"
@@ -2191,10 +2277,11 @@ dependencies = [
"actix",
"actix-files",
"actix-rt",
+ "actix-slog",
"actix-web",
"askama",
"askama_actix",
- "byte-slice-cast",
+ "async-trait",
"derive_more",
"futures",
"glib",
@@ -2207,14 +2294,25 @@ dependencies = [
"rand",
"serde",
"serde_json",
+ "slog",
+ "slog-async",
+ "slog-scope",
+ "slog-stdlog",
"structopt",
"tokio",
"toml",
"tsclientlib",
"tsproto-packets",
+ "xtra",
]
[[package]]
+name = "pollster"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9824e18e85003f0b5a38fa1932ae8be8c2aac9447c2f28ab6f9704dbe0a1ab58"
+
+[[package]]
name = "ppv-lite86"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2376,9 +2474,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.3.9"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
+checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"
dependencies = [
"aho-corasick",
"memchr",
@@ -2397,9 +2495,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.18"
+version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
+checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
[[package]]
name = "remove_dir_all"
@@ -2437,7 +2535,7 @@ version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e"
dependencies = [
- "base64 0.12.3",
+ "base64",
"bytes",
"encoding_rs",
"futures-core",
@@ -2482,7 +2580,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19"
dependencies = [
- "base64 0.12.3",
+ "base64",
"blake2b_simd",
"constant_time_eq",
"crossbeam-utils",
@@ -2490,9 +2588,9 @@ dependencies = [
[[package]]
name = "rustc-demangle"
-version = "0.1.16"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
+checksum = "b2610b7f643d18c87dff3b489950269617e6601a51f1f05aa5daefee36f64f0b"
[[package]]
name = "rustc_version"
@@ -2520,6 +2618,12 @@ dependencies = [
]
[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2595,9 +2699,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.58"
+version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4"
+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
dependencies = [
"itoa",
"ryu",
@@ -2629,6 +2733,19 @@ dependencies = [
]
[[package]]
+name = "sha-1"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "170a36ea86c864a3f16dd2687712dd6646f7019f301e57537c7f4dc9f5916770"
+dependencies = [
+ "block-buffer",
+ "cfg-if 0.1.10",
+ "cpuid-bool",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
name = "sha1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2689,6 +2806,29 @@ dependencies = [
]
[[package]]
+name = "slog-scope"
+version = "4.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c44c89dd8b0ae4537d1ae318353eaf7840b4869c536e31c41e963d1ea523ee6"
+dependencies = [
+ "arc-swap",
+ "lazy_static",
+ "slog",
+]
+
+[[package]]
+name = "slog-stdlog"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d87903baf655da2d82bc3ac3f7ef43868c58bf712b3a661fda72009304c23"
+dependencies = [
+ "crossbeam",
+ "log",
+ "slog",
+ "slog-scope",
+]
+
+[[package]]
name = "slog-term"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2713,7 +2853,7 @@ version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"libc",
"redox_syscall",
"winapi 0.3.9",
@@ -2726,6 +2866,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
+name = "spinning_top"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e529d73e80d64b5f2631f9035113347c578a1c9c7774b83a2b880788459ab36"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "spinny"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3351aa083e2a6d649dd3dcac94e6b6c2db4141fba9b457abbbdfb4254fa3a754"
+dependencies = [
+ "lock_api",
+ "loom",
+ "once_cell",
+]
+
+[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2733,9 +2893,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "standback"
-version = "0.2.10"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33a71ea1ea5f8747d1af1979bfb7e65c3a025a70609f04ceb78425bc5adad8e6"
+checksum = "f4e0831040d2cf2bdfd51b844be71885783d489898a192f254ae25d57cce725c"
dependencies = [
"version_check 0.9.2",
]
@@ -2813,9 +2973,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
-version = "0.3.18"
+version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a33f6461027d7f08a13715659b2948e1602c31a3756aeae9378bfe7518c72e82"
+checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8"
dependencies = [
"clap",
"lazy_static",
@@ -2824,9 +2984,9 @@ dependencies = [
[[package]]
name = "structopt-derive"
-version = "0.4.11"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c92e775028122a4b3dd55d58f14fc5120289c69bee99df1d117ae30f84b225c9"
+checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8"
dependencies = [
"heck",
"proc-macro-error",
@@ -2861,24 +3021,12 @@ checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd"
[[package]]
name = "syn"
-version = "1.0.42"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "synstructure"
-version = "0.12.4"
+version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
+checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd"
dependencies = [
"proc-macro2",
"quote",
- "syn",
"unicode-xid",
]
@@ -2932,7 +3080,7 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"libc",
"rand",
"redox_syscall",
@@ -2961,18 +3109,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.20"
+version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
+checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.20"
+version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
+checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab"
dependencies = [
"proc-macro2",
"quote",
@@ -3065,6 +3213,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
[[package]]
+name = "tinyvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b78a366903f506d2ad52ca8dc552102ffdd3e937ba8a227f024dc1d1eae28575"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
name = "tokio"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3080,6 +3243,7 @@ dependencies = [
"mio",
"mio-named-pipes",
"mio-uds",
+ "num_cpus",
"pin-project-lite",
"signal-hook-registry",
"slab",
@@ -3110,26 +3274,13 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930"
-dependencies = [
- "bytes",
- "futures-core",
- "futures-sink",
- "log",
- "pin-project-lite",
- "tokio",
-]
-
-[[package]]
-name = "tokio-util"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
dependencies = [
"bytes",
"futures-core",
+ "futures-io",
"futures-sink",
"log",
"pin-project-lite",
@@ -3138,9 +3289,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.5.6"
+version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
dependencies = [
"serde",
]
@@ -3157,7 +3308,7 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"log",
"pin-project-lite",
"tracing-core",
@@ -3180,26 +3331,6 @@ checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
[[package]]
name = "trust-dns-proto"
-version = "0.18.0-alpha.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a7f3a2ab8a919f5eca52a468866a67ed7d3efa265d48a652a9a3452272b413f"
-dependencies = [
- "async-trait",
- "enum-as-inner",
- "failure",
- "futures",
- "idna",
- "lazy_static",
- "log",
- "rand",
- "smallvec",
- "socket2",
- "tokio",
- "url",
-]
-
-[[package]]
-name = "trust-dns-proto"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdd7061ba6f4d4d9721afedffbfd403f20f39a4301fee1b70d6fcd09cca69f28"
@@ -3220,31 +3351,12 @@ dependencies = [
[[package]]
name = "trust-dns-resolver"
-version = "0.18.0-alpha.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f90b1502b226f8b2514c6d5b37bafa8c200d7ca4102d57dc36ee0f3b7a04a2f"
-dependencies = [
- "cfg-if",
- "failure",
- "futures",
- "ipconfig",
- "lazy_static",
- "log",
- "lru-cache",
- "resolv-conf",
- "smallvec",
- "tokio",
- "trust-dns-proto 0.18.0-alpha.2",
-]
-
-[[package]]
-name = "trust-dns-resolver"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f23cdfdc3d8300b3c50c9e84302d3bd6d860fb9529af84ace6cf9665f181b77"
dependencies = [
"backtrace",
- "cfg-if",
+ "cfg-if 0.1.10",
"futures",
"ipconfig",
"lazy_static",
@@ -3254,7 +3366,7 @@ dependencies = [
"smallvec",
"thiserror",
"tokio",
- "trust-dns-proto 0.19.5",
+ "trust-dns-proto",
]
[[package]]
@@ -3266,9 +3378,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "ts-bookkeeping"
version = "0.1.0"
-source = "git+https://github.com/ReSpeak/tsclientlib#aba8899a418146cf91a35c20c6c107c42231d09a"
+source = "git+https://github.com/ReSpeak/tsclientlib#5b6da0feb88a07237448afe2f34d38cbe06f3249"
dependencies = [
- "base64 0.12.3",
+ "base64",
"derive_more",
"heck",
"itertools",
@@ -3287,10 +3399,10 @@ dependencies = [
[[package]]
name = "tsclientlib"
version = "0.1.0"
-source = "git+https://github.com/ReSpeak/tsclientlib#aba8899a418146cf91a35c20c6c107c42231d09a"
+source = "git+https://github.com/ReSpeak/tsclientlib#5b6da0feb88a07237448afe2f34d38cbe06f3249"
dependencies = [
"audiopus",
- "base64 0.12.3",
+ "base64",
"futures",
"git-testament",
"itertools",
@@ -3305,8 +3417,8 @@ dependencies = [
"thiserror",
"time 0.2.22",
"tokio",
- "trust-dns-proto 0.19.5",
- "trust-dns-resolver 0.19.5",
+ "trust-dns-proto",
+ "trust-dns-resolver",
"ts-bookkeeping",
"tsproto",
"tsproto-packets",
@@ -3317,10 +3429,10 @@ dependencies = [
[[package]]
name = "tsproto"
version = "0.1.0"
-source = "git+https://github.com/ReSpeak/tsclientlib#aba8899a418146cf91a35c20c6c107c42231d09a"
+source = "git+https://github.com/ReSpeak/tsclientlib#5b6da0feb88a07237448afe2f34d38cbe06f3249"
dependencies = [
"aes",
- "base64 0.12.3",
+ "base64",
"bitflags",
"curve25519-dalek",
"eax",
@@ -3350,10 +3462,10 @@ dependencies = [
[[package]]
name = "tsproto-packets"
version = "0.1.0"
-source = "git+https://github.com/ReSpeak/tsclientlib#aba8899a418146cf91a35c20c6c107c42231d09a"
+source = "git+https://github.com/ReSpeak/tsclientlib#5b6da0feb88a07237448afe2f34d38cbe06f3249"
dependencies = [
"arrayref",
- "base64 0.12.3",
+ "base64",
"bitflags",
"num-derive",
"num-traits",
@@ -3366,9 +3478,9 @@ dependencies = [
[[package]]
name = "tsproto-structs"
version = "0.1.0"
-source = "git+https://github.com/ReSpeak/tsclientlib#aba8899a418146cf91a35c20c6c107c42231d09a"
+source = "git+https://github.com/ReSpeak/tsclientlib#5b6da0feb88a07237448afe2f34d38cbe06f3249"
dependencies = [
- "base64 0.12.3",
+ "base64",
"csv",
"heck",
"lazy_static",
@@ -3380,10 +3492,10 @@ dependencies = [
[[package]]
name = "tsproto-types"
version = "0.1.0"
-source = "git+https://github.com/ReSpeak/tsclientlib#aba8899a418146cf91a35c20c6c107c42231d09a"
+source = "git+https://github.com/ReSpeak/tsclientlib#5b6da0feb88a07237448afe2f34d38cbe06f3249"
dependencies = [
"arrayref",
- "base64 0.12.3",
+ "base64",
"bitflags",
"curve25519-dalek",
"flakebi-ring",
@@ -3441,7 +3553,7 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
dependencies = [
- "tinyvec",
+ "tinyvec 0.3.4",
]
[[package]]
@@ -3490,18 +3602,19 @@ dependencies = [
[[package]]
name = "v_escape"
-version = "0.7.4"
+version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "660b101c07b5d0863deb9e7fb3138777e858d6d2a79f9e6049a27d1cc77c6da6"
+checksum = "039a44473286eb84e4e74f90165feff67c802dbeced7ee4c5b00d719b0d0475e"
dependencies = [
+ "buf-min",
"v_escape_derive",
]
[[package]]
name = "v_escape_derive"
-version = "0.5.6"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae"
+checksum = "c860ad1273f4eee7006cee05db20c9e60e5d24cba024a32e1094aa8e574f3668"
dependencies = [
"nom 4.2.3",
"proc-macro2",
@@ -3511,11 +3624,11 @@ dependencies = [
[[package]]
name = "v_htmlescape"
-version = "0.4.5"
+version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41"
+checksum = "11d7c2a33ed7cf0dc1b42bcf39e01b6512f9df08f09e1cd8a49d9dc49a6a9482"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"v_escape",
]
@@ -3577,7 +3690,7 @@ version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"serde",
"serde_json",
"wasm-bindgen-macro",
@@ -3604,7 +3717,7 @@ version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"js-sys",
"wasm-bindgen",
"web-sys",
@@ -3651,9 +3764,9 @@ dependencies = [
[[package]]
name = "widestring"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a763e303c0e0f23b0da40888724762e802a8ffefbc22de4127ef42493c2ea68c"
+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
[[package]]
name = "winapi"
@@ -3718,6 +3831,23 @@ dependencies = [
]
[[package]]
+name = "xtra"
+version = "0.5.0-beta.6"
+source = "git+https://github.com/Restioson/xtra#540f5c136b9a83ea2238968c81ba057b6c948c6b"
+dependencies = [
+ "async-trait",
+ "barrage",
+ "catty",
+ "flume",
+ "futures-core",
+ "futures-sink",
+ "futures-timer",
+ "futures-util",
+ "pollster",
+ "tokio",
+]
+
+[[package]]
name = "yaml-rust"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 163eb34..0517bea 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,32 +5,41 @@ authors = ["Jokler <jokler@protonmail.com>"]
edition = "2018"
license = "GPL-3.0-or-later"
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
[dependencies]
tsclientlib = { git = "https://github.com/ReSpeak/tsclientlib", features = ["unstable"] }
tsproto-packets = { git = "https://github.com/ReSpeak/tsclientlib" }
-log = "0.4.11"
-log4rs = "0.13.0"
-toml = "0.5.6"
-structopt = "0.3.16"
+toml = "0.5.7"
+structopt = "0.3.20"
humantime = "2.0.1"
-tokio = { version = "0.2.22", features = ["tcp", "io-util", "sync", "process"] }
-futures = "0.3.5"
+slog = "2.5.2"
+slog-async = "2.5.0"
+slog-scope = "4.3.0"
+slog-stdlog = "4.0.0"
+
+log = "0.4.11"
+log4rs = { version = "0.13.0" }
+
+tokio = { version = "0.2.22", features = ["rt-threaded", "process", "blocking", "io-std"] }
+futures = "0.3.6"
+# git version for async Actor trait
+xtra = { git = "https://github.com/Restioson/xtra", features = ["with-tokio-0_2"] }
+async-trait = "0.1.41"
-glib = "0.10.1"
-gstreamer = "0.16.2"
-gstreamer-app = "0.16.0"
-gstreamer-audio = "0.16.2"
-byte-slice-cast = "0.3.5"
-serde_json = "1.0.57"
-serde = "1.0.114"
-actix = "0.9.0"
+glib = "0.10.2"
+gstreamer = "0.16.4"
+gstreamer-app = "0.16.3"
+gstreamer-audio = "0.16.4"
+
+serde = "1.0.116"
+serde_json = "1.0.59"
+rand = { version = "0.7.3", features = ["small_rng"] }
+derive_more = "0.99.11"
+
+actix = "0.10.0"
actix-rt = "1.1.1"
-actix-web = "2.0.0"
-actix-files = "0.2.2"
+actix-web = "3.1.0"
+actix-files = "0.4.0"
+actix-slog = "0.2.1"
+askama_actix = "0.11.1"
askama = "0.10.3"
-rand = { version = "0.7.3", features = ["small_rng"] }
-derive_more = "0.99.9"
-askama_actix = "0.10.0"
diff --git a/src/audio_player.rs b/src/audio_player.rs
index 1f6649f..23581f9 100644
--- a/src/audio_player.rs
+++ b/src/audio_player.rs
@@ -7,36 +7,31 @@ use gstreamer as gst;
use gstreamer_app::{AppSink, AppSinkCallbacks};
use gstreamer_audio::{StreamVolume, StreamVolumeFormat};
-use crate::bot::{MusicBotMessage, State};
use glib::BoolError;
-use log::{debug, error, info, warn};
-use std::sync::{Arc, RwLock};
-use tokio::sync::mpsc::UnboundedSender;
+use slog::{debug, error, info, warn, Logger};
+use xtra::WeakAddress;
+use crate::bot::{MusicBot, MusicBotMessage, State};
use crate::command::{Seek, VolumeChange};
use crate::youtube_dl::AudioMetadata;
static GST_INIT: Once = Once::new();
-#[derive(PartialEq, Eq, Debug, Clone, Copy)]
-pub enum PollResult {
- Continue,
- Quit,
-}
-
pub struct AudioPlayer {
pipeline: gst::Pipeline,
bus: gst::Bus,
http_src: gst::Element,
- volume_f64: RwLock<f64>,
+ volume_f64: f64,
volume: gst::Element,
- sender: Arc<RwLock<UnboundedSender<MusicBotMessage>>>,
- currently_playing: RwLock<Option<AudioMetadata>>,
+ currently_playing: Option<AudioMetadata>,
+
+ logger: Logger,
}
fn make_element(factoryname: &str, display_name: &str) -> Result<gst::Element, AudioPlayerError> {
- Ok(gst::ElementFactory::make(factoryname, Some(display_name))?)
+ Ok(gst::ElementFactory::make(factoryname, Some(display_name))
+ .map_err(|_| AudioPlayerError::MissingPlugin(factoryname.to_string()))?)
}
fn link_elements(a: &gst::Element, b: &gst::Element) -> Result<(), AudioPlayerError> {
@@ -49,11 +44,12 @@ fn add_decode_bin_new_pad_callback(
decode_bin: &gst::Element,
audio_bin: gst::Bin,
ghost_pad: gst::GhostPad,
+ logger: Logger,
) {
decode_bin.connect_pad_added(move |_, new_pad| {
- debug!("New pad received on decode bin");
+ debug!(logger, "New pad received on decode bin");
let name = if let Some(caps) = new_pad.get_current_caps() {
- debug!("Pad caps: {}", caps.to_string());
+ debug!(logger, "Found caps"; "caps" => caps.to_string());
if let Some(structure) = caps.get_structure(0) {
Some(structure.get_name().to_string())
} else {
@@ -68,7 +64,7 @@ fn add_decode_bin_new_pad_callback(
peer.unlink(&ghost_pad).unwrap();
}
- info!("Found raw audio, linking audio bin");
+ info!(logger, "Found raw audio, linking audio bin");
new_pad.link(&ghost_pad).unwrap();
audio_bin.sync_state_with_parent().unwrap();
@@ -77,27 +73,15 @@ fn add_decode_bin_new_pad_callback(
}
impl AudioPlayer {
- pub fn new(
- sender: Arc<RwLock<UnboundedSender<MusicBotMessage>>>,
- callback: Option<Box<dyn FnMut(&[u8]) + Send>>,
- ) -> Result<Self, AudioPlayerError> {
+ pub fn new(logger: Logger) -> Result<Self, AudioPlayerError> {
GST_INIT.call_once(|| gst::init().unwrap());
- info!("Creating audio player");
+ info!(logger, "Creating audio player");
let pipeline = gst::Pipeline::new(Some("TeamSpeak Audio Player"));
let bus = pipeline.get_bus().unwrap();
let http_src = make_element("souphttpsrc", "http source")?;
- let decode_bin = make_element("decodebin", "decode bin")?;
- pipeline.add_many(&[&http_src, &decode_bin])?;
-
- link_elements(&http_src, &decode_bin)?;
-
- let (audio_bin, volume, ghost_pad) = Self::create_audio_bin(callback)?;
-
- add_decode_bin_new_pad_callback(&decode_bin, audio_bin.clone(), ghost_pad);
-
- pipeline.add(&audio_bin)?;
+ let volume = make_element("volume", "volume")?;
// The documentation says that we have to make sure to handle
// all messages if auto flushing is deactivated.
@@ -111,26 +95,31 @@ impl AudioPlayer {
pipeline,
bus,
http_src,
+ logger,
- volume_f64: RwLock::new(0.0),
+ volume_f64: 0.0,
volume,
- sender,
- currently_playing: RwLock::new(None),
+ currently_playing: None,
})
}
- fn create_audio_bin(
+ pub fn setup_with_audio_callback(
+ &self,
callback: Option<Box<dyn FnMut(&[u8]) + Send>>,
- ) -> Result<(gst::Bin, gst::Element, gst::GhostPad), AudioPlayerError> {
+ ) -> Result<(), AudioPlayerError> {
+ let decode_bin = make_element("decodebin", "decode bin")?;
+ self.pipeline.add_many(&[&self.http_src, &decode_bin])?;
+
+ link_elements(&self.http_src, &decode_bin)?;
+
let audio_bin = gst::Bin::new(Some("audio bin"));
let queue = make_element("queue", "audio queue")?;
let convert = make_element("audioconvert", "audio converter")?;
- let volume = make_element("volume", "volume")?;
let resample = make_element("audioresample", "audio resampler")?;
let pads = queue.get_sink_pads();
let queue_sink_pad = pads.first().unwrap();
- audio_bin.add_many(&[&queue, &convert, &volume, &resample])?;
+ audio_bin.add_many(&[&queue, &convert, &self.volume, &resample])?;
if let Some(mut callback) = callback {
let opus_enc = make_element("opusenc", "opus encoder")?;
@@ -160,49 +149,64 @@ impl AudioPlayer {
audio_bin.add_many(&[&opus_enc, &sink])?;
- gst::Element::link_many(&[&queue, &convert, &volume, &resample, &opus_enc, &sink])?;
+ gst::Element::link_many(&[
+ &queue,
+ &convert,
+ &self.volume,
+ &resample,
+ &opus_enc,
+ &sink,
+ ])?;
} else {
let sink = make_element("autoaudiosink", "auto audio sink")?;
audio_bin.add_many(&[&sink])?;
- gst::Element::link_many(&[&queue, &convert, &volume, &resample, &sink])?;
+ gst::Element::link_many(&[&queue, &convert, &self.volume, &resample, &sink])?;
};
let ghost_pad = GhostPad::with_target(Some("audio bin sink"), queue_sink_pad).unwrap();
ghost_pad.set_active(true)?;
audio_bin.add_pad(&ghost_pad)?;
- Ok((audio_bin, volume, ghost_pad))
+ add_decode_bin_new_pad_callback(
+ &decode_bin,
+ audio_bin.clone(),
+ ghost_pad,
+ self.logger.clone(),
+ );
+
+ self.pipeline.add(&audio_bin)?;
+
+ Ok(())
}
- pub fn set_metadata(&self, data: AudioMetadata) -> Result<(), AudioPlayerError> {
+ pub fn set_metadata(&mut self, data: AudioMetadata) -> Result<(), AudioPlayerError> {
self.set_source_url(data.url.clone())?;
- let mut currently_playing = self.currently_playing.write().unwrap();
- *currently_playing = Some(data);
+ self.currently_playing = Some(data);
Ok(())
}
fn set_source_url(&self, location: String) -> Result<(), AudioPlayerError> {
- info!("Setting location URI: {}", location);
+ info!(self.logger, "Setting source"; "url" => &location);
self.http_src.set_property("location", &location)?;
Ok(())
}
- pub fn change_volume(&self, volume: VolumeChange) -> Result<(), AudioPlayerError> {
+ pub fn change_volume(&mut self, volume: VolumeChange) -> Result<(), AudioPlayerError> {
let new_volume = match volume {
- VolumeChange::Positive(vol) => self.volume() + vol,
- VolumeChange::Negative(vol) => self.volume() - vol,
+ VolumeChange::Positive(vol) => self.volume_f64 + vol,
+ VolumeChange::Negative(vol) => self.volume_f64 - vol,
VolumeChange::Absolute(vol) => vol,
};
let new_volume = new_volume.max(0.0).min(1.0);
- *self.volume_f64.write().unwrap() = new_volume;
+ self.volume_f64 = new_volume;
let db = 50.0 * new_volume.log10();
- info!("Setting volume: {} -> {} dB", new_volume, db);
+ info!(self.logger, "Setting volume"; "volume" => new_volume, "db" => db);
let linear =
StreamVolume::convert_volume(StreamVolumeFormat::Db, StreamVolumeFormat::Linear, db);
@@ -212,36 +216,10 @@ impl AudioPlayer {
Ok(())
}
- pub fn is_started(&self) -> bool {
- let (_, current, pending) = self.pipeline.get_state(gst::ClockTime(None));
-
- match (current, pending) {
- (gst::State::Null, gst::State::VoidPending) => false,
- (_, gst::State::Null) => false,
- (gst::State::Ready, gst::State::VoidPending) => false,
- _ => true,
- }
- }
-
- pub fn volume(&self) -> f64 {
- *self.volume_f64.read().unwrap()
- }
-
- pub fn position(&self) -> Option<Duration> {
- self.pipeline
- .query_position::<gst::ClockTime>()
- .and_then(|t| t.0.map(Duration::from_nanos))
- }
-
- pub fn currently_playing(&self) -> Option<AudioMetadata> {
- self.currently_playing.read().unwrap().clone()
- }
+ pub fn reset(&mut self) -> Result<(), AudioPlayerError> {
+ info!(self.logger, "Setting pipeline state"; "to" => "null");
- pub fn reset(&self) -> Result<(), AudioPlayerError> {
- info!("Setting pipeline state to null");
-
- let mut currently_playing = self.currently_playing.write().unwrap();
- *currently_playing = None;
+ self.currently_playing = None;
self.pipeline.set_state(gst::State::Null)?;
@@ -249,7 +227,7 @@ impl AudioPlayer {
}
pub fn play(&self) -> Result<(), AudioPlayerError> {
- info!("Setting pipeline state to playing");
+ info!(self.logger, "Setting pipeline state"; "to" => "playing");
self.pipeline.set_state(gst::State::Playing)?;
@@ -257,7 +235,7 @@ impl AudioPlayer {
}
pub fn pause(&self) -> Result<(), AudioPlayerError> {
- info!("Setting pipeline state to paused");
+ info!(self.logger, "Setting pipeline state"; "to" => "paused");
self.pipeline.set_state(gst::State::Paused)?;
@@ -289,7 +267,7 @@ impl AudioPlayer {
};
let time = humantime::format_duration(absolute);
- info!("Seeking to {}", time);
+ info!(self.logger, "Seeking"; "time" => %time);
self.pipeline.seek_simple(
gst::SeekFlags::FLUSH,
@@ -300,121 +278,125 @@ impl AudioPlayer {
}
pub fn stop_current(&self) -> Result<(), AudioPlayerError> {
- info!("Stopping pipeline, sending EOS");
+ info!(self.logger, "Stopping pipeline, sending EOS");
self.bus.post(&gst::message::Eos::new())?;
Ok(())
}
- pub fn quit(&self, reason: String) {
- info!("Quitting audio player");
-
- if self
- .bus
- .post(&gst::message::Application::new(gst::Structure::new_empty(
- "quit",
- )))
- .is_err()
- {
- warn!("Tried to send \"quit\" app event on flushing bus.");
+ pub fn is_started(&self) -> bool {
+ let (_, current, pending) = self.pipeline.get_state(gst::ClockTime(None));
+
+ match (current, pending) {
+ (gst::State::Null, gst::State::VoidPending) => false,
+ (_, gst::State::Null) => false,
+ (gst::State::Ready, gst::State::VoidPending) => false,
+ _ => true,
}
+ }
- let sender = self.sender.read().unwrap();
- sender.send(MusicBotMessage::Quit(reason)).unwrap();
+ pub fn volume(&self) -> f64 {
+ self.volume_f64
}
- fn send_state(&self, state: State) {
- info!("Sending state {:?} to application", state);
- let sender = self.sender.read().unwrap();
- sender.send(MusicBotMessage::StateChange(state)).unwrap();
+ pub fn position(&self) -> Option<Duration> {
+ self.pipeline
+ .query_position::<gst::ClockTime>()
+ .and_then(|t| t.0.map(Duration::from_nanos))
}
- pub fn poll(&self) -> PollResult {
- debug!("Polling GStreamer");
- 'outer: loop {
- while let Some(msg) = self.bus.timed_pop(gst::ClockTime(None)) {
- use gst::MessageView;
-
- match msg.view() {
- MessageView::StateChanged(state) => {
- if let Some(src) = state.get_src() {
- if src.get_name() != self.pipeline.get_name() {
- continue;
- }
- }
+ pub fn currently_playing(&self) -> Option<AudioMetadata> {
+ self.currently_playing.clone()
+ }
+
+ pub fn register_bot(&self, bot: WeakAddress<MusicBot>) {
+ let pipeline_name = self.pipeline.get_name();
+ debug!(self.logger, "Setting sync handler on gstreamer bus");
- let old = state.get_old();
- let current = state.get_current();
- let pending = state.get_pending();
-
- match (old, current, pending) {
- (gst::State::Paused, gst::State::Playing, gst::State::VoidPending) => {
- self.send_state(State::Playing)
- }
- (gst::State::Playing, gst::State::Paused, gst::State::VoidPending) => {
- self.send_state(State::Paused)
- }
- (_, gst::State::Ready, gst::State::Null) => {
- self.send_state(State::Stopped)
- }
- (_, gst::State::Null, gst::State::VoidPending) => {
- self.send_state(State::Stopped)
- }
- _ => {
- debug!(
- "Pipeline transitioned from {:?} to {:?}, with {:?} pending",
- old, current, pending
- );
- }
+ let logger = self.logger.clone();
+ let handle = tokio::runtime::Handle::current();
+ self.bus.set_sync_handler(move |_, msg| {
+ use gst::MessageView;
+
+ match msg.view() {
+ MessageView::StateChanged(state) => {
+ if let Some(src) = state.get_src() {
+ if src.get_name() != pipeline_name {
+ return gst::BusSyncReply::Drop;
}
}
- MessageView::Eos(..) => {
- info!("End of stream reached");
- self.reset().unwrap();
- break 'outer;
- }
- MessageView::Warning(warn) => {
- warn!(
- "Warning from {:?}: {} ({:?})",
- warn.get_src().map(|s| s.get_path_string()),
- warn.get_error(),
- warn.get_debug()
- );
- break 'outer;
- }
- MessageView::Error(err) => {
- error!(
- "Error from {:?}: {} ({:?})",
- err.get_src().map(|s| s.get_path_string()),
- err.get_error(),
- err.get_debug()
- );
- break 'outer;
- }
- MessageView::Application(content) => {
- if let Some(s) = content.get_structure() {
- if s.get_name() == "quit" {
- self.reset().unwrap();
- return PollResult::Quit;
- }
+ let old = state.get_old();
+ let current = state.get_current();
+ let pending = state.get_pending();
+
+ match (old, current, pending) {
+ (gst::State::Paused, gst::State::Playing, gst::State::VoidPending) => {
+ send_state(&handle, &bot, State::Playing);
+ }
+ (gst::State::Playing, gst::State::Paused, gst::State::VoidPending) => {
+ send_state(&handle, &bot, State::Paused);
+ }
+ (_, gst::State::Ready, gst::State::Null) => {
+ send_state(&handle, &bot, State::Stopped);
+ }
+ (_, gst::State::Null, gst::State::VoidPending) => {
+ send_state(&handle, &bot, State::Stopped);
+ }
+ _ => {
+ debug!(
+ logger,
+ "Pipeline transitioned";
+ "from" => ?old,
+ "to" => ?current,
+ "pending" => ?pending
+ );
}
}
- _ => {
- //debug!("Unhandled message on bus: {:?}", msg)
- }
- };
+ }
+ MessageView::Eos(..) => {
+ info!(logger, "End of stream reached");
+
+ send_state(&handle, &bot, State::EndOfStream);
+ }
+ MessageView::Warning(warn) => {
+ warn!(
+ logger,
+ "Received warning from bus";
+ "source" => ?warn.get_src().map(|s| s.get_path_string()),
+ "error" => %warn.get_error(),
+ "debug" => ?warn.get_debug()
+ );
+ }
+ MessageView::Error(err) => {
+ error!(
+ logger,
+ "Received error from bus";
+ "source" => ?err.get_src().map(|s| s.get_path_string()),
+ "error" => %err.get_error(),
+ "debug" => ?err.get_debug()
+ );
+
+ send_state(&handle, &bot, State::EndOfStream);
+ }
+ _ => {
+ //debug!("Unhandled message on bus: {:?}", msg)
+ }
}
- }
- debug!("Left GStreamer message loop");
- PollResult::Continue
+ gst::BusSyncReply::Drop
+ });
}
}
+fn send_state(handle: &tokio::runtime::Handle, addr: &WeakAddress<MusicBot>, state: State) {
+ handle.spawn(addr.send(MusicBotMessage::StateChange(state)));
+}
+
#[derive(Debug)]
pub enum AudioPlayerError {
+ MissingPlugin(String),
GStreamerError(glib::error::BoolError),
StateChangeFailed,
SeekError,
diff --git a/src/bot/master.rs b/src/bot/master.rs
index 1480e17..94332ac 100644
--- a/src/bot/master.rs
+++ b/src/bot/master.rs
@@ -1,41 +1,52 @@
use std::collections::HashMap;
-use std::future::Future;
-use std::sync::{Arc, RwLock};
-use log::info;
+use async_trait::async_trait;
+use futures::future;
use rand::{rngs::SmallRng, seq::SliceRandom, SeedableRng};
use serde::{Deserialize, Serialize};
-use tokio::sync::mpsc::UnboundedSender;
-use tsclientlib::{ClientId, Connection, Identity, MessageTarget};
+use slog::{error, info, o, trace, Logger};
+use tsclientlib::{ClientId, ConnectOptions, Connection, Identity, MessageTarget};
+use xtra::{spawn::Tokio, Actor, Address, Context, Handler, Message, WeakAddress};
use crate::audio_player::AudioPlayerError;
use crate::teamspeak::TeamSpeakConnection;
use crate::Args;
-use crate::bot::{MusicBot, MusicBotArgs, MusicBotMessage};
+use crate::bot::{GetBotData, GetChannel, GetName, MusicBot, MusicBotArgs, MusicBotMessage};
pub struct MasterBot {
- config: Arc<MasterConfig>,
- music_bots: Arc<RwLock<MusicBots>>,
+ config: MasterConfig,
+ my_addr: Option<WeakAddress<Self>>,
teamspeak: TeamSpeakConnection,
- sender: Arc<RwLock<UnboundedSender<MusicBotMessage>>>,
+ available_names: Vec<String>,
+ available_ids: Vec<Identity>,
+ connected_bots: HashMap<String, Address<MusicBot>>,
+ rng: SmallRng,
+ logger: Logger,
}
-struct MusicBots {
- rng: SmallRng,
- available_names: Vec<usize>,
- available_ids: Vec<usize>,
- connected_bots: HashMap<String, Arc<MusicBot>>,
+#[derive(Debug, Serialize, Deserialize)]
+pub struct MasterArgs {
+ #[serde(default = "default_name")]
+ pub master_name: String,
+ pub address: String,
+ pub channel: Option<String>,
+ #[serde(default = "default_verbose")]
+ pub verbose: u8,
+ pub domain: String,
+ pub bind_address: String,
+ pub names: Vec<String>,
+ pub id: Option<Identity>,
+ pub ids: Option<Vec<Identity>>,
}
impl MasterBot {
- pub async fn new(args: MasterArgs) -> (Arc<Self>, impl Future) {
- let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
- let tx = Arc::new(RwLock::new(tx));
- info!("Starting in TeamSpeak mode");
+ pub async fn spawn(args: MasterArgs, logger: Logger) -> Address<Self> {
+ info!(logger, "Starting in TeamSpeak mode");
let mut con_config = Connection::build(args.address.clone())
+ .logger(logger.clone())
.version(tsclientlib::Version::Linux_3_3_2)
.name(args.master_name.clone())
.identity(args.id.expect("identity should exist"))
@@ -47,168 +58,116 @@ impl MasterBot {
con_config = con_config.channel(channel);
}
- let connection = TeamSpeakConnection::new(tx.clone(), con_config)
- .await
- .unwrap();
+ let connection = TeamSpeakConnection::new(logger.clone()).await.unwrap();
+ trace!(logger, "Created teamspeak connection");
- let config = Arc::new(MasterConfig {
+ let config = MasterConfig {
master_name: args.master_name,
address: args.address,
- names: args.names,
- ids: args.ids.expect("identies should exists"),
- local: args.local,
verbose: args.verbose,
- });
-
- let name_count = config.names.len();
- let id_count = config.ids.len();
+ };
- let music_bots = Arc::new(RwLock::new(MusicBots {
+ let bot_addr = Self {
+ config,
+ my_addr: None,
+ teamspeak: connection,
+ logger: logger.clone(),
rng: SmallRng::from_entropy(),
- available_names: (0..name_count).collect(),
- available_ids: (0..id_count).collect(),
+ available_names: args.names,
+ available_ids: args.ids.expect("identities"),
connected_bots: HashMap::new(),
- }));
+ }
+ .create(None)
+ .spawn(&mut Tokio::Global);
- let bot = Arc::new(Self {
- config,
- music_bots,
- teamspeak: connection,
- sender: tx.clone(),
- });
-
- let cbot = bot.clone();
- let msg_loop = async move {
- 'outer: loop {
- while let Some(msg) = rx.recv().await {
- match msg {
- MusicBotMessage::Quit(reason) => {
- let mut cteamspeak = cbot.teamspeak.clone();
- cteamspeak.disconnect(&reason).await;
- break 'outer;
- }
- MusicBotMessage::ClientDisconnected { id, .. } => {
- if id == cbot.my_id().await {
- // TODO Reconnect since quit was not called
- break 'outer;
- }
- }
- _ => cbot.on_message(msg).await.unwrap(),
- }
- }
- }
- };
+ bot_addr.send(Connect(con_config)).await.unwrap().unwrap();
+ trace!(logger, "Spawned master bot actor");
- (bot, msg_loop)
+ bot_addr
}
- async fn build_bot_args_for(&self, id: ClientId) -> Result<MusicBotArgs, BotCreationError> {
- let mut cteamspeak = self.teamspeak.clone();
- let channel = match cteamspeak.channel_of_user(id).await {
+ async fn bot_args_for_client(
+ &mut self,
+ user_id: ClientId,
+ ) -> Result<MusicBotArgs, BotCreationError> {
+ let channel = match self.teamspeak.channel_of_user(user_id).await {
Some(channel) => channel,
None => return Err(BotCreationError::UnfoundUser),
};
- if channel == cteamspeak.my_channel().await {
+ if channel == self.teamspeak.current_channel().await.unwrap() {
return Err(BotCreationError::MasterChannel(
self.config.master_name.clone(),
));
}
- let MusicBots {
- ref mut rng,
- ref mut available_names,
- ref mut available_ids,
- ref connected_bots,
- } = &mut *self.music_bots.write().expect("RwLock was not poisoned");
-
- for bot in connected_bots.values() {
- if bot.my_channel().await == channel {
- return Err(BotCreationError::MultipleBots(bot.name().to_owned()));
+ for bot in self.connected_bots.values() {
+ if bot.send(GetChannel).await.unwrap() == Some(channel) {
+ return Err(BotCreationError::MultipleBots(
+ bot.send(GetName).await.unwrap(),
+ ));
}
}
- let channel_path = cteamspeak
- .channel_path_of_user(id)
+ let channel_path = self
+ .teamspeak
+ .channel_path_of_user(user_id)
.await
.expect("can find poke sender");
- available_names.shuffle(rng);
- let name_index = match available_names.pop() {
+ self.available_names.shuffle(&mut self.rng);
+ let name = match self.available_names.pop() {
Some(v) => v,
None => {
return Err(BotCreationError::OutOfNames);
}
};
- let name = self.config.names[name_index].clone();
- available_ids.shuffle(rng);
- let id_index = match available_ids.pop() {
+ self.available_ids.shuffle(&mut self.rng);
+ let identity = match self.available_ids.pop() {
Some(v) => v,
None => {
return Err(BotCreationError::OutOfIdentities);
}
};
- let id = self.config.ids[id_index].clone();
-
- let cmusic_bots = self.music_bots.clone();
- let disconnect_cb = Box::new(move |n, name_index, id_index| {
- let mut music_bots = cmusic_bots.write().expect("RwLock was not poisoned");
- music_bots.connected_bots.remove(&n);
- music_bots.available_names.push(name_index);
- music_bots.available_ids.push(id_index);
- });
-
- info!("Connecting to {} on {}", channel_path, self.config.address);
-
Ok(MusicBotArgs {
- name,
- name_index,
- id_index,
- local: self.config.local,
+ name: name.clone(),
+ master: self.my_addr.clone(),
address: self.config.address.clone(),
- id,
+ identity,
+ local: false,
channel: channel_path,
verbose: self.config.verbose,
- disconnect_cb,
+ logger: self.logger.new(o!("musicbot" => name)),
})
}
- async fn spawn_bot_for(&self, id: ClientId) {
- match self.build_bot_args_for(id).await {
+ async fn spawn_bot_for_client(&mut self, id: ClientId) {
+ match self.bot_args_for_client(id).await {
Ok(bot_args) => {
- let (bot, fut) = MusicBot::new(bot_args).await;
- tokio::spawn(fut);
- let mut music_bots = self.music_bots.write().expect("RwLock was not poisoned");
- music_bots
- .connected_bots
- .insert(bot.name().to_string(), bot);
- }
- Err(e) => {
- let mut cteamspeak = self.teamspeak.clone();
- cteamspeak.send_message_to_user(id, e.to_string()).await
+ let name = bot_args.name.clone();
+ let bot = MusicBot::spawn(bot_args).await;
+ self.connected_bots.insert(name, bot);
}
+ Err(e) => self.teamspeak.send_message_to_user(id, e.to_string()).await,
}
}
- async fn on_message(&self, message: MusicBotMessage) -> Result<(), AudioPlayerError> {
+ async fn on_message(&mut self, message: MusicBotMessage) -> Result<(), AudioPlayerError> {
match message {
MusicBotMessage::TextMessage(message) => {
if let MessageTarget::Poke(who) = message.target {
- info!("Poked by {}, creating bot for their channel", who);
- self.spawn_bot_for(who).await;
+ info!(
+ self.logger,
+ "Poked, creating bot"; "user" => %who
+ );
+ self.spawn_bot_for_client(who).await;
}
}
- MusicBotMessage::ChannelAdded(id) => {
- let mut cteamspeak = self.teamspeak.clone();
- cteamspeak.subscribe(id).await;
- }
MusicBotMessage::ClientAdded(id) => {
- let mut cteamspeak = self.teamspeak.clone();
-
- if id == cteamspeak.my_id().await {
- cteamspeak
+ if id == self.teamspeak.my_id().await {
+ self.teamspeak
.set_description(String::from("Poke me if you want a music bot!"))
.await;
}
@@ -219,41 +178,17 @@ impl MasterBot {
Ok(())
}
- async fn my_id(&self) -> ClientId {
- let mut cteamspeak = self.teamspeak.clone();
+ pub async fn bot_data(&self, name: String) -> Option<crate::web_server::BotData> {
+ let bot = self.connected_bots.get(&name)?;
- cteamspeak.my_id().await
+ bot.send(GetBotData).await.ok()
}
- pub fn bot_data(&self, name: String) -> Option<crate::web_server::BotData> {
- let music_bots = self.music_bots.read().unwrap();
- let bot = music_bots.connected_bots.get(&name)?;
-
- Some(crate::web_server::BotData {
- name,
- state: bot.state(),
- volume: bot.volume(),
- position: bot.position(),
- currently_playing: bot.currently_playing(),
- playlist: bot.playlist_to_vec(),
- })
- }
-
- pub fn bot_datas(&self) -> Vec<crate::web_server::BotData> {
- let music_bots = self.music_bots.read().unwrap();
-
- let len = music_bots.connected_bots.len();
+ pub async fn bot_datas(&self) -> Vec<crate::web_server::BotData> {
+ let len = self.connected_bots.len();
let mut result = Vec::with_capacity(len);
- for (name, bot) in &music_bots.connected_bots {
- let bot_data = crate::web_server::BotData {
- name: name.clone(),
- state: bot.state(),
- volume: bot.volume(),
- position: bot.position(),
- currently_playing: bot.currently_playing(),
- playlist: bot.playlist_to_vec(),
- };
-
+ for bot in self.connected_bots.values() {
+ let bot_data = bot.send(GetBotData).await.unwrap();
result.push(bot_data);
}
@@ -261,24 +196,96 @@ impl MasterBot {
}
pub fn bot_names(&self) -> Vec<String> {
- let music_bots = self.music_bots.read().unwrap();
-
- let len = music_bots.connected_bots.len();
+ let len = self.connected_bots.len();
let mut result = Vec::with_capacity(len);
- for name in music_bots.connected_bots.keys() {
+ for name in self.connected_bots.keys() {
result.push(name.clone());
}
result
}
- pub fn quit(&self, reason: String) {
- let music_bots = self.music_bots.read().unwrap();
- for bot in music_bots.connected_bots.values() {
- bot.quit(reason.clone())
+ fn on_bot_disconnect(&mut self, name: String, id: Identity) {
+ self.connected_bots.remove(&name);
+ self.available_names.push(name);
+ self.available_ids.push(id);
+ }
+
+ pub async fn quit(&mut self, reason: String) -> Result<(), tsclientlib::Error> {
+ let futures = self
+ .connected_bots
+ .values()
+ .map(|b| b.send(Quit(reason.clone())));
+ for res in future::join_all(futures).await {
+ if let Err(e) = res {
+ error!(self.logger, "Failed to shut down bot"; "error" => %e);
+ }
}
- let sender = self.sender.read().unwrap();
- sender.send(MusicBotMessage::Quit(reason)).unwrap();
+ self.teamspeak.disconnect(&reason).await
+ }
+}
+
+#[async_trait]
+impl Actor for MasterBot {
+ async fn started(&mut self, ctx: &mut Context<Self>) {
+ self.my_addr = Some(ctx.address().unwrap().downgrade());
+ }
+}
+
+pub struct Connect(pub ConnectOptions);
+impl Message for Connect {
+ type Result = Result<(), tsclientlib::Error>;
+}
+
+#[async_trait]
+impl Handler<Connect> for MasterBot {
+ async fn handle(
+ &mut self,
+ opt: Connect,
+ ctx: &mut Context<Self>,
+ ) -> Result<(), tsclientlib::Error> {
+ let addr = ctx.address().unwrap();
+ self.teamspeak.connect_for_bot(opt.0, addr.downgrade())?;
+ Ok(())
+ }
+}
+
+pub struct Quit(pub String);
+impl Message for Quit {
+ type Result = Result<(), tsclientlib::Error>;
+}
+
+#[async_trait]
+impl Handler<Quit> for MasterBot {
+ async fn handle(&mut self, q: Quit, _: &mut Context<Self>) -> Result<(), tsclientlib::Error> {
+ self.quit(q.0).await
+ }
+}
+
+pub struct BotDisonnected {
+ pub name: String,
+ pub identity: Identity,
+}
+
+impl Message for BotDisonnected {
+ type Result = ();
+}
+
+#[async_trait]
+impl Handler<BotDisonnected> for MasterBot {
+ async fn handle(&mut self, dc: BotDisonnected, _: &mut Context<Self>) {
+ self.on_bot_disconnect(dc.name, dc.identity);
+ }
+}
+
+#[async_trait]
+impl Handler<MusicBotMessage> for MasterBot {
+ async fn handle(
+ &mut self,
+ msg: MusicBotMessage,
+ _: &mut Context<Self>,
+ ) -> Result<(), AudioPlayerError> {
+ self.on_message(msg).await
}
}
@@ -313,31 +320,10 @@ impl std::fmt::Display for BotCreationError {
}
}
-#[derive(Debug, Serialize, Deserialize)]
-pub struct MasterArgs {
- #[serde(default = "default_name")]
- pub master_name: String,
- #[serde(default = "default_local")]
- pub local: bool,
- pub address: String,
- pub channel: Option<String>,
- #[serde(default = "default_verbose")]
- pub verbose: u8,
- pub domain: String,
- pub bind_address: String,
- pub names: Vec<String>,
- pub id: Option<Identity>,
- pub ids: Option<Vec<Identity>>,
-}
-
fn default_name() -> String {
String::from("PokeBot")
}
-fn default_local() -> bool {
- false
-}
-
fn default_verbose() -> u8 {
0
}
@@ -345,7 +331,6 @@ fn default_verbose() -> u8 {
impl MasterArgs {
pub fn merge(self, args: Args) -> Self {
let address = args.address.unwrap_or(self.address);
- let local = args.local || self.local;
let channel = args.master_channel.or(self.channel);
let verbose = if args.verbose > 0 {
args.verbose
@@ -357,7 +342,6 @@ impl MasterArgs {
master_name: self.master_name,
names: self.names,
ids: self.ids,
- local,
address,
domain: self.domain,
bind_address: self.bind_address,
@@ -371,8 +355,5 @@ impl MasterArgs {
pub struct MasterConfig {
pub master_name: String,
pub address: String,
- pub names: Vec<String>,
- pub ids: Vec<Identity>,
- pub local: bool,
pub verbose: u8,
}
diff --git a/src/bot/music.rs b/src/bot/music.rs
index 90305d0..a57b66c 100644
--- a/src/bot/music.rs
+++ b/src/bot/music.rs
@@ -1,25 +1,22 @@
-use std::future::Future;
-use std::io::BufRead;
-use std::sync::{Arc, RwLock};
-use std::thread;
-use std::time::Duration;
+use async_trait::async_trait;
-use log::{debug, info};
use serde::Serialize;
+use slog::{debug, info, Logger};
use structopt::StructOpt;
-use tokio::sync::mpsc::UnboundedSender;
use tsclientlib::{data, ChannelId, ClientId, Connection, Identity, Invoker, MessageTarget};
+use xtra::{spawn::Tokio, Actor, Address, Context, Handler, Message, WeakAddress};
-use crate::audio_player::{AudioPlayer, AudioPlayerError, PollResult};
+use crate::audio_player::{AudioPlayer, AudioPlayerError};
+use crate::bot::{BotDisonnected, Connect, MasterBot, Quit};
use crate::command::Command;
use crate::command::VolumeChange;
use crate::playlist::Playlist;
use crate::teamspeak as ts;
-use crate::youtube_dl::AudioMetadata;
+use crate::youtube_dl::{self, AudioMetadata};
use ts::TeamSpeakConnection;
#[derive(Debug)]
-pub struct Message {
+pub struct ChatMessage {
pub target: MessageTarget,
pub invoker: Invoker,
pub text: String,
@@ -33,6 +30,10 @@ pub enum State {
EndOfStream,
}
+impl Message for State {
+ type Result = ();
+}
+
impl std::fmt::Display for State {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
@@ -47,7 +48,7 @@ impl std::fmt::Display for State {
#[derive(Debug)]
pub enum MusicBotMessage {
- TextMessage(Message),
+ TextMessage(ChatMessage),
ClientChannel {
client: ClientId,
old_channel: ChannelId,
@@ -59,112 +60,95 @@ pub enum MusicBotMessage {
client: Box<data::Client>,
},
StateChange(State),
- Quit(String),
+}
+
+impl Message for MusicBotMessage {
+ type Result = Result<(), AudioPlayerError>;
}
pub struct MusicBot {
name: String,
- player: Arc<AudioPlayer>,
+ identity: Identity,
+ player: AudioPlayer,
teamspeak: Option<TeamSpeakConnection>,
- playlist: Arc<RwLock<Playlist>>,
- state: Arc<RwLock<State>>,
+ master: Option<WeakAddress<MasterBot>>,
+ playlist: Playlist,
+ state: State,
+ logger: Logger,
}
pub struct MusicBotArgs {
pub name: String,
- pub name_index: usize,
- pub id_index: usize,
+ pub master: Option<WeakAddress<MasterBot>>,
pub local: bool,
pub address: String,
- pub id: Identity,
+ pub identity: Identity,
pub channel: String,
pub verbose: u8,
- pub disconnect_cb: Box<dyn FnMut(String, usize, usize) + Send + Sync>,
+ pub logger: Logger,
}
impl MusicBot {
- pub async fn new(args: MusicBotArgs) -> (Arc<Self>, impl Future<Output = ()>) {
- let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
- let tx = Arc::new(RwLock::new(tx));
- let (player, connection) = if args.local {
- info!("Starting in CLI mode");
- let audio_player = AudioPlayer::new(tx.clone(), None).unwrap();
-
- (audio_player, None)
- } else {
- info!("Starting in TeamSpeak mode");
-
- let con_config = Connection::build(args.address)
- .version(tsclientlib::Version::Linux_3_3_2)
- .name(format!("🎵 {}", args.name))
- .identity(args.id)
- .log_commands(args.verbose >= 1)
- .log_packets(args.verbose >= 2)
- .log_udp_packets(args.verbose >= 3)
- .channel(args.channel);
-
- let connection = TeamSpeakConnection::new(tx.clone(), con_config)
- .await
- .unwrap();
- let mut cconnection = connection.clone();
- let audio_player = AudioPlayer::new(
- tx.clone(),
- Some(Box::new(move |samples| {
- let mut rt = tokio::runtime::Runtime::new().unwrap();
- rt.block_on(cconnection.send_audio_packet(samples));
- })),
- )
- .unwrap();
-
- (audio_player, Some(connection))
- };
-
+ pub async fn spawn(args: MusicBotArgs) -> Address<Self> {
+ let mut player = AudioPlayer::new(args.logger.clone()).unwrap();
player.change_volume(VolumeChange::Absolute(0.5)).unwrap();
- let player = Arc::new(player);
- let playlist = Arc::new(RwLock::new(Playlist::new()));
- spawn_gstreamer_thread(player.clone(), tx.clone());
+ let playlist = Playlist::new(args.logger.clone());
- if args.local {
- spawn_stdin_reader(tx);
- }
+ let teamspeak = if args.local {
+ info!(args.logger, "Starting in CLI mode");
+ player.setup_with_audio_callback(None).unwrap();
- let bot = Arc::new(Self {
+ None
+ } else {
+ Some(TeamSpeakConnection::new(args.logger.clone()).await.unwrap())
+ };
+ let bot = Self {
name: args.name.clone(),
+ master: args.master,
+ identity: args.identity.clone(),
player,
- teamspeak: connection,
+ teamspeak,
playlist,
- state: Arc::new(RwLock::new(State::EndOfStream)),
- });
-
- let cbot = bot.clone();
- let mut disconnect_cb = args.disconnect_cb;
- let name = args.name;
- let name_index = args.name_index;
- let id_index = args.id_index;
- let msg_loop = async move {
- 'outer: loop {
- while let Some(msg) = rx.recv().await {
- if let MusicBotMessage::Quit(reason) = msg {
- if let Some(ts) = &cbot.teamspeak {
- let mut ts = ts.clone();
- ts.disconnect(&reason).await;
- }
- disconnect_cb(name, name_index, id_index);
- break 'outer;
- }
- cbot.on_message(msg).await.unwrap();
- }
- }
- debug!("Left message loop");
+ state: State::EndOfStream,
+ logger: args.logger.clone(),
};
- bot.update_name(State::EndOfStream).await;
+ let bot_addr = bot.create(None).spawn(&mut Tokio::Global);
+
+ info!(
+ args.logger,
+ "Connecting";
+ "name" => &args.name,
+ "channel" => &args.channel,
+ "address" => &args.address,
+ );
+
+ let opt = Connection::build(args.address)
+ .logger(args.logger.clone())
+ .version(tsclientlib::Version::Linux_3_3_2)
+ .name(format!("🎵 {}", args.name))
+ .identity(args.identity)
+ .log_commands(args.verbose >= 1)
+ .log_packets(args.verbose >= 2)
+ .log_udp_packets(args.verbose >= 3)
+ .channel(args.channel);
+ bot_addr.send(Connect(opt)).await.unwrap().unwrap();
+ bot_addr
+ .send(MusicBotMessage::StateChange(State::EndOfStream))
+ .await
+ .unwrap()
+ .unwrap();
+
+ if args.local {
+ debug!(args.logger, "Spawning stdin reader thread");
+ spawn_stdin_reader(bot_addr.downgrade());
+ }
- (bot, msg_loop)
+ bot_addr
}
- async fn start_playing_audio(&self, metadata: AudioMetadata) {
+ async fn start_playing_audio(&mut self, metadata: AudioMetadata) {
let duration = if let Some(duration) = metadata.duration {
format!("({})", ts::bold(&humantime::format_duration(duration)))
} else {
@@ -184,25 +168,16 @@ impl MusicBot {
self.player.play().unwrap();
}
- pub async fn add_audio(&self, url: String, user: String) {
- match crate::youtube_dl::get_audio_download_from_url(url).await {
+ pub async fn add_audio(&mut self, url: String, user: String) {
+ match youtube_dl::get_audio_download_from_url(url, &self.logger).await {
Ok(mut metadata) => {
metadata.added_by = user;
- info!("Found audio url: {}", metadata.url);
+ info!(self.logger, "Found source"; "url" => &metadata.url);
- // RWLockGuard can not be kept around or the compiler complains that
- // it might cross the await boundary
- self.playlist
- .write()
- .expect("RwLock was not poisoned")
- .push(metadata.clone());
+ self.playlist.push(metadata.clone());
if !self.player.is_started() {
- let entry = self
- .playlist
- .write()
- .expect("RwLock was not poisoned")
- .pop();
+ let entry = self.playlist.pop();
if let Some(request) = entry {
self.start_playing_audio(request).await;
}
@@ -222,7 +197,7 @@ impl MusicBot {
}
}
Err(e) => {
- info!("Failed to find audio url: {}", e);
+ info!(self.logger, "Failed to find audio url"; "error" => &e);
self.send_message(format!("Failed to find url: {}", e))
.await;
@@ -235,74 +210,50 @@ impl MusicBot {
}
pub fn state(&self) -> State {
- *self.state.read().expect("RwLock was not poisoned")
+ self.state
}
- pub fn volume(&self) -> f64 {
+ pub async fn volume(&self) -> f64 {
self.player.volume()
}
- pub fn position(&self) -> Option<Duration> {
- self.player.position()
- }
-
- pub fn currently_playing(&self) -> Option<AudioMetadata> {
- self.player.currently_playing()
- }
+ pub async fn current_channel(&mut self) -> Option<ChannelId> {
+ let ts = self.teamspeak.as_mut().expect("current_channel needs ts");
- pub fn playlist_to_vec(&self) -> Vec<AudioMetadata> {
- self.playlist.read().unwrap().to_vec()
+ ts.current_channel().await
}
- pub async fn my_channel(&self) -> ChannelId {
- let ts = self.teamspeak.as_ref().expect("my_channel needs ts");
-
- let mut ts = ts.clone();
- ts.my_channel().await
- }
+ async fn user_count(&mut self, channel: ChannelId) -> u32 {
+ let ts = self.teamspeak.as_mut().expect("user_count needs ts");
- async fn user_count(&self, channel: ChannelId) -> u32 {
- let ts = self.teamspeak.as_ref().expect("user_count needs ts");
-
- let mut ts = ts.clone();
ts.user_count(channel).await
}
- async fn send_message(&self, text: String) {
- debug!("Sending message to TeamSpeak: {}", text);
+ async fn send_message(&mut self, text: String) {
+ debug!(self.logger, "Sending message to TeamSpeak"; "message" => &text);
- if let Some(ts) = &self.teamspeak {
- let mut ts = ts.clone();
+ if let Some(ts) = &mut self.teamspeak {
ts.send_message_to_channel(text).await;
}
}
- async fn set_nickname(&self, name: String) {
- info!("Setting TeamSpeak nickname: {}", name);
+ async fn set_nickname(&mut self, name: String) {
+ info!(self.logger, "Setting TeamSpeak nickname"; "name" => &name);
- if let Some(ts) = &self.teamspeak {
- let mut ts = ts.clone();
+ if let Some(ts) = &mut self.teamspeak {
ts.set_nickname(name).await;
}
}
- async fn set_description(&self, desc: String) {
- info!("Setting TeamSpeak description: {}", desc);
+ async fn set_description(&mut self, desc: String) {
+ info!(self.logger, "Setting TeamSpeak description"; "description" => &desc);
- if let Some(ts) = &self.teamspeak {
- let mut ts = ts.clone();
+ if let Some(ts) = &mut self.teamspeak {
ts.set_description(desc).await;
}
}
- async fn subscribe(&self, id: ChannelId) {
- if let Some(ts) = &self.teamspeak {
- let mut ts = ts.clone();
- ts.subscribe(id).await;
- }
- }
-
- async fn on_text(&self, message: Message) -> Result<(), AudioPlayerError> {
+ async fn on_text(&mut self, message: ChatMessage) -> Result<(), AudioPlayerError> {
let msg = message.text;
if msg.starts_with('!') {
let tokens = msg[1..].split_whitespace().collect::<Vec<_>>();
@@ -319,13 +270,15 @@ impl MusicBot {
Ok(())
}
- async fn on_command(&self, command: Command, invoker: Invoker) -> Result<(), AudioPlayerError> {
+ async fn on_command(
+ &mut self,
+ command: Command,
+ invoker: Invoker,
+ ) -> Result<(), AudioPlayerError> {
match command {
Command::Play => {
- let playlist = self.playlist.read().expect("RwLock was not poisoned");
-
if !self.player.is_started() {
- if !playlist.is_empty() {
+ if !self.playlist.is_empty() {
self.player.stop_current()?;
}
} else {
@@ -357,35 +310,32 @@ impl MusicBot {
}
}
Command::Next => {
- let playlist = self.playlist.read().expect("RwLock was not poisoned");
- if !playlist.is_empty() {
- info!("Skipping to next track");
+ if !self.playlist.is_empty() {
+ info!(self.logger, "Skipping to next track");
self.player.stop_current()?;
} else {
- info!("Playlist empty, cannot skip");
+ info!(self.logger, "Playlist empty, cannot skip");
self.player.reset()?;
}
}
Command::Clear => {
- self.playlist
- .write()
- .expect("RwLock was not poisoned")
- .clear();
+ self.send_message(String::from("Cleared playlist")).await;
+ self.playlist.clear();
}
Command::Volume { volume } => {
self.player.change_volume(volume)?;
self.update_name(self.state()).await;
}
Command::Leave => {
- self.quit(String::from("Leaving"));
+ self.quit(String::from("Leaving"), true).await.unwrap();
}
}
Ok(())
}
- async fn update_name(&self, state: State) {
- let volume = (self.volume() * 100.0).round();
+ async fn update_name(&mut self, state: State) {
+ let volume = (self.volume().await * 100.0).round();
let name = match state {
State::EndOfStream => format!("🎵 {} ({}%)", self.name, volume),
_ => format!("🎵 {} - {} ({}%)", self.name, state, volume),
@@ -393,43 +343,39 @@ impl MusicBot {
self.set_nickname(name).await;
}
- async fn on_state(&self, state: State) -> Result<(), AudioPlayerError> {
- let current_state = *self.state.read().unwrap();
- if current_state != state {
- match state {
+ async fn on_state(&mut self, new_state: State) -> Result<(), AudioPlayerError> {
+ if self.state != new_state {
+ match new_state {
State::EndOfStream => {
- let next_track = self
- .playlist
- .write()
- .expect("RwLock was not poisoned")
- .pop();
+ self.player.reset()?;
+ let next_track = self.playlist.pop();
if let Some(request) = next_track {
- info!("Advancing playlist");
+ info!(self.logger, "Advancing playlist");
self.start_playing_audio(request).await;
} else {
- self.update_name(state).await;
+ self.update_name(new_state).await;
self.set_description(String::new()).await;
}
}
State::Stopped => {
- if current_state != State::EndOfStream {
- self.update_name(state).await;
+ if self.state != State::EndOfStream {
+ self.update_name(new_state).await;
self.set_description(String::new()).await;
}
}
- _ => self.update_name(state).await,
+ _ => self.update_name(new_state).await,
}
}
- if !(current_state == State::EndOfStream && state == State::Stopped) {
- *self.state.write().unwrap() = state;
+ if !(self.state == State::EndOfStream && new_state == State::Stopped) {
+ self.state = new_state;
}
Ok(())
}
- async fn on_message(&self, message: MusicBotMessage) -> Result<(), AudioPlayerError> {
+ async fn on_message(&mut self, message: MusicBotMessage) -> Result<(), AudioPlayerError> {
match message {
MusicBotMessage::TextMessage(message) => {
if MessageTarget::Channel == message.target {
@@ -446,9 +392,6 @@ impl MusicBot {
let old_channel = client.channel;
self.on_client_left_channel(old_channel).await;
}
- MusicBotMessage::ChannelAdded(id) => {
- self.subscribe(id).await;
- }
MusicBotMessage::StateChange(state) => {
self.on_state(state).await?;
}
@@ -458,60 +401,163 @@ impl MusicBot {
Ok(())
}
- async fn on_client_left_channel(&self, old_channel: ChannelId) {
- let my_channel = self.my_channel().await;
- if old_channel == my_channel && self.user_count(my_channel).await <= 1 {
- self.quit(String::from("Channel is empty"));
+ // FIXME logs an error if this music bot is the one leaving
+ async fn on_client_left_channel(&mut self, old_channel: ChannelId) {
+ let current_channel = match self.current_channel().await {
+ Some(c) => c,
+ None => {
+ return;
+ }
+ };
+ if old_channel == current_channel && self.user_count(current_channel).await <= 1 {
+ self.quit(String::from("Channel is empty"), true)
+ .await
+ .unwrap();
+ }
+ }
+
+ pub async fn quit(
+ &mut self,
+ reason: String,
+ inform_master: bool,
+ ) -> Result<(), tsclientlib::Error> {
+ // FIXME logs errors if the bot is playing something because it tries to
+ // change its name and description
+ self.player.reset().unwrap();
+
+ let ts = self.teamspeak.as_mut().unwrap();
+ ts.disconnect(&reason).await?;
+
+ if inform_master {
+ if let Some(master) = &self.master {
+ master
+ .send(BotDisonnected {
+ name: self.name.clone(),
+ identity: self.identity.clone(),
+ })
+ .await
+ .unwrap();
+ }
+ }
+
+ Ok(())
+ }
+}
+
+#[async_trait]
+impl Actor for MusicBot {
+ async fn started(&mut self, ctx: &mut Context<Self>) {
+ let addr = ctx.address().unwrap().downgrade();
+ self.player.register_bot(addr);
+ }
+}
+
+#[async_trait]
+impl Handler<Connect> for MusicBot {
+ async fn handle(
+ &mut self,
+ opt: Connect,
+ ctx: &mut Context<Self>,
+ ) -> Result<(), tsclientlib::Error> {
+ let addr = ctx.address().unwrap().downgrade();
+ self.teamspeak
+ .as_mut()
+ .unwrap()
+ .connect_for_bot(opt.0, addr)?;
+
+ let mut connection = self.teamspeak.as_ref().unwrap().clone();
+ let handle = tokio::runtime::Handle::current();
+ self.player
+ .setup_with_audio_callback(Some(Box::new(move |samples| {
+ handle.block_on(connection.send_audio_packet(samples));
+ })))
+ .unwrap();
+
+ Ok(())
+ }
+}
+
+pub struct GetName;
+impl Message for GetName {
+ type Result = String;
+}
+
+#[async_trait]
+impl Handler<GetName> for MusicBot {
+ async fn handle(&mut self, _: GetName, _: &mut Context<Self>) -> String {
+ self.name().to_owned()
+ }
+}
+
+pub struct GetBotData;
+impl Message for GetBotData {
+ type Result = crate::web_server::BotData;
+}
+
+#[async_trait]
+impl Handler<GetBotData> for MusicBot {
+ async fn handle(&mut self, _: GetBotData, _: &mut Context<Self>) -> crate::web_server::BotData {
+ crate::web_server::BotData {
+ name: self.name.clone(),
+ playlist: self.playlist.to_vec(),
+ currently_playing: self.player.currently_playing(),
+ position: self.player.position(),
+ state: self.state(),
+ volume: self.volume().await,
}
}
+}
- pub fn quit(&self, reason: String) {
- self.player.quit(reason);
+pub struct GetChannel;
+impl Message for GetChannel {
+ type Result = Option<ChannelId>;
+}
+
+#[async_trait]
+impl Handler<GetChannel> for MusicBot {
+ async fn handle(&mut self, _: GetChannel, _: &mut Context<Self>) -> Option<ChannelId> {
+ self.current_channel().await
}
}
-fn spawn_stdin_reader(tx: Arc<RwLock<UnboundedSender<MusicBotMessage>>>) {
- debug!("Spawning stdin reader thread");
- thread::Builder::new()
- .name(String::from("stdin reader"))
- .spawn(move || {
- let stdin = ::std::io::stdin();
- let lock = stdin.lock();
- for line in lock.lines() {
- let line = line.unwrap();
-
- let message = MusicBotMessage::TextMessage(Message {
- target: MessageTarget::Channel,
- invoker: Invoker {
- name: String::from("stdin"),
- id: ClientId(0),
- uid: None,
- },
- text: line,
- });
-
- let tx = tx.read().unwrap();
- tx.send(message).unwrap();
- }
- })
- .expect("Failed to spawn stdin reader thread");
+#[async_trait]
+impl Handler<Quit> for MusicBot {
+ async fn handle(&mut self, q: Quit, _: &mut Context<Self>) -> Result<(), tsclientlib::Error> {
+ self.quit(q.0, false).await
+ }
}
-fn spawn_gstreamer_thread(
- player: Arc<AudioPlayer>,
- tx: Arc<RwLock<UnboundedSender<MusicBotMessage>>>,
-) {
- thread::Builder::new()
- .name(String::from("gstreamer polling"))
- .spawn(move || loop {
- if player.poll() == PollResult::Quit {
- break;
- }
+#[async_trait]
+impl Handler<MusicBotMessage> for MusicBot {
+ async fn handle(
+ &mut self,
+ msg: MusicBotMessage,
+ _: &mut Context<Self>,
+ ) -> Result<(), AudioPlayerError> {
+ self.on_message(msg).await
+ }
+}
- tx.read()
- .unwrap()
- .send(MusicBotMessage::StateChange(State::EndOfStream))
- .unwrap();
- })
- .expect("Failed to spawn gstreamer thread");
+fn spawn_stdin_reader(addr: WeakAddress<MusicBot>) {
+ use tokio::io::AsyncBufReadExt;
+
+ tokio::task::spawn(async move {
+ let stdin = tokio::io::stdin();
+ let reader = tokio::io::BufReader::new(stdin);
+ let mut lines = reader.lines();
+
+ while let Some(line) = lines.next_line().await.unwrap() {
+ let message = MusicBotMessage::TextMessage(ChatMessage {
+ target: MessageTarget::Channel,
+ invoker: Invoker {
+ name: String::from("stdin"),
+ id: ClientId(0),
+ uid: None,
+ },
+ text: line,
+ });
+
+ addr.send(message).await.unwrap().unwrap();
+ }
+ });
}
diff --git a/src/log_bridge.rs b/src/log_bridge.rs
new file mode 100644
index 0000000..35bcb01
--- /dev/null
+++ b/src/log_bridge.rs
@@ -0,0 +1,101 @@
+// TODO Temporary file until we have a better logging setup for slog
+
+use slog::{Drain, KV};
+use std::fmt::{self, Arguments, Write};
+
+pub struct LogBridge<T>(pub T);
+
+impl<T: log::Log> Drain for LogBridge<T> {
+ type Ok = ();
+ type Err = slog::Error;
+
+ fn log(&self, record: &slog::Record, kvs: &slog::OwnedKVList) -> Result<(), Self::Err> {
+ let mut target = record.tag();
+ if target.is_empty() {
+ target = record.module();
+ }
+
+ let lazy = LazyLog::new(record, kvs);
+
+ self.0.log(
+ &log::Record::builder()
+ .args(format_args!("{}", lazy))
+ .level(level_to_log(record.level()))
+ .target(target)
+ .module_path_static(Some(record.module()))
+ .file_static(Some(record.file()))
+ .line(Some(record.line()))
+ .build(),
+ );
+
+ Ok(())
+ }
+
+ fn is_enabled(&self, level: slog::Level) -> bool {
+ let meta = log::Metadata::builder().level(level_to_log(level)).build();
+
+ self.0.enabled(&meta)
+ }
+}
+
+fn level_to_log(level: slog::Level) -> log::Level {
+ match level {
+ slog::Level::Critical | slog::Level::Error => log::Level::Error,
+ slog::Level::Warning => log::Level::Warn,
+ slog::Level::Info => log::Level::Info,
+ slog::Level::Debug => log::Level::Debug,
+ slog::Level::Trace => log::Level::Trace,
+ }
+}
+
+struct LazyLog<'a> {
+ record: &'a slog::Record<'a>,
+ kvs: &'a slog::OwnedKVList,
+}
+
+impl<'a> LazyLog<'a> {
+ fn new(record: &'a slog::Record, kvs: &'a slog::OwnedKVList) -> Self {
+ LazyLog { record, kvs }
+ }
+}
+
+impl<'a> fmt::Display for LazyLog<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.record.msg())?;
+
+ let mut ser = StringSerializer::new();
+
+ self.kvs
+ .serialize(self.record, &mut ser)
+ .map_err(|_| fmt::Error)?;
+ self.record
+ .kv()
+ .serialize(self.record, &mut ser)
+ .map_err(|_| fmt::Error)?;
+
+ write!(f, "{}", ser.finish())
+ }
+}
+
+struct StringSerializer {
+ inner: String,
+}
+
+impl StringSerializer {
+ fn new() -> Self {
+ StringSerializer {
+ inner: String::new(),
+ }
+ }
+
+ fn finish(self) -> String {
+ self.inner
+ }
+}
+
+impl slog::Serializer for StringSerializer {
+ fn emit_arguments(&mut self, key: slog::Key, value: &Arguments) -> slog::Result {
+ write!(self.inner, ", {}: {}", key, value)?;
+ Ok(())
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index f755db0..51b1e38 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,22 +2,25 @@ use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::thread;
-use std::time::Duration;
-use log::{debug, error, info};
+use slog::{debug, error, info, o, Drain, Logger};
use structopt::clap::AppSettings;
use structopt::StructOpt;
+#[cfg(unix)]
+use tokio::signal::unix::*;
use tsclientlib::Identity;
mod audio_player;
mod bot;
mod command;
+mod log_bridge;
mod playlist;
mod teamspeak;
mod web_server;
mod youtube_dl;
-use bot::{MasterArgs, MasterBot, MusicBot, MusicBotArgs};
+use bot::{MasterArgs, MasterBot, MusicBot, MusicBotArgs, Quit};
+use log_bridge::LogBridge;
#[derive(StructOpt, Debug)]
#[structopt(global_settings = &[AppSettings::ColoredHelp])]
@@ -51,6 +54,10 @@ pub struct Args {
help = "The channel the master bot should connect to"
)]
master_channel: Option<String>,
+ // 0. Print nothing
+ // 1. Print command string
+ // 2. Print packets
+ // 3. Print udp packets
#[structopt(
short = "v",
long = "verbose",
@@ -58,25 +65,44 @@ pub struct Args {
parse(from_occurrences)
)]
verbose: u8,
- // 0. Print nothing
- // 1. Print command string
- // 2. Print packets
- // 3. Print udp packets
}
#[tokio::main]
async fn main() {
- if let Err(e) = run().await {
- println!("Error: {}", e);
+ let root_logger = {
+ let config = log4rs::load_config_file("log4rs.yml", Default::default()).unwrap();
+ let drain = LogBridge(log4rs::Logger::new(config)).fuse();
+ // slog_async adds a channel because log4rs if not unwind safe
+ let drain = slog_async::Async::new(drain).build().fuse();
+
+ Logger::root(drain, o!())
+ };
+
+ let scope_guard = slog_scope::set_global_logger(root_logger.clone());
+ // On SIGTERM the logger resets for some reason which makes the bot panic
+ // if it tries to log anything
+ scope_guard.cancel_reset();
+
+ slog_stdlog::init().unwrap();
+
+ if let Err(e) = run(root_logger.clone()).await {
+ error!(root_logger, "{}", e);
}
}
-async fn run() -> Result<(), Box<dyn std::error::Error>> {
- log4rs::init_file("log4rs.yml", Default::default()).unwrap();
-
+async fn run(root_logger: Logger) -> Result<(), Box<dyn std::error::Error>> {
// Parse command line options
let args = Args::from_args();
+ // Set up signal handlers
+ let ctrl_c = tokio::task::spawn(tokio::signal::ctrl_c());
+ #[cfg(unix)]
+ let (sighup, sigterm, sigquit) = (
+ tokio::task::spawn(hangup()),
+ tokio::task::spawn(terminate()),
+ tokio::task::spawn(quit()),
+ );
+
let mut file = File::open(&args.config_path)?;
let mut toml = String::new();
file.read_to_string(&mut toml)?;
@@ -107,14 +133,14 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
if let Some(level) = args.wanted_level {
if let Some(id) = &mut config.id {
- info!("Upgrading master identity");
+ info!(root_logger, "Upgrading master identity");
id.upgrade_level(level).expect("can upgrade level");
}
if let Some(ids) = &mut config.ids {
let len = ids.len();
for (i, id) in ids.iter_mut().enumerate() {
- info!("Upgrading bot identity {}/{}", i + 1, len);
+ info!(root_logger, "Upgrading bot identity"; "current" => i + 1, "amount" => len);
id.upgrade_level(level).expect("can upgrade level");
}
}
@@ -127,53 +153,101 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
}
if config.id.is_none() || config.ids.is_none() {
- error!("Failed to find required identites, try running with `-g`");
+ error!(
+ root_logger,
+ "Failed to find required identites, try running with `-g`"
+ );
return Ok(());
}
+ let local = args.local;
let bot_args = config.merge(args);
- info!("Starting PokeBot!");
- debug!("Received CLI arguments: {:?}", std::env::args());
+ info!(root_logger, "Starting PokeBot!");
+ debug!(root_logger, "Received CLI arguments"; "args" => ?std::env::args());
- if bot_args.local {
+ if local {
let name = bot_args.names[0].clone();
- let id = bot_args.ids.expect("identies should exists")[0].clone();
-
- let disconnect_cb = Box::new(move |_, _, _| {});
+ let identity = bot_args.ids.expect("identies should exists")[0].clone();
let bot_args = MusicBotArgs {
name,
- name_index: 0,
- id_index: 0,
+ master: None,
local: true,
address: bot_args.address.clone(),
- id,
+ identity,
channel: String::from("local"),
verbose: bot_args.verbose,
- disconnect_cb,
+ logger: root_logger,
};
- MusicBot::new(bot_args).await.1.await;
+ MusicBot::spawn(bot_args).await;
+
+ ctrl_c.await??;
} else {
let domain = bot_args.domain.clone();
let bind_address = bot_args.bind_address.clone();
- let (bot, fut) = MasterBot::new(bot_args).await;
-
- thread::spawn(|| {
- let web_args = web_server::WebServerArgs {
- domain,
- bind_address,
- bot,
- };
- if let Err(e) = web_server::start(web_args) {
- error!("Error in web server: {}", e);
+ let bot_name = bot_args.master_name.clone();
+ let bot_logger = root_logger.new(o!("master" => bot_name.clone()));
+ let bot = MasterBot::spawn(bot_args, bot_logger).await;
+
+ let web_args = web_server::WebServerArgs {
+ domain,
+ bind_address,
+ bot: bot.downgrade(),
+ };
+ spawn_web_server(web_args, root_logger.new(o!("webserver" => bot_name)));
+
+ #[cfg(unix)]
+ tokio::select! {
+ res = ctrl_c => {
+ res??;
+ info!(root_logger, "Received signal, shutting down"; "signal" => "SIGINT");
}
- });
+ _ = sigterm => {
+ info!(root_logger, "Received signal, shutting down"; "signal" => "SIGTERM");
+ }
+ _ = sighup => {
+ info!(root_logger, "Received signal, shutting down"; "signal" => "SIGHUP");
+ }
+ _ = sigquit => {
+ info!(root_logger, "Received signal, shutting down"; "signal" => "SIGQUIT");
+ }
+ };
+
+ #[cfg(windows)]
+ ctrl_c.await??;
- fut.await;
- // Keep tokio running while the bot disconnects
- tokio::time::delay_for(Duration::from_secs(1)).await;
+ bot.send(Quit(String::from("Stopping")))
+ .await
+ .unwrap()
+ .unwrap();
}
Ok(())
}
+
+pub fn spawn_web_server(args: web_server::WebServerArgs, logger: Logger) {
+ thread::spawn(move || {
+ if let Err(e) = web_server::start(args, logger.clone()) {
+ error!(logger, "Error in web server"; "error" => %e);
+ }
+ });
+}
+
+#[cfg(unix)]
+pub async fn terminate() -> std::io::Result<()> {
+ signal(SignalKind::terminate())?.recv().await;
+ Ok(())
+}
+
+#[cfg(unix)]
+pub async fn hangup() -> std::io::Result<()> {
+ signal(SignalKind::hangup())?.recv().await;
+ Ok(())
+}
+
+#[cfg(unix)]
+pub async fn quit() -> std::io::Result<()> {
+ signal(SignalKind::quit())?.recv().await;
+ Ok(())
+}
diff --git a/src/playlist.rs b/src/playlist.rs
index 445f8a5..31fcfc0 100644
--- a/src/playlist.rs
+++ b/src/playlist.rs
@@ -1,29 +1,35 @@
use std::collections::VecDeque;
-use log::info;
+use slog::{info, Logger};
use crate::youtube_dl::AudioMetadata;
pub struct Playlist {
data: VecDeque<AudioMetadata>,
+ logger: Logger,
}
impl Playlist {
- pub fn new() -> Self {
+ pub fn new(logger: Logger) -> Self {
Self {
data: VecDeque::new(),
+ logger,
}
}
pub fn push(&mut self, data: AudioMetadata) {
- info!("Adding {:?} to playlist", &data.title);
+ info!(self.logger, "Adding to playlist"; "title" => &data.title);
self.data.push_front(data)
}
pub fn pop(&mut self) -> Option<AudioMetadata> {
let res = self.data.pop_back();
- info!("Popping {:?} from playlist", res.as_ref().map(|r| &r.title));
+ info!(
+ self.logger,
+ "Popping from playlist";
+ "title" => res.as_ref().map(|r| &r.title)
+ );
res
}
@@ -45,6 +51,6 @@ impl Playlist {
pub fn clear(&mut self) {
self.data.clear();
- info!("Cleared playlist")
+ info!(self.logger, "Cleared playlist")
}
}
diff --git a/src/teamspeak/mod.rs b/src/teamspeak/mod.rs
index beb3f44..7a68d38 100644
--- a/src/teamspeak/mod.rs
+++ b/src/teamspeak/mod.rs
@@ -1,7 +1,5 @@
-use std::sync::{Arc, RwLock};
-
use futures::stream::StreamExt;
-use tokio::sync::mpsc::UnboundedSender;
+use xtra::{Actor, Handler, WeakAddress};
use tsclientlib::data::exts::{M2BClientEditExt, M2BClientUpdateExt};
use tsclientlib::{
@@ -10,9 +8,9 @@ use tsclientlib::{
ChannelId, ClientId, ConnectOptions, DisconnectOptions, MessageTarget, OutCommandExt, Reason,
};
-use log::{debug, error};
+use slog::{debug, error, info, trace, Logger};
-use crate::bot::{Message, MusicBotMessage};
+use crate::bot::{ChatMessage, MusicBotMessage};
mod bbcode;
@@ -20,7 +18,8 @@ pub use bbcode::*;
#[derive(Clone)]
pub struct TeamSpeakConnection {
- handle: SyncConnectionHandle,
+ handle: Option<SyncConnectionHandle>,
+ logger: Logger,
}
fn get_message(event: &Event) -> Option<MusicBotMessage> {
@@ -31,7 +30,7 @@ fn get_message(event: &Event) -> Option<MusicBotMessage> {
target,
invoker: sender,
message: msg,
- } => Some(MusicBotMessage::TextMessage(Message {
+ } => Some(MusicBotMessage::TextMessage(ChatMessage {
target: *target,
invoker: sender.clone(),
text: msg.clone(),
@@ -86,50 +85,82 @@ fn get_message(event: &Event) -> Option<MusicBotMessage> {
}
impl TeamSpeakConnection {
- pub async fn new(
- tx: Arc<RwLock<UnboundedSender<MusicBotMessage>>>,
+ pub async fn new(logger: Logger) -> Result<TeamSpeakConnection, tsclientlib::Error> {
+ Ok(TeamSpeakConnection {
+ handle: None,
+ logger,
+ })
+ }
+
+ pub fn connect_for_bot<T: Actor + Handler<MusicBotMessage>>(
+ &mut self,
options: ConnectOptions,
- ) -> Result<TeamSpeakConnection, tsclientlib::Error> {
+ bot: WeakAddress<T>,
+ ) -> Result<(), tsclientlib::Error> {
+ info!(self.logger, "Starting TeamSpeak connection");
+
let conn = options.connect()?;
- let conn = SyncConnection::from(conn);
- let mut handle = conn.get_handle();
-
- tokio::spawn(conn.for_each(move |i| {
- let tx = tx.clone();
- async move {
- match i {
- Ok(SyncStreamItem::ConEvents(events)) => {
+ let mut conn = SyncConnection::from(conn);
+ let handle = conn.get_handle();
+ self.handle = Some(handle);
+
+ let ev_logger = self.logger.clone();
+ tokio::spawn(async move {
+ while let Some(item) = conn.next().await {
+ use SyncStreamItem::*;
+
+ match item {
+ Ok(ConEvents(events)) => {
for event in &events {
if let Some(msg) = get_message(event) {
- let tx = tx.read().expect("RwLock was not poisoned");
- // Ignore the result because the receiver might get dropped first.
- let _ = tx.send(msg);
+ tokio::spawn(bot.send(msg));
}
}
}
- Err(e) => error!("Error occured during event reading: {}", e),
- Ok(SyncStreamItem::DisconnectedTemporarily) => debug!("Temporary disconnect!"),
- _ => (),
+ Err(e) => error!(ev_logger, "Error occured during event reading: {}", e),
+ Ok(DisconnectedTemporarily(r)) => {
+ debug!(ev_logger, "Temporary disconnect"; "reason" => ?r)
+ }
+ Ok(Audio(_)) => {
+ trace!(ev_logger, "Audio received");
+ }
+ Ok(IdentityLevelIncreasing(_)) => {
+ trace!(ev_logger, "Identity level increasing");
+ }
+ Ok(IdentityLevelIncreased) => {
+ trace!(ev_logger, "Identity level increased");
+ }
+ Ok(NetworkStatsUpdated) => {
+ trace!(ev_logger, "Network stats updated");
+ }
}
}
- }));
-
- handle.wait_until_connected().await?;
-
- let mut chandle = handle.clone();
- chandle
- .with_connection(|mut conn| {
- conn.get_state()
- .expect("is connected")
- .server
- .set_subscribed(true)
- .send(&mut conn)
- .unwrap()
- })
- .await
- .unwrap();
-
- Ok(TeamSpeakConnection { handle })
+ });
+
+ let mut handle = self.handle.clone();
+ tokio::spawn(async move {
+ handle
+ .as_mut()
+ .expect("connect_for_bot was called")
+ .wait_until_connected()
+ .await
+ .unwrap();
+ handle
+ .as_mut()
+ .expect("connect_for_bot was called")
+ .with_connection(|mut conn| {
+ conn.get_state()
+ .expect("can get state")
+ .server
+ .set_subscribed(true)
+ .send(&mut conn)
+ })
+ .await
+ .and_then(|v| v)
+ .unwrap();
+ });
+
+ Ok(())
}
pub async fn send_audio_packet(&mut self, samples: &[u8]) {
@@ -140,22 +171,25 @@ impl TeamSpeakConnection {
data: samples,
});
- self.handle
- .with_connection(|conn| {
- if let Err(e) = conn
- .get_tsproto_client_mut()
+ if let Err(e) = self
+ .handle
+ .as_mut()
+ .expect("connect_for_bot was called")
+ .with_connection(move |conn| {
+ conn.get_tsproto_client_mut()
.expect("can get tsproto client")
.send_packet(packet)
- {
- error!("Failed to send voice packet: {}", e);
- }
})
.await
- .unwrap();
+ {
+ error!(self.logger, "Failed to send voice packet: {}", e);
+ }
}
pub async fn channel_of_user(&mut self, id: ClientId) -> Option<ChannelId> {
self.handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |conn| {
conn.get_state()
.expect("can get state")
@@ -164,30 +198,28 @@ impl TeamSpeakConnection {
.map(|c| c.channel)
})
.await
- .unwrap()
+ .map_err(|e| error!(self.logger, "Failed to get channel of user"; "error" => %e))
+ .ok()
+ .and_then(|v| v)
}
pub async fn channel_path_of_user(&mut self, id: ClientId) -> Option<String> {
self.handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |conn| {
let state = conn.get_state().expect("can get state");
let channel_id = state.clients.get(&id)?.channel;
- let mut channel = state
- .channels
- .get(&channel_id)
- .expect("can find user channel");
+ let mut channel = state.channels.get(&channel_id)?;
let mut names = vec![&channel.name[..]];
// Channel 0 is the root channel
while channel.parent != ChannelId(0) {
names.push("/");
- channel = state
- .channels
- .get(&channel.parent)
- .expect("can find user channel");
+ channel = state.channels.get(&channel.parent)?;
names.push(&channel.name);
}
@@ -199,11 +231,15 @@ impl TeamSpeakConnection {
Some(path)
})
.await
- .unwrap()
+ .map_err(|e| error!(self.logger, "Failed to get channel path of user"; "error" => %e))
+ .ok()
+ .and_then(|v| v)
}
- pub async fn my_channel(&mut self) -> ChannelId {
+ pub async fn current_channel(&mut self) -> Option<ChannelId> {
self.handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |conn| {
let state = conn.get_state().expect("can get state");
state
@@ -213,11 +249,14 @@ impl TeamSpeakConnection {
.channel
})
.await
- .unwrap()
+ .map_err(|e| error!(self.logger, "Failed to get channel"; "error" => %e))
+ .ok()
}
pub async fn my_id(&mut self) -> ClientId {
self.handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |conn| conn.get_state().expect("can get state").own_client)
.await
.unwrap()
@@ -225,6 +264,8 @@ impl TeamSpeakConnection {
pub async fn user_count(&mut self, channel: ChannelId) -> u32 {
self.handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |conn| {
let state = conn.get_state().expect("can get state");
let mut count = 0;
@@ -241,88 +282,90 @@ impl TeamSpeakConnection {
}
pub async fn set_nickname(&mut self, name: String) {
- self.handle
+ if let Err(e) = self
+ .handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |mut conn| {
conn.get_state()
.expect("can get state")
.client_update()
.set_name(&name)
.send(&mut conn)
- .map_err(|e| error!("Failed to set nickname: {}", e))
})
.await
- .unwrap()
- .unwrap();
+ .and_then(|v| v)
+ {
+ error!(self.logger, "Failed to set nickname: {}", e);
+ }
}
pub async fn set_description(&mut self, desc: String) {
- self.handle
+ if let Err(e) = self
+ .handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |mut conn| {
let state = conn.get_state().expect("can get state");
- let _ = state
+ state
.clients
.get(&state.own_client)
.expect("can get myself")
.edit()
.set_description(&desc)
.send(&mut conn)
- .map_err(|e| error!("Failed to change description: {}", e));
})
.await
- .unwrap()
+ .and_then(|v| v)
+ {
+ error!(self.logger, "Failed to change description: {}", e);
+ }
}
pub async fn send_message_to_channel(&mut self, text: String) {
- self.handle
+ if let Err(e) = self
+ .handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |mut conn| {
- let _ = conn
- .get_state()
+ conn.get_state()
.expect("can get state")
.send_message(MessageTarget::Channel, &text)
.send(&mut conn)
- .map_err(|e| error!("Failed to send message: {}", e));
})
.await
- .unwrap()
+ .and_then(|v| v)
+ {
+ error!(self.logger, "Failed to send message: {}", e);
+ }
}
pub async fn send_message_to_user(&mut self, client: ClientId, text: String) {
- self.handle
+ if let Err(e) = self
+ .handle
+ .as_mut()
+ .expect("connect_for_bot was called")
.with_connection(move |mut conn| {
- let _ = conn
- .get_state()
+ conn.get_state()
.expect("can get state")
.send_message(MessageTarget::Client(client), &text)
.send(&mut conn)
- .map_err(|e| error!("Failed to send message: {}", e));
})
.await
- .unwrap()
+ .and_then(|v| v)
+ {
+ error!(self.logger, "Failed to send message: {}", e);
+ }
}
- pub async fn subscribe(&mut self, id: ChannelId) {
- self.handle
- .with_connection(move |mut conn| {
- let channel = match conn.get_state().expect("can get state").channels.get(&id) {
- Some(c) => c,
- None => {
- error!("Failed to find channel to subscribe to");
- return;
- }
- };
-
- if let Err(e) = channel.set_subscribed(true).send(&mut conn) {
- error!("Failed to send subscribe packet: {}", e);
- }
- })
- .await
- .unwrap()
- }
-
- pub async fn disconnect(&mut self, reason: &str) {
+ pub async fn disconnect(&mut self, reason: &str) -> Result<(), tsclientlib::Error> {
let opt = DisconnectOptions::new()
.reason(Reason::Clientdisconnect)
.message(reason);
- self.handle.disconnect(opt).await.unwrap();
+ self.handle
+ .as_mut()
+ .expect("connect_for_bot was called")
+ .disconnect(opt)
+ .await
}
}
diff --git a/src/web_server.rs b/src/web_server.rs
index d731fae..8e5d446 100644
--- a/src/web_server.rs
+++ b/src/web_server.rs
@@ -1,38 +1,38 @@
-use std::sync::Arc;
use std::time::Duration;
-use actix::{Actor, Addr};
-use actix_web::{get, middleware::Logger, post, web, App, HttpServer, Responder};
-use askama::Template;
-use askama_actix::TemplateIntoResponse;
+use actix_slog::StructuredLogger;
+use actix_web::{get, post, web, App, HttpServer, Responder};
+use askama_actix::{Template, TemplateIntoResponse};
use serde::{Deserialize, Serialize};
+use slog::Logger;
+use xtra::WeakAddress;
use crate::bot::MasterBot;
use crate::youtube_dl::AudioMetadata;
mod api;
-mod bot_executor;
+mod bot_data;
mod default;
mod front_end_cookie;
mod tmtu;
-pub use bot_executor::*;
+pub use bot_data::*;
use front_end_cookie::FrontEnd;
pub struct WebServerArgs {
pub domain: String,
pub bind_address: String,
- pub bot: Arc<MasterBot>,
+ pub bot: WeakAddress<MasterBot>,
}
#[actix_rt::main]
-pub async fn start(args: WebServerArgs) -> std::io::Result<()> {
- let cbot = args.bot.clone();
- let bot_addr: Addr<BotExecutor> = BotExecutor(cbot.clone()).start();
+pub async fn start(args: WebServerArgs, logger: Logger) -> std::io::Result<()> {
+ let bot = args.bot;
+ let bind_address = args.bind_address;
HttpServer::new(move || {
App::new()
- .data(bot_addr.clone())
- .wrap(Logger::default())
+ .data(bot.clone())
+ .wrap(StructuredLogger::new(logger.clone()))
.service(index)
.service(get_bot)
.service(post_front_end)
@@ -44,12 +44,10 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> {
.service(web::scope("/docs").service(get_api_docs))
.service(actix_files::Files::new("/static", "web_server/static/"))
})
- .bind(args.bind_address)?
+ .bind(bind_address)?
.run()
.await?;
- args.bot.quit(String::from("Stopping"));
-
Ok(())
}
@@ -75,7 +73,7 @@ pub struct BotData {
}
#[get("/")]
-async fn index(bot: web::Data<Addr<BotExecutor>>, front: FrontEnd) -> impl Responder {
+async fn index(bot: web::Data<WeakAddress<MasterBot>>, front: FrontEnd) -> impl Responder {
match front {
FrontEnd::Default => default::index(bot).await,
FrontEnd::Tmtu => tmtu::index(bot).await,
@@ -84,7 +82,7 @@ async fn index(bot: web::Data<Addr<BotExecutor>>, front: FrontEnd) -> impl Respo
#[get("/bot/{name}")]
async fn get_bot(
- bot: web::Data<Addr<BotExecutor>>,
+ bot: web::Data<WeakAddress<MasterBot>>,
name: web::Path<String>,
front: FrontEnd,
) -> impl Responder {
diff --git a/src/web_server/api.rs b/src/web_server/api.rs
index 4deedad..b1d50c4 100644
--- a/src/web_server/api.rs
+++ b/src/web_server/api.rs
@@ -1,22 +1,23 @@
-use actix::Addr;
use actix_web::{get, web, HttpResponse, Responder, ResponseError};
use derive_more::Display;
use serde::Serialize;
+use xtra::WeakAddress;
-use crate::web_server::{BotDataListRequest, BotDataRequest, BotExecutor};
+use crate::web_server::{BotDataListRequest, BotDataRequest};
+use crate::MasterBot;
#[get("/bots")]
-pub async fn get_bot_list(bot: web::Data<Addr<BotExecutor>>) -> impl Responder {
- let bot_datas = match bot.send(BotDataListRequest).await.unwrap() {
- Ok(data) => data,
- Err(_) => Vec::with_capacity(0),
- };
+pub async fn get_bot_list(bot: web::Data<WeakAddress<MasterBot>>) -> impl Responder {
+ let bot_datas = bot.send(BotDataListRequest).await.unwrap();
web::Json(bot_datas)
}
#[get("/bots/{name}")]
-pub async fn get_bot(bot: web::Data<Addr<BotExecutor>>, name: web::Path<String>) -> impl Responder {
+pub async fn get_bot(
+ bot: web::Data<WeakAddress<MasterBot>>,
+ name: web::Path<String>,
+) -> impl Responder {
if let Some(bot_data) = bot.send(BotDataRequest(name.into_inner())).await.unwrap() {
Ok(web::Json(bot_data))
} else {
diff --git a/src/web_server/bot_data.rs b/src/web_server/bot_data.rs
new file mode 100644
index 0000000..af0c5e1
--- /dev/null
+++ b/src/web_server/bot_data.rs
@@ -0,0 +1,47 @@
+use async_trait::async_trait;
+
+use xtra::{Context, Handler, Message};
+
+use crate::bot::MasterBot;
+use crate::web_server::BotData;
+
+pub struct BotNameListRequest;
+
+impl Message for BotNameListRequest {
+ type Result = Vec<String>;
+}
+
+#[async_trait]
+impl Handler<BotNameListRequest> for MasterBot {
+ async fn handle(&mut self, _: BotNameListRequest, _: &mut Context<Self>) -> Vec<String> {
+ self.bot_names()
+ }
+}
+
+pub struct BotDataListRequest;
+
+impl Message for BotDataListRequest {
+ type Result = Vec<BotData>;
+}
+
+#[async_trait]
+impl Handler<BotDataListRequest> for MasterBot {
+ async fn handle(&mut self, _: BotDataListRequest, _: &mut Context<Self>) -> Vec<BotData> {
+ self.bot_datas().await
+ }
+}
+
+pub struct BotDataRequest(pub String);
+
+impl Message for BotDataRequest {
+ type Result = Option<BotData>;
+}
+
+#[async_trait]
+impl Handler<BotDataRequest> for MasterBot {
+ async fn handle(&mut self, r: BotDataRequest, _: &mut Context<Self>) -> Option<BotData> {
+ let name = r.0;
+
+ self.bot_data(name).await
+ }
+}
diff --git a/src/web_server/bot_executor.rs b/src/web_server/bot_executor.rs
deleted file mode 100644
index 0d3e7b7..0000000
--- a/src/web_server/bot_executor.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use std::sync::Arc;
-
-use actix::{Actor, Context, Handler, Message};
-
-use crate::bot::MasterBot;
-use crate::web_server::BotData;
-
-pub struct BotExecutor(pub Arc<MasterBot>);
-
-impl Actor for BotExecutor {
- type Context = Context<Self>;
-}
-
-pub struct BotNameListRequest;
-
-impl Message for BotNameListRequest {
- // A plain Vec does not work for some reason
- type Result = Result<Vec<String>, ()>;
-}
-
-impl Handler<BotNameListRequest> for BotExecutor {
- type Result = Result<Vec<String>, ()>;
-
- fn handle(&mut self, _: BotNameListRequest, _: &mut Self::Context) -> Self::Result {
- let bot = &self.0;
-
- Ok(bot.bot_names())
- }
-}
-
-pub struct BotDataListRequest;
-
-impl Message for BotDataListRequest {
- // A plain Vec does not work for some reason
- type Result = Result<Vec<BotData>, ()>;
-}
-
-impl Handler<BotDataListRequest> for BotExecutor {
- type Result = Result<Vec<BotData>, ()>;
-
- fn handle(&mut self, _: BotDataListRequest, _: &mut Self::Context) -> Self::Result {
- let bot = &self.0;
-
- Ok(bot.bot_datas())
- }
-}
-
-pub struct BotDataRequest(pub String);
-
-impl Message for BotDataRequest {
- type Result = Option<BotData>;
-}
-
-impl Handler<BotDataRequest> for BotExecutor {
- type Result = Option<BotData>;
-
- fn handle(&mut self, r: BotDataRequest, _: &mut Self::Context) -> Self::Result {
- let name = r.0;
- let bot = &self.0;
-
- bot.bot_data(name)
- }
-}
diff --git a/src/web_server/default.rs b/src/web_server/default.rs
index 542dade..6b15784 100644
--- a/src/web_server/default.rs
+++ b/src/web_server/default.rs
@@ -1,9 +1,9 @@
-use actix::Addr;
use actix_web::{http::header, web, Error, HttpResponse};
-use askama::Template;
-use askama_actix::TemplateIntoResponse;
+use askama_actix::{Template, TemplateIntoResponse};
+use xtra::WeakAddress;
-use crate::web_server::{filters, BotData, BotDataRequest, BotExecutor, BotNameListRequest};
+use crate::web_server::{filters, BotData, BotDataRequest, BotNameListRequest};
+use crate::MasterBot;
#[derive(Template)]
#[template(path = "index.htm")]
@@ -12,8 +12,8 @@ struct OverviewTemplate<'a> {
bot: Option<&'a BotData>,
}
-pub async fn index(bot: web::Data<Addr<BotExecutor>>) -> Result<HttpResponse, Error> {
- let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap();
+pub async fn index(bot: web::Data<WeakAddress<MasterBot>>) -> Result<HttpResponse, Error> {
+ let bot_names = bot.send(BotNameListRequest).await.unwrap();
OverviewTemplate {
bot_names: &bot_names,
@@ -23,10 +23,10 @@ pub async fn index(bot: web::Data<Addr<BotExecutor>>) -> Result<HttpResponse, Er
}
pub async fn get_bot(
- bot: web::Data<Addr<BotExecutor>>,
+ bot: web::Data<WeakAddress<MasterBot>>,
name: String,
) -> Result<HttpResponse, Error> {
- let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap();
+ let bot_names = bot.send(BotNameListRequest).await.unwrap();
if let Some(bot) = bot.send(BotDataRequest(name)).await.unwrap() {
OverviewTemplate {
@@ -35,7 +35,6 @@ pub async fn get_bot(
}
.into_response()
} else {
- // TODO to 404 or not to 404
Ok(HttpResponse::Found().header(header::LOCATION, "/").finish())
}
}
diff --git a/src/web_server/tmtu.rs b/src/web_server/tmtu.rs
index 33a14af..e9eea98 100644
--- a/src/web_server/tmtu.rs
+++ b/src/web_server/tmtu.rs
@@ -1,9 +1,9 @@
-use actix::Addr;
use actix_web::{http::header, web, Error, HttpResponse};
-use askama::Template;
-use askama_actix::TemplateIntoResponse;
+use askama_actix::{Template, TemplateIntoResponse};
+use xtra::WeakAddress;
-use crate::web_server::{filters, BotData, BotDataRequest, BotExecutor, BotNameListRequest};
+use crate::web_server::{filters, BotData, BotDataRequest, BotNameListRequest};
+use crate::MasterBot;
#[derive(Template)]
#[template(path = "tmtu/index.htm")]
@@ -12,8 +12,8 @@ struct TmtuTemplate {
bot: Option<BotData>,
}
-pub async fn index(bot: web::Data<Addr<BotExecutor>>) -> Result<HttpResponse, Error> {
- let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap();
+pub async fn index(bot: web::Data<WeakAddress<MasterBot>>) -> Result<HttpResponse, Error> {
+ let bot_names = bot.send(BotNameListRequest).await.unwrap();
TmtuTemplate {
bot_names,
@@ -23,10 +23,10 @@ pub async fn index(bot: web::Data<Addr<BotExecutor>>) -> Result<HttpResponse, Er
}
pub async fn get_bot(
- bot: web::Data<Addr<BotExecutor>>,
+ bot: web::Data<WeakAddress<MasterBot>>,
name: String,
) -> Result<HttpResponse, Error> {
- let bot_names = bot.send(BotNameListRequest).await.unwrap().unwrap();
+ let bot_names = bot.send(BotNameListRequest).await.unwrap();
if let Some(bot) = bot.send(BotDataRequest(name)).await.unwrap() {
TmtuTemplate {
@@ -35,7 +35,6 @@ pub async fn get_bot(
}
.into_response()
} else {
- // TODO to 404 or not to 404
Ok(HttpResponse::Found().header(header::LOCATION, "/").finish())
}
}
diff --git a/src/youtube_dl.rs b/src/youtube_dl.rs
index 496d0b4..ea10107 100644
--- a/src/youtube_dl.rs
+++ b/src/youtube_dl.rs
@@ -5,7 +5,7 @@ use tokio::process::Command;
use serde::{Deserialize, Serialize};
-use log::debug;
+use slog::{debug, Logger};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AudioMetadata {
@@ -28,13 +28,16 @@ where
Ok(dur.map(Duration::from_secs_f64))
}
-pub async fn get_audio_download_from_url(uri: String) -> Result<AudioMetadata, String> {
+pub async fn get_audio_download_from_url(
+ url: String,
+ logger: &Logger,
+) -> Result<AudioMetadata, String> {
//youtube-dl sometimes just fails, so we give it a second try
- let ytdl_output = match run_youtube_dl(&uri).await {
+ let ytdl_output = match run_youtube_dl(&url, &logger).await {
Ok(o) => o,
Err(e) => {
if e.contains("Unable to extract video data") {
- run_youtube_dl(&uri).await?
+ run_youtube_dl(&url, &logger).await?
} else {
return Err(e);
}
@@ -46,14 +49,14 @@ pub async fn get_audio_download_from_url(uri: String) -> Result<AudioMetadata, S
Ok(output)
}
-async fn run_youtube_dl(url: &str) -> Result<String, String> {
+async fn run_youtube_dl(url: &str, logger: &Logger) -> Result<String, String> {
let ytdl_args = ["--no-playlist", "-f", "bestaudio/best", "-j", &url];
let mut cmd = Command::new("youtube-dl");
cmd.args(&ytdl_args);
cmd.stdin(Stdio::null());
- debug!("yt-dl command: {:?}", cmd);
+ debug!(logger, "running yt-dl"; "command" => ?cmd);
let ytdl_output = cmd.output().await.unwrap();
if !ytdl_output.status.success() {