Skip to content

Commit 3b2f96a

Browse files
committed
WIP: synchronization of transports
1 parent 977f366 commit 3b2f96a

File tree

12 files changed

+314
-61
lines changed

12 files changed

+314
-61
lines changed

deltachat-ffi/src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
559559
EventType::IncomingCallAccepted { .. } => 2560,
560560
EventType::OutgoingCallAccepted { .. } => 2570,
561561
EventType::CallEnded { .. } => 2580,
562+
EventType::TransportsModified => 2600,
562563
#[allow(unreachable_patterns)]
563564
#[cfg(test)]
564565
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
@@ -593,7 +594,8 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
593594
| EventType::AccountsBackgroundFetchDone
594595
| EventType::ChatlistChanged
595596
| EventType::AccountsChanged
596-
| EventType::AccountsItemChanged => 0,
597+
| EventType::AccountsItemChanged
598+
| EventType::TransportsModified => 0,
597599
EventType::IncomingReaction { contact_id, .. }
598600
| EventType::IncomingWebxdcNotify { contact_id, .. } => contact_id.to_u32() as libc::c_int,
599601
EventType::MsgsChanged { chat_id, .. }
@@ -681,7 +683,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
681683
| EventType::IncomingCallAccepted { .. }
682684
| EventType::OutgoingCallAccepted { .. }
683685
| EventType::CallEnded { .. }
684-
| EventType::EventChannelOverflow { .. } => 0,
686+
| EventType::EventChannelOverflow { .. }
687+
| EventType::TransportsModified => 0,
685688
EventType::MsgsChanged { msg_id, .. }
686689
| EventType::ReactionsChanged { msg_id, .. }
687690
| EventType::IncomingReaction { msg_id, .. }
@@ -780,7 +783,8 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
780783
| EventType::AccountsChanged
781784
| EventType::AccountsItemChanged
782785
| EventType::IncomingCallAccepted { .. }
783-
| EventType::WebxdcRealtimeAdvertisementReceived { .. } => ptr::null_mut(),
786+
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
787+
| EventType::TransportsModified => ptr::null_mut(),
784788
EventType::IncomingCall {
785789
place_call_info, ..
786790
} => {

deltachat-jsonrpc/src/api/types/events.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,11 @@ pub enum EventType {
460460
/// ID of the chat which the message belongs to.
461461
chat_id: u32,
462462
},
463+
464+
/// One or more transports has changed.
465+
///
466+
/// Transport list should be refreshed.
467+
TransportsModified,
463468
}
464469

465470
impl From<CoreEventType> for EventType {
@@ -642,6 +647,8 @@ impl From<CoreEventType> for EventType {
642647
msg_id: msg_id.to_u32(),
643648
chat_id: chat_id.to_u32(),
644649
},
650+
CoreEventType::TransportsModified => TransportsModified,
651+
645652
#[allow(unreachable_patterns)]
646653
#[cfg(test)]
647654
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),

deltachat-rpc-client/src/deltachat_rpc_client/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class EventType(str, Enum):
8080
CONFIG_SYNCED = "ConfigSynced"
8181
WEBXDC_REALTIME_DATA = "WebxdcRealtimeData"
8282
WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED = "WebxdcRealtimeAdvertisementReceived"
83+
TRANSPORTS_MODIFIED = "TransportsModified"
8384

8485

8586
class ChatId(IntEnum):

deltachat-rpc-client/tests/test_multitransport.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
from deltachat_rpc_client import EventType
34
from deltachat_rpc_client.rpc import JsonRpcError
45

56

