From 1846f20f6e80ed5c7d42f3becedcfe396f94a71a Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 21 Mar 2020 14:24:41 +0100 Subject: [PATCH] upgrade repl --- Cargo.lock | 11 ++ Cargo.toml | 13 +- README.md | 4 +- examples/repl/cmdline.rs | 407 ++++++++++++++++++++------------------- examples/repl/main.rs | 259 +++++++------------------ src/contact.rs | 4 +- src/context.rs | 12 ++ src/dc_receive_imf.rs | 4 +- src/e2ee.rs | 4 +- src/lib.rs | 17 +- src/log.rs | 8 +- src/mimefactory.rs | 2 +- src/mimeparser.rs | 2 +- src/peerstate.rs | 6 +- src/scheduler.rs | 1 - src/securejoin.rs | 4 +- src/sql.rs | 12 +- 17 files changed, 352 insertions(+), 418 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0dc28f8b..e7f163ace 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -619,6 +627,7 @@ dependencies = [ name = "deltachat" version = "1.27.0" dependencies = [ + "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)", "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -646,6 +655,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)", "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mailparse 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3139,6 +3149,7 @@ dependencies = [ "checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" "checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" "checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" diff --git a/Cargo.toml b/Cargo.toml index 2393bb68d..ba9d61252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,12 +57,15 @@ mailparse = "0.10.2" encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" } native-tls = "0.2.3" image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] } -pretty_env_logger = "0.3.1" - -rustyline = { version = "4.1.0", optional = true } futures = "0.3.4" crossbeam-queue = "0.2.1" + +pretty_env_logger = { version = "0.3.1", optional = true } +log = {version = "0.4.8", optional = true } +rustyline = { version = "4.1.0", optional = true } +ansi_term = { version = "0.12.1", optional = true } + [dev-dependencies] tempfile = "3.0" pretty_assertions = "0.6.1" @@ -83,10 +86,12 @@ path = "examples/simple.rs" [[example]] name = "repl" path = "examples/repl/main.rs" -required-features = ["rustyline"] +required-features = ["repl"] [features] default = ["nightly"] +internals = [] +repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"] vendored = ["async-native-tls/vendored", "reqwest/native-tls-vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] diff --git a/README.md b/README.md index 6b4613b56..50c244b14 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ To download and install the official compiler for the Rust programming language, and the Cargo package manager, run the command in your user environment: ``` -curl https://sh.rustup.rs -sSf | sh +$ curl https://sh.rustup.rs -sSf | sh ``` ## Using the CLI client @@ -17,7 +17,7 @@ curl https://sh.rustup.rs -sSf | sh Compile and run Delta Chat Core command line utility, using `cargo`: ``` -cargo run --example repl -- /path/to/db +$ RUST_LOG=info cargo run --example repl --features repl -- /path/to/db ``` Configure your account (if not already configured): diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index b065f5845..7b135aaa4 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1,6 +1,7 @@ -use std::path::Path; use std::str::FromStr; +use async_std::path::Path; + use deltachat::chat::{self, Chat, ChatId, ChatVisibility}; use deltachat::chatlist::*; use deltachat::constants::*; @@ -10,7 +11,6 @@ use deltachat::dc_receive_imf::*; use deltachat::dc_tools::*; use deltachat::error::Error; use deltachat::imex::*; -use deltachat::job::*; use deltachat::location; use deltachat::lot::LotState; use deltachat::message::{self, Message, MessageState, MsgId}; @@ -23,63 +23,66 @@ use deltachat::{config, provider}; /// Reset database tables. /// Argument is a bitmask, executing single or multiple actions in one call. /// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4. -fn dc_reset_tables(context: &Context, bits: i32) -> i32 { +async fn reset_tables(context: &Context, bits: i32) { println!("Resetting tables ({})...", bits); if 0 != bits & 1 { - sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap(); + context + .sql() + .execute("DELETE FROM jobs;", paramsv![]) + .await + .unwrap(); println!("(1) Jobs reset."); } if 0 != bits & 2 { - sql::execute( - context, - &context.sql, - "DELETE FROM acpeerstates;", - params![], - ) - .unwrap(); + context + .sql() + .execute("DELETE FROM acpeerstates;", paramsv![]) + .await + .unwrap(); println!("(2) Peerstates reset."); } if 0 != bits & 4 { - sql::execute(context, &context.sql, "DELETE FROM keypairs;", params![]).unwrap(); + context + .sql() + .execute("DELETE FROM keypairs;", paramsv![]) + .await + .unwrap(); println!("(4) Private keypairs reset."); } if 0 != bits & 8 { - sql::execute( - context, - &context.sql, - "DELETE FROM contacts WHERE id>9;", - params![], - ) - .unwrap(); - sql::execute( - context, - &context.sql, - "DELETE FROM chats WHERE id>9;", - params![], - ) - .unwrap(); - sql::execute( - context, - &context.sql, - "DELETE FROM chats_contacts;", - params![], - ) - .unwrap(); - sql::execute( - context, - &context.sql, - "DELETE FROM msgs WHERE id>9;", - params![], - ) - .unwrap(); - sql::execute( - context, - &context.sql, - "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';", - params![], - ) - .unwrap(); - sql::execute(context, &context.sql, "DELETE FROM leftgrps;", params![]).unwrap(); + context + .sql() + .execute("DELETE FROM contacts WHERE id>9;", paramsv![]) + .await + .unwrap(); + context + .sql() + .execute("DELETE FROM chats WHERE id>9;", paramsv![]) + .await + .unwrap(); + context + .sql() + .execute("DELETE FROM chats_contacts;", paramsv![]) + .await + .unwrap(); + context + .sql() + .execute("DELETE FROM msgs WHERE id>9;", paramsv![]) + .await + .unwrap(); + context + .sql() + .execute( + "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';", + paramsv![], + ) + .await + .unwrap(); + context + .sql() + .execute("DELETE FROM leftgrps;", paramsv![]) + .await + .unwrap(); println!("(8) Rest but server config reset."); } @@ -87,14 +90,12 @@ fn dc_reset_tables(context: &Context, bits: i32) -> i32 { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); - - 1 } -fn dc_poke_eml_file(context: &Context, filename: impl AsRef) -> Result<(), Error> { - let data = dc_read_file(context, filename)?; +async fn poke_eml_file(context: &Context, filename: impl AsRef) -> Result<(), Error> { + let data = dc_read_file(context, filename).await?; - if let Err(err) = dc_receive_imf(context, &data, "import", 0, false) { + if let Err(err) = dc_receive_imf(context, &data, "import", 0, false).await { println!("dc_receive_imf errored: {:?}", err); } Ok(()) @@ -103,38 +104,29 @@ fn dc_poke_eml_file(context: &Context, filename: impl AsRef) -> Result<(), /// Import a file to the database. /// For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on. /// For normal importing, use imex(). -/// -/// @private @memberof Context -/// @param context The context as created by dc_context_new(). -/// @param spec The file or directory to import. NULL for the last command. -/// @return 1=success, 0=error. -fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { - if !context.sql.is_open() { - error!(context, "Import: Database not opened."); - return 0; - } - - let mut read_cnt = 0; +async fn poke_spec(context: &Context, spec: Option<&str>) -> bool { + let mut read_cnt: usize = 0; let real_spec: String; - /* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */ + // if `spec` is given, remember it for later usage; if it is not given, try to use the last one if let Some(spec) = spec { real_spec = spec.to_string(); context - .sql + .sql() .set_raw_config(context, "import_spec", Some(&real_spec)) + .await .unwrap(); } else { - let rs = context.sql.get_raw_config(context, "import_spec"); + let rs = context.sql().get_raw_config(context, "import_spec").await; if rs.is_none() { error!(context, "Import: No file or folder given."); - return 0; + return false; } real_spec = rs.unwrap(); } if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) { - if suffix == "eml" && dc_poke_eml_file(context, &real_spec).is_ok() { + if suffix == "eml" && poke_eml_file(context, &real_spec).await.is_ok() { read_cnt += 1 } } else { @@ -143,7 +135,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { let dir = std::fs::read_dir(dir_name); if dir.is_err() { error!(context, "Import: Cannot open directory \"{}\".", &real_spec,); - return 0; + return false; } else { let dir = dir.unwrap(); for entry in dir { @@ -156,7 +148,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { if name.ends_with(".eml") { let path_plus_name = format!("{}/{}", &real_spec, name); println!("Import: {}", path_plus_name); - if dc_poke_eml_file(context, path_plus_name).is_ok() { + if poke_eml_file(context, path_plus_name).await.is_ok() { read_cnt += 1 } } @@ -170,11 +162,14 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { msg_id: MsgId::new(0), }); } - 1 + true } -fn log_msg(context: &Context, prefix: impl AsRef, msg: &Message) { - let contact = Contact::get_by_id(context, msg.get_from_id()).expect("invalid contact"); +async fn log_msg(context: &Context, prefix: impl AsRef, msg: &Message) { + let contact = Contact::get_by_id(context, msg.get_from_id()) + .await + .expect("invalid contact"); + let contact_name = contact.get_name(); let contact_id = contact.get_id(); @@ -217,7 +212,7 @@ fn log_msg(context: &Context, prefix: impl AsRef, msg: &Message) { ); } -fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { +async fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { let mut lines_out = 0; for &msg_id in msglist { if msg_id.is_daymarker() { @@ -233,8 +228,8 @@ fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { ); lines_out += 1 } - let msg = Message::load_from_db(context, msg_id)?; - log_msg(context, "", &msg); + let msg = Message::load_from_db(context, msg_id).await?; + log_msg(context, "", &msg).await; } } if lines_out > 0 { @@ -245,7 +240,7 @@ fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { Ok(()) } -fn log_contactlist(context: &Context, contacts: &Vec) { +async fn log_contactlist(context: &Context, contacts: &Vec) { let mut contacts = contacts.clone(); if !contacts.contains(&1) { contacts.push(1); @@ -253,10 +248,10 @@ fn log_contactlist(context: &Context, contacts: &Vec) { for contact_id in contacts { let line; let mut line2 = "".to_string(); - if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if let Ok(contact) = Contact::get_by_id(context, contact_id).await { let name = contact.get_name(); let addr = contact.get_addr(); - let verified_state = contact.is_verified(context); + let verified_state = contact.is_verified(context).await; let verified_str = if VerifiedStatus::Unverified != verified_state { if verified_state == VerifiedStatus::BidirectVerified { " √√" @@ -280,7 +275,7 @@ fn log_contactlist(context: &Context, contacts: &Vec) { "addr unset" } ); - let peerstate = Peerstate::from_addr(context, &context.sql, &addr); + let peerstate = Peerstate::from_addr(context, &addr).await; if peerstate.is_some() && contact_id != 1 as libc::c_uint { line2 = format!( ", prefer-encrypt={}", @@ -297,10 +292,13 @@ fn chat_prefix(chat: &Chat) -> &'static str { chat.typ.into() } -pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { - let chat_id = *context.cmdline_sel_chat_id.read().unwrap(); +pub async fn cmdline( + context: Context, + line: &str, + chat_id: &mut ChatId, +) -> Result<(), failure::Error> { let mut sel_chat = if !chat_id.is_unset() { - Chat::load_from_db(context, chat_id).ok() + Chat::load_from_db(&context, *chat_id).await.ok() } else { None }; @@ -341,7 +339,6 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { configure\n\ connect\n\ disconnect\n\ - interrupt\n\ maybenetwork\n\ housekeeping\n\ help imex (Import/Export)\n\ @@ -403,7 +400,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { =============================================" ), }, - "initiate-key-transfer" => match initiate_key_transfer(context) { + "initiate-key-transfer" => match initiate_key_transfer(&context).await { Ok(setup_code) => println!( "Setup code for the transferred setup message: {}", setup_code, @@ -413,9 +410,9 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "get-setupcodebegin" => { ensure!(!arg1.is_empty(), "Argument missing."); let msg_id: MsgId = MsgId::new(arg1.parse()?); - let msg = Message::load_from_db(context, msg_id)?; + let msg = Message::load_from_db(&context, msg_id).await?; if msg.is_setupmessage() { - let setupcodebegin = msg.get_setupcodebegin(context); + let setupcodebegin = msg.get_setupcodebegin(&context).await; println!( "The setup code for setup message {} starts with: {}", msg_id, @@ -430,29 +427,29 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { !arg1.is_empty() && !arg2.is_empty(), "Arguments expected" ); - continue_key_transfer(context, MsgId::new(arg1.parse()?), &arg2)?; + continue_key_transfer(&context, MsgId::new(arg1.parse()?), &arg2).await?; } "has-backup" => { - has_backup(context, blobdir)?; + has_backup(&context, blobdir).await?; } "export-backup" => { - imex(context, ImexMode::ExportBackup, Some(blobdir)); + imex(&context, ImexMode::ExportBackup, Some(blobdir)).await?; } "import-backup" => { ensure!(!arg1.is_empty(), "Argument missing."); - imex(context, ImexMode::ImportBackup, Some(arg1)); + imex(&context, ImexMode::ImportBackup, Some(arg1)).await?; } "export-keys" => { - imex(context, ImexMode::ExportSelfKeys, Some(blobdir)); + imex(&context, ImexMode::ExportSelfKeys, Some(blobdir)).await?; } "import-keys" => { - imex(context, ImexMode::ImportSelfKeys, Some(blobdir)); + imex(&context, ImexMode::ImportSelfKeys, Some(blobdir)).await?; } "export-setup" => { - let setup_code = create_setup_code(context); + let setup_code = create_setup_code(&context); let file_name = blobdir.join("autocrypt-setup-message.html"); - let file_content = render_setup_file(context, &setup_code)?; - std::fs::write(&file_name, file_content)?; + let file_content = render_setup_file(&context, &setup_code).await?; + async_std::fs::write(&file_name, file_content).await?; println!( "Setup message written to: {}\nSetup code: {}", file_name.display(), @@ -460,49 +457,47 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); } "poke" => { - ensure!(0 != poke_spec(context, Some(arg1)), "Poke failed"); + ensure!(poke_spec(&context, Some(arg1)).await, "Poke failed"); } "reset" => { ensure!(!arg1.is_empty(), "Argument missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config"); let bits: i32 = arg1.parse()?; ensure!(bits < 16, " must be lower than 16."); - ensure!(0 != dc_reset_tables(context, bits), "Reset failed"); + reset_tables(&context, bits).await; } "stop" => { - context.stop_ongoing(); + context.stop_ongoing().await; } "set" => { ensure!(!arg1.is_empty(), "Argument missing."); let key = config::Config::from_str(&arg1)?; let value = if arg2.is_empty() { None } else { Some(arg2) }; - context.set_config(key, value)?; + context.set_config(key, value).await?; } "get" => { ensure!(!arg1.is_empty(), "Argument missing."); let key = config::Config::from_str(&arg1)?; - let val = context.get_config(key); + let val = context.get_config(key).await; println!("{}={:?}", key, val); } "info" => { - println!("{:#?}", context.get_info()); - } - "interrupt" => { - interrupt_inbox_idle(context); + println!("{:#?}", context.get_info().await); } "maybenetwork" => { - maybe_network(context); + context.maybe_network().await; } "housekeeping" => { - sql::housekeeping(context); + sql::housekeeping(&context).await; } "listchats" | "listarchived" | "chats" => { let listflags = if arg0 == "listarchived" { 0x01 } else { 0 }; let chatlist = Chatlist::try_load( - context, + &context, listflags, if arg1.is_empty() { None } else { Some(arg1) }, None, - )?; + ) + .await?; let cnt = chatlist.len(); if cnt > 0 { @@ -511,20 +506,20 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); for i in (0..cnt).rev() { - let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; + let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?; println!( "{}#{}: {} [{} fresh] {}", chat_prefix(&chat), chat.get_id(), chat.get_name(), - chat.get_id().get_fresh_msg_cnt(context), + chat.get_id().get_fresh_msg_cnt(&context).await, match chat.visibility { ChatVisibility::Normal => "", ChatVisibility::Archived => "📦", ChatVisibility::Pinned => "📌", }, ); - let lot = chatlist.get_summary(context, i, Some(&chat)); + let lot = chatlist.get_summary(&context, i, Some(&chat)).await; let statestr = if chat.visibility == ChatVisibility::Archived { " [Archived]" } else { @@ -557,7 +552,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); } } - if location::is_sending_locations_to_chat(context, ChatId::new(0)) { + if location::is_sending_locations_to_chat(&context, ChatId::new(0)).await { println!("Location streaming enabled."); } println!("{} chats", cnt); @@ -567,21 +562,21 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { bail!("Argument [chat-id] is missing."); } if !arg1.is_empty() { - let chat_id = ChatId::new(arg1.parse()?); - println!("Selecting chat {}", chat_id); - sel_chat = Some(Chat::load_from_db(context, chat_id)?); - *context.cmdline_sel_chat_id.write().unwrap() = chat_id; + let id = ChatId::new(arg1.parse()?); + println!("Selecting chat {}", id); + sel_chat = Some(Chat::load_from_db(&context, id).await?); + *chat_id = id; } ensure!(sel_chat.is_some(), "Failed to select chat"); let sel_chat = sel_chat.as_ref().unwrap(); - let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, None); - let members = chat::get_chat_contacts(context, sel_chat.id); + let msglist = chat::get_chat_msgs(&context, sel_chat.get_id(), 0x1, None).await; + let members = chat::get_chat_contacts(&context, sel_chat.id).await; let subtitle = if sel_chat.is_device_talk() { "device-talk".to_string() } else if sel_chat.get_type() == Chattype::Single && !members.is_empty() { - let contact = Contact::get_by_id(context, members[0])?; + let contact = Contact::get_by_id(&context, members[0]).await?; contact.get_addr().to_string() } else { format!("{} member(s)", members.len()) @@ -597,7 +592,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } else { "" }, - match sel_chat.get_profile_image(context) { + match sel_chat.get_profile_image(&context).await { Some(icon) => match icon.to_str() { Some(icon) => format!(" Icon: {}", icon), _ => " Icon: Err".to_string(), @@ -605,38 +600,42 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { _ => "".to_string(), }, ); - log_msglist(context, &msglist)?; - if let Some(draft) = sel_chat.get_id().get_draft(context)? { - log_msg(context, "Draft", &draft); + log_msglist(&context, &msglist).await?; + if let Some(draft) = sel_chat.get_id().get_draft(&context).await? { + log_msg(&context, "Draft", &draft).await; } - println!("{} messages.", sel_chat.get_id().get_msg_cnt(context)); - chat::marknoticed_chat(context, sel_chat.get_id())?; + println!( + "{} messages.", + sel_chat.get_id().get_msg_cnt(&context).await + ); + chat::marknoticed_chat(&context, sel_chat.get_id()).await?; } "createchat" => { ensure!(!arg1.is_empty(), "Argument missing."); let contact_id: libc::c_int = arg1.parse()?; - let chat_id = chat::create_by_contact_id(context, contact_id as u32)?; + let chat_id = chat::create_by_contact_id(&context, contact_id as u32).await?; println!("Single#{} created successfully.", chat_id,); } "createchatbymsg" => { ensure!(!arg1.is_empty(), "Argument missing"); let msg_id = MsgId::new(arg1.parse()?); - let chat_id = chat::create_by_msg_id(context, msg_id)?; - let chat = Chat::load_from_db(context, chat_id)?; + let chat_id = chat::create_by_msg_id(&context, msg_id).await?; + let chat = Chat::load_from_db(&context, chat_id).await?; println!("{}#{} created successfully.", chat_prefix(&chat), chat_id,); } "creategroup" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id = chat::create_group_chat(context, VerifiedStatus::Unverified, arg1)?; + let chat_id = + chat::create_group_chat(&context, VerifiedStatus::Unverified, arg1).await?; println!("Group#{} created successfully.", chat_id); } "createverified" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id = chat::create_group_chat(context, VerifiedStatus::Verified, arg1)?; + let chat_id = chat::create_group_chat(&context, VerifiedStatus::Verified, arg1).await?; println!("VerifiedGroup#{} created successfully.", chat_id); } @@ -646,10 +645,12 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let contact_id_0: libc::c_int = arg1.parse()?; if chat::add_contact_to_chat( - context, + &context, sel_chat.as_ref().unwrap().get_id(), contact_id_0 as u32, - ) { + ) + .await + { println!("Contact added to chat."); } else { bail!("Cannot add contact to chat."); @@ -660,17 +661,18 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ensure!(!arg1.is_empty(), "Argument missing."); let contact_id_1: libc::c_int = arg1.parse()?; chat::remove_contact_from_chat( - context, + &context, sel_chat.as_ref().unwrap().get_id(), contact_id_1 as u32, - )?; + ) + .await?; println!("Contact added to chat."); } "groupname" => { ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "Argument missing."); - chat::set_chat_name(context, sel_chat.as_ref().unwrap().get_id(), arg1)?; + chat::set_chat_name(&context, sel_chat.as_ref().unwrap().get_id(), arg1).await?; println!("Chat name set"); } @@ -678,24 +680,27 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "Argument missing."); - chat::set_chat_profile_image(context, sel_chat.as_ref().unwrap().get_id(), arg1)?; + chat::set_chat_profile_image(&context, sel_chat.as_ref().unwrap().get_id(), arg1) + .await?; println!("Chat image set"); } "chatinfo" => { ensure!(sel_chat.is_some(), "No chat selected."); - let contacts = chat::get_chat_contacts(context, sel_chat.as_ref().unwrap().get_id()); + let contacts = + chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await; println!("Memberlist:"); - log_contactlist(context, &contacts); + log_contactlist(&context, &contacts).await; println!( "{} contacts\nLocation streaming: {}", contacts.len(), location::is_sending_locations_to_chat( - context, + &context, sel_chat.as_ref().unwrap().get_id() - ), + ) + .await, ); } "getlocations" => { @@ -703,12 +708,13 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let contact_id = arg1.parse().unwrap_or_default(); let locations = location::get_range( - context, + &context, sel_chat.as_ref().unwrap().get_id(), contact_id, 0, 0, - ); + ) + .await; let default_marker = "-".to_string(); for location in &locations { let marker = location.marker.as_ref().unwrap_or(&default_marker); @@ -734,7 +740,12 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ensure!(!arg1.is_empty(), "No timeout given."); let seconds = arg1.parse()?; - location::send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds); + location::send_locations_to_chat( + &context, + sel_chat.as_ref().unwrap().get_id(), + seconds, + ) + .await; println!( "Locations will be sent to Chat#{} for {} seconds. Use 'setlocation ' to play around.", sel_chat.as_ref().unwrap().get_id(), @@ -749,7 +760,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let latitude = arg1.parse()?; let longitude = arg2.parse()?; - let continue_streaming = location::set(context, latitude, longitude, 0.); + let continue_streaming = location::set(&context, latitude, longitude, 0.).await; if continue_streaming { println!("Success, streaming should be continued."); } else { @@ -757,7 +768,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } } "dellocations" => { - location::delete_all(context)?; + location::delete_all(&context).await?; } "send" => { ensure!(sel_chat.is_some(), "No chat selected."); @@ -765,11 +776,11 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let msg = format!("{} {}", arg1, arg2); - chat::send_text_msg(context, sel_chat.as_ref().unwrap().get_id(), msg)?; + chat::send_text_msg(&context, sel_chat.as_ref().unwrap().get_id(), msg).await?; } "sendempty" => { ensure!(sel_chat.is_some(), "No chat selected."); - chat::send_text_msg(context, sel_chat.as_ref().unwrap().get_id(), "".into())?; + chat::send_text_msg(&context, sel_chat.as_ref().unwrap().get_id(), "".into()).await?; } "sendimage" | "sendfile" => { ensure!(sel_chat.is_some(), "No chat selected."); @@ -784,7 +795,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { if !arg2.is_empty() { msg.set_text(Some(arg2.to_string())); } - chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?; + chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?; } "listmsgs" => { ensure!(!arg1.is_empty(), "Argument missing."); @@ -795,9 +806,9 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ChatId::new(0) }; - let msglist = context.search_msgs(chat, arg1); + let msglist = context.search_msgs(chat, arg1).await; - log_msglist(context, &msglist)?; + log_msglist(&context, &msglist).await?; println!("{} messages.", msglist.len()); } "draft" => { @@ -810,10 +821,16 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { .as_ref() .unwrap() .get_id() - .set_draft(context, Some(&mut draft)); + .set_draft(&context, Some(&mut draft)) + .await; println!("Draft saved."); } else { - sel_chat.as_ref().unwrap().get_id().set_draft(context, None); + sel_chat + .as_ref() + .unwrap() + .get_id() + .set_draft(&context, None) + .await; println!("Draft deleted."); } } @@ -824,21 +841,22 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); let mut msg = Message::new(Viewtype::Text); msg.set_text(Some(arg1.to_string())); - chat::add_device_msg(context, None, Some(&mut msg))?; + chat::add_device_msg(&context, None, Some(&mut msg)).await?; } "updatedevicechats" => { - context.update_device_chats()?; + context.update_device_chats().await?; } "listmedia" => { ensure!(sel_chat.is_some(), "No chat selected."); let images = chat::get_chat_media( - context, + &context, sel_chat.as_ref().unwrap().get_id(), Viewtype::Image, Viewtype::Gif, Viewtype::Video, - ); + ) + .await; println!("{} images or videos: ", images.len()); for (i, data) in images.iter().enumerate() { if 0 == i { @@ -852,31 +870,33 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "archive" | "unarchive" | "pin" | "unpin" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = ChatId::new(arg1.parse()?); - chat_id.set_visibility( - context, - match arg0 { - "archive" => ChatVisibility::Archived, - "unarchive" | "unpin" => ChatVisibility::Normal, - "pin" => ChatVisibility::Pinned, - _ => panic!("Unexpected command (This should never happen)"), - }, - )?; + chat_id + .set_visibility( + &context, + match arg0 { + "archive" => ChatVisibility::Archived, + "unarchive" | "unpin" => ChatVisibility::Normal, + "pin" => ChatVisibility::Pinned, + _ => panic!("Unexpected command (This should never happen)"), + }, + ) + .await?; } "delchat" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = ChatId::new(arg1.parse()?); - chat_id.delete(context)?; + chat_id.delete(&context).await?; } "msginfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let id = MsgId::new(arg1.parse()?); - let res = message::get_msg_info(context, id); + let res = message::get_msg_info(&context, id).await; println!("{}", res); } "listfresh" => { - let msglist = context.get_fresh_msgs(); + let msglist = context.get_fresh_msgs().await; - log_msglist(context, &msglist)?; + log_msglist(&context, &msglist).await?; print!("{} fresh messages.", msglist.len()); } "forward" => { @@ -888,37 +908,38 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let mut msg_ids = [MsgId::new(0); 1]; let chat_id = ChatId::new(arg2.parse()?); msg_ids[0] = MsgId::new(arg1.parse()?); - chat::forward_msgs(context, &msg_ids, chat_id)?; + chat::forward_msgs(&context, &msg_ids, chat_id).await?; } "markseen" => { ensure!(!arg1.is_empty(), "Argument missing."); - let mut msg_ids = [MsgId::new(0); 1]; + let mut msg_ids = vec![MsgId::new(0)]; msg_ids[0] = MsgId::new(arg1.parse()?); - message::markseen_msgs(context, &msg_ids); + message::markseen_msgs(&context, msg_ids).await; } "star" | "unstar" => { ensure!(!arg1.is_empty(), "Argument missing."); - let mut msg_ids = [MsgId::new(0); 1]; + let mut msg_ids = vec![MsgId::new(0); 1]; msg_ids[0] = MsgId::new(arg1.parse()?); - message::star_msgs(context, &msg_ids, arg0 == "star"); + message::star_msgs(&context, msg_ids, arg0 == "star").await; } "delmsg" => { ensure!(!arg1.is_empty(), "Argument missing."); let mut ids = [MsgId::new(0); 1]; ids[0] = MsgId::new(arg1.parse()?); - message::delete_msgs(context, &ids); + message::delete_msgs(&context, &ids).await; } "listcontacts" | "contacts" | "listverified" => { let contacts = Contact::get_all( - context, + &context, if arg0 == "listverified" { 0x1 | 0x2 } else { 0x2 }, Some(arg1), - )?; - log_contactlist(context, &contacts); + ) + .await?; + log_contactlist(&context, &contacts).await; println!("{} contacts.", contacts.len()); } "addcontact" => { @@ -926,30 +947,30 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { if !arg2.is_empty() { let book = format!("{}\n{}", arg1, arg2); - Contact::add_address_book(context, book)?; + Contact::add_address_book(&context, book).await?; } else { - Contact::create(context, "", arg1)?; + Contact::create(&context, "", arg1).await?; } } "contactinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let contact_id = arg1.parse()?; - let contact = Contact::get_by_id(context, contact_id)?; + let contact = Contact::get_by_id(&context, contact_id).await?; let name_n_addr = contact.get_name_n_addr(); let mut res = format!( "Contact info for: {}:\nIcon: {}\n", name_n_addr, - match contact.get_profile_image(context) { + match contact.get_profile_image(&context).await { Some(image) => image.to_str().unwrap().to_string(), None => "NoIcon".to_string(), } ); - res += &Contact::get_encrinfo(context, contact_id)?; + res += &Contact::get_encrinfo(&context, contact_id).await?; - let chatlist = Chatlist::try_load(context, 0, None, Some(contact_id))?; + let chatlist = Chatlist::try_load(&context, 0, None, Some(contact_id)).await?; let chatlist_cnt = chatlist.len(); if chatlist_cnt > 0 { res += &format!( @@ -960,7 +981,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { if 0 != i { res += ", "; } - let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; + let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?; res += &format!("{}#{}", chat_prefix(&chat), chat.get_id()); } } @@ -969,11 +990,11 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } "delcontact" => { ensure!(!arg1.is_empty(), "Argument missing."); - Contact::delete(context, arg1.parse()?)?; + Contact::delete(&context, arg1.parse()?).await?; } "checkqr" => { ensure!(!arg1.is_empty(), "Argument missing."); - let res = check_qr(context, arg1); + let res = check_qr(&context, arg1).await; println!( "state={}, id={}, text1={:?}, text2={:?}", res.get_state(), @@ -984,7 +1005,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } "setqr" => { ensure!(!arg1.is_empty(), "Argument missing."); - match set_config_from_qr(context, arg1) { + match set_config_from_qr(&context, arg1).await { Ok(()) => println!("Config set from QR code, you can now call 'configure'"), Err(err) => println!("Cannot set config from QR code: {:?}", err), } @@ -1021,7 +1042,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "fileinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); - if let Ok(buf) = dc_read_file(context, &arg1) { + if let Ok(buf) = dc_read_file(&context, &arg1).await { let (width, height) = dc_get_filemeta(&buf)?; println!("width={}, height={}", width, height); } else { @@ -1031,7 +1052,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "emptyserver" => { ensure!(!arg1.is_empty(), "Argument missing"); - message::dc_empty_server(context, arg1.parse()?); + message::dc_empty_server(&context, arg1.parse()?).await; } "" => (), _ => bail!("Unknown command: \"{}\" type ? for help.", arg0), diff --git a/examples/repl/main.rs b/examples/repl/main.rs index c1873829c..f30e848b8 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -8,25 +8,20 @@ extern crate deltachat; #[macro_use] extern crate failure; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate rusqlite; use std::borrow::Cow::{self, Borrowed, Owned}; use std::io::{self, Write}; -use std::path::Path; use std::process::Command; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex, RwLock}; +use ansi_term::Color; +use async_std::path::Path; use deltachat::chat::ChatId; use deltachat::config; use deltachat::context::*; -use deltachat::job::*; use deltachat::oauth2::*; use deltachat::securejoin::*; use deltachat::Event; +use log::{error, info, warn}; use rustyline::completion::{Completer, FilenameCompleter, Pair}; use rustyline::config::OutputStreamType; use rustyline::error::ReadlineError; @@ -39,179 +34,83 @@ use rustyline::{ mod cmdline; use self::cmdline::*; -// Event Handler - -fn receive_event(_context: &Context, event: Event) { +/// Event Handler +fn receive_event(event: Event) { + let yellow = Color::Yellow.normal(); match event { Event::Info(msg) => { /* do not show the event as this would fill the screen */ - println!("{}", msg); + info!("{}", msg); } Event::SmtpConnected(msg) => { - println!("[DC_EVENT_SMTP_CONNECTED] {}", msg); + info!("[SMTP_CONNECTED] {}", msg); } Event::ImapConnected(msg) => { - println!("[DC_EVENT_IMAP_CONNECTED] {}", msg); + info!("[IMAP_CONNECTED] {}", msg); } Event::SmtpMessageSent(msg) => { - println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg); + info!("[SMTP_MESSAGE_SENT] {}", msg); } Event::Warning(msg) => { - println!("[Warning] {}", msg); + warn!("{}", msg); } Event::Error(msg) => { - println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg); + error!("{}", msg); } Event::ErrorNetwork(msg) => { - println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg); + error!("[NETWORK] msg={}", msg); } Event::ErrorSelfNotInGroup(msg) => { - println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg); + error!("[SELF_NOT_IN_GROUP] {}", msg); } Event::MsgsChanged { chat_id, msg_id } => { - print!( - "\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m", - chat_id, msg_id, + info!( + "{}", + yellow.paint(format!( + "Received MSGS_CHANGED(chat_id={}, msg_id={})", + chat_id, msg_id, + )) ); } Event::ContactsChanged(_) => { - print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m"); + info!("{}", yellow.paint("Received CONTACTS_CHANGED()")); } Event::LocationChanged(contact) => { - print!( - "\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m", - contact, + info!( + "{}", + yellow.paint(format!("Received LOCATION_CHANGED(contact={:?})", contact)) ); } Event::ConfigureProgress(progress) => { - print!( - "\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m", - progress, + info!( + "{}", + yellow.paint(format!("Received CONFIGURE_PROGRESS({} ‰)", progress)) ); } Event::ImexProgress(progress) => { - print!( - "\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m", - progress, + info!( + "{}", + yellow.paint(format!("Received IMEX_PROGRESS({} ‰)", progress)) ); } Event::ImexFileWritten(file) => { - print!( - "\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m", - file.display() + info!( + "{}", + yellow.paint(format!("Received IMEX_FILE_WRITTEN({})", file.display())) ); } Event::ChatModified(chat) => { - print!( - "\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m", - chat + info!( + "{}", + yellow.paint(format!("Received CHAT_MODIFIED({})", chat)) ); } _ => { - print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event); + info!("Received {:?}", event); } } } -// Threads for waiting for messages and for jobs - -lazy_static! { - static ref HANDLE: Arc>> = Arc::new(Mutex::new(None)); - static ref IS_RUNNING: AtomicBool = AtomicBool::new(true); -} - -struct Handle { - handle_imap: Option>, - handle_mvbox: Option>, - handle_sentbox: Option>, - handle_smtp: Option>, -} - -macro_rules! while_running { - ($code:block) => { - if IS_RUNNING.load(Ordering::Relaxed) { - $code - } else { - break; - } - }; -} - -fn start_threads(c: Arc>) { - if HANDLE.clone().lock().unwrap().is_some() { - return; - } - - println!("Starting threads"); - IS_RUNNING.store(true, Ordering::Relaxed); - - let ctx = c.clone(); - let handle_imap = std::thread::spawn(move || loop { - while_running!({ - perform_inbox_jobs(&ctx.read().unwrap()); - perform_inbox_fetch(&ctx.read().unwrap()); - while_running!({ - let context = ctx.read().unwrap(); - perform_inbox_idle(&context); - }); - }); - }); - - let ctx = c.clone(); - let handle_mvbox = std::thread::spawn(move || loop { - while_running!({ - perform_mvbox_fetch(&ctx.read().unwrap()); - while_running!({ - perform_mvbox_idle(&ctx.read().unwrap()); - }); - }); - }); - - let ctx = c.clone(); - let handle_sentbox = std::thread::spawn(move || loop { - while_running!({ - perform_sentbox_fetch(&ctx.read().unwrap()); - while_running!({ - perform_sentbox_idle(&ctx.read().unwrap()); - }); - }); - }); - - let ctx = c; - let handle_smtp = std::thread::spawn(move || loop { - while_running!({ - perform_smtp_jobs(&ctx.read().unwrap()); - while_running!({ - perform_smtp_idle(&ctx.read().unwrap()); - }); - }); - }); - - *HANDLE.clone().lock().unwrap() = Some(Handle { - handle_imap: Some(handle_imap), - handle_mvbox: Some(handle_mvbox), - handle_sentbox: Some(handle_sentbox), - handle_smtp: Some(handle_smtp), - }); -} - -fn stop_threads(context: &Context) { - if let Some(ref mut handle) = *HANDLE.clone().lock().unwrap() { - println!("Stopping threads"); - IS_RUNNING.store(false, Ordering::Relaxed); - - interrupt_inbox_idle(context); - interrupt_mvbox_idle(context); - interrupt_sentbox_idle(context); - interrupt_smtp_idle(context); - - handle.handle_imap.take().unwrap().join().unwrap(); - handle.handle_mvbox.take().unwrap().join().unwrap(); - handle.handle_sentbox.take().unwrap().join().unwrap(); - handle.handle_smtp.take().unwrap().join().unwrap(); - } -} - // === The main loop struct DcHelper { @@ -361,21 +260,22 @@ impl Highlighter for DcHelper { impl Helper for DcHelper {} -fn main_0(args: Vec) -> Result<(), failure::Error> { +async fn start(args: Vec) -> Result<(), failure::Error> { if args.len() < 2 { println!("Error: Bad arguments, expected [db-name]."); return Err(format_err!("No db-name specified")); } - let context = Context::new( - Box::new(receive_event), - "CLI".into(), - Path::new(&args[1]).to_path_buf(), - )?; + let context = Context::new("CLI".into(), Path::new(&args[1]).to_path_buf()).await?; + + let ctx = context.clone(); + std::thread::spawn(move || loop { + if let Ok(event) = ctx.get_next_event() { + receive_event(event); + } + }); println!("Delta Chat Core is awaiting your commands."); - let ctx = Arc::new(RwLock::new(context)); - let config = Config::builder() .history_ignore_space(true) .completion_type(CompletionType::List) @@ -395,6 +295,8 @@ fn main_0(args: Vec) -> Result<(), failure::Error> { println!("No previous history."); } + let mut selected_chat = ChatId::default(); + loop { let p = "> "; let readline = rl.readline(&p); @@ -402,8 +304,7 @@ fn main_0(args: Vec) -> Result<(), failure::Error> { Ok(line) => { // TODO: ignore "set mail_pw" rl.add_history_entry(line.as_str()); - let ctx = ctx.clone(); - match handle_cmd(line.trim(), ctx) { + match handle_cmd(line.trim(), context.clone(), &mut selected_chat).await { Ok(ExitResult::Continue) => {} Ok(ExitResult::Exit) => break, Err(err) => println!("Error: {}", err), @@ -421,9 +322,8 @@ fn main_0(args: Vec) -> Result<(), failure::Error> { } rl.save_history(".dc-history.txt")?; println!("history saved"); - { - stop_threads(&ctx.read().unwrap()); - } + + context.stop().await; Ok(()) } @@ -434,43 +334,29 @@ enum ExitResult { Exit, } -fn handle_cmd(line: &str, ctx: Arc>) -> Result { +async fn handle_cmd( + line: &str, + ctx: Context, + selected_chat: &mut ChatId, +) -> Result { let mut args = line.splitn(2, ' '); let arg0 = args.next().unwrap_or_default(); let arg1 = args.next().unwrap_or_default(); match arg0 { "connect" => { - start_threads(ctx); + ctx.run().await; } "disconnect" => { - stop_threads(&ctx.read().unwrap()); - } - "smtp-jobs" => { - if HANDLE.clone().lock().unwrap().is_some() { - println!("smtp-jobs are already running in a thread.",); - } else { - perform_smtp_jobs(&ctx.read().unwrap()); - } - } - "imap-jobs" => { - if HANDLE.clone().lock().unwrap().is_some() { - println!("inbox-jobs are already running in a thread."); - } else { - perform_inbox_jobs(&ctx.read().unwrap()); - } + ctx.stop().await; } "configure" => { - start_threads(ctx.clone()); - ctx.read().unwrap().configure(); + ctx.configure().await?; } "oauth2" => { - if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) { - let oauth2_url = dc_get_oauth2_url( - &ctx.read().unwrap(), - &addr, - "chat.delta:/com.b44t.messenger", - ); + if let Some(addr) = ctx.get_config(config::Config::Addr).await { + let oauth2_url = + dc_get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await; if oauth2_url.is_none() { println!("OAuth2 not available for {}.", &addr); } else { @@ -485,11 +371,10 @@ fn handle_cmd(line: &str, ctx: Arc>) -> Result { - start_threads(ctx.clone()); - if let Some(mut qr) = dc_get_securejoin_qr( - &ctx.read().unwrap(), - ChatId::new(arg1.parse().unwrap_or_default()), - ) { + ctx.run().await; + if let Some(mut qr) = + dc_get_securejoin_qr(&ctx, ChatId::new(arg1.parse().unwrap_or_default())).await + { if !qr.is_empty() { if arg0 == "getbadqr" && qr.len() > 40 { qr.replace_range(12..22, "0000000000") @@ -505,23 +390,23 @@ fn handle_cmd(line: &str, ctx: Arc>) -> Result { - start_threads(ctx.clone()); + ctx.run().await; if !arg0.is_empty() { - dc_join_securejoin(&ctx.read().unwrap(), arg1); + dc_join_securejoin(&ctx, arg1).await; } } "exit" | "quit" => return Ok(ExitResult::Exit), - _ => dc_cmdline(&ctx.read().unwrap(), line)?, + _ => cmdline(ctx.clone(), line, selected_chat).await?, } Ok(ExitResult::Continue) } -pub fn main() -> Result<(), failure::Error> { +fn main() -> Result<(), failure::Error> { let _ = pretty_env_logger::try_init(); - let args: Vec = std::env::args().collect(); - main_0(args)?; + let args = std::env::args().collect(); + async_std::task::block_on(async move { start(args).await })?; Ok(()) } diff --git a/src/contact.rs b/src/contact.rs index cb3ab93be..6db5a0943 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -674,7 +674,7 @@ impl Contact { let mut ret = String::new(); if let Ok(contact) = Contact::load_from_db(context, contact_id).await { - let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr).await; + let peerstate = Peerstate::from_addr(context, &contact.addr).await; let loginparam = LoginParam::from_database(context, "configured_").await; let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; @@ -940,7 +940,7 @@ impl Contact { } } - let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr).await; + let peerstate = Peerstate::from_addr(context, &self.addr).await; if let Some(ps) = peerstate { if ps.verified_key.is_some() { return VerifiedStatus::BidirectVerified; diff --git a/src/context.rs b/src/context.rs index f5545c6ab..879602d4f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -136,10 +136,22 @@ impl Context { l.run(self.clone()); } + pub async fn is_running(&self) -> bool { + self.inner.scheduler.read().await.is_running() + } + pub async fn stop(&self) { self.inner.stop().await; } + /// Returns a reference to the underlying SQL instance. + /// + /// Warning: this is only here for testing, not part of the public API. + #[cfg(feature = "internals")] + pub fn sql(&self) -> &Sql { + &self.inner.sql + } + /// Returns database file path. pub fn get_dbfile(&self) -> &Path { self.dbfile.as_path() diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 4433c08bd..36c5f6972 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1434,7 +1434,7 @@ async fn check_verified_properties( // this check is skipped for SELF as there is no proper SELF-peerstate // and results in group-splits otherwise. if from_id != DC_CONTACT_ID_SELF { - let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr()).await; + let peerstate = Peerstate::from_addr(context, contact.get_addr()).await; if peerstate.is_none() || contact.is_verified_ex(context, peerstate.as_ref()).await @@ -1488,7 +1488,7 @@ async fn check_verified_properties( context.is_self_addr(&to_addr).await ); let mut is_verified = _is_verified != 0; - let peerstate = Peerstate::from_addr(context, &context.sql, &to_addr).await; + let peerstate = Peerstate::from_addr(context, &to_addr).await; // mark gossiped keys (if any) as verified if mimeparser.gossipped_addr.contains(&to_addr) { diff --git a/src/e2ee.rs b/src/e2ee.rs index 549ee4b72..1c6c421ce 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -137,7 +137,7 @@ pub async fn try_decrypt( let autocryptheader = Aheader::from_headers(context, &from, &mail.headers); if message_time > 0 { - peerstate = Peerstate::from_addr(context, &context.sql, &from).await; + peerstate = Peerstate::from_addr(context, &from).await; if let Some(ref mut peerstate) = peerstate { if let Some(ref header) = autocryptheader { @@ -167,7 +167,7 @@ pub async fn try_decrypt( .await { if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { - peerstate = Peerstate::from_addr(&context, &context.sql, &from).await; + peerstate = Peerstate::from_addr(&context, &from).await; } if let Some(ref peerstate) = peerstate { if peerstate.degrade_event.is_some() { diff --git a/src/lib.rs b/src/lib.rs index 0012e2fdc..2bcd59ee3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,20 +20,18 @@ pub trait ToSql: rusqlite::ToSql + Send + Sync {} impl ToSql for T {} -macro_rules! paramsv { - () => { - Vec::new() - }; - ($($param:expr),+ $(,)?) => { - vec![$(&$param as &dyn $crate::ToSql),+] - }; -} - #[macro_use] pub mod log; #[macro_use] pub mod error; +#[cfg(feature = "internals")] +#[macro_use] +pub mod sql; +#[cfg(not(feature = "internals"))] +#[macro_use] +mod sql; + pub mod headerdef; pub(crate) mod events; @@ -71,7 +69,6 @@ pub mod qr; pub mod securejoin; mod simplify; mod smtp; -mod sql; pub mod stock; mod token; #[macro_use] diff --git a/src/log.rs b/src/log.rs index dbd298bcd..db56c1f70 100644 --- a/src/log.rs +++ b/src/log.rs @@ -7,9 +7,7 @@ macro_rules! info { }; ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{ let formatted = format!($msg, $($args),*); - let thread = ::std::thread::current(); - let full = format!("{thid:?} {file}:{line}: {msg}", - thid = thread.id(), + let full = format!("{file}:{line}: {msg}", file = file!(), line = line!(), msg = &formatted); @@ -24,9 +22,7 @@ macro_rules! warn { }; ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{ let formatted = format!($msg, $($args),*); - let thread = ::std::thread::current(); - let full = format!("{thid:?} {file}:{line}: {msg}", - thid = thread.id(), + let full = format!("{file}:{line}: {msg}", file = file!(), line = line!(), msg = &formatted); diff --git a/src/mimefactory.rs b/src/mimefactory.rs index a541b5997..b4f73f95f 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -240,7 +240,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .filter(|(_, addr)| addr != &self_addr) { res.push(( - Peerstate::from_addr(self.context, &self.context.sql, addr).await, + Peerstate::from_addr(self.context, addr).await, addr.as_str(), )); } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 707d7afeb..7b1d8388c 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -886,7 +886,7 @@ async fn update_gossip_peerstates( .unwrap() .contains(&header.addr.to_lowercase()) { - let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr).await; + let mut peerstate = Peerstate::from_addr(context, &header.addr).await; if let Some(ref mut peerstate) = peerstate { peerstate.apply_gossip(header, message_time); peerstate.save_to_db(&context.sql, false).await?; diff --git a/src/peerstate.rs b/src/peerstate.rs index a2cbc6ef1..beec1bbf5 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -144,7 +144,7 @@ impl<'a> Peerstate<'a> { res } - pub async fn from_addr(context: &'a Context, _sql: &Sql, addr: &str) -> Option> { + pub async fn from_addr(context: &'a Context, addr: &str) -> Option> { let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;"; Self::from_stmt(context, query, paramsv![addr]).await } @@ -510,7 +510,7 @@ mod tests { "failed to save to db" ); - let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr) + let peerstate_new = Peerstate::from_addr(&ctx.ctx, addr) .await .expect("failed to load peerstate from db"); @@ -586,7 +586,7 @@ mod tests { "failed to save" ); - let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr) + let peerstate_new = Peerstate::from_addr(&ctx.ctx, addr) .await .expect("failed to load peerstate from db"); diff --git a/src/scheduler.rs b/src/scheduler.rs index 7f86d03e4..70b99220f 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -217,7 +217,6 @@ impl Scheduler { task::spawn(async move { smtp_loop(ctx1, smtp_handlers).await }); info!(ctx, "scheduler is running"); - println!("RUN DONE"); } fn set_probe_network(&mut self, val: bool) { diff --git a/src/securejoin.rs b/src/securejoin.rs index 6998c4f7b..f6c445009 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -349,9 +349,7 @@ async fn fingerprint_equals_sender( if contacts.len() == 1 { if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await { - if let Some(peerstate) = - Peerstate::from_addr(context, &context.sql, contact.get_addr()).await - { + if let Some(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await { let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref()); if peerstate.public_key_fingerprint.is_some() && &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap() diff --git a/src/sql.rs b/src/sql.rs index 0d25d7757..0d84580e5 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -17,6 +17,16 @@ use crate::dc_tools::*; use crate::param::*; use crate::peerstate::*; +#[macro_export] +macro_rules! paramsv { + () => { + Vec::new() + }; + ($($param:expr),+ $(,)?) => { + vec![$(&$param as &dyn $crate::ToSql),+] + }; +} + #[derive(Debug, Fail)] pub enum Error { #[fail(display = "Sqlite Error: {:?}", _0)] @@ -1295,7 +1305,7 @@ async fn open( ) .await?; for addr in &addrs { - if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, addr).await { + if let Some(ref mut peerstate) = Peerstate::from_addr(context, addr).await { peerstate.recalc_fingerprint(); peerstate.save_to_db(sql, false).await?; }