diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6a16900d05..8af9930684 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,8 +26,8 @@ configurations.configureEach { exclude(module = "commons-logging") } -val canonicalVersionCode = 421 -val canonicalVersionName = "1.28.0" +val canonicalVersionCode = 426 +val canonicalVersionName = "1.28.1" val postFixSize = 10 val abiPostFix = mapOf( diff --git a/app/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt b/app/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt index 65ca0c6e6f..03cc5c6ff6 100644 --- a/app/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt +++ b/app/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt @@ -16,7 +16,7 @@ data class OpenGroup( val room: String, @SerialName("displayName") // This rename caters for existing data val name: String, - val description: String?, + val description: String? = null, val publicKey: String, val imageId: String?, val infoUpdates: Int, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/AvatarUploadManager.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/AvatarUploadManager.kt index 3e063b32bb..bbd7d83918 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/AvatarUploadManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/AvatarUploadManager.kt @@ -191,7 +191,11 @@ class AvatarUploadManager @Inject constructor( val result = it.userProfile.getPic() val userPic = remoteFile.toUserPic() if (isReupload) { - it.userProfile.setReuploadedPic(userPic) + it.userProfile.setPic(userPic) + + // TODO: We'll need to call this when the libsession re-enables the re-uploaded + // avatar logic. + // it.userProfile.setReuploadedPic(userPic) } else { it.userProfile.setPic(userPic) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 901469552a..1e6c21e67e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -20,6 +20,7 @@ import static org.thoughtcrime.securesms.database.MmsSmsColumns.ID; import static org.thoughtcrime.securesms.database.MmsSmsColumns.NOTIFIED; import static org.thoughtcrime.securesms.database.MmsSmsColumns.READ; +import static org.thoughtcrime.securesms.database.MmsSmsColumns.THREAD_ID; import android.content.ContentValues; import android.content.Context; @@ -525,6 +526,8 @@ public int getMessagePositionInConversation(long threadId, long sentTimestamp, @ return -1; } + // Please note this migration contain a mistake (message_id used as thread_id), it's corrected in the subsequent release, + // so you shouldn't try to fix it here. private static void migrateLegacyCommunityAddresses(final SQLiteDatabase db, final String tableName) { final String query = "SELECT " + ID + ", " + MmsSmsColumns.ADDRESS + " FROM " + tableName; try (final Cursor cursor = db.rawQuery(query)) { @@ -578,11 +581,80 @@ private static void migrateLegacyCommunityAddresses(final SQLiteDatabase db, fin } } + // This is an attempt to fix the issue in migrateLegacyCommunityAddresses + private static void migrateLegacyCommunityAddresses2(final SQLiteDatabase db, final String tableName) { + final String query = "SELECT " + ID + ", " + THREAD_ID + ", " + MmsSmsColumns.ADDRESS + " FROM " + tableName; + try (final Cursor cursor = db.rawQuery(query)) { + while (cursor.moveToNext()) { + final long messageId = cursor.getLong(0); + final long threadId = cursor.getLong(1); + final String address = cursor.getString(2); + final String newAddress; + + try { + if (address.startsWith(GroupUtil.COMMUNITY_PREFIX)) { + // First, if a message has a sender being a community address, it suggests the message + // is sent by us (this is an assumption from other part of the code). + // This also means that the address will be the thread's address, if the thread address + // is indeed a community address + final String threadSql = "SELECT " + ThreadDatabase.ADDRESS + " FROM " + + ThreadDatabase.TABLE_NAME + " WHERE " + ThreadDatabase.ID + " = ?"; + try (final Cursor threadCursor = db.rawQuery(threadSql, threadId)) { + if (threadCursor.moveToNext()) { + final Address threadAddress = Address.fromSerialized(threadCursor.getString(0)); + if (threadAddress instanceof Address.Community) { + newAddress = threadAddress.getAddress(); + } else { + // If this message has a sender being a community address, but the thread address + // is not community(!), we'll have to fall back to unsafe group id migration + final String groupId = GroupUtil.getDecodedGroupID(address); + final int dotIndex = groupId.lastIndexOf('.'); + if (dotIndex > 0 && dotIndex < groupId.length() - 1) { + newAddress = new Address.Community( + groupId.substring(0, dotIndex), + groupId.substring(dotIndex + 1) + ).getAddress(); + } else { + Log.w(TAG, "Unable to decode group id from address: " + address); + continue; + } + } + } else { + Log.w(TAG, "Thread not found for message id = " + messageId); + // Thread not found? - this is strange but if we don't have threads these messages + // aren't visible anyway. + continue; + } + } + } else { + continue; + } + } catch (Throwable e) { + Log.e(TAG, "Error while migrating address " + address, e); + continue; + } + + if (!newAddress.equals(address)) { + Log.i(TAG, "Migrating message ID=" + messageId); + ContentValues contentValues = new ContentValues(1); + contentValues.put(MmsSmsColumns.ADDRESS, newAddress); + db.update(tableName, contentValues, ID + " = ?", new String[]{String.valueOf(messageId)}); + } + } + } + + } + public static void migrateLegacyCommunityAddresses(final SQLiteDatabase db) { migrateLegacyCommunityAddresses(db, SmsDatabase.TABLE_NAME); migrateLegacyCommunityAddresses(db, MmsDatabase.TABLE_NAME); } + public static void migrateLegacyCommunityAddresses2(final SQLiteDatabase db) { + migrateLegacyCommunityAddresses2(db, SmsDatabase.TABLE_NAME); + migrateLegacyCommunityAddresses2(db, MmsDatabase.TABLE_NAME); + } + private Cursor queryTables(String[] projection, String selection, String order, String limit) { String reactionsColumn = "json_group_array(json_object(" + "'" + ReactionDatabase.ROW_ID + "', " + ReactionDatabase.TABLE_NAME + "." + ReactionDatabase.ROW_ID + ", " + diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt index 843ff69d6a..8b47b7dfb0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt @@ -6,6 +6,7 @@ import android.database.Cursor import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.json.JSONArray import org.json.JSONException import org.session.libsignal.utilities.JsonUtil.SaneJSONObject @@ -86,15 +87,15 @@ class ReactionDatabase(context: Context, helper: Provider) @JvmField val CREATE_MESSAGE_ID_MMS_INDEX = arrayOf("CREATE INDEX IF NOT EXISTS reaction_message_id_mms_idx ON $TABLE_NAME ($MESSAGE_ID, $IS_MMS)") - @JvmField - val MIGRATE_REACTION_TABLE_TO_USE_RECIPIENT_SETTINGS = arrayOf( + fun migrateToDropForeignConstraint(db: SQLiteDatabase) { // Create the new table with updated schema - """ + db.rawExecSQL( + """ CREATE TABLE ${TABLE_NAME}_new ( $ROW_ID INTEGER PRIMARY KEY, $MESSAGE_ID INTEGER NOT NULL, $IS_MMS INTEGER NOT NULL, - $AUTHOR_ID INTEGER NOT NULL REFERENCES ${RecipientSettingsDatabase.TABLE_NAME} (${RecipientSettingsDatabase.COL_ADDRESS}) ON DELETE CASCADE, + $AUTHOR_ID TEXT NOT NULL, $EMOJI TEXT NOT NULL, $SERVER_ID TEXT NOT NULL, $COUNT INTEGER NOT NULL, @@ -103,27 +104,34 @@ class ReactionDatabase(context: Context, helper: Provider) $DATE_RECEIVED INTEGER NOT NULL, UNIQUE($MESSAGE_ID, $IS_MMS, $EMOJI, $AUTHOR_ID) ON CONFLICT REPLACE ) - """, + """ + ) // Copy data from the old table to the new table - """ - INSERT INTO ${TABLE_NAME}_new ($ROW_ID, $MESSAGE_ID, $IS_MMS, $AUTHOR_ID, $EMOJI, $SERVER_ID, $COUNT, $SORT_ID, $DATE_SENT, $DATE_RECEIVED) - SELECT $ROW_ID, $MESSAGE_ID, $IS_MMS, ${AUTHOR_ID}, $EMOJI, $SERVER_ID, $COUNT, $SORT_ID, $DATE_SENT, $DATE_RECEIVED + db.rawExecSQL( + """ + INSERT OR REPLACE INTO ${TABLE_NAME}_new ($ROW_ID, $MESSAGE_ID, $IS_MMS, $AUTHOR_ID, $EMOJI, $SERVER_ID, $COUNT, $SORT_ID, $DATE_SENT, $DATE_RECEIVED) + SELECT $ROW_ID, $MESSAGE_ID, $IS_MMS, $AUTHOR_ID, $EMOJI, $SERVER_ID, $COUNT, $SORT_ID, $DATE_SENT, $DATE_RECEIVED FROM $TABLE_NAME - """, + """) // Drop the old table and their triggers - "DROP TABLE $TABLE_NAME", - "DROP TRIGGER reactions_sms_delete", - "DROP TRIGGER reactions_mms_delete", + db.rawExecSQL("DROP TRIGGER IF EXISTS reactions_sms_delete") + db.rawExecSQL("DROP TRIGGER IF EXISTS reactions_mms_delete") + db.rawExecSQL("DROP TABLE IF EXISTS $TABLE_NAME") // Rename the new table to the original table name - "ALTER TABLE ${TABLE_NAME}_new RENAME TO $TABLE_NAME", + db.rawExecSQL("ALTER TABLE ${TABLE_NAME}_new RENAME TO $TABLE_NAME") // Add the necessary indexes and triggers to the new table - *CREATE_INDEXS, - *CREATE_REACTION_TRIGGERS, - ) + for (indexCmd in CREATE_INDEXS) { + db.rawExecSQL(indexCmd) + } + + for (triggerCmd in CREATE_REACTION_TRIGGERS) { + db.rawExecSQL(triggerCmd) + } + } private fun readReaction(cursor: Cursor): ReactionRecord { return ReactionRecord( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 1ff53df14e..ae56d16c0f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -98,9 +98,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV50 = 71; private static final int lokiV51 = 72; private static final int lokiV52 = 73; + private static final int lokiV53 = 74; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV52; + private static final int DATABASE_VERSION = lokiV53; private static final int MIN_DATABASE_VERSION = lokiV7; public static final String DATABASE_NAME = "session.db"; @@ -248,8 +249,8 @@ public void onCreate(SQLiteDatabase db) { db.execSQL(ThreadDatabase.ADD_SNIPPET_CONTENT_COLUMN); executeStatements(db, RecipientSettingsDatabase.Companion.getMIGRATION_CREATE_TABLE()); + ReactionDatabase.Companion.migrateToDropForeignConstraint(db); db.execSQL(RecipientSettingsDatabase.MIGRATE_DROP_OLD_TABLE); - executeStatements(db, ReactionDatabase.MIGRATE_REACTION_TABLE_TO_USE_RECIPIENT_SETTINGS); db.execSQL(BlindedIdMappingDatabase.DROP_TABLE_COMMAND); db.execSQL(ExpirationConfigurationDatabase.DROP_TABLE_COMMAND); db.execSQL(SessionContactDatabase.getDropTableCommand()); @@ -570,8 +571,8 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { executeStatements(db, RecipientSettingsDatabase.Companion.getMIGRATION_CREATE_TABLE()); db.execSQL(RecipientSettingsDatabase.MIGRATE_MOVE_DATA_FROM_OLD_TABLE); + ReactionDatabase.Companion.migrateToDropForeignConstraint(db); db.execSQL(RecipientSettingsDatabase.MIGRATE_DROP_OLD_TABLE); - executeStatements(db, ReactionDatabase.MIGRATE_REACTION_TABLE_TO_USE_RECIPIENT_SETTINGS); db.execSQL(BlindedIdMappingDatabase.DROP_TABLE_COMMAND); db.execSQL(ExpirationConfigurationDatabase.DROP_TABLE_COMMAND); db.execSQL(SessionContactDatabase.getDropTableCommand()); @@ -582,6 +583,10 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { executeStatements(db, CommunityDatabase.Companion.getMIGRATE_DROP_OLD_TABLES()); } + if (oldVersion < lokiV53) { + MmsSmsDatabase.migrateLegacyCommunityAddresses2(db); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index f224826456..6d94fc83f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -226,12 +225,13 @@ class DefaultConversationRepository @Inject constructor( it == TextSecurePreferences.SET_FORCE_POST_PRO } ).debounce(500) - .onStart { emit(Unit) } - .mapLatest { - withContext(Dispatchers.Default) { - threadDb.getThreads(allAddresses) - } - } + .onStart { emit(allAddresses) } + .map { allAddresses } + } + .map { addresses -> + withContext(Dispatchers.Default) { + threadDb.getThreads(addresses) + } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a315f92efe..ed3b35a9ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ kotlinVersion = "2.2.20" kryoVersion = "5.6.2" kspVersion = "2.2.10-2.0.2" legacySupportV13Version = "1.0.0" -libsessionUtilAndroidVersion = "1.0.8" +libsessionUtilAndroidVersion = "1.0.8-1-g27817b4" media3ExoplayerVersion = "1.8.0" mockitoCoreVersion = "5.20.0" navVersion = "2.9.4"