diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index fa13b6e5fa..91001b7442 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1113,6 +1113,28 @@ void dc_set_draft (dc_context_t* context, uint32_t ch uint32_t dc_add_device_msg (dc_context_t* context, dc_msg_t* msg); +/** + * Add a message only one time to the device-chat. + * The device-message is defined by a name. + * If a message with the same name was added before, + * the message is not added again. + * Use dc_add_device_msg() to add device-messages unconditionally. + * + * Sends the event #DC_EVENT_MSGS_CHANGED on success. + * + * @memberof dc_context_t + * @param context The context as created by dc_context_new(). + * @param label A unique name for the message to add. + * The label is typically not displayed to the user and + * must be created from the characters `A-Z`, `a-z`, `0-9`, `_` or `-`. + * @param msg Message to be added to the device-chat. + * The message appears to the user as an incoming message. + * @return The ID of the added message, + * this might be the id of an older message with the same name. + */ +uint32_t dc_add_device_msg_once (dc_context_t* context, const char* label, dc_msg_t* msg); + + /** * Get draft for a chat, if any. * See dc_set_draft() for more details about drafts. diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 6c52f09ca4..b7895549d0 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -828,6 +828,27 @@ pub unsafe extern "C" fn dc_add_device_msg(context: *mut dc_context_t, msg: *mut .unwrap_or(0) } +#[no_mangle] +pub unsafe extern "C" fn dc_add_device_msg_once( + context: *mut dc_context_t, + label: *const libc::c_char, + msg: *mut dc_msg_t, +) -> u32 { + if context.is_null() || label.is_null() || msg.is_null() { + eprintln!("ignoring careless call to dc_add_device_msg_once()"); + return 0; + } + let ffi_context = &mut *context; + let ffi_msg = &mut *msg; + ffi_context + .with_inner(|ctx| { + chat::add_device_msg_once(ctx, &to_string_lossy(label), &mut ffi_msg.message) + .unwrap_or_log_default(ctx, "Failed to add device message once") + }) + .map(|msg_id| msg_id.to_u32()) + .unwrap_or(0) +} + #[no_mangle] pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) -> *mut dc_msg_t { if context.is_null() { diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 750c164140..a71924bf54 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -21,7 +21,7 @@ def test_getinfo(self, acfactory): d = ac1.get_info() assert d["arch"] assert d["number_of_chats"] == "0" - assert d["bcc_self"] == "1" + assert d["bcc_self"] == "0" def test_is_not_configured(self, acfactory): ac1 = acfactory.get_unconfigured_account() @@ -43,7 +43,7 @@ def test_has_savemime(self, acfactory): def test_has_bccself(self, acfactory): ac1 = acfactory.get_unconfigured_account() assert "bcc_self" in ac1.get_config("sys.config_keys").split() - assert ac1.get_config("bcc_self") == "1" + assert ac1.get_config("bcc_self") == "0" def test_selfcontact_if_unconfigured(self, acfactory): ac1 = acfactory.get_unconfigured_account() @@ -405,6 +405,9 @@ def test_one_account_send_bcc_setting(self, acfactory, lp): wait_successful_IMAP_SMTP_connection(ac1) wait_configuration_progress(ac1, 1000) + lp.sec("ac1: setting bcc_self=1") + ac1.set_config("bcc_self", "1") + lp.sec("send out message with bcc to ourselves") msg_out = chat.send_text("message2") ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") diff --git a/src/chat.rs b/src/chat.rs index 6accc8ef62..aa96657553 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1,6 +1,7 @@ use std::path::{Path, PathBuf}; use itertools::Itertools; +use num_traits::FromPrimitive; use crate::blob::{BlobErrorKind, BlobObject}; use crate::chatlist::*; @@ -1012,7 +1013,8 @@ pub fn get_chat_msgs( Ok(ret) }; let success = if chat_id == DC_CHAT_ID_DEADDROP { - let show_emails = context.get_config_int(Config::ShowEmails); + let show_emails = + ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); context.sql.query_map( concat!( "SELECT m.id AS id, m.timestamp AS timestamp", @@ -1029,7 +1031,7 @@ pub fn get_chat_msgs( " AND m.msgrmsg>=?", " ORDER BY m.timestamp,m.id;" ), - params![if show_emails == 2 { 0 } else { 1 }], + params![if show_emails == ShowEmails::All { 0 } else { 1 }], process_row, process_rows, ) @@ -1927,16 +1929,41 @@ pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef) -> (u32, } pub fn add_device_msg(context: &Context, msg: &mut Message) -> Result { + let label = format!("info-{}", dc_create_id()); + add_device_msg_once(context, &label, msg) +} + +pub fn add_device_msg_once( + context: &Context, + label: &str, + msg: &mut Message, +) -> Result { let (chat_id, _blocked) = create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?; let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); + // chat_id has an sql-index so it makes sense to add this although redundant + if let Ok(msg_id) = context.sql.query_row( + "SELECT id FROM msgs WHERE chat_id=? AND label=?", + params![chat_id, label], + |row| { + let msg_id: MsgId = row.get(0)?; + Ok(msg_id) + }, + ) { + info!( + context, + "device-message {} already exist as {}", label, msg_id + ); + return Ok(msg_id); + } + prepare_msg_blob(context, msg)?; unarchive(context, chat_id)?; context.sql.execute( - "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ - VALUES (?,?,?, ?,?,?, ?,?,?);", + "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid,label) \ + VALUES (?,?,?, ?,?,?, ?,?,?,?);", params![ chat_id, DC_CONTACT_ID_DEVICE, @@ -1947,12 +1974,14 @@ pub fn add_device_msg(context: &Context, msg: &mut Message) -> Result Result { } } } + // no maybe_add_bcc_self_device_msg() here. + // the ui shows the dialog with the setup code on this device, + // it would be too much noise to have two things popping up at the same time. + // maybe_add_bcc_self_device_msg() is called on the other device + // once the transfer is completed. + maybe_add_bcc_self_device_msg(context)?; Ok(setup_code) } @@ -230,6 +236,21 @@ pub fn create_setup_code(_context: &Context) -> String { ret } +fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { + if !context.sql.get_raw_config_bool(context, "bcc_self") { + let mut msg = Message::new(Viewtype::Text); + // TODO: define this as a stockstring once the wording is settled. + msg.text = Some( + "It seems you are using multiple devices with Delta Chat. Great!\n\n\ + If you also want to synchronize outgoing messages accross all devices, \ + go to the settings and enable \"Send copy to self\"." + .to_string(), + ); + chat::add_device_msg_once(context, "bcc-self-hint", &mut msg)?; + } + Ok(()) +} + pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> { ensure!(!msg_id.is_special(), "wrong id"); @@ -244,6 +265,7 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) let sc = normalize_setup_code(setup_code); let armored_key = decrypt_setup_file(context, &sc, file)?; set_self_key(context, &armored_key, true, true)?; + maybe_add_bcc_self_device_msg(context)?; Ok(()) } else { diff --git a/src/sql.rs b/src/sql.rs index 2b9fa2aac7..b1f2102776 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -5,6 +5,7 @@ use std::time::Duration; use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS}; use thread_local_object::ThreadLocal; +use crate::constants::ShowEmails; use crate::context::Context; use crate::dc_tools::*; use crate::error::{Error, Result}; @@ -348,7 +349,7 @@ fn open( } if !readonly { - let mut exists_before_update = 0; + let mut exists_before_update = false; let mut dbversion_before_update = 0; /* Init tables to dbversion=0 */ if !sql.table_exists("config") { @@ -478,7 +479,7 @@ fn open( sql.set_raw_config_int(context, "dbversion", 0)?; } } else { - exists_before_update = 1; + exists_before_update = true; dbversion_before_update = sql .get_raw_config_int(context, "dbversion") .unwrap_or_default(); @@ -488,6 +489,7 @@ fn open( // this should be done before updates that use high-level objects that // rely themselves on the low-level structure. // -------------------------------------------------------------------- + let mut dbversion = dbversion_before_update; let mut recalc_fingerprints = 0; let mut update_file_paths = 0; @@ -588,6 +590,8 @@ fn open( } if dbversion < 27 { info!(context, "[migration] v27"); + // chat.id=1 and chat.id=2 are the old deaddrops, + // the current ones are defined by chats.blocked=2 sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?; sql.execute( "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);", @@ -653,6 +657,9 @@ fn open( params![], )?; if dbversion_before_update == 34 { + // migrate database from the use of verified-flags to verified_key, + // _only_ version 34 (0.17.0) has the fields public_key_verified and gossip_key_verified + // this block can be deleted in half a year or so (created 5/2018) sql.execute( "UPDATE acpeerstates SET verified_key=gossip_key, verified_key_fingerprint=gossip_key_fingerprint WHERE gossip_key_verified=2;", params![] @@ -682,6 +689,8 @@ fn open( } if dbversion < 42 { info!(context, "[migration] v42"); + // older versions set the txt-field to the filenames, for debugging and fulltext search. + // to allow text+attachment compound messages, we need to reset these fields. sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?; dbversion = 42; sql.set_raw_config_int(context, "dbversion", 42)?; @@ -735,14 +744,19 @@ fn open( } if dbversion < 50 { info!(context, "[migration] v50"); - if 0 != exists_before_update { - sql.set_raw_config_int(context, "show_emails", 2)?; + // installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly; + // keep this default and use DC_SHOW_EMAILS_NO + // only for new installations + if exists_before_update { + sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32)?; } dbversion = 50; sql.set_raw_config_int(context, "dbversion", 50)?; } if dbversion < 53 { info!(context, "[migration] v53"); + // the messages containing _only_ locations + // are also added to the database as _hidden_. sql.execute( "CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);", params![] @@ -790,9 +804,26 @@ fn open( "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;", params![], )?; - sql.set_raw_config_int(context, "dbversion", 55)?; } + if dbversion < 57 { + info!(context, "[migration] v57"); + // label is a unique name and is currently used for device-messages only. + // in contrast to rfc724_mid and other fields, the label is generated on the device + // and allows reliable identifications this way. + sql.execute( + "ALTER TABLE msgs ADD COLUMN label TEXT DEFAULT '';", + params![], + )?; + if exists_before_update && sql.get_raw_config_int(context, "bcc_self").is_none() { + sql.set_raw_config_int(context, "bcc_self", 1)?; + } + sql.set_raw_config_int(context, "dbversion", 57)?; + } + + // (2) updates that require high-level objects + // (the structure is complete now and all objects are usable) + // -------------------------------------------------------------------- if 0 != recalc_fingerprints { info!(context, "[migration] recalc fingerprints");