Files
continuwuity/src/api/client/space.rs
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

196 lines
4.7 KiB
Rust
Raw Normal View History

2025-02-07 07:09:45 +00:00
use std::{
collections::{BTreeSet, VecDeque},
str::FromStr,
};
2023-07-02 16:06:54 +02:00
2024-07-16 08:05:25 +00:00
use axum::extract::State;
2025-02-07 07:09:45 +00:00
use conduwuit::{
Err, Result,
utils::{future::TryExtExt, stream::IterStream},
2025-02-07 07:09:45 +00:00
};
use conduwuit_service::{
Services,
2025-02-07 07:09:45 +00:00
rooms::spaces::{
PaginationToken, SummaryAccessibility, get_parent_children_via, summary_to_chunk,
2025-02-07 07:09:45 +00:00
},
2024-03-16 16:09:11 -04:00
};
use futures::{StreamExt, TryFutureExt, future::OptionFuture};
use ruma::{
OwnedRoomId, OwnedServerName, RoomId, UInt, UserId, api::client::space::get_hierarchy,
};
2024-03-16 16:09:11 -04:00
2025-02-07 07:09:45 +00:00
use crate::Ruma;
2024-03-05 19:48:54 -05:00
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy`
2023-07-02 16:06:54 +02:00
///
/// Paginates over the space tree in a depth-first manner to locate child rooms
/// of a given space.
2024-07-16 08:05:25 +00:00
pub(crate) async fn get_hierarchy_route(
State(services): State<crate::State>,
body: Ruma<get_hierarchy::v1::Request>,
2024-07-16 08:05:25 +00:00
) -> Result<get_hierarchy::v1::Response> {
let limit = body
2024-03-25 17:05:11 -04:00
.limit
.unwrap_or_else(|| UInt::from(10_u32))
.min(UInt::from(100_u32));
2024-03-25 17:05:11 -04:00
let max_depth = body
.max_depth
.unwrap_or_else(|| UInt::from(3_u32))
.min(UInt::from(10_u32));
let key = body
.from
.as_ref()
.and_then(|s| PaginationToken::from_str(s).ok());
2023-07-02 16:06:54 +02:00
2024-03-16 16:09:11 -04:00
// Should prevent unexpeded behaviour in (bad) clients
if let Some(ref token) = key {
if token.suggested_only != body.suggested_only || token.max_depth != max_depth {
2025-02-07 07:09:45 +00:00
return Err!(Request(InvalidParam(
"suggested_only and max_depth cannot change on paginated requests"
)));
2024-03-16 16:09:11 -04:00
}
}
2023-07-02 16:06:54 +02:00
2025-02-06 20:08:00 +00:00
get_client_hierarchy(
&services,
body.sender_user(),
&body.room_id,
limit.try_into().unwrap_or(10),
2025-02-07 07:09:45 +00:00
max_depth.try_into().unwrap_or(usize::MAX),
2025-02-06 20:08:00 +00:00
body.suggested_only,
2025-02-07 07:09:45 +00:00
key.as_ref()
.into_iter()
.flat_map(|t| t.short_room_ids.iter()),
2025-02-06 20:08:00 +00:00
)
.await
}
2025-02-07 07:09:45 +00:00
async fn get_client_hierarchy<'a, ShortRoomIds>(
2025-02-06 20:08:00 +00:00
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
limit: usize,
2025-02-07 07:09:45 +00:00
max_depth: usize,
2025-02-06 20:08:00 +00:00
suggested_only: bool,
2025-02-07 07:09:45 +00:00
short_room_ids: ShortRoomIds,
) -> Result<get_hierarchy::v1::Response>
where
ShortRoomIds: Iterator<Item = &'a u64> + Clone + Send + Sync + 'a,
{
type Via = Vec<OwnedServerName>;
type Entry = (OwnedRoomId, Via);
type Rooms = VecDeque<Entry>;
let mut queue: Rooms = [(
room_id.to_owned(),
room_id
.server_name()
.map(ToOwned::to_owned)
.into_iter()
.collect(),
)]
.into();
let mut rooms = Vec::with_capacity(limit);
let mut parents = BTreeSet::new();
while let Some((current_room, via)) = queue.pop_front() {
let summary = services
.rooms
.spaces
.get_summary_and_children_client(&current_room, suggested_only, sender_user, &via)
.await?;
match (summary, current_room == room_id) {
| (None | Some(SummaryAccessibility::Inaccessible), false) => {
// Just ignore other unavailable rooms
},
| (None, true) => {
return Err!(Request(Forbidden("The requested room was not found")));
},
| (Some(SummaryAccessibility::Inaccessible), true) => {
return Err!(Request(Forbidden("The requested room is inaccessible")));
},
2025-02-06 20:08:00 +00:00
| (Some(SummaryAccessibility::Accessible(summary)), _) => {
2025-02-07 07:09:45 +00:00
let populate = parents.len() >= short_room_ids.clone().count();
2025-02-06 20:08:00 +00:00
2025-02-07 07:09:45 +00:00
let mut children: Vec<Entry> = get_parent_children_via(&summary, suggested_only)
.filter(|(room, _)| !parents.contains(room))
.rev()
.map(|(key, val)| (key, val.collect()))
.collect();
2025-04-25 20:59:52 -07:00
if populate {
rooms.push(summary_to_chunk(summary.clone()));
} else {
2025-02-06 20:08:00 +00:00
children = children
.iter()
.rev()
.stream()
.skip_while(|(room, _)| {
services
.rooms
.short
.get_shortroomid(room)
2025-02-07 07:09:45 +00:00
.map_ok(|short| {
Some(&short) != short_room_ids.clone().nth(parents.len())
})
2025-02-06 20:08:00 +00:00
.unwrap_or_else(|_| false)
})
.map(Clone::clone)
2025-02-07 07:09:45 +00:00
.collect::<Vec<Entry>>()
2025-02-06 20:08:00 +00:00
.await
.into_iter()
.rev()
.collect();
2025-02-07 07:09:45 +00:00
}
2025-02-06 20:08:00 +00:00
2025-04-25 20:59:52 -07:00
if !populate && queue.is_empty() && children.is_empty() {
break;
2025-02-06 20:08:00 +00:00
}
2025-02-07 07:09:45 +00:00
parents.insert(current_room.clone());
if rooms.len() >= limit {
break;
2025-02-06 20:08:00 +00:00
}
2025-02-07 07:09:45 +00:00
if parents.len() > max_depth {
2025-02-07 07:09:45 +00:00
continue;
}
queue.extend(children);
2025-02-06 20:08:00 +00:00
},
}
}
2025-02-07 07:09:45 +00:00
let next_batch: OptionFuture<_> = queue
.pop_front()
.map(|(room, _)| async move {
parents.insert(room);
2025-02-06 20:08:00 +00:00
let next_short_room_ids: Vec<_> = parents
.iter()
.stream()
2025-02-07 07:09:45 +00:00
.filter_map(|room_id| services.rooms.short.get_shortroomid(room_id).ok())
2025-02-06 20:08:00 +00:00
.collect()
.await;
2025-02-07 07:09:45 +00:00
(next_short_room_ids.iter().ne(short_room_ids) && !next_short_room_ids.is_empty())
.then_some(PaginationToken {
short_room_ids: next_short_room_ids,
limit: limit.try_into().ok()?,
2025-02-07 07:09:45 +00:00
max_depth: max_depth.try_into().ok()?,
suggested_only,
})
.as_ref()
.map(PaginationToken::to_string)
})
.into();
2025-02-06 20:08:00 +00:00
2025-02-07 07:09:45 +00:00
Ok(get_hierarchy::v1::Response {
next_batch: next_batch.await.flatten(),
rooms,
})
2023-07-02 16:06:54 +02:00
}