diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index c52cce2d6..bf19ba09d 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -403,12 +403,14 @@ int dc_set_config (dc_context_t* context, const char* char* dc_get_config (dc_context_t* context, const char* key); /** - * Set (translated) stock string + * Set stock string translation. + * + * The function will emit warnings if it returns an error state. * * @param context The context object - * @param stock_id the integer id of the stock message + * @param stock_id the integer id of the stock message (DC_STR_*) * @param stock_msg the message to be used - * @return void + * @return int (==0 on error, 1 on success) */ int dc_set_stock_translation(dc_context_t* context, uint32_t, const char* value); diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index f73bcf0b2..dc89cf167 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -25,6 +25,7 @@ use num_traits::{FromPrimitive, ToPrimitive}; use deltachat::contact::Contact; use deltachat::context::Context; use deltachat::dc_tools::{as_path, as_str, dc_strdup, to_string_lossy, OsStrExt, StrExt}; +use deltachat::stock::StockMessage; use deltachat::*; // as C lacks a good and portable error handling, @@ -350,19 +351,25 @@ pub unsafe extern "C" fn dc_set_stock_translation( ) -> libc::c_int { if context.is_null() || stock_msg.is_null() { eprintln!("ignoring careless call to dc_set_stock_string"); - return; + return 0; } - let msg = as_str(stock_msg); + let msg = as_str(stock_msg).to_string(); let ffi_context = &*context; ffi_context - .with_inner(|ctx| match ctx.set_stock_translation(stock_id, msg) { - Ok(()) => 1, - Err(err) => { - warn!(ctx, "could not set translation: {}", err); + .with_inner(|ctx| match StockMessage::from_u32(stock_id) { + Some(id) => match ctx.set_stock_translation(id, msg) { + Ok(()) => 1, + Err(err) => { + warn!(ctx, "set_stock_translation failed: {}", err); + 0 + } + }, + None => { + warn!(ctx, "invalid stock message id {}", stock_id); 0 } }) - .unwrap_or(()) + .unwrap_or(0) } #[no_mangle] diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 11c9c3e23..b34eeaa8e 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -71,6 +71,18 @@ class Account(object): d[key.lower()] = value return d + def set_stock_translation(self, id, string): + """ set stock translation string. + + :param id: id of stock string (const.DC_STR_*) + :param value: string to set as new transalation + :returns: None + """ + string = string.encode("utf8") + res = lib.dc_set_stock_translation(self._dc_context, id, string) + if res == 0: + raise ValueError("could not set translation string") + def set_config(self, name, value): """ set configuration values. diff --git a/python/src/deltachat/const.py b/python/src/deltachat/const.py index cb7aec707..99e387fe1 100644 --- a/python/src/deltachat/const.py +++ b/python/src/deltachat/const.py @@ -85,14 +85,61 @@ DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061 DC_EVENT_GET_STRING = 2091 DC_EVENT_FILE_COPIED = 2055 DC_EVENT_IS_OFFLINE = 2081 +DC_STR_SELFNOTINGRP = 21 DC_PROVIDER_STATUS_OK = 1 DC_PROVIDER_STATUS_PREPARATION = 2 DC_PROVIDER_STATUS_BROKEN = 3 +DC_STR_NOMESSAGES = 1 +DC_STR_SELF = 2 +DC_STR_DRAFT = 3 +DC_STR_MEMBER = 4 +DC_STR_CONTACT = 6 +DC_STR_VOICEMESSAGE = 7 +DC_STR_DEADDROP = 8 +DC_STR_IMAGE = 9 +DC_STR_VIDEO = 10 +DC_STR_AUDIO = 11 +DC_STR_FILE = 12 +DC_STR_STATUSLINE = 13 +DC_STR_NEWGROUPDRAFT = 14 +DC_STR_MSGGRPNAME = 15 +DC_STR_MSGGRPIMGCHANGED = 16 +DC_STR_MSGADDMEMBER = 17 +DC_STR_MSGDELMEMBER = 18 +DC_STR_MSGGROUPLEFT = 19 +DC_STR_GIF = 23 +DC_STR_ENCRYPTEDMSG = 24 +DC_STR_E2E_AVAILABLE = 25 +DC_STR_ENCR_TRANSP = 27 +DC_STR_ENCR_NONE = 28 +DC_STR_CANTDECRYPT_MSG_BODY = 29 +DC_STR_FINGERPRINTS = 30 +DC_STR_READRCPT = 31 +DC_STR_READRCPT_MAILBODY = 32 +DC_STR_MSGGRPIMGDELETED = 33 +DC_STR_E2E_PREFERRED = 34 +DC_STR_CONTACT_VERIFIED = 35 +DC_STR_CONTACT_NOT_VERIFIED = 36 +DC_STR_CONTACT_SETUP_CHANGED = 37 +DC_STR_ARCHIVEDCHATS = 40 +DC_STR_STARREDMSGS = 41 +DC_STR_AC_SETUP_MSG_SUBJECT = 42 +DC_STR_AC_SETUP_MSG_BODY = 43 +DC_STR_SELFTALK_SUBTITLE = 50 +DC_STR_CANNOT_LOGIN = 60 +DC_STR_SERVER_RESPONSE = 61 +DC_STR_MSGACTIONBYUSER = 62 +DC_STR_MSGACTIONBYME = 63 +DC_STR_MSGLOCATIONENABLED = 64 +DC_STR_MSGLOCATIONDISABLED = 65 +DC_STR_LOCATION = 66 +DC_STR_STICKER = 67 +DC_STR_COUNT = 67 # end const generated def read_event_defines(f): - rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|' + rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_STR|' r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*') for line in f: m = rex.match(line) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 05f41ae72..79c7871bb 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -148,6 +148,27 @@ class TestOfflineChat: chat.set_name("title2") assert chat.get_name() == "title2" + def test_group_chat_creation_with_translation(self, ac1): + ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s") + ac1._evlogger.consume_events() + with pytest.raises(ValueError): + ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %2$s") + ac1._evlogger.get_matching("DC_EVENT_WARNING") + with pytest.raises(ValueError): + ac1.set_stock_translation(500, "xyz %1$s") + ac1._evlogger.get_matching("DC_EVENT_WARNING") + contact1 = ac1.create_contact("some1@hello.com", name="some1") + contact2 = ac1.create_contact("some2@hello.com", name="some2") + chat = ac1.create_group_chat(name="title1") + chat.add_contact(contact1) + chat.add_contact(contact2) + assert chat.get_name() == "title1" + assert contact1 in chat.get_contacts() + assert contact2 in chat.get_contacts() + assert not chat.is_promoted() + msg = chat.get_draft() + assert msg.text == "xyz title1" + @pytest.mark.parametrize("verified", [True, False]) def test_group_chat_qr(self, acfactory, ac1, verified): ac2 = acfactory.get_configured_offline_account() diff --git a/src/constants.rs b/src/constants.rs index 309379abf..fb1c486d7 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -257,7 +257,7 @@ const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1; const DC_SHOW_EMAILS_ALL: usize = 2; // TODO: Strings need some doumentation about used placeholders. -// These constants are used to request strings using #DC_EVENT_GET_STRING. +// These constants are used to set stock translation strings const DC_STR_NOMESSAGES: usize = 1; const DC_STR_SELF: usize = 2; diff --git a/src/context.rs b/src/context.rs index d85e0c16d..2d321e58d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -63,7 +63,7 @@ pub struct Context { pub running_state: Arc>, /// Mutex to avoid generating the key for the user more than once. pub generating_key_mutex: Mutex<()>, - pub translated_stockstrings: HashMap, + pub translated_stockstrings: Arc>>, } #[derive(Debug, PartialEq, Eq)] @@ -146,7 +146,7 @@ impl Context { probe_imap_network: Arc::new(RwLock::new(false)), perform_inbox_jobs_needed: Arc::new(RwLock::new(false)), generating_key_mutex: Mutex::new(()), - translated_stockstrings: HashMap::new(), + translated_stockstrings: Arc::new(RwLock::new(HashMap::new())), }; ensure!( diff --git a/src/imex.rs b/src/imex.rs index 7b15f8add..3d9db3b74 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -805,19 +805,12 @@ mod tests { assert!(msg.contains("-----END PGP MESSAGE-----\n")); } - fn ac_setup_msg_cb(ctx: &Context, evt: Event) -> libc::uintptr_t { - match evt { - Event::GetString { - id: StockMessage::AcSetupMsgBody, - .. - } => unsafe { "hello\r\nthere".strdup() as usize }, - _ => logging_cb(ctx, evt), - } - } - #[test] fn otest_render_setup_file_newline_replace() { - let t = test_context(Some(Box::new(ac_setup_msg_cb))); + let t = dummy_context(); + t.ctx + .set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string()) + .unwrap(); configure_alice_keypair(&t.ctx); let msg = render_setup_file(&t.ctx, "pw").unwrap(); println!("{}", &msg); diff --git a/src/lib.rs b/src/lib.rs index 07f98144c..219c07076 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,7 @@ pub mod qr; pub mod securejoin; mod smtp; pub mod sql; -mod stock; +pub mod stock; mod token; #[macro_use] mod wrapmime; diff --git a/src/stock.rs b/src/stock.rs index 5e0004ba8..cf9bb77c3 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -125,7 +125,7 @@ impl Context { /// Set the stock string for the [StockMessage]. /// pub fn set_stock_translation( - &mut self, + &self, id: StockMessage, stockstring: String, ) -> Result<(), Error> { @@ -143,7 +143,10 @@ impl Context { id.fallback() ); } - self.translated_stockstrings.insert(id as usize, stockstring); + self.translated_stockstrings + .write() + .unwrap() + .insert(id as usize, stockstring); Ok(()) } @@ -152,8 +155,13 @@ impl Context { /// Return a translation (if it was set with set_stock_translation before) /// or a default (English) string. pub fn stock_str(&self, id: StockMessage) -> Cow { - match self.translated_stockstrings.get(&(id as usize)) { - Some(x) => Cow::Borrowed(x), + match self + .translated_stockstrings + .read() + .unwrap() + .get(&(id as usize)) + { + Some(ref x) => Cow::Owned(x.to_string()), None => Cow::Borrowed(id.fallback()), } } @@ -274,7 +282,7 @@ mod tests { #[test] fn test_set_stock_translation() { - let mut t = dummy_context(); + let t = dummy_context(); t.ctx .set_stock_translation(StockMessage::NoMessages, "xyz".to_string()) .unwrap(); @@ -283,7 +291,7 @@ mod tests { #[test] fn test_set_stock_translation_wrong_replacements() { - let mut t = dummy_context(); + let t = dummy_context(); assert!(t .ctx .set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string())