forked from lavina/lavina
1
0
Fork 0

dialogs impl for irc and xmpp

This commit is contained in:
Nikita Vilunov 2024-04-22 21:12:51 +02:00
parent 772d38f7bb
commit c3a709424e
8 changed files with 120 additions and 17 deletions

5
Cargo.lock generated
View File

@ -1775,6 +1775,7 @@ dependencies = [
"atoi", "atoi",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono",
"crc", "crc",
"crossbeam-queue", "crossbeam-queue",
"either", "either",
@ -1833,6 +1834,7 @@ dependencies = [
"sha2", "sha2",
"sqlx-core", "sqlx-core",
"sqlx-mysql", "sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite", "sqlx-sqlite",
"syn 1.0.109", "syn 1.0.109",
"tempfile", "tempfile",
@ -1850,6 +1852,7 @@ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono",
"crc", "crc",
"digest", "digest",
"dotenvy", "dotenvy",
@ -1891,6 +1894,7 @@ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bitflags 2.5.0", "bitflags 2.5.0",
"byteorder", "byteorder",
"chrono",
"crc", "crc",
"dotenvy", "dotenvy",
"etcetera", "etcetera",
@ -1926,6 +1930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
dependencies = [ dependencies = [
"atoi", "atoi",
"chrono",
"flume", "flume",
"futures-channel", "futures-channel",
"futures-core", "futures-core",

View File

@ -5,7 +5,7 @@ version.workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
sqlx = { version = "0.7.4", features = ["sqlite", "migrate"] } sqlx = { version = "0.7.4", features = ["sqlite", "migrate", "chrono"] }
serde.workspace = true serde.workspace = true
tokio.workspace = true tokio.workspace = true
tracing.workspace = true tracing.workspace = true

View File

@ -6,6 +6,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use chrono::{DateTime, Utc};
use tokio::sync::RwLock as AsyncRwLock; use tokio::sync::RwLock as AsyncRwLock;
use crate::player::{PlayerId, PlayerRegistry, Updates}; use crate::player::{PlayerId, PlayerRegistry, Updates};
@ -52,7 +53,13 @@ struct DialogRegistryInner {
pub struct DialogRegistry(Arc<AsyncRwLock<DialogRegistryInner>>); pub struct DialogRegistry(Arc<AsyncRwLock<DialogRegistryInner>>);
impl DialogRegistry { impl DialogRegistry {
pub async fn send_message(&self, from: PlayerId, to: PlayerId, body: Str) -> Result<()> { pub async fn send_message(
&self,
from: PlayerId,
to: PlayerId,
body: Str,
created_at: &DateTime<Utc>,
) -> Result<()> {
let guard = self.0.read().await; let guard = self.0.read().await;
let id = DialogId::new(from.clone(), to.clone()); let id = DialogId::new(from.clone(), to.clone());
let dialog = guard.dialogs.get(&id); let dialog = guard.dialogs.get(&id);
@ -85,13 +92,13 @@ impl DialogRegistry {
Some(t) => t, Some(t) => t,
None => { None => {
tracing::info!("Dialog {id:?} does not exist, creating a new one in storage"); tracing::info!("Dialog {id:?} does not exist, creating a new one in storage");
guard.storage.initialize_dialog(p1.as_inner(), p2.as_inner()).await? guard.storage.initialize_dialog(p1.as_inner(), p2.as_inner(), created_at).await?
} }
}; };
tracing::info!("Dialog {id:?} loaded"); tracing::info!("Dialog {id:?} loaded");
guard.storage.increment_dialog_message_count(stored_dialog.storage_id).await?; guard.storage.increment_dialog_message_count(stored_dialog.id).await?;
let dialog = Dialog { let dialog = Dialog {
storage_id: stored_dialog.storage_id, storage_id: stored_dialog.id,
player_storage_id_1: stored_dialog.participant_1, player_storage_id_1: stored_dialog.participant_1,
player_storage_id_2: stored_dialog.participant_2, player_storage_id_2: stored_dialog.participant_2,
message_count: stored_dialog.message_count + 1, message_count: stored_dialog.message_count + 1,

View File

@ -105,6 +105,17 @@ impl PlayerConnection {
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await; self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?) Ok(deferred.await?)
} }
pub async fn send_dialog_message(&self, recipient: PlayerId, body: Str) -> Result<()> {
let (promise, deferred) = oneshot();
let cmd = ClientCommand::SendDialogMessage {
recipient,
body,
promise,
};
self.player_handle.send(ActorCommand::ClientCommand(cmd, self.connection_id.clone())).await;
Ok(deferred.await?)
}
} }
/// Handle to a player actor. /// Handle to a player actor.
@ -256,7 +267,13 @@ impl PlayerRegistry {
if let Some((handle, _)) = inner.players.get(id) { if let Some((handle, _)) = inner.players.get(id) {
handle.clone() handle.clone()
} else { } else {
let (handle, fiber) = Player::launch(id.clone(), inner.room_registry.clone(), inner.dialogs.clone(), inner.storage.clone()).await; let (handle, fiber) = Player::launch(
id.clone(),
inner.room_registry.clone(),
inner.dialogs.clone(),
inner.storage.clone(),
)
.await;
inner.players.insert(id.clone(), (handle.clone(), fiber)); inner.players.insert(id.clone(), (handle.clone(), fiber));
inner.metric_active_players.inc(); inner.metric_active_players.inc();
handle handle
@ -305,7 +322,12 @@ struct Player {
storage: Storage, storage: Storage,
} }
impl Player { impl Player {
async fn launch(player_id: PlayerId, rooms: RoomRegistry, dialogs: DialogRegistry, storage: Storage) -> (PlayerHandle, JoinHandle<Player>) { async fn launch(
player_id: PlayerId,
rooms: RoomRegistry,
dialogs: DialogRegistry,
storage: Storage,
) -> (PlayerHandle, JoinHandle<Player>) {
let (tx, rx) = channel(32); let (tx, rx) = channel(32);
let handle = PlayerHandle { tx }; let handle = PlayerHandle { tx };
let handle_clone = handle.clone(); let handle_clone = handle.clone();
@ -499,7 +521,7 @@ impl Player {
async fn send_dialog_message(&self, connection_id: ConnectionId, recipient: PlayerId, body: Str) { async fn send_dialog_message(&self, connection_id: ConnectionId, recipient: PlayerId, body: Str) {
let created_at = chrono::Utc::now(); let created_at = chrono::Utc::now();
self.dialogs.send_message(self.player_id.clone(), recipient.clone(), body.clone()).await.unwrap(); self.dialogs.send_message(self.player_id.clone(), recipient.clone(), body.clone(), &created_at).await.unwrap();
let update = Updates::NewDialogMessage { let update = Updates::NewDialogMessage {
sender: self.player_id.clone(), sender: self.player_id.clone(),
receiver: recipient.clone(), receiver: recipient.clone(),

View File

@ -1,4 +1,5 @@
use anyhow::Result; use anyhow::Result;
use chrono::{DateTime, Utc};
use sqlx::FromRow; use sqlx::FromRow;
use crate::repo::Storage; use crate::repo::Storage;
@ -7,8 +8,8 @@ impl Storage {
pub async fn retrieve_dialog(&self, participant_1: &str, participant_2: &str) -> Result<Option<StoredDialog>> { pub async fn retrieve_dialog(&self, participant_1: &str, participant_2: &str) -> Result<Option<StoredDialog>> {
let mut executor = self.conn.lock().await; let mut executor = self.conn.lock().await;
let res = sqlx::query_as( let res = sqlx::query_as(
"select r.participant_1, r.participant_2, r.message_count "select r.id, r.participant_1, r.participant_2, r.message_count
from rooms r join users u1 on r.participant_1 = u1.id join users u2 on r.participant_2 = u2.id from dialogs r join users u1 on r.participant_1 = u1.id join users u2 on r.participant_2 = u2.id
where u1.name = ? and u2.name = ?;", where u1.name = ? and u2.name = ?;",
) )
.bind(participant_1) .bind(participant_1)
@ -23,7 +24,7 @@ impl Storage {
let mut executor = self.conn.lock().await; let mut executor = self.conn.lock().await;
sqlx::query( sqlx::query(
"update rooms set message_count = message_count + 1 "update rooms set message_count = message_count + 1
where storage_id = ?;", where id = ?;",
) )
.bind(storage_id) .bind(storage_id)
.execute(&mut *executor) .execute(&mut *executor)
@ -32,19 +33,25 @@ impl Storage {
Ok(()) Ok(())
} }
pub async fn initialize_dialog(&self, participant_1: &str, participant_2: &str) -> Result<StoredDialog> { pub async fn initialize_dialog(
&self,
participant_1: &str,
participant_2: &str,
created_at: &DateTime<Utc>,
) -> Result<StoredDialog> {
let mut executor = self.conn.lock().await; let mut executor = self.conn.lock().await;
let res: StoredDialog = sqlx::query_as( let res: StoredDialog = sqlx::query_as(
"insert into dialogs(participant_1, participant_2, created_at) "insert into dialogs(participant_1, participant_2, created_at)
values ( values (
select id from users where name = ?, (select id from users where name = ?),
select id from users where name = ?, (select id from users where name = ?),
now() ?
) )
returning id, participant_1, participant_2, message_count;", returning id, participant_1, participant_2, message_count;",
) )
.bind(participant_1) .bind(participant_1)
.bind(participant_2) .bind(participant_2)
.bind(&created_at)
.fetch_one(&mut *executor) .fetch_one(&mut *executor)
.await?; .await?;
@ -54,7 +61,7 @@ impl Storage {
#[derive(FromRow)] #[derive(FromRow)]
pub struct StoredDialog { pub struct StoredDialog {
pub storage_id: u32, pub id: u32,
pub participant_1: u32, pub participant_1: u32,
pub participant_2: u32, pub participant_2: u32,
pub message_count: u32, pub message_count: u32,

View File

@ -653,6 +653,32 @@ async fn handle_update(
.await?; .await?;
writer.flush().await? writer.flush().await?
} }
Updates::NewDialogMessage {
sender,
receiver,
body,
created_at,
} => {
let mut tags = vec![];
if user.enabled_capabilities.contains(Capabilities::ServerTime) {
let tag = Tag {
key: "time".into(),
value: Some(created_at.to_rfc3339_opts(SecondsFormat::Millis, true).into()),
};
tags.push(tag);
}
ServerMessage {
tags,
sender: Some(sender.as_inner().clone()),
body: ServerMessageBody::PrivateMessage {
target: Recipient::Nick(receiver.as_inner().clone()),
body: body.clone(),
},
}
.write_async(writer)
.await?;
writer.flush().await?
}
} }
Ok(()) Ok(())
} }
@ -699,6 +725,10 @@ async fn handle_incoming_message(
let room_id = RoomId::from(chan)?; let room_id = RoomId::from(chan)?;
user_handle.send_message(room_id, body).await?; user_handle.send_message(room_id, body).await?;
} }
Recipient::Nick(nick) => {
let receiver = PlayerId::from(nick)?;
user_handle.send_dialog_message(receiver, body).await?;
}
_ => log::warn!("Unsupported target type"), _ => log::warn!("Unsupported target type"),
}, },
ClientMessage::Topic { chan, topic } => { ClientMessage::Topic { chan, topic } => {

View File

@ -1,5 +1,6 @@
//! Handling of all client2server message stanzas //! Handling of all client2server message stanzas
use lavina_core::player::PlayerId;
use quick_xml::events::Event; use quick_xml::events::Event;
use lavina_core::prelude::*; use lavina_core::prelude::*;
@ -40,6 +41,9 @@ impl<'a> XmppConnection<'a> {
} }
.serialize(output); .serialize(output);
Ok(()) Ok(())
} else if server.0.as_ref() == &*self.hostname && m.r#type == MessageType::Chat {
self.user_handle.send_dialog_message(PlayerId::from(name.0.clone())?, m.body.clone()).await?;
Ok(())
} else { } else {
todo!() todo!()
} }

View File

@ -39,6 +39,34 @@ impl<'a> XmppConnection<'a> {
} }
.serialize(output); .serialize(output);
} }
Updates::NewDialogMessage {
sender,
receiver,
body,
created_at: _,
} => {
if receiver == self.user.player_id {
Message::<()> {
to: Some(Jid {
name: Some(self.user.xmpp_name.clone()),
server: Server(self.hostname.clone()),
resource: Some(self.user.xmpp_resource.clone()),
}),
from: Some(Jid {
name: Some(Name(sender.as_inner().clone())),
server: Server(self.hostname.clone()),
resource: Some(Resource(sender.into_inner())),
}),
id: None,
r#type: MessageType::Chat,
lang: None,
subject: None,
body: body.into(),
custom: vec![],
}
.serialize(output);
}
}
_ => {} _ => {}
} }
Ok(()) Ok(())