Move expiration configuration to a separate table

This commit is contained in:
charles 2022-12-05 13:00:53 +11:00
parent 92123b2417
commit 8c002247d6
12 changed files with 116 additions and 103 deletions

View File

@ -85,9 +85,9 @@ class ExpirationSettingsViewModel(
fun onSetClick() = viewModelScope.launch {
val expiresIn = _selectedExpirationTimer.value?.value?.toIntOrNull() ?: 0
val expiryType = _selectedExpirationType.value?.number ?: 0
val expiryChangeTimestamp = System.currentTimeMillis()
threadDb.updateExpiryConfig(threadId, expiresIn, expiryType, expiryChangeTimestamp)
val expiryType = _selectedExpirationType.value
val expiryChangeTimestampMs = System.currentTimeMillis()
storage.addExpirationConfiguration(ExpirationConfiguration(threadId, expiresIn, expiryType, expiryChangeTimestampMs))
}
@dagger.assisted.AssistedFactory

View File

@ -0,0 +1,92 @@
package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import org.session.libsession.messaging.messages.ExpirationConfiguration
import org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX
import org.session.libsession.utilities.GroupUtil.OPEN_GROUP_INBOX_PREFIX
import org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
class ExpirationConfigurationDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
companion object {
const val TABLE_NAME = "expiration_configuration"
const val THREAD_ID = "thread_id"
const val DURATION_SECONDS = "duration_seconds"
const val EXPIRATION_TYPE = "expiration_type"
const val UPDATED_TIMESTAMP_MS = "updated_timestamp_ms"
@JvmField
val CREATE_EXPIRATION_CONFIGURATION_TABLE_COMMAND = """
CREATE TABLE $TABLE_NAME (
$THREAD_ID INTEGER NOT NULL PRIMARY KEY ON CONFLICT REPLACE,
$DURATION_SECONDS INTEGER NOT NULL,
$EXPIRATION_TYPE INTEGER DEFAULT NULL,
$UPDATED_TIMESTAMP_MS INTEGER DEFAULT NULL
)
""".trimIndent()
@JvmField
val MIGRATE_GROUP_CONVERSATION_EXPIRY_TYPE_COMMAND = """
INSERT INTO $TABLE_NAME ($THREAD_ID, $DURATION_SECONDS, $EXPIRATION_TYPE) SELECT ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ID}, ${RecipientDatabase.EXPIRE_MESSAGES}, 1
FROM ${ThreadDatabase.TABLE_NAME}, ${RecipientDatabase.TABLE_NAME}
WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} LIKE '$CLOSED_GROUP_PREFIX%'
AND EXISTS (SELECT ${RecipientDatabase.EXPIRE_MESSAGES} FROM ${RecipientDatabase.TABLE_NAME} WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} = ${RecipientDatabase.TABLE_NAME}.${RecipientDatabase.ADDRESS} AND ${RecipientDatabase.EXPIRE_MESSAGES} > 0)
""".trimIndent()
@JvmField
val MIGRATE_ONE_TO_ONE_CONVERSATION_EXPIRY_TYPE_COMMAND = """
INSERT INTO $TABLE_NAME ($THREAD_ID, $DURATION_SECONDS, $EXPIRATION_TYPE) SELECT ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ID}, ${RecipientDatabase.EXPIRE_MESSAGES}, 2
FROM ${ThreadDatabase.TABLE_NAME}, ${RecipientDatabase.TABLE_NAME}
WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$CLOSED_GROUP_PREFIX%'
AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$OPEN_GROUP_PREFIX%'
AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$OPEN_GROUP_INBOX_PREFIX%'
AND EXISTS (SELECT ${RecipientDatabase.EXPIRE_MESSAGES} FROM ${RecipientDatabase.TABLE_NAME} WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} = ${RecipientDatabase.TABLE_NAME}.${RecipientDatabase.ADDRESS} AND ${RecipientDatabase.EXPIRE_MESSAGES} > 0)
""".trimIndent()
private fun readExpirationConfiguration(cursor: Cursor): ExpirationConfiguration {
return ExpirationConfiguration(
threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)),
durationSeconds = cursor.getInt(cursor.getColumnIndexOrThrow(DURATION_SECONDS)),
expirationType = ExpirationType.valueOf(cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRATION_TYPE))),
updatedTimestampMs = cursor.getLong(cursor.getColumnIndexOrThrow(UPDATED_TIMESTAMP_MS))
)
}
}
fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? {
val query = "$THREAD_ID = ?"
val args = arrayOf("$threadId")
val mappings: MutableList<ExpirationConfiguration> = mutableListOf()
readableDatabase.query(TABLE_NAME, null, query, args, null, null, null).use { cursor ->
while (cursor.moveToNext()) {
mappings += readExpirationConfiguration(cursor)
}
}
return mappings.firstOrNull()
}
fun addExpirationConfiguration(configuration: ExpirationConfiguration) {
writableDatabase.beginTransaction()
try {
val values = ContentValues().apply {
put(THREAD_ID, configuration.threadId)
put(DURATION_SECONDS, configuration.durationSeconds)
put(EXPIRATION_TYPE, configuration.expirationType?.number)
put(UPDATED_TIMESTAMP_MS, configuration.updatedTimestampMs)
}
writableDatabase.insert(TABLE_NAME, null, values)
writableDatabase.setTransactionSuccessful()
} finally {
writableDatabase.endTransaction()
}
}
}

