Merge remote-tracking branch 'upstream/dev' into message-request-response

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt
This commit is contained in:
Morgan Pretty
2023-01-24 14:44:56 +11:00
534 changed files with 7258 additions and 5873 deletions

View File

@@ -33,8 +33,9 @@ import androidx.annotation.VisibleForTesting;
import com.bumptech.glide.Glide;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
@@ -318,6 +319,28 @@ public class AttachmentDatabase extends Database {
notifyAttachmentListeners();
}
@SuppressWarnings("ResultOfMethodCallIgnored")
void deleteAttachmentsForMessages(long[] mmsIds) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
String mmsIdString = StringUtils.join(mmsIds, ',');
try {
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE}, MMS_ID + " IN (?)",
new String[] {mmsIdString}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
deleteAttachmentOnDisk(cursor.getString(0), cursor.getString(1), cursor.getString(2));
}
} finally {
if (cursor != null)
cursor.close();
}
database.delete(TABLE_NAME, MMS_ID + " IN (?)", new String[] {mmsIdString});
notifyAttachmentListeners();
}
public void deleteAttachment(@NonNull AttachmentId id) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();

View File

@@ -23,7 +23,7 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.session.libsession.utilities.WindowDebouncer;
import org.thoughtcrime.securesms.ApplicationContext;
@@ -72,6 +72,11 @@ public abstract class Database {
context.getContentResolver().notifyChange(DatabaseContentProviders.StickerPack.CONTENT_URI, null);
}
protected void notifyRecipientListeners() {
context.getContentResolver().notifyChange(DatabaseContentProviders.Recipient.CONTENT_URI, null);
notifyConversationListListeners();
}
protected void setNotifyConverationListeners(Cursor cursor, long threadId) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId));
}

View File

@@ -38,6 +38,10 @@ public class DatabaseContentProviders {
public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.stickerpack");
}
public static class Recipient extends NoopContentProvider {
public static final Uri CONTENT_URI = Uri.parse("content://network.loki.securesms.database.recipient");
}
private static abstract class NoopContentProvider extends ContentProvider {
@Override

View File

@@ -19,7 +19,7 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;

View File

@@ -1,9 +1,9 @@
package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.database.Cursor
import androidx.core.database.getStringOrNull
import net.sqlcipher.Cursor
import net.sqlcipher.database.SQLiteDatabase
import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.session.libsignal.utilities.Base64
fun <T> SQLiteDatabase.get(table: String, query: String?, arguments: Array<String>?, get: (Cursor) -> T): T? {

View File

@@ -6,7 +6,7 @@ import android.database.Cursor;
import android.net.Uri;
import androidx.annotation.Nullable;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;

View File

@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.database;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
@@ -12,7 +11,7 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.jetbrains.annotations.NotNull;
import org.session.libsession.utilities.Address;
@@ -319,6 +318,19 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
notifyConversationListListeners();
}
public boolean hasDownloadedProfilePicture(String groupId) {
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{AVATAR}, GROUP_ID + " = ?",
new String[] {groupId},
null, null, null))
{
if (cursor != null && cursor.moveToNext()) {
return !cursor.isNull(0);
}
return false;
}
}
public void updateMembers(String groupId, List<Address> members) {
Collections.sort(members);

View File

@@ -51,31 +51,31 @@ class GroupMemberDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
return mappings.map { it.role }
}
fun addGroupMember(member: GroupMember) {
fun setGroupMembers(members: List<GroupMember>) {
writableDatabase.beginTransaction()
try {
val values = ContentValues().apply {
put(GROUP_ID, member.groupId)
put(PROFILE_ID, member.profileId)
put(ROLE, member.role.name)
val grouped = members.groupBy { it.role }
grouped.forEach { (role, members) ->
if (members.isEmpty()) return@forEach
val toDeleteQuery = "$GROUP_ID = ? AND $ROLE = ?"
val toDeleteArgs = arrayOf(members.first().groupId, role.name)
writableDatabase.delete(TABLE_NAME, toDeleteQuery, toDeleteArgs)
members.forEach { member ->
val values = ContentValues().apply {
put(GROUP_ID, member.groupId)
put(PROFILE_ID, member.profileId)
put(ROLE, member.role.name)
}
val query = "$GROUP_ID = ? AND $PROFILE_ID = ?"
val args = arrayOf(member.groupId, member.profileId)
writableDatabase.insertOrUpdate(TABLE_NAME, values, query, args)
}
writableDatabase.setTransactionSuccessful()
}
val query = "$GROUP_ID = ? AND $PROFILE_ID = ?"
val args = arrayOf(member.groupId, member.profileId)
writableDatabase.insertOrUpdate(TABLE_NAME, values, query, args)
writableDatabase.setTransactionSuccessful()
} finally {
writableDatabase.endTransaction()
}
}
fun clearGroupMemberRoles(groupId: String) {
writableDatabase.beginTransaction()
try {
val query = "$GROUP_ID = ?"
val args = arrayOf(groupId)
writableDatabase.delete(TABLE_NAME, query, args)
writableDatabase.setTransactionSuccessful()
} finally {
writableDatabase.endTransaction()
}

View File

@@ -1,14 +1,14 @@
package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.apache.commons.lang3.StringUtils;
import org.session.libsession.utilities.Address;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@@ -110,6 +110,11 @@ public class GroupReceiptDatabase extends Database {
db.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)});
}
void deleteRowsForMessages(long[] mmsIds) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, MMS_ID + " IN (?)", new String[] {StringUtils.join(mmsIds, ',')});
}
void deleteAllRows() {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, null, null);

