From 24793891e052cfe66bde1b1fd65d75584c7c0949 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20K=C3=B6sters?= <timo@koesters.xyz>
Date: Fri, 14 May 2021 11:03:18 +0200
Subject: [PATCH] feat: implement GET /presence

---
 Cargo.toml                    |  4 +--
 src/client_server/presence.rs | 51 ++++++++++++++++++++++++++++++++---
 src/database/rooms/edus.rs    | 41 ++++++++++++++++++++++++++++
 src/main.rs                   |  1 +
 4 files changed, 92 insertions(+), 5 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 78496e4f..950924a6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -108,5 +108,5 @@ maintainer-scripts = "debian/"
 systemd-units = { unit-name = "matrix-conduit" }
 
 # For flamegraphs:
-#[profile.release]
-#debug = true
+[profile.release]
+debug = true
diff --git a/src/client_server/presence.rs b/src/client_server/presence.rs
index 175853f5..9f4f7a39 100644
--- a/src/client_server/presence.rs
+++ b/src/client_server/presence.rs
@@ -1,10 +1,10 @@
 use super::State;
 use crate::{utils, ConduitResult, Database, Ruma};
-use ruma::api::client::r0::presence::set_presence;
-use std::convert::TryInto;
+use ruma::api::client::r0::presence::{get_presence, set_presence};
+use std::{convert::TryInto, time::Duration};
 
 #[cfg(feature = "conduit_bin")]
-use rocket::put;
+use rocket::{get, put};
 
 #[cfg_attr(
     feature = "conduit_bin",
@@ -46,3 +46,48 @@ pub async fn set_presence_route(
 
     Ok(set_presence::Response.into())
 }
+
+#[cfg_attr(
+    feature = "conduit_bin",
+    get("/_matrix/client/r0/presence/<_>/status", data = "<body>")
+)]
+#[tracing::instrument(skip(db, body))]
+pub async fn get_presence_route(
+    db: State<'_, Database>,
+    body: Ruma<get_presence::Request<'_>>,
+) -> ConduitResult<get_presence::Response> {
+    let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+
+    let mut presence_event = None;
+
+    for room_id in db
+        .rooms
+        .get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])
+    {
+        let room_id = room_id?;
+
+        if let Some(presence) = db
+            .rooms
+            .edus
+            .get_last_presence_event(&sender_user, &room_id)?
+        {
+            presence_event = Some(presence);
+        }
+    }
+
+    if let Some(presence) = presence_event {
+        Ok(get_presence::Response {
+            // TODO: Should ruma just use the presenceeventcontent type here?
+            status_msg: presence.content.status_msg,
+            currently_active: presence.content.currently_active,
+            last_active_ago: presence
+                .content
+                .last_active_ago
+                .map(|millis| Duration::from_millis(millis.into())),
+            presence: presence.content.presence,
+        }
+        .into())
+    } else {
+        todo!();
+    }
+}
diff --git a/src/database/rooms/edus.rs b/src/database/rooms/edus.rs
index 56000e0c..3bf2e067 100644
--- a/src/database/rooms/edus.rs
+++ b/src/database/rooms/edus.rs
@@ -367,6 +367,47 @@ impl RoomEdus {
             .transpose()
     }
 
+    pub fn get_last_presence_event(
+        &self,
+        user_id: &UserId,
+        room_id: &RoomId,
+    ) -> Result<Option<PresenceEvent>> {
+        let last_update = match self.last_presence_update(user_id)? {
+            Some(last) => last,
+            None => return Ok(None),
+        };
+
+        let mut presence_id = room_id.as_bytes().to_vec();
+        presence_id.push(0xff);
+        presence_id.extend_from_slice(&last_update.to_be_bytes());
+        presence_id.push(0xff);
+        presence_id.extend_from_slice(&user_id.as_bytes());
+
+        self.presenceid_presence
+            .get(presence_id)?
+            .map(|value| {
+                let mut presence = serde_json::from_slice::<PresenceEvent>(&value)
+                    .map_err(|_| Error::bad_database("Invalid presence event in db."))?;
+                let current_timestamp: UInt = utils::millis_since_unix_epoch()
+                    .try_into()
+                    .expect("time is valid");
+
+                if presence.content.presence == PresenceState::Online {
+                    // Don't set last_active_ago when the user is online
+                    presence.content.last_active_ago = None;
+                } else {
+                    // Convert from timestamp to duration
+                    presence.content.last_active_ago = presence
+                        .content
+                        .last_active_ago
+                        .map(|timestamp| current_timestamp - timestamp);
+                }
+
+                Ok(presence)
+            })
+            .transpose()
+    }
+
     /// Sets all users to offline who have been quiet for too long.
     pub fn presence_maintain(
         &self,
diff --git a/src/main.rs b/src/main.rs
index 5005a372..57eb0d00 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -69,6 +69,7 @@ fn setup_rocket(config: Figment, data: Database) -> rocket::Rocket<rocket::Build
                 client_server::get_avatar_url_route,
                 client_server::get_profile_route,
                 client_server::set_presence_route,
+                client_server::get_presence_route,
                 client_server::upload_keys_route,
                 client_server::get_keys_route,
                 client_server::claim_keys_route,