diff --git a/deltachat-repl/Cargo.toml b/deltachat-repl/Cargo.toml index 962d1a8b7..6536c7828 100644 --- a/deltachat-repl/Cargo.toml +++ b/deltachat-repl/Cargo.toml @@ -7,6 +7,7 @@ repository = "https://github.com/deltachat/deltachat-core-rust" [dependencies] anyhow = { workspace = true } +console-subscriber = "0.4.0" deltachat = { workspace = true, features = ["internals"]} dirs = "5" log = { workspace = true } @@ -15,6 +16,7 @@ qr2term = "0.3.3" rusqlite = { workspace = true } rustyline = "14" tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] } +tracing = "0.1.40" tracing-subscriber = { workspace = true, features = ["env-filter"] } [features] diff --git a/deltachat-repl/README.md b/deltachat-repl/README.md new file mode 100644 index 000000000..213554fd3 --- /dev/null +++ b/deltachat-repl/README.md @@ -0,0 +1,60 @@ +# Delta Chat REPL + +This is a simple [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) frontend build on top of delta chat core. +It's purpose is to help with quick testing during development, it is not meant for end users. + +Dependencies: +- if you want to use `getqr` you need `qrencode` (macOS: `brew install qrencode`) + +## Usage + +``` +cargo run +``` + +Type in `help` to learn about what comands are available. + +## Usage with `tokio-console` + +Tokio is the async runtime that delta chat core uses. +Core uses tokio tasks, which is something similar to a thread. +`tokio-console` is like a task manager for these tokio-tasks. + +Examples of tasks: +- The event loop in the repl tool which processes events received from core +- The repl loop itself which waits for and executes user commands +- The imap task that manages imap connection in core + +``` +RUSTFLAGS="--cfg tokio_unstable" cargo run +``` + +Then in a new console window start [`tokio-console`](https://github.com/tokio-rs/console). +You can install it via `cargo install tokio-console`. + +### Quick Example + +An example session in the repl tool. + +``` +RUSTFLAGS="--cfg tokio_unstable" cargo run test-db/db +setqr dcaccount:https://nine.testrun.org/new +configure +connect +listchats +getqr +``` + +If it crashes you can just start it again and use the openpgp4fpr url instead of scanning the code from the terminal. +Or install `qrencode` to fix the crash and run `getqr` again. + +Use the qrcode/openpgp4fpr link to setup the contact on deltachat. +Then write a message to that new contact, after that we can accept the chat in the repl tool and send a reply: + +``` +listchats +accept 12 +chat 12 +send hi! +chat +``` \ No newline at end of file diff --git a/deltachat-repl/src/main.rs b/deltachat-repl/src/main.rs index 2b16cc68f..7e327d4f3 100644 --- a/deltachat-repl/src/main.rs +++ b/deltachat-repl/src/main.rs @@ -30,7 +30,7 @@ use rustyline::{ }; use tokio::fs; use tokio::runtime::Handle; -use tracing_subscriber::EnvFilter; +use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, Layer}; mod cmdline; use self::cmdline::*; @@ -317,7 +317,7 @@ async fn start(args: Vec) -> Result<(), Error> { .await?; let events = context.get_event_emitter(); - tokio::task::spawn(async move { + spawn_named_task!("repl:receive_event", async move { while let Some(event) = events.recv().await { receive_event(event.typ); } @@ -333,7 +333,7 @@ async fn start(args: Vec) -> Result<(), Error> { let mut selected_chat = ChatId::default(); let ctx = context.clone(); - let input_loop = tokio::task::spawn_blocking(move || { + let input_loop = spawn_named_blocking_task!("repl:input_loop", move || { let h = DcHelper { completer: FilenameCompleter::new(), highlighter: MatchingBracketHighlighter::new(), @@ -481,11 +481,22 @@ async fn handle_cmd( #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_env_filter( - EnvFilter::from_default_env().add_directive("deltachat_repl=info".parse()?), - ) - .init(); + tracing::subscriber::set_global_default({ + let subscribers = tracing_subscriber::Registry::default().with( + tracing_subscriber::fmt::layer().with_filter( + tracing_subscriber::EnvFilter::from_default_env() + .add_directive("deltachat_repl=info".parse()?), + ), + ); + #[cfg(tokio_unstable)] + { + subscribers.with(console_subscriber::spawn()) + } + #[cfg(not(tokio_unstable))] + { + subscribers + } + })?; let args = std::env::args().collect(); start(args).await?; diff --git a/src/tools.rs b/src/tools.rs index 2b0f6618c..4f371fa26 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -708,6 +708,130 @@ pub(crate) fn inc_and_check( Ok(()) } +/// Spawns a named asynchronous task if the `tokio_unstable` feature is enabled. +/// +/// Spawns a new asynchronous task, returning a [tokio::task::JoinHandle] for it. +/// The provided future will start running in the background immediately when spawn is called, even if you don't await the returned JoinHandle. +/// See [tokio::task::spawn]. +/// +/// If the rustflag `tokio_unstable` is active, the task will be given the specified `name` +/// for easier identification in monitoring tools (like tokio-console). +/// If `tokio_unstable` is not set, the task is spawned normally without a name. +/// +/// # Parameters +/// +/// - `name`: The name of the task (used only if `tokio_unstable` is enabled). +/// - `future`: The future to be executed, which must implement `Future`, be `Send`, and `'static`. +/// +/// # Returns +/// +/// A [tokio::task::JoinHandle] that can be awaited to retrieve the output of the future. +/// +/// # Panics +/// +/// Panics if the task fails to spawn when `tokio_unstable` is enabled. +/// +/// # Example +/// +/// ``` +/// use tokio::task; +/// +/// let handle = spawn_named_task!("my_task", async { +/// // Your async code here +/// }); +/// +/// let result = handle.await.unwrap(); +/// ``` +#[macro_export] +macro_rules! spawn_named_task { + ($name:expr, $future:expr) => {{ + #[inline(always)] + pub fn __spawn_named_task( + name: &str, + future: Fut, + ) -> ::tokio::task::JoinHandle + where + Fut: ::std::future::Future + Send + 'static, + Fut::Output: Send + 'static, + { + #[cfg(tokio_unstable)] + { + ::tokio::task::Builder::new() + .name(name) + .spawn(future) + .expect("Failed to spawn task") + } + #[cfg(not(tokio_unstable))] + { + ::tokio::task::spawn(future) + } + } + __spawn_named_task($name, $future) + }}; +} + +/// Spawns a named blocking task if the `tokio_unstable` feature is enabled. +/// +/// Spawns a new blocking task, returning a [tokio::task::JoinHandle] for it. +/// The provided future will start running in the background immediately when spawn is called, even if you don't await the returned JoinHandle. +/// See [tokio::task::spawn_blocking]. +/// +/// If the rustflag `tokio_unstable` is active, the task will be given the specified `name` +/// for easier identification in monitoring tools (like tokio-console). +/// If `tokio_unstable` is not set, the task is spawned normally without a name. +/// +/// # Parameters +/// +/// - `name`: The name of the task (used only if `tokio_unstable` is enabled). +/// - `future`: The future to be executed, which must implement `Future`, be `Send`, and `'static`. +/// +/// # Returns +/// +/// A [tokio::task::JoinHandle] that can be awaited to retrieve the output of the future. +/// +/// # Panics +/// +/// Panics if the task fails to spawn when `tokio_unstable` is enabled. +/// +/// # Example +/// +/// ``` +/// use tokio::task; +/// +/// let handle = spawn_named_blocking_task!("my_task", async { +/// // Your async code here +/// }); +/// +/// let result = handle.await.unwrap(); +/// ``` +#[macro_export] +macro_rules! spawn_named_blocking_task { + ($name:expr, $future:expr) => {{ + #[inline(always)] + pub fn __spawn_named_blocking_task( + name: &str, + future: Fut, + ) -> ::tokio::task::JoinHandle + where + Fut: FnOnce() -> ReturnType + Send + 'static, + ReturnType: Send + 'static, + { + #[cfg(tokio_unstable)] + { + ::tokio::task::Builder::new() + .name(name) + .spawn_blocking(future) + .expect("Failed to spawn task") + } + #[cfg(not(tokio_unstable))] + { + ::tokio::task::spawn_blocking(future) + } + } + __spawn_named_blocking_task($name, $future) + }}; +} + #[cfg(test)] mod tests { #![allow(clippy::indexing_slicing)]