1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145
|
/*
Copyright (C) 2019-2025 Selwin van Dijk
This file is part of signalbackup-tools.
signalbackup-tools is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
signalbackup-tools is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with signalbackup-tools. If not, see <https://www.gnu.org/licenses/>.
*/
#include "signalbackup.ih"
bool SignalBackup::importThread(SignalBackup *source, long long int thread)
{
Logger::message(__FUNCTION__, " (", thread, ")");
// known incompatibilities. There are almost certainly also unknown ones!
if ((d_databaseversion >= 215 && source->d_databaseversion < 215) || // part.unique_id dropped from db
(d_databaseversion < 215 && source->d_databaseversion >= 215) ||
(d_databaseversion >= 185 && source->d_databaseversion < 185) || // from/to_recipient_id
(d_databaseversion < 185 && source->d_databaseversion >= 185) || //
(d_databaseversion >= 172 && source->d_databaseversion < 172) || // group.members dropped
(d_databaseversion < 172 && source->d_databaseversion >= 172) || //
(d_databaseversion >= 168 && source->d_databaseversion < 168) || // sms table dropped
(d_databaseversion < 168 && source->d_databaseversion >= 168) || // sms table dropped
(d_databaseversion >= 33 && source->d_databaseversion < 33) ||
(d_databaseversion < 33 && source->d_databaseversion >= 33) ||
(d_databaseversion >= 27 && source->d_databaseversion < 27) ||
(d_databaseversion < 27 && source->d_databaseversion >= 27))
{
Logger::error("Source and target database at incompatible versions");
return false;
}
if (source->d_database.containsTable("remapped_recipients"))
{
SqliteDB::QueryResults r;
source->d_database.exec("SELECT * FROM remapped_recipients", &r);
if (r.rows())
{
Logger::warnOnce("Source database contains 'remapped_recipients'. This case may not yet be handled correctly by this program!");
if (d_verbose) [[unlikely]]
{
for (unsigned int i = 0; i < r.rows(); ++i)
{
long long int id = r.getValueAs<long long int>(i, "_id");
long long int oldid = r.getValueAs<long long int>(i, "old_id");
long long int newid = r.getValueAs<long long int>(i, "new_id");
Logger::message(id, " : ", oldid, " -> ", newid, "\n");
Logger::message("Old id:");
source->d_database.print("SELECT * FROM recipient WHERE _id = ?", oldid);
Logger::message_end();
Logger::message("New id:");
source->d_database.print("SELECT * FROM recipient WHERE _id = ?", newid);
Logger::message_end();
}
}
// apply the remapping (probably only some reactions _may_ need to be transferred?)
source->remapRecipients();
// now, the remapping was 'applied', old_id should not occur in database anymore, and remapped_recipients can be cleared?
source->d_database.exec("DELETE FROM remapped_recipients");
}
}
/*
// if target contains releasechannel recipient, make sure to remove it from source
int target_releasechannel = -1;
int source_releasechannel = -1;
for (auto const &kv : d_keyvalueframes)
if (kv->key() == "releasechannel.recipient_id" && !kv->value().empty())
{
target_releasechannel = bepaald::toNumber<int>(kv->value());
break;
}
if (target_releasechannel >= 0)
for (auto const &skv : source->d_keyvalueframes)
if (skv->key() == "releasechannel.recipient_id")
{
source_releasechannel = bepaald::toNumber<int>(skv->value());
source->d_database.exec("DELETE FROM recipient WHERE _id = ?", source_releasechannel);
std::cout << "Deleted double releasechannel recipient from source database (_id: " << source_releasechannel << ")" << std::endl;
break;
}
*/
// do not import release_channel from source. Target will either have its own, or be too old for one
int source_releasechannel = -1;
for (auto const &skv : source->d_keyvalueframes)
if (skv->key() == "releasechannel.recipient_id")
{
source_releasechannel = bepaald::toNumber<int>(skv->value());
source->d_database.exec("DELETE FROM recipient WHERE _id = ?", source_releasechannel);
Logger::message("Deleted releasechannel recipient from source database (_id: ", source_releasechannel, ")");
break;
}
long long int targetthread = -1;
SqliteDB::QueryResults results;
if (d_databaseversion < 24) // old database version
{
// get targetthread from source thread id (source.thread_id->source.recipient_id->target.thread_id
source->d_database.exec("SELECT " + source->d_thread_recipient_id + " FROM thread WHERE _id = ?", thread, &results);
if (results.rows() != 1 || results.columns() != 1 ||
!results.valueHasType<std::string>(0, 0))
{
Logger::error("Failed to get recipient id from source database");
return false;
}
std::string recipient_id = results.getValueAs<std::string>(0, 0);
targetthread = getThreadIdFromRecipient(recipient_id); // -1 if none found
}
else // new database version
{
// get targetthread from source thread id (source.thread_id->source.recipient_id->source.recipient.phone/group_id->target.thread_id
if (source->d_database.tableContainsColumn("recipient", source->d_recipient_aci, source->d_recipient_e164,
"group_id", "distribution_list_id", source->d_recipient_storage_service))
source->d_database.exec("SELECT "
"IFNULL(" + source->d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + source->d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"IFNULL(distribution_list.distribution_id, '') AS distribution_id, "
"IFNULL(" + source->d_recipient_storage_service + ", '') AS storage_service "
"FROM recipient "
"LEFT JOIN distribution_list ON distribution_list._id = recipient.distribution_list_id "
"WHERE recipient._id IS (SELECT " + source->d_thread_recipient_id + " FROM thread WHERE thread._id = ?)",
thread, &results);
else
source->d_database.exec("SELECT "
"IFNULL(" + source->d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + source->d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"'' AS distribution_id, "
"'' AS storage_service "
"FROM recipient "
"WHERE _id IS (SELECT " + source->d_thread_recipient_id + " FROM thread WHERE _id = ?)",
thread, &results);
if (results.rows() != 1)
{
// skip current thread if it is the releasechannel-thread
// maybe I should deal with this in the future
SqliteDB::QueryResults res2;
source->d_database.exec("SELECT " + source->d_thread_recipient_id + " FROM thread WHERE _id = ?", thread, &res2);
if (res2.rows() &&
((res2.valueHasType<long long int>(0, 0) && res2.getValueAs<long long int>(0, 0) == source_releasechannel) ||
(res2.valueHasType<std::string>(0, 0) && bepaald::toNumber<int>(res2.getValueAs<std::string>(0, 0)) == source_releasechannel)))
{
Logger::message("Skipping releasechannel...");
return true; // when this channel is actually active, maybe remove this return statement and
// manually set targetthread with the help of target_releasechannel (if != -1)
}
Logger::error("Failed to get uuid/phone/group_id from source database");
return false;
}
//std::string phone_or_group = results.getValueAs<std::string>(0, 0);
RecipientIdentification rec_id = {results(0, "uuid"), results(0, "phone"), results(0, "group_id"), results(0, "distribution_id"), results(0, "storage_service")};
if (d_verbose) [[unlikely]]
Logger::message("Trying to match source recipient: {\"", rec_id.uuid, "\", \"", rec_id.phone, "\", \"", rec_id.group_id, "\"}");
if (d_database.tableContainsColumn("recipient", "distribution_list_id"))
{
long long int distribution_list_id = d_database.getSingleResultAs<long long int>("SELECT _id FROM distribution_list WHERE distribution_id = ?",
rec_id.distribution_id, -1);
d_database.exec("SELECT _id FROM recipient WHERE "
// match by aci
"(" + d_recipient_aci + " IS NOT NULL AND " + d_recipient_aci + " IS ?) OR "
// only match by phone if match by aci fails:
"CASE WHEN (SELECT COUNT(_id) FROM recipient WHERE (" + d_recipient_aci + " IS NOT NULL AND " + d_recipient_aci + " IS ?)) = 0 THEN "
"(" + d_recipient_e164 + " IS NOT NULL AND " + d_recipient_e164 + " IS ?) END OR "
// match by group_id
"(group_id IS NOT NULL AND group_id IS ?) OR "
"(distribution_list_id IS NOT NULL AND distribution_list_id IS ?)",
{rec_id.uuid, rec_id.uuid, rec_id.phone, rec_id.group_id, distribution_list_id}, &results);
}
else
d_database.exec("SELECT _id FROM recipient WHERE "
// match by aci
"(" + d_recipient_aci + " IS NOT NULL AND " + d_recipient_aci + " IS ?) OR "
// only match by phone if match by aci fails:
"CASE WHEN (SELECT COUNT(_id) FROM recipient WHERE (" + d_recipient_aci + " IS NOT NULL AND " + d_recipient_aci + " IS ?)) = 0 THEN "
"(" + d_recipient_e164 + " IS NOT NULL AND " + d_recipient_e164 + " IS ?) END OR "
// match by group_id
"(group_id IS NOT NULL AND group_id IS ?)", {rec_id.uuid, rec_id.uuid, rec_id.phone, rec_id.group_id}, &results);
if (results.rows() != 1 || results.columns() != 1 ||
!results.valueHasType<long long int>(0, 0))
{
Logger::message("Failed to find recipient._id matching uuid/phone/group_id in target database");
// d_database.prettyPrint("SELECT _id, " + d_recipient_aci + "," + d_recipient_e164 + ",group_id FROM recipient "
// "WHERE " + d_recipient_aci + " = ? OR " +
// d_recipient_e164 + " = ? OR group_id = ?", {rec_id.uuid, rec_id.phone, rec_id.group_id});
}
else
{
long long int recipient_id = results.getValueAs<long long int>(0, 0);
targetthread = getThreadIdFromRecipient(bepaald::toString(recipient_id));
if (d_verbose) [[unlikely]]
Logger::message("Matched source recipient with target ", recipient_id, ", targetthread: ", targetthread);
}
}
// std::cout << "RECIPIENTS BEFORE CROP:" << std::endl;
// source->d_database.prettyPrint("SELECT _id, COALESCE(signal_profile_name, group_id) FROM recipient");
// delete doubles
/* work in progress */
/* I dont think the recipentId == recipientId part is right */
if (false /*skipexisting*/ && targetthread != -1)
{
SqliteDB::QueryResults existing;
d_database.exec("SELECT body, thread_id, " + d_mms_date_sent + ", " + d_mms_recipient_id + " FROM " + d_mms_table +
" WHERE thread_id = ?", targetthread, &existing);
int count = 0;
for (unsigned int i = 0; i < existing.rows(); ++i)
{
source->d_database.exec("DELETE FROM " + d_mms_table +
" WHERE body = ? AND thread_id = ? AND " + d_mms_date_sent + " = ? AND " + d_mms_recipient_id + " = ?",
{existing.value(i, "body"), thread, existing.value(i, d_mms_date_sent), existing.value(i, d_mms_recipient_id)});
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing messages in source thread");
// check if any messages are left:
if (source->d_database.getSingleResultAs<long long int>("SELECT COUNT(*) FROM " + d_mms_table + " WHERE thread_id = ?", thread, -1) == 0)
{
Logger::message("After removing existing messages, thread is empty -> skipping...");
return true;
}
}
// crop the source db to the specified thread
source->cropToThread(thread);
// std::cout << "RECIPIENTS AFTER CROP:" << std::endl;
// source->d_database.prettyPrint("SELECT _id, COALESCE(signal_profile_name, group_id) FROM recipient");
// remove any storage_key entries that are already in target...
if (d_database.containsTable("storage_key") && source->d_database.containsTable("storage_key"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT key FROM storage_key", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM storage_key WHERE key = ?", res.value(i, 0));
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing storage_keys");
}
// delete double megaphones
if (d_database.containsTable("megaphone") && source->d_database.containsTable("megaphone"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT event FROM megaphone", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM megaphone WHERE event = ?", res.value(i, 0));
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing megaphones");
}
// delete double remote_megaphones
if (d_database.containsTable("remote_megaphone") && source->d_database.containsTable("remote_megaphone"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT uuid FROM remote_megaphone", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM remote_megaphone WHERE uuid = ?", res.value(i, 0));
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing remote_megaphone's");
}
// delete double cds (contact discovery service entries)
if (d_database.containsTable("cds") && source->d_database.containsTable("cds"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT e164 FROM cds", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM cds WHERE e164 = ?", res.value(i, 0));
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing cds's");
}
// UNTESTED
// remove double in app payment subscribers
// this table has unique string: subscriber_id_ID TEXT NOT NULL UNIQUE
// and combo: UNIQUE(currency_code, type)
if (d_database.containsTable("in_app_payment_subscriber"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT subscriber_id, currency_code, type FROM in_app_payment_subscriber", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM in_app_payment_subscriber WHERE subscriber_id = ?", res.value(i, "subscriber_id"));
count += source->d_database.changed();
source->d_database.exec("DELETE FROM in_app_payment_subscriber WHERE currency_code = ? AND type = ?", {res.value(i, "currency_code"), res.value(i, "type")});
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing cds's");
}
// remove any kyber_keys that are already in target...
if (d_database.containsTable("kyber_prekey") && source->d_database.containsTable("kyber_prekey"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT key_id FROM kyber_prekey", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM kyber_prekey WHERE key_id = ?", res.value(i, 0));
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing kyber_prekeys");
}
// NOTE THIS IS SUPERFLUOUS NOW? (SINCE key_id by itself is unique and removed above?)
// delete double kyber prekey entries
if (d_database.containsTable("kyber_prekey") && source->d_database.containsTable("kyber_prekey"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT account_id, key_id FROM kyber_prekey", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM kyber_prekey WHERE account_id = ? AND key_id = ?", {res.value(i, "account_id"), res.value(i, "key_id")});
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing kyber_prekey's");
}
// delete double key_value entries (this table does not exist anymore currently)
if (d_database.containsTable("key_value") && source->d_database.containsTable("key_value"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT key FROM key_value", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM key_value WHERE key = ?", res.getValueAs<std::string>(i, 0));
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing key values");
}
// delete double backup_media_snapshot (v257)
// UNTESTED not sure what this does, but if it is going
// to be used for incremental backups (backup v2), and
// the media_id is used to determine if an attachment
// was already backed up, this needs to be correct to
// prevent data loss..
if (d_database.containsTable("backup_media_snapshot") && source->d_database.containsTable("backup_media_snapshot"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT media_id FROM backup_media_snapshot", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM backup_media_snapshot WHERE media_id = ?", res.value(i, "media_id"));
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing backup_media_snapshot entries");
}
// the target will have its own job_spec etc...
if (source->d_database.containsTable("job_spec"))
source->d_database.exec("DELETE FROM job_spec");
if (source->d_database.containsTable("push")) // dropped around dbv205
source->d_database.exec("DELETE FROM push");
if (source->d_database.containsTable("constraint_spec"))
source->d_database.exec("DELETE FROM constraint_spec"); // has to do with job_spec, references it...
if (source->d_database.containsTable("dependency_spec"))
source->d_database.exec("DELETE FROM dependency_spec"); // has to do with job_spec, references it...
// we will delete any notification_profile data, these are specific not to any threads,
// but to the phone owner. The notification profiles will probably already exist on the
// target, or can be easily recreated. They are difficult to import (they contain multiple
// UNIQUE fields and should only be imported once, while this function will otherwise
// do it for each thread imported....
if (source->d_database.containsTable("notification_profile_allowed_members"))
source->d_database.exec("DELETE FROM notification_profile_allowed_members");
if (source->d_database.containsTable("notification_profile_schedule"))
source->d_database.exec("DELETE FROM notification_profile_schedule");
if (source->d_database.containsTable("notification_profile"))
source->d_database.exec("DELETE FROM notification_profile");
// NOT NECESSARY (these tables are skipped when merging anyway) AND CAUSES BREAKAGE
// all emoji_search_* tables are ignored on import, delete from source here to prevent failing unique constraints
/*
if (source->d_database.containsTable("emoji_search_data"))
source->d_database.exec("DELETE FROM emoji_search_data");
if (source->d_database.containsTable("emoji_search_idx"))
source->d_database.exec("DELETE FROM emoji_search_idx");
if (source->d_database.containsTable("emoji_search_content"))
source->d_database.exec("DELETE FROM emoji_search_content");
if (source->d_database.containsTable("emoji_search_docsize"))
source->d_database.exec("DELETE FROM emoji_search_docsize");
if (source->d_database.containsTable("emoji_search_config"))
source->d_database.exec("DELETE FROM emoji_search_config");
*/
if (d_database.containsTable("group_call_ring") && source->d_database.containsTable("group_call_ring"))
{
// not sure what this table is for, but it has a UNIQUE ring_id field,
// so let's just delete any double ring_id's
SqliteDB::QueryResults res;
d_database.exec("SELECT ring_id FROM group_call_ring", &res);
for (unsigned int i = 0; i < res.rows(); ++i)
source->d_database.exec("DELETE FROM group_call_ring WHERE ring_id = ?", res.getValueAs<long long int>(i, 0));
}
// untested
/*
sqlite> SELECT * FROM sqlite_master WHERE name IS "payments";
type|name|tbl_name|rootpage|sql
table|payments|payments|60|CREATE TABLE payments(_id INTEGER PRIMARY KEY, uuid TEXT DEFAULT NULL, recipient INTEGER DEFAULT 0, recipient_address TEXT DEFAULT NULL, timestamp INTEGER, note TEXT DEFAULT NULL, direction INTEGER, state INTEGER, failure_reason INTEGER, amount BLOB NOT NULL, fee BLOB NOT NULL, transaction_record BLOB DEFAULT NULL, receipt BLOB DEFAULT NULL, payment_metadata BLOB DEFAULT NULL, receipt_public_key TEXT DEFAULT NULL, block_index INTEGER DEFAULT 0, block_timestamp INTEGER DEFAULT 0, seen INTEGER, UNIQUE(uuid) ON CONFLICT ABORT)
*/
// NOTE: 'recipient' here is probably recipient._id which should be updated later in updateRecipientId()
// maybe delete completely if recipient is not in this bit of database?
/*
// delete double payments
if (d_database.containsTable("payments") && source->d_database.containsTable("payments"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT uuid FROM payments", &res);
std::cout << " Deleting " << res.rows() << " existing payments" << std::endl;
for (unsigned int i = 0; i < res.rows(); ++i)
source->d_database.exec("DELETE FROM payments WHERE uuid = ?", res.getValueAs<std::string>(i, 0));
}
*/
/*
sqlite> SELECT * FROM sqlite_master WHERE name IS "chat_colors";
table|chat_colors|chat_colors|65|CREATE TABLE chat_colors (_id INTEGER PRIMARY KEY AUTOINCREMENT,chat_colors BLOB)
*/
// untested: I guess chat_colors are in target, or they can be easily recreated
if (source->d_database.containsTable("chat_colors"))
source->d_database.exec("DELETE FROM chat_colors");
// TODO deal with 'sender_keys'
/*
sqlite> SELECT * FROM sqlite_master WHERE name IS "sender_keys";
table|sender_keys|sender_keys|71|CREATE TABLE sender_keys (_id INTEGER PRIMARY KEY AUTOINCREMENT, recipient_id INTEGER NOT NULL, device INTEGER NOT NULL, distribution_id TEXT NOT NULL, record BLOB NOT NULL, created_at INTEGER NOT NULL, UNIQUE(recipient_id, device, distribution_id) ON CONFLICT REPLACE)
*/
// notes:
// * there is recipient_id, which probably needs to be adjusted in updateRecipientId(), but it must be unique so it must then be deleted if the adjustment can be made
// * there is a created_at, probably a timestamp -> delete the older one?, also use this in croptodate?
//source->d_database.exec("VACUUM");
// id's need to be unique
makeIdsUnique(source);
// delete double remapped_recipients
if (d_database.containsTable("remapped_recipients") && source->d_database.containsTable("remapped_recipients"))
{
SqliteDB::QueryResults res;
source->d_database.exec("SELECT * FROM remapped_recipients", &res); // get all remapped recipients in source
for (unsigned int i = 0; i < res.rows(); ++i)
{
long long int id = res.getValueAs<long long int>(i, "_id");
long long int oldid = res.getValueAs<long long int>(i, "old_id");
long long int newid = res.getValueAs<long long int>(i, "new_id");
SqliteDB::QueryResults r2;
d_database.exec("SELECT * FROM remapped_recipients WHERE old_id = ? AND new_id = ?", {oldid, newid}, &r2);
if (r2.rows()) // this mapping is in target already
{
Logger::message("Skipping import of remapped_recipient (", oldid, " -> ", newid, "), mapping already in target database");
source->d_database.exec("DELETE FROM remapped_recipients WHERE _id = ?", id);
}
}
}
// merge into existing thread, set the id on the sms, mms, and drafts
// drop the recipient_preferences, identities and thread tables, they are already in the target db
if (targetthread > -1)
{
Logger::message(" Found existing thread for this recipient in target database, merging into thread ", targetthread);
// set thread_ids to found targetthread
for (auto const &dbl : s_databaselinks)
if (dbl.table == "thread")
{
for (auto const &c : dbl.connections)
{
if (source->d_database.containsTable(c.table))
source->d_database.exec("UPDATE " + c.table + " SET " + c.column + " = ?", targetthread);
}
break;
}
// see below for comment explaining this function
if (d_databaseversion >= 24)
{
//d_database.exec("SELECT _id, COALESCE(uuid,phone,group_id) AS identifier FROM recipient", &results);
if (d_database.tableContainsColumn("recipient", "distribution_list_id", d_recipient_storage_service))
d_database.exec("SELECT recipient._id, "
"IFNULL(" + d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"IFNULL(distribution_list.distribution_id, '') AS distribution_id, "
"IFNULL(" + d_recipient_storage_service + ", '') AS storage_service "
"FROM recipient "
"LEFT JOIN distribution_list ON distribution_list._id = recipient.distribution_list_id ", &results);
else
d_database.exec("SELECT _id, "
"IFNULL(" + d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"'' AS 'distribution_id', "
"'' AS 'storage_service' "
"FROM recipient", &results);
Logger::message(" updateRecipientIds");
//results.prettyPrint(d_truncate);
for (unsigned int i = 0; i < results.rows(); ++i)
{
RecipientIdentification rec_id = {results(i, "uuid"), results(i, "phone"), results(i, "group_id"), results(i, "distribution_id"), results(i, "storage_service")};
//source->updateRecipientId(results.getValueAs<long long int>(i, "_id"), results.getValueAs<std::string>(i, "identifier"));
source->updateRecipientId(results.getValueAs<long long int>(i, "_id"), rec_id);
}
}
source->d_database.exec("DROP TABLE thread");
source->d_database.exec("DROP TABLE identities");
// even though the thread already exists, not all recipients are guaranteed to
// exist in target. For example: current thread is group conversation, in the source
// a member was added, but this member did not yet exist in the target.
//
// the same probably goes for ancient (<24) databases, but I'll write that if someone ever
// tries to merge those.
//
// delete existsing recipients (recipient table has unique constraint on phone, uuid and group_id)
if (d_databaseversion < 24)
source->d_database.exec("DROP TABLE recipient_preferences");
else
{
// get the unique features of existing recipients
SqliteDB::QueryResults existing_rec;
if (d_database.tableContainsColumn("recipient", "distribution_list_id") &&
d_database.tableContainsColumn("recipient", d_recipient_storage_service))
d_database.exec("SELECT recipient._id, "
"IFNULL(" + d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"IFNULL(distribution_list.distribution_id, '') AS distribution_id, "
"IFNULL(" + source->d_recipient_storage_service + ", '') AS storage_service "
"FROM recipient "
"LEFT JOIN distribution_list ON distribution_list._id = recipient.distribution_list_id ", &existing_rec);
else
d_database.exec("SELECT _id, "
"IFNULL(" + d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"'' AS distribution_id, "
"'' AS storage_service "
"FROM recipient", &existing_rec);
// for each of them, check if they are also in source, and delete
int count = 0;
for (unsigned int i = 0; i < results.rows(); ++i)
{
RecipientIdentification rec_id = {existing_rec(i, "uuid"), existing_rec(i, "phone"), existing_rec(i, "group_id"),
existing_rec(i, "distribution_id"), existing_rec(i, "storage_service")};
if (source->d_database.tableContainsColumn("recipient", "distribution_list_id"))
{
long long int distribution_list_id = source->d_database.getSingleResultAs<long long int>("SELECT _id FROM distribution_list WHERE distribution_id = ?",
rec_id.distribution_id, -1);
source->d_database.exec("DELETE FROM recipient WHERE "
// one-of uuid/phone/group_id is set and equal to existing recipient,
// or distribution_list_id points to dist_list with same _id
"((" + source->d_recipient_aci + " IS NOT NULL AND " + source->d_recipient_aci + " IS ?) OR "
"(" + source->d_recipient_e164 + " IS NOT NULL AND " + source->d_recipient_e164 + " IS ?) OR "
"(group_id IS NOT NULL AND group_id IS ?) OR "
"(distribution_list_id IS NOT NULL AND distribution_list_id IS ?))",
{rec_id.uuid, rec_id.phone, rec_id.group_id, distribution_list_id});
count += source->d_database.changed();
}
else
{
source->d_database.exec("DELETE FROM recipient WHERE "
// one-of uuid/phone/group_id is set and equal to existing recipient
"((" + source->d_recipient_aci + " IS NOT NULL AND " + source->d_recipient_aci + " IS ?) OR "
"(" + source->d_recipient_e164 + " IS NOT NULL AND " + source->d_recipient_e164 + " IS ?) OR "
"(group_id IS NOT NULL AND group_id IS ?))", {rec_id.uuid, rec_id.phone, rec_id.group_id});
count += source->d_database.changed();
}
}
if (count)
Logger::message("Dropped ", count, " existing recipients from source database");
}
source->d_database.exec("DROP TABLE groups");
source->d_avatars.clear();
}
else // no matching thread in target (but recipient may still exist)
{
Logger::message(" No existing thread found in target database for this recipient, importing.");
// unpin thread from source database, to prevent uniqueness issues with target
if (source->d_database.tableContainsColumn("thread", source->d_thread_pinned) &&
d_database.tableContainsColumn("thread", d_thread_pinned))
{
// before dbv266, 'unpinned' meant the column was set to '0', after dbv266 it was 'NULL'. Make sure to use target's default here!
std::string target_pinned_default = d_database.getSingleResultAs<std::string>("SELECT dflt_value FROM pragma_table_info('thread') WHERE name = '" + d_thread_pinned + "'", std::string());
if (!source->d_database.exec("UPDATE thread SET " + source->d_thread_pinned + " = " + target_pinned_default))
Logger::warning("Failed to unpin threads in source database.");
}
// check identities and recipient prefs for presence of values, they may be there (even
// though no thread was found (for example via a group chat or deleted thread))
// get identities from target, drop all rows from source that are already present
if (d_databaseversion < 24)
{
d_database.exec("SELECT address FROM identities", &results); // address == phonenumber/__text_secure_group
for (unsigned int i = 0; i < results.rows(); ++i)
if (results.header(0) == "address" && results.valueHasType<std::string>(i, 0))
source->d_database.exec("DELETE FROM identities WHERE address = '" + results.getValueAs<std::string>(i, 0) + "'");
}
else
{
// get all phonenums/groups_ids for all in identities
if (d_database.tableContainsColumn("recipient", "distribution_list_id", source->d_recipient_storage_service))
d_database.exec("SELECT "
"IFNULL(" + d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"IFNULL(distribution_list.distribution_id, '') AS distribution_id, "
"IFNULL(" + source->d_recipient_storage_service + ", '') AS storage_service "
"FROM recipient "
"LEFT JOIN distribution_list ON distribution_list._id = recipient.distribution_list_id "
"WHERE recipient._id IN (SELECT address FROM identities)", &results);
else
d_database.exec("SELECT "
"IFNULL(" + d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"'' AS distribution_id, "
"'' AS storage_service "
"FROM recipient "
"WHERE _id IN (SELECT address FROM identities)", &results);
for (unsigned int i = 0; i < results.rows(); ++i)
{
RecipientIdentification rec_id = {results(i, "uuid"), results(i, "phone"), results(i, "group_id"), results(i, "distribution_id"), results(i, "storage_service")};
// source->d_database.exec("DELETE FROM identities WHERE address IN (SELECT _id FROM recipient WHERE COALESCE(uuid,phone,group_id) = '" + results.getValueAs<std::string>(i, 0) + "')");
if (source->d_database.tableContainsColumn("recipient", "distribution_list_id"))
{
long long int distribution_list_id = source->d_database.getSingleResultAs<long long int>("SELECT _id FROM distribution_list WHERE distribution_id = ?",
rec_id.distribution_id, -1);
source->d_database.exec("DELETE FROM identities WHERE address IN "
"(SELECT _id FROM recipient WHERE "
"(" + source->d_recipient_aci + " IS NOT NULL AND " + source->d_recipient_aci + " IS ?) OR "
"(" + source->d_recipient_e164 + " IS NOT NULL AND " + source->d_recipient_e164 + " IS ?) OR "
"(group_id IS NOT NULL AND group_id IS ?) OR "
"(distribution_list_id IS NOT NULL AND distribution_list_id IS ?))",
{rec_id.uuid, rec_id.phone, rec_id.group_id, distribution_list_id});
}
else
source->d_database.exec("DELETE FROM identities WHERE address IN "
"(SELECT _id FROM recipient WHERE "
"(" + source->d_recipient_aci + " IS NOT NULL AND " + source->d_recipient_aci + " IS ?) OR "
"(" + source->d_recipient_e164 + " IS NOT NULL AND " + source->d_recipient_e164 + " IS ?) OR "
"(group_id IS NOT NULL AND group_id IS ?))",
{rec_id.uuid, rec_id.phone, rec_id.group_id});
}
}
// get recipient(_preferences) from target, drop all rows from source that are already present
if (d_databaseversion < 24)
{
d_database.exec("SELECT recipient_ids FROM recipient_preferences", &results);
for (unsigned int i = 0; i < results.rows(); ++i)
if (results.header(0) == "recipient_ids" && results.valueHasType<std::string>(i, 0))
source->d_database.exec("DELETE FROM recipient_preferences WHERE recipient_ids = '" + results.getValueAs<std::string>(i, 0) + "'");
}
else
{
//d_database.exec("SELECT _id,COALESCE(uuid,phone,group_id) AS ident FROM recipient", &results);
if (d_database.tableContainsColumn("recipient", "distribution_list_id", d_recipient_storage_service))
d_database.exec("SELECT recipient._id, "
"IFNULL(" + d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"IFNULL(distribution_list.distribution_id, '') AS distribution_id, "
"IFNULL(" + d_recipient_storage_service + ", '') AS storage_service "
"FROM recipient "
"LEFT JOIN distribution_list ON distribution_list._id = recipient.distribution_list_id", &results);
else
d_database.exec("SELECT _id, "
"IFNULL(" + d_recipient_aci + ", '') AS uuid, "
"IFNULL(" + d_recipient_e164 + ", '') AS phone, "
"IFNULL(group_id, '') AS group_id, "
"'' AS distribution_id, "
"'' AS storage_service "
"FROM recipient", &results);
Logger::message(" updateRecipientIds (2)");
//results.prettyPrint(d_truncate);
int count = 0;
for (unsigned int i = 0; i < results.rows(); ++i)
{
// if the recipient is already in target, we are going to delete it from
// source, to prevent doubles. However, many tables refer to the recipient._id
// which was made unique above. If we just delete the doubles (by phone/group_id,
// and in the future probably uuid), the fields in other tables will point
// to random or non-existing recipients, so we need to remap them:
RecipientIdentification rec_id = {results(i, "uuid"), results(i, "phone"), results(i, "group_id"),
results(i, "distribution_id"), results(i, "storage_service")};
source->updateRecipientId(results.getValueAs<long long int>(i, "_id"), rec_id);
//source->updateRecipientId(results.getValueAs<long long int>(i, "_id"), results.getValueAs<std::string>(i, "ident"));
// std::cout << "Testing if recipient is present:" << std::endl;
// std::cout << "\"" << rec_id.uuid << "\" \"" << rec_id.phone << "\" \"" << rec_id.group_id << "\" \"" << rec_id.group_type << "\" \"" << rec_id.storage_service_key << "\"" << std::endl;
// now drop the already present recipient from source.
// source->d_database.exec("DELETE FROM recipient WHERE COALESCE(uuid,phone,group_id) = '" + results.getValueAs<std::string>(i, "ident") + "'");
if (d_database.tableContainsColumn("recipient", "distribution_list_id"))
{
long long int distribution_list_id = source->d_database.getSingleResultAs<long long int>("SELECT _id FROM distribution_list WHERE distribution_id = ?",
rec_id.distribution_id, -1);
source->d_database.exec("DELETE FROM recipient WHERE "
// one-of uuid/phone/group_id is set and equal to existing recipient
"((" + source->d_recipient_aci + " IS NOT NULL AND " + source->d_recipient_aci + " IS ?) OR "
"(" + source->d_recipient_e164 + " IS NOT NULL AND " + source->d_recipient_e164 + " IS ?) OR "
"(group_id IS NOT NULL AND group_id IS ?) OR "
"(distribution_list_id IS NOT NULL AND distribution_list_id IS ?))",
{rec_id.uuid, rec_id.phone, rec_id.group_id, distribution_list_id});
count += source->d_database.changed();
}
else
{
source->d_database.exec("DELETE FROM recipient WHERE "
// one-of uuid/phone/group_id is set and equal to existing recipient
"((" + source->d_recipient_aci + " IS NOT NULL AND " + source->d_recipient_aci + " IS ?) OR "
"(" + source->d_recipient_e164 + " IS NOT NULL AND " + source->d_recipient_e164 + " IS ?) OR "
"(group_id IS NOT NULL AND group_id IS ?))", {rec_id.uuid, rec_id.phone, rec_id.group_id});
count += source->d_database.changed();
}
}
if (count)
Logger::message("Dropped ", count, " existing recipients from source database");
}
// even though the source was cropped to single thread, and this thread was not in target, avatar might still already be in target
// because contact (and avatar) might be present in group in source, and only as one-on-one in target
bool erased = true;
while (erased)
{
erased = false;
for (std::vector<std::pair<std::string, DeepCopyingUniquePtr<AvatarFrame>>>::iterator sourceav = source->d_avatars.begin(); sourceav != source->d_avatars.end(); ++sourceav)
{
for (std::vector<std::pair<std::string, DeepCopyingUniquePtr<AvatarFrame>>>::iterator targetav = d_avatars.begin(); targetav != d_avatars.end(); ++targetav)
if (sourceav->first == targetav->first)
{
source->d_avatars.erase(sourceav);
erased = true;
break;
}
if (erased)
break;
}
}
// Just because the group has no thread, doesn't mean it doesn't exist already
int count = 0;
SqliteDB::QueryResults existing_groups;
d_database.exec("SELECT group_id, recipient_id FROM groups", &existing_groups);
for (unsigned int i = 0; i < existing_groups.rows(); ++i)
{
SqliteDB::QueryResults removed_group_recipient_id;
source->d_database.exec("DELETE FROM groups WHERE group_id = ? RETURNING recipient_id", existing_groups.value(i, "group_id"), &removed_group_recipient_id);
int changed = source->d_database.changed();
count += changed;
if (changed && existing_groups.valueAsInt(i, "recipient_id", -1) != removed_group_recipient_id.valueAsInt(0, "recipient_id", -2))
Logger::warning("Existing group removed from source table, but recipient_ids did not match "
"(", existing_groups.valueAsInt(i, "recipient_id", -1), ", ", removed_group_recipient_id.valueAsInt(0, "recipient_id", -2), ")");
}
if (count)
Logger::message("Removed ", count, " existing groups from source database");
}
// delete group_membership's already present
if (d_database.containsTable("group_membership"))
{
SqliteDB::QueryResults gm_results;
d_database.exec("SELECT DISTINCT group_id, recipient_id FROM group_membership", &gm_results);
for (unsigned int i = 0; i < gm_results.rows(); ++i)
source->d_database.exec("DELETE FROM group_membership WHERE group_id = ? AND recipient_id = ?",
{gm_results.value(i, "group_id"), gm_results.value(i, "recipient_id")});
}
// delete double call.call_id's (call_id is timestamp, this shouldn't naturally
// happen, but does when merging threads that overlap in time)
if (d_database.containsTable("call"))
{
SqliteDB::QueryResults call_results;
d_database.exec("SELECT DISTINCT call_id FROM call", &call_results);
for (unsigned int i = 0; i < call_results.rows(); ++i)
source->d_database.exec("DELETE FROM call WHERE call_id = ?",
call_results.value(i, "call_id"));
}
// delete double stickers
if (d_database.containsTable("sticker") && source->d_database.containsTable("sticker"))
{
SqliteDB::QueryResults installed_stickers;
d_database.exec("SELECT pack_id, sticker_id, cover FROM sticker", &installed_stickers);
int count = 0;
for (unsigned int i = 0; i < installed_stickers.rows(); ++i)
{
SqliteDB::QueryResults deleted_sticker_ids;
source->d_database.exec("DELETE FROM sticker WHERE pack_id = ? AND sticker_id = ? AND cover = ? RETURNING _id",
{installed_stickers.value(i, "pack_id"), installed_stickers.value(i, "sticker_id"), installed_stickers.value(i, "cover")},
&deleted_sticker_ids);
count += source->d_database.changed();
// delete actual sticker image
for (unsigned int j = 0; j < deleted_sticker_ids.rows(); ++j)
{
long long int erased = deleted_sticker_ids.valueAsInt(j, "_id");
if (erased == -1)
continue;
auto it = std::find_if(source->d_stickers.begin(), source->d_stickers.end(), [erased](auto const &s) { return s.first == static_cast<uint64_t>(erased); });
if (it != source->d_stickers.end())
source->d_stickers.erase(it);
}
}
if (count)
Logger::message(" Deleted ", count, " existing stickers");
}
// delete double pendingpnisignaturemessages
if (d_database.containsTable("pending_pni_signature_message") && source->d_database.containsTable("pending_pni_signature_message"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT recipient_id, sent_timestamp, device_id FROM pending_pni_signature_message", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
source->d_database.exec("DELETE FROM pending_pni_signature_message "
"WHERE recipient_id = ? AND sent_timestamp = ? AND device_id = ?",
{res.value(i, "recipient_id"), res.value(i, "sent_timestamp"), res.value(i, "device_id")});
count += source->d_database.changed();
}
if (count)
Logger::message(" Deleted ", count, " existing pending_pni_signature_messages");
}
// delete double distribution lists?
if (d_database.containsTable("distribution_list") && source->d_database.containsTable("distribution_list"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT distribution_id FROM distribution_list", &res);
int count = 0;
for (unsigned int i = 0; i < res.rows(); ++i)
{
SqliteDB::QueryResults deleted_distribution_list_recipients;
source->d_database.exec("DELETE FROM distribution_list WHERE distribution_id = ? RETURNING recipient_id", res.value(i, 0), &deleted_distribution_list_recipients);
count += source->d_database.changed();
//deleted_distribution_list_recipients.prettyPrint(d_truncate);
// delete the corresponding recipient
for (unsigned int j = 0; j < deleted_distribution_list_recipients.rows(); ++j)
{
source->d_database.exec("DELETE FROM recipient WHERE _id = ?", deleted_distribution_list_recipients.value(j, "recipient_id"));
// the number can be zero 0, as the recipient is matched (by distribution_list_id, for
// MY_STORY it is always 00000000-0000-0000-0000-000000000000), after which it is deleted
// as a doubled, existing recipient above (see: Dropped x existing recipients from source database)
if (source->d_database.changed() > 1) [[unlikely]]
Logger::warning("Unexpected number of distribution_list recipients deleted: ", source->d_database.changed());
}
}
if (count)
Logger::message(" Deleted ", count, " existing distribution lists");
// clean up the member table
source->d_database.exec("DELETE FROM distribution_list_member WHERE list_id NOT IN (SELECT DISTINCT _id FROM distribution_list)");
}
// delete exisiting name_collisions (only possible if targetthread is an existing thread)
if (source->d_database.containsTable("name_collision") &&
d_database.containsTable("name_collision"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT thread_id FROM name_collision", &res);
for (unsigned int i = 0; i < res.rows(); ++i)
source->d_database.exec("DELETE FROM name_collision WHERE thread_id = ?", res.value(i, 0));
// delete corresponding collision_members
source->d_database.exec("DELETE FROM name_collision_membership WHERE collision_id NOT IN (SELECT _id FROM name_collision)");
}
// delete existing chat_folder_memberships
if (source->d_database.containsTable("chat_folder_membership") &&
d_database.containsTable("chat_folder_membership"))
{
SqliteDB::QueryResults res;
d_database.exec("SELECT chat_folder_id, thread_id FROM chat_folder_membership", &res);
for (unsigned int i = 0; i < res.rows(); ++i)
source->d_database.exec("DELETE FROM chat_folder_membership WHERE chat_folder_id = ? AND thread_id = ?",
{res.value(i, "chat_folder_id"), res.value(i, "thread_id")});
}
// delete the default chat_folder
if (source->d_database.containsTable("chat_folder_membership") &&
d_database.containsTable("chat_folder_membership"))
{
source->d_database.exec("DELETE FROM chat_folder WHERE NAME IS NULL");
}
// // export database
// std::cout << "Writing database..." << std::endl;
// SqliteDB database("NEWSTYLE.2.sqlite", false);
// if (!SqliteDB::copyDb(source->d_database, database))
// std::cout << "Error exporting sqlite database" << std::endl;
// return;
// now import the source tables into target,
// get tables
std::string q("SELECT sql, name, type FROM sqlite_master");
source->d_database.exec(q, &results);
std::vector<std::string> tables;
for (unsigned int i = 0; i < results.rows(); ++i)
{
if (!results.isNull(i, 0))
{
//std::cout << "Dealing with: " << results.getValueAs<std::string>(i, 1) << std::endl;
if (results.valueHasType<std::string>(i, 1) &&
(results.getValueAs<std::string>(i, 1) != "sms_fts" &&
STRING_STARTS_WITH(results.getValueAs<std::string>(i, 1), "sms_fts")))
;//std::cout << "Skipping " << results[i][1].second << " because it is sms_ftssecrettable" << std::endl;
else if (results.valueHasType<std::string>(i, 1) &&
(results.getValueAs<std::string>(i, 1) != d_mms_table + "_fts" &&
STRING_STARTS_WITH(results.getValueAs<std::string>(i, 1), d_mms_table + "_fts")))
;//std::cout << "Skipping " << results[i][1].second << " because it is mms_ftssecrettable" << std::endl;
else if (results.valueHasType<std::string>(i, 1) &&
(results.getValueAs<std::string>(i, 1) != "emoji_search" &&
STRING_STARTS_WITH(results.getValueAs<std::string>(i, 1), "emoji_search")))
;//std::cout << "Skipping " << results.getValueAs<std::string>(i, 1) << " because it is emoji_search_ftssecrettable" << std::endl;
else if (results.valueHasType<std::string>(i, 1) &&
STRING_STARTS_WITH(results.getValueAs<std::string>(i, 1), "sqlite_"))
;
else
if (results.valueHasType<std::string>(i, 2) && results.getValueAs<std::string>(i, 2) == "table")
{
tables.emplace_back(results.getValueAs<std::string>(i, 1));
//std::cout << "Added: " << results.getValueAs<std::string>(i, 1) << std::endl;
}
}
}
// write contents of tables
for (std::string const &table : tables)
{
if (table == "signed_prekeys" ||
table == "one_time_prekeys" ||
table == "sessions" ||
//table == "job_spec" || // this is in the official export. But it makes testing more difficult. it
//table == "constraint_spec" || // should be ok to export these (if present in source), since we are only
//table == "dependency_spec" || // dealing with exported backups (not from live installations) -> they should
//table == "emoji_search" || // have been excluded + the official import should be able to deal with them
STRING_STARTS_WITH(table, "sms_fts") ||
STRING_STARTS_WITH(table, d_mms_table + "_fts") ||
STRING_STARTS_WITH(table, "sqlite_"))
continue;
source->d_database.exec("SELECT * FROM " + table, &results);
if (results.rows() == 0)
{
if (d_verbose) [[unlikely]]
Logger::message("Importing statements from source table '", table, "'... (0 entries) ...done");
continue;
}
if (!d_database.containsTable(table))
{
Logger::warning("Skipping table '", table, "', as it is not present in target database. Data may be missing.");
continue;
}
// check if source contains columns not existing in target
// even though target is newer, a fresh install would not have
// created dropped columns that may still be present in
// source database;
unsigned int idx = 0;
while (idx < results.headers().size())
{
if (!d_database.tableContainsColumn(table, results.headers()[idx]))
{
// attempt translate
std::string oldname = results.headers()[idx];
std::string newname = getTranslatedName(table, oldname);
if (!newname.empty() && results.renameColumn(idx, newname))
{
Logger::message(" NOTE: Translating column name from '", table, ".", oldname, "' (source) to '", table, ".", newname, "' (target)");
}
else //: drop
{
Logger::message(" NOTE: Dropping column '", table, ".", results.headers()[idx], "' from source : Column not present in target database");
if (results.removeColumn(idx))
continue;
}
}
// else
++idx;
}
// if all columns were dropped, the entire table (probably) does not exist in target database, we'll just skip it
// for instance, a (newly/currently?) created database will not have the megaphone table
if (results.columns() == 0)
{
Logger::warning("Skipping table '", table, "', it has no columns left.");
continue;
}
Logger::message_start("Importing statements from source table '", table, "'... (", results.rows(), " entries)");
for (unsigned int i = 0; i < results.rows(); ++i)
{
// if (table == "identities")
// {
// std::cout << "Trying to add: ";
// for (unsigned int j = 0; j < results.columns(); ++j)
// std::cout << results.valueAsString(i, j) << " ";
// std::cout << std::endl;
// }
SqlStatementFrame newframe = buildSqlStatementFrame(table, results.headers(), results.row(i));
d_database.exec(newframe.bindStatement(), newframe.parameters());
//newframe.printInfo();
}
Logger::message_end(" ...done");
}
// and copy avatars and attachments.
for (auto &att : source->d_attachments)
d_attachments.emplace(std::move(att));
for (auto &av : source->d_avatars)
d_avatars.emplace_back(std::move(av));
// stickers???
for (auto &s : source->d_stickers)
d_stickers.emplace(std::move(s));
/*
THIS IS NOT TRUE, CURRENTLY THE RELEASECHANNEL
THREAD FROM SOURCE IS SKIPPED UNCONDITIONALLY
AND THE RELEASECHANNEL RECIPIENT FROM SOURCE
IS REMOVED. ADDING THIS KEY WILL CAUSE TROUBLE
// if target has no release channel-recipient, but
// source does, it is copied over, we need the pref
if (target_releasechannel == -1)
for (auto &skv : source->d_keyvalueframes)
if (skv->key() == "releasechannel.recipient_id")
{
d_keyvalueframes.emplace_back(std::move(skv));
break;
}
*/
// update thread snippet and date and count
updateThreadsEntries();
d_database.exec("VACUUM");
d_database.freeMemory();
return checkDbIntegrity();
}
|