@@ -156,3 +157,47 @@ def test_reconfigure_transport(acfactory) -> None:
156157
# Reconfiguring the transport should not reset
157158
# the settings as if when configuring the first transport.
158159
assert account.get_config("mvbox_move") == "1"
160+
161+
162+
def test_transport_synchronization(acfactory, log) -> None:
163+
"""Test synchronization of transports between devices."""
164+
ac1, ac2 = acfactory.get_online_accounts(2)
165+
ac1_clone = ac1.clone()
166+
ac1_clone.bring_online()
167+
168+
qr = acfactory.get_account_qr()
169+
170+
ac1.add_transport_from_qr(qr)
171+
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
172+
assert len(ac1.list_transports()) == 2
173+
assert len(ac1_clone.list_transports()) == 2
174+
175+
ac1_clone.add_transport_from_qr(qr)
176+
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
177+
assert len(ac1.list_transports()) == 3
178+
assert len(ac1_clone.list_transports()) == 3
179+
180+
log.section("ac1 clone removes second transport")
181+
[transport1, transport2, transport3] = ac1_clone.list_transports()
182+
addr3 = transport3["addr"]
183+
ac1_clone.delete_transport(transport2["addr"])
184+
185+
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
186+
[transport1, transport3] = ac1.list_transports()
187+
188+
log.section("ac1 changes the primary transport")
189+
ac1.set_config("configured_addr", transport3["addr"])
190+
191+
log.section("ac1 removes the first transport")
192+
ac1.delete_transport(transport1["addr"])
193+
194+
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
195+
[transport3] = ac1_clone.list_transports()
196+
assert transport3["addr"] == addr3
197+
assert ac1_clone.get_config("configured_addr") == addr3
198+
199+
ac2_chat = ac2.create_chat(ac1)
200+
ac2_chat.send_text("Hello!")
201+
202+
assert ac1.wait_for_incoming_msg().get_snapshot().text == "Hello!"
203+
assert ac1_clone.wait_for_incoming_msg().get_snapshot().text == "Hello!"

src/config.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ use crate::mimefactory::RECOMMENDED_FILE_SIZE;
2121
use crate::provider::{Provider, get_provider_by_id};
2222
use crate::sync::{self, Sync::*, SyncData};
2323
use crate::tools::get_abs_path;
24-
use crate::transport::ConfiguredLoginParam;
2524
use crate::{constants, stats};
2625

