diff --git a/Cargo.lock b/Cargo.lock index aefdfeb6..c62cb132 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index c6e78d18..70b57a85 100644 --- a/Cargo.toml +++ b/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", diff --git a/src/login.rs b/src/login.rs index c477ea4c..2ee7f270 100644 --- a/src/login.rs +++ b/src/login.rs @@ -331,7 +331,7 @@ impl Login { self.freeze(); let handle: JoinHandle> = 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(), diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs index aaefc2fc..834dfc86 100644 --- a/src/session/content/room_history/mod.rs +++ b/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, #[template_child] pub stack: TemplateChild, + pub is_loading: Cell, } #[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: >k::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(); } } diff --git a/src/session/mod.rs b/src/session/mod.rs index 96e5fa70..f44a2950 100644 --- a/src/session/mod.rs +++ b/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(), diff --git a/src/session/room/event.rs b/src/session/room/event.rs index 0e123475..1c1e6c3d 100644 --- a/src/session/room/event.rs +++ b/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 { + pub fn matrix_transaction_id(&self) -> Option> { self.imp() .pure_event .borrow() diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs index ff201db9..809a26c9 100644 --- a/src/session/room/mod.rs +++ b/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>, pub matrix_room: RefCell>, @@ -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) { 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) { 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 diff --git a/src/session/room/timeline.rs b/src/session/room/timeline.rs index 899e59e9..282f532a 100644 --- a/src/session/room/timeline.rs +++ b/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>> + '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, Event>>, /// Maps the temporary `EventId` of the pending Event to the real /// `EventId` - pub pending_events: RefCell>>, + pub pending_events: RefCell, Box>>, /// A Hashset of `EventId`s that where just redacted. pub redacted_events: RefCell>>, - pub oldest_event: RefCell>>, pub state: Cell, /// The most recent verification request event pub verification: RefCell>, + pub backward_stream: Arc>>, } #[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> = 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 = 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::() + .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) { 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) { 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 = 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> { - self.imp().oldest_event.borrow().clone() - } fn add_loading_spinner(&self) { self.imp() .list @@ -728,68 +802,6 @@ impl Timeline { self.upcast_ref::().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 = 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, + matrix_room: MatrixRoom, +) -> matrix_sdk::Result { + 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, + stream: impl Stream, +) { + 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; + }; + }); + }); +} diff --git a/src/utils.rs b/src/utils.rs index 1ff7e386..450ea9aa 100644 --- a/src/utils.rs +++ b/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) /// /// Returns a `(transaction_id, event_id)` tuple. The `event_id` is derived from /// the `transaction_id`. -pub fn pending_event_ids() -> (Uuid, Box) { - let txn_id = Uuid::new_v4(); +pub fn pending_event_ids() -> (Box, Box) { + let txn_id = TransactionId::new(); let event_id = EventId::parse(format!("${}:fractal.gnome.org", txn_id)).unwrap(); (txn_id, event_id) }