From 2053c9cbb3614c799407132eaafca0d317b47bae Mon Sep 17 00:00:00 2001 From: Shagnor Date: Sat, 28 Jan 2023 23:00:53 +1100 Subject: [PATCH] Add who command. --- Cargo.lock | 7 +++++ blastmud_game/Cargo.toml | 1 + blastmud_game/src/db.rs | 30 ++++++++++++++++++ .../src/message_handler/user_commands.rs | 4 +++ .../src/message_handler/user_commands/who.rs | 31 +++++++++++++++++++ blastmud_game/src/models/session.rs | 4 ++- 6 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 blastmud_game/src/message_handler/user_commands/who.rs diff --git a/Cargo.lock b/Cargo.lock index 97e13968..fe33dfff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,7 @@ dependencies = [ "deadpool", "deadpool-postgres", "futures", + "humantime", "itertools", "log", "nix", @@ -683,6 +684,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.23" diff --git a/blastmud_game/Cargo.toml b/blastmud_game/Cargo.toml index ee8249ac..f796cb57 100644 --- a/blastmud_game/Cargo.toml +++ b/blastmud_game/Cargo.toml @@ -37,3 +37,4 @@ once_cell = "1.16.0" rand = "0.8.5" async-recursion = "1.0.0" rand_distr = "0.4.3" +humantime = "2.1.0" diff --git a/blastmud_game/src/db.rs b/blastmud_game/src/db.rs index 60353fc8..10c6b1de 100644 --- a/blastmud_game/src/db.rs +++ b/blastmud_game/src/db.rs @@ -18,8 +18,10 @@ use crate::models::{ use tokio_postgres::types::ToSql; use std::collections::BTreeSet; use std::sync::Arc; +use serde::{Serialize, Deserialize}; use serde_json::{self, Value}; use futures::FutureExt; +use chrono::{DateTime, Utc}; #[derive(Clone, Debug)] pub struct DBPool { @@ -53,6 +55,12 @@ impl From for SendqueueItem { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OnlineInfo { + pub username: String, + pub time: Option> +} + impl DBPool { pub async fn record_listener_ping(self: &DBPool, listener: Uuid) -> DResult<()> { self.get_conn().await?.execute( @@ -176,6 +184,14 @@ impl DBPool { Ok(()) } + pub async fn bump_session_time(&self, session: &ListenerSession) -> DResult<()> { + self.get_conn().await?.query( + "UPDATE sessions SET details=JSONB_SET(details, '{last_active}', to_json(NOW())::jsonb) \ + WHERE session = $1", &[&session.session] + ).await?; + Ok(()) + } + pub async fn get_conn(self: &DBPool) -> DResult { let conn = self.pool.get().await?; @@ -591,6 +607,20 @@ impl DBTrans { pub async fn alloc_item_code(&self) -> DResult { Ok(self.pg_trans()?.query_one("SELECT NEXTVAL('item_seq')", &[]).await?.get(0)) } + + pub async fn get_online_info(&self) ->DResult> { + Ok(self.pg_trans()?.query( + "SELECT jsonb_build_object(\ + 'username', u.details->>'username',\ + 'time', s.details->>'last_active'\ + ) FROM sessions s \ + JOIN users u ON u.current_session = s.session \ + ORDER BY s.details->>'last_active' DESC", &[] + ).await? + .into_iter() + .filter_map(|row| serde_json::from_value(row.get(0)).ok()) + .collect()) + } pub async fn commit(mut self: Self) -> DResult<()> { let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None)); diff --git a/blastmud_game/src/message_handler/user_commands.rs b/blastmud_game/src/message_handler/user_commands.rs index c0d5fe54..a3b66460 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -24,6 +24,7 @@ mod quit; mod register; pub mod say; mod whisper; +mod who; pub struct VerbContext<'l> { pub session: &'l ListenerSession, @@ -114,6 +115,8 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! { "-" => whisper::VERB, "whisper" => whisper::VERB, "tell" => whisper::VERB, + + "who" => who::VERB, }; fn resolve_handler(ctx: &VerbContext, cmd: &str) -> Option<&'static UserVerbRef> { @@ -173,6 +176,7 @@ pub async fn handle(session: &ListenerSession, msg: &str, pool: &DBPool) -> DRes } } } + pool.bump_session_time(&session).await?; Ok(()) } diff --git a/blastmud_game/src/message_handler/user_commands/who.rs b/blastmud_game/src/message_handler/user_commands/who.rs new file mode 100644 index 00000000..8c2c2e26 --- /dev/null +++ b/blastmud_game/src/message_handler/user_commands/who.rs @@ -0,0 +1,31 @@ +use super::{VerbContext, UserVerb, UserVerbRef, UResult}; +use async_trait::async_trait; +use ansi::{ignore_special_characters, ansi}; +use chrono::Utc; + +pub struct Verb; +#[async_trait] +impl UserVerb for Verb { + async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> { + let mut msg = String::new(); + msg.push_str(&format!(ansi!("| {:20} | {:15} |\n"), + ansi!("Username"), + ansi!("Idle"))); + for online in ctx.trans.get_online_info().await? { + if let Some(online_time) = online.time { + let diff = + humantime::format_duration( + std::time::Duration::from_secs( + (Utc::now() - online_time).num_seconds() as u64)); + msg.push_str(&format!( + "| {:20} | {:15} |\n", &ignore_special_characters(&online.username), + &format!("{}", &diff))); + } + } + msg.push_str("\n"); + ctx.trans.queue_for_session(ctx.session, Some(&msg)).await?; + Ok(()) + } +} +static VERB_INT: Verb = Verb; +pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef; diff --git a/blastmud_game/src/models/session.rs b/blastmud_game/src/models/session.rs index fc344884..12b5806d 100644 --- a/blastmud_game/src/models/session.rs +++ b/blastmud_game/src/models/session.rs @@ -1,6 +1,7 @@ use serde::{Serialize, Deserialize}; use std::collections::VecDeque; use crate::regular_tasks::queued_command::QueueCommand; +use chrono::{DateTime, Utc}; #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(default)] @@ -8,6 +9,7 @@ pub struct Session { pub source: String, pub less_explicit_mode: bool, pub queue: VecDeque, + pub last_active: Option>, // Reminder: Consider backwards compatibility when updating this. New fields should generally // be an Option, or you should ensure the default value is sensible, or things will // crash out for existing sessions. @@ -26,6 +28,6 @@ impl Session { impl Default for Session { fn default() -> Self { Session { source: "unknown".to_owned(), less_explicit_mode: false, - queue: VecDeque::new() } + queue: VecDeque::new(), last_active: None } } }