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 let new_version = sql
1255 .get_raw_config_int(VERSION_CFG)
1256 .await?
1257 .unwrap_or_default();
1258 if new_version != dbversion || !exists_before_update {
1259 let created_db = if exists_before_update {
1260 ""
1261 } else {
1262 "Created new database. "
1263 };
1264 info!(context, "{}Migration done from v{}.", created_db, dbversion);
1265 }
1266 info!(context, "Database version: v{new_version}.");
1267
1268 Ok((update_icons, disable_server_delete, recode_avatar))
1269}
1270
1271fn migrate_key_contacts(
1272 context: &Context,
1273 transaction: &mut rusqlite::Transaction<'_>,
1274) -> std::result::Result<(), anyhow::Error> {
1275 info!(context, "Starting key-contact transition.");
1276
1277 transaction.execute_batch(
1280 "ALTER TABLE contacts ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';
1281
1282 -- Verifier is an ID of the verifier contact.
1283 -- 0 if the contact is not verified.
1284 ALTER TABLE contacts ADD COLUMN verifier INTEGER NOT NULL DEFAULT 0;
1285
1286 CREATE INDEX contacts_fingerprint_index ON contacts (fingerprint);
1287
1288 CREATE TABLE public_keys (
1289 id INTEGER PRIMARY KEY AUTOINCREMENT,
1290 fingerprint TEXT NOT NULL UNIQUE, -- Upper-case fingerprint of the key.
1291 public_key BLOB NOT NULL -- Binary key, not ASCII-armored
1292 ) STRICT;
1293 CREATE INDEX public_key_index ON public_keys (fingerprint);
1294
1295 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1296 SELECT public_key_fingerprint, public_key FROM acpeerstates
1297 WHERE public_key_fingerprint IS NOT NULL AND public_key IS NOT NULL;
1298
1299 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1300 SELECT gossip_key_fingerprint, gossip_key FROM acpeerstates
1301 WHERE gossip_key_fingerprint IS NOT NULL AND gossip_key IS NOT NULL;
1302
1303 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1304 SELECT verified_key_fingerprint, verified_key FROM acpeerstates
1305 WHERE verified_key_fingerprint IS NOT NULL AND verified_key IS NOT NULL;
1306
1307 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1308 SELECT secondary_verified_key_fingerprint, secondary_verified_key FROM acpeerstates
1309 WHERE secondary_verified_key_fingerprint IS NOT NULL AND secondary_verified_key IS NOT NULL;",
1310 )
1311 .context("Creating key-contact tables")?;
1312
1313 let Some(self_addr): Option<String> = transaction
1314 .query_row(
1315 "SELECT value FROM config WHERE keyname='configured_addr'",
1316 (),
1317 |row| row.get(0),
1318 )
1319 .optional()
1320 .context("Step 0")?
1321 else {
1322 info!(
1323 context,
1324 "Not yet configured, no need to migrate key-contacts"
1325 );
1326 return Ok(());
1327 };
1328
1329 let mut autocrypt_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1336 let mut autocrypt_key_contacts_with_reset_peerstate: BTreeMap<u32, u32> = BTreeMap::new();
1337 let mut verified_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1338 {
1339 let mut verifications: BTreeMap<u32, u32> = BTreeMap::new();
1343
1344 let mut load_contacts_stmt = transaction
1345 .prepare(
1346 "SELECT c.id, c.name, c.addr, c.origin, c.blocked, c.last_seen,
1347 c.authname, c.param, c.status, c.is_bot, c.selfavatar_sent,
1348 IFNULL(p.public_key, p.gossip_key),
1349 p.verified_key, IFNULL(p.verifier, ''),
1350 p.secondary_verified_key, p.secondary_verifier, p.prefer_encrypted
1351 FROM contacts c
1352 INNER JOIN acpeerstates p ON c.addr=p.addr
1353 WHERE c.id > 9
1354 ORDER BY p.last_seen DESC",
1355 )
1356 .context("Step 2")?;
1357
1358 let all_address_contacts: rusqlite::Result<Vec<_>> = load_contacts_stmt
1359 .query_map((), |row| {
1360 let id: i64 = row.get(0)?;
1361 let name: String = row.get(1)?;
1362 let addr: String = row.get(2)?;
1363 let origin: i64 = row.get(3)?;
1364 let blocked: Option<bool> = row.get(4)?;
1365 let last_seen: i64 = row.get(5)?;
1366 let authname: String = row.get(6)?;
1367 let param: String = row.get(7)?;
1368 let status: Option<String> = row.get(8)?;
1369 let is_bot: bool = row.get(9)?;
1370 let selfavatar_sent: i64 = row.get(10)?;
1371 let autocrypt_key = row
1372 .get(11)
1373 .ok()
1374 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1375 let verified_key = row
1376 .get(12)
1377 .ok()
1378 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1379 let verifier: String = row.get(13)?;
1380 let secondary_verified_key = row
1381 .get(12)
1382 .ok()
1383 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1384 let secondary_verifier: String = row.get(15)?;
1385 let prefer_encrypt: u8 = row.get(16)?;
1386 Ok((
1387 id,
1388 name,
1389 addr,
1390 origin,
1391 blocked,
1392 last_seen,
1393 authname,
1394 param,
1395 status,
1396 is_bot,
1397 selfavatar_sent,
1398 autocrypt_key,
1399 verified_key,
1400 verifier,
1401 secondary_verified_key,
1402 secondary_verifier,
1403 prefer_encrypt,
1404 ))
1405 })
1406 .context("Step 3")?
1407 .collect();
1408
1409 let mut insert_contact_stmt = transaction
1410 .prepare(
1411 "INSERT INTO contacts (name, addr, origin, blocked, last_seen,
1412 authname, param, status, is_bot, selfavatar_sent, fingerprint)
1413 VALUES(?,?,?,?,?,?,?,?,?,?,?)",
1414 )
1415 .context("Step 4")?;
1416 let mut fingerprint_to_id_stmt = transaction
1417 .prepare("SELECT id FROM contacts WHERE fingerprint=? AND id>9")
1418 .context("Step 5")?;
1419 let mut original_contact_id_from_addr_stmt = transaction
1420 .prepare("SELECT id FROM contacts WHERE addr=? AND fingerprint='' AND id>9")
1421 .context("Step 6")?;
1422
1423 for row in all_address_contacts? {
1424 let (
1425 original_id,
1426 name,
1427 addr,
1428 origin,
1429 blocked,
1430 last_seen,
1431 authname,
1432 param,
1433 status,
1434 is_bot,
1435 selfavatar_sent,
1436 autocrypt_key,
1437 verified_key,
1438 verifier,
1439 secondary_verified_key,
1440 secondary_verifier,
1441 prefer_encrypt,
1442 ) = row;
1443 let mut insert_contact = |key: SignedPublicKey| -> Result<u32> {
1444 let fingerprint = key.dc_fingerprint().hex();
1445 let existing_contact_id: Option<u32> = fingerprint_to_id_stmt
1446 .query_row((&fingerprint,), |row| row.get(0))
1447 .optional()
1448 .context("Step 7")?;
1449 if let Some(existing_contact_id) = existing_contact_id {
1450 return Ok(existing_contact_id);
1451 }
1452 insert_contact_stmt
1453 .execute((
1454 &name,
1455 &addr,
1456 origin,
1457 blocked,
1458 last_seen,
1459 &authname,
1460 ¶m,
1461 &status,
1462 is_bot,
1463 selfavatar_sent,
1464 fingerprint.clone(),
1465 ))
1466 .context("Step 8")?;
1467 let id = transaction
1468 .last_insert_rowid()
1469 .try_into()
1470 .context("Step 9")?;
1471 info!(
1472 context,
1473 "Inserted new contact id={id} name='{name}' addr='{addr}' fingerprint={fingerprint}"
1474 );
1475 Ok(id)
1476 };
1477 let mut original_contact_id_from_addr = |addr: &str, default: u32| -> Result<u32> {
1478 if addr_cmp(addr, &self_addr) {
1479 Ok(1) } else if addr.is_empty() {
1481 Ok(default)
1482 } else {
1483 original_contact_id_from_addr_stmt
1484 .query_row((addr,), |row| row.get(0))
1485 .with_context(|| format!("Original contact '{addr}' not found"))
1486 }
1487 };
1488
1489 let Some(autocrypt_key) = autocrypt_key else {
1490 continue;
1491 };
1492 let new_id = insert_contact(autocrypt_key).context("Step 10")?;
1493
1494 if prefer_encrypt != 20 {
1497 autocrypt_key_contacts.insert(original_id.try_into().context("Step 11")?, new_id);
1498 } else {
1499 autocrypt_key_contacts_with_reset_peerstate
1500 .insert(original_id.try_into().context("Step 12")?, new_id);
1501 }
1502
1503 let Some(verified_key) = verified_key else {
1504 continue;
1505 };
1506 let new_id = insert_contact(verified_key).context("Step 13")?;
1507 verified_key_contacts.insert(original_id.try_into().context("Step 14")?, new_id);
1508 let verifier_id =
1512 original_contact_id_from_addr(&verifier, new_id).context("Step 15")?;
1513 verifications.insert(new_id, verifier_id);
1514
1515 let Some(secondary_verified_key) = secondary_verified_key else {
1516 continue;
1517 };
1518 let new_id = insert_contact(secondary_verified_key).context("Step 16")?;
1519 let verifier_id: u32 =
1520 original_contact_id_from_addr(&secondary_verifier, new_id).context("Step 17")?;
1521 verifications.entry(new_id).or_insert(verifier_id);
1523 }
1524 info!(
1525 context,
1526 "Created key-contacts identified by autocrypt key: {autocrypt_key_contacts:?}"
1527 );
1528 info!(
1529 context,
1530 "Created key-contacts with 'reset' peerstate identified by autocrypt key: {autocrypt_key_contacts_with_reset_peerstate:?}"
1531 );
1532 info!(
1533 context,
1534 "Created key-contacts identified by verified key: {verified_key_contacts:?}"
1535 );
1536
1537 for (&new_contact, &verifier_original_contact) in &verifications {
1538 let verifier = if verifier_original_contact == 1 {
1539 1 } else if verifier_original_contact == new_contact {
1541 new_contact } else {
1543 match verified_key_contacts.get(&verifier_original_contact) {
1546 Some(v) => *v,
1547 None => {
1548 warn!(
1549 context,
1550 "Couldn't find key-contact for {verifier_original_contact} who verified {new_contact}"
1551 );
1552 continue;
1553 }
1554 }
1555 };
1556 transaction
1557 .execute(
1558 "UPDATE contacts SET verifier=? WHERE id=?",
1559 (verifier, new_contact),
1560 )
1561 .context("Step 18")?;
1562 }
1563 info!(context, "Migrated verifications: {verifications:?}");
1564 }
1565
1566 let mut orphaned_contacts: BTreeSet<u32> = transaction
1571 .prepare("SELECT id FROM contacts WHERE id>9")
1572 .context("Step 19")?
1573 .query_map((), |row| row.get::<usize, u32>(0))
1574 .context("Step 20")?
1575 .collect::<Result<BTreeSet<u32>, rusqlite::Error>>()
1576 .context("Step 21")?;
1577
1578 {
1579 let mut stmt = transaction
1580 .prepare(
1581 "SELECT c.id, c.type, c.grpid, c.protected
1582 FROM chats c
1583 WHERE id>9",
1584 )
1585 .context("Step 22")?;
1586 let all_chats = stmt
1587 .query_map((), |row| {
1588 let id: u32 = row.get(0)?;
1589 let typ: u32 = row.get(1)?;
1590 let grpid: String = row.get(2)?;
1591 let protected: u32 = row.get(3)?;
1592 Ok((id, typ, grpid, protected))
1593 })
1594 .context("Step 23")?;
1595 let mut load_chat_contacts_stmt = transaction.prepare(
1596 "SELECT contact_id, add_timestamp>=remove_timestamp FROM chats_contacts
1597 WHERE chat_id=? AND contact_id>9",
1598 )?;
1599 let is_chatmail: Option<String> = transaction
1600 .query_row(
1601 "SELECT value FROM config WHERE keyname='is_chatmail'",
1602 (),
1603 |row| row.get(0),
1604 )
1605 .optional()
1606 .context("Step 23.1")?;
1607 let is_chatmail = is_chatmail
1608 .and_then(|s| s.parse::<i32>().ok())
1609 .unwrap_or_default()
1610 != 0;
1611 let map_to_key_contact = |old_member: &u32| {
1612 autocrypt_key_contacts
1613 .get(old_member)
1614 .or_else(|| {
1615 if is_chatmail {
1620 autocrypt_key_contacts_with_reset_peerstate.get(old_member)
1621 } else {
1622 None
1623 }
1624 })
1625 .copied()
1626 };
1627
1628 let mut update_member_stmt = transaction
1629 .prepare("UPDATE chats_contacts SET contact_id=? WHERE contact_id=? AND chat_id=?")?;
1630 let mut addr_cmp_stmt = transaction
1631 .prepare("SELECT c.addr=d.addr FROM contacts c, contacts d WHERE c.id=? AND d.id=?")?;
1632 for chat in all_chats {
1633 let (chat_id, typ, grpid, protected) = chat.context("Step 24")?;
1634 let old_members: Vec<(u32, bool)> = load_chat_contacts_stmt
1636 .query_map((chat_id,), |row| {
1637 let id: u32 = row.get(0)?;
1638 let present: bool = row.get(1)?;
1639 Ok((id, present))
1640 })
1641 .context("Step 25")?
1642 .collect::<Result<Vec<_>, _>>()
1643 .context("Step 26")?;
1644
1645 let mut keep_address_contacts = |reason: &str| {
1646 info!(
1647 context,
1648 "Chat {chat_id} will be an unencrypted chat with contacts identified by email address: {reason}."
1649 );
1650 for (m, _) in &old_members {
1651 orphaned_contacts.remove(m);
1652 }
1653 };
1654 let old_and_new_members: Vec<(u32, bool, Option<u32>)> = match typ {
1655 100 => {
1663 let Some((old_member, _)) = old_members.first() else {
1664 info!(
1665 context,
1666 "1:1 chat {chat_id} doesn't contain contact, probably it's self or device chat."
1667 );
1668 continue;
1669 };
1670
1671 let Some(new_contact) = map_to_key_contact(old_member) else {
1672 keep_address_contacts("No peerstate, or peerstate in 'reset' state");
1673 continue;
1674 };
1675 if !addr_cmp_stmt
1676 .query_row((old_member, new_contact), |row| row.get::<_, bool>(0))?
1677 {
1678 transaction
1682 .execute("UPDATE chats SET protected=0 WHERE id=?", (chat_id,))?;
1683
1684 keep_address_contacts("key contact has different email");
1685 continue;
1686 }
1687 vec![(*old_member, true, Some(new_contact))]
1688 }
1689
1690 120 => {
1692 if grpid.is_empty() {
1693 keep_address_contacts("Empty chat-Group-ID");
1696 continue;
1697 } else if protected == 1 {
1698 old_members
1699 .iter()
1700 .map(|&(id, present)| {
1701 (id, present, verified_key_contacts.get(&id).copied())
1702 })
1703 .collect()
1704 } else {
1705 old_members
1706 .iter()
1707 .map(|&(id, present)| (id, present, map_to_key_contact(&id)))
1708 .collect::<Vec<(u32, bool, Option<u32>)>>()
1709 }
1710 }
1711
1712 140 => {
1714 keep_address_contacts("Mailinglist");
1715 continue;
1716 }
1717
1718 160 => old_members
1720 .iter()
1721 .map(|(original, _)| {
1722 (
1723 *original,
1724 true,
1725 autocrypt_key_contacts
1726 .get(original)
1727 .or_else(|| {
1731 autocrypt_key_contacts_with_reset_peerstate.get(original)
1732 })
1733 .copied(),
1734 )
1735 })
1736 .collect::<Vec<(u32, bool, Option<u32>)>>(),
1737 _ => {
1738 warn!(context, "Invalid chat type {typ}");
1739 continue;
1740 }
1741 };
1742
1743 if typ == 120
1746 && old_and_new_members
1747 .iter()
1748 .any(|&(_old, present, new)| present && new.is_none())
1749 {
1750 transaction
1751 .execute("UPDATE chats SET grpid='' WHERE id=?", (chat_id,))
1752 .context("Step 26.1")?;
1753 keep_address_contacts("Group contains contact without peerstate");
1754 continue;
1755 }
1756
1757 let human_readable_transitions = old_and_new_members
1758 .iter()
1759 .map(|(old, _, new)| format!("{old}->{}", new.unwrap_or_default()))
1760 .collect::<Vec<String>>()
1761 .join(" ");
1762 info!(
1763 context,
1764 "Migrating chat {chat_id} to key-contacts: {human_readable_transitions}"
1765 );
1766
1767 for (old_member, _, new_member) in old_and_new_members {
1768 if let Some(new_member) = new_member {
1769 orphaned_contacts.remove(&new_member);
1770 let res = update_member_stmt.execute((new_member, old_member, chat_id));
1771 if res.is_err() {
1772 let member_to_delete: u32 = transaction
1779 .query_row(
1780 "SELECT contact_id
1781 FROM chats_contacts
1782 WHERE chat_id=? AND contact_id IN (?,?)
1783 ORDER BY add_timestamp>=remove_timestamp, add_timestamp LIMIT 1",
1784 (chat_id, new_member, old_member),
1785 |row| row.get(0),
1786 )
1787 .context("Step 27")?;
1788 info!(
1789 context,
1790 "Chat partner is in the chat {chat_id} multiple times. \
1791 Deleting {member_to_delete}, then trying to update \
1792 {old_member}->{new_member} again"
1793 );
1794 transaction
1795 .execute(
1796 "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
1797 (chat_id, member_to_delete),
1798 )
1799 .context("Step 28")?;
1800 update_member_stmt.execute((new_member, old_member, chat_id))?;
1803 }
1804 } else {
1805 info!(
1806 context,
1807 "Old member {old_member} in chat {chat_id} can't be upgraded to key-contact, removing them"
1808 );
1809 transaction
1810 .execute(
1811 "DELETE FROM chats_contacts WHERE contact_id=? AND chat_id=?",
1812 (old_member, chat_id),
1813 )
1814 .context("Step 29")?;
1815 }
1816 }
1817 }
1818 }
1819
1820 {
1822 info!(
1823 context,
1824 "Marking contacts which remained in no chat at all as hidden: {orphaned_contacts:?}"
1825 );
1826 let mut mark_as_hidden_stmt = transaction
1827 .prepare("UPDATE contacts SET origin=? WHERE id=?")
1828 .context("Step 30")?;
1829 for contact in orphaned_contacts {
1830 mark_as_hidden_stmt
1831 .execute((0x8, contact))
1832 .context("Step 31")?;
1833 }
1834 }
1835
1836 {
1839 let mut contacts_map = autocrypt_key_contacts_with_reset_peerstate;
1840 for (old, new) in autocrypt_key_contacts {
1841 contacts_map.insert(old, new);
1842 }
1843 transaction
1844 .execute(
1845 "CREATE TABLE key_contacts_map (
1846 old_id INTEGER PRIMARY KEY NOT NULL,
1847 new_id INTEGER NOT NULL
1848 ) STRICT",
1849 (),
1850 )
1851 .context("Step 32")?;
1852 {
1853 let mut stmt = transaction
1854 .prepare("INSERT INTO key_contacts_map (old_id, new_id) VALUES (?, ?)")
1855 .context("Step 33")?;
1856 for ids in contacts_map {
1857 stmt.execute(ids).context("Step 34")?;
1858 }
1859 }
1860 transaction
1861 .execute(
1862 "INSERT INTO config (keyname, value) VALUES (
1863 'first_key_contacts_msg_id',
1864 IFNULL((SELECT MAX(id)+1 FROM msgs), 0)
1865 )",
1866 (),
1867 )
1868 .context("Step 35")?;
1869 }
1870
1871 Ok(())
1872}
1873
1874pub(crate) async fn msgs_to_key_contacts(context: &Context) -> Result<()> {
1876 let sql = &context.sql;
1877 if sql
1878 .get_raw_config_int64("first_key_contacts_msg_id")
1879 .await?
1880 <= Some(0)
1881 {
1882 return Ok(());
1883 }
1884 let trans_fn = |t: &mut rusqlite::Transaction| {
1885 let mut first_key_contacts_msg_id: u64 = t
1886 .query_one(
1887 "SELECT CAST(value AS INTEGER) FROM config WHERE keyname='first_key_contacts_msg_id'",
1888 (),
1889 |row| row.get(0),
1890 )
1891 .context("Get first_key_contacts_msg_id")?;
1892 let mut stmt = t
1893 .prepare(
1894 "UPDATE msgs SET
1895 from_id=IFNULL(
1896 (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.from_id),
1897 from_id
1898 ),
1899 to_id=IFNULL(
1900 (SELECT new_id FROM key_contacts_map WHERE old_id=msgs.to_id),
1901 to_id
1902 )
1903 WHERE id>=? AND id<?
1904 AND chat_id>9
1905 AND (param GLOB '*\nc=1*' OR param GLOB 'c=1*')",
1906 )
1907 .context("Prepare stmt")?;
1908 let msgs_to_migrate = 1000;
1909 let mut msgs_migrated: u64 = 0;
1910 while first_key_contacts_msg_id > 0 && msgs_migrated < msgs_to_migrate {
1911 let start_msg_id = first_key_contacts_msg_id.saturating_sub(msgs_to_migrate);
1912 let cnt: u64 = stmt
1913 .execute((start_msg_id, first_key_contacts_msg_id))
1914 .context("UPDATE msgs")?
1915 .try_into()?;
1916 msgs_migrated += cnt;
1917 first_key_contacts_msg_id = start_msg_id;
1918 }
1919 t.execute(
1920 "UPDATE config SET value=? WHERE keyname='first_key_contacts_msg_id'",
1921 (first_key_contacts_msg_id,),
1922 )
1923 .context("Update first_key_contacts_msg_id")?;
1924 Ok((msgs_migrated, first_key_contacts_msg_id))
1925 };
1926 let start = Time::now();
1927 let mut msgs_migrated = 0;
1928 loop {
1929 let (n, first_key_contacts_msg_id) = sql.transaction(trans_fn).await?;
1930 msgs_migrated += n;
1931 if first_key_contacts_msg_id == 0 || time_elapsed(&start) >= Duration::from_millis(500) {
1932 break;
1933 }
1934 }
1935 sql.uncache_raw_config("first_key_contacts_msg_id").await;
1936 info!(
1937 context,
1938 "Rewriting {msgs_migrated} msgs to key-contacts took {:?}.",
1939 time_elapsed(&start),
1940 );
1941 Ok(())
1942}
1943
1944impl Sql {
1945 async fn set_db_version(&self, version: i32) -> Result<()> {
1946 self.set_raw_config_int(VERSION_CFG, version).await?;
1947 Ok(())
1948 }
1949
1950 fn set_db_version_trans(transaction: &mut rusqlite::Transaction, version: i32) -> Result<()> {
1952 transaction.execute(
1953 "UPDATE config SET value=? WHERE keyname=?;",
1954 (format!("{version}"), VERSION_CFG),
1955 )?;
1956 Ok(())
1957 }
1958
1959 async fn set_db_version_in_cache(&self, version: i32) -> Result<()> {
1960 let mut lock = self.config_cache.write().await;
1961 lock.insert(VERSION_CFG.to_string(), Some(format!("{version}")));
1962 Ok(())
1963 }
1964
1965 async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
1966 self.execute_migration_transaction(
1967 |transaction| {
1968 transaction.execute_batch(query)?;
1969 Ok(())
1970 },
1971 version,
1972 )
1973 .await
1974 }
1975
1976 async fn execute_migration_transaction(
1977 &self,
1978 migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
1979 version: i32,
1980 ) -> Result<()> {
1981 #[cfg(test)]
1982 if STOP_MIGRATIONS_AT.try_with(|stop_migrations_at| version > *stop_migrations_at)
1983 == Ok(true)
1984 {
1985 println!("Not running migration {version}, because STOP_MIGRATIONS_AT is set");
1986 return Ok(());
1987 }
1988
1989 self.transaction(move |transaction| {
1990 let curr_version: String = transaction.query_row(
1991 "SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
1992 ("0", VERSION_CFG),
1993 |row| row.get(0),
1994 )?;
1995 let curr_version: i32 = curr_version.parse()?;
1996 ensure!(curr_version < version, "Db version must be increased");
1997 Self::set_db_version_trans(transaction, version)?;
1998 migration(transaction)?;
1999
2000 Ok(())
2001 })
2002 .await
2003 .with_context(|| format!("execute_migration failed for version {version}"))?;
2004
2005 self.config_cache.write().await.clear();
2006
2007 Ok(())
2008 }
2009}
2010
2011#[cfg(test)]
2012mod migrations_tests;