From b46000fadc7450799a27154a6398276c95fa208b Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Mon, 1 Apr 2024 10:52:36 +0100 Subject: [PATCH] feat: recurse relationships --- Cargo.lock | 27 ++--- Cargo.toml | 2 +- src/api/client_server/relations.rs | 91 +++------------ src/service/rooms/pdu_metadata/mod.rs | 158 +++++++++++++++++--------- 4 files changed, 138 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3b15593..834deaa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2027,7 +2027,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.9.4" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "assign", "js_int", @@ -2042,12 +2042,13 @@ dependencies = [ "ruma-server-util", "ruma-signatures", "ruma-state-res", + "web-time", ] [[package]] name = "ruma-appservice-api" version = "0.9.0" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "js_int", "ruma-common", @@ -2059,7 +2060,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.17.4" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "as_variant", "assign", @@ -2078,7 +2079,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.12.1" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "as_variant", "base64 0.21.7", @@ -2108,7 +2109,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.27.11" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "as_variant", "indexmap 2.2.5", @@ -2130,7 +2131,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.8.0" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "js_int", "ruma-common", @@ -2142,7 +2143,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.3" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "js_int", "thiserror", @@ -2151,7 +2152,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.8.0" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "js_int", "ruma-common", @@ -2161,7 +2162,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.12.0" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "once_cell", "proc-macro-crate", @@ -2176,7 +2177,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.8.0" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "js_int", "ruma-common", @@ -2188,7 +2189,7 @@ dependencies = [ [[package]] name = "ruma-server-util" version = "0.2.0" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "headers", "ruma-common", @@ -2199,7 +2200,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.14.0" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "base64 0.21.7", "ed25519-dalek", @@ -2215,7 +2216,7 @@ dependencies = [ [[package]] name = "ruma-state-res" version = "0.10.0" -source = "git+https://github.com/ruma/ruma?rev=5495b85aa311c2805302edb0a7de40399e22b397#5495b85aa311c2805302edb0a7de40399e22b397" +source = "git+https://github.com/ruma/ruma?rev=c5f8137ba9741b2317313256b57e6e14b61fb419#c5f8137ba9741b2317313256b57e6e14b61fb419" dependencies = [ "itertools 0.11.0", "js_int", diff --git a/Cargo.toml b/Cargo.toml index e23501ee..e0eb8c9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ tower-http = { version = "0.4.1", features = [ # Used for matrix spec type definitions and helpers #ruma = { version = "0.4.0", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] } -ruma = { git = "https://github.com/ruma/ruma", rev = "5495b85aa311c2805302edb0a7de40399e22b397", features = [ +ruma = { git = "https://github.com/ruma/ruma", rev = "c5f8137ba9741b2317313256b57e6e14b61fb419", features = [ "appservice-api-c", "client-api", "compat", diff --git a/src/api/client_server/relations.rs b/src/api/client_server/relations.rs index 124f1310..27c00729 100644 --- a/src/api/client_server/relations.rs +++ b/src/api/client_server/relations.rs @@ -3,7 +3,7 @@ use ruma::api::client::relations::{ get_relating_events_with_rel_type_and_event_type, }; -use crate::{service::rooms::timeline::PduCount, services, Result, Ruma}; +use crate::{services, Result, Ruma}; /// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}` pub async fn get_relating_events_with_rel_type_and_event_type_route( @@ -11,27 +11,6 @@ pub async fn get_relating_events_with_rel_type_and_event_type_route( ) -> Result { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - let from = match body.from.clone() { - Some(from) => PduCount::try_from_string(&from)?, - None => match ruma::api::Direction::Backward { - // TODO: fix ruma so `body.dir` exists - ruma::api::Direction::Forward => PduCount::min(), - ruma::api::Direction::Backward => PduCount::max(), - }, - }; - - let to = body - .to - .as_ref() - .and_then(|t| PduCount::try_from_string(t).ok()); - - // Use limit or else 10, with maximum 100 - let limit = body - .limit - .and_then(|u| u32::try_from(u).ok()) - .map_or(10_usize, |u| u as usize) - .min(100); - let res = services() .rooms .pdu_metadata @@ -41,9 +20,11 @@ pub async fn get_relating_events_with_rel_type_and_event_type_route( &body.event_id, Some(body.event_type.clone()), Some(body.rel_type.clone()), - from, - to, - limit, + body.from.clone(), + body.to.clone(), + body.limit, + body.recurse, + &body.dir, )?; Ok( @@ -51,6 +32,7 @@ pub async fn get_relating_events_with_rel_type_and_event_type_route( chunk: res.chunk, next_batch: res.next_batch, prev_batch: res.prev_batch, + recursion_depth: res.recursion_depth, }, ) } @@ -61,27 +43,6 @@ pub async fn get_relating_events_with_rel_type_route( ) -> Result { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - let from = match body.from.clone() { - Some(from) => PduCount::try_from_string(&from)?, - None => match ruma::api::Direction::Backward { - // TODO: fix ruma so `body.dir` exists - ruma::api::Direction::Forward => PduCount::min(), - ruma::api::Direction::Backward => PduCount::max(), - }, - }; - - let to = body - .to - .as_ref() - .and_then(|t| PduCount::try_from_string(t).ok()); - - // Use limit or else 10, with maximum 100 - let limit = body - .limit - .and_then(|u| u32::try_from(u).ok()) - .map_or(10_usize, |u| u as usize) - .min(100); - let res = services() .rooms .pdu_metadata @@ -91,15 +52,18 @@ pub async fn get_relating_events_with_rel_type_route( &body.event_id, None, Some(body.rel_type.clone()), - from, - to, - limit, + body.from.clone(), + body.to.clone(), + body.limit, + body.recurse, + &body.dir, )?; Ok(get_relating_events_with_rel_type::v1::Response { chunk: res.chunk, next_batch: res.next_batch, prev_batch: res.prev_batch, + recursion_depth: res.recursion_depth, }) } @@ -109,27 +73,6 @@ pub async fn get_relating_events_route( ) -> Result { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - let from = match body.from.clone() { - Some(from) => PduCount::try_from_string(&from)?, - None => match ruma::api::Direction::Backward { - // TODO: fix ruma so `body.dir` exists - ruma::api::Direction::Forward => PduCount::min(), - ruma::api::Direction::Backward => PduCount::max(), - }, - }; - - let to = body - .to - .as_ref() - .and_then(|t| PduCount::try_from_string(t).ok()); - - // Use limit or else 10, with maximum 100 - let limit = body - .limit - .and_then(|u| u32::try_from(u).ok()) - .map_or(10_usize, |u| u as usize) - .min(100); - services() .rooms .pdu_metadata @@ -139,8 +82,10 @@ pub async fn get_relating_events_route( &body.event_id, None, None, - from, - to, - limit, + body.from.clone(), + body.to.clone(), + body.limit, + body.recurse, + &body.dir, ) } diff --git a/src/service/rooms/pdu_metadata/mod.rs b/src/service/rooms/pdu_metadata/mod.rs index 411f4f54..5ffe8846 100644 --- a/src/service/rooms/pdu_metadata/mod.rs +++ b/src/service/rooms/pdu_metadata/mod.rs @@ -3,9 +3,9 @@ use std::sync::Arc; pub use data::Data; use ruma::{ - api::client::relations::get_relating_events, + api::{client::relations::get_relating_events, Direction}, events::{relation::RelationType, TimelineEventType}, - EventId, RoomId, UserId, + EventId, RoomId, UInt, UserId, }; use serde::Deserialize; @@ -48,37 +48,57 @@ impl Service { target: &EventId, filter_event_type: Option, filter_rel_type: Option, - from: PduCount, - to: Option, - limit: usize, + from: Option, + to: Option, + limit: Option, + recurse: bool, + dir: &Direction, ) -> Result { + let from = match from { + Some(from) => PduCount::try_from_string(&from)?, + None => match dir { + Direction::Forward => PduCount::min(), + Direction::Backward => PduCount::max(), + }, + }; + + let to = to.as_ref().and_then(|t| PduCount::try_from_string(t).ok()); + + // Use limit or else 10, with maximum 100 + let limit = limit + .and_then(|u| u32::try_from(u).ok()) + .map_or(10_usize, |u| u as usize) + .min(100); + let next_token; - //TODO: Fix ruma: match body.dir { - match ruma::api::Direction::Backward { - ruma::api::Direction::Forward => { - let events_after: Vec<_> = services() - .rooms - .pdu_metadata - .relations_until(sender_user, room_id, target, from)? // TODO: should be relations_after - .filter(|r| { - r.as_ref().map_or(true, |(_, pdu)| { - filter_event_type.as_ref().map_or(true, |t| &pdu.kind == t) - && if let Ok(content) = - serde_json::from_str::( - pdu.content.get(), - ) - { - filter_rel_type - .as_ref() - .map_or(true, |r| &content.relates_to.rel_type == r) - } else { - false - } - }) + // Spec (v1.10) recommends depth of at least 3 + let depth: u8 = if recurse { 3 } else { 1 }; + + match dir { + Direction::Forward => { + let relations_until = &services().rooms.pdu_metadata.relations_until( + sender_user, + room_id, + target, + from, + depth, + )?; + let events_after: Vec<_> = relations_until // TODO: should be relations_after + .iter() + .filter(|(_, pdu)| { + filter_event_type.as_ref().map_or(true, |t| &pdu.kind == t) + && if let Ok(content) = + serde_json::from_str::(pdu.content.get()) + { + filter_rel_type + .as_ref() + .map_or(true, |r| &content.relates_to.rel_type == r) + } else { + false + } }) .take(limit) - .filter_map(|r| r.ok()) // Filter out buggy events .filter(|(_, pdu)| { services() .rooms @@ -86,7 +106,7 @@ impl Service { .user_can_see_event(sender_user, room_id, &pdu.event_id) .unwrap_or(false) }) - .take_while(|&(k, _)| Some(k) != to) // Stop at `to` + .take_while(|(k, _)| Some(k) != to.as_ref()) // Stop at `to` .collect(); next_token = events_after.last().map(|(count, _)| count).copied(); @@ -101,31 +121,32 @@ impl Service { chunk: events_after, next_batch: next_token.map(|t| t.stringify()), prev_batch: Some(from.stringify()), + recursion_depth: if recurse { Some(depth.into()) } else { None }, }) } - ruma::api::Direction::Backward => { - let events_before: Vec<_> = services() - .rooms - .pdu_metadata - .relations_until(sender_user, room_id, target, from)? - .filter(|r| { - r.as_ref().map_or(true, |(_, pdu)| { - filter_event_type.as_ref().map_or(true, |t| &pdu.kind == t) - && if let Ok(content) = - serde_json::from_str::( - pdu.content.get(), - ) - { - filter_rel_type - .as_ref() - .map_or(true, |r| &content.relates_to.rel_type == r) - } else { - false - } - }) + Direction::Backward => { + let relations_until = &services().rooms.pdu_metadata.relations_until( + sender_user, + room_id, + target, + from, + depth, + )?; + let events_before: Vec<_> = relations_until + .iter() + .filter(|(_, pdu)| { + filter_event_type.as_ref().map_or(true, |t| &pdu.kind == t) + && if let Ok(content) = + serde_json::from_str::(pdu.content.get()) + { + filter_rel_type + .as_ref() + .map_or(true, |r| &content.relates_to.rel_type == r) + } else { + false + } }) .take(limit) - .filter_map(|r| r.ok()) // Filter out buggy events .filter(|(_, pdu)| { services() .rooms @@ -133,7 +154,7 @@ impl Service { .user_can_see_event(sender_user, room_id, &pdu.event_id) .unwrap_or(false) }) - .take_while(|&(k, _)| Some(k) != to) // Stop at `to` + .take_while(|&(k, _)| Some(k) != to.as_ref()) // Stop at `to` .collect(); next_token = events_before.last().map(|(count, _)| count).copied(); @@ -147,6 +168,7 @@ impl Service { chunk: events_before, next_batch: next_token.map(|t| t.stringify()), prev_batch: Some(from.stringify()), + recursion_depth: if recurse { Some(depth.into()) } else { None }, }) } } @@ -158,14 +180,44 @@ impl Service { room_id: &'a RoomId, target: &'a EventId, until: PduCount, - ) -> Result> + 'a> { + max_depth: u8, + ) -> Result> { let room_id = services().rooms.short.get_or_create_shortroomid(room_id)?; let target = match services().rooms.timeline.get_pdu_count(target)? { Some(PduCount::Normal(c)) => c, // TODO: Support backfilled relations _ => 0, // This will result in an empty iterator }; - self.db.relations_until(user_id, room_id, target, until) + + self.db + .relations_until(user_id, room_id, target, until) + .map(|mut relations| { + let mut pdus: Vec<_> = (*relations).into_iter().filter_map(Result::ok).collect(); + let mut stack: Vec<_> = + pdus.clone().iter().map(|pdu| (pdu.to_owned(), 1)).collect(); + + while let Some(stack_pdu) = stack.pop() { + let target = match stack_pdu.0 .0 { + PduCount::Normal(c) => c, + // TODO: Support backfilled relations + PduCount::Backfilled(_) => 0, // This will result in an empty iterator + }; + + if let Ok(relations) = self.db.relations_until(user_id, room_id, target, until) + { + for relation in relations.flatten() { + if stack_pdu.1 < max_depth { + stack.push((relation.clone(), stack_pdu.1 + 1)); + } + + pdus.push(relation); + } + } + } + + pdus.sort_by(|a, b| a.0.cmp(&b.0)); + pdus + }) } #[tracing::instrument(skip(self, room_id, event_ids))]