1use std::collections::BTreeMap;
4use std::collections::BTreeSet;
5use std::time::Duration;
6
7use anyhow::{Context as _, Result, ensure};
8use deltachat_contact_tools::EmailAddress;
9use deltachat_contact_tools::addr_cmp;
10use pgp::composed::SignedPublicKey;
11use rusqlite::OptionalExtension;
12
13use crate::config::Config;
14use crate::configure::EnteredLoginParam;
15use crate::constants::ShowEmails;
16use crate::context::Context;
17use crate::imap;
18use crate::key::DcKey;
19use crate::log::{info, warn};
20use crate::login_param::ConfiguredLoginParam;
21use crate::message::MsgId;
22use crate::provider::get_provider_by_domain;
23use crate::sql::Sql;
24use crate::tools::{Time, inc_and_check, time_elapsed};
25
26const DBVERSION: i32 = 68;
27const VERSION_CFG: &str = "dbversion";
28const TABLES: &str = include_str!("./tables.sql");
29
30#[cfg(test)]
31tokio::task_local! {
32 static STOP_MIGRATIONS_AT: i32;
33}
34
35pub async fn run(context: &Context, sql: &Sql) -> Result<(bool, bool, bool)> {
36 let mut exists_before_update = false;
37 let mut dbversion_before_update = DBVERSION;
38
39 if !sql
40 .table_exists("config")
41 .await
42 .context("failed to check if config table exists")?
43 {
44 sql.transaction(move |transaction| {
45 transaction.execute_batch(TABLES)?;
46
47 transaction.execute(
49 "INSERT INTO config (keyname, value) VALUES (?, ?);",
50 (VERSION_CFG, format!("{dbversion_before_update}")),
51 )?;
52 Ok(())
53 })
54 .await
55 .context("Creating tables failed")?;
56
57 let mut lock = context.sql.config_cache.write().await;
58 lock.insert(
59 VERSION_CFG.to_string(),
60 Some(format!("{dbversion_before_update}")),
61 );
62 drop(lock);
63 } else {
64 exists_before_update = true;
65 dbversion_before_update = sql
66 .get_raw_config_int(VERSION_CFG)
67 .await?
68 .unwrap_or_default();
69 }
70
71 let dbversion = dbversion_before_update;
72 let mut update_icons = !exists_before_update;
73 let mut disable_server_delete = false;
74 let mut recode_avatar = false;
75
76 if dbversion < 1 {
77 sql.execute_migration(
78 r#"
79CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');
80CREATE INDEX leftgrps_index1 ON leftgrps (grpid);"#,
81 1,
82 )
83 .await?;
84 }
85 if dbversion < 2 {
86 sql.execute_migration(
87 "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
88 2,
89 )
90 .await?;
91 }
92 if dbversion < 7 {
93 sql.execute_migration(
94 "CREATE TABLE keypairs (\
95 id INTEGER PRIMARY KEY, \
96 addr TEXT DEFAULT '' COLLATE NOCASE, \
97 is_default INTEGER DEFAULT 0, \
98 private_key, \
99 public_key, \
100 created INTEGER DEFAULT 0);",
101 7,
102 )
103 .await?;
104 }
105 if dbversion < 10 {
106 sql.execute_migration(
107 "CREATE TABLE acpeerstates (\
108 id INTEGER PRIMARY KEY, \
109 addr TEXT DEFAULT '' COLLATE NOCASE, \
110 last_seen INTEGER DEFAULT 0, \
111 last_seen_autocrypt INTEGER DEFAULT 0, \
112 public_key, \
113 prefer_encrypted INTEGER DEFAULT 0); \
114 CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);",
115 10,
116 )
117 .await?;
118 }
119 if dbversion < 12 {
120 sql.execute_migration(
121 r#"
122CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);
123CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);"#,
124 12,
125 )
126 .await?;
127 }
128 if dbversion < 17 {
129 sql.execute_migration(
130 r#"
131ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;
132CREATE INDEX chats_index2 ON chats (archived);
133ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;
134CREATE INDEX msgs_index5 ON msgs (starred);"#,
135 17,
136 )
137 .await?;
138 }
139 if dbversion < 18 {
140 sql.execute_migration(
141 r#"
142ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;
143ALTER TABLE acpeerstates ADD COLUMN gossip_key;"#,
144 18,
145 )
146 .await?;
147 }
148 if dbversion < 27 {
149 sql.execute_migration(
152 r#"
153DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;
154CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
155ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
156ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;"#,
157 27,
158 )
159 .await?;
160 }
161 if dbversion < 34 {
162 sql.execute_migration(
163 r#"
164ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;
165ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
166ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';
167ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';
168CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
169CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);"#,
170 34,
171 )
172 .await?;
173 }
174 if dbversion < 39 {
175 sql.execute_migration(
176 r#"
177CREATE TABLE tokens (
178 id INTEGER PRIMARY KEY,
179 namespc INTEGER DEFAULT 0,
180 foreign_id INTEGER DEFAULT 0,
181 token TEXT DEFAULT '',
182 timestamp INTEGER DEFAULT 0
183);
184ALTER TABLE acpeerstates ADD COLUMN verified_key;
185ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';
186CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);"#,
187 39,
188 )
189 .await?;
190 }
191 if dbversion < 40 {
192 sql.execute_migration("ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", 40)
193 .await?;
194 }
195 if dbversion < 44 {
196 sql.execute_migration("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", 44)
197 .await?;
198 }
199 if dbversion < 46 {
200 sql.execute_migration(
201 r#"
202ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;
203ALTER TABLE msgs ADD COLUMN mime_references TEXT;"#,
204 46,
205 )
206 .await?;
207 }
208 if dbversion < 47 {
209 sql.execute_migration("ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", 47)
210 .await?;
211 }
212 if dbversion < 48 {
213 sql.execute_migration(
215 "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
216 48,
217 )
218 .await?;
219 }
220 if dbversion < 49 {
221 sql.execute_migration(
222 "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
223 49,
224 )
225 .await?;
226 }
227 if dbversion < 50 {
228 if exists_before_update {
232 sql.set_raw_config_int("show_emails", ShowEmails::All as i32)
233 .await?;
234 }
235 sql.set_db_version(50).await?;
236 }
237 if dbversion < 53 {
238 sql.execute_migration(
241 r#"
242CREATE TABLE locations (
243 id INTEGER PRIMARY KEY AUTOINCREMENT,
244 latitude REAL DEFAULT 0.0,
245 longitude REAL DEFAULT 0.0,
246 accuracy REAL DEFAULT 0.0,
247 timestamp INTEGER DEFAULT 0,
248 chat_id INTEGER DEFAULT 0,
249 from_id INTEGER DEFAULT 0
250);"
251CREATE INDEX locations_index1 ON locations (from_id);
252CREATE INDEX locations_index2 ON locations (timestamp);
253ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;
254ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;
255ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;
256CREATE INDEX chats_index3 ON chats (locations_send_until);"#,
257 53,
258 )
259 .await?;
260 }
261 if dbversion < 54 {
262 sql.execute_migration(
263 r#"
264ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;
265CREATE INDEX msgs_index6 ON msgs (location_id);"#,
266 54,
267 )
268 .await?;
269 }
270 if dbversion < 55 {
271 sql.execute_migration(
272 "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
273 55,
274 )
275 .await?;
276 }
277 if dbversion < 59 {
278 sql.execute_migration(
281 r#"
282CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);
283CREATE INDEX devmsglabels_index1 ON devmsglabels (label);"#, 59)
284 .await?;
285 if exists_before_update && sql.get_raw_config_int("bcc_self").await?.is_none() {
286 sql.set_raw_config_int("bcc_self", 1).await?;
287 }
288 }
289
290 if dbversion < 60 {
291 sql.execute_migration(
292 "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;",
293 60,
294 )
295 .await?;
296 }
297 if dbversion < 61 {
298 sql.execute_migration(
299 "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;",
300 61,
301 )
302 .await?;
303 update_icons = true;
304 }
305 if dbversion < 62 {
306 sql.execute_migration(
307 "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;",
308 62,
309 )
310 .await?;
311 }
312 if dbversion < 63 {
313 sql.execute_migration("UPDATE chats SET grpid='' WHERE type=100", 63)
314 .await?;
315 }
316 if dbversion < 64 {
317 sql.execute_migration("ALTER TABLE msgs ADD COLUMN error TEXT DEFAULT '';", 64)
318 .await?;
319 }
320 if dbversion < 65 {
321 sql.execute_migration(
322 r#"
323ALTER TABLE chats ADD COLUMN ephemeral_timer INTEGER;
324ALTER TABLE msgs ADD COLUMN ephemeral_timer INTEGER DEFAULT 0;
325ALTER TABLE msgs ADD COLUMN ephemeral_timestamp INTEGER DEFAULT 0;"#,
326 65,
327 )
328 .await?;
329 }
330 if dbversion < 66 {
331 update_icons = true;
332 sql.set_db_version(66).await?;
333 }
334 if dbversion < 67 {
335 for prefix in &["", "configured_"] {
336 if let Some(server_flags) = sql
337 .get_raw_config_int(&format!("{prefix}server_flags"))
338 .await?
339 {
340 let imap_socket_flags = server_flags & 0x700;
341 let key = &format!("{prefix}mail_security");
342 match imap_socket_flags {
343 0x100 => sql.set_raw_config_int(key, 2).await?, 0x200 => sql.set_raw_config_int(key, 1).await?, 0x400 => sql.set_raw_config_int(key, 3).await?, _ => sql.set_raw_config_int(key, 0).await?,
347 }
348 let smtp_socket_flags = server_flags & 0x70000;
349 let key = &format!("{prefix}send_security");
350 match smtp_socket_flags {
351 0x10000 => sql.set_raw_config_int(key, 2).await?, 0x20000 => sql.set_raw_config_int(key, 1).await?, 0x40000 => sql.set_raw_config_int(key, 3).await?, _ => sql.set_raw_config_int(key, 0).await?,
355 }
356 }
357 }
358 sql.set_db_version(67).await?;
359 }
360 if dbversion < 68 {
361 sql.execute_migration(
363 "CREATE INDEX IF NOT EXISTS msgs_index7 ON msgs (state, hidden, chat_id);",
364 68,
365 )
366 .await?;
367 }
368 if dbversion < 69 {
369 sql.execute_migration(
370 r#"
371ALTER TABLE chats ADD COLUMN protected INTEGER DEFAULT 0;
372-- 120=group, 130=old verified group
373UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
374 69,
375 )
376 .await?;
377 }
378
379 if dbversion < 71 {
380 if let Ok(addr) = context.get_primary_self_addr().await {
381 if let Ok(domain) = EmailAddress::new(&addr).map(|email| email.domain) {
382 context
383 .set_config_internal(
384 Config::ConfiguredProvider,
385 get_provider_by_domain(&domain).map(|provider| provider.id),
386 )
387 .await?;
388 } else {
389 warn!(context, "Can't parse configured address: {:?}", addr);
390 }
391 }
392
393 sql.set_db_version(71).await?;
394 }
395 if dbversion < 72 && !sql.col_exists("msgs", "mime_modified").await? {
396 sql.execute_migration(
397 r#"
398 ALTER TABLE msgs ADD COLUMN mime_modified INTEGER DEFAULT 0;"#,
399 72,
400 )
401 .await?;
402 }
403 if dbversion < 73 {
404 use Config::*;
405 sql.execute(
406 r#"
407CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0, uid_next INTEGER DEFAULT 0);"#,
408()
409 )
410 .await?;
411 for c in &[
412 ConfiguredInboxFolder,
413 ConfiguredSentboxFolder,
414 ConfiguredMvboxFolder,
415 ] {
416 if let Some(folder) = context.get_config(*c).await? {
417 let (uid_validity, last_seen_uid) =
418 imap::get_config_last_seen_uid(context, &folder).await?;
419 if last_seen_uid > 0 {
420 imap::set_uid_next(context, &folder, last_seen_uid + 1).await?;
421 imap::set_uidvalidity(context, &folder, uid_validity).await?;
422 }
423 }
424 }
425 if exists_before_update {
426 disable_server_delete = true;
427
428 if let Some(provider) = context.get_configured_provider().await? {
430 if let Some(defaults) = &provider.config_defaults {
431 if defaults.iter().any(|d| d.key == Config::DeleteServerAfter) {
432 disable_server_delete = false;
433 }
434 }
435 }
436 }
437 sql.set_db_version(73).await?;
438 }
439 if dbversion < 74 {
440 sql.execute_migration("UPDATE contacts SET name='' WHERE name=authname", 74)
441 .await?;
442 }
443 if dbversion < 75 {
444 sql.execute_migration(
445 "ALTER TABLE contacts ADD COLUMN status TEXT DEFAULT '';",
446 75,
447 )
448 .await?;
449 }
450 if dbversion < 76 {
451 sql.execute_migration("ALTER TABLE msgs ADD COLUMN subject TEXT DEFAULT '';", 76)
452 .await?;
453 }
454 if dbversion < 77 {
455 recode_avatar = true;
456 sql.set_db_version(77).await?;
457 }
458 if dbversion < 78 {
459 sql.execute_migration("UPDATE chats SET archived=1 WHERE blocked=2;", 78)
462 .await?;
463 }
464 if dbversion < 79 {
465 sql.execute_migration(
466 r#"
467 ALTER TABLE msgs ADD COLUMN download_state INTEGER DEFAULT 0;
468 "#,
469 79,
470 )
471 .await?;
472 }
473 if dbversion < 80 {
474 sql.execute_migration(
475 r#"CREATE TABLE multi_device_sync (
476id INTEGER PRIMARY KEY AUTOINCREMENT,
477item TEXT DEFAULT '');"#,
478 80,
479 )
480 .await?;
481 }
482 if dbversion < 81 {
483 sql.execute_migration("ALTER TABLE msgs ADD COLUMN hop_info TEXT;", 81)
484 .await?;
485 }
486 if dbversion < 82 {
487 sql.execute_migration(
488 r#"CREATE TABLE imap (
489id INTEGER PRIMARY KEY AUTOINCREMENT,
490rfc724_mid TEXT DEFAULT '', -- Message-ID header
491folder TEXT DEFAULT '', -- IMAP folder
492target TEXT DEFAULT '', -- Destination folder, empty to delete.
493uid INTEGER DEFAULT 0, -- UID
494uidvalidity INTEGER DEFAULT 0,
495UNIQUE (folder, uid, uidvalidity)
496);
497CREATE INDEX imap_folder ON imap(folder);
498CREATE INDEX imap_messageid ON imap(rfc724_mid);
499
500INSERT INTO imap
501(rfc724_mid, folder, target, uid, uidvalidity)
502SELECT
503rfc724_mid,
504server_folder AS folder,
505server_folder AS target,
506server_uid AS uid,
507(SELECT uidvalidity FROM imap_sync WHERE folder=server_folder) AS uidvalidity
508FROM msgs
509WHERE server_uid>0
510ON CONFLICT (folder, uid, uidvalidity)
511DO UPDATE SET rfc724_mid=excluded.rfc724_mid,
512 target=excluded.target;
513"#,
514 82,
515 )
516 .await?;
517 }
518 if dbversion < 83 {
519 sql.execute_migration(
520 "ALTER TABLE imap_sync
521 ADD COLUMN modseq -- Highest modification sequence
522 INTEGER DEFAULT 0",
523 83,
524 )
525 .await?;
526 }
527 if dbversion < 84 {
528 sql.execute_migration(
529 r#"CREATE TABLE msgs_status_updates (
530id INTEGER PRIMARY KEY AUTOINCREMENT,
531msg_id INTEGER,
532update_item TEXT DEFAULT '',
533update_item_read INTEGER DEFAULT 0 -- XXX unused
534);
535CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);"#,
536 84,
537 )
538 .await?;
539 }
540 if dbversion < 85 {
541 sql.execute_migration(
542 r#"CREATE TABLE smtp (
543id INTEGER PRIMARY KEY,
544rfc724_mid TEXT NOT NULL, -- Message-ID
545mime TEXT NOT NULL, -- SMTP payload
546msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
547recipients TEXT NOT NULL, -- List of recipients separated by space
548retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
549);
550CREATE INDEX smtp_messageid ON imap(rfc724_mid);
551"#,
552 85,
553 )
554 .await?;
555 }
556 if dbversion < 86 {
557 sql.execute_migration(
558 r#"CREATE TABLE bobstate (
559 id INTEGER PRIMARY KEY AUTOINCREMENT,
560 invite TEXT NOT NULL,
561 next_step INTEGER NOT NULL,
562 chat_id INTEGER NOT NULL
563 );"#,
564 86,
565 )
566 .await?;
567 }
568 if dbversion < 87 {
569 sql.execute_migration(
571 "CREATE INDEX IF NOT EXISTS msgs_index8 ON msgs (ephemeral_timestamp);",
572 87,
573 )
574 .await?;
575 }
576 if dbversion < 88 {
577 sql.execute_migration("DROP TABLE IF EXISTS backup_blobs;", 88)
578 .await?;
579 }
580 if dbversion < 89 {
581 sql.execute_migration(
582 r#"CREATE TABLE imap_markseen (
583 id INTEGER,
584 FOREIGN KEY(id) REFERENCES imap(id) ON DELETE CASCADE
585 );"#,
586 89,
587 )
588 .await?;
589 }
590 if dbversion < 90 {
591 sql.execute_migration(
592 r#"CREATE TABLE smtp_mdns (
593 msg_id INTEGER NOT NULL, -- id of the message in msgs table which requested MDN (DEPRECATED 2024-06-21)
594 from_id INTEGER NOT NULL, -- id of the contact that sent the message, MDN destination
595 rfc724_mid TEXT NOT NULL, -- Message-ID header
596 retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send MDN
597 );"#,
598 90,
599 )
600 .await?;
601 }
602 if dbversion < 91 {
603 sql.execute_migration(
604 r#"CREATE TABLE smtp_status_updates (
605 msg_id INTEGER NOT NULL UNIQUE, -- msg_id of the webxdc instance with pending updates
606 first_serial INTEGER NOT NULL, -- id in msgs_status_updates
607 last_serial INTEGER NOT NULL, -- id in msgs_status_updates
608 descr TEXT NOT NULL -- text to send along with the updates
609 );"#,
610 91,
611 )
612 .await?;
613 }
614 if dbversion < 92 {
615 sql.execute_migration(
616 r#"CREATE TABLE reactions (
617 msg_id INTEGER NOT NULL, -- id of the message reacted to
618 contact_id INTEGER NOT NULL, -- id of the contact reacting to the message
619 reaction TEXT DEFAULT '' NOT NULL, -- a sequence of emojis separated by spaces
620 PRIMARY KEY(msg_id, contact_id),
621 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted
622 FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE -- delete reactions when contact is deleted
623 )"#,
624 92
625 ).await?;
626 }
627 if dbversion < 93 {
628 sql.execute_migration(
630 "CREATE TABLE sending_domains(domain TEXT PRIMARY KEY, dkim_works INTEGER DEFAULT 0);",
631 93,
632 )
633 .await?;
634 }
635 if dbversion < 94 {
636 sql.execute_migration(
637 "CREATE TABLE new_acpeerstates (
642 id INTEGER PRIMARY KEY,
643 addr TEXT DEFAULT '' COLLATE NOCASE,
644 last_seen INTEGER DEFAULT 0,
645 last_seen_autocrypt INTEGER DEFAULT 0,
646 public_key,
647 prefer_encrypted INTEGER DEFAULT 0,
648 gossip_timestamp INTEGER DEFAULT 0,
649 gossip_key,
650 public_key_fingerprint TEXT DEFAULT '',
651 gossip_key_fingerprint TEXT DEFAULT '',
652 verified_key,
653 verified_key_fingerprint TEXT DEFAULT '',
654 UNIQUE (addr) -- Only one peerstate per address
655 );
656 INSERT OR IGNORE INTO new_acpeerstates SELECT
657 id, addr, last_seen, last_seen_autocrypt, public_key, prefer_encrypted,
658 gossip_timestamp, gossip_key, public_key_fingerprint,
659 gossip_key_fingerprint, verified_key, verified_key_fingerprint
660 FROM acpeerstates;
661 DROP TABLE acpeerstates;
662 ALTER TABLE new_acpeerstates RENAME TO acpeerstates;
663 CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);
664 CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
665 CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);
666 CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);
667 ",
668 94,
669 )
670 .await?;
671 }
672 if dbversion < 95 {
673 sql.execute_migration(
674 "CREATE TABLE new_chats_contacts (chat_id INTEGER, contact_id INTEGER, UNIQUE(chat_id, contact_id));\
675 INSERT OR IGNORE INTO new_chats_contacts SELECT chat_id, contact_id FROM chats_contacts;\
676 DROP TABLE chats_contacts;\
677 ALTER TABLE new_chats_contacts RENAME TO chats_contacts;\
678 CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);\
679 CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
680 95
681 ).await?;
682 }
683 if dbversion < 96 {
684 sql.execute_migration(
685 "ALTER TABLE acpeerstates ADD COLUMN verifier TEXT DEFAULT '';",
686 96,
687 )
688 .await?;
689 }
690 if dbversion < 97 {
691 sql.execute_migration(
692 "CREATE TABLE dns_cache (
693 hostname TEXT NOT NULL,
694 address TEXT NOT NULL, -- IPv4 or IPv6 address
695 timestamp INTEGER NOT NULL,
696 UNIQUE (hostname, address)
697 )",
698 97,
699 )
700 .await?;
701 }
702 if dbversion < 98 {
703 if exists_before_update && sql.get_raw_config_int("show_emails").await?.is_none() {
704 sql.set_raw_config_int("show_emails", ShowEmails::Off as i32)
705 .await?;
706 }
707 sql.set_db_version(98).await?;
708 }
709 if dbversion < 99 {
710 sql.set_db_version(99).await?;
722 }
723 if dbversion < 100 {
724 sql.execute_migration(
725 "ALTER TABLE msgs ADD COLUMN mime_compressed INTEGER NOT NULL DEFAULT 0",
726 100,
727 )
728 .await?;
729 }
730 if dbversion < 101 {
731 sql.execute_migration(
734 "DROP TABLE smtp;
735 CREATE TABLE smtp (
736 id INTEGER PRIMARY KEY AUTOINCREMENT,
737 rfc724_mid TEXT NOT NULL, -- Message-ID
738 mime TEXT NOT NULL, -- SMTP payload
739 msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
740 recipients TEXT NOT NULL, -- List of recipients separated by space
741 retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
742 );
743 ",
744 101,
745 )
746 .await?;
747 }
748
749 if dbversion < 102 {
750 sql.execute_migration(
751 "CREATE TABLE download (
752 msg_id INTEGER NOT NULL -- id of the message stub in msgs table
753 )",
754 102,
755 )
756 .await?;
757 }
758
759 if dbversion < 103 {
761 sql.execute_migration(
762 "ALTER TABLE contacts ADD COLUMN is_bot INTEGER NOT NULL DEFAULT 0",
763 103,
764 )
765 .await?;
766 }
767
768 if dbversion < 104 {
769 sql.execute_migration(
770 "ALTER TABLE acpeerstates
771 ADD COLUMN secondary_verified_key;
772 ALTER TABLE acpeerstates
773 ADD COLUMN secondary_verified_key_fingerprint TEXT DEFAULT '';
774 ALTER TABLE acpeerstates
775 ADD COLUMN secondary_verifier TEXT DEFAULT ''",
776 104,
777 )
778 .await?;
779 }
780
781 if dbversion < 105 {
782 sql.execute_migration(
784 r#"CREATE TABLE new_msgs_status_updates (
785id INTEGER PRIMARY KEY AUTOINCREMENT,
786msg_id INTEGER,
787update_item TEXT DEFAULT '',
788uid TEXT UNIQUE
789);
790INSERT OR IGNORE INTO new_msgs_status_updates SELECT
791 id, msg_id, update_item, NULL
792FROM msgs_status_updates;
793DROP TABLE msgs_status_updates;
794ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
795CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
796CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
797"#,
798 105,
799 )
800 .await?;
801 }
802
803 if dbversion < 106 {
804 sql.execute_migration(
806 "CREATE TABLE new_config (
807 id INTEGER PRIMARY KEY,
808 keyname TEXT UNIQUE,
809 value TEXT NOT NULL
810 );
811 INSERT OR IGNORE INTO new_config SELECT
812 id, keyname, value
813 FROM config;
814 DROP TABLE config;
815 ALTER TABLE new_config RENAME TO config;
816 CREATE INDEX config_index1 ON config (keyname);",
817 106,
818 )
819 .await?;
820 }
821
822 if dbversion < 107 {
823 sql.execute_migration(
824 "CREATE TABLE new_keypairs (
825 id INTEGER PRIMARY KEY AUTOINCREMENT,
826 private_key UNIQUE NOT NULL,
827 public_key UNIQUE NOT NULL
828 );
829 INSERT OR IGNORE INTO new_keypairs SELECT id, private_key, public_key FROM keypairs;
830
831 INSERT OR IGNORE
832 INTO config (keyname, value)
833 VALUES
834 ('key_id', (SELECT id FROM new_keypairs
835 WHERE private_key=
836 (SELECT private_key FROM keypairs
837 WHERE addr=(SELECT value FROM config WHERE keyname='configured_addr')
838 AND is_default=1)));
839
840 -- We do not drop the old `keypairs` table for now,
841 -- but move it to `old_keypairs`. We can remove it later
842 -- in next migrations. This may be needed for recovery
843 -- in case something is wrong with the migration.
844 ALTER TABLE keypairs RENAME TO old_keypairs;
845 ALTER TABLE new_keypairs RENAME TO keypairs;
846 ",
847 107,
848 )
849 .await?;
850 }
851
852 if dbversion < 108 {
853 let version = 108;
854 let chunk_size = context.get_max_smtp_rcpt_to().await?;
855 sql.transaction(move |trans| {
856 Sql::set_db_version_trans(trans, version)?;
857 let id_max =
858 trans.query_row("SELECT IFNULL((SELECT MAX(id) FROM smtp), 0)", (), |row| {
859 let id_max: i64 = row.get(0)?;
860 Ok(id_max)
861 })?;
862 while let Some((id, rfc724_mid, mime, msg_id, recipients, retries)) = trans
863 .query_row(
864 "SELECT id, rfc724_mid, mime, msg_id, recipients, retries FROM smtp \
865 WHERE id<=? LIMIT 1",
866 (id_max,),
867 |row| {
868 let id: i64 = row.get(0)?;
869 let rfc724_mid: String = row.get(1)?;
870 let mime: String = row.get(2)?;
871 let msg_id: MsgId = row.get(3)?;
872 let recipients: String = row.get(4)?;
873 let retries: i64 = row.get(5)?;
874 Ok((id, rfc724_mid, mime, msg_id, recipients, retries))
875 },
876 )
877 .optional()?
878 {
879 trans.execute("DELETE FROM smtp WHERE id=?", (id,))?;
880 let recipients = recipients.split(' ').collect::<Vec<_>>();
881 for recipients in recipients.chunks(chunk_size) {
882 let recipients = recipients.join(" ");
883 trans.execute(
884 "INSERT INTO smtp (rfc724_mid, mime, msg_id, recipients, retries) \
885 VALUES (?, ?, ?, ?, ?)",
886 (&rfc724_mid, &mime, msg_id, recipients, retries),
887 )?;
888 }
889 }
890 Ok(())
891 })
892 .await
893 .with_context(|| format!("migration failed for version {version}"))?;
894
895 sql.set_db_version_in_cache(version).await?;
896 }
897
898 if dbversion < 109 {
899 sql.execute_migration(
900 r#"ALTER TABLE acpeerstates
901 ADD COLUMN backward_verified_key_id -- What we think the contact has as our verified key
902 INTEGER;
903 UPDATE acpeerstates
904 SET backward_verified_key_id=(SELECT value FROM config WHERE keyname='key_id')
905 WHERE verified_key IS NOT NULL
906 "#,
907 109,
908 )
909 .await?;
910 }
911
912 if dbversion < 110 {
913 sql.execute_migration(
914 "ALTER TABLE keypairs ADD COLUMN addr TEXT DEFAULT '' COLLATE NOCASE;
915 ALTER TABLE keypairs ADD COLUMN is_default INTEGER DEFAULT 0;
916 ALTER TABLE keypairs ADD COLUMN created INTEGER DEFAULT 0;
917 UPDATE keypairs SET addr=(SELECT value FROM config WHERE keyname='configured_addr'), is_default=1;",
918 110,
919 )
920 .await?;
921 }
922
923 if dbversion < 111 {
924 sql.execute_migration(
925 "CREATE TABLE iroh_gossip_peers (msg_id TEXT not NULL, topic TEXT NOT NULL, public_key TEXT NOT NULL)",
926 111,
927 )
928 .await?;
929 }
930
931 if dbversion < 112 {
932 sql.execute_migration(
933 "DROP TABLE iroh_gossip_peers; CREATE TABLE iroh_gossip_peers (msg_id INTEGER not NULL, topic BLOB NOT NULL, public_key BLOB NOT NULL, relay_server TEXT, UNIQUE (public_key, topic)) STRICT",
934 112,
935 )
936 .await?;
937 }
938
939 if dbversion < 113 {
940 sql.execute_migration(
941 "DROP TABLE iroh_gossip_peers; CREATE TABLE iroh_gossip_peers (msg_id INTEGER not NULL, topic BLOB NOT NULL, public_key BLOB NOT NULL, relay_server TEXT, UNIQUE (topic, public_key), PRIMARY KEY(topic, public_key)) STRICT",
942 113,
943 )
944 .await?;
945 }
946
947 if dbversion < 114 {
948 sql.execute_migration("CREATE INDEX reactions_index1 ON reactions (msg_id)", 114)
949 .await?;
950 }
951
952 if dbversion < 115 {
953 sql.execute_migration("ALTER TABLE msgs ADD COLUMN txt_normalized TEXT", 115)
954 .await?;
955 }
956 let mut migration_version: i32 = 115;
957
958 inc_and_check(&mut migration_version, 116)?;
959 if dbversion < migration_version {
960 sql.execute_migration(
963 "ALTER TABLE msgs ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0",
964 migration_version,
965 )
966 .await?;
967 }
968
969 inc_and_check(&mut migration_version, 117)?;
970 if dbversion < migration_version {
971 sql.execute_migration(
972 "CREATE TABLE connection_history (
973 host TEXT NOT NULL, -- server hostname
974 port INTEGER NOT NULL, -- server port
975 alpn TEXT NOT NULL, -- ALPN such as smtp or imap
976 addr TEXT NOT NULL, -- IP address
977 timestamp INTEGER NOT NULL, -- timestamp of the most recent successful connection
978 UNIQUE (host, port, alpn, addr)
979 ) STRICT",
980 migration_version,
981 )
982 .await?;
983 }
984
985 inc_and_check(&mut migration_version, 118)?;
986 if dbversion < migration_version {
987 sql.execute_migration(
988 "CREATE TABLE tokens_new (
989 id INTEGER PRIMARY KEY,
990 namespc INTEGER DEFAULT 0,
991 foreign_key TEXT DEFAULT '',
992 token TEXT DEFAULT '',
993 timestamp INTEGER DEFAULT 0
994 ) STRICT;
995 INSERT INTO tokens_new
996 SELECT t.id, t.namespc, IFNULL(c.grpid, ''), t.token, t.timestamp
997 FROM tokens t LEFT JOIN chats c ON t.foreign_id=c.id;
998 DROP TABLE tokens;
999 ALTER TABLE tokens_new RENAME TO tokens;",
1000 migration_version,
1001 )
1002 .await?;
1003 }
1004
1005 inc_and_check(&mut migration_version, 119)?;
1006 if dbversion < migration_version {
1007 sql.execute_migration(
1008 "CREATE TABLE imap_send (
1009 id INTEGER PRIMARY KEY AUTOINCREMENT,
1010 mime TEXT NOT NULL, -- Message content
1011 msg_id INTEGER NOT NULL, -- ID of the message in the `msgs` table
1012 attempts INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
1013 )",
1014 migration_version,
1015 )
1016 .await?;
1017 }
1018
1019 inc_and_check(&mut migration_version, 120)?;
1020 if dbversion < migration_version {
1021 sql.execute_migration(
1033 "INSERT OR IGNORE INTO config (keyname, value)
1034 SELECT 'delete_server_after', '0'
1035 FROM config WHERE keyname='configured'
1036 ",
1037 migration_version,
1038 )
1039 .await?;
1040 }
1041
1042 inc_and_check(&mut migration_version, 121)?;
1043 if dbversion < migration_version {
1044 sql.execute_migration(
1045 "CREATE INDEX chats_index4 ON chats (name)",
1046 migration_version,
1047 )
1048 .await?;
1049 }
1050
1051 inc_and_check(&mut migration_version, 122)?;
1052 if dbversion < migration_version {
1053 sql.execute_migration(
1054 "ALTER TABLE tokens ADD COLUMN foreign_id INTEGER NOT NULL DEFAULT 0",
1055 migration_version,
1056 )
1057 .await?;
1058 }
1059
1060 inc_and_check(&mut migration_version, 123)?;
1061 if dbversion < migration_version {
1062 sql.execute_migration(
1064 "CREATE TABLE new_msgs_status_updates (
1065 id INTEGER PRIMARY KEY AUTOINCREMENT,
1066 msg_id INTEGER,
1067 update_item TEXT DEFAULT '',
1068 uid TEXT UNIQUE,
1069 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE
1070 );
1071 INSERT OR IGNORE INTO new_msgs_status_updates SELECT
1072 id, msg_id, update_item, uid
1073 FROM msgs_status_updates;
1074 DROP TABLE msgs_status_updates;
1075 ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
1076 CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
1077 CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
1078 ",
1079 migration_version,
1080 )
1081 .await?;
1082 }
1083
1084 inc_and_check(&mut migration_version, 124)?;
1085 if dbversion < migration_version {
1086 sql.execute_migration(
1088 "UPDATE chats
1089 SET protected=1 -- ProtectionStatus::Protected
1090 WHERE type==100 -- Chattype::Single
1091 AND EXISTS (
1092 SELECT 1 FROM chats_contacts cc
1093 WHERE cc.chat_id==chats.id
1094 AND cc.contact_id=1
1095 )
1096 ",
1097 migration_version,
1098 )
1099 .await?;
1100 }
1101
1102 inc_and_check(&mut migration_version, 125)?;
1103 if dbversion < migration_version {
1104 sql.execute_migration(
1105 "CREATE TABLE http_cache (
1106 url TEXT PRIMARY KEY,
1107 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1108 blobname TEXT NOT NULL,
1109 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1110 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1111 ) STRICT",
1112 migration_version,
1113 )
1114 .await?;
1115 }
1116
1117 inc_and_check(&mut migration_version, 126)?;
1118 if dbversion < migration_version {
1119 sql.execute_migration(
1121 "DROP TABLE http_cache;
1122 CREATE TABLE http_cache (
1123 url TEXT PRIMARY KEY,
1124 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1125 stale INTEGER NOT NULL, -- When the cache entry is considered stale, timestamp in seconds.
1126 blobname TEXT NOT NULL,
1127 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1128 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1129 ) STRICT",
1130 migration_version,
1131 )
1132 .await?;
1133 }
1134
1135 inc_and_check(&mut migration_version, 127)?;
1136 if dbversion < migration_version {
1137 sql.execute_migration(
1139 "INSERT OR IGNORE INTO config (keyname, value)
1140 SELECT 'bcc_self', '1'
1141 FROM config WHERE keyname='delete_server_after' AND value='0'
1142 ",
1143 migration_version,
1144 )
1145 .await?;
1146 }
1147
1148 inc_and_check(&mut migration_version, 128)?;
1149 if dbversion < migration_version {
1150 sql.execute_migration(
1156 "ALTER TABLE chats_contacts
1157 ADD COLUMN add_timestamp NOT NULL DEFAULT 0;
1158 ALTER TABLE chats_contacts
1159 ADD COLUMN remove_timestamp NOT NULL DEFAULT 0;
1160 ",
1161 migration_version,
1162 )
1163 .await?;
1164 }
1165
1166 inc_and_check(&mut migration_version, 129)?;
1167 if dbversion < migration_version {
1168 sql.execute_migration(
1176 "INSERT OR IGNORE INTO config (keyname, value)
1177 SELECT 'bcc_self', '1'
1178 FROM config WHERE keyname='delete_server_after' AND value!='1'
1179 ",
1180 migration_version,
1181 )
1182 .await?;
1183 }
1184
1185 inc_and_check(&mut migration_version, 130)?;
1186 if dbversion < migration_version {
1187 sql.execute_migration(
1188 "
1189CREATE TABLE gossip_timestamp (
1190 chat_id INTEGER NOT NULL,
1191 fingerprint TEXT NOT NULL, -- Upper-case fingerprint of the key.
1192 timestamp INTEGER NOT NULL,
1193 UNIQUE (chat_id, fingerprint)
1194) STRICT;
1195CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
1196",
1197 migration_version,
1198 )
1199 .await?;
1200 }
1201
1202 inc_and_check(&mut migration_version, 131)?;
1203 if dbversion < migration_version {
1204 let entered_param = EnteredLoginParam::load(context).await?;
1205 let configured_param = ConfiguredLoginParam::load_legacy(context).await?;
1206
1207 sql.execute_migration_transaction(
1208 |transaction| {
1209 transaction.execute(
1210 "CREATE TABLE transports (
1211 id INTEGER PRIMARY KEY AUTOINCREMENT,
1212 addr TEXT NOT NULL,
1213 entered_param TEXT NOT NULL,
1214 configured_param TEXT NOT NULL,
1215 UNIQUE(addr)
1216 )",
1217 (),
1218 )?;
1219 if let Some(configured_param) = configured_param {
1220 transaction.execute(
1221 "INSERT INTO transports (addr, entered_param, configured_param)
1222 VALUES (?, ?, ?)",
1223 (
1224 configured_param.addr.clone(),
1225 serde_json::to_string(&entered_param)?,
1226 configured_param.into_json()?,
1227 ),
1228 )?;
1229 }
1230
1231 Ok(())
1232 },
1233 migration_version,
1234 )
1235 .await?;
1236 }
1237
1238 inc_and_check(&mut migration_version, 132)?;
1239 if dbversion < migration_version {
1240 let start = Time::now();
1241 sql.execute_migration_transaction(|t| migrate_key_contacts(context, t), migration_version)
1242 .await?;
1243 info!(
1244 context,
1245 "key-contacts migration took {:?} in total.",
1246 time_elapsed(&start),
1247 );
1248 context
1250 .set_config_internal(Config::LastHousekeeping, None)
1251 .await?;
1252 }
1253
1254 inc_and_check(&mut migration_version, 133)?;
1255 if dbversion < migration_version {
1256 sql.execute_migration(
1258 "UPDATE chats SET protected=0 WHERE protected!=1",
1259 migration_version,
1260 )
1261 .await?;
1262 }
1263
1264 let new_version = sql
1265 .get_raw_config_int(VERSION_CFG)
1266 .await?
1267 .unwrap_or_default();
1268 if new_version != dbversion || !exists_before_update {
1269 let created_db = if exists_before_update {
1270 ""
1271 } else {
1272 "Created new database. "
1273 };
1274 info!(context, "{}Migration done from v{}.", created_db, dbversion);
1275 }
1276 info!(context, "Database version: v{new_version}.");
1277
1278 Ok((update_icons, disable_server_delete, recode_avatar))
1279}
1280
1281fn migrate_key_contacts(
1282 context: &Context,
1283 transaction: &mut rusqlite::Transaction<'_>,
1284) -> std::result::Result<(), anyhow::Error> {
1285 info!(context, "Starting key-contact transition.");
1286
1287 transaction.execute_batch(
1290 "ALTER TABLE contacts ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';
1291
1292 -- Verifier is an ID of the verifier contact.
1293 -- 0 if the contact is not verified.
1294 ALTER TABLE contacts ADD COLUMN verifier INTEGER NOT NULL DEFAULT 0;
1295
1296 CREATE INDEX contacts_fingerprint_index ON contacts (fingerprint);
1297
1298 CREATE TABLE public_keys (
1299 id INTEGER PRIMARY KEY AUTOINCREMENT,
1300 fingerprint TEXT NOT NULL UNIQUE, -- Upper-case fingerprint of the key.
1301 public_key BLOB NOT NULL -- Binary key, not ASCII-armored
1302 ) STRICT;
1303 CREATE INDEX public_key_index ON public_keys (fingerprint);
1304
1305 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1306 SELECT public_key_fingerprint, public_key FROM acpeerstates
1307 WHERE public_key_fingerprint IS NOT NULL AND public_key IS NOT NULL;
1308
1309 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1310 SELECT gossip_key_fingerprint, gossip_key FROM acpeerstates
1311 WHERE gossip_key_fingerprint IS NOT NULL AND gossip_key IS NOT NULL;
1312
1313 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1314 SELECT verified_key_fingerprint, verified_key FROM acpeerstates
1315 WHERE verified_key_fingerprint IS NOT NULL AND verified_key IS NOT NULL;
1316
1317 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1318 SELECT secondary_verified_key_fingerprint, secondary_verified_key FROM acpeerstates
1319 WHERE secondary_verified_key_fingerprint IS NOT NULL AND secondary_verified_key IS NOT NULL;",
1320 )
1321 .context("Creating key-contact tables")?;
1322
1323 let Some(self_addr): Option<String> = transaction
1324 .query_row(
1325 "SELECT value FROM config WHERE keyname='configured_addr'",
1326 (),
1327 |row| row.get(0),
1328 )
1329 .optional()
1330 .context("Step 0")?
1331 else {
1332 info!(
1333 context,
1334 "Not yet configured, no need to migrate key-contacts"
1335 );
1336 return Ok(());
1337 };
1338
1339 let mut autocrypt_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1346 let mut autocrypt_key_contacts_with_reset_peerstate: BTreeMap<u32, u32> = BTreeMap::new();
1347 let mut verified_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1348 {
1349 let mut verifications: BTreeMap<u32, u32> = BTreeMap::new();
1353
1354 let mut load_contacts_stmt = transaction
1355 .prepare(
1356 "SELECT c.id, c.name, c.addr, c.origin, c.blocked, c.last_seen,
1357 c.authname, c.param, c.status, c.is_bot, c.selfavatar_sent,
1358 IFNULL(p.public_key, p.gossip_key),
1359 p.verified_key, IFNULL(p.verifier, ''),
1360 p.secondary_verified_key, p.secondary_verifier, p.prefer_encrypted
1361 FROM contacts c
1362 INNER JOIN acpeerstates p ON c.addr=p.addr
1363 WHERE c.id > 9
1364 ORDER BY p.last_seen DESC",
1365 )
1366 .context("Step 2")?;
1367
1368 let all_address_contacts: rusqlite::Result<Vec<_>> = load_contacts_stmt
1369 .query_map((), |row| {
1370 let id: i64 = row.get(0)?;
1371 let name: String = row.get(1)?;
1372 let addr: String = row.get(2)?;
1373 let origin: i64 = row.get(3)?;
1374 let blocked: Option<bool> = row.get(4)?;
1375 let last_seen: i64 = row.get(5)?;
1376 let authname: String = row.get(6)?;
1377 let param: String = row.get(7)?;
1378 let status: Option<String> = row.get(8)?;
1379 let is_bot: bool = row.get(9)?;
1380 let selfavatar_sent: i64 = row.get(10)?;
1381 let autocrypt_key = row
1382 .get(11)
1383 .ok()
1384 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1385 let verified_key = row
1386 .get(12)
1387 .ok()
1388 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1389 let verifier: String = row.get(13)?;
1390 let secondary_verified_key = row
1391 .get(12)
1392 .ok()
1393 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1394 let secondary_verifier: String = row.get(15)?;
1395 let prefer_encrypt: u8 = row.get(16)?;
1396 Ok((
1397 id,
1398 name,
1399 addr,
1400 origin,
1401 blocked,
1402 last_seen,
1403 authname,
1404 param,
1405 status,
1406 is_bot,
1407 selfavatar_sent,
1408 autocrypt_key,
1409 verified_key,
1410 verifier,
1411 secondary_verified_key,
1412 secondary_verifier,
1413 prefer_encrypt,
1414 ))
1415 })
1416 .context("Step 3")?
1417 .collect();
1418
1419 let mut insert_contact_stmt = transaction
1420 .prepare(
1421 "INSERT INTO contacts (name, addr, origin, blocked, last_seen,
1422 authname, param, status, is_bot, selfavatar_sent, fingerprint)
1423 VALUES(?,?,?,?,?,?,?,?,?,?,?)",
1424 )
1425 .context("Step 4")?;
1426 let mut fingerprint_to_id_stmt = transaction
1427 .prepare("SELECT id FROM contacts WHERE fingerprint=? AND id>9")
1428 .context("Step 5")?;
1429 let mut original_contact_id_from_addr_stmt = transaction
1430 .prepare("SELECT id FROM contacts WHERE addr=? AND fingerprint='' AND id>9")
1431 .context("Step 6")?;
1432
1433 for row in all_address_contacts? {
1434 let (
1435 original_id,
1436 name,
1437 addr,
1438 origin,
1439 blocked,
1440 last_seen,
1441 authname,
1442 param,
1443 status,
1444 is_bot,
1445 selfavatar_sent,
1446 autocrypt_key,
1447 verified_key,
1448 verifier,
1449 secondary_verified_key,
1450 secondary_verifier,
1451 prefer_encrypt,
1452 ) = row;
1453 let mut insert_contact = |key: SignedPublicKey| -> Result<u32> {
1454 let fingerprint = key.dc_fingerprint().hex();
1455 let existing_contact_id: Option<u32> = fingerprint_to_id_stmt
1456 .query_row((&fingerprint,), |row| row.get(0))
1457 .optional()
1458 .context("Step 7")?;
1459 if let Some(existing_contact_id) = existing_contact_id {
1460 return Ok(existing_contact_id);
1461 }
1462 insert_contact_stmt
1463 .execute((
1464 &name,
1465 &addr,
1466 origin,
1467 blocked,
1468 last_seen,
1469 &authname,
1470 ¶m,
1471 &status,
1472 is_bot,
1473 selfavatar_sent,
1474 fingerprint.clone(),
1475 ))
1476 .context("Step 8")?;
1477 let id = transaction
1478 .last_insert_rowid()
1479 .try_into()
1480 .context("Step 9")?;
1481 info!(
1482 context,
1483 "Inserted new contact id={id} name='{name}' addr='{addr}' fingerprint={fingerprint}"
1484 );
1485 Ok(id)
1486 };
1487 let mut original_contact_id_from_addr = |addr: &str, default: u32| -> Result<u32> {
1488 if addr_cmp(addr, &self_addr) {
1489 Ok(1) } else if addr.is_empty() {
1491 Ok(default)
1492 } else {
1493 Ok(original_contact_id_from_addr_stmt
1494 .query_row((addr,), |row| row.get(0))
1495 .optional()
1496 .with_context(|| format!("Original contact '{addr}' not found"))?
1497 .unwrap_or(default))
1498 }
1499 };
1500
1501 let Some(autocrypt_key) = autocrypt_key else {
1502 continue;
1503 };
1504 let new_id = insert_contact(autocrypt_key).context("Step 10")?;
1505
1506 if prefer_encrypt != 20 {
1509 autocrypt_key_contacts.insert(original_id.try_into().context("Step 11")?, new_id);
1510 } else {
1511 autocrypt_key_contacts_with_reset_peerstate
1512 .insert(original_id.try_into().context("Step 12")?, new_id);
1513 }
1514
1515 let Some(verified_key) = verified_key else {
1516 continue;
1517 };
1518 let new_id = insert_contact(verified_key).context("Step 13")?;
1519 verified_key_contacts.insert(original_id.try_into().context("Step 14")?, new_id);
1520
1521 let verifier_id = if addr_cmp(&verifier, &addr) {
1522 1 } else {
1526 original_contact_id_from_addr(&verifier, new_id).context("Step 15")?
1530 };
1531 verifications.insert(new_id, verifier_id);
1532
1533 let Some(secondary_verified_key) = secondary_verified_key else {
1534 continue;
1535 };
1536 let new_id = insert_contact(secondary_verified_key).context("Step 16")?;
1537 let verifier_id: u32 = if addr_cmp(&secondary_verifier, &addr) {
1538 1 } else {
1540 original_contact_id_from_addr(&secondary_verifier, new_id).context("Step 17")?
1541 };
1542 verifications.entry(new_id).or_insert(verifier_id);
1544 }
1545 info!(
1546 context,
1547 "Created key-contacts identified by autocrypt key: {autocrypt_key_contacts:?}"
1548 );
1549 info!(
1550 context,
1551 "Created key-contacts with 'reset' peerstate identified by autocrypt key: {autocrypt_key_contacts_with_reset_peerstate:?}"
1552 );
1553 info!(
1554 context,
1555 "Created key-contacts identified by verified key: {verified_key_contacts:?}"
1556 );
1557
1558 for (&new_contact, &verifier_original_contact) in &verifications {
1559 let verifier = if verifier_original_contact == 1 {
1560 1 } else if verifier_original_contact == new_contact {
1562 new_contact } else {
1564 match verified_key_contacts.get(&verifier_original_contact) {
1567 Some(v) => *v,
1568 None => {
1569 warn!(
1570 context,
1571 "Couldn't find key-contact for {verifier_original_contact} who verified {new_contact}"
1572 );
1573 continue;
1574 }
1575 }
1576 };
1577 transaction
1578 .execute(
1579 "UPDATE contacts SET verifier=? WHERE id=?",
1580 (verifier, new_contact),
1581 )
1582 .context("Step 18")?;
1583 }
1584 info!(context, "Migrated verifications: {verifications:?}");
1585 }
1586
1587 let mut orphaned_contacts: BTreeSet<u32> = transaction
1592 .prepare("SELECT id FROM contacts WHERE id>9")
1593 .context("Step 19")?
1594 .query_map((), |row| row.get::<usize, u32>(0))
1595 .context("Step 20")?
1596 .collect::<Result<BTreeSet<u32>, rusqlite::Error>>()
1597 .context("Step 21")?;
1598
1599 {
1600 let mut stmt = transaction
1601 .prepare(
1602 "SELECT c.id, c.type, c.grpid, c.protected
1603 FROM chats c
1604 WHERE id>9",
1605 )
1606 .context("Step 22")?;
1607 let all_chats = stmt
1608 .query_map((), |row| {
1609 let id: u32 = row.get(0)?;
1610 let typ: u32 = row.get(1)?;
1611 let grpid: String = row.get(2)?;
1612 let protected: u32 = row.get(3)?;
1613 Ok((id, typ, grpid, protected))
1614 })
1615 .context("Step 23")?;
1616 let mut load_chat_contacts_stmt = transaction.prepare(
1617 "SELECT contact_id, add_timestamp>=remove_timestamp FROM chats_contacts
1618 WHERE chat_id=? AND contact_id>9",
1619 )?;
1620 let is_chatmail: Option<String> = transaction
1621 .query_row(
1622 "SELECT value FROM config WHERE keyname='is_chatmail'",
1623 (),
1624 |row| row.get(0),
1625 )
1626 .optional()
1627 .context("Step 23.1")?;
1628 let is_chatmail = is_chatmail
1629 .and_then(|s| s.parse::<i32>().ok())
1630 .unwrap_or_default()
1631 != 0;
1632 let map_to_key_contact = |old_member: &u32| {
1633 autocrypt_key_contacts
1634 .get(old_member)
1635 .or_else(|| {
1636 if is_chatmail {
1641 autocrypt_key_contacts_with_reset_peerstate.get(old_member)
1642 } else {
1643 None
1644 }
1645 })
1646 .copied()
1647 };
1648
1649 let mut update_member_stmt = transaction
1650 .prepare("UPDATE chats_contacts SET contact_id=? WHERE contact_id=? AND chat_id=?")?;
1651 let mut addr_cmp_stmt = transaction
1652 .prepare("SELECT c.addr=d.addr FROM contacts c, contacts d WHERE c.id=? AND d.id=?")?;
1653 for chat in all_chats {
1654 let (chat_id, typ, grpid, protected) = chat.context("Step 24")?;
1655 let old_members: Vec<(u32, bool)> = load_chat_contacts_stmt
1657 .query_map((chat_id,), |row| {
1658 let id: u32 = row.get(0)?;
1659 let present: bool = row.get(1)?;
1660 Ok((id, present))
1661 })
1662 .context("Step 25")?
1663 .collect::<Result<Vec<_>, _>>()
1664 .context("Step 26")?;
1665
1666 let mut keep_address_contacts = |reason: &str| -> Result<()> {
1667 info!(
1668 context,
1669 "Chat {chat_id} will be an unencrypted chat with contacts identified by email address: {reason}."
1670 );
1671 for (m, _) in &old_members {
1672 orphaned_contacts.remove(m);
1673 }
1674
1675 transaction
1679 .execute("UPDATE chats SET protected=0 WHERE id=?", (chat_id,))
1680 .context("Step 26.0")?;
1681
1682 Ok(())
1683 };
1684 let old_and_new_members: Vec<(u32, bool, Option<u32>)> = match typ {
1685 100 => {
1693 let Some((old_member, _)) = old_members.first() else {
1694 info!(
1695 context,
1696 "1:1 chat {chat_id} doesn't contain contact, probably it's self or device chat."
1697 );
1698 continue;
1699 };
1700
1701 let Some(new_contact) = map_to_key_contact(old_member) else {
1702 keep_address_contacts("No peerstate, or peerstate in 'reset' state")?;
1703 continue;
1704 };
1705 if !addr_cmp_stmt
1706 .query_row((old_member, new_contact), |row| row.get::<_, bool>(0))?
1707 {
1708 keep_address_contacts("key contact has different email")?;
1709 continue;
1710 }
1711 vec![(*old_member, true, Some(new_contact))]
1712 }
1713
1714 120 => {
1716 if grpid.is_empty() {
1717 keep_address_contacts("Empty chat-Group-ID")?;
1720 continue;
1721 } else if protected == 1 {
1722 old_members
1723 .iter()
1724 .map(|&(id, present)| {
1725 (id, present, verified_key_contacts.get(&id).copied())
1726 })
1727 .collect()
1728 } else {
1729 old_members
1730 .iter()
1731 .map(|&(id, present)| (id, present, map_to_key_contact(&id)))
1732 .collect::<Vec<(u32, bool, Option<u32>)>>()
1733 }
1734 }
1735
1736 140 => {
1738 keep_address_contacts("Mailinglist")?;
1739 continue;
1740 }
1741
1742 160 => old_members
1744 .iter()
1745 .map(|(original, _)| {
1746 (
1747 *original,
1748 true,
1749 autocrypt_key_contacts
1750 .get(original)
1751 .or_else(|| {
1755 autocrypt_key_contacts_with_reset_peerstate.get(original)
1756 })
1757 .copied(),
1758 )
1759 })
1760 .collect::<Vec<(u32, bool, Option<u32>)>>(),
1761 _ => {
1762 warn!(context, "Invalid chat type {typ}");
1763 continue;
1764 }
1765 };
1766
1767 if typ == 120
1770 && old_and_new_members
1771 .iter()
1772 .any(|&(_old, present, new)| present && new.is_none())
1773 {
1774 transaction
1775 .execute("UPDATE chats SET grpid='' WHERE id=?", (chat_id,))
1776 .context("Step 26.1")?;
1777 keep_address_contacts("Group contains contact without peerstate")?;
1778 continue;
1779 }
1780
1781 let human_readable_transitions = old_and_new_members
1782 .iter()
1783 .map(|(old, _, new)| format!("{old}->{}", new.unwrap_or_default()))
1784 .collect::<Vec<String>>()
1785 .join(" ");
1786 info!(
1787 context,
1788 "Migrating chat {chat_id} to key-contacts: {human_readable_transitions}"
1789 );
1790
1791 for (old_member, _, new_member) in old_and_new_members {
1792 if let Some(new_member) = new_member {
1793 orphaned_contacts.remove(&new_member);
1794 let res = update_member_stmt.execute((new_member, old_member, chat_id));
1795 if res.is_err() {
1796 let member_to_delete: u32 = transaction
1803 .query_row(
1804 "SELECT contact_id
1805 FROM chats_contacts
1806 WHERE chat_id=? AND contact_id IN (?,?)
1807 ORDER BY add_timestamp>=remove_timestamp, add_timestamp LIMIT 1",
1808 (chat_id, new_member, old_member),
1809 |row| row.get(0),
1810 )
1811 .context("Step 27")?;
1812 info!(
1813 context,
1814 "Chat partner is in the chat {chat_id} multiple times. \
1815 Deleting {member_to_delete}, then trying to update \
1816 {old_member}->{new_member} again"
1817 );
1818 transaction
1819 .execute(
1820 "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
1821 (chat_id, member_to_delete),
1822 )
1823 .context("Step 28")?;
1824 update_member_stmt.execute((new_member, old_member, chat_id))?;
1827 }
1828 } else {
1829 info!(
1830 context,
1831 "Old member {old_member} in chat {chat_id} can't be upgraded to key-contact, removing them"
1832 );
1833 transaction
1834 .execute(
1835 "DELETE FROM chats_contacts WHERE contact_id=? AND chat_id=?",
1836 (old_member, chat_id),
1837 )
1838 .context("Step 29")?;
1839 }
1840 }
1841 }
1842 }
1843
1844 {
1846 info!(
1847 context,
1848 "Marking contacts which remained in no chat at all as hidden: {orphaned_contacts:?}"
1849 );
1850 let mut mark_as_hidden_stmt = transaction
1851 .prepare("UPDATE contacts SET origin=? WHERE id=?")
1852 .context("Step 30")?;
1853 for contact in orphaned_contacts {
1854 mark_as_hidden_stmt
1855 .execute((0x8, contact))
1856 .context("Step 31")?;
1857 }
1858 }
1859
1860 {
1863 let mut contacts_map = autocrypt_key_contacts_with_reset_peerstate;
1864 for (old, new) in autocrypt_key_contacts {
1865 contacts_map.insert(old, new);
1866 }
1867 transaction
1868 .execute(
1869 "CREATE TABLE key_contacts_map (
1870 old_id INTEGER PRIMARY KEY NOT NULL,
1871 new_id INTEGER NOT NULL
1872 ) STRICT",
1873 (),
1874 )
1875 .context("Step 32")?;
1876 {
1877 let mut stmt = transaction
1878 .prepare("INSERT INTO key_contacts_map (old_id, new_id) VALUES (?, ?)")
1879 .context("Step 33")?;
1880 for ids in contacts_map {
1881 stmt.execute(ids).context("Step 34")?;
1882 }
1883 }
1884 transaction
1885 .execute(
1886 "INSERT INTO config (keyname, value) VALUES (
1887 'first_key_contacts_msg_id',
1888 IFNULL((SELECT MAX(id)+1 FROM msgs), 0)
1889 )",
1890 (),
1891 )
1892 .context("Step 35")?;
1893 }
1894
1895 Ok(())
1896}
1897
1898pub(crate) async fn msgs_to_key_contacts(context: &Context) -> Result<()> {
1900 let sql = &context.sql;
1901 if sql
1902 .get_raw_config_int64("first_key_contacts_msg_id")
1903 .await?
1904 <= Some(0)
1905 {
1906 return Ok(());
1907 }
1908 let trans_fn = |t: &mut rusqlite::Transaction| {
1909 let mut first_key_contacts_msg_id: u64 = t
1910 .query_one(
1911 "SELECT CAST(value AS INTEGER) FROM config WHERE keyname='first_key_contacts_msg_id'",
1912 (),
1913 |row| row.get(0),
1914 )
1915 .context("Get first_key_contacts_msg_id")?;
1916 let mut stmt = t
1917 .prepare(
1918 "UPDATE msgs SET
1919 from_id=IFNULL(
1920 (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.from_id),
1921 from_id
1922 ),
1923 to_id=IFNULL(
1924 (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.to_id),
1925 to_id
1926 )
1927 WHERE id>=? AND id<?
1928 AND chat_id>9
1929 AND (param GLOB '*\nc=1*' OR param GLOB 'c=1*')",
1930 )
1931 .context("Prepare stmt")?;
1932 let msgs_to_migrate = 1000;
1933 let mut msgs_migrated: u64 = 0;
1934 while first_key_contacts_msg_id > 0 && msgs_migrated < msgs_to_migrate {
1935 let start_msg_id = first_key_contacts_msg_id.saturating_sub(msgs_to_migrate);
1936 let cnt: u64 = stmt
1937 .execute((start_msg_id, first_key_contacts_msg_id))
1938 .context("UPDATE msgs")?
1939 .try_into()?;
1940 msgs_migrated += cnt;
1941 first_key_contacts_msg_id = start_msg_id;
1942 }
1943 t.execute(
1944 "UPDATE config SET value=? WHERE keyname='first_key_contacts_msg_id'",
1945 (first_key_contacts_msg_id,),
1946 )
1947 .context("Update first_key_contacts_msg_id")?;
1948 Ok((msgs_migrated, first_key_contacts_msg_id))
1949 };
1950 let start = Time::now();
1951 let mut msgs_migrated = 0;
1952 loop {
1953 let (n, first_key_contacts_msg_id) = sql.transaction(trans_fn).await?;
1954 msgs_migrated += n;
1955 if first_key_contacts_msg_id == 0 || time_elapsed(&start) >= Duration::from_millis(500) {
1956 break;
1957 }
1958 }
1959 sql.uncache_raw_config("first_key_contacts_msg_id").await;
1960 info!(
1961 context,
1962 "Rewriting {msgs_migrated} msgs to key-contacts took {:?}.",
1963 time_elapsed(&start),
1964 );
1965 Ok(())
1966}
1967
1968impl Sql {
1969 async fn set_db_version(&self, version: i32) -> Result<()> {
1970 self.set_raw_config_int(VERSION_CFG, version).await?;
1971 Ok(())
1972 }
1973
1974 fn set_db_version_trans(transaction: &mut rusqlite::Transaction, version: i32) -> Result<()> {
1976 transaction.execute(
1977 "UPDATE config SET value=? WHERE keyname=?;",
1978 (format!("{version}"), VERSION_CFG),
1979 )?;
1980 Ok(())
1981 }
1982
1983 async fn set_db_version_in_cache(&self, version: i32) -> Result<()> {
1984 let mut lock = self.config_cache.write().await;
1985 lock.insert(VERSION_CFG.to_string(), Some(format!("{version}")));
1986 Ok(())
1987 }
1988
1989 async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
1990 self.execute_migration_transaction(
1991 |transaction| {
1992 transaction.execute_batch(query)?;
1993 Ok(())
1994 },
1995 version,
1996 )
1997 .await
1998 }
1999
2000 async fn execute_migration_transaction(
2001 &self,
2002 migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
2003 version: i32,
2004 ) -> Result<()> {
2005 #[cfg(test)]
2006 if STOP_MIGRATIONS_AT.try_with(|stop_migrations_at| version > *stop_migrations_at)
2007 == Ok(true)
2008 {
2009 println!("Not running migration {version}, because STOP_MIGRATIONS_AT is set");
2010 return Ok(());
2011 }
2012
2013 self.transaction(move |transaction| {
2014 let curr_version: String = transaction.query_row(
2015 "SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
2016 ("0", VERSION_CFG),
2017 |row| row.get(0),
2018 )?;
2019 let curr_version: i32 = curr_version.parse()?;
2020 ensure!(curr_version < version, "Db version must be increased");
2021 Self::set_db_version_trans(transaction, version)?;
2022 migration(transaction)?;
2023
2024 Ok(())
2025 })
2026 .await
2027 .with_context(|| format!("execute_migration failed for version {version}"))?;
2028
2029 self.config_cache.write().await.clear();
2030
2031 Ok(())
2032 }
2033}
2034
2035#[cfg(test)]
2036mod migrations_tests;