View File

@ -963,32 +963,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration? {
val threadDb = DatabaseComponent.get(context).threadDatabase()
threadDb.readerFor(threadDb.conversationList).use { reader ->
while (reader.next != null) {
val thread = reader.current
if (thread.threadId == threadId &&
(thread.recipient.isClosedGroupRecipient || thread.recipient.isContactRecipient)
) {
return ExpirationConfiguration(
thread.threadId,
thread.expiresIn.toInt(),
thread.expiryType,
thread.expiryChangeTimestamp
)
}
}
}
return null
return DatabaseComponent.get(context).expirationConfigurationDatabase().getExpirationConfiguration(threadId)
}
override fun updateExpirationConfiguration(config: ExpirationConfiguration) {
DatabaseComponent.get(context).threadDatabase().updateExpiryConfig(
config.threadId,
config.durationSeconds,
config.expirationType?.number ?: 0,
config.lastChangeTimestampMs
)
override fun addExpirationConfiguration(config: ExpirationConfiguration) {
DatabaseComponent.get(context).expirationConfigurationDatabase().addExpirationConfiguration(config)
}
override fun getExpiringMessages(messageIds: LongArray): List<Pair<String, Int>> {

View File

@ -18,7 +18,6 @@
package org.thoughtcrime.securesms.database;
import static org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX;
import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_INBOX_PREFIX;
import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX;
import static org.thoughtcrime.securesms.database.GroupDatabase.GROUP_ID;
@ -97,8 +96,6 @@ public class ThreadDatabase extends Database {
public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
public static final String READ_RECEIPT_COUNT = "read_receipt_count";
public static final String EXPIRES_IN = "expires_in";
public static final String EXPIRY_TYPE = "expiry_type";
public static final String EXPIRY_CHANGE_TIMESTAMP= "expiry_change_timestamp";
public static final String LAST_SEEN = "last_seen";
public static final String HAS_SENT = "has_sent";
public static final String IS_PINNED = "is_pinned";
@ -121,8 +118,7 @@ public class ThreadDatabase extends Database {
private static final String[] THREAD_PROJECTION = {
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE,
SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED,
EXPIRY_TYPE, EXPIRY_CHANGE_TIMESTAMP
SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED
};
private static final List<String> TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION)
@ -139,36 +135,6 @@ public class ThreadDatabase extends Database {
"ADD COLUMN " + IS_PINNED + " INTEGER DEFAULT 0;";
}
public static String getCreateExpiryTypeCommand() {
return "ALTER TABLE "+ TABLE_NAME + " " +
"ADD COLUMN " + EXPIRY_TYPE + " INTEGER DEFAULT 0;";
}
public static String getCreateExpiryChangeTimestampCommand() {
return "ALTER TABLE "+ TABLE_NAME + " " +
"ADD COLUMN " + EXPIRY_CHANGE_TIMESTAMP + " INTEGER DEFAULT 0;";
}
public static String getUpdateGroupConversationExpiryTypeCommand() {
return "UPDATE " + TABLE_NAME + " SET " + EXPIRY_TYPE + " = 1, " +
EXPIRES_IN + " = (SELECT " + RecipientDatabase.EXPIRE_MESSAGES + " FROM " + RecipientDatabase.TABLE_NAME +
" WHERE " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + " AND " + RecipientDatabase.EXPIRE_MESSAGES + " > 0 )" +
"WHERE " + TABLE_NAME + "." + ADDRESS + " LIKE '" + CLOSED_GROUP_PREFIX + "%' " +
"AND EXISTS (SELECT " + RecipientDatabase.EXPIRE_MESSAGES + " FROM " + RecipientDatabase.TABLE_NAME +
" WHERE " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + " AND " + RecipientDatabase.EXPIRE_MESSAGES + " > 0 )";
}
public static String getUpdateOneToOneConversationExpiryTypeCommand() {
return "UPDATE " + TABLE_NAME + " SET " + EXPIRY_TYPE + " = 2, " +
EXPIRES_IN + " = (SELECT " + RecipientDatabase.EXPIRE_MESSAGES + " FROM " + RecipientDatabase.TABLE_NAME +
" WHERE " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + " AND " + RecipientDatabase.EXPIRE_MESSAGES + " > 0 )" +
"WHERE " + ADDRESS + " NOT LIKE '" + CLOSED_GROUP_PREFIX + "%'" +
"AND " + ADDRESS + " NOT LIKE '" + OPEN_GROUP_PREFIX + "%'" +
"AND " + ADDRESS + " NOT LIKE '" + OPEN_GROUP_INBOX_PREFIX + "%'" +
"AND EXISTS (SELECT " + RecipientDatabase.EXPIRE_MESSAGES + " FROM " + RecipientDatabase.TABLE_NAME +
" WHERE " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + " AND " + RecipientDatabase.EXPIRE_MESSAGES + " > 0 )";
}
public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@ -234,18 +200,6 @@ public class ThreadDatabase extends Database {
notifyConversationListListeners();
}
public void updateExpiryConfig(long threadId, int expiresIn, int expiryType, long expiryChangeTimestamp) {
ContentValues contentValues = new ContentValues(3);
contentValues.put(EXPIRES_IN, expiresIn);
contentValues.put(EXPIRY_TYPE, expiryType);
contentValues.put(EXPIRY_CHANGE_TIMESTAMP, expiryChangeTimestamp);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""});
notifyConversationListListeners();
}
private void deleteThread(long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""});
@ -955,8 +909,6 @@ public class ThreadDatabase extends Database {
int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT));
int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN));
int expiryType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRY_TYPE));
long expiryChangeTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRY_CHANGE_TIMESTAMP));
long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN));
Uri snippetUri = getSnippetUri(cursor);
boolean pinned = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.IS_PINNED)) != 0;
@ -967,7 +919,7 @@ public class ThreadDatabase extends Database {
return new ThreadRecord(body, snippetUri, recipient, date, count,
unreadCount, threadId, deliveryReceiptCount, status, type,
distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned, expiryType, expiryChangeTimestamp);
distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned);
}
private @Nullable Uri getSnippetUri(Cursor cursor) {

View File

@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.BlindedIdMappingDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
import org.thoughtcrime.securesms.database.ExpirationConfigurationDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupMemberDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
@ -181,8 +182,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.RESET_SEQ_NO); // probably not needed but consistent with all migrations
db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND);
db.execSQL(ReactionDatabase.CREATE_REACTION_TABLE_COMMAND);
db.execSQL(ThreadDatabase.getCreateExpiryTypeCommand());
db.execSQL(ThreadDatabase.getCreateExpiryChangeTimestampCommand());
db.execSQL(ExpirationConfigurationDatabase.CREATE_EXPIRATION_CONFIGURATION_TABLE_COMMAND);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
@ -418,10 +418,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
}
if (oldVersion < lokiV39) {
db.execSQL(ThreadDatabase.getCreateExpiryTypeCommand());
db.execSQL(ThreadDatabase.getCreateExpiryChangeTimestampCommand());
db.execSQL(ThreadDatabase.getUpdateGroupConversationExpiryTypeCommand());
db.execSQL(ThreadDatabase.getUpdateOneToOneConversationExpiryTypeCommand());
db.execSQL(ExpirationConfigurationDatabase.CREATE_EXPIRATION_CONFIGURATION_TABLE_COMMAND);
db.execSQL(ExpirationConfigurationDatabase.MIGRATE_GROUP_CONVERSATION_EXPIRY_TYPE_COMMAND);
db.execSQL(ExpirationConfigurationDatabase.MIGRATE_ONE_TO_ONE_CONVERSATION_EXPIRY_TYPE_COMMAND);
}
db.setTransactionSuccessful();

