Add who command.

This commit is contained in:
Condorra 2023-01-28 23:00:53 +11:00
parent 29b02407fa
commit 2053c9cbb3
6 changed files with 76 additions and 1 deletions

7
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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<Row> for SendqueueItem {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OnlineInfo {
pub username: String,
pub time: Option<DateTime<Utc>>
}
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<Object> {
let conn = self.pool.get().await?;
@ -591,6 +607,20 @@ impl DBTrans {
pub async fn alloc_item_code(&self) -> DResult<i64> {
Ok(self.pg_trans()?.query_one("SELECT NEXTVAL('item_seq')", &[]).await?.get(0))
}
pub async fn get_online_info(&self) ->DResult<Vec<OnlineInfo>> {
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));

View File

@ -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(())
}

View File

@ -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!("<bold><bgblue><white>| {:20} | {:15} |<reset>\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;

View File

@ -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<QueueCommand>,
pub last_active: Option<DateTime<Utc>>,
// 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 }
}
}