From 1459dbf64d619fc7889af6f91ce73bcc2e5c3635 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 16 Jan 2025 11:04:18 -0500 Subject: [PATCH] Add backup support for DirectStoryReplyMessages. --- .../chat_item_direct_story_reply_00.binproto | Bin 0 -> 593 bytes .../chat_item_direct_story_reply_01.binproto | Bin 0 -> 719 bytes .../chat_item_direct_story_reply_02.binproto | Bin 0 -> 776 bytes .../chat_item_direct_story_reply_03.binproto | Bin 0 -> 742 bytes .../chat_item_direct_story_reply_04.binproto | Bin 0 -> 742 bytes .../chat_item_direct_story_reply_05.binproto | Bin 0 -> 761 bytes .../chat_item_direct_story_reply_06.binproto | Bin 0 -> 689 bytes .../chat_item_direct_story_reply_07.binproto | Bin 0 -> 704 bytes .../chat_item_direct_story_reply_08.binproto | Bin 0 -> 681 bytes .../chat_item_direct_story_reply_09.binproto | Bin 0 -> 546 bytes .../chat_item_direct_story_reply_10.binproto | Bin 0 -> 570 bytes .../chat_item_direct_story_reply_11.binproto | Bin 0 -> 582 bytes .../chat_item_direct_story_reply_12.binproto | Bin 0 -> 548 bytes .../chat_item_direct_story_reply_13.binproto | Bin 0 -> 579 bytes .../chat_item_direct_story_reply_14.binproto | Bin 0 -> 628 bytes .../backup/v2/ArchiveImportExportTests.kt | 5 ++ .../securesms/backup/v2/ArchiveErrorCases.kt | 4 + .../database/MessageTableArchiveExtensions.kt | 6 +- .../v2/exporters/ChatItemArchiveExporter.kt | 39 +++++++++- .../v2/importer/ChatItemArchiveImporter.kt | 73 ++++++++++++++---- .../securesms/database/MessageTable.kt | 1 + 21 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_00.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_01.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_02.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_03.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_04.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_05.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_06.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_07.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_08.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_09.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_10.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_11.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_12.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_13.binproto create mode 100644 app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_14.binproto diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_00.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_00.binproto new file mode 100644 index 0000000000000000000000000000000000000000..176fa49afd9f31a381efa63b8bcba1c13253bfcd GIT binary patch literal 593 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@^ur4SH9V$SFG#}`bTggEY=`G5GRp}@jFEf;5- zfWl`MqY;+@mmZf+Zenq9qC#GNZf0I$S*Aj9YF=?_QD%ukN@{X`QK~{_UP)?EK~ZW+ IVo_!>00+^+^8f$< literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_01.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_01.binproto new file mode 100644 index 0000000000000000000000000000000000000000..43516451fa69bb5680b29f085cb18a1821cde9b2 GIT binary patch literal 719 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@`fj7k?6K}?CcpW7c_FmdAJxOe9N;iHBEi~h7+ zoNY3TaU-LYAQ#Jr`I8!e;q__no*x%X6&MvJ&D}Hcni1C&MlNkG#mqc~l+?7$yv&l! z{Jhj+g|hsd(vpJ25{2T#^vsfy%wi!n4h8{WSPP{p374c+l;~IJ7H6jCCFbZBQoHl8oqz;5wS h^?h-Hl0a9{zTLh%EgzU@Jutnyul|X~<*wx`nE|kE@^JtF literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_02.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_02.binproto new file mode 100644 index 0000000000000000000000000000000000000000..b2e22b3222566dfd204b46d677ea5b5f030ef0a7 GIT binary patch literal 776 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@^cCZ$d$Ad^vI-skqm7fig^xL7z?q!?x~eqxjo z=&?3noCC%70#V92u z(ZVRvBrPp9(b&jHfJsV#i@78D^#=R5Ep1;Uv-47Olmv1QJ~%6};lAmmyDPN(|1DP1 ZIP?1a9Tp?*%HV9DKzG-mlJZ~+1^^|s|5E?} literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_03.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_03.binproto new file mode 100644 index 0000000000000000000000000000000000000000..9f7bbfb03822a36a4346730876bbd0fe8a0b3788 GIT binary patch literal 742 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@_Aj7rZLflNk;`JdY#Uoi3F=i=pH5xBAV_11N! zN=zJ#vlx#vavfpha^up;O)M@>RLINE&CE+I%Ty>%%_~kV$}CYxNlnf#N>#|rD@iRX zC`v6!EXphvVgrURs{#v!R~L> zt0tt~3O@CX$M2+)fRe5U$KF+Ue!f~Vq5rt?As3D9n@)dYG2$$Bbq)+KEiHAmU;+Tu C3-8we literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_04.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_04.binproto new file mode 100644 index 0000000000000000000000000000000000000000..d067f6314341aece18acd3ddee75926144619fba GIT binary patch literal 742 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@_Aj7rZLflNk;1)tj=Uoi3F<>KaG5m-O>)Y8?4 zN({3YPcljgaLOiVVjFio^bOif8jF|tffG7(^q x641NOIc>Vv*X2thQ@KT4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@`Tj7q;4flNk;g`e9WUoi3F;9}!o(PH9YoW=N% zQA&`D<-`0*4M6End-wdfV5$HWn%FDABJmj_#H4U;(vzjfOu05Pa)oedm6l}YCFUxW zuFouL8m4r)DD@yb$bc-|7^AdA(3vv=O^Q^dYv;EyO-3xsyDhh;5xj3uB z!`%uKldH;=1iUBS$mI7Gyr3Xg{&Sbix)WLg9gjbGDK2jn3~ZkG`{<_JZ3ZjnEbC$e E07OppD*ylh literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_06.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_06.binproto new file mode 100644 index 0000000000000000000000000000000000000000..59a2ff7284bc29ee935bd7e5e45bc8d3f360a655 GIT binary patch literal 689 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@`Bj7pmrflNk;MW5RrUoi3F<>KaG5xBAV_11N! zS`4!or!aC&WaQH0(#cIME>2X)%g@csODxM&C{E2QPA$qTQAkNm&M!(;$jmEAEh;EV zElDiOEEbAa5-v%tDABLbEzV5OOU%(N$Vtr1vtmp3&UC4&5^~_;NvZPi@z2lpjS9%k z5@3>8bpKxeZDS>Y!-v%FD9QD|F;!anT4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@^hj7kR?flNk;#h=?BUoi3F=i=pH5m-O>)Y8?4 zMob)xvltgLN(pkYe3(C}0jTQJ-aS7qm?|(TOq#oA;x!|#7Dg^@F2&3|g_P8^%)HE! z%>2C6VuiB&oYInl#1e(##PrOPlFVWuHVy^>76m4uP$l7#)QS@Q3f#`k3ODar>zEicFMV-^4FFT4>ly$6 literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_08.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_08.binproto new file mode 100644 index 0000000000000000000000000000000000000000..a47942bb4b5d83b5379fe98af3f52601dc0690d4 GIT binary patch literal 681 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@`Jj7qB*flNk;C7;_LUoi1v<6_}pF=Cj-IGIsO zkc;KR{7DT!$xnOt{J3DM02P|pE5IW086w1_aBk9*rN>OUQn+lmv`R}d^Ad9vO7cq* z6^c>|N>efu^HLI16_ScdOHvieQj0Q6N{bar^Gb7t*fT4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH7UQDW)m_Qw}Yy!g3zIama4EPlOpov9TQ P2jeV8pszm6pVR;VorSL? literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_10.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_10.binproto new file mode 100644 index 0000000000000000000000000000000000000000..b82372ec1d3102277ba1d12b1901f0e8dd9b1c63 GIT binary patch literal 570 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmHK`xdL^CvX`m3-Q}=f?$81xAHQbN5WVW+Vg@pV$ikkSV$~ literal 0 HcmV?d00001 diff --git a/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_11.binproto b/app/src/androidTest/assets/backupTests/chat_item_direct_story_reply_11.binproto new file mode 100644 index 0000000000000000000000000000000000000000..6ab5a7408de644cb288879fc77d0b6a98f7656dd GIT binary patch literal 582 zcmea}U=+CVYr*{QhEfXsyKEj#$*x#(ig%Lc-3T4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmHT4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmHT4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmHT4SDnN=53x$z@T~eE!J@?C1T;m9#SLhTl_rk?tZezH8@o%F+$t_T-1YmD}2wTN(wQD2D?ZwdU*l8%qYRAz^K7!z-Tc+#)&b} zi80BEG1-GLfH8tGfiZ)zfU$zHfswI;k!cnqI|rjE0|Uc<5Q_=O`VVF?i!%IY_|GQ9 z!ojG;0HTmKnoIB;1A_xY14I#{5V{DH z!qi81{#$z~T#qfqi=tQ^b25`tlURduGfOgx6j@)DwyyTieK^tT8|(bctz4IKLf5NT zK9;^0aqq+~u{be5D+UfJHV!5MmH@^|rE(yHQDW8S_Qw}Yyg0bnI9R-xIDkHmmlEV+ z`7nP{15ozU-aS7qm?|(rgeLY1utqEv;= $lastSeenReceivedTime") diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt index 276c5357ef..de391bc341 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt @@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.ChatItem import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage import org.thoughtcrime.securesms.backup.v2.proto.ContactAttachment import org.thoughtcrime.securesms.backup.v2.proto.ContactMessage +import org.thoughtcrime.securesms.backup.v2.proto.DirectStoryReplyMessage import org.thoughtcrime.securesms.backup.v2.proto.ExpirationTimerChatUpdate import org.thoughtcrime.securesms.backup.v2.proto.GenericGroupUpdate import org.thoughtcrime.securesms.backup.v2.proto.GroupCall @@ -291,6 +292,10 @@ class ChatItemArchiveExporter( builder.viewOnceMessage = record.toRemoteViewOnceMessage(mediaArchiveEnabled = mediaArchiveEnabled, reactionRecords = extraData.reactionsById[id], attachments = extraData.attachmentsById[id]) } + record.parentStoryId != 0L -> { + builder.directStoryReplyMessage = record.toRemoteDirectStoryReplyMessage(mediaArchiveEnabled = mediaArchiveEnabled, reactionRecords = extraData.reactionsById[id], attachments = extraData.attachmentsById[record.id]) ?: continue + } + else -> { if (record.body.isNullOrEmpty() && !extraData.attachmentsById.containsKey(record.id)) { Log.w(TAG, ExportSkips.emptyChatItem(record.dateSent)) @@ -852,6 +857,36 @@ private fun Contact.PostalAddress.Type.toRemote(): ContactAttachment.PostalAddre } } +private fun BackupMessageRecord.toRemoteDirectStoryReplyMessage(mediaArchiveEnabled: Boolean, reactionRecords: List?, attachments: List?): DirectStoryReplyMessage? { + if (this.body.isNullOrBlank()) { + Log.w(TAG, ExportSkips.directStoryReplyHasNoBody(this.dateSent)) + return null + } + + val isReaction = MessageTypes.isStoryReaction(this.type) + + return DirectStoryReplyMessage( + storySentTimestamp = this.parentStoryId.takeUnless { it == MessageTable.PARENT_STORY_MISSING_ID }, + emoji = if (isReaction) { + this.body + } else { + null + }, + textReply = if (!isReaction) { + DirectStoryReplyMessage.TextReply( + text = Text( + body = this.body, + bodyRanges = this.bodyRanges?.toRemoteBodyRanges(this.dateSent) ?: emptyList() + ), + longText = attachments?.firstOrNull { it.contentType == MediaUtil.LONG_TEXT }?.toRemoteFilePointer(mediaArchiveEnabled) + ) + } else { + null + }, + reactions = reactionRecords.toRemote() + ) +} + private fun BackupMessageRecord.toRemoteStandardMessage(db: SignalDatabase, mediaArchiveEnabled: Boolean, reactionRecords: List?, mentions: List?, attachments: List?): StandardMessage { val text = body?.let { Text( @@ -1344,7 +1379,8 @@ private fun Cursor.toBackupMessageRecord(pastIds: Set, backupStartTime: Lo identityMismatchRecipientIds = this.requireString(MessageTable.MISMATCHED_IDENTITIES).parseIdentityMismatches(), baseType = this.requireLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK, messageExtras = this.requireBlob(MessageTable.MESSAGE_EXTRAS).parseMessageExtras(), - viewOnce = this.requireBoolean(MessageTable.VIEW_ONCE) + viewOnce = this.requireBoolean(MessageTable.VIEW_ONCE), + parentStoryId = this.requireLong(MessageTable.PARENT_STORY_ID) ) } @@ -1372,6 +1408,7 @@ private class BackupMessageRecord( val quoteBodyRanges: ByteArray?, val quoteType: Int, val originalMessageId: Long?, + val parentStoryId: Long, val latestRevisionId: Long?, val hasDeliveryReceipt: Boolean, val hasReadReceipt: Boolean, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt index cbba360def..ce0301b39c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt @@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.BodyRange import org.thoughtcrime.securesms.backup.v2.proto.ChatItem import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage import org.thoughtcrime.securesms.backup.v2.proto.ContactAttachment +import org.thoughtcrime.securesms.backup.v2.proto.DirectStoryReplyMessage import org.thoughtcrime.securesms.backup.v2.proto.GroupCall import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall import org.thoughtcrime.securesms.backup.v2.proto.LinkPreview @@ -125,7 +126,8 @@ class ChatItemArchiveImporter( MessageTable.VIEW_ONCE, MessageTable.MESSAGE_EXTRAS, MessageTable.ORIGINAL_MESSAGE_ID, - MessageTable.LATEST_REVISION_ID + MessageTable.LATEST_REVISION_ID, + MessageTable.PARENT_STORY_ID ) private val REACTION_COLUMNS = arrayOf( @@ -238,10 +240,11 @@ class ChatItemArchiveImporter( private fun ChatItem.toMessageInsert(fromRecipientId: RecipientId, chatRecipientId: RecipientId, threadId: Long): MessageInsert { val contentValues = this.toMessageContentValues(fromRecipientId, chatRecipientId, threadId) - var followUp: ((Long) -> Unit)? = null + val followUps: MutableList<(Long) -> Unit> = mutableListOf() + if (this.updateMessage != null) { if (this.updateMessage.individualCall != null && this.updateMessage.individualCall.callId != null) { - followUp = { messageRowId -> + followUps += { messageRowId -> val values = contentValuesOf( CallTable.CALL_ID to updateMessage.individualCall.callId, CallTable.MESSAGE_ID to messageRowId, @@ -263,7 +266,7 @@ class ChatItemArchiveImporter( db.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values) } } else if (this.updateMessage.groupCall != null && this.updateMessage.groupCall.callId != null) { - followUp = { messageRowId -> + followUps += { messageRowId -> val ringer: RecipientId? = this.updateMessage.groupCall.ringerRecipientId?.let { importState.remoteToLocalRecipientId[it] } val values = contentValuesOf( @@ -295,7 +298,7 @@ class ChatItemArchiveImporter( } if (this.paymentNotification != null) { - followUp = { messageRowId -> + followUps += { messageRowId -> val uuid = tryRestorePayment(this, chatRecipientId) if (uuid != null) { db.update(MessageTable.TABLE_NAME) @@ -347,7 +350,7 @@ class ChatItemArchiveImporter( if (contact != null) { val contactAttachment: Attachment? = contact.avatarAttachment - followUp = { messageRowId -> + followUps += { messageRowId -> val attachmentMap = if (contactAttachment != null) { SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(contactAttachment), emptyList()) } else { @@ -365,6 +368,19 @@ class ChatItemArchiveImporter( } } + if (this.directStoryReplyMessage != null) { + val longTextAttachment: Attachment? = this.directStoryReplyMessage.textReply?.longText?.toLocalAttachment( + importState = importState, + contentType = "text/x-signal-plain" + ) + + if (longTextAttachment != null) { + followUps += { messageRowId -> + SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(longTextAttachment), emptyList()) + } + } + } + if (this.standardMessage != null) { val bodyRanges = this.standardMessage.text?.bodyRanges if (!bodyRanges.isNullOrEmpty()) { @@ -380,7 +396,7 @@ class ChatItemArchiveImporter( } } if (mentions.isNotEmpty()) { - followUp = { messageId -> + followUps += { messageId -> SignalDatabase.mentions.insert(threadId, messageId, mentions) } } @@ -391,19 +407,17 @@ class ChatItemArchiveImporter( attachment.toLocalAttachment() } - val longTextAttachments: List = this.standardMessage.longText?.let { longTextPointer -> - longTextPointer.toLocalAttachment( - importState = importState, - contentType = "text/x-signal-plain" - ) - }?.let { listOf(it) } ?: emptyList() + val longTextAttachments: List = this.standardMessage.longText?.toLocalAttachment( + importState = importState, + contentType = "text/x-signal-plain" + )?.let { listOf(it) } ?: emptyList() val quoteAttachments: List = this.standardMessage.quote?.toLocalAttachments() ?: emptyList() val hasAttachments = attachments.isNotEmpty() || linkPreviewAttachments.isNotEmpty() || quoteAttachments.isNotEmpty() || longTextAttachments.isNotEmpty() if (hasAttachments || linkPreviews.isNotEmpty()) { - followUp = { messageRowId -> + followUps += { messageRowId -> val attachmentMap = if (hasAttachments) { SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, attachments + linkPreviewAttachments + longTextAttachments, quoteAttachments) } else { @@ -424,7 +438,7 @@ class ChatItemArchiveImporter( val sticker = this.stickerMessage.sticker val attachment = sticker.toLocalAttachment() if (attachment != null) { - followUp = { messageRowId -> + followUps += { messageRowId -> SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(attachment), emptyList()) } } @@ -433,12 +447,20 @@ class ChatItemArchiveImporter( if (this.viewOnceMessage != null) { val attachment = this.viewOnceMessage.attachment?.toLocalAttachment() if (attachment != null) { - followUp = { messageRowId -> + followUps += { messageRowId -> SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(attachment), emptyList()) } } } + val followUp: ((Long) -> Unit)? = if (followUps.isNotEmpty()) { + { messageId -> + followUps.forEach { it(messageId) } + } + } else { + null + } + return MessageInsert(contentValues, followUp) } @@ -505,6 +527,7 @@ class ChatItemArchiveImporter( this.paymentNotification != null -> contentValues.addPaymentNotification(this, chatRecipientId) this.giftBadge != null -> contentValues.addGiftBadge(this.giftBadge) this.viewOnceMessage != null -> contentValues.addViewOnce(this.viewOnceMessage) + this.directStoryReplyMessage != null -> contentValues.addDirectStoryReply(this.directStoryReplyMessage) } return contentValues @@ -548,6 +571,7 @@ class ChatItemArchiveImporter( this.contactMessage != null -> this.contactMessage.reactions this.stickerMessage != null -> this.stickerMessage.reactions this.viewOnceMessage != null -> this.viewOnceMessage.reactions + this.directStoryReplyMessage != null -> this.directStoryReplyMessage.reactions else -> emptyList() } @@ -625,6 +649,10 @@ class ChatItemArchiveImporter( type = type or MessageTypes.SPECIAL_TYPE_GIFT_BADGE } + if (this.directStoryReplyMessage?.emoji != null) { + type = type or MessageTypes.SPECIAL_TYPE_STORY_REACTION + } + return type } @@ -848,6 +876,19 @@ class ChatItemArchiveImporter( put(MessageTable.VIEW_ONCE, true.toInt()) } + private fun ContentValues.addDirectStoryReply(directStoryReply: DirectStoryReplyMessage) { + put(MessageTable.PARENT_STORY_ID, directStoryReply.storySentTimestamp?.takeUnless { it == 0L } ?: MessageTable.PARENT_STORY_MISSING_ID) + + if (directStoryReply.emoji != null) { + put(MessageTable.BODY, directStoryReply.emoji) + } + + if (directStoryReply.textReply != null) { + put(MessageTable.BODY, directStoryReply.textReply.text?.body) + put(MessageTable.MESSAGE_RANGES, directStoryReply.textReply.text?.bodyRanges?.toLocalBodyRanges()?.encode()) + } + } + private fun String?.tryParseMoney(): Money? { if (this.isNullOrEmpty()) { return null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index 820e3b81f7..6ccdd3ab64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -210,6 +210,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat const val QUOTE_TARGET_MISSING_ID = -1L const val ADDRESSABLE_MESSAGE_LIMIT = 5 + const val PARENT_STORY_MISSING_ID = -1L const val CREATE_TABLE = """ CREATE TABLE $TABLE_NAME (