mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-04-25 17:49:58 -04:00
feat: Support creating Timeline with initial items from sliding sync
Co-authored-by: Benjamin Kampmann <ben@gnunicorn.org>
This commit is contained in:
committed by
Jonas Platte
parent
2ab697328f
commit
fcb37b6962
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
|
||||
@@ -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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user