View File

@@ -5,7 +5,7 @@ import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec;

View File

@@ -300,6 +300,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val lastHash = database.insertOrUpdate(lastMessageHashValueTable2, row, query, arrayOf( snode.toString(), publicKey, namespace.toString() ))
}
override fun clearAllLastMessageHashes() {
val database = databaseHelper.writableDatabase
database.delete(lastMessageHashValueTable2, null, null)
}
override fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set<String>? {
val database = databaseHelper.readableDatabase
val query = "${Companion.publicKey} = ? AND ${Companion.receivedMessageHashNamespace} = ?"
@@ -321,6 +326,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.insertOrUpdate(receivedMessageHashValuesTable, row, query, arrayOf( publicKey, namespace.toString() ))
}
override fun clearReceivedMessageHashValues() {
val database = databaseHelper.writableDatabase
database.delete(receivedMessageHashValuesTable, null, null)
}
override fun getAuthToken(server: String): String? {
val database = databaseHelper.readableDatabase
return database.get(openGroupAuthTokenTable, "${Companion.server} = ?", wrap(server)) { cursor ->
@@ -339,7 +349,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
}
override fun getLastMessageServerID(room: String, server: String): Long? {
val database = databaseHelper.writableDatabase
val database = databaseHelper.readableDatabase
val index = "$server.$room"
return database.get(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) { cursor ->
cursor.getInt(lastMessageServerID)
@@ -510,7 +520,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
}
fun getServerCapabilities(serverName: String): List<String> {
val database = databaseHelper.writableDatabase
val database = databaseHelper.readableDatabase
return database.get(serverCapabilitiesTable, "$server = ?", wrap(serverName)) { cursor ->
cursor.getString(capabilities)
}?.split(",") ?: emptyList()
@@ -523,7 +533,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
}
fun getLastInboxMessageId(serverName: String): Long? {
val database = databaseHelper.writableDatabase
val database = databaseHelper.readableDatabase
return database.get(lastInboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor ->
cursor.getInt(lastInboxMessageServerId)
}?.toLong()
@@ -540,7 +550,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
}
fun getLastOutboxMessageId(serverName: String): Long? {
val database = databaseHelper.writableDatabase
val database = databaseHelper.readableDatabase
return database.get(lastOutboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor ->
cursor.getInt(lastOutboxMessageServerId)
}?.toLong()

View File

@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import net.sqlcipher.database.SQLiteDatabase.CONFLICT_REPLACE
import net.zetetic.database.sqlcipher.SQLiteDatabase.CONFLICT_REPLACE
import org.session.libsignal.database.LokiMessageDatabaseProtocol
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
@@ -77,6 +77,25 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
database.endTransaction()
}
fun deleteMessages(messageIDs: List<Long>) {
val database = databaseHelper.writableDatabase
database.beginTransaction()
database.delete(
messageIDTable,
"${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})",
messageIDs.map { "$it" }.toTypedArray()
)
database.delete(
messageThreadMappingTable,
"${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})",
messageIDs.map { "$it" }.toTypedArray()
)
database.setTransactionSuccessful()
database.endTransaction()
}
/**
* @return pair of sms or mms table-specific ID and whether it is in SMS table
*/
@@ -96,6 +115,37 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
}
}
fun getMessageIDs(serverIDs: List<Long>, threadID: Long): Pair<List<Long>, List<Long>> {
val database = databaseHelper.readableDatabase
// Retrieve the message ids
val messageIdCursor = database
.rawQuery(
"""
SELECT ${messageThreadMappingTable}.${messageID}, ${messageIDTable}.${messageType}
FROM ${messageThreadMappingTable}
JOIN ${messageIDTable} ON ${messageIDTable}.message_id = ${messageThreadMappingTable}.${messageID}
WHERE (
${messageThreadMappingTable}.${Companion.threadID} = $threadID AND
${messageThreadMappingTable}.${Companion.serverID} IN (${serverIDs.joinToString(",")})
)
"""
)
val smsMessageIds: MutableList<Long> = mutableListOf()
val mmsMessageIds: MutableList<Long> = mutableListOf()
while (messageIdCursor.moveToNext()) {
if (messageIdCursor.getInt(1) == SMS_TYPE) {
smsMessageIds.add(messageIdCursor.getLong(0))
}
else {
mmsMessageIds.add(messageIdCursor.getLong(0))
}
}
return Pair(smsMessageIds, mmsMessageIds)
}
override fun setServerID(messageID: Long, serverID: Long, isSms: Boolean) {
val database = databaseHelper.writableDatabase
val contentValues = ContentValues(3)
@@ -136,6 +186,11 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
database.insertOrUpdate(errorMessageTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
}
fun clearErrorMessage(messageID: Long) {
val database = databaseHelper.writableDatabase
database.delete(errorMessageTable, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
}
fun deleteThread(threadId: Long) {
val database = databaseHelper.writableDatabase
try {
@@ -178,6 +233,15 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
database.delete(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
}
fun deleteMessageServerHashes(messageIDs: List<Long>) {
val database = databaseHelper.writableDatabase
database.delete(
messageHashTable,
"${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})",
messageIDs.map { "$it" }.toTypedArray()
)
}
fun migrateThreadId(legacyThreadId: Long, newThreadId: Long) {
val database = databaseHelper.writableDatabase
val contentValues = ContentValues(1)

View File

@@ -7,7 +7,7 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.utilities.Address;

View File

@@ -5,7 +5,7 @@ import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.Document;
@@ -42,6 +42,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
public abstract void markAsDeleted(long messageId, boolean read);
public abstract boolean deleteMessage(long messageId);
public abstract boolean deleteMessages(long[] messageId, long threadId);
public abstract void updateThreadId(long fromId, long toId);

View File

@@ -995,6 +995,23 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
return threadDeleted
}
override fun deleteMessages(messageIds: LongArray, threadId: Long): Boolean {
val attachmentDatabase = get(context).attachmentDatabase()
val groupReceiptDatabase = get(context).groupReceiptDatabase()
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) })
groupReceiptDatabase.deleteRowsForMessages(messageIds)
val database = databaseHelper.writableDatabase
database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(",")))
val threadDeleted = get(context).threadDatabase().update(threadId, false)
notifyConversationListeners(threadId)
notifyStickerListeners()
notifyStickerPackListeners()
return threadDeleted
}
override fun updateThreadId(fromId: Long, toId: Long) {
val contentValues = ContentValues(1)
contentValues.put(THREAD_ID, toId)

View File

@@ -22,8 +22,8 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteQueryBuilder;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteQueryBuilder;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.Util;
@@ -112,6 +112,64 @@ public class MmsSmsDatabase extends Database {
return getMessageFor(timestamp, author.serialize());
}
public long getPreviousPage(long threadId, long fromTime, int limit) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" ASC";
String selection = MmsSmsColumns.THREAD_ID+" = "+threadId
+ " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > "+fromTime;
String limitStr = ""+limit;
long sent = -1;
Cursor cursor = queryTables(PROJECTION, selection, order, limitStr);
if (cursor == null) return sent;
Reader reader = readerFor(cursor);
if (!cursor.move(limit)) {
cursor.moveToLast();
}
MessageRecord record = reader.getCurrent();
sent = record.getDateSent();
reader.close();
return sent;
}
public Cursor getConversationPage(long threadId, long fromTime, long toTime, int limit) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC";
String selection = MmsSmsColumns.THREAD_ID + " = "+threadId
+ " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" <= " + fromTime;
String limitStr = null;
if (toTime != -1L) {
selection += " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > "+toTime;
} else {
limitStr = ""+limit;
}
return queryTables(PROJECTION, selection, order, limitStr);
}
public boolean hasNextPage(long threadId, long toTime) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC";
String selection = MmsSmsColumns.THREAD_ID + " = "+threadId
+ " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" < " + toTime; // check if there's at least one message before the `toTime`
Cursor cursor = queryTables(PROJECTION, selection, order, null);
boolean hasNext = false;
if (cursor != null) {
hasNext = cursor.getCount() > 0;
cursor.close();
}
return hasNext;
}
public boolean hasPreviousPage(long threadId, long fromTime) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC";
String selection = MmsSmsColumns.THREAD_ID + " = "+threadId
+ " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > " + fromTime; // check if there's at least one message after the `fromTime`
Cursor cursor = queryTables(PROJECTION, selection, order, null);
boolean hasNext = false;
if (cursor != null) {
hasNext = cursor.getCount() > 0;
cursor.close();
}
return hasNext;
}
public Cursor getConversation(long threadId, boolean reverse, long offset, long limit) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + (reverse ? " DESC" : " ASC");
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
@@ -199,16 +257,16 @@ public class MmsSmsDatabase extends Database {
return -1;
}
public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull Address address) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
public int getMessagePositionInConversation(long threadId, long sentTimestamp, @NonNull Address address) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsSmsColumns.ADDRESS }, selection, order, null)) {
try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.ADDRESS }, selection, order, null)) {
String serializedAddress = address.serialize();
boolean isOwnNumber = Util.isOwnNumber(context, address.serialize());
while (cursor != null && cursor.moveToNext()) {
boolean timestampMatches = cursor.getLong(0) == receivedTimestamp;
boolean timestampMatches = cursor.getLong(0) == sentTimestamp;
boolean addressMatches = serializedAddress.equals(cursor.getString(1));
if (timestampMatches && (addressMatches || isOwnNumber)) {

View File

@@ -6,7 +6,7 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
import org.session.libsignal.utilities.Log;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.session.libsignal.utilities.Base64;

View File

@@ -48,6 +48,14 @@ class ReactionDatabase(context: Context, helper: SQLCipherOpenHelper) : Database
)
""".trimIndent()
@JvmField
val CREATE_INDEXS = arrayOf(
"CREATE INDEX IF NOT EXISTS reaction_message_id_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.MESSAGE_ID + ");",
"CREATE INDEX IF NOT EXISTS reaction_is_mms_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.IS_MMS + ");",
"CREATE INDEX IF NOT EXISTS reaction_message_id_is_mms_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.MESSAGE_ID + ", " + ReactionDatabase.IS_MMS + ");",
"CREATE INDEX IF NOT EXISTS reaction_sort_id_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.SORT_ID + ");",
)
@JvmField
val CREATE_REACTION_TRIGGERS = arrayOf(
"""

View File

@@ -11,7 +11,7 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.MaterialColor;
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class RecipientDatabase extends Database {
@@ -232,6 +233,7 @@ public class RecipientDatabase extends Database {
values.put(COLOR, color.serialize());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setColor(color);
notifyRecipientListeners();
}
public void setDefaultSubscriptionId(@NonNull Recipient recipient, int defaultSubscriptionId) {
@@ -239,6 +241,7 @@ public class RecipientDatabase extends Database {
values.put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setDefaultSubscriptionId(Optional.of(defaultSubscriptionId));
notifyRecipientListeners();
}
public void setForceSmsSelection(@NonNull Recipient recipient, boolean forceSmsSelection) {
@@ -246,6 +249,7 @@ public class RecipientDatabase extends Database {
contentValues.put(FORCE_SMS_SELECTION, forceSmsSelection ? 1 : 0);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setForceSmsSelection(forceSmsSelection);
notifyRecipientListeners();
}
public void setApproved(@NonNull Recipient recipient, boolean approved) {
@@ -253,6 +257,7 @@ public class RecipientDatabase extends Database {
values.put(APPROVED, approved ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setApproved(approved);
notifyRecipientListeners();
}
public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) {
@@ -260,6 +265,7 @@ public class RecipientDatabase extends Database {
values.put(APPROVED_ME, approvedMe ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setHasApprovedMe(approvedMe);
notifyRecipientListeners();
}
public void setBlocked(@NonNull Recipient recipient, boolean blocked) {
@@ -267,6 +273,24 @@ public class RecipientDatabase extends Database {
values.put(BLOCK, blocked ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setBlocked(blocked);
notifyRecipientListeners();
}
public void setBlocked(@NonNull List<Recipient> recipients, boolean blocked) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
try {
ContentValues values = new ContentValues();
values.put(BLOCK, blocked ? 1 : 0);
for (Recipient recipient : recipients) {
db.update(TABLE_NAME, values, ADDRESS + " = ?", new String[]{recipient.getAddress().serialize()});
recipient.resolve().setBlocked(blocked);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
notifyRecipientListeners();
}
public void setMuted(@NonNull Recipient recipient, long until) {
@@ -274,6 +298,7 @@ public class RecipientDatabase extends Database {
values.put(MUTE_UNTIL, until);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setMuted(until);
notifyRecipientListeners();
}
/**
@@ -287,6 +312,7 @@ public class RecipientDatabase extends Database {
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setNotifyType(notifyType);
notifyConversationListListeners();
notifyRecipientListeners();
}
public void setExpireMessages(@NonNull Recipient recipient, int expiration) {
@@ -296,6 +322,7 @@ public class RecipientDatabase extends Database {
values.put(EXPIRE_MESSAGES, expiration);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setExpireMessages(expiration);
notifyRecipientListeners();
}
public void setUnidentifiedAccessMode(@NonNull Recipient recipient, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) {
@@ -303,6 +330,7 @@ public class RecipientDatabase extends Database {
values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode);
notifyRecipientListeners();
}
public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) {
@@ -310,6 +338,7 @@ public class RecipientDatabase extends Database {
values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey));
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setProfileKey(profileKey);
notifyRecipientListeners();
}
public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) {
@@ -317,6 +346,7 @@ public class RecipientDatabase extends Database {
contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setProfileAvatar(profileAvatar);
notifyRecipientListeners();
}
public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) {
@@ -325,6 +355,7 @@ public class RecipientDatabase extends Database {
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setName(profileName);
recipient.resolve().setProfileName(profileName);
notifyRecipientListeners();
}
public void setProfileSharing(@NonNull Recipient recipient, boolean enabled) {
@@ -332,6 +363,7 @@ public class RecipientDatabase extends Database {
contentValues.put(PROFILE_SHARING, enabled ? 1 : 0);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.setProfileSharing(enabled);
notifyRecipientListeners();
}
public void setNotificationChannel(@NonNull Recipient recipient, @Nullable String notificationChannel) {
@@ -339,6 +371,7 @@ public class RecipientDatabase extends Database {
contentValues.put(NOTIFICATION_CHANNEL, notificationChannel);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.setNotificationChannel(notificationChannel);
notifyRecipientListeners();
}
public void setRegistered(@NonNull Recipient recipient, RegisteredState registeredState) {
@@ -346,6 +379,7 @@ public class RecipientDatabase extends Database {
contentValues.put(REGISTERED, registeredState.getId());
updateOrInsert(recipient.getAddress(), contentValues);
recipient.setRegistered(registeredState);
notifyRecipientListeners();
}
private void updateOrInsert(Address address, ContentValues contentValues) {
@@ -365,6 +399,22 @@ public class RecipientDatabase extends Database {
database.endTransaction();
}
public List<Recipient> getBlockedContacts() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, BLOCK + " = 1",
null, null, null, null, null);
RecipientReader reader = new RecipientReader(context, cursor);
List<Recipient> returnList = new ArrayList<>();
Recipient current;
while ((current = reader.getNext()) != null) {
returnList.add(current);
}
reader.close();
return returnList;
}
public static class RecipientReader implements Closeable {
private final Context context;

View File

@@ -1,13 +1,13 @@
package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import net.sqlcipher.Cursor;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.session.libsession.utilities.Util;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@@ -63,7 +63,7 @@ public class SearchDatabase extends Database {
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " +
SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
"FROM " + SmsDatabase.TABLE_NAME + " " +
"INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " +
@@ -74,13 +74,13 @@ public class SearchDatabase extends Database {
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " +
MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
"FROM " + MmsDatabase.TABLE_NAME + " " +
"INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " +
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
"WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? " +
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " +
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC " +
"LIMIT ?";
private static final String MESSAGES_FOR_THREAD_QUERY =
@@ -88,7 +88,7 @@ public class SearchDatabase extends Database {
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " +
SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
"FROM " + SmsDatabase.TABLE_NAME + " " +
"INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " +
@@ -99,13 +99,13 @@ public class SearchDatabase extends Database {
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " +
MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
"FROM " + MmsDatabase.TABLE_NAME + " " +
"INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " +
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
"WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? AND " + MmsDatabase.TABLE_NAME + "." + MmsSmsColumns.THREAD_ID + " = ? " +
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " +
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC " +
"LIMIT 500";
public SearchDatabase(@NonNull Context context, @NonNull SQLCipherOpenHelper databaseHelper) {

View File

@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import androidx.core.database.getStringOrNull
import net.sqlcipher.Cursor
import android.database.Cursor
import org.session.libsession.messaging.contacts.Contact
import org.session.libsignal.utilities.Base64
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
@@ -75,21 +75,6 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da
}
fun contactFromCursor(cursor: Cursor): Contact {
val sessionID = cursor.getString(sessionID)
val contact = Contact(sessionID)
contact.name = cursor.getStringOrNull(name)
contact.nickname = cursor.getStringOrNull(nickname)
contact.profilePictureURL = cursor.getStringOrNull(profilePictureURL)
contact.profilePictureFileName = cursor.getStringOrNull(profilePictureFileName)
cursor.getStringOrNull(profilePictureEncryptionKey)?.let {
contact.profilePictureEncryptionKey = Base64.decode(it)
}
contact.threadID = cursor.getLong(threadID)
contact.isTrusted = cursor.getInt(isTrusted) != 0
return contact
}
fun contactFromCursor(cursor: android.database.Cursor): Contact {
val sessionID = cursor.getString(cursor.getColumnIndexOrThrow(sessionID))
val contact = Contact(sessionID)
contact.name = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(name))

View File

@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import net.sqlcipher.Cursor
import android.database.Cursor
import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob

View File

@@ -28,9 +28,10 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteStatement;
import org.apache.commons.lang3.StringUtils;
import org.session.libsession.messaging.calls.CallMessageType;
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage;
import org.session.libsession.messaging.messages.signal.IncomingTextMessage;
@@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -596,6 +598,30 @@ public class SmsDatabase extends MessagingDatabase {
return threadDeleted;
}
@Override
public boolean deleteMessages(long[] messageIds, long threadId) {
String[] argsArray = new String[messageIds.length];
String[] argValues = new String[messageIds.length];
Arrays.fill(argsArray, "?");
for (int i = 0; i < messageIds.length; i++) {
argValues[i] = (messageIds[i] + "");
}
String combinedMessageIdArgss = StringUtils.join(messageIds, ',');
String combinedMessageIds = StringUtils.join(messageIds, ',');
Log.i("MessageDatabase", "Deleting: " + combinedMessageIds);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(
TABLE_NAME,
ID + " IN (" + StringUtils.join(argsArray, ',') + ")",
argValues
);
boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false);
notifyConversationListeners(threadId);
return threadDeleted;
}
@Override
public void updateThreadId(long fromId, long toId) {
ContentValues contentValues = new ContentValues(1);

View File

@@ -7,28 +7,18 @@ import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.BlindedIdMapping
import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.jobs.*
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.MessageRequestResponse
import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage
import org.session.libsession.messaging.messages.signal.IncomingTextMessage
import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.messages.signal.*
import org.session.libsession.messaging.messages.visible.Attachment
import org.session.libsession.messaging.messages.visible.Profile
import org.session.libsession.messaging.messages.visible.Reaction
import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.open_groups.GroupMember
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
@@ -38,7 +28,7 @@ import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.*
import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.GroupRecord
import org.session.libsession.utilities.GroupUtil
@@ -318,12 +308,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return getAllOpenGroups().values.firstOrNull { it.server == server && it.room == room }
}
override fun clearGroupMemberRoles(groupId: String) {
DatabaseComponent.get(context).groupMemberDatabase().clearGroupMemberRoles(groupId)
}
override fun addGroupMemberRole(member: GroupMember) {
DatabaseComponent.get(context).groupMemberDatabase().addGroupMember(member)
override fun setGroupMemberRoles(members: List<GroupMember>) {
DatabaseComponent.get(context).groupMemberDatabase().setGroupMembers(members)
}
override fun isDuplicateMessage(timestamp: Long): Boolean {
@@ -338,6 +324,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).groupDatabase().updateProfilePicture(groupID, newValue)
}
override fun hasDownloadedProfilePicture(groupID: String): Boolean {
return DatabaseComponent.get(context).groupDatabase().hasDownloadedProfilePicture(groupID)
}
override fun getReceivedMessageTimestamps(): Set<Long> {
return SessionMetaProtocol.getTimestamps()
}
@@ -431,6 +421,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
}
override fun clearErrorMessage(messageID: Long) {
val db = DatabaseComponent.get(context).lokiMessageDatabase()
db.clearErrorMessage(messageID)
}
override fun setMessageServerHash(messageID: Long, serverHash: String) {
DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(messageID, serverHash)
}
@@ -565,8 +560,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return DatabaseComponent.get(context).groupDatabase().allGroups
}
override fun addOpenGroup(urlAsString: String) {
OpenGroupManager.addOpenGroup(urlAsString, context)
override fun addOpenGroup(urlAsString: String): OpenGroupApi.RoomInfo? {
return OpenGroupManager.addOpenGroup(urlAsString, context)
}
override fun onOpenGroupAdded(server: String) {
@@ -673,7 +668,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val threadId = threadDatabase.getOrCreateThreadIdFor(recipient)
if (contact.didApproveMe == true) {
recipientDatabase.setApprovedMe(recipient, true)
threadDatabase.setHasSent(threadId, true)
}
if (contact.isApproved == true) {
recipientDatabase.setApproved(recipient, true)
@@ -974,4 +968,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms))
}
override fun unblock(toUnblock: List<Recipient>) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
recipientDb.setBlocked(toUnblock, false)
}
override fun blockedContacts(): List<Recipient> {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
return recipientDb.blockedContacts
}
}

View File

@@ -32,7 +32,7 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.jetbrains.annotations.NotNull;
import org.session.libsession.utilities.Address;
@@ -502,15 +502,23 @@ public class ThreadDatabase extends Database {
return db.rawQuery(query, null);
}
public void setLastSeen(long threadId) {
public void setLastSeen(long threadId, long timestamp) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1);
contentValues.put(LAST_SEEN, System.currentTimeMillis());
if (timestamp == -1) {
contentValues.put(LAST_SEEN, System.currentTimeMillis());
} else {
contentValues.put(LAST_SEEN, timestamp);
}
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)});
notifyConversationListListeners();
}
public void setLastSeen(long threadId) {
setLastSeen(threadId, -1);
}
public Pair<Long, Boolean> getLastSeenAndHasSent(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN, HAS_SENT}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);

View File

@@ -1,14 +1,18 @@
package org.thoughtcrime.securesms.database.helpers;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteDatabaseHook;
import net.sqlcipher.database.SQLiteOpenHelper;
import net.zetetic.database.sqlcipher.SQLiteConnection;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabaseHook;
import net.zetetic.database.sqlcipher.SQLiteException;
import net.zetetic.database.sqlcipher.SQLiteOpenHelper;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.utilities.Log;
@@ -35,6 +39,11 @@ import org.thoughtcrime.securesms.database.SessionContactDatabase;
import org.thoughtcrime.securesms.database.SessionJobDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import java.io.File;
import network.loki.messenger.R;
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
@@ -75,40 +84,157 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV36 = 57;
private static final int lokiV37 = 58;
private static final int lokiV38 = 59;
private static final int lokiV39 = 60;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV38;
private static final String DATABASE_NAME = "signal.db";
private static final int DATABASE_VERSION = lokiV39;
private static final int MIN_DATABASE_VERSION = lokiV7;
private static final String CIPHER3_DATABASE_NAME = "signal.db";
public static final String DATABASE_NAME = "signal_v4.db";
private final Context context;
private final DatabaseSecret databaseSecret;
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, MIN_DATABASE_VERSION, null, new SQLiteDatabaseHook() {
@Override
public void preKey(SQLiteDatabase db) {
db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;");
db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;");
public void preKey(SQLiteConnection connection) {
SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
}
@Override
public void postKey(SQLiteDatabase db) {
db.rawExecSQL("PRAGMA kdf_iter = '1';");
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
public void postKey(SQLiteConnection connection) {
SQLCipherOpenHelper.applySQLCipherPragmas(connection, true);
// if not vacuumed in a while, perform that operation
long currentTime = System.currentTimeMillis();
// 7 days
if (currentTime - TextSecurePreferences.getLastVacuumTime(context) > 604_800_000) {
db.rawExecSQL("VACUUM;");
connection.execute("VACUUM;", null, null);
TextSecurePreferences.setLastVacuumNow(context);
}
}
});
}, true);
this.context = context.getApplicationContext();
this.databaseSecret = databaseSecret;
}
private static void applySQLCipherPragmas(SQLiteConnection connection, boolean useSQLCipher4) {
if (useSQLCipher4) {
connection.execute("PRAGMA kdf_iter = '256000';", null, null);
}
else {
connection.execute("PRAGMA cipher_compatibility = 3;", null, null);
connection.execute("PRAGMA kdf_iter = '1';", null, null);
}
connection.execute("PRAGMA cipher_page_size = 4096;", null, null);
}
private static SQLiteDatabase open(String path, DatabaseSecret databaseSecret, boolean useSQLCipher4) throws SQLiteException {
return SQLiteDatabase.openDatabase(path, databaseSecret.asString(), null, SQLiteDatabase.OPEN_READWRITE, new SQLiteDatabaseHook() {
@Override
public void preKey(SQLiteConnection connection) { SQLCipherOpenHelper.applySQLCipherPragmas(connection, useSQLCipher4); }
@Override
public void postKey(SQLiteConnection connection) { SQLCipherOpenHelper.applySQLCipherPragmas(connection, useSQLCipher4); }
});
}
public static void migrateSqlCipher3To4IfNeeded(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) throws Exception {
String oldDbPath = context.getDatabasePath(CIPHER3_DATABASE_NAME).getPath();
File oldDbFile = new File(oldDbPath);
// If the old SQLCipher3 database file doesn't exist then no need to do anything
if (!oldDbFile.exists()) { return; }
try {
// Define the location for the new database
String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath();
File newDbFile = new File(newDbPath);
// If the new database file already exists then check if it's valid first, if it's in an
// invalid state we should delete it and try to migrate again
if (newDbFile.exists()) {
// If the old database hasn't been modified since the new database was created, then we can
// assume the user hasn't downgraded for some reason and made changes to the old database and
// can remove the old database file (it won't be used anymore)
if (oldDbFile.lastModified() <= newDbFile.lastModified()) {
// TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past
// //noinspection ResultOfMethodCallIgnored
// oldDbFile.delete();
return;
}
// If the old database does have newer changes then the new database could have stale/invalid
// data and we should re-migrate to avoid losing any data or issues
if (!newDbFile.delete()) {
throw new Exception("Failed to remove invalid new database");
}
}
if (!newDbFile.createNewFile()) {
throw new Exception("Failed to create new database");
}
// Open the old database and extract it's version
SQLiteDatabase oldDb = SQLCipherOpenHelper.open(oldDbPath, databaseSecret, false);
int oldDbVersion = oldDb.getVersion();
// Export the old database to the new one (will have the default 'kdf_iter' and 'page_size' settings)
oldDb.rawExecSQL(
String.format("ATTACH DATABASE '%s' AS sqlcipher4 KEY '%s'", newDbPath, databaseSecret.asString())
);
Cursor cursor = oldDb.rawQuery("SELECT sqlcipher_export('sqlcipher4')");
cursor.moveToLast();
cursor.close();
oldDb.rawExecSQL("DETACH DATABASE sqlcipher4");
oldDb.close();
// Open the newly migrated database (to ensure it works) and set it's version so we don't try
// to run any of our custom migrations
SQLiteDatabase newDb = SQLCipherOpenHelper.open(newDbPath, databaseSecret, true);
newDb.setVersion(oldDbVersion);
newDb.close();
// TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past
// Remove the old database file since it will no longer be used
// //noinspection ResultOfMethodCallIgnored
// oldDbFile.delete();
}
catch (Exception e) {
Log.e(TAG, "Migration from SQLCipher3 to SQLCipher4 failed", e);
// Notify the user of the issue so they know they can downgrade until the issue is fixed
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
String channelId = context.getString(R.string.NotificationChannel_failures);
if (NotificationChannels.supported()) {
NotificationChannel channel = new NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH);
channel.enableVibration(true);
notificationManager.createNotificationChannel(channel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setColor(context.getResources().getColor(R.color.textsecure_primary))
.setCategory(NotificationCompat.CATEGORY_ERROR)
.setContentTitle(context.getString(R.string.ErrorNotifier_migration))
.setContentText(context.getString(R.string.ErrorNotifier_migration_downgrade))
.setAutoCancel(true);
if (!NotificationChannels.supported()) {
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
}
notificationManager.notify(5874, builder.build());
// Throw the error (app will crash but there is nothing else we can do unfortunately)
throw e;
}
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SmsDatabase.CREATE_TABLE);
@@ -188,6 +314,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
executeStatements(db, DraftDatabase.CREATE_INDEXS);
executeStatements(db, GroupDatabase.CREATE_INDEXS);
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
executeStatements(db, ReactionDatabase.CREATE_INDEXS);
executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS);
}
@@ -195,9 +322,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
// Loki - Enable write ahead logging mode and increase the cache size.
// This should be disabled if we ever run into serious race condition bugs.
db.enableWriteAheadLogging();
db.execSQL("PRAGMA cache_size = 10000");
}
@@ -414,20 +539,16 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND);
}
if (oldVersion < lokiV39) {
executeStatements(db, ReactionDatabase.CREATE_INDEXS);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
public SQLiteDatabase getReadableDatabase() {
return getReadableDatabase(databaseSecret.asString());
}
public SQLiteDatabase getWritableDatabase() {
return getWritableDatabase(databaseSecret.asString());
}
public void markCurrent(SQLiteDatabase db) {
db.setVersion(DATABASE_VERSION);
}

View File

@@ -33,6 +33,7 @@ import org.session.libsession.utilities.NetworkFailure;
import org.session.libsession.utilities.recipients.Recipient;
import java.util.List;
import java.util.Objects;
/**
* The base class for message record models that are displayed in
@@ -140,14 +141,16 @@ public abstract class MessageRecord extends DisplayRecord {
return spannable;
}
@Override
public boolean equals(Object other) {
return other instanceof MessageRecord
&& ((MessageRecord) other).getId() == getId()
&& ((MessageRecord) other).isMms() == isMms();
&& ((MessageRecord) other).getId() == getId()
&& ((MessageRecord) other).isMms() == isMms();
}
@Override
public int hashCode() {
return (int)getId();
return Objects.hash(id, isMms());
}
public @NonNull List<ReactionRecord> getReactions() {

View File

@@ -2,13 +2,15 @@ package org.thoughtcrime.securesms.database.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.session.libsession.utilities.Contact;
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsession.utilities.Contact;
import org.session.libsession.utilities.IdentityKeyMismatch;
import org.session.libsession.utilities.NetworkFailure;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import java.util.LinkedList;
import java.util.List;

View File

@@ -8,6 +8,8 @@ import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.utilities.Address;
import org.thoughtcrime.securesms.mms.SlideDeck;
import java.util.Objects;
public class Quote {
private final long id;
@@ -47,4 +49,17 @@ public class Quote {
public QuoteModel getQuoteModel() {
return new QuoteModel(id, author, text, missing, attachment.asAttachments());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Quote quote = (Quote) o;
return id == quote.id && missing == quote.missing && Objects.equals(author, quote.author) && Objects.equals(text, quote.text) && Objects.equals(attachment, quote.attachment);
}
@Override
public int hashCode() {
return Objects.hash(id, author, text, missing, attachment);
}
}

View File

@@ -65,7 +65,7 @@ public class ThreadRecord extends DisplayRecord {
this.archived = archived;
this.expiresIn = expiresIn;
this.lastSeen = lastSeen;
this.pinned = pinned;
this.pinned = pinned;
}
public @Nullable Uri getSnippetUri() {