Browse Source

timeline: Use streaming api to load timeline

This load the timeline only once the user opends the room.
This also updates some deps, including the sdk.
merge-requests/1327/merge
Julian Sparber 4 years ago
parent
commit
b530e11a68
  1. 169
      Cargo.lock
  2. 3
      Cargo.toml
  3. 2
      src/login.rs
  4. 82
      src/session/content/room_history/mod.rs
  5. 11
      src/session/mod.rs
  6. 4
      src/session/room/event.rs
  7. 40
      src/session/room/mod.rs
  8. 257
      src/session/room/timeline.rs
  9. 7
      src/utils.rs

169
Cargo.lock generated

@ -313,14 +313,14 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backoff"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fe17f59a06fe8b87a6fc8bf53bb70b3aba76d7685f432487a68cd5552853625"
checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
dependencies = [
"futures-core",
"getrandom 0.2.4",
"instant",
"pin-project",
"pin-project-lite",
"rand 0.8.4",
"tokio",
]
@ -387,6 +387,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
dependencies = [
"generic-array",
]
[[package]]
name = "block-modes"
version = "0.7.0"
@ -710,20 +719,19 @@ dependencies = [
]
[[package]]
name = "crypto-mac"
version = "0.10.1"
name = "crypto-common"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a"
checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a"
dependencies = [
"generic-array",
"subtle",
@ -745,7 +753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest",
"digest 0.9.0",
"rand_core 0.5.1",
"subtle",
"zeroize",
@ -800,6 +808,17 @@ dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
dependencies = [
"block-buffer 0.10.2",
"crypto-common",
"subtle",
]
[[package]]
name = "easy-parallel"
version = "3.2.0"
@ -825,7 +844,7 @@ dependencies = [
"ed25519",
"rand 0.7.3",
"serde",
"sha2",
"sha2 0.9.9",
"zeroize",
]
@ -981,6 +1000,7 @@ name = "fractal"
version = "0.0.1"
dependencies = [
"ashpd",
"async-stream",
"futures",
"gettext-rs",
"gst-plugin-gtk4",
@ -1812,7 +1832,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
dependencies = [
"digest",
"digest 0.9.0",
"hmac 0.10.1",
]
@ -1822,18 +1842,17 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
dependencies = [
"crypto-mac 0.10.1",
"digest",
"crypto-mac",
"digest 0.9.0",
]
[[package]]
name = "hmac"
version = "0.11.0"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2"
dependencies = [
"crypto-mac 0.11.1",
"digest",
"digest 0.10.2",
]
[[package]]
@ -2207,6 +2226,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "lru"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "274353858935c992b13c0ca408752e2121da852d07dec7ce5f108c77dfa14d1f"
dependencies = [
"hashbrown",
]
[[package]]
name = "mac"
version = "0.1.1"
@ -2263,7 +2291,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "matrix-qrcode"
version = "0.2.0"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=messages-api#cb8a156b3ac13138398354d75e0b766143fdc660"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=timeline_stream#887986ebce4c72dbf1d2e8198e453162e94ea11f"
dependencies = [
"base64",
"byteorder",
@ -2271,13 +2299,14 @@ dependencies = [
"qrcode",
"rqrr",
"ruma-identifiers",
"ruma-serde",
"thiserror",
]
[[package]]
name = "matrix-sdk"
version = "0.4.1"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=messages-api#cb8a156b3ac13138398354d75e0b766143fdc660"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=timeline_stream#887986ebce4c72dbf1d2e8198e453162e94ea11f"
dependencies = [
"anymap2",
"async-stream",
@ -2306,14 +2335,15 @@ dependencies = [
[[package]]
name = "matrix-sdk-base"
version = "0.4.1"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=messages-api#cb8a156b3ac13138398354d75e0b766143fdc660"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=timeline_stream#887986ebce4c72dbf1d2e8198e453162e94ea11f"
dependencies = [
"chacha20poly1305",
"dashmap",
"futures-channel",
"futures-core",
"futures-util",
"hmac 0.11.0",
"lru",
"hmac 0.12.0",
"lru 0.7.2",
"matrix-sdk-common",
"matrix-sdk-crypto",
"pbkdf2",
@ -2321,7 +2351,7 @@ dependencies = [
"ruma",
"serde",
"serde_json",
"sha2",
"sha2 0.10.1",
"sled",
"thiserror",
"tokio",
@ -2332,7 +2362,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-common"
version = "0.4.1"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=messages-api#cb8a156b3ac13138398354d75e0b766143fdc660"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=timeline_stream#887986ebce4c72dbf1d2e8198e453162e94ea11f"
dependencies = [
"async-lock",
"async-trait",
@ -2342,13 +2372,15 @@ dependencies = [
"serde",
"tokio",
"uuid",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "matrix-sdk-crypto"
version = "0.4.1"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=messages-api#cb8a156b3ac13138398354d75e0b766143fdc660"
source = "git+https://github.com/jsparber/matrix-rust-sdk.git?branch=timeline_stream#887986ebce4c72dbf1d2e8198e453162e94ea11f"
dependencies = [
"aes 0.7.5",
"aes-gcm",
@ -2359,7 +2391,7 @@ dependencies = [
"dashmap",
"futures-util",
"getrandom 0.2.4",
"hmac 0.11.0",
"hmac 0.12.0",
"matrix-qrcode",
"matrix-sdk-common",
"olm-rs",
@ -2368,7 +2400,7 @@ dependencies = [
"ruma",
"serde",
"serde_json",
"sha2",
"sha2 0.10.1",
"sled",
"thiserror",
"tracing",
@ -2862,11 +2894,11 @@ checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
[[package]]
name = "pbkdf2"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739"
checksum = "a4628cc3cf953b82edcd3c1388c5715401420ce5524fedbab426bd5aba017434"
dependencies = [
"crypto-mac 0.11.1",
"digest 0.10.2",
]
[[package]]
@ -2928,26 +2960,6 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
dependencies = [
"proc-macro2 1.0.36",
"quote 1.0.14",
"syn 1.0.85",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
@ -3374,13 +3386,13 @@ checksum = "6fa79947f53b20adb909a323d828d0fd744fa9d854792df07913b083bcd4d63b"
dependencies = [
"g2p",
"image",
"lru",
"lru 0.6.6",
]
[[package]]
name = "ruma"
version = "0.4.0"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"assign",
"js_int",
@ -3398,7 +3410,7 @@ dependencies = [
[[package]]
name = "ruma-api"
version = "0.18.5"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"bytes",
"http",
@ -3414,7 +3426,7 @@ dependencies = [
[[package]]
name = "ruma-api-macros"
version = "0.18.5"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"proc-macro-crate 1.1.0",
"proc-macro2 1.0.36",
@ -3425,7 +3437,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.12.3"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"assign",
"bytes",
@ -3445,7 +3457,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.6.0"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"indexmap",
"js_int",
@ -3460,7 +3472,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.24.6"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"indoc",
"js_int",
@ -3472,12 +3484,13 @@ dependencies = [
"serde",
"serde_json",
"thiserror",
"wildmatch",
]
[[package]]
name = "ruma-events-macros"
version = "0.24.6"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"proc-macro-crate 1.1.0",
"proc-macro2 1.0.36",
@ -3488,7 +3501,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.3.1"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"js_int",
"ruma-api",
@ -3503,20 +3516,22 @@ dependencies = [
[[package]]
name = "ruma-identifiers"
version = "0.20.0"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"percent-encoding",
"rand 0.8.4",
"ruma-identifiers-macros",
"ruma-identifiers-validation",
"ruma-serde",
"ruma-serde-macros",
"serde",
"uuid",
]
[[package]]
name = "ruma-identifiers-macros"
version = "0.20.0"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"quote 1.0.14",
"ruma-identifiers-validation",
@ -3526,7 +3541,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.5.0"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"thiserror",
]
@ -3534,8 +3549,9 @@ dependencies = [
[[package]]
name = "ruma-serde"
version = "0.5.0"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"base64",
"bytes",
"form_urlencoded",
"itoa 0.4.8",
@ -3548,7 +3564,7 @@ dependencies = [
[[package]]
name = "ruma-serde-macros"
version = "0.5.0"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"proc-macro-crate 1.1.0",
"proc-macro2 1.0.36",
@ -3559,7 +3575,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.9.0"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"base64",
"ed25519-dalek",
@ -3568,7 +3584,7 @@ dependencies = [
"ruma-identifiers",
"ruma-serde",
"serde_json",
"sha2",
"sha2 0.9.9",
"thiserror",
"tracing",
]
@ -3576,7 +3592,7 @@ dependencies = [
[[package]]
name = "ruma-state-res"
version = "0.4.1"
source = "git+https://github.com/ruma/ruma/?rev=fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d#fdbc4d6d1dd273c8a6ac95b329943ed8c68df70d"
source = "git+https://github.com/ruma/ruma/?rev=37095f88553b311e7a70adaaabe39976fb8ff71c#37095f88553b311e7a70adaaabe39976fb8ff71c"
dependencies = [
"itertools",
"js_int",
@ -3652,7 +3668,7 @@ dependencies = [
"num",
"rand 0.8.4",
"serde",
"sha2",
"sha2 0.9.9",
"zbus 1.9.1",
"zbus_macros 1.9.1",
"zvariant 2.10.0",
@ -3781,13 +3797,24 @@ version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer",
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.10.2",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"

3
Cargo.toml

@ -13,6 +13,7 @@ incremental = false
codegen-units = 16
[dependencies]
async-stream = "0.3"
log = "0.4"
mime = "0.3.16"
tracing-subscriber = "0.3"
@ -59,7 +60,7 @@ version = "0.1.0"
[dependencies.matrix-sdk]
git = "https://github.com/jsparber/matrix-rust-sdk.git"
branch = "messages-api"
branch = "timeline_stream"
features = [
"socks",
"encryption",

2
src/login.rs

@ -331,7 +331,7 @@ impl Login {
self.freeze();
let handle: JoinHandle<MatrixResult<_>> = spawn_tokio!(async move {
let client = Client::new(homeserver_clone)?;
let client = Client::new(homeserver_clone).await?;
Ok(client
.send(
get_supported_versions::Request::new(),

82
src/session/content/room_history/mod.rs

@ -29,6 +29,7 @@ use crate::{
room::{Item, Room, RoomType, Timeline, TimelineState},
user::UserExt,
},
spawn,
};
mod imp {
@ -76,6 +77,7 @@ mod imp {
pub error: TemplateChild<adw::StatusPage>,
#[template_child]
pub stack: TemplateChild<gtk::Stack>,
pub is_loading: Cell<bool>,
}
#[glib::object_subclass]
@ -245,13 +247,13 @@ mod imp {
} else {
obj.set_sticky(adj.value() + adj.page_size() == adj.upper());
}
obj.load_more_messages(adj);
obj.start_loading();
}));
adj.connect_upper_notify(clone!(@weak obj => move |adj| {
adj.connect_upper_notify(clone!(@weak obj => move |_| {
if obj.sticky() {
obj.scroll_down();
}
obj.load_more_messages(adj);
obj.start_loading();
}));
let key_events = gtk::EventControllerKey::new();
@ -318,19 +320,15 @@ impl RoomHistory {
return;
}
if let Some(category_handler) = priv_.category_handler.take() {
if let Some(room) = self.room() {
if let Some(room) = self.room() {
if let Some(category_handler) = priv_.category_handler.take() {
room.disconnect(category_handler);
}
}
if let Some(empty_timeline_handler) = priv_.empty_timeline_handler.take() {
if let Some(room) = self.room() {
if let Some(empty_timeline_handler) = priv_.empty_timeline_handler.take() {
room.timeline().disconnect(empty_timeline_handler);
}
}
if let Some(room) = self.room() {
if let Some(state_timeline_handler) = priv_.state_timeline_handler.take() {
room.timeline().disconnect(state_timeline_handler);
}
@ -373,10 +371,10 @@ impl RoomHistory {
.map(|room| gtk::NoSelection::new(Some(room.timeline())));
priv_.listview.set_model(model.as_ref());
priv_.is_loading.set(false);
priv_.room.replace(room);
let adj = priv_.listview.vadjustment().unwrap();
self.load_more_messages(&adj);
self.update_view();
self.start_loading();
self.update_room_state();
self.notify("room");
self.notify("empty");
@ -537,17 +535,57 @@ impl RoomHistory {
}
}
fn load_more_messages(&self, adj: &gtk::Adjustment) {
fn need_messages(&self) -> bool {
let adj = self.imp().listview.vadjustment().unwrap();
// Load more messages when the user gets close to the end of the known room
// history Use the page size twice to detect if the user gets close to
// the end
if let Some(room) = self.room() {
if adj.value() < adj.page_size() * 2.0
|| adj.upper() <= adj.page_size() / 2.0
|| room.timeline().is_empty()
{
room.timeline().load_previous_events();
// the endload_timeline
adj.value() < adj.page_size() * 2.0 || adj.upper() <= adj.page_size() / 2.0
}
fn start_loading(&self) {
let priv_ = self.imp();
if !priv_.is_loading.get() {
let room = if let Some(room) = self.room() {
room
} else {
return;
};
if !self.need_messages() && !room.timeline().is_empty() {
return;
}
priv_.is_loading.set(true);
let obj_weak = self.downgrade();
spawn!(async move {
loop {
// We don't want to hold a strong ref to `obj` on `await`
let need = if let Some(obj) = obj_weak.upgrade() {
if obj.room().as_ref() == Some(&room) {
obj.need_messages() || room.timeline().is_empty()
} else {
return;
}
} else {
return;
};
if need {
if !room.timeline().load().await {
break;
}
} else {
break;
}
}
// Remove the task
if let Some(obj) = obj_weak.upgrade() {
obj.imp().is_loading.set(false);
}
});
}
}
@ -585,9 +623,7 @@ impl RoomHistory {
}
fn try_again(&self) {
if let Some(room) = self.room() {
room.timeline().load_previous_events();
}
self.start_loading();
}
}

11
src/session/mod.rs

@ -40,7 +40,6 @@ use matrix_sdk::{
assign,
identifiers::RoomId,
},
uuid::Uuid,
Client, Error as MatrixError, HttpError,
};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
@ -293,11 +292,7 @@ impl Session {
) {
self.imp().logout_on_dispose.set(true);
let mut path = glib::user_data_dir();
path.push(
&Uuid::new_v4()
.to_hyphenated()
.encode_lower(&mut Uuid::encode_buffer()),
);
path.push(glib::uuid_string_random().as_str());
let handle = spawn_tokio!(async move {
let passphrase: String = {
@ -322,7 +317,7 @@ impl Session {
config
};
let client = Client::new_with_config(homeserver.clone(), config).unwrap();
let client = Client::new_with_config(homeserver.clone(), config).await?;
let response = client
.login(&username, &password, None, Some("Fractal Next"))
.await;
@ -371,7 +366,7 @@ impl Session {
.passphrase(session.secret.passphrase.clone())
.store_path(session.path.clone());
let client = Client::new_with_config(session.homeserver.clone(), config).unwrap();
let client = Client::new_with_config(session.homeserver.clone(), config).await?;
client
.restore_login(matrix_sdk::Session {
user_id: session.user_id.clone(),

4
src/session/room/event.rs

@ -16,7 +16,7 @@ use matrix_sdk::{
AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent,
Unsigned,
},
identifiers::{EventId, UserId},
identifiers::{EventId, TransactionId, UserId},
MilliSecondsSinceUnixEpoch,
},
Error as MatrixError,
@ -255,7 +255,7 @@ impl Event {
}
}
pub fn matrix_transaction_id(&self) -> Option<String> {
pub fn matrix_transaction_id(&self) -> Option<Box<TransactionId>> {
self.imp()
.pure_event
.borrow()

40
src/session/room/mod.rs

@ -35,11 +35,10 @@ use matrix_sdk::{
AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, SyncMessageEvent,
Unsigned,
},
identifiers::{EventId, RoomId, UserId},
identifiers::{EventId, RoomId, TransactionId, UserId},
serde::Raw,
MilliSecondsSinceUnixEpoch,
},
uuid::Uuid,
};
use serde_json::value::RawValue;
@ -75,7 +74,7 @@ mod imp {
use super::*;
#[derive(Debug, Default)]
#[derive(Default)]
pub struct Room {
pub room_id: OnceCell<Box<RoomId>>,
pub matrix_room: RefCell<Option<MatrixRoom>>,
@ -318,6 +317,14 @@ mod imp {
obj.bind_property("display-name", obj.avatar(), "display-name")
.flags(glib::BindingFlags::SYNC_CREATE)
.build();
// Load the room history when idle
spawn!(
glib::source::PRIORITY_LOW,
clone!(@weak obj => async move {
obj.timeline().load().await;
})
);
}
}
}
@ -491,30 +498,28 @@ impl Room {
MatrixRoom::Joined(room) => match category {
RoomType::Invited => Ok(()),
RoomType::Favorite => {
room.set_tag(TagName::Favorite.as_ref(), TagInfo::new())
.await?;
room.set_tag(TagName::Favorite, TagInfo::new()).await?;
if previous_category == RoomType::LowPriority {
room.remove_tag(TagName::LowPriority.as_ref()).await?;
room.remove_tag(TagName::LowPriority).await?;
}
Ok(())
}
RoomType::Normal => {
match previous_category {
RoomType::Favorite => {
room.remove_tag(TagName::Favorite.as_ref()).await?;
room.remove_tag(TagName::Favorite).await?;
}
RoomType::LowPriority => {
room.remove_tag(TagName::LowPriority.as_ref()).await?;
room.remove_tag(TagName::LowPriority).await?;
}
_ => {}
}
Ok(())
}
RoomType::LowPriority => {
room.set_tag(TagName::LowPriority.as_ref(), TagInfo::new())
.await?;
room.set_tag(TagName::LowPriority, TagInfo::new()).await?;
if previous_category == RoomType::Favorite {
room.remove_tag(TagName::Favorite.as_ref()).await?;
room.remove_tag(TagName::Favorite).await?;
}
Ok(())
}
@ -793,7 +798,7 @@ impl Room {
self.notify("inviter");
}
/// Add new events to the timeline
/// Update the room state based on the new sync response
pub fn append_events(&self, batch: Vec<Event>) {
let priv_ = self.imp();
@ -832,7 +837,6 @@ impl Room {
}
}
priv_.timeline.get().unwrap().append(batch);
priv_.latest_change.replace(latest_change);
self.notify("latest-change");
self.emit_by_name::<()>("order-changed", &[]);
@ -907,7 +911,7 @@ impl Room {
}
/// Send the given `event` in this room, with the temporary ID `txn_id`.
fn send_room_message_event(&self, event: AnySyncMessageEvent, txn_id: Uuid) {
fn send_room_message_event(&self, event: AnySyncMessageEvent, txn_id: Box<TransactionId>) {
if let MatrixRoom::Joined(matrix_room) = self.matrix_room() {
let content = event.content();
let json = serde_json::to_string(&AnySyncRoomEvent::Message(event)).unwrap();
@ -918,9 +922,10 @@ impl Room {
.timeline
.get()
.unwrap()
.append_pending(txn_id, event);
.append_pending(&txn_id, event);
let handle = spawn_tokio!(async move { matrix_room.send(content, Some(txn_id)).await });
let handle =
spawn_tokio!(async move { matrix_room.send(content, Some(&txn_id)).await });
spawn!(
glib::PRIORITY_DEFAULT_IDLE,
@ -989,7 +994,7 @@ impl Room {
.timeline
.get()
.unwrap()
.append_pending(txn_id, event);
.append_pending(&txn_id, event);
let handle = spawn_tokio!(async move {
matrix_room
@ -1103,7 +1108,6 @@ impl Room {
pub fn handle_left_response(&self, response_room: LeftRoom) {
self.set_matrix_room(self.session().client().get_room(self.room_id()).unwrap());
self.append_events(
response_room
.timeline

257
src/session/room/timeline.rs

@ -1,16 +1,19 @@
use std::collections::HashMap;
use std::{
collections::{HashMap, HashSet, VecDeque},
pin::Pin,
sync::Arc,
};
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use futures::{lock::Mutex, pin_mut, Stream, StreamExt};
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
use log::{error, warn};
use matrix_sdk::{
deserialized_responses::SyncRoomEvent,
room::Room as MatrixRoom,
ruma::{
api::client::r0::message::get_message_events::Direction,
events::{
room::message::MessageType, AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent,
},
identifiers::EventId,
events::{room::message::MessageType, AnySyncMessageEvent, AnySyncRoomEvent},
identifiers::{EventId, TransactionId},
},
uuid::Uuid,
Error as MatrixError,
};
@ -20,7 +23,7 @@ use crate::{
user::UserExt,
verification::{IdentityVerification, VERIFICATION_CREATION_TIMEOUT},
},
spawn, spawn_tokio,
spawn_tokio,
};
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
@ -40,11 +43,12 @@ impl Default for TimelineState {
}
}
const MAX_BATCH_SIZE: usize = 20;
type BackwardStream =
Pin<Box<dyn Stream<Item = Vec<matrix_sdk::Result<SyncRoomEvent>>> + 'static + Send>>;
mod imp {
use std::{
cell::{Cell, RefCell},
collections::{HashSet, VecDeque},
};
use std::cell::{Cell, RefCell};
use glib::object::WeakRef;
use once_cell::{sync::Lazy, unsync::OnceCell};
@ -62,13 +66,13 @@ mod imp {
pub event_map: RefCell<HashMap<Box<EventId>, Event>>,
/// Maps the temporary `EventId` of the pending Event to the real
/// `EventId`
pub pending_events: RefCell<HashMap<String, Box<EventId>>>,
pub pending_events: RefCell<HashMap<Box<TransactionId>, Box<EventId>>>,
/// A Hashset of `EventId`s that where just redacted.
pub redacted_events: RefCell<HashSet<Box<EventId>>>,
pub oldest_event: RefCell<Option<Box<EventId>>>,
pub state: Cell<TimelineState>,
/// The most recent verification request event
pub verification: RefCell<Option<IdentityVerification>>,
pub backward_stream: Arc<Mutex<Option<BackwardStream>>>,
}
#[glib::object_subclass]
@ -510,8 +514,92 @@ impl Timeline {
}
}
/// Load the timeline
/// This function should also be called to load more events
/// Returns `true` when messages where successfully added
pub async fn load(&self) -> bool {
let priv_ = self.imp();
if matches!(
self.state(),
TimelineState::Loading | TimelineState::Complete
) {
return false;
}
self.set_state(TimelineState::Loading);
self.add_loading_spinner();
let matrix_room = self.room().matrix_room();
let room_weak = self.downgrade().into();
let backward_stream = priv_.backward_stream.clone();
let handle: tokio::task::JoinHandle<matrix_sdk::Result<_>> = spawn_tokio!(async move {
let mut backward_stream_guard = backward_stream.lock().await;
if backward_stream_guard.is_none() {
backward_stream_guard
.replace(create_streams_handler(room_weak, matrix_room).await?);
}
Ok(backward_stream_guard.as_mut().unwrap().next().await)
});
match handle.await.unwrap() {
Ok(Some(events)) => {
let events: Vec<Event> = events
.into_iter()
.filter_map(|event| match event {
Ok(event) => Some(event),
Err(error) => {
error!("Failed to load messages: {}", error);
None
}
})
.map(|event| Event::new(event, &self.room()))
.collect();
self.remove_loading_spinner();
if events.is_empty() {
self.set_state(TimelineState::Error);
return false;
}
self.set_state(TimelineState::Ready);
self.prepend(events);
true
}
Ok(None) => {
self.remove_loading_spinner();
self.set_state(TimelineState::Complete);
false
}
Err(error) => {
error!("Failed to load timeline: {}", error);
self.set_state(TimelineState::Error);
self.remove_loading_spinner();
false
}
}
}
async fn clear(&self) {
let priv_ = self.imp();
// Remove backward stream so that we create new streams
let mut backward_stream = priv_.backward_stream.lock().await;
backward_stream.take();
let length = priv_.list.borrow().len();
priv_.relates_to_events.replace(HashMap::new());
priv_.list.replace(VecDeque::new());
priv_.event_map.replace(HashMap::new());
priv_.pending_events.replace(HashMap::new());
priv_.redacted_events.replace(HashSet::new());
self.notify("empty");
self.upcast_ref::<gio::ListModel>()
.items_changed(0, length as u32, 0);
}
/// Append the new events
// TODO: This should be lazy, for inspiration see: https://blogs.gnome.org/ebassi/documentation/lazy-loading/
pub fn append(&self, batch: Vec<Event>) {
let priv_ = self.imp();
@ -527,12 +615,6 @@ impl Timeline {
// multiple times
list.reserve(batch.len());
if list.is_empty() {
priv_
.oldest_event
.replace(batch.first().as_ref().map(|event| event.matrix_event_id()));
}
list.len()
};
@ -578,7 +660,7 @@ impl Timeline {
}
/// Append an event that wasn't yet fully sent and received via a sync
pub fn append_pending(&self, txn_id: Uuid, event: Event) {
pub fn append_pending(&self, txn_id: &TransactionId, event: Event) {
let priv_ = self.imp();
priv_
@ -589,7 +671,7 @@ impl Timeline {
priv_
.pending_events
.borrow_mut()
.insert(txn_id.to_string(), event.matrix_event_id());
.insert(txn_id.to_owned(), event.matrix_event_id());
let index = {
let mut list = priv_.list.borrow_mut();
@ -634,7 +716,7 @@ impl Timeline {
let handle =
spawn_tokio!(async move { matrix_room.event(event_id_clone.as_ref()).await });
match handle.await.unwrap() {
Ok(room_event) => Ok(Event::new(room_event.event.into(), &room)),
Ok(room_event) => Ok(Event::new(room_event.into(), &room)),
Err(error) => {
// TODO: Retry on connection error?
warn!("Could not fetch event {}: {}", event_id, error);
@ -645,15 +727,10 @@ impl Timeline {
}
/// Prepends a batch of events
// TODO: This should be lazy, see: https://blogs.gnome.org/ebassi/documentation/lazy-loading/
pub fn prepend(&self, batch: Vec<Event>) {
let priv_ = self.imp();
let mut added = batch.len();
priv_
.oldest_event
.replace(batch.last().as_ref().map(|event| event.matrix_event_id()));
{
let mut hidden_events: Vec<Event> = vec![];
// Extend the size of the list so that rust doesn't need to reallocate memory
@ -712,9 +789,6 @@ impl Timeline {
|| (priv_.list.borrow().len() == 1 && self.state() == TimelineState::Loading)
}
fn oldest_event(&self) -> Option<Box<EventId>> {
self.imp().oldest_event.borrow().clone()
}
fn add_loading_spinner(&self) {
self.imp()
.list
@ -728,68 +802,6 @@ impl Timeline {
self.upcast_ref::<gio::ListModel>().items_changed(0, 1, 0);
}
pub fn load_previous_events(&self) {
if matches!(
self.state(),
TimelineState::Loading | TimelineState::Complete
) {
return;
}
self.set_state(TimelineState::Loading);
self.add_loading_spinner();
let matrix_room = self.room().matrix_room();
let last_event = self.oldest_event();
let contains_last_event = last_event.is_some();
let handle = spawn_tokio!(async move {
matrix_room
.messages(last_event.as_deref(), None, 20, Direction::Backward)
.await
});
spawn!(
glib::PRIORITY_LOW,
clone!(@weak self as obj => async move {
obj.remove_loading_spinner();
// FIXME: If the request fails it's automatically restarted because the added events (none), didn't fill the screen.
// We should block the loading for some time before retrying
match handle.await.unwrap() {
Ok(Some(events)) => {
let events: Vec<Event> = if contains_last_event {
events
.into_iter()
.skip(1)
.map(|event| Event::new(event, &obj.room())).collect()
} else {
events
.into_iter()
.map(|event| Event::new(event, &obj.room())).collect()
};
if events.iter().any(|event| matches!(event.matrix_event(), Some(AnySyncRoomEvent::State(AnySyncStateEvent::RoomCreate(_))))) {
obj.set_state(TimelineState::Complete);
} else {
obj.set_state(TimelineState::Ready);
}
obj.prepend(events);
},
Ok(None) => {
error!("The start event wasn't found in the timeline for room {}.", obj.room().room_id());
obj.set_state(TimelineState::Error);
},
Err(error) => {
error!("Couldn't load previous events for room {}: {}", error, obj.room().room_id());
obj.set_state(TimelineState::Error);
}
}
})
);
}
fn set_verification(&self, verification: IdentityVerification) {
self.imp().verification.replace(Some(verification));
self.notify("verification");
@ -899,3 +911,58 @@ impl Timeline {
}
}
}
async fn create_streams_handler(
timeline: glib::SendWeakRef<Timeline>,
matrix_room: MatrixRoom,
) -> matrix_sdk::Result<BackwardStream> {
let (forward_stream, backward_stream) = matrix_room.timeline().await?;
tokio::spawn(async move {
handle_forward_stream(timeline, forward_stream).await;
});
Ok(Box::pin(backward_stream.ready_chunks(MAX_BATCH_SIZE)))
}
async fn handle_forward_stream(
timeline: glib::SendWeakRef<Timeline>,
stream: impl Stream<Item = SyncRoomEvent>,
) {
let stream = stream.ready_chunks(MAX_BATCH_SIZE);
pin_mut!(stream);
while let Some(events) = stream.next().await {
let timeline = timeline.clone();
let (sender, receiver) = futures::channel::oneshot::channel();
let ctx = glib::MainContext::default();
ctx.spawn(async move {
let result = if let Some(timeline) = timeline.upgrade() {
timeline.append(
events
.into_iter()
.map(|event| Event::new(event, &timeline.room()))
.collect(),
);
true
} else {
false
};
sender.send(result).unwrap();
});
if !receiver.await.unwrap() {
break;
}
}
let ctx = glib::MainContext::default();
ctx.spawn(async move {
crate::spawn!(async move {
if let Some(timeline) = timeline.upgrade() {
timeline.clear().await;
};
});
});
}

7
src/utils.rs

@ -69,8 +69,7 @@ use gtk::{
};
use matrix_sdk::{
media::MediaType,
ruma::{EventId, UInt},
uuid::Uuid,
ruma::{EventId, TransactionId, UInt},
};
use mime::Mime;
@ -209,8 +208,8 @@ pub fn filename_for_mime(mime_type: Option<&str>, fallback: Option<mime::Name>)
///
/// Returns a `(transaction_id, event_id)` tuple. The `event_id` is derived from
/// the `transaction_id`.
pub fn pending_event_ids() -> (Uuid, Box<EventId>) {
let txn_id = Uuid::new_v4();
pub fn pending_event_ids() -> (Box<TransactionId>, Box<EventId>) {
let txn_id = TransactionId::new();
let event_id = EventId::parse(format!("${}:fractal.gnome.org", txn_id)).unwrap();
(txn_id, event_id)
}

Loading…
Cancel
Save