feat: Support creating Timeline with initial items from sliding sync

Co-authored-by: Benjamin Kampmann <ben@gnunicorn.org>
This commit is contained in:
Jonas Platte
2022-11-21 12:30:11 +01:00
committed by Jonas Platte
parent 2ab697328f
commit fcb37b6962
11 changed files with 133 additions and 100 deletions

5
Cargo.lock generated
View File

@@ -624,9 +624,9 @@ checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
[[package]]
name = "chrono"
version = "0.4.22"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -2265,6 +2265,7 @@ name = "jack-in"
version = "0.2.0"
dependencies = [
"app_dirs2",
"chrono",
"dialoguer",
"eyre",
"futures",

View File

@@ -12,10 +12,6 @@ repository = "https://github.com/matrix-org/matrix-rust-sdk"
[lib]
crate-type = ["cdylib", "staticlib"]
[features]
default = ["experimental-room-preview"] # the whole crate is still very experimental, so this is fine
experimental-room-preview = ["matrix-sdk/experimental-room-preview"]
[build-dependencies]
uniffi_build = { workspace = true, features = ["builtin-bindgen"] }

View File

@@ -5,10 +5,6 @@ use futures_signals::{
signal_vec::{SignalVecExt, VecDiff},
};
use futures_util::{pin_mut, StreamExt};
#[cfg(feature = "experimental-room-preview")]
use matrix_sdk::ruma::events::{
room::message::SyncRoomMessageEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
};
use matrix_sdk::ruma::{
api::client::sync::sync_events::{
v4::RoomSubscription as RumaRoomSubscription,
@@ -23,9 +19,7 @@ pub use matrix_sdk::{
use tokio::task::JoinHandle;
use super::{Client, Room, RUNTIME};
use crate::helpers::unwrap_or_clone_arc;
#[cfg(feature = "experimental-room-preview")]
use crate::EventTimelineItem;
use crate::{helpers::unwrap_or_clone_arc, EventTimelineItem};
pub struct StoppableSpawn {
handle: Arc<RwLock<Option<JoinHandle<()>>>>,
@@ -126,25 +120,14 @@ impl SlidingSyncRoom {
}
}
#[cfg(feature = "experimental-room-preview")]
#[uniffi::export]
impl SlidingSyncRoom {
#[allow(clippy::significant_drop_in_scrutinee)]
pub fn latest_room_message(&self) -> Option<Arc<EventTimelineItem>> {
let messages = self.inner.timeline();
// room is having the latest events at the end,
let lock = messages.lock_ref();
for ev in lock.iter().rev() {
if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
SyncRoomMessageEvent::Original(o),
))) = ev.event.deserialize()
{
let inner =
matrix_sdk::room::timeline::EventTimelineItem::_new(o, ev.event.clone());
return Some(Arc::new(EventTimelineItem(inner)));
}
}
None
RUNTIME.block_on(async {
let item = self.inner.timeline().await.latest()?.as_event()?.to_owned();
Some(Arc::new(EventTimelineItem(item)))
})
}
}

View File

@@ -42,7 +42,6 @@ appservice = ["ruma/appservice-api-s"]
image-proc = ["dep:image"]
image-rayon = ["image-proc", "image?/jpeg_rayon"]
experimental-room-preview = []
experimental-timeline = ["ruma/unstable-msc2677"]
sliding-sync = [

View File

@@ -16,8 +16,6 @@ use std::fmt;
use indexmap::IndexMap;
use matrix_sdk_base::deserialized_responses::EncryptionInfo;
#[cfg(feature = "experimental-room-preview")]
use ruma::events::room::message::{OriginalSyncRoomMessageEvent, Relation};
use ruma::{
events::{
relation::{AnnotationChunk, AnnotationType},
@@ -85,37 +83,6 @@ macro_rules! build {
}
impl EventTimelineItem {
#[cfg(feature = "experimental-room-preview")]
#[doc(hidden)] // FIXME: Remove. Used for matrix-sdk-ffi temporarily.
pub fn _new(ev: OriginalSyncRoomMessageEvent, raw: Raw<AnySyncTimelineEvent>) -> Self {
let edited = ev.unsigned.relations.as_ref().map_or(false, |r| r.replace.is_some());
let reactions = ev
.unsigned
.relations
.and_then(|r| r.annotation)
.map(BundledReactions::from)
.unwrap_or_default();
Self {
key: TimelineKey::EventId(ev.event_id),
event_id: None,
sender: ev.sender,
content: TimelineItemContent::Message(Message {
msgtype: ev.content.msgtype,
in_reply_to: ev.content.relates_to.and_then(|rel| match rel {
Relation::Reply { in_reply_to } => Some(in_reply_to.event_id),
_ => None,
}),
edited,
}),
reactions,
origin_server_ts: Some(ev.origin_server_ts),
is_own: false, // FIXME: Potentially wrong
encryption_info: None, // FIXME: Potentially wrong
raw: Some(raw),
}
}
/// Get the [`TimelineKey`] of this item.
pub fn key(&self) -> &TimelineKey {
&self.key

View File

@@ -23,7 +23,10 @@ use std::{
use futures_core::Stream;
use futures_signals::signal_vec::{MutableVec, SignalVec, SignalVecExt, VecDiff};
use matrix_sdk_base::{deserialized_responses::EncryptionInfo, locks::Mutex};
use matrix_sdk_base::{
deserialized_responses::{EncryptionInfo, SyncTimelineEvent},
locks::Mutex,
};
use ruma::{
assign,
events::{
@@ -89,7 +92,22 @@ struct TimelineInnerMetadata {
impl Timeline {
pub(super) async fn new(room: &room::Common) -> Self {
Self::with_events(room, None, Vec::new()).await
}
pub(crate) async fn with_events(
room: &room::Common,
prev_token: Option<String>,
events: Vec<SyncTimelineEvent>,
) -> Self {
let inner = TimelineInner::default();
let own_user_id = room.own_user_id();
for ev in events.into_iter().rev() {
inner
.handle_back_paginated_event(ev.event.cast(), ev.encryption_info, own_user_id)
.await;
}
match room.account_data_static::<FullyReadEventContent>().await {
Ok(Some(fully_read)) => match fully_read.deserialize() {
@@ -176,7 +194,7 @@ impl Timeline {
Timeline {
inner,
room: room.clone(),
start_token: StdMutex::new(None),
start_token: StdMutex::new(prev_token),
_end_token: StdMutex::new(None),
_timeline_event_handler_guard,
_fully_read_handler_guard,
@@ -255,6 +273,11 @@ impl Timeline {
.await;
}
/// Get the latest of the timeline's items.
pub fn latest(&self) -> Option<Arc<TimelineItem>> {
self.inner.items.lock_ref().last().cloned()
}
/// Get a signal of the timeline's items.
///
/// You can poll this signal to receive updates, the first of which will

View File

@@ -29,6 +29,8 @@ use ruma::{
use thiserror::Error;
use url::Url;
#[cfg(feature = "experimental-timeline")]
use crate::room::timeline::Timeline;
use crate::{Client, Result};
/// Internal representation of errors in Sliding Sync
@@ -106,6 +108,7 @@ pub type AliveRoomTimeline = Arc<futures_signals::signal_vec::MutableVec<SyncTim
/// Room info as giving by the SlidingSync Feature.
#[derive(Debug, Clone)]
pub struct SlidingSyncRoom {
client: Client,
room_id: OwnedRoomId,
inner: v4::SlidingSyncRoom,
is_loading_more: Mutable<bool>,
@@ -115,6 +118,7 @@ pub struct SlidingSyncRoom {
impl SlidingSyncRoom {
fn from(
client: Client,
room_id: OwnedRoomId,
mut inner: v4::SlidingSyncRoom,
timeline: Vec<SyncTimelineEvent>,
@@ -122,6 +126,7 @@ impl SlidingSyncRoom {
// we overwrite to only keep one copy
inner.timeline = vec![];
Self {
client,
room_id,
is_loading_more: Mutable::new(false),
prev_batch: Mutable::new(inner.prev_batch.clone()),
@@ -146,10 +151,20 @@ impl SlidingSyncRoom {
}
/// `AliveTimeline` of this room
#[cfg(not(feature = "experimental-timeline"))]
pub fn timeline(&self) -> AliveRoomTimeline {
self.timeline.clone()
}
/// `Timeline` of this room
#[cfg(feature = "experimental-timeline")]
pub async fn timeline(&self) -> Timeline {
let current_timeline = self.timeline.lock_ref().to_vec();
let prev_batch = self.prev_batch.lock_ref().clone();
let room = self.client.get_room(&self.room_id).unwrap();
Timeline::with_events(&room, prev_batch, current_timeline).await
}
/// This rooms name as calculated by the server, if any
pub fn name(&self) -> Option<&str> {
self.inner.name.as_deref()
@@ -490,7 +505,7 @@ impl SlidingSync {
} else {
rooms_map.insert_cloned(
id.clone(),
SlidingSyncRoom::from(id.clone(), room_data, timeline),
SlidingSyncRoom::from(self.client.clone(), id.clone(), room_data, timeline),
);
rooms.push(id);
}

View File

@@ -10,11 +10,12 @@ file-logging = ["dep:log4rs"]
[dependencies]
app_dirs2 = "2"
chrono = "0.4.23"
dialoguer = "0.10.2"
eyre = "0.6"
futures = { version = "0.3.1" }
futures-signals = "0.3.24"
matrix-sdk = { path = "../../crates/matrix-sdk", default-features = false, features = ["e2e-encryption", "anyhow", "native-tls", "sled", "sliding-sync"], version = "0.6.0" }
matrix-sdk = { path = "../../crates/matrix-sdk", default-features = false, features = ["e2e-encryption", "anyhow", "native-tls", "sled", "sliding-sync", "experimental-timeline"], version = "0.6.0" }
matrix-sdk-common = { path = "../../crates/matrix-sdk-common", version = "0.6.0" }
matrix-sdk-sled = { path = "../../crates/matrix-sdk-sled", features = ["state-store", "crypto-store"], version = "0.2.0" }
sanitize-filename-reader-friendly = "2.2.1"
@@ -22,7 +23,7 @@ serde_json = "1.0.85"
structopt = "0.3"
tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros"] }
tracing-flame = "0.2"
tracing-subscriber = "0.3.15"
tracing-subscriber = "0.3.15"
tui-logger = "0.8.1"
tuirealm = "~1.8"
tui-realm-stdlib = "1.2.0"

View File

@@ -1,7 +1,15 @@
use std::time::{Duration, Instant};
use std::{
sync::Arc,
time::{Duration, Instant},
};
use futures_signals::signal::Mutable;
use matrix_sdk::{ruma::OwnedRoomId, SlidingSyncView};
use futures::{pin_mut, StreamExt};
use futures_signals::{
signal::Mutable,
signal_vec::{MutableVec, VecDiff},
};
use matrix_sdk::{room::timeline::TimelineItem, ruma::OwnedRoomId, SlidingSyncView};
use tokio::task::JoinHandle;
#[derive(Clone, Default)]
pub struct CurrentRoomSummary {
@@ -17,7 +25,9 @@ pub struct SlidingSyncState {
/// the current list selector for the room
first_render: Option<Duration>,
full_sync: Option<Duration>,
tl_handle: Mutable<Option<JoinHandle<()>>>,
pub selected_room: Mutable<Option<OwnedRoomId>>,
pub current_timeline: MutableVec<Arc<TimelineItem>>,
}
impl SlidingSyncState {
@@ -27,7 +37,9 @@ impl SlidingSyncState {
view,
first_render: None,
full_sync: None,
tl_handle: Default::default(),
selected_room: Default::default(),
current_timeline: Default::default(),
}
}
@@ -40,6 +52,49 @@ impl SlidingSyncState {
}
pub fn select_room(&self, r: Option<OwnedRoomId>) {
self.current_timeline.lock_mut().clear();
if let Some(c) = self.tl_handle.lock_mut().take() {
c.abort();
}
if let Some(room) =
r.as_ref().and_then(|room_id| self.view.rooms.lock_ref().get(room_id).cloned())
{
let current_timeline = self.current_timeline.clone();
let handle = tokio::spawn(async move {
let timeline = room.timeline().await;
let listener = timeline.stream();
pin_mut!(listener);
while let Some(diff) = listener.next().await {
match diff {
VecDiff::Clear {} => {
current_timeline.lock_mut().clear();
}
VecDiff::InsertAt { index, value } => {
current_timeline.lock_mut().insert_cloned(index, value);
}
VecDiff::Move { old_index, new_index } => {
current_timeline.lock_mut().move_from_to(old_index, new_index);
}
VecDiff::Pop {} => {
current_timeline.lock_mut().pop();
}
VecDiff::Push { value } => {
current_timeline.lock_mut().push_cloned(value);
}
VecDiff::RemoveAt { index } => {
current_timeline.lock_mut().remove(index);
}
VecDiff::Replace { values } => {
current_timeline.lock_mut().replace_cloned(values);
}
VecDiff::UpdateAt { index, value } => {
current_timeline.lock_mut().set_cloned(index, value);
}
}
}
});
*self.tl_handle.lock_mut() = Some(handle);
}
self.selected_room.replace(r);
}

View File

@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent, MessageLikeEvent};
use chrono::{offset::Local, DateTime};
use tuirealm::{
command::{Cmd, CmdResult},
event::{Key, KeyEvent, KeyModifiers},
@@ -23,7 +23,7 @@ pub struct Details {
liststate: ListState,
name: Option<String>,
state_events_counts: Vec<(String, usize)>,
current_room_timeline: Vec<AnyTimelineEvent>,
current_room_timeline: Vec<String>,
}
impl Details {
@@ -68,15 +68,25 @@ impl Details {
state_events.iter().map(|(k, l)| (k.clone(), l.len())).collect();
state_events_counts.sort_by_key(|(_, count)| *count);
let mut timeline: Vec<AnyTimelineEvent> = room_data
.timeline()
let timeline: Vec<String> = self
.sstate
.current_timeline
.lock_ref()
.iter()
.filter_map(|d| d.event.deserialize().ok())
.map(|e| e.into_full_event(room_id.clone()))
.filter_map(|t| t.as_event()) // we ignore virtual events
.filter_map(|e| e.content().as_message().map(|m| (e, m)))
.map(|(e, m)| {
format!(
"[{}] {}: {}",
e.origin_server_ts()
.and_then(|r| r.to_system_time())
.map(|s| DateTime::<Local>::from(s).format("%Y-%m-%dT%T").to_string())
.unwrap_or_default(),
e.sender(),
m.body()
)
})
.collect();
timeline.reverse();
self.current_room_timeline = timeline;
self.name = Some(name);
self.state_events_counts = state_events_counts;
@@ -166,28 +176,8 @@ impl MockComponent for Details {
let title = (name.to_owned(), Alignment::Left);
let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag();
let mut details = vec![];
for e in self.current_room_timeline.iter() {
let body = {
match e {
AnyTimelineEvent::MessageLike(m) => {
if let AnyMessageLikeEvent::RoomMessage(MessageLikeEvent::Original(m)) = m {
m.content.body().to_owned()
} else {
m.event_type().to_string()
}
}
AnyTimelineEvent::State(s) => s.event_type().to_string(),
}
};
details.push(ListItem::new(format!(
"[{}] {}: {}",
e.origin_server_ts().as_secs(),
e.sender().as_str(),
body
)));
}
let details: Vec<_> =
self.current_room_timeline.iter().map(|e| ListItem::new(e.clone())).collect();
frame.render_stateful_widget(
List::new(details)

View File

@@ -7,7 +7,9 @@ use std::path::{Path, PathBuf};
use app_dirs2::{app_root, AppDataType, AppInfo};
use dialoguer::{theme::ColorfulTheme, Password};
use eyre::{eyre, Result};
use futures_signals::signal_vec::VecDiff;
use matrix_sdk::{
room::timeline::TimelineItem,
ruma::{OwnedRoomId, OwnedUserId},
Client,
};
@@ -45,6 +47,7 @@ pub enum Msg {
pub enum JackInEvent {
Any, // match all
SyncUpdate(client::state::SlidingSyncState),
RoomDataUpdate(VecDiff<TimelineItem>),
}
impl PartialOrd for JackInEvent {