From 28ce6a32cd03d9c4961033da6bc2c78988294be2 Mon Sep 17 00:00:00 2001
From: _ <>
Date: Thu, 26 Nov 2020 23:30:33 +0000
Subject: [PATCH] :lipstick: Show servers in the server list even if they
aren't connected yet
---
Cargo.toml | 3 +-
handlebars/relay/relay_server_list.html | 2 +-
src/bin/ptth_relay.rs | 9 +-
src/lib.rs | 12 ++-
src/relay/config.rs | 108 ++++++++++++++++++++
src/relay/mod.rs | 125 +++++++++---------------
todo.md | 2 -
7 files changed, 171 insertions(+), 90 deletions(-)
create mode 100644 src/relay/config.rs
diff --git a/Cargo.toml b/Cargo.toml
index bfb8f78..e85a51d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,7 @@ futures = "0.3.7"
handlebars = "3.5.1"
http = "0.2.1"
hyper = "0.13.8"
+itertools = "0.9.0"
lazy_static = "1.4.0"
maplit = "1.0.2"
percent-encoding = "2.1.0"
@@ -30,7 +31,7 @@ reqwest = { version = "0.10.8", features = ["stream"] }
rmp-serde = "0.14.4"
serde = {version = "1.0.117", features = ["derive"]}
structopt = "0.3.20"
-# thiserror = "1.0.22"
+thiserror = "1.0.22"
tokio = { version = "0.2.22", features = ["full"] }
tracing = "0.1.21"
tracing-futures = "0.2.4"
diff --git a/handlebars/relay/relay_server_list.html b/handlebars/relay/relay_server_list.html
index 08d536f..3fd8a8a 100644
--- a/handlebars/relay/relay_server_list.html
+++ b/handlebars/relay/relay_server_list.html
@@ -46,7 +46,7 @@
{{#each servers}}
- {{this.name}} |
+ {{this.display_name}} |
{{this.last_seen}} |
{{/each}}
diff --git a/src/bin/ptth_relay.rs b/src/bin/ptth_relay.rs
index cd78d21..ecf8867 100644
--- a/src/bin/ptth_relay.rs
+++ b/src/bin/ptth_relay.rs
@@ -12,7 +12,10 @@ use tracing_subscriber::{
};
use ptth::relay;
-use ptth::relay::RelayState;
+use ptth::relay::{
+ Config,
+ RelayState,
+};
#[tokio::main]
async fn main () -> Result <(), Box > {
@@ -23,7 +26,7 @@ async fn main () -> Result <(), Box > {
;
let config_path = PathBuf::from ("config/ptth_relay.toml");
- let config_file = ptth::load_toml::load_public (&config_path);
+ let config = Config::from_file (&config_path).await?;
info! ("ptth_relay Git version: {:?}", ptth::git_version::GIT_VERSION);
@@ -31,7 +34,7 @@ async fn main () -> Result <(), Box > {
forced_shutdown.wrap_server (
relay::run_relay (
- Arc::new (RelayState::from (&config_file)),
+ Arc::new (RelayState::from (config)),
shutdown_rx,
Some (config_path)
)
diff --git a/src/lib.rs b/src/lib.rs
index bc89e25..9877e04 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -43,6 +43,7 @@ pub fn password_is_bad (mut password: String) -> bool {
#[cfg (test)]
mod tests {
use std::{
+ convert::TryFrom,
sync::{
Arc,
},
@@ -104,14 +105,17 @@ mod tests {
let api_key = "AnacondaHardcoverGrannyUnlatchLankinessMutate";
let tripcode = base64::encode (blake3::hash (api_key.as_bytes ()).as_bytes ());
debug! ("Relay is expecting tripcode {}", tripcode);
- let config_file = relay::ConfigFile {
+ let config_file = relay::config::file::Config {
port: None,
- server_tripcodes: hashmap! {
- server_name.into () => tripcode,
+ servers: hashmap! {
+ server_name.into () => relay::config::file::Server {
+ tripcode,
+ display_name: None,
+ },
},
};
- let relay_state = Arc::new (relay::RelayState::from (&config_file));
+ let relay_state = Arc::new (relay::RelayState::from (relay::config::Config::try_from (config_file).unwrap ()));
let relay_state_2 = relay_state.clone ();
let (stop_relay_tx, stop_relay_rx) = oneshot::channel ();
diff --git a/src/relay/config.rs b/src/relay/config.rs
new file mode 100644
index 0000000..d48b2df
--- /dev/null
+++ b/src/relay/config.rs
@@ -0,0 +1,108 @@
+use std::{
+ collections::*,
+ convert::{TryFrom, TryInto},
+ iter::FromIterator,
+ path::Path,
+};
+
+use serde::Deserialize;
+use thiserror::Error;
+
+#[derive (Error, Debug)]
+pub enum ConfigError {
+ #[error ("I/O error")]
+ Io (#[from] std::io::Error),
+
+ #[error ("UTF-8 decoding failed")]
+ Utf8 (#[from] std::string::FromUtf8Error),
+
+ #[error ("TOML parsing failed")]
+ Toml (#[from] toml::de::Error),
+
+ #[error ("base64 decoding failed")]
+ Base64Decode (#[from] base64::DecodeError),
+
+ #[error ("tripcode not 32 bytes after decoding")]
+ TripcodeBadLength,
+
+ #[error ("unknown config error")]
+ Unknown,
+}
+
+// Stuff we need to load from the config file and use to
+// set up the HTTP server
+
+pub mod file {
+ use super::*;
+
+ #[derive (Deserialize)]
+ pub struct Server {
+ pub tripcode: String,
+ pub display_name: Option ,
+ }
+
+ #[derive (Deserialize)]
+ pub struct Config {
+ pub port: Option ,
+ pub servers: HashMap ,
+ }
+}
+
+// Stuff we actually need at runtime
+
+pub struct Server {
+ pub tripcode: blake3::Hash,
+ pub display_name: Option ,
+}
+
+pub struct Config {
+ pub servers: HashMap ,
+}
+
+impl TryFrom for Server {
+ type Error = ConfigError;
+
+ fn try_from (f: file::Server) -> Result {
+ let bytes: Vec = base64::decode (f.tripcode)?;
+ let bytes: [u8; 32] = (&bytes [..]).try_into ().map_err (|_| ConfigError::TripcodeBadLength)?;
+
+ let tripcode = blake3::Hash::from (bytes);
+
+ Ok (Self {
+ tripcode,
+ display_name: f.display_name,
+ })
+ }
+}
+
+impl TryFrom for Config {
+ type Error = ConfigError;
+
+ fn try_from (f: file::Config) -> Result {
+ let servers = f.servers.into_iter ()
+ .map (|(k, v)| Ok::<_, ConfigError> ((k, v.try_into ()?)));
+
+ let servers = itertools::process_results (servers, |i| HashMap::from_iter (i))?;
+
+ Ok (Self {
+ servers,
+ })
+ }
+}
+
+impl Config {
+ pub async fn from_file (path: &Path) -> Result {
+ use tokio::prelude::*;
+
+ let mut f = tokio::fs::File::open (path).await?;
+
+ let mut buffer = vec! [0u8; 4096];
+ let bytes_read = f.read (&mut buffer).await?;
+ buffer.truncate (bytes_read);
+
+ let config_s = String::from_utf8 (buffer)?;
+ let new_config: file::Config = toml::from_str (&config_s)?;
+
+ Self::try_from (new_config)
+ }
+}
diff --git a/src/relay/mod.rs b/src/relay/mod.rs
index 9b9171e..11cfdd5 100644
--- a/src/relay/mod.rs
+++ b/src/relay/mod.rs
@@ -27,7 +27,6 @@ use hyper::{
};
use hyper::service::{make_service_fn, service_fn};
use serde::{
- Deserialize,
Serialize,
};
use tokio::{
@@ -51,6 +50,10 @@ use crate::{
prefix_match,
};
+pub mod config;
+
+pub use config::{Config, ConfigError};
+
/*
Here's what we need to handle:
@@ -92,48 +95,6 @@ enum RequestRendezvous {
type ResponseRendezvous = oneshot::Sender >;
-// Stuff we need to load from the config file and use to
-// set up the HTTP server
-
-#[derive (Default, Deserialize)]
-pub struct ConfigServers {
- pub tripcodes: HashMap ,
- pub display_names: HashMap ,
-}
-
-#[derive (Default, Deserialize)]
-pub struct ConfigFile {
- pub port: Option ,
- pub servers: ConfigServers,
-}
-
-// Stuff we actually need at runtime
-
-struct Config {
- server_tripcodes: HashMap ,
-}
-
-impl From <&ConfigFile> for Config {
- fn from (f: &ConfigFile) -> Self {
- let server_tripcodes = HashMap::from_iter (f.servers.tripcodes.iter ()
- .map (|(k, v)| {
- use std::convert::TryInto;
- let bytes: Vec = base64::decode (v).unwrap ();
- let bytes: [u8; 32] = (&bytes [..]).try_into ().unwrap ();
-
- let v = blake3::Hash::from (bytes);
-
- debug! ("Tripcode {} => {}", k, v.to_hex ());
-
- (k.clone (), v)
- }));
-
- Self {
- server_tripcodes,
- }
- }
-}
-
use chrono::{
DateTime,
SecondsFormat,
@@ -168,12 +129,12 @@ pub struct RelayState {
shutdown_watch_rx: watch::Receiver ,
}
-impl From <&ConfigFile> for RelayState {
- fn from (config_file: &ConfigFile) -> Self {
+impl From for RelayState {
+ fn from (config: Config) -> Self {
let (shutdown_watch_tx, shutdown_watch_rx) = watch::channel (false);
Self {
- config: Config::from (config_file).into (),
+ config: config.into (),
handlebars: Arc::new (load_templates (&PathBuf::new ()).unwrap ()),
request_rendezvous: Default::default (),
server_status: Default::default (),
@@ -222,12 +183,12 @@ async fn handle_http_listen (
let expected_tripcode = {
let config = state.config.read ().await;
- match config.server_tripcodes.get (&watcher_code) {
+ match config.servers.get (&watcher_code) {
None => {
error! ("Denied http_listen for non-existent server name {}", watcher_code);
return trip_error;
},
- Some (x) => (*x).clone (),
+ Some (x) => (*x).tripcode.clone (),
}
};
let actual_tripcode = blake3::hash (api_key);
@@ -395,7 +356,7 @@ async fn handle_http_request (
{
{
let config = state.config.read ().await;
- if ! config.server_tripcodes.contains_key (&watcher_code) {
+ if ! config.servers.contains_key (&watcher_code) {
return error_reply (StatusCode::NOT_FOUND, "Unknown server");
}
}
@@ -538,8 +499,8 @@ fn pretty_print_last_seen (
#[derive (Serialize)]
struct ServerEntry <'a> {
- path: String,
- name: String,
+ id: String,
+ display_name: String,
last_seen: Cow <'a, str>,
}
@@ -551,39 +512,56 @@ struct ServerListPage <'a> {
async fn handle_server_list_internal (state: &Arc )
-> ServerListPage <'static>
{
- let all_servers: Vec <_> = {
+ let display_names: HashMap = {
let guard = state.config.read ().await;
- (*guard).server_tripcodes.keys ().cloned ().collect ()
+
+ let servers = (*guard).servers.iter ()
+ .map (|(k, v)| {
+ let display_name = v.display_name
+ .clone ()
+ .unwrap_or_else (|| k.clone ());
+
+ (k.clone (), display_name)
+ });
+
+ HashMap::from_iter (servers)
};
- let server_status = {
+ let server_statuses = {
let guard = state.server_status.lock ().await;
(*guard).clone ()
};
let now = Utc::now ();
- let mut servers: Vec <_> = server_status.into_iter ()
- .map (|(name, server)| {
- let display_name = percent_encoding::percent_decode_str (&name).decode_utf8 ().unwrap_or_else (|_| "Server name isn't UTF-8".into ()).to_string ();
-
+ let mut servers: Vec <_> = display_names.into_iter ()
+ .map (|(id, display_name)| {
use LastSeen::*;
- let last_seen = match pretty_print_last_seen (now, server.last_seen) {
+ let status = match server_statuses.get (&id) {
+ None => return ServerEntry {
+ display_name,
+ id,
+ last_seen: "Never".into (),
+ },
+ Some (x) => x,
+ };
+
+ let last_seen = match pretty_print_last_seen (now, status.last_seen) {
Negative => "Error (negative time)".into (),
Connected => "Connected".into (),
Description (s) => s.into (),
};
ServerEntry {
- name: display_name,
- path: name,
- last_seen: last_seen,
+ display_name,
+ id,
+ last_seen,
}
})
.collect ();
- servers.sort_by (|a, b| a.name.cmp (&b.name));
+ servers.sort_by (|a, b| a.display_name.cmp (&b.display_name));
ServerListPage {
servers,
@@ -681,26 +659,15 @@ pub fn load_templates (asset_root: &Path)
async fn reload_config (
state: &Arc ,
config_reload_path: &Path
-) -> Option <()> {
- use tokio::prelude::*;
-
- let mut f = tokio::fs::File::open (config_reload_path).await.ok ()?;
-
- let mut buffer = vec! [0u8; 4096];
- let bytes_read = f.read (&mut buffer).await.ok ()?;
- buffer.truncate (bytes_read);
-
- let config_s = String::from_utf8 (buffer).ok ()?;
- let new_config: ConfigFile = toml::from_str (&config_s).ok ()?;
-
- let new_config = Config::from (&new_config);
+) -> Result <(), ConfigError> {
+ let new_config = Config::from_file (config_reload_path).await?;
let mut config = state.config.write ().await;
(*config) = new_config;
- debug! ("Loaded {} server tripcodes", config.server_tripcodes.len ());
+ debug! ("Loaded {} server configs", config.servers.len ());
- Some (())
+ Ok (())
}
pub async fn run_relay (
@@ -722,7 +689,7 @@ pub async fn run_relay (
loop {
reload_interval.tick ().await;
- reload_config (&state_2, &config_reload_path).await;
+ reload_config (&state_2, &config_reload_path).await.ok ();
}
});
}
diff --git a/todo.md b/todo.md
index d2f7013..02488b8 100644
--- a/todo.md
+++ b/todo.md
@@ -1,5 +1,3 @@
-- Server list should include offline or never-launched servers
-- Allow relay to rename servers
- Estimate bandwidth per server?
- "Preview as" feature for Markdown (It's not threaded through the relay yet)
- Remote `tail -f` (_Complicated_) (Maybe use chunked encoding or something?)