diff --git a/Cargo.lock b/Cargo.lock index 012e80075..3d587249a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,24 +109,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" -[[package]] -name = "app_dirs2" -version = "2.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e7b35733e3a8c1ccb90385088dd5b6eaa61325cb4d1ad56e683b5224ff352e" -dependencies = [ - "jni", - "ndk-context", - "winapi", - "xdg", -] - -[[package]] -name = "arc-swap" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" - [[package]] name = "arrayref" version = "0.3.6" @@ -661,12 +643,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "cast" version = "0.3.0" @@ -688,12 +664,6 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.0" @@ -806,7 +776,7 @@ dependencies = [ "once_cell", "strsim", "termcolor", - "textwrap 0.16.0", + "textwrap", ] [[package]] @@ -893,16 +863,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "concurrent-queue" version = "2.1.0" @@ -912,19 +872,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "console" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "unicode-width", - "windows-sys 0.42.0", -] - [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1100,31 +1047,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossterm" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio", - "parking_lot 0.12.1", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" -dependencies = [ - "winapi", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -1295,29 +1217,6 @@ dependencies = [ "const-oid", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dialoguer" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3c796f3b0b408d9fd581611b47fa850821fcb84aa640b83a3c1a5be2d691f2" -dependencies = [ - "console", - "shell-words", - "tempfile", - "zeroize", -] - [[package]] name = "digest" version = "0.9.0" @@ -1400,12 +1299,6 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - [[package]] name = "encoding_rs" version = "0.8.32" @@ -2397,55 +2290,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" -[[package]] -name = "jack-in" -version = "0.2.0" -dependencies = [ - "app_dirs2", - "chrono", - "clap 4.1.8", - "dialoguer", - "eyeball", - "eyeball-im", - "eyre", - "futures", - "log4rs", - "matrix-sdk", - "matrix-sdk-common", - "matrix-sdk-sled", - "sanitize-filename-reader-friendly", - "serde_json", - "tokio", - "tracing", - "tracing-flame", - "tracing-subscriber", - "tui-logger", - "tui-realm-stdlib", - "tuirealm", -] - -[[package]] -name = "jni" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bfb8e36ca99b00e6d368320e0822dec9d81db4ccf122f82091f972c90b9985" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "jpeg-decoder" version = "0.1.22" @@ -2522,29 +2366,6 @@ dependencies = [ "log", ] -[[package]] -name = "lazy-regex" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a505da2f89befd87ab425d252795f0f285e100b43e7d22d29528df3d9a576793" -dependencies = [ - "lazy-regex-proc_macros", - "once_cell", - "regex", -] - -[[package]] -name = "lazy-regex-proc_macros" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -2619,12 +2440,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "log-mdc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" - [[package]] name = "log-panics" version = "2.1.0" @@ -2635,24 +2450,6 @@ dependencies = [ "log", ] -[[package]] -name = "log4rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36ca1786d9e79b8193a68d480a0907b612f109537115c6ff655a3a1967533fd" -dependencies = [ - "anyhow", - "arc-swap", - "chrono", - "derivative", - "fnv", - "log", - "log-mdc", - "parking_lot 0.12.1", - "thiserror", - "thread-id", -] - [[package]] name = "mac" version = "0.1.1" @@ -3326,12 +3123,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -4879,12 +4670,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - [[package]] name = "signal-hook" version = "0.3.15" @@ -4895,17 +4680,6 @@ dependencies = [ "signal-hook-registry", ] -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4967,24 +4741,12 @@ dependencies = [ "uuid", ] -[[package]] -name = "slog" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" - [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "smawk" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" - [[package]] name = "socket2" version = "0.4.7" @@ -5152,17 +4914,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" -dependencies = [ - "smawk", - "unicode-linebreak", - "unicode-width", -] - [[package]] name = "textwrap" version = "0.16.0" @@ -5189,17 +4940,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread-id" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f" -dependencies = [ - "libc", - "redox_syscall", - "winapi", -] - [[package]] name = "thread_local" version = "1.1.7" @@ -5594,17 +5334,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-flame" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bae117ee14789185e129aaee5d93750abe67fdc5a9a62650452bfe4e122a3a9" -dependencies = [ - "lazy_static", - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-futures" version = "0.2.5" @@ -5665,70 +5394,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" -[[package]] -name = "tui" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" -dependencies = [ - "bitflags", - "cassowary", - "crossterm", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "tui-logger" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a10e7f291d91032ade13ba20e71d4c4c26daa3fa3edf58b9dd5aa7595240f7" -dependencies = [ - "chrono", - "fxhash", - "lazy_static", - "log", - "parking_lot 0.12.1", - "slog", - "tui", -] - -[[package]] -name = "tui-realm-stdlib" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f252bf8b07c6fd708ddd6349b5f044ae5b488b26929c745728d9c7e2cebfa6" -dependencies = [ - "textwrap 0.15.2", - "tuirealm", - "unicode-width", -] - -[[package]] -name = "tuirealm" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265411b5606f400459af94fbc5aae6a7bc0e98094d08cb5868390c932be88e26" -dependencies = [ - "bitflags", - "crossterm", - "lazy-regex", - "thiserror", - "tui", - "tuirealm_derive", -] - -[[package]] -name = "tuirealm_derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0adcdaf59881626555558eae08f8a53003c8a1961723b4d7a10c51599abbc81" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "typenum" version = "1.16.0" @@ -5762,16 +5427,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" -[[package]] -name = "unicode-linebreak" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" -dependencies = [ - "hashbrown", - "regex", -] - [[package]] name = "unicode-normalization" version = "0.1.22" @@ -6394,15 +6049,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "xdg" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" -dependencies = [ - "dirs", -] - [[package]] name = "xshell" version = "0.1.17" diff --git a/labs/jack-in/Cargo.toml b/labs/jack-in/Cargo.toml deleted file mode 100644 index 096eed58e..000000000 --- a/labs/jack-in/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "jack-in" -publish = false -description = "an experimental sliding sync/syncv3 terminal client to jack into the matrix" -version = "0.2.0" -edition = "2021" - -[features] -file-logging = ["dep:log4rs"] - -[dependencies] -app_dirs2 = "2" -chrono = "0.4.23" -clap = { version = "4.0.29", features = ["derive", "env"] } -dialoguer = "0.10.2" -eyeball = { workspace = true } -eyeball-im = { workspace = true } -eyre = "0.6" -futures = { version = "0.3.1" } -matrix-sdk = { path = "../../crates/matrix-sdk", default-features = false, features = ["e2e-encryption", "anyhow", "native-tls", "sled", "experimental-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" -serde_json = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tracing-flame = "0.2" -tracing-subscriber = "0.3.15" -tui-logger = "0.8.1" -tuirealm = "~1.8" -tui-realm-stdlib = "1.2.0" - -# file-logging specials -tracing = { version = "0.1.35", features = ["log"] } -log4rs = { version = "1.1.1", default-features = false, features = ["file_appender"], optional = true } diff --git a/labs/jack-in/README.md b/labs/jack-in/README.md deleted file mode 100644 index 910680262..000000000 --- a/labs/jack-in/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Jack-in -_your experimental terminal to connect to matrix via sliding sync_ - -A simple example client, using sliding sync to [jack in](https://matrix.fandom.com/wiki/Jacking_in) to matrix via the sliding-sync-proxy. - -## Use: - -You will need a [running sliding sync proxy](https://github.com/matrix-org/sliding-sync/) for now. - -From the roof of this workspace, run jack-in via `cargo run -p jack-in` . Please note that you need to specify the access-token and username, both can be done via environment variables, too. As well as the homeserver (or `http://localhost:8008` will be assumed). See below for how to acquire an access token. - -``` -Your experimental sliding-sync jack into the matrix - -Usage: jack-in [OPTIONS] --user - -Options: - -p, --password - The password of your account. If not given and no database found, it will prompt you for it [env: JACKIN_PASSWORD=] - --fresh - Create a fresh database, drop all existing cache - -l, --log - RUST_LOG log-levels [env: JACKIN_LOG=] [default: jack_in=info,warn] - -u, --user - The userID to log in with [env: JACKIN_USER=] - --store-pass - The password to encrypt the store with [env: JACKIN_STORE_PASSWORD=] - --flames - Activate tracing and write the flamegraph to the specified file - --sliding-sync-proxy - The address of the sliding sync server to connect (probs the proxy) [env: JACKIN_SYNC_PROXY=] [default: http://localhost:8008] - --full-sync-mode - Activate growing window rather than pagination for full-sync [default: paging] [possible values: growing, paging] - --limit - Limit the growing/paging to this number of maximum items to caonsider "done" - --batch-size - define the batch_size per request - --timeline-limit - define the timeline items to load - -h, --help - Print help information -``` - - -### Get the access token -1. In [element.io](https://develop.element.org) navigate to `Settings` -> `Help & About`, under _Advanced_ (on the bottom) you can find your Access token -2. Copy it and set as the `JACKIN_SYNC_TOKEN` environment variable or as `--token` cli-parameter on jack-in run diff --git a/labs/jack-in/src/app/mod.rs b/labs/jack-in/src/app/mod.rs deleted file mode 100644 index 35996bddf..000000000 --- a/labs/jack-in/src/app/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub use super::*; -pub mod model; diff --git a/labs/jack-in/src/app/model.rs b/labs/jack-in/src/app/model.rs deleted file mode 100644 index 2f833ec99..000000000 --- a/labs/jack-in/src/app/model.rs +++ /dev/null @@ -1,230 +0,0 @@ -//! ## Model -//! -//! app model - -use std::time::Duration; - -use futures::executor::block_on; -use matrix_sdk::{ruma::events::room::message::RoomMessageEventContent, Client}; -use tokio::sync::mpsc; -use tracing::warn; -use tuirealm::{ - props::{Alignment, Borders, Color}, - terminal::TerminalBridge, - tui::layout::{Constraint, Direction, Layout}, - Application, AttrValue, Attribute, EventListenerCfg, Sub, SubClause, SubEventClause, Update, -}; - -use super::{ - components::{Details, InputText, Label, Logger, Rooms, StatusBar}, - Id, JackInEvent, MatrixPoller, Msg, -}; -use crate::client::state::SlidingSyncState; - -pub struct Model { - /// Application - pub app: Application, - /// Indicates that the application must quit - pub quit: bool, - /// Tells whether to redraw interface - pub redraw: bool, - /// Used to draw to terminal - pub terminal: TerminalBridge, - /// show the logger console - pub show_logger: bool, - sliding_sync: SlidingSyncState, - tx: mpsc::Sender, - pub client: Client, -} - -impl Model { - pub(crate) fn new( - sliding_sync: SlidingSyncState, - tx: mpsc::Sender, - poller: MatrixPoller, - client: Client, - ) -> Self { - let app = Self::init_app(sliding_sync.clone(), poller); - - Self { - app, - tx, - sliding_sync, - quit: false, - redraw: true, - terminal: TerminalBridge::new().expect("Cannot initialize terminal"), - show_logger: true, - client, - } - } -} - -impl Model { - pub fn set_title(&mut self, title: String) { - assert!(self.app.attr(&Id::Label, Attribute::Text, AttrValue::String(title),).is_ok()); - } - pub fn view(&mut self) { - assert!(self - .terminal - .raw_mut() - .draw(|f| { - let mut areas = vec![ - Constraint::Length(3), // Header - Constraint::Min(10), // body - Constraint::Length(3), // Status Footer - ]; - if self.show_logger { - areas.push( - Constraint::Length(12), // logs - ); - } - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(1) - .constraints(areas) - .split(f.size()); - - // Body - let body_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Length(35), Constraint::Min(23)].as_ref()) - .split(chunks[1]); - - let details_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(10), Constraint::Max(3)].as_ref()) - .split(body_chunks[1]); - - self.app.view(&Id::Rooms, f, body_chunks[0]); - self.app.view(&Id::Details, f, details_chunks[0]); - self.app.view(&Id::TextMessage, f, details_chunks[1]); - self.app.view(&Id::Status, f, chunks[2]); - self.app.view(&Id::Label, f, chunks[0]); - if self.show_logger { - self.app.view(&Id::Logger, f, chunks[3]); - } - }) - .is_ok()); - } - - fn init_app( - sliding_sync: SlidingSyncState, - poller: MatrixPoller, - ) -> Application { - let mut app: Application = Application::init( - EventListenerCfg::default() - .default_input_listener(Duration::from_millis(20)) - .port(Box::new(poller), Duration::from_millis(100)) - .poll_timeout(Duration::from_millis(10)) - .tick_interval(Duration::from_millis(200)), - ); - // Mount components - assert!(app - .mount( - Id::Label, - Box::new( - Label::default() - .text("Loading") - .alignment(Alignment::Center) - .background(Color::Reset) - .borders(Borders::default()) - .foreground(Color::Green), - ), - Vec::default(), - ) - .is_ok()); - // mount logger - assert!(app.remount(Id::Logger, Box::::default(), Vec::default()).is_ok()); - - assert!(app - .mount( - Id::Status, - Box::new(StatusBar::new(sliding_sync.clone())), - vec![Sub::new(SubEventClause::Any, SubClause::Always)] - ) - .is_ok()); - - assert!(app.mount(Id::TextMessage, Box::::default(), vec![]).is_ok()); - assert!(app - .mount( - Id::Rooms, - Box::new( - Rooms::new(sliding_sync.clone()) - .borders(Borders::default().color(Color::Green)) - ), - vec![Sub::new(SubEventClause::Any, SubClause::Always)] - ) - .is_ok()); - - assert!(app - .mount( - Id::Details, - Box::new( - Details::new(sliding_sync).borders(Borders::default().color(Color::Green)) - ), - vec![Sub::new(SubEventClause::Any, SubClause::Always)] - ) - .is_ok()); - // Active letter counter - assert!(app.active(&Id::Rooms).is_ok()); - app - } -} - -// Let's implement Update for model - -impl Update for Model { - fn update(&mut self, msg: Option) -> Option { - if let Some(msg) = msg { - // Set redraw - self.redraw = true; - // Match message - match msg { - Msg::AppClose => { - self.quit = true; // Terminate - None - } - Msg::Clock => None, - Msg::RoomsBlur => { - // Give focus to room details - if self.sliding_sync.has_selected_room() { - let _ = self.app.blur(); - assert!(self.app.active(&Id::Details).is_ok()); - } - None - } - Msg::DetailsBlur => { - // Give focus to room list - let _ = self.app.blur(); - assert!(self.app.active(&Id::TextMessage).is_ok()); - None - } - Msg::TextBlur => { - // Give focus to room list - let _ = self.app.blur(); - assert!(self.app.active(&Id::Rooms).is_ok()); - None - } - Msg::SelectRoom(r) => { - warn!("setting room, sending msg"); - self.sliding_sync.select_room(r); - let _ = self.tx.try_send(self.sliding_sync.clone()); - None - } - Msg::SendMessage(m) => { - if let Some(tl) = &*self.sliding_sync.room_timeline.read() { - block_on(async move { - // fire and forget - tl.send(RoomMessageEventContent::text_plain(m).into(), None).await; - }); - } else { - warn!("asked to send message, but no room is selected"); - } - None - } - } - } else { - None - } - } -} diff --git a/labs/jack-in/src/client/mod.rs b/labs/jack-in/src/client/mod.rs deleted file mode 100644 index 79b180140..000000000 --- a/labs/jack-in/src/client/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -use eyre::{Result, WrapErr}; -use futures::{pin_mut, StreamExt}; -use tokio::sync::mpsc; -use tracing::{error, info, warn}; - -pub mod state; - -use matrix_sdk::{ - ruma::{api::client::error::ErrorKind, OwnedRoomId}, - Client, SlidingSyncListBuilder, SlidingSyncState, -}; - -pub async fn run_client( - client: Client, - tx: mpsc::Sender, - config: crate::SlidingSyncConfig, -) -> Result<()> { - info!("Starting sliding sync now"); - let builder = client.sliding_sync().await; - let mut full_sync_view_builder = SlidingSyncListBuilder::default_with_fullsync() - .timeline_limit(10u32) - .sync_mode(config.full_sync_mode.into()); - if let Some(size) = config.batch_size { - full_sync_view_builder = full_sync_view_builder.full_sync_batch_size(size); - } - - if let Some(limit) = config.limit { - full_sync_view_builder = - full_sync_view_builder.full_sync_maximum_number_of_rooms_to_fetch(limit); - } - if let Some(limit) = config.timeline_limit { - full_sync_view_builder = full_sync_view_builder.timeline_limit(limit); - } - - let full_sync_view = full_sync_view_builder.build()?; - - let syncer = builder - .homeserver(config.proxy.parse().wrap_err("can't parse sync proxy")?) - .add_list(full_sync_view) - .with_common_extensions() - .cold_cache("jack-in-default") - .build() - .await?; - let stream = syncer.stream(); - let view = syncer.list("full-sync").expect("we have the full syncer there").clone(); - let mut ssync_state = state::SlidingSyncState::new(syncer.clone(), view.clone()); - tx.send(ssync_state.clone()).await?; - - info!("starting polling"); - - pin_mut!(stream); - if let Some(Err(e)) = stream.next().await { - error!("Stopped: Initial Query on sliding sync failed: {e:?}"); - return Ok(()); - } - - { - ssync_state.set_first_render_now(); - tx.send(ssync_state.clone()).await?; - } - info!("Done initial sliding sync"); - - loop { - match stream.next().await { - Some(Ok(_)) => { - // we are switching into live updates mode next. ignoring - let state = view.state(); - ssync_state.set_view_state(state.clone()); - - if state == SlidingSyncState::FullyLoaded { - info!("Reached live sync"); - break; - } - let _ = tx.send(ssync_state.clone()).await; - } - Some(Err(e)) => { - if e.client_api_error_kind() != Some(&ErrorKind::UnknownPos) { - error!("Error: {e}"); - break; - } - } - None => { - error!("Never reached live state"); - break; - } - } - } - - { - ssync_state.set_full_sync_now(); - tx.send(ssync_state.clone()).await?; - } - - let mut err_counter = 0; - let mut prev_selected_room: Option = None; - - while let Some(update) = stream.next().await { - { - let selected_room = ssync_state.selected_room.get(); - if let Some(room_id) = selected_room { - if let Some(prev) = &prev_selected_room { - if prev != &room_id { - syncer.unsubscribe(prev.clone()); - syncer.subscribe(room_id.clone(), None); - prev_selected_room = Some(room_id.clone()); - } - } else { - syncer.subscribe(room_id.clone(), None); - prev_selected_room = Some(room_id.clone()); - } - } - } - match update { - Ok(update) => { - info!("Live update received: {update:?}"); - tx.send(ssync_state.clone()).await?; - err_counter = 0; - } - Err(e) => { - warn!("Live update error: {e:?}"); - err_counter += 1; - if err_counter > 3 { - error!("Received 3 errors in a row. stopping."); - break; - } - } - } - } - Ok(()) -} diff --git a/labs/jack-in/src/client/state.rs b/labs/jack-in/src/client/state.rs deleted file mode 100644 index 32ad09253..000000000 --- a/labs/jack-in/src/client/state.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::{ - sync::{Arc, RwLock as StdRwLock}, - time::{Duration, Instant}, -}; - -use eyeball::shared::Observable as SharedObservable; -use eyeball_im::{ObservableVector, VectorDiff}; -use futures::{pin_mut, StreamExt}; -use matrix_sdk::{ - room::timeline::{Timeline, TimelineItem}, - ruma::{OwnedRoomId, RoomId}, - SlidingSync, SlidingSyncList, SlidingSyncRoom, SlidingSyncState as ViewState, -}; -use tokio::task::JoinHandle; - -#[derive(Clone, Default)] -pub struct CurrentRoomSummary { - pub name: String, - pub state_events_counts: Vec<(String, usize)>, - //pub state_events: BTreeMap>, -} - -#[derive(Clone, Debug)] -pub struct SlidingSyncState { - started: Instant, - syncer: SlidingSync, - view: SlidingSyncList, - /// the current list selector for the room - first_render: Option, - full_sync: Option, - current_state: ViewState, - tl_handle: SharedObservable>>, - pub selected_room: SharedObservable>, - pub current_timeline: Arc>>>, - pub room_timeline: SharedObservable>, -} - -impl SlidingSyncState { - pub fn new(syncer: SlidingSync, view: SlidingSyncList) -> Self { - Self { - started: Instant::now(), - syncer, - view, - first_render: None, - full_sync: None, - current_state: ViewState::default(), - tl_handle: Default::default(), - selected_room: Default::default(), - current_timeline: Default::default(), - room_timeline: Default::default(), - } - } - - pub fn started(&self) -> &Instant { - &self.started - } - - pub fn has_selected_room(&self) -> bool { - self.selected_room.read().is_some() - } - - pub fn select_room(&self, r: Option) { - self.current_timeline.write().unwrap().clear(); - if let Some(c) = self.tl_handle.take() { - c.abort(); - } - if let Some(room) = r.as_ref().and_then(|room_id| self.get_room(room_id)) { - let current_timeline = self.current_timeline.clone(); - let room_timeline = self.room_timeline.clone(); - let handle = tokio::spawn(async move { - let timeline = room.timeline().await.unwrap(); - let (items, listener) = timeline.subscribe().await; - room_timeline.set(Some(timeline)); - { - let mut lock = current_timeline.write().unwrap(); - lock.clear(); - lock.append(items.into_iter().collect()); - } - pin_mut!(listener); - while let Some(diff) = listener.next().await { - match diff { - VectorDiff::Append { values } => { - current_timeline.write().unwrap().append(values); - } - VectorDiff::Clear => { - current_timeline.write().unwrap().clear(); - } - VectorDiff::Insert { index, value } => { - current_timeline.write().unwrap().insert(index, value); - } - VectorDiff::PopBack => { - current_timeline.write().unwrap().pop_back(); - } - VectorDiff::PopFront => { - current_timeline.write().unwrap().pop_front(); - } - VectorDiff::PushBack { value } => { - current_timeline.write().unwrap().push_back(value); - } - VectorDiff::PushFront { value } => { - current_timeline.write().unwrap().push_front(value); - } - VectorDiff::Remove { index } => { - current_timeline.write().unwrap().remove(index); - } - VectorDiff::Set { index, value } => { - current_timeline.write().unwrap().set(index, value); - } - VectorDiff::Reset { values } => { - let mut lock = current_timeline.write().unwrap(); - lock.clear(); - lock.append(values); - } - } - } - }); - self.tl_handle.set(Some(handle)); - } - self.selected_room.set(r); - } - - pub fn time_to_first_render(&self) -> Option { - self.first_render - } - - pub fn time_to_full_sync(&self) -> Option { - self.full_sync - } - pub fn current_state(&self) -> &ViewState { - &self.current_state - } - - pub fn loaded_rooms_count(&self) -> usize { - self.syncer.get_number_of_rooms() - } - - pub fn total_rooms_count(&self) -> Option { - self.view.maximum_number_of_rooms() - } - - pub fn set_first_render_now(&mut self) { - self.first_render = Some(self.started.elapsed()) - } - - pub fn view(&self) -> &SlidingSyncList { - &self.view - } - - pub fn get_room(&self, room_id: &RoomId) -> Option { - self.syncer.get_room(room_id) - } - - pub fn get_all_rooms(&self) -> Vec { - self.syncer.get_all_rooms() - } - - pub fn set_full_sync_now(&mut self) { - self.full_sync = Some(self.started.elapsed()) - } - - pub fn set_view_state(&mut self, current_state: ViewState) { - self.current_state = current_state - } -} diff --git a/labs/jack-in/src/components/details.rs b/labs/jack-in/src/components/details.rs deleted file mode 100644 index 351a31c99..000000000 --- a/labs/jack-in/src/components/details.rs +++ /dev/null @@ -1,321 +0,0 @@ -use std::collections::BTreeMap; - -use chrono::{offset::Local, DateTime}; -use matrix_sdk::room::timeline::TimelineItemContent; -use tuirealm::{ - command::{Cmd, CmdResult}, - event::{Key, KeyEvent, KeyModifiers}, - props::{Alignment, Borders, Color, Style}, - tui::{ - layout::{Constraint, Direction, Layout, Rect}, - style::Modifier, - text::Spans, - widgets::{Cell, List, ListItem, ListState, Row, Table, Tabs}, - }, - AttrValue, Attribute, Component, Event, Frame, MockComponent, Props, State, -}; - -use super::{super::client::state::SlidingSyncState, get_block, JackInEvent, Msg}; - -/// ## Details -pub struct Details { - props: Props, - sstate: SlidingSyncState, - liststate: ListState, - name: Option, - state_events_counts: Vec<(String, usize)>, - current_room_timeline: Vec, -} - -impl Details { - pub fn new(sstate: SlidingSyncState) -> Self { - Self { - props: Props::default(), - sstate, - liststate: Default::default(), - name: None, - state_events_counts: Default::default(), - current_room_timeline: Default::default(), - } - } - - pub fn set_sliding_sync(&mut self, sstate: SlidingSyncState) { - self.sstate = sstate; - // we gotta refresh data next time it comes around - self.name = None; - } - - pub fn refresh_data(&mut self) { - let Some(room_id) = self.sstate.selected_room.get() else { return }; - let Some(room_data) = self.sstate.get_room(&room_id) else { - return; - }; - - let name = room_data.name().unwrap_or("unknown").to_owned(); - - let state_events = room_data - .required_state() - .iter() - .filter_map(|r| r.deserialize().ok()) - .fold(BTreeMap::>::new(), |mut b, r| { - let event_name = r.event_type(); - b.entry(event_name.to_string()) - .and_modify(|l| l.push(r.clone())) - .or_insert_with(|| vec![r.clone()]); - b - }); - - let mut state_events_counts: Vec<(String, usize)> = - state_events.iter().map(|(k, l)| (k.clone(), l.len())).collect(); - state_events_counts.sort_by_key(|(_, count)| *count); - - let timeline: Vec = self - .sstate - .current_timeline - .read() - .unwrap() - .iter() - .filter_map(|t| t.as_event()) // we ignore virtual events - .map(|e| match e.content() { - TimelineItemContent::Message(m) => format!( - "[{}] {}: {}", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - m.body() - ), - TimelineItemContent::RedactedMessage => format!( - "[{}] {} - redacted -", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - ), - TimelineItemContent::Sticker(s) => format!( - "[{}] {}: {}", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - s.content().body, - ), - TimelineItemContent::MembershipChange(m) => format!( - "[{}] {} - membership change '{:?}' for {}", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - m.change(), - m.user_id(), - ), - TimelineItemContent::ProfileChange(_) => format!( - "[{}] {} - profile change", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - ), - TimelineItemContent::OtherState(s) => format!( - "[{}] {}: '{}' state event", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - s.content().event_type(), - ), - TimelineItemContent::UnableToDecrypt(_) => format!( - "[{}] {} - unable to decrypt -", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - ), - TimelineItemContent::FailedToParseState { event_type, state_key, error } => { - format!( - "[{}] {} - failed to parse {event_type}({state_key}): {error}", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - ) - } - TimelineItemContent::FailedToParseMessageLike { event_type, error } => format!( - "[{}] {} - failed to parse {event_type}: {error}", - e.timestamp() - .to_system_time() - .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) - .unwrap_or_default(), - e.sender(), - ), - }) - .collect(); - self.current_room_timeline = timeline; - self.name = Some(name); - self.state_events_counts = state_events_counts; - } - - pub fn select_dir(&mut self, count: i32) { - let total = self.current_room_timeline.len() as i32; - let current = self.liststate.selected().unwrap_or_default() as i32; - let next = { - let next = current + count; - if next >= total { - next - total - } else if next < 0 { - total + next - } else { - next - } - }; - self.liststate.select(Some(next.try_into().unwrap_or_default())); - } - - pub fn borders(mut self, b: Borders) -> Self { - self.attr(Attribute::Borders, AttrValue::Borders(b)); - self - } -} - -impl MockComponent for Details { - fn view(&mut self, frame: &mut Frame<'_>, area: Rect) { - if self.name.is_none() { - self.refresh_data(); - } - - let borders = self - .props - .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) - .unwrap_borders(); - - let Some(name) = &self.name else { - // still empty - frame.render_widget( - Table::new(vec![Row::new(vec![Cell::from( - "Choose a room with up/down and press to select", - )])]) - .block(get_block( - borders, - ("".to_owned(), Alignment::Left), - false, - )), - area, - ); - return; - }; - - let areas = vec![ - Constraint::Length(3), // Events - Constraint::Min(10), // Timeline - ]; - - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(1) - .constraints(areas) - .split(area); - - let events_title = ("Events".to_owned(), Alignment::Left); - - let events_borders = self - .props - .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) - .unwrap_borders(); - - let mut tabs = vec![]; - - for (title, count) in &self.state_events_counts { - tabs.push(Spans::from(format!("{title}: {count}"))); - } - - frame.render_widget( - Tabs::new(tabs) - .style(Style::default().fg(Color::LightCyan)) - .block(get_block(events_borders, events_title, false)) - .style(Style::default().fg(Color::White).bg(Color::Black)), - chunks[0], - ); - - let title = (name.to_owned(), Alignment::Left); - let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); - - let details: Vec<_> = - self.current_room_timeline.iter().map(|e| ListItem::new(e.clone())).collect(); - - frame.render_stateful_widget( - List::new(details) - .style(Style::default().fg(Color::White)) - .highlight_style( - Style::default().fg(Color::LightCyan).add_modifier(Modifier::ITALIC), - ) - .highlight_symbol(">>") - .block(get_block(borders, title, focus)), - chunks[1], - &mut self.liststate, - ); - } - - fn query(&self, attr: Attribute) -> Option { - self.props.get(attr) - } - - fn attr(&mut self, attr: Attribute, value: AttrValue) { - self.props.set(attr, value); - } - - fn state(&self) -> State { - State::None - } - - fn perform(&mut self, _: Cmd) -> CmdResult { - CmdResult::None - } -} - -impl Component for Details { - fn on(&mut self, ev: Event) -> Option { - let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); - if focus { - // we only care about user input if we are in focus. - match ev { - Event::Keyboard(KeyEvent { code: Key::Down, modifiers: KeyModifiers::NONE }) => { - self.select_dir(1); - return None; - } - Event::Keyboard(KeyEvent { code: Key::Down, modifiers: KeyModifiers::SHIFT }) => { - self.select_dir(10); - return None; - } - Event::Keyboard(KeyEvent { code: Key::Up, modifiers: KeyModifiers::NONE }) => { - self.select_dir(-1); - return None; - } - Event::Keyboard(KeyEvent { code: Key::Up, modifiers: KeyModifiers::SHIFT }) => { - self.select_dir(-10); - return None; - } - Event::Keyboard(KeyEvent { code: Key::Tab, modifiers: KeyModifiers::NONE }) => { - return Some(Msg::DetailsBlur) - } // Return focus lost - Event::Keyboard(KeyEvent { code: Key::Esc, modifiers: KeyModifiers::NONE }) => { - return Some(Msg::AppClose) - } - _ => {} - } - } - - if let Event::User(JackInEvent::SyncUpdate(s)) = ev { - self.set_sliding_sync(s); - } - - None - } -} diff --git a/labs/jack-in/src/components/input.rs b/labs/jack-in/src/components/input.rs deleted file mode 100644 index efb4af669..000000000 --- a/labs/jack-in/src/components/input.rs +++ /dev/null @@ -1,70 +0,0 @@ -use tui_realm_stdlib::{states::InputStates, Input}; -use tuirealm::{ - command::{Cmd, Direction, Position}, - event::{Key, KeyEvent, KeyModifiers}, - props::{Alignment, BorderType, Borders, Color, InputType, Style}, - Component, Event, MockComponent, -}; - -use super::{JackInEvent, Msg}; - -#[derive(MockComponent)] -pub struct InputText { - component: Input, -} - -impl Default for InputText { - fn default() -> Self { - Self { - component: Input::default() - .borders( - Borders::default().modifiers(BorderType::Rounded).color(Color::LightYellow), - ) - .foreground(Color::LightYellow) - .input_type(InputType::Text) - .title("Send a message", Alignment::Left) - .invalid_style(Style::default().fg(Color::Red)), - } - } -} - -impl Component for InputText { - fn on(&mut self, ev: Event) -> Option { - match ev { - Event::Keyboard(KeyEvent { code: Key::Left, .. }) => { - self.perform(Cmd::Move(Direction::Left)); - } - Event::Keyboard(KeyEvent { code: Key::Right, .. }) => { - self.perform(Cmd::Move(Direction::Right)); - } - Event::Keyboard(KeyEvent { code: Key::Home, .. }) => { - self.perform(Cmd::GoTo(Position::Begin)); - } - Event::Keyboard(KeyEvent { code: Key::End, .. }) => { - self.perform(Cmd::GoTo(Position::End)); - } - Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => { - self.perform(Cmd::Cancel); - } - Event::Keyboard(KeyEvent { code: Key::Backspace, .. }) => { - self.perform(Cmd::Delete); - } - Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE }) => { - self.perform(Cmd::Type(ch)); - } - Event::Keyboard(KeyEvent { code: Key::Enter, .. }) => { - let input = self.component.states.get_value(); - self.component.states = InputStates::default(); - return Some(Msg::SendMessage(input)); - } - Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => { - return Some(Msg::TextBlur); - } - Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { - return Some(Msg::AppClose); - } - _ => {} - } - None - } -} diff --git a/labs/jack-in/src/components/label.rs b/labs/jack-in/src/components/label.rs deleted file mode 100644 index 167be6c47..000000000 --- a/labs/jack-in/src/components/label.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! ## Label -//! -//! label component - -use tuirealm::{ - command::{Cmd, CmdResult}, - props::{Alignment, Borders, Color, Style, TextModifiers}, - tui::{ - layout::Rect, - widgets::{Block, Paragraph}, - }, - AttrValue, Attribute, Component, Event, Frame, MockComponent, Props, State, -}; - -use super::{JackInEvent, Msg}; - -/// ## Label -/// -/// Simple label component; just renders a text -/// NOTE: since I need just one label, I'm not going to use different object; I -/// will directly implement Component for Label. This is not ideal actually and -/// in a real app you should differentiate Mock Components from Application -/// Components. -#[derive(Default)] -pub struct Label { - props: Props, -} - -impl Label { - pub fn text(mut self, s: S) -> Self - where - S: AsRef, - { - self.attr(Attribute::Text, AttrValue::String(s.as_ref().to_owned())); - self - } - - pub fn alignment(mut self, a: Alignment) -> Self { - self.attr(Attribute::TextAlign, AttrValue::Alignment(a)); - self - } - - pub fn foreground(mut self, c: Color) -> Self { - self.attr(Attribute::Foreground, AttrValue::Color(c)); - self - } - - pub fn background(mut self, c: Color) -> Self { - self.attr(Attribute::Background, AttrValue::Color(c)); - self - } - - pub fn borders(mut self, b: Borders) -> Self { - self.attr(Attribute::Borders, AttrValue::Borders(b)); - self - } -} - -impl MockComponent for Label { - fn view(&mut self, frame: &mut Frame<'_>, area: Rect) { - // Check if visible - if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) { - // Get properties - let text = self - .props - .get_or(Attribute::Text, AttrValue::String(String::default())) - .unwrap_string(); - let alignment = self - .props - .get_or(Attribute::TextAlign, AttrValue::Alignment(Alignment::Left)) - .unwrap_alignment(); - let foreground = self - .props - .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset)) - .unwrap_color(); - let background = self - .props - .get_or(Attribute::Background, AttrValue::Color(Color::Reset)) - .unwrap_color(); - let modifiers = self - .props - .get_or(Attribute::TextProps, AttrValue::TextModifiers(TextModifiers::empty())) - .unwrap_text_modifiers(); - - let borders = self - .props - .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) - .unwrap_borders(); - - frame.render_widget( - Paragraph::new(text) - .block(Block::default().borders(borders.sides).border_style(borders.style())) - .style(Style::default().fg(foreground).bg(background).add_modifier(modifiers)) - .alignment(alignment), - area, - ); - } - } - - fn query(&self, attr: Attribute) -> Option { - self.props.get(attr) - } - - fn attr(&mut self, attr: Attribute, value: AttrValue) { - self.props.set(attr, value); - } - - fn state(&self) -> State { - State::None - } - - fn perform(&mut self, _: Cmd) -> CmdResult { - CmdResult::None - } -} - -impl Component for Label { - fn on(&mut self, _: Event) -> Option { - // Does nothing - None - } -} diff --git a/labs/jack-in/src/components/logger.rs b/labs/jack-in/src/components/logger.rs deleted file mode 100644 index f9950abd1..000000000 --- a/labs/jack-in/src/components/logger.rs +++ /dev/null @@ -1,67 +0,0 @@ -use tui_logger::TuiLoggerWidget; -use tuirealm::{ - command::{Cmd, CmdResult}, - props::{Alignment, Borders, Color, Style}, - tui::layout::Rect, - AttrValue, Attribute, Component, Event, Frame, MockComponent, Props, State, -}; - -use super::{get_block, JackInEvent, Msg}; - -/// ## Logger -/// -/// Simple label component; just renders a text -/// NOTE: since I need just one label, I'm not going to use different object; I -/// will directly implement Component for Logger. This is not ideal actually and -/// in a real app you should differentiate Mock Components from Application -/// Components. -#[derive(Default)] -pub struct Logger { - props: Props, -} - -impl MockComponent for Logger { - fn view(&mut self, frame: &mut Frame<'_>, area: Rect) { - let title = ("Logs".to_owned(), Alignment::Center); - - let borders = self - .props - .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) - .unwrap_borders(); - let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); - frame.render_widget( - TuiLoggerWidget::default() - .style_error(Style::default().fg(Color::Red)) - .style_debug(Style::default().fg(Color::Green)) - .style_warn(Style::default().fg(Color::Yellow)) - .style_trace(Style::default().fg(Color::Gray)) - .style_info(Style::default().fg(Color::Blue)) - .block(get_block(borders, title, focus)) - .style(Style::default().fg(Color::White).bg(Color::Black)), - area, - ); - } - - fn query(&self, attr: Attribute) -> Option { - self.props.get(attr) - } - - fn attr(&mut self, attr: Attribute, value: AttrValue) { - self.props.set(attr, value); - } - - fn state(&self) -> State { - State::None - } - - fn perform(&mut self, _: Cmd) -> CmdResult { - CmdResult::None - } -} - -impl Component for Logger { - fn on(&mut self, _: Event) -> Option { - // Does nothing - None - } -} diff --git a/labs/jack-in/src/components/mod.rs b/labs/jack-in/src/components/mod.rs deleted file mode 100644 index c966b4a7e..000000000 --- a/labs/jack-in/src/components/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! ## Components -//! -//! demo example components - -use tuirealm::{ - props::{Alignment, Borders, Color, Style}, - tui::widgets::Block, -}; - -use super::{JackInEvent, Msg}; - -// -- modules -mod details; -mod input; -mod label; -mod logger; -mod rooms; -mod statusbar; - -// -- export -pub use details::Details; -pub use input::InputText; -pub use label::Label; -pub use logger::Logger; -pub use rooms::Rooms; -pub use statusbar::StatusBar; - -/// ### get_block -/// -/// Get block -pub(crate) fn get_block<'a>(props: Borders, title: (String, Alignment), focus: bool) -> Block<'a> { - Block::default() - .borders(props.sides) - .border_style(match focus { - true => props.style(), - false => Style::default().fg(Color::Reset).bg(Color::Reset), - }) - .border_type(props.modifiers) - .title(title.0) - .title_alignment(title.1) -} diff --git a/labs/jack-in/src/components/rooms.rs b/labs/jack-in/src/components/rooms.rs deleted file mode 100644 index f6cd780cd..000000000 --- a/labs/jack-in/src/components/rooms.rs +++ /dev/null @@ -1,155 +0,0 @@ -use tracing::warn; -use tuirealm::{ - command::{Cmd, CmdResult}, - event::{Key, KeyEvent, KeyModifiers}, - props::{Alignment, Borders, Color, Style}, - tui::{ - layout::{Constraint, Rect}, - style::Modifier, - widgets::{Cell, Row, Table, TableState}, - }, - AttrValue, Attribute, Component, Event, Frame, MockComponent, Props, State, -}; - -use super::{super::client::state::SlidingSyncState, get_block, JackInEvent, Msg}; - -/// ## Rooms -pub struct Rooms { - props: Props, - sstate: SlidingSyncState, - tablestate: TableState, -} - -impl Rooms { - pub fn new(sstate: SlidingSyncState) -> Self { - Self { props: Props::default(), sstate, tablestate: Default::default() } - } - - pub fn set_sliding_sync(&mut self, sstate: SlidingSyncState) { - self.sstate = sstate; - } - - pub fn select_dir(&mut self, count: i32) { - let rooms_count = self.sstate.loaded_rooms_count() as i32; - let current = self.tablestate.selected().unwrap_or_default() as i32; - let next = { - let next = current + count; - if next >= rooms_count { - next - rooms_count - } else if next < 0 { - rooms_count + next - } else { - next - } - }; - self.tablestate.select(Some(next.try_into().unwrap_or_default())); - } - - pub fn borders(mut self, b: Borders) -> Self { - self.attr(Attribute::Borders, AttrValue::Borders(b)); - self - } -} - -impl MockComponent for Rooms { - fn view(&mut self, frame: &mut Frame<'_>, area: Rect) { - let title = ("Rooms".to_owned(), Alignment::Center); - - let borders = self - .props - .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) - .unwrap_borders(); - let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); - - let mut paras = vec![]; - - for r in self.sstate.get_all_rooms() { - let mut cells = vec![Cell::from(r.name().unwrap_or("unknown").to_owned())]; - if let Some(c) = r.unread_notifications().notification_count { - let count: u32 = c.try_into().unwrap_or_default(); - if count > 0 { - cells.push(Cell::from(c.to_string())) - } - } - paras.push(Row::new(cells)); - } - - frame.render_stateful_widget( - Table::new(paras) - .style(Style::default().fg(Color::White)) - .highlight_style( - Style::default().fg(Color::LightCyan).add_modifier(Modifier::ITALIC), - ) - .highlight_symbol(">>") - .widths(&[Constraint::Min(30), Constraint::Max(4)]) - .block(get_block(borders, title, focus)), - area, - &mut self.tablestate, - ); - } - - fn query(&self, attr: Attribute) -> Option { - self.props.get(attr) - } - - fn attr(&mut self, attr: Attribute, value: AttrValue) { - self.props.set(attr, value); - } - - fn state(&self) -> State { - State::None - } - - fn perform(&mut self, _: Cmd) -> CmdResult { - CmdResult::None - } -} - -impl Component for Rooms { - fn on(&mut self, ev: Event) -> Option { - let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); - if focus { - // we only care about user input if we are in focus. - match ev { - Event::Keyboard(KeyEvent { code: Key::Down, modifiers: KeyModifiers::NONE }) => { - self.select_dir(1); - return None; - } - Event::Keyboard(KeyEvent { code: Key::Down, modifiers: KeyModifiers::SHIFT }) => { - self.select_dir(10); - return None; - } - Event::Keyboard(KeyEvent { code: Key::Up, modifiers: KeyModifiers::NONE }) => { - self.select_dir(-1); - return None; - } - Event::Keyboard(KeyEvent { code: Key::Up, modifiers: KeyModifiers::SHIFT }) => { - self.select_dir(-10); - return None; - } - Event::Keyboard(KeyEvent { code: Key::Enter, modifiers: KeyModifiers::NONE }) => { - if let Some(idx) = self.tablestate.selected() { - if let Some(room_id) = self.sstate.view().get_room_id(idx) { - warn!("selecting, {:?}", room_id); - return Some(Msg::SelectRoom(Some(room_id))); - } - } - return None; - } - Event::Keyboard(KeyEvent { code: Key::Tab, modifiers: KeyModifiers::NONE }) => { - return Some(Msg::RoomsBlur) - } // Return focus lost - Event::Keyboard(KeyEvent { code: Key::Esc, modifiers: KeyModifiers::NONE }) => { - return Some(Msg::AppClose) - } - _ => {} - } - } - - if let Event::User(JackInEvent::SyncUpdate(s)) = ev { - self.set_sliding_sync(s); - } - - None - } -} diff --git a/labs/jack-in/src/components/statusbar.rs b/labs/jack-in/src/components/statusbar.rs deleted file mode 100644 index 141d58407..000000000 --- a/labs/jack-in/src/components/statusbar.rs +++ /dev/null @@ -1,100 +0,0 @@ -use tuirealm::{ - command::{Cmd, CmdResult}, - props::{Alignment, Borders, Color, Style}, - tui::{layout::Rect, text::Spans, widgets::Tabs}, - AttrValue, Attribute, Component, Event, Frame, MockComponent, Props, State, -}; - -use super::{super::client::state::SlidingSyncState, get_block, JackInEvent, Msg}; - -/// ## StatusBar -pub struct StatusBar { - props: Props, - sstate: SlidingSyncState, -} - -impl StatusBar { - pub fn new(sstate: SlidingSyncState) -> Self { - Self { props: Props::default(), sstate } - } - pub fn set_sliding_sync(&mut self, sstate: SlidingSyncState) { - self.sstate = sstate; - } -} - -impl MockComponent for StatusBar { - fn view(&mut self, frame: &mut Frame<'_>, area: Rect) { - let title = ("Status".to_owned(), Alignment::Left); - - let borders = self - .props - .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) - .unwrap_borders(); - - let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); - - let tabs = { - let mut tabs = - vec![Spans::from(format!("Current state: {:?}", self.sstate.current_state()))]; - if let Some(dur) = self.sstate.time_to_first_render() { - tabs.push(Spans::from(format!("First view: {}ms", dur.as_millis()))); - - if let Some(dur) = self.sstate.time_to_full_sync() { - tabs.push(Spans::from(format!("Full sync: {}ms", dur.as_millis()))); - if let Some(count) = self.sstate.total_rooms_count() { - tabs.push(Spans::from(format!("{count} rooms"))); - } - } else { - tabs.push(Spans::from(format!( - "Loaded {} rooms in {}s", - self.sstate.loaded_rooms_count(), - self.sstate.started().elapsed().as_secs() - ))); - } - } else { - tabs.push(Spans::from(format!( - "loading for {}s", - self.sstate.started().elapsed().as_secs() - ))); - } - tabs - }; - - frame.render_widget( - Tabs::new(tabs) - .style(Style::default().fg(Color::LightCyan)) - .block(get_block(borders, title, focus)) - .style(Style::default().fg(Color::White).bg(Color::Black)), - area, - ); - } - - fn query(&self, attr: Attribute) -> Option { - self.props.get(attr) - } - - fn attr(&mut self, attr: Attribute, value: AttrValue) { - self.props.set(attr, value); - } - - fn state(&self) -> State { - State::None - } - - fn perform(&mut self, _: Cmd) -> CmdResult { - CmdResult::None - } -} - -impl Component for StatusBar { - fn on(&mut self, ev: Event) -> Option { - if let Event::User(JackInEvent::SyncUpdate(s)) = ev { - self.set_sliding_sync(s); - None - } else if let Event::Tick = ev { - Some(Msg::Clock) - } else { - None - } - } -} diff --git a/labs/jack-in/src/config.rs b/labs/jack-in/src/config.rs deleted file mode 100644 index 0dec9715a..000000000 --- a/labs/jack-in/src/config.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::path::PathBuf; - -use clap::{Args, Parser, ValueEnum}; -use matrix_sdk::SlidingSyncMode; - -#[derive(Debug, Parser)] -#[command(name = "jack-in", about = "Your experimental sliding-sync jack into the matrix")] -pub struct Opt { - /// The password of your account. If not given and no database found, it - /// will prompt you for it - #[arg(short, long, env = "JACKIN_PASSWORD")] - pub password: Option, - - /// Create a fresh database, drop all existing cache - #[arg(long)] - pub fresh: bool, - - /// RUST_LOG log-levels - #[arg(short, long, env = "JACKIN_LOG", default_value = "jack_in=info,warn")] - pub log: String, - - /// The userID to log in with - #[arg(short, long, env = "JACKIN_USER")] - pub user: String, - - /// The password to encrypt the store with - #[arg(long, env = "JACKIN_STORE_PASSWORD")] - pub store_pass: Option, - - #[arg(long)] - /// Activate tracing and write the flamegraph to the specified file - pub flames: Option, - - #[command(flatten)] - /// Sliding Sync configuration flags - pub sliding_sync: SlidingSyncConfig, -} - -#[derive(Debug, Clone, ValueEnum)] -#[value(rename_all = "lower")] -pub enum FullSyncMode { - Growing, - Paging, -} - -impl From for SlidingSyncMode { - fn from(val: FullSyncMode) -> Self { - match val { - FullSyncMode::Growing => SlidingSyncMode::GrowingFullSync, - FullSyncMode::Paging => SlidingSyncMode::PagingFullSync, - } - } -} - -#[derive(Debug, Args)] -pub struct SlidingSyncConfig { - /// The address of the sliding sync server to connect (probs the proxy) - #[arg( - long = "sliding-sync-proxy", - default_value = "http://localhost:8008", - env = "JACKIN_SYNC_PROXY" - )] - pub proxy: String, - - /// Activate growing window rather than pagination for full-sync - #[arg(long, default_value = "paging")] - pub full_sync_mode: FullSyncMode, - - /// Limit the growing/paging to this number of maximum items to caonsider - /// "done" - #[arg(long)] - pub limit: Option, - - /// define the batch_size per request - #[arg(long)] - pub batch_size: Option, - - /// define the timeline items to load - #[arg(long)] - pub timeline_limit: Option, -} diff --git a/labs/jack-in/src/main.rs b/labs/jack-in/src/main.rs deleted file mode 100644 index cd97b2750..000000000 --- a/labs/jack-in/src/main.rs +++ /dev/null @@ -1,298 +0,0 @@ -//! ## Jack-in -//! -//! a demonstration and debugging implementation TUI client for sliding sync - -use std::{path::Path, time::Duration}; - -use app_dirs2::{app_root, AppDataType, AppInfo}; -use clap::Parser; -use dialoguer::{theme::ColorfulTheme, Password}; -use eyeball_im::VectorDiff; -use eyre::{eyre, Result}; -use matrix_sdk::{ - config::RequestConfig, - room::timeline::TimelineItem, - ruma::{OwnedRoomId, OwnedUserId}, - Client, -}; -use matrix_sdk_sled::make_store_config; -use sanitize_filename_reader_friendly::sanitize; -use tracing::{error, info, log}; -use tracing_flame::FlameLayer; -use tracing_subscriber::prelude::*; -use tuirealm::{application::PollStrategy, Event, Update}; - -const APP_INFO: AppInfo = AppInfo { name: "jack-in", author: "Matrix-Rust-SDK Core Team" }; - -// -- internal -mod app; -mod client; -mod components; -mod config; -use app::model::Model; -use config::{Opt, SlidingSyncConfig}; -use tokio::sync::mpsc; - -// Let's define the messages handled by our app. NOTE: it must derive -// `PartialEq` -#[derive(PartialEq, Eq)] -pub enum Msg { - AppClose, - Clock, - RoomsBlur, - DetailsBlur, - TextBlur, - SelectRoom(Option), - SendMessage(String), -} - -#[allow(clippy::large_enum_variant)] -#[derive(Clone)] -pub enum JackInEvent { - Any, // match all - SyncUpdate(client::state::SlidingSyncState), - RoomDataUpdate(VectorDiff), -} - -impl PartialOrd for JackInEvent { - fn partial_cmp(&self, _other: &Self) -> Option { - None - } -} - -impl Eq for JackInEvent {} - -impl PartialEq for JackInEvent { - fn eq(&self, _other: &JackInEvent) -> bool { - false - } -} - -// Let's define the component ids for our application -#[derive(Debug, Eq, PartialEq, PartialOrd, Clone, Hash)] -pub enum Id { - Clock, - DigitCounter, - LetterCounter, - Label, - TextMessage, - Logger, - Status, - Rooms, - Details, -} - -pub(crate) struct MatrixPoller(mpsc::Receiver); - -impl tuirealm::listener::Poll for MatrixPoller { - fn poll(&mut self) -> tuirealm::listener::ListenerResult>> { - match self.0.try_recv() { - Ok(v) => Ok(Some(Event::User(JackInEvent::SyncUpdate(v)))), - Err(mpsc::error::TryRecvError::Empty) => Ok(None), - _ => Err(tuirealm::listener::ListenerError::ListenerDied), - } - } -} - -fn setup_flames(path: &Path) -> impl Drop { - let (flame_layer, _guard) = FlameLayer::with_file(path).expect("Couldn't write flamegraph"); - - tracing_subscriber::registry().with(flame_layer).init(); - _guard -} - -#[tokio::main] -async fn main() -> Result<()> { - let opt = Opt::parse(); - - let user_id: OwnedUserId = opt.user.clone().parse()?; - - if let Some(ref p) = opt.flames { - setup_flames(p.as_path()); - } else { - // Configure log - - //tracing_subscriber::fmt::init(); - #[cfg(feature = "file-logging")] - { - use log4rs::{ - append::file::FileAppender, - config::{Appender, Config, Logger, Root}, - encode::pattern::PatternEncoder, - }; - - let file = FileAppender::builder() - .encoder(Box::new(PatternEncoder::default())) - .build("jack-in.log") - .unwrap(); - - let config = Config::builder() - .appender(Appender::builder().build("file", Box::new(file))) - .logger( - Logger::builder() - .appender("file") - .build("matrix_sdk::sliding_sync", log::LevelFilter::Trace), - ) - .logger( - Logger::builder() - .appender("file") - .build("matrix_sdk::http_client", log::LevelFilter::Debug), - ) - .logger( - Logger::builder() - .appender("file") - .build("matrix_sdk_base::sliding_sync", log::LevelFilter::Debug), - ) - .logger( - Logger::builder().appender("file").build("reqwest", log::LevelFilter::Trace), - ) - .logger( - Logger::builder().appender("file").build("matrix_sdk", log::LevelFilter::Warn), - ) - .build(Root::builder().build(log::LevelFilter::Error)) - .unwrap(); - - log4rs::init_config(config).expect("Logging with log4rs failed to initialize"); - } - #[cfg(not(feature = "file-logging"))] - { - tui_logger::init_logger(log::LevelFilter::Trace).unwrap(); - // Set default level for unknown targets to Trace - tui_logger::set_default_level(log::LevelFilter::Warn); - - for pair in opt.log.split(',') { - if let Some((name, lvl)) = pair.split_once('=') { - let level = match lvl.to_lowercase().as_str() { - "trace" => log::LevelFilter::Trace, - "debug" => log::LevelFilter::Debug, - "info" => log::LevelFilter::Info, - "warn" => log::LevelFilter::Warn, - "error" => log::LevelFilter::Error, - // nothing means error - _ => continue, - }; - tui_logger::set_level_for_target(name, level); - } else { - let level = match pair.to_lowercase().as_str() { - "trace" => log::LevelFilter::Trace, - "debug" => log::LevelFilter::Debug, - "info" => log::LevelFilter::Info, - "warn" => log::LevelFilter::Warn, - "error" => log::LevelFilter::Error, - // nothing means error - _ => continue, - }; - tui_logger::set_default_level(level); - } - } - } - } - - let data_path = app_root(AppDataType::UserData, &APP_INFO)?.join(sanitize(user_id.as_str())); - if opt.fresh { - // drop the database first; - std::fs::remove_dir_all(&data_path)?; - } - std::fs::create_dir_all(&data_path)?; - let store_config = make_store_config(&data_path, opt.store_pass.as_deref()).await?; - let request_config = RequestConfig::default().timeout(Duration::from_secs(90)); - - let client = Client::builder() - .user_agent("jack-in") - .server_name(user_id.server_name()) - .request_config(request_config) - .store_config(store_config) - .build() - .await?; - - let session_key = b"jackin::session_token"; - - if let Some(session) = client - .store() - .get_custom_value(session_key) - .await? - .map(|v| serde_json::from_slice(&v)) - .transpose()? - { - info!("Restoring session from store"); - client.restore_session(session).await?; - } else { - let theme = ColorfulTheme::default(); - let password = match opt.password { - Some(ref pw) => pw.clone(), - _ => Password::with_theme(&theme) - .with_prompt(format!("Password for {user_id:} :")) - .interact()?, - }; - client.login_username(&user_id, &password).await?; - } - - if let Some(session) = client.session() { - client.store().set_custom_value(session_key, serde_json::to_vec(&session)?).await?; - } - - let sliding_client = client.clone(); - - let (tx, mut rx) = mpsc::channel(100); - let model_tx = tx.clone(); - let sliding_sync_proxy = opt.sliding_sync.proxy.clone(); - - tokio::spawn(async move { - if let Err(e) = client::run_client(sliding_client, tx, opt.sliding_sync).await { - error!("Running the client failed: {:#?}", e); - } - }); - - let start_sync = - rx.recv().await.ok_or_else(|| eyre!("failure getting the sliding sync state"))?; - // ensure client still works as normal: fetch user info: - let display_name = client - .account() - .get_display_name() - .await? - .map(|s| format!("{s} ({user_id})")) - .unwrap_or_else(|| format!("{user_id}")); - let poller = MatrixPoller(rx); - let mut model = Model::new(start_sync, model_tx, poller, client); - model.set_title(format!("{display_name} via {sliding_sync_proxy}")); - run_ui(model).await; - - Ok(()) -} - -async fn run_ui(mut model: Model) { - // Enter alternate screen - let _ = model.terminal.enter_alternate_screen(); - let _ = model.terminal.enable_raw_mode(); - // Main loop - // NOTE: loop until quit; quit is set in update if AppClose is received from - // counter - while !model.quit { - // Tick - match model.app.tick(PollStrategy::Once) { - Err(err) => { - model.set_title(format!("Application error: {err}")); - } - Ok(messages) if !messages.is_empty() => { - // NOTE: redraw if at least one msg has been processed - model.redraw = true; - for msg in messages.into_iter() { - let mut msg = Some(msg); - while msg.is_some() { - msg = model.update(msg); - } - } - } - _ => {} - } - // Redraw - if model.redraw { - model.view(); - model.redraw = false; - } - } - // Terminate terminal - let _ = model.terminal.leave_alternate_screen(); - let _ = model.terminal.disable_raw_mode(); - let _ = model.terminal.clear_screen(); -} diff --git a/tarpaulin.toml b/tarpaulin.toml index 89b44e20d..9af3af97c 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -21,8 +21,6 @@ exclude = [ # testing "matrix-sdk-test", "matrix-sdk-test-macros", - # labs - "jack-in", # repo automation (ci, codegen) "uniffi-bindgen", "xtask",