View File

@ -29,7 +29,6 @@ import androidx.annotation.Nullable;
import org.session.libsession.utilities.ExpirationUtil;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase;
@ -49,8 +48,6 @@ public class ThreadRecord extends DisplayRecord {
private final int distributionType;
private final boolean archived;
private final long expiresIn;
private final int expiryType;
private final long expiryChangeTimestamp;
private final long lastSeen;
private final boolean pinned;
private final int recipientHash;
@ -59,7 +56,7 @@ public class ThreadRecord extends DisplayRecord {
@NonNull Recipient recipient, long date, long count, int unreadCount,
long threadId, int deliveryReceiptCount, int status, long snippetType,
int distributionType, boolean archived, long expiresIn, long lastSeen,
int readReceiptCount, boolean pinned, int expiryType, long expiryChangeTimestamp)
int readReceiptCount, boolean pinned)
{
super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount);
this.snippetUri = snippetUri;
@ -68,8 +65,6 @@ public class ThreadRecord extends DisplayRecord {
this.distributionType = distributionType;
this.archived = archived;
this.expiresIn = expiresIn;
this.expiryType = expiryType;
this.expiryChangeTimestamp = expiryChangeTimestamp;
this.lastSeen = lastSeen;
this.pinned = pinned;
this.recipientHash = recipient.hashCode();
@ -174,15 +169,6 @@ public class ThreadRecord extends DisplayRecord {
return expiresIn;
}
@Nullable
public ExpirationType getExpiryType() {
return ExpirationType.valueOf(expiryType);
}
public long getExpiryChangeTimestamp() {
return expiryChangeTimestamp;
}
public long getLastSeen() {
return lastSeen;
}

View File

@ -46,4 +46,5 @@ interface DatabaseComponent {
fun attachmentProvider(): MessageDataProvider
fun blindedIdMappingDatabase(): BlindedIdMappingDatabase
fun groupMemberDatabase(): GroupMemberDatabase
fun expirationConfigurationDatabase(): ExpirationConfigurationDatabase
}

View File

@ -133,6 +133,10 @@ object DatabaseModule {
@Singleton
fun provideEmojiSearchDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = EmojiSearchDatabase(context, openHelper)
@Provides
@Singleton
fun provideExpirationConfigurationDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = ExpirationConfigurationDatabase(context, openHelper)
@Provides
@Singleton
fun provideStorage(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = Storage(context,openHelper)

View File

@ -200,6 +200,6 @@ interface StorageProtocol {
fun unblock(toUnblock: List<Recipient>)
fun blockedContacts(): List<Recipient>
fun getExpirationConfiguration(threadId: Long): ExpirationConfiguration?
fun updateExpirationConfiguration(config: ExpirationConfiguration)
fun addExpirationConfiguration(config: ExpirationConfiguration)
fun getExpiringMessages(messageIds: LongArray): List<Pair<String, Int>>
}

View File

@ -6,7 +6,7 @@ class ExpirationConfiguration(
val threadId: Long = -1,
val durationSeconds: Int = 0,
val expirationType: ExpirationType? = null,
val lastChangeTimestampMs: Long = 0
val updatedTimestampMs: Long = 0
) {
val isEnabled = durationSeconds > 0
}

View File

@ -45,7 +45,7 @@ abstract class Message {
builder.expirationTimer = config.durationSeconds
if (config.isEnabled) {
builder.expirationType = config.expirationType
builder.lastDisappearingMessageChangeTimestamp = config.lastChangeTimestampMs
builder.lastDisappearingMessageChangeTimestamp = config.updatedTimestampMs
}
}
}

View File

@ -88,7 +88,7 @@ fun updateExpirationConfigurationIfNeeded(message: Message, proto: SignalService
val threadID = storage.getOrCreateThreadIdFor(message.sender!!, message.groupPublicKey, openGroupID)
if (threadID <= 0) return
val localConfig = storage.getExpirationConfiguration(threadID)
if (localConfig == null || localConfig.lastChangeTimestampMs < proto.lastDisappearingMessageChangeTimestamp) return
if (localConfig == null || localConfig.updatedTimestampMs < proto.lastDisappearingMessageChangeTimestamp) return
val durationSeconds = if (proto.hasExpirationTimer()) proto.expirationTimer else 0
val type = if (proto.hasExpirationType()) proto.expirationType else null
val remoteConfig = ExpirationConfiguration(
@ -97,7 +97,7 @@ fun updateExpirationConfigurationIfNeeded(message: Message, proto: SignalService
type,
proto.lastDisappearingMessageChangeTimestamp
)
storage.updateExpirationConfiguration(remoteConfig)
storage.addExpirationConfiguration(remoteConfig)
}
// region Control Messages