mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
feat: add AccountsChanged and AccountsItemChanged events (#6118)
- **feat: add `AccountsChanged` and `AccountsItemChanged` events** - **emit event and add tests** closes #6106 TODO: - [x] test receiving synced config from second device - [x] bug: investigate how to delay the configuration event until it is actually configured - because desktop gets the event but still shows account as if it was unconfigured, maybe event is emitted before the value is written to the database? - [x] update node bindings constants
This commit is contained in:
@@ -6413,6 +6413,25 @@ void dc_event_unref(dc_event_t* event);
|
||||
|
||||
#define DC_EVENT_CHATLIST_ITEM_CHANGED 2301
|
||||
|
||||
/**
|
||||
* Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
|
||||
*
|
||||
* This event is only emitted by the account manager.
|
||||
*/
|
||||
|
||||
#define DC_EVENT_ACCOUNTS_CHANGED 2302
|
||||
|
||||
/**
|
||||
* Inform that an account property that might be shown in the account list changed, namely:
|
||||
* - is_configured (see dc_is_configured())
|
||||
* - displayname
|
||||
* - selfavatar
|
||||
* - private_tag
|
||||
*
|
||||
* This event is emitted from the account whose property changed.
|
||||
*/
|
||||
|
||||
#define DC_EVENT_ACCOUNTS_ITEM_CHANGED 2303
|
||||
|
||||
/**
|
||||
* Inform that some events have been skipped due to event channel overflow.
|
||||
|
||||
@@ -569,6 +569,8 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
|
||||
EventType::AccountsBackgroundFetchDone => 2200,
|
||||
EventType::ChatlistChanged => 2300,
|
||||
EventType::ChatlistItemChanged { .. } => 2301,
|
||||
EventType::AccountsChanged => 2302,
|
||||
EventType::AccountsItemChanged => 2303,
|
||||
EventType::EventChannelOverflow { .. } => 2400,
|
||||
#[allow(unreachable_patterns)]
|
||||
#[cfg(test)]
|
||||
@@ -601,8 +603,10 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::ConfigSynced { .. }
|
||||
| EventType::IncomingMsgBunch { .. }
|
||||
| EventType::ErrorSelfNotInGroup(_)
|
||||
| EventType::AccountsBackgroundFetchDone => 0,
|
||||
EventType::ChatlistChanged => 0,
|
||||
| EventType::AccountsBackgroundFetchDone
|
||||
| EventType::ChatlistChanged
|
||||
| EventType::AccountsChanged
|
||||
| EventType::AccountsItemChanged => 0,
|
||||
EventType::IncomingReaction { contact_id, .. }
|
||||
| EventType::IncomingWebxdcNotify { contact_id, .. } => contact_id.to_u32() as libc::c_int,
|
||||
EventType::MsgsChanged { chat_id, .. }
|
||||
@@ -676,6 +680,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::AccountsBackgroundFetchDone
|
||||
| EventType::ChatlistChanged
|
||||
| EventType::ChatlistItemChanged { .. }
|
||||
| EventType::AccountsChanged
|
||||
| EventType::AccountsItemChanged
|
||||
| EventType::ConfigSynced { .. }
|
||||
| EventType::ChatModified(_)
|
||||
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
|
||||
@@ -751,6 +757,8 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
||||
| EventType::IncomingMsgBunch { .. }
|
||||
| EventType::ChatlistItemChanged { .. }
|
||||
| EventType::ChatlistChanged
|
||||
| EventType::AccountsChanged
|
||||
| EventType::AccountsItemChanged
|
||||
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
|
||||
| EventType::EventChannelOverflow { .. } => ptr::null_mut(),
|
||||
EventType::ConfigureProgress { comment, .. } => {
|
||||
|
||||
@@ -285,6 +285,20 @@ pub enum EventType {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatlistItemChanged { chat_id: Option<u32> },
|
||||
|
||||
/// Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
|
||||
///
|
||||
/// This event is only emitted by the account manager
|
||||
AccountsChanged,
|
||||
|
||||
/// Inform that an account property that might be shown in the account list changed, namely:
|
||||
/// - is_configured (see is_configured())
|
||||
/// - displayname
|
||||
/// - selfavatar
|
||||
/// - private_tag
|
||||
///
|
||||
/// This event is emitted from the account whose property changed.
|
||||
AccountsItemChanged,
|
||||
|
||||
/// Inform than some events have been skipped due to event channel overflow.
|
||||
EventChannelOverflow { n: u64 },
|
||||
}
|
||||
@@ -426,6 +440,8 @@ impl From<CoreEventType> for EventType {
|
||||
},
|
||||
CoreEventType::ChatlistChanged => ChatlistChanged,
|
||||
CoreEventType::EventChannelOverflow { n } => EventChannelOverflow { n },
|
||||
CoreEventType::AccountsChanged => AccountsChanged,
|
||||
CoreEventType::AccountsItemChanged => AccountsItemChanged,
|
||||
#[allow(unreachable_patterns)]
|
||||
#[cfg(test)]
|
||||
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
|
||||
|
||||
@@ -61,6 +61,8 @@ class EventType(str, Enum):
|
||||
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
|
||||
CHATLIST_CHANGED = "ChatlistChanged"
|
||||
CHATLIST_ITEM_CHANGED = "ChatlistItemChanged"
|
||||
ACCOUNTS_CHANGED = "AccountsChanged"
|
||||
ACCOUNTS_ITEM_CHANGED = "AccountsItemChanged"
|
||||
CONFIG_SYNCED = "ConfigSynced"
|
||||
WEBXDC_REALTIME_DATA = "WebxdcRealtimeData"
|
||||
WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED = "WebxdcRealtimeAdvertisementReceived"
|
||||
|
||||
29
deltachat-rpc-client/tests/test_account_events.py
Normal file
29
deltachat-rpc-client/tests/test_account_events.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from deltachat_rpc_client import EventType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from deltachat_rpc_client.pytestplugin import ACFactory
|
||||
|
||||
|
||||
def test_event_on_configuration(acfactory: ACFactory) -> None:
|
||||
"""
|
||||
Test if ACCOUNTS_ITEM_CHANGED event is emitted on configure
|
||||
"""
|
||||
|
||||
account = acfactory.new_preconfigured_account()
|
||||
account.clear_all_events()
|
||||
assert not account.is_configured()
|
||||
future = account.configure.future()
|
||||
while True:
|
||||
event = account.wait_for_event()
|
||||
if event.kind == EventType.ACCOUNTS_ITEM_CHANGED:
|
||||
break
|
||||
assert account.is_configured()
|
||||
|
||||
future()
|
||||
|
||||
|
||||
# other tests are written in rust: src/tests/account_events.rs
|
||||
@@ -31,6 +31,8 @@ module.exports = {
|
||||
DC_DOWNLOAD_IN_PROGRESS: 1000,
|
||||
DC_DOWNLOAD_UNDECIPHERABLE: 30,
|
||||
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: 2200,
|
||||
DC_EVENT_ACCOUNTS_CHANGED: 2302,
|
||||
DC_EVENT_ACCOUNTS_ITEM_CHANGED: 2303,
|
||||
DC_EVENT_CHANNEL_OVERFLOW: 2400,
|
||||
DC_EVENT_CHATLIST_CHANGED: 2300,
|
||||
DC_EVENT_CHATLIST_ITEM_CHANGED: 2301,
|
||||
|
||||
@@ -44,5 +44,7 @@ module.exports = {
|
||||
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
|
||||
2300: 'DC_EVENT_CHATLIST_CHANGED',
|
||||
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED',
|
||||
2302: 'DC_EVENT_ACCOUNTS_CHANGED',
|
||||
2303: 'DC_EVENT_ACCOUNTS_ITEM_CHANGED',
|
||||
2400: 'DC_EVENT_CHANNEL_OVERFLOW'
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ export enum C {
|
||||
DC_DOWNLOAD_IN_PROGRESS = 1000,
|
||||
DC_DOWNLOAD_UNDECIPHERABLE = 30,
|
||||
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200,
|
||||
DC_EVENT_ACCOUNTS_CHANGED = 2302,
|
||||
DC_EVENT_ACCOUNTS_ITEM_CHANGED = 2303,
|
||||
DC_EVENT_CHANNEL_OVERFLOW = 2400,
|
||||
DC_EVENT_CHATLIST_CHANGED = 2300,
|
||||
DC_EVENT_CHATLIST_ITEM_CHANGED = 2301,
|
||||
@@ -353,5 +355,7 @@ export const EventId2EventName: { [key: number]: string } = {
|
||||
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
|
||||
2300: 'DC_EVENT_CHATLIST_CHANGED',
|
||||
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED',
|
||||
2302: 'DC_EVENT_ACCOUNTS_CHANGED',
|
||||
2303: 'DC_EVENT_ACCOUNTS_ITEM_CHANGED',
|
||||
2400: 'DC_EVENT_CHANNEL_OVERFLOW',
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ impl Accounts {
|
||||
ctx.open("".to_string()).await?;
|
||||
|
||||
self.accounts.insert(account_config.id, ctx);
|
||||
self.emit_event(EventType::AccountsChanged);
|
||||
|
||||
Ok(account_config.id)
|
||||
}
|
||||
@@ -156,6 +157,7 @@ impl Accounts {
|
||||
.build()
|
||||
.await?;
|
||||
self.accounts.insert(account_config.id, ctx);
|
||||
self.emit_event(EventType::AccountsChanged);
|
||||
|
||||
Ok(account_config.id)
|
||||
}
|
||||
@@ -190,6 +192,7 @@ impl Accounts {
|
||||
.context("failed to remove account data")?;
|
||||
}
|
||||
self.config.remove_account(id).await?;
|
||||
self.emit_event(EventType::AccountsChanged);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -791,6 +791,12 @@ impl Context {
|
||||
self.sql.set_raw_config(key.as_ref(), value).await?;
|
||||
}
|
||||
}
|
||||
if matches!(
|
||||
key,
|
||||
Config::Displayname | Config::Selfavatar | Config::PrivateTag
|
||||
) {
|
||||
self.emit_event(EventType::AccountsItemChanged);
|
||||
}
|
||||
if key.is_synced() {
|
||||
self.emit_event(EventType::ConfigSynced { key });
|
||||
}
|
||||
|
||||
@@ -36,10 +36,10 @@ use crate::message::Message;
|
||||
use crate::oauth2::get_oauth2_addr;
|
||||
use crate::provider::{Protocol, Socket, UsernamePattern};
|
||||
use crate::smtp::Smtp;
|
||||
use crate::stock_str;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::tools::time;
|
||||
use crate::{chat, e2ee, provider};
|
||||
use crate::{stock_str, EventType};
|
||||
use deltachat_contact_tools::addr_cmp;
|
||||
|
||||
macro_rules! progress {
|
||||
@@ -486,6 +486,7 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
|
||||
update_device_chats_handle.await??;
|
||||
|
||||
ctx.sql.set_raw_config_bool("configured", true).await?;
|
||||
ctx.emit_event(EventType::AccountsItemChanged);
|
||||
|
||||
Ok(configured_param)
|
||||
}
|
||||
|
||||
@@ -344,6 +344,20 @@ pub enum EventType {
|
||||
chat_id: Option<ChatId>,
|
||||
},
|
||||
|
||||
/// Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
|
||||
///
|
||||
/// This event is only emitted by the account manager
|
||||
AccountsChanged,
|
||||
|
||||
/// Inform that an account property that might be shown in the account list changed, namely:
|
||||
/// - is_configured (see [crate::context::Context::is_configured])
|
||||
/// - displayname
|
||||
/// - selfavatar
|
||||
/// - private_tag
|
||||
///
|
||||
/// This event is emitted from the account whose property changed.
|
||||
AccountsItemChanged,
|
||||
|
||||
/// Event for using in tests, e.g. as a fence between normally generated events.
|
||||
#[cfg(test)]
|
||||
Test,
|
||||
|
||||
@@ -426,6 +426,7 @@ async fn import_backup_stream_inner<R: tokio::io::AsyncRead + Unpin>(
|
||||
if res.is_ok() {
|
||||
context.emit_event(EventType::ImexProgress(999));
|
||||
res = context.sql.run_migrations(context).await;
|
||||
context.emit_event(EventType::AccountsItemChanged);
|
||||
}
|
||||
if res.is_ok() {
|
||||
delete_and_reset_all_device_msgs(context)
|
||||
|
||||
@@ -391,7 +391,7 @@ impl TestContext {
|
||||
Self {
|
||||
ctx,
|
||||
dir,
|
||||
evtracker: EventTracker(evtracker_receiver),
|
||||
evtracker: EventTracker::new(evtracker_receiver),
|
||||
_log_sink,
|
||||
}
|
||||
}
|
||||
@@ -1087,6 +1087,10 @@ impl DerefMut for EventTracker {
|
||||
}
|
||||
|
||||
impl EventTracker {
|
||||
pub fn new(emitter: EventEmitter) -> Self {
|
||||
Self(emitter)
|
||||
}
|
||||
|
||||
/// Consumes emitted events returning the first matching one.
|
||||
///
|
||||
/// If no matching events are ready this will wait for new events to arrive and time out
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
mod account_events;
|
||||
mod aeap;
|
||||
mod verified_chats;
|
||||
|
||||
170
src/tests/account_events.rs
Normal file
170
src/tests/account_events.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
//! contains tests for account (list) events
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::accounts::Accounts;
|
||||
use crate::config::Config;
|
||||
use crate::imex::{get_backup, has_backup, imex, BackupProvider, ImexMode};
|
||||
use crate::test_utils::{sync, EventTracker, TestContext, TestContextManager};
|
||||
use crate::EventType;
|
||||
|
||||
async fn wait_for_item_changed(context: &TestContext) {
|
||||
context
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::AccountsItemChanged))
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_account_event() -> Result<()> {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut manager = Accounts::new(dir.path().join("accounts"), true).await?;
|
||||
let tracker = EventTracker::new(manager.get_event_emitter());
|
||||
|
||||
// create account
|
||||
tracker.clear_events();
|
||||
let account_id = manager.add_account().await?;
|
||||
tracker
|
||||
.get_matching(|evt| matches!(evt, EventType::AccountsChanged))
|
||||
.await;
|
||||
|
||||
// remove account
|
||||
tracker.clear_events();
|
||||
manager.remove_account(account_id).await?;
|
||||
tracker
|
||||
.get_matching(|evt| matches!(evt, EventType::AccountsChanged))
|
||||
.await;
|
||||
|
||||
// create closed account
|
||||
tracker.clear_events();
|
||||
manager.add_closed_account().await?;
|
||||
tracker
|
||||
.get_matching(|evt| matches!(evt, EventType::AccountsChanged))
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// configuration is tested by python tests in deltachat-rpc-client/tests/test_account_events.py
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_set_displayname() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let context = tcm.alice().await;
|
||||
context.evtracker.clear_events();
|
||||
context
|
||||
.set_config(crate::config::Config::Displayname, Some("🐰 Alice"))
|
||||
.await?;
|
||||
wait_for_item_changed(&context).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_set_selfavatar() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let context = tcm.alice().await;
|
||||
let file = context.dir.path().join("avatar.jpg");
|
||||
let bytes = include_bytes!("../../test-data/image/avatar1000x1000.jpg");
|
||||
tokio::fs::write(&file, bytes).await?;
|
||||
context.evtracker.clear_events();
|
||||
context
|
||||
.set_config(
|
||||
crate::config::Config::Selfavatar,
|
||||
Some(file.to_str().unwrap()),
|
||||
)
|
||||
.await?;
|
||||
wait_for_item_changed(&context).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_set_private_tag() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let context = tcm.alice().await;
|
||||
context.evtracker.clear_events();
|
||||
context
|
||||
.set_config(crate::config::Config::PrivateTag, Some("Wonderland"))
|
||||
.await?;
|
||||
wait_for_item_changed(&context).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_import_backup() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let context1 = tcm.alice().await;
|
||||
let backup_dir = tempfile::tempdir().unwrap();
|
||||
assert!(
|
||||
imex(&context1, ImexMode::ExportBackup, backup_dir.path(), None)
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
let context2 = TestContext::new().await;
|
||||
assert!(!context2.is_configured().await?);
|
||||
context2.evtracker.clear_events();
|
||||
let backup = has_backup(&context2, backup_dir.path()).await?;
|
||||
imex(&context2, ImexMode::ImportBackup, backup.as_ref(), None).await?;
|
||||
assert!(context2.is_configured().await?);
|
||||
wait_for_item_changed(&context2).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_receive_backup() {
|
||||
let mut tcm = TestContextManager::new();
|
||||
// Create first device.
|
||||
let ctx0 = tcm.alice().await;
|
||||
// Prepare to transfer backup.
|
||||
let provider = BackupProvider::prepare(&ctx0).await.unwrap();
|
||||
// Set up second device.
|
||||
let ctx1 = tcm.unconfigured().await;
|
||||
|
||||
ctx1.evtracker.clear_events();
|
||||
get_backup(&ctx1, provider.qr()).await.unwrap();
|
||||
|
||||
// Make sure the provider finishes without an error.
|
||||
tokio::time::timeout(Duration::from_secs(30), provider)
|
||||
.await
|
||||
.expect("timed out")
|
||||
.expect("error in provider");
|
||||
|
||||
wait_for_item_changed(&ctx1).await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_sync() -> Result<()> {
|
||||
let alice0 = TestContext::new_alice().await;
|
||||
let alice1 = TestContext::new_alice().await;
|
||||
for a in [&alice0, &alice1] {
|
||||
a.set_config_bool(Config::SyncMsgs, true).await?;
|
||||
}
|
||||
|
||||
let new_name = "new name";
|
||||
alice0
|
||||
.set_config(Config::Displayname, Some(new_name))
|
||||
.await?;
|
||||
alice1.evtracker.clear_events();
|
||||
sync(&alice0, &alice1).await;
|
||||
wait_for_item_changed(&alice1).await;
|
||||
assert_eq!(
|
||||
alice1.get_config(Config::Displayname).await?,
|
||||
Some(new_name.to_owned())
|
||||
);
|
||||
|
||||
assert!(alice0.get_config(Config::Selfavatar).await?.is_none());
|
||||
let file = alice0.dir.path().join("avatar.png");
|
||||
let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
|
||||
tokio::fs::write(&file, bytes).await?;
|
||||
alice0
|
||||
.set_config(Config::Selfavatar, Some(file.to_str().unwrap()))
|
||||
.await?;
|
||||
alice1.evtracker.clear_events();
|
||||
sync(&alice0, &alice1).await;
|
||||
wait_for_item_changed(&alice1).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user