Skip to content

Commit 7baac2d

Browse files
committed
WIP: synchronization of transports
1 parent 2b3cb38 commit 7baac2d

File tree

8 files changed

+294
-56
lines changed

8 files changed

+294
-56
lines changed

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 & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -819,11 +819,19 @@ impl Context {
819819
self,
820820
"Creating a pseudo configured account which will not be able to send or receive messages. Only meant for tests!"
821821
);
822-
ConfiguredLoginParam::from_json(&format!(
823-
r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#
824-
))?
825-
.save_to_transports_table(self, &EnteredLoginParam::default())
826-
.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+
format!(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?;
827835
}
828836
self.sql
829837
.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/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
@@ -1430,6 +1430,21 @@ CREATE INDEX imap_sync_index ON imap_sync(transport_id, folder);
14301430
.await?;
14311431
}
14321432

1433+
inc_and_check(&mut migration_version, 141)?;
1434+
if dbversion < migration_version {
1435+
sql.execute_migration(
1436+
"ALTER TABLE transports
1437+
ADD COLUMN add_timestamp INTEGER NOT NULL DEFAULT 0;
1438+
CREATE TABLE removed_transports (
1439+
addr TEXT NOT NULL,
1440+
remove_timestamp INTEGER NOT NULL,
1441+
UNIQUE(addr)
1442+
) STRICT;",
1443+
migration_version,
1444+
)
1445+
.await?;
1446+
}
1447+
14331448
let new_version = sql
14341449
.get_raw_config_int(VERSION_CFG)
14351450
.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)]

src/test_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ impl TestContext {
600600
self.ctx
601601
.set_config(Config::ConfiguredAddr, Some(addr))
602602
.await
603-
.unwrap();
603+
.expect("Failed to configure address");
604604

605605
if let Some(name) = addr.split('@').next() {
606606
self.set_name(name);

0 commit comments

Comments
 (0)