Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 92 additions & 4 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use tauri::{AppHandle, Manager, State};

use crate::{
dto::{
HomeserverInfo, RegisterAuth, RegisterEmailToken, RegisterStep, RoomMemberDto, RoomsSnapshot,
HomeserverInfo, NotifyMode, RegisterAuth, RegisterEmailToken, RegisterStep, RoomMemberDto, RoomsSnapshot,
SendOptions, SessionInfo, SpaceSummary, StickerOptions, TimelineItemDto,
},
error::{Error, Result},
matrix::{auth, core::AppState, media, packs, presence, profile, rooms, timeline},
matrix::{auth, core::AppState, directory, media, packs, presence, profile, rooms, timeline},
rtc, verification,
};

Expand Down Expand Up @@ -157,9 +157,36 @@ pub async fn create_room(
rooms::create(&*state.core().await?, room).await
}

/// Join by alias or ID. `via` names servers to try, for a room ID ours
/// doesn't know yet — one found in another server's directory, say.
#[tauri::command]
pub async fn join_room(state: State<'_, AppState>, alias_or_id: String) -> Result<String> {
rooms::join(&*state.core().await?, &alias_or_id).await
pub async fn join_room(
state: State<'_, AppState>,
alias_or_id: String,
via: Option<Vec<String>>,
) -> Result<String> {
rooms::join(&*state.core().await?, &alias_or_id, &via.unwrap_or_default()).await
}

/// One page of public spaces from a server's directory — ours when `server` is
/// omitted.
#[tauri::command]
pub async fn get_public_spaces(
state: State<'_, AppState>,
server: Option<String>,
search: Option<String>,
since: Option<String>,
) -> Result<directory::DirectoryPage> {
directory::public_spaces(&*state.core().await?, server, search, since).await
}

/// The rooms inside a space, readable before joining it.
#[tauri::command]
pub async fn get_space_rooms(
state: State<'_, AppState>,
space_id: String,
) -> Result<Vec<directory::PublicRoom>> {
directory::space_rooms(&*state.core().await?, &parse_room_id(&space_id)?).await
}

/// Leave a room. Doesn't forget it — see `rooms::leave`.
Expand Down Expand Up @@ -224,6 +251,67 @@ pub async fn set_room_muted(
Ok(())
}

/// Set the notification level on several rooms at once — every room in a
/// space, say. `None` returns them to the default.
#[tauri::command]
pub async fn set_rooms_notify_mode(
app: AppHandle,
state: State<'_, AppState>,
room_ids: Vec<String>,
mode: Option<NotifyMode>,
) -> Result<()> {
let core = state.core().await?;
each_room(&app, &core, &room_ids, |core, room_id| async move {
match mode {
Some(mode) => rooms::set_notify_mode(core, &room_id, mode).await,
// No mode: back to the default, which is what unmuting means.
None => rooms::set_muted(core, &room_id, false).await,
}
})
.await
}

/// Mark several rooms read at once — a whole space, say — without opening any.
#[tauri::command]
pub async fn mark_rooms_read(
app: AppHandle,
state: State<'_, AppState>,
room_ids: Vec<String>,
) -> Result<()> {
let core = state.core().await?;
each_room(&app, &core, &room_ids, |core, room_id| async move {
rooms::mark_read(core, &room_id).await
})
.await
}

/// Run `action` on each room in turn, then refresh it in the sidebar.
///
/// One at a time: push rules are a single account-wide ruleset the SDK caches,
/// and concurrent writes to it race. A room that fails doesn't stop the rest;
/// the first error is reported once they've all had their turn.
async fn each_room<'a, F, Fut>(
app: &AppHandle,
core: &'a crate::matrix::MatrixCore,
room_ids: &[String],
action: F,
) -> Result<()>
where
F: Fn(&'a crate::matrix::MatrixCore, OwnedRoomId) -> Fut,
Fut: std::future::Future<Output = Result<()>>,
{
let mut first_error = None;
for raw in room_ids {
let room_id = parse_room_id(raw)?;
if let Err(e) = action(core, room_id.clone()).await {
tracing::warn!("{room_id}: {e}");
first_error.get_or_insert(e);
}
rooms::emit_room_refresh(app, core, &room_id).await;
}
first_error.map_or(Ok(()), Err)
}

#[tauri::command]
pub async fn set_room_marked_unread(
state: State<'_, AppState>,
Expand Down
34 changes: 34 additions & 0 deletions src-tauri/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ pub struct RoomSummary {
/// room type we don't draw. Spaces have their own flag and their own rail.
pub is_utility: bool,
pub is_muted: bool,
/// The level set for this room, if one has been. `None` is the account's
/// default for this kind of room.
pub notify_mode: Option<NotifyMode>,
pub membership: RoomMembership,
pub notification_count: u64,
pub highlight_count: u64,
Expand Down Expand Up @@ -215,6 +218,37 @@ pub struct LatestEvent {
pub timestamp: u64,
}

/// How much a room notifies — Matrix's per-room push rule, by its effect.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, serde::Deserialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum NotifyMode {
All,
Mentions,
Mute,
}

impl From<matrix_sdk::notification_settings::RoomNotificationMode> for NotifyMode {
fn from(mode: matrix_sdk::notification_settings::RoomNotificationMode) -> Self {
use matrix_sdk::notification_settings::RoomNotificationMode as M;
match mode {
M::AllMessages => Self::All,
M::MentionsAndKeywordsOnly => Self::Mentions,
M::Mute => Self::Mute,
}
}
}

impl From<NotifyMode> for matrix_sdk::notification_settings::RoomNotificationMode {
fn from(mode: NotifyMode) -> Self {
match mode {
NotifyMode::All => Self::AllMessages,
NotifyMode::Mentions => Self::MentionsAndKeywordsOnly,
NotifyMode::Mute => Self::Mute,
}
}
}

#[derive(Debug, Clone, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
Expand Down
22 changes: 21 additions & 1 deletion src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,28 @@ impl From<url::ParseError> for Error {

impl Serialize for Error {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
UwuError { kind: self.kind(), message: self.to_string() }.serialize(s)
UwuError { kind: self.kind(), message: message(self) }.serialize(s)
}
}

/// The error's own message, plus whatever caused it that it doesn't already say.
///
/// Transport errors keep the useful part in their sources: reqwest's top line
/// is "error sending request for url (…)", and only further down does it say
/// "operation timed out" or "connection refused".
fn message(error: &Error) -> String {
let mut message = error.to_string();
let mut source = std::error::Error::source(error);
while let Some(cause) = source {
let text = cause.to_string();
// `#[error("{0}")]` wrappers repeat their inner error word for word.
if !text.is_empty() && !message.contains(&text) {
message.push_str(": ");
message.push_str(&text);
}
source = cause.source();
}
message
}

pub type Result<T, E = Error> = std::result::Result<T, E>;
4 changes: 4 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,17 @@ pub fn run() {
commands::get_members,
commands::create_room,
commands::join_room,
commands::get_public_spaces,
commands::get_space_rooms,
commands::leave_room,
commands::update_room,
commands::get_room_permissions,
commands::invite_user,
commands::set_typing,
commands::set_room_muted,
commands::set_room_marked_unread,
commands::set_rooms_notify_mode,
commands::mark_rooms_read,
// image packs
commands::get_image_packs,
commands::get_all_image_packs,
Expand Down
14 changes: 1 addition & 13 deletions src-tauri/src/matrix/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use matrix_sdk::{
authentication::matrix::MatrixSession,
encryption::{BackupDownloadStrategy, EncryptionSettings},
ruma::{
ClientSecret, OwnedClientSecret, OwnedSessionId, RoomId, UInt,
ClientSecret, OwnedClientSecret, OwnedSessionId, UInt,
api::client::{
account::{register, request_registration_token_via_email},
session::get_login_types::v3::LoginType,
Expand Down Expand Up @@ -670,18 +670,6 @@ fn friendly_login_error(error: &matrix_sdk::Error) -> String {
}
}

/// Resolve a room alias or ID to a room ID.
pub async fn resolve_room(
client: &Client,
alias_or_id: &str,
) -> Result<matrix_sdk::ruma::OwnedRoomId> {
if let Ok(room_id) = RoomId::parse(alias_or_id) {
return Ok(room_id);
}
let alias = matrix_sdk::ruma::RoomAliasId::parse(alias_or_id)?;
Ok(client.resolve_room_alias(&alias).await?.room_id)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading