Save receipt timestamps on sms/mms database.

This commit is contained in:
Lucio Maciel 2021-08-26 16:33:13 -03:00 committed by Greyson Parrelli
parent 3dc1ce3353
commit 0273d0f285
5 changed files with 74 additions and 35 deletions

View file

@ -13,33 +13,53 @@ public class EarlyReceiptCache {
private static final String TAG = Log.tag(EarlyReceiptCache.class);
private final LRUCache<Long, Map<RecipientId, Long>> cache = new LRUCache<>(100);
private final LRUCache<Long, Map<RecipientId, Receipt>> cache = new LRUCache<>(100);
private final String name;
public EarlyReceiptCache(@NonNull String name) {
this.name = name;
}
public synchronized void increment(long timestamp, @NonNull RecipientId origin) {
Map<RecipientId, Long> receipts = cache.get(timestamp);
public synchronized void increment(long timestamp, @NonNull RecipientId origin, long receiptTimestamp) {
Map<RecipientId, Receipt> receipts = cache.get(timestamp);
if (receipts == null) {
receipts = new HashMap<>();
}
Long count = receipts.get(origin);
Receipt receipt = receipts.get(origin);
if (count != null) {
receipts.put(origin, ++count);
if (receipt != null) {
receipt.count++;
receipt.timestamp = receiptTimestamp;
} else {
receipts.put(origin, 1L);
receipt = new Receipt(1, receiptTimestamp);
}
receipts.put(origin, receipt);
cache.put(timestamp, receipts);
}
public synchronized Map<RecipientId, Long> remove(long timestamp) {
Map<RecipientId, Long> receipts = cache.remove(timestamp);
public synchronized Map<RecipientId, Receipt> remove(long timestamp) {
Map<RecipientId, Receipt> receipts = cache.remove(timestamp);
return receipts != null ? receipts : new HashMap<>();
}
public class Receipt {
private long count;
private long timestamp;
private Receipt(long count, long timestamp) {
this.count = count;
this.timestamp = timestamp;
}
public long getCount() {
return count;
}
public long getTimestamp() {
return timestamp;
}
}
}

View file

@ -170,7 +170,8 @@ public class MmsDatabase extends MessageDatabase {
MENTIONS_SELF + " INTEGER DEFAULT 0, " +
NOTIFIED_TIMESTAMP + " INTEGER DEFAULT 0, " +
VIEWED_RECEIPT_COUNT + " INTEGER DEFAULT 0, " +
SERVER_GUID + " TEXT DEFAULT NULL);";
SERVER_GUID + " TEXT DEFAULT NULL, "+
RECEIPT_TIMESTAMP + " INTEGER DEFAULT -1);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_read_and_notified_and_thread_id_index ON " + TABLE_NAME + "(" + READ + "," + NOTIFIED + "," + THREAD_ID + ");",
@ -617,25 +618,29 @@ public class MmsDatabase extends MessageDatabase {
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
Set<ThreadUpdate> threadUpdates = new HashSet<>();
try (Cursor cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, RECIPIENT_ID, receiptType.getColumnName()},
try (Cursor cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, RECIPIENT_ID, receiptType.getColumnName(), RECEIPT_TIMESTAMP},
DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())},
null, null, null, null))
{
while (cursor.moveToNext()) {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)))) {
RecipientId theirRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)));
if (Types.isOutgoingMessageType(CursorUtil.requireLong(cursor, MESSAGE_BOX))) {
RecipientId theirRecipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID));
RecipientId ourRecipientId = messageId.getRecipientId();
String columnName = receiptType.getColumnName();
if (ourRecipientId.equals(theirRecipientId) || Recipient.resolved(theirRecipientId).isGroup()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long id = CursorUtil.requireLong(cursor, ID);
long threadId = CursorUtil.requireLong(cursor, THREAD_ID);
int status = receiptType.getGroupStatus();
boolean isFirstIncrement = cursor.getLong(cursor.getColumnIndexOrThrow(columnName)) == 0;
boolean isFirstIncrement = CursorUtil.requireLong(cursor, columnName) == 0;
long savedTimestamp = CursorUtil.requireLong(cursor, RECEIPT_TIMESTAMP);
long updatedTimestamp = isFirstIncrement ? Math.max(savedTimestamp, timestamp) : savedTimestamp;
database.execSQL("UPDATE " + TABLE_NAME + " SET " +
columnName + " = " + columnName + " + 1 WHERE " + ID + " = ?",
new String[] {String.valueOf(id)});
columnName + " = " + columnName + " + 1, " +
RECEIPT_TIMESTAMP + " = ? WHERE " +
ID + " = ?",
SqlUtil.buildArgs(updatedTimestamp, id));
DatabaseFactory.getGroupReceiptDatabase(context).update(ourRecipientId, id, status, timestamp);
@ -645,7 +650,7 @@ public class MmsDatabase extends MessageDatabase {
}
if (threadUpdates.size() > 0 && receiptType == ReceiptType.DELIVERY) {
earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId());
earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId(), timestamp);
}
return threadUpdates;
@ -1484,7 +1489,7 @@ public class MmsDatabase extends MessageDatabase {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
Map<RecipientId, Long> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(message.getSentTimeMillis());
Map<RecipientId, EarlyReceiptCache.Receipt> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(message.getSentTimeMillis());
ContentValues contentValues = new ContentValues();
contentValues.put(DATE_SENT, message.getSentTimeMillis());
@ -1498,7 +1503,8 @@ public class MmsDatabase extends MessageDatabase {
contentValues.put(EXPIRES_IN, message.getExpiresIn());
contentValues.put(VIEW_ONCE, message.isViewOnce());
contentValues.put(RECIPIENT_ID, message.getRecipient().getId().serialize());
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(EarlyReceiptCache.Receipt::getCount).sum());
contentValues.put(RECEIPT_TIMESTAMP, Stream.of(earlyDeliveryReceipts.values()).mapToLong(EarlyReceiptCache.Receipt::getTimestamp).max().orElse(-1));
if (message.getRecipient().isSelf() && hasAudioAttachment(message.getAttachments())) {
contentValues.put(VIEWED_RECEIPT_COUNT, 1L);

View file

@ -29,6 +29,7 @@ public interface MmsSmsColumns {
public static final String REACTIONS_LAST_SEEN = "reactions_last_seen";
public static final String REMOTE_DELETED = "remote_deleted";
public static final String SERVER_GUID = "server_guid";
public static final String RECEIPT_TIMESTAMP = "receipt_timestamp";
/**
* For storage efficiency, all types are stored within a single 64-bit integer column in the

View file

@ -125,8 +125,9 @@ public class SmsDatabase extends MessageDatabase {
REACTIONS_UNREAD + " INTEGER DEFAULT 0, " +
REACTIONS_LAST_SEEN + " INTEGER DEFAULT -1, " +
REMOTE_DELETED + " INTEGER DEFAULT 0, " +
NOTIFIED_TIMESTAMP + " INTEGER DEFAULT 0," +
SERVER_GUID + " TEXT DEFAULT NULL);";
NOTIFIED_TIMESTAMP + " INTEGER DEFAULT 0, " +
SERVER_GUID + " TEXT DEFAULT NULL, " +
RECEIPT_TIMESTAMP + " INTEGER DEFAULT -1);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS sms_read_and_notified_and_thread_id_index ON " + TABLE_NAME + "(" + READ + "," + NOTIFIED + "," + THREAD_ID + ");",
@ -488,24 +489,28 @@ public class SmsDatabase extends MessageDatabase {
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
Set<ThreadUpdate> threadUpdates = new HashSet<>();
try (Cursor cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, RECIPIENT_ID, TYPE, DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT},
try (Cursor cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, RECIPIENT_ID, TYPE, DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, RECEIPT_TIMESTAMP},
DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())},
null, null, null, null)) {
while (cursor.moveToNext()) {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) {
if (Types.isOutgoingMessageType(CursorUtil.requireLong(cursor, TYPE))) {
RecipientId theirRecipientId = messageId.getRecipientId();
RecipientId outRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)));
RecipientId outRecipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID));
if (outRecipientId.equals(theirRecipientId)) {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long id = CursorUtil.requireLong(cursor, ID);
long threadId = CursorUtil.requireLong(cursor, THREAD_ID);
String columnName = receiptType.getColumnName();
boolean isFirstIncrement = cursor.getLong(cursor.getColumnIndexOrThrow(columnName)) == 0;
boolean isFirstIncrement = CursorUtil.requireLong(cursor, columnName) == 0;
long savedTimestamp = CursorUtil.requireLong(cursor, RECEIPT_TIMESTAMP);
long updatedTimestamp = isFirstIncrement ? Math.max(savedTimestamp, timestamp) : savedTimestamp;
database.execSQL("UPDATE " + TABLE_NAME +
" SET " + columnName + " = " + columnName + " + 1 WHERE " +
" SET " + columnName + " = " + columnName + " + 1, " +
RECEIPT_TIMESTAMP + " = ? WHERE " +
ID + " = ?",
new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))});
SqlUtil.buildArgs(updatedTimestamp, id));
threadUpdates.add(new ThreadUpdate(threadId, !isFirstIncrement));
}
@ -513,7 +518,7 @@ public class SmsDatabase extends MessageDatabase {
}
if (threadUpdates.size() > 0 && receiptType == ReceiptType.DELIVERY) {
earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId());
earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId(), timestamp);
}
return threadUpdates;
@ -1201,8 +1206,8 @@ public class SmsDatabase extends MessageDatabase {
if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT;
RecipientId recipientId = message.getRecipient().getId();
Map<RecipientId, Long> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date);
RecipientId recipientId = message.getRecipient().getId();
Map<RecipientId, EarlyReceiptCache.Receipt> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date);
ContentValues contentValues = new ContentValues(6);
contentValues.put(RECIPIENT_ID, recipientId.serialize());
@ -1214,7 +1219,8 @@ public class SmsDatabase extends MessageDatabase {
contentValues.put(TYPE, type);
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
contentValues.put(EXPIRES_IN, message.getExpiresIn());
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(EarlyReceiptCache.Receipt::getCount).sum());
contentValues.put(RECEIPT_TIMESTAMP, Stream.of(earlyDeliveryReceipts.values()).mapToLong(EarlyReceiptCache.Receipt::getTimestamp).max().orElse(-1));
long messageId = db.insert(TABLE_NAME, null, contentValues);

View file

@ -216,8 +216,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int IDENTITY_MIGRATION = 114;
private static final int GROUP_CALL_RING_TABLE = 115;
private static final int CLEANUP_SESSION_MIGRATION = 116;
private static final int RECEIPT_TIMESTAMP = 117;
private static final int DATABASE_VERSION = 116;
private static final int DATABASE_VERSION = 117;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -2040,6 +2041,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
Log.i(TAG, "Cleaned up " + storageCount + " storageIds.");
}
if (oldVersion < RECEIPT_TIMESTAMP) {
db.execSQL("ALTER TABLE sms ADD COLUMN receipt_timestamp INTEGER DEFAULT -1");
db.execSQL("ALTER TABLE mms ADD COLUMN receipt_timestamp INTEGER DEFAULT -1");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();