diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 58efa0557..1eb500e33 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2414,6 +2414,22 @@ dc_array_t* dc_get_locations (dc_context_t* context, uint32_t cha void dc_delete_all_locations (dc_context_t* context); +/** + * Get last error string. + * + * This is the same error string as logged via #DC_EVENT_ERROR, + * however, using this function avoids race conditions + * if the failing function is called in another thread than dc_get_next_event(). + * + * @memberof dc_context_t + * @param context The context object. + * @return Last error or an empty string if there is no last error. + * NULL is never returned. + * The returned value must be released using dc_str_unref() after usage. + */ +char* dc_get_last_error (dc_context_t* context); + + /** * Release a string returned by another deltachat-core function. * - Strings returned by any deltachat-core-function diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index f1542786b..3037882b7 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -2206,6 +2206,16 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) { }); } +#[no_mangle] +pub unsafe extern "C" fn dc_get_last_error(context: *mut dc_context_t) -> *mut libc::c_char { + if context.is_null() { + eprintln!("ignoring careless call to dc_get_last_error()"); + return "".strdup(); + } + let ctx = &*context; + block_on(ctx.get_last_error()).strdup() +} + // dc_array_t pub type dc_array_t = dc_array::dc_array_t; diff --git a/src/context.rs b/src/context.rs index a9b542886..ce3ddec38 100644 --- a/src/context.rs +++ b/src/context.rs @@ -76,6 +76,11 @@ pub struct InnerContext { pub(crate) id: u32, creation_time: SystemTime, + + /// The text of the last error logged and emitted as an event. + /// If the ui wants to display an error after a failure, + /// `last_error` should be used to avoid races with the event thread. + pub(crate) last_error: RwLock, } #[derive(Debug)] @@ -147,6 +152,7 @@ impl Context { quota: RwLock::new(None), creation_time: std::time::SystemTime::now(), last_full_folder_scan: Mutex::new(None), + last_error: RwLock::new("".to_string()), }; let ctx = Context { diff --git a/src/log.rs b/src/log.rs index 0df7792f6..f9acc3ecc 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,6 +1,7 @@ //! # Logging. use crate::context::Context; +use async_std::task::block_on; #[macro_export] macro_rules! info { @@ -39,10 +40,28 @@ macro_rules! error { }; ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{ let formatted = format!($msg, $($args),*); + $ctx.set_last_error(&formatted); $ctx.emit_event($crate::EventType::Error(formatted)); }}; } +impl Context { + /// Set last error string. + /// Implemented as blocking as used from macros in different, not always async blocks. + pub fn set_last_error(&self, error: &str) { + block_on(async move { + let mut last_error = self.last_error.write().await; + *last_error = error.to_string(); + }); + } + + /// Get last error string. + pub async fn get_last_error(&self) -> String { + let last_error = &*self.last_error.read().await; + last_error.clone() + } +} + pub trait LogExt where Self: std::marker::Sized, @@ -134,3 +153,31 @@ impl LogExt for Result { self } } + +#[cfg(test)] +mod tests { + use crate::test_utils::TestContext; + use anyhow::Result; + + #[async_std::test] + async fn test_get_last_error() -> Result<()> { + let t = TestContext::new().await; + + assert_eq!(t.get_last_error().await, ""); + + error!(t, "foo-error"); + assert_eq!(t.get_last_error().await, "foo-error"); + + warn!(t, "foo-warning"); + assert_eq!(t.get_last_error().await, "foo-error"); + + info!(t, "foo-info"); + assert_eq!(t.get_last_error().await, "foo-error"); + + error!(t, "bar-error"); + error!(t, "baz-error"); + assert_eq!(t.get_last_error().await, "baz-error"); + + Ok(()) + } +}