2726
/// The available configuration keys.
@@ -820,11 +819,19 @@ impl Context {
820819
self,
821820
"Creating a pseudo configured account which will not be able to send or receive messages. Only meant for tests!"
822821
);
823-
ConfiguredLoginParam::from_json(&format!(
824-
r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#
825-
))?
826-
.save_to_transports_table(self, &EnteredLoginParam::default())
827-
.await?;
822+
self.sql
823+
.execute(
824+
"INSERT INTO transports (addr, entered_param, configured_param) VALUES (?, ?, ?)",
825+
(
826+
addr,
827+
serde_json::to_string(&EnteredLoginParam::default())?,
828+
r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#
829+
),
830+
)
831+
.await?;
832+
self.sql
833+
.set_raw_config(Config::ConfiguredAddr.as_ref(), Some(addr))
834+
.await?;
828835
}
829836
self.sql
830837
.transaction(|transaction| {

src/configure.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use crate::sync::Sync::*;
4040
use crate::tools::time;
4141
use crate::transport::{
4242
ConfiguredCertificateChecks, ConfiguredLoginParam, ConfiguredServerLoginParam,
43-
ConnectionCandidate,
43+
ConnectionCandidate, sync_transports,
4444
};
4545
use crate::{EventType, stock_str};
4646
use crate::{chat, provider};
@@ -205,6 +205,7 @@ impl Context {
205205
/// Removes the transport with the specified email address
206206
/// (i.e. [EnteredLoginParam::addr]).
207207
pub async fn delete_transport(&self, addr: &str) -> Result<()> {
208+
let now = time();
208209
self.sql
209210
.transaction(|transaction| {
210211
let primary_addr = transaction.query_row(
@@ -232,10 +233,19 @@ impl Context {
232233
"DELETE FROM imap_sync WHERE transport_id=?",
233234
(transport_id,),
234235
)?;
236+
transaction.execute(
237+
"INSERT INTO removed_transports (addr, remove_timestamp)
238+
VALUES (?, ?)
239+
ON CONFLICT (addr)
240+
DO UPDATE SET remove_timestamp = excluded.remove_timestamp",
241+
(addr, now),
242+
)?;
235243

236244
Ok(())
237245
})
238246
.await?;
247+
sync_transports(self).await?;
248+
239249
Ok(())
240250
}
241251

@@ -553,7 +563,8 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
553563

554564
progress!(ctx, 900);
555565

556-
if !ctx.is_configured().await? {
566+
let is_configured = ctx.is_configured().await?;
567+
if !is_configured {
557568
ctx.sql.set_raw_config("mvbox_move", Some("0")).await?;
558569
ctx.sql.set_raw_config("only_fetch_mvbox", None).await?;
559570
}
@@ -568,8 +579,10 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
568579

569580
let provider = configured_param.provider;
570581
configured_param
571-
.save_to_transports_table(ctx, param)
582+
.clone()
583+
.save_to_transports_table(ctx, param, time())
572584
.await?;
585+
sync_transports(ctx).await?;
573586

574587
ctx.set_config_internal(Config::ConfiguredTimestamp, Some(&time().to_string()))
575588
.await?;

src/events/payload.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,11 @@ pub enum EventType {
417417
chat_id: ChatId,
418418
},
419419

420+
/// One or more transports has changed.
421+
///
422+
/// Transport list should be refreshed.
423+
TransportsModified,
424+
420425
/// Event for using in tests, e.g. as a fence between normally generated events.
421426
#[cfg(test)]
422427
Test,

src/receive_imf.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,11 @@ pub(crate) async fn receive_imf_inner(
827827
if let Some(ref sync_items) = mime_parser.sync_items {
828828
if from_id == ContactId::SELF {
829829
if mime_parser.was_encrypted() {
830+
// Receiving encrypted message from self updates primary transport.
831+
context
832+
.sql
833+
.set_raw_config("configured_addr", Some(&mime_parser.from.addr))
834+
.await?;
830835
context
831836
.execute_sync_items(sync_items, mime_parser.timestamp_sent)
832837
.await;

src/sql/migrations.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,21 @@ CREATE INDEX imap_sync_index ON imap_sync(transport_id, folder);
14451445
.await?;
14461446
}
14471447

1448+
inc_and_check(&mut migration_version, 141)?;
1449+
if dbversion < migration_version {
1450+
sql.execute_migration(
1451+
"ALTER TABLE transports
1452+
ADD COLUMN add_timestamp INTEGER NOT NULL DEFAULT 0;
1453+
CREATE TABLE removed_transports (
1454+
addr TEXT NOT NULL,
1455+
remove_timestamp INTEGER NOT NULL,
1456+
UNIQUE(addr)
1457+
) STRICT;",
1458+
migration_version,
1459+
)
1460+
.await?;
1461+
}
1462+
14481463
let new_version = sql
14491464
.get_raw_config_int(VERSION_CFG)
14501465
.await?

src/sync.rs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ use crate::config::Config;
99
use crate::constants::Blocked;
1010
use crate::contact::ContactId;
1111
use crate::context::Context;
12-
use crate::log::LogExt;
13-
use crate::log::warn;
12+
use crate::events::EventType;
13+
use crate::log::{LogExt as _, warn};
14+
use crate::login_param::EnteredLoginParam;
1415
use crate::message::{Message, MsgId, Viewtype};
1516
use crate::mimeparser::SystemMessage;
1617
use crate::param::Param;
1718
use crate::sync::SyncData::{AddQrToken, AlterChat, DeleteQrToken};
1819
use crate::token::Namespace;
1920
use crate::tools::time;
21+
use crate::transport::{ConfiguredLoginParamJson, save_transport};
2022
use crate::{message, stock_str, token};
2123
use std::collections::HashSet;
2224

@@ -52,6 +54,29 @@ pub(crate) struct QrTokenData {
5254
pub(crate) grpid: Option<String>,
5355
}
5456

57+
#[derive(Debug, Serialize, Deserialize)]
58+
pub(crate) struct TransportData {
59+
/// Configured login parameters.
60+
pub(crate) configured: ConfiguredLoginParamJson,
61+
62+
/// Login parameters entered by the user.
63+
///
64+
/// They can be used to reconfigure the transport.
65+
pub(crate) entered: EnteredLoginParam,
66+
67+
/// Timestamp of when the transport was last time (re)configured.
68+
pub(crate) timestamp: i64,
69+
}
70+
71+
#[derive(Debug, Serialize, Deserialize)]
72+
pub(crate) struct RemovedTransportData {
73+
/// Address of the removed transport.
74+
pub(crate) addr: String,
75+
76+
/// Timestamp of when the transport was removed.
77+
pub(crate) timestamp: i64,
78+
}
79+
5580
#[derive(Debug, Serialize, Deserialize)]
5681
pub(crate) enum SyncData {
5782
AddQrToken(QrTokenData),
@@ -71,6 +96,13 @@ pub(crate) enum SyncData {
7196
DeleteMessages {
7297
msgs: Vec<String>, // RFC724 id (i.e. "Message-Id" header)
7398
},
99+
100+
/// Update transport configuration.
101+
Transports {
102+
transports: Vec<TransportData>,
103+
104+
removed_transports: Vec<RemovedTransportData>,
105+
},
74106
}
75107

76108
#[derive(Debug, Serialize, Deserialize)]
@@ -274,6 +306,10 @@ impl Context {
274306
SyncData::Config { key, val } => self.sync_config(key, val).await,
275307
SyncData::SaveMessage { src, dest } => self.save_message(src, dest).await,
276308
SyncData::DeleteMessages { msgs } => self.sync_message_deletion(msgs).await,
309+
SyncData::Transports {
310+
transports,
311+
removed_transports,
312+
} => self.sync_transports(transports, removed_transports).await,
277313
},
278314
SyncDataOrUnknown::Unknown(data) => {
279315
warn!(self, "Ignored unknown sync item: {data}.");
@@ -347,6 +383,46 @@ impl Context {
347383
message::delete_msgs_locally_done(self, &msg_ids, modified_chat_ids).await?;
348384
Ok(())
349385
}
386+
387+
/// Process received data for transport synchronization.
388+
async fn sync_transports(
389+
&self,
390+
transports: &[TransportData],
391+
removed_transports: &[RemovedTransportData],
392+
) -> Result<()> {
393+
for TransportData {
394+
configured,
395+
entered,
396+
timestamp,
397+
} in transports
398+
{
399+
save_transport(self, entered, configured, *timestamp).await?;
400+
}
401+
402+
self.sql
403+
.transaction(|transaction| {
404+
for RemovedTransportData { addr, timestamp } in removed_transports {
405+
transaction.execute(
406+
"DELETE FROM transports
407+
WHERE addr=? AND add_timestamp<=?",
408+
(addr, timestamp),
409+
)?;
410+
transaction.execute(
411+
"INSERT INTO removed_transports (addr, remove_timestamp)
412+
VALUES (?, ?)
413+
ON CONFLICT (addr) DO
414+
UPDATE SET remove_timestamp = excluded.remove_timestamp
415+
WHERE excluded.remove_timestamp > remove_timestamp",
416+
(addr, timestamp),
417+
)?;
418+
}
419+
Ok(())
420+
})
421+
.await?;
422+
423+
self.emit_event(EventType::TransportsModified);
424+
Ok(())
425+
}
350426
}
351427

352428
#[cfg(test)]

0 commit comments

Comments
 (0)