Skip to content

Commit

Permalink
Add support for syncing forced unread status.
Browse files Browse the repository at this point in the history
  • Loading branch information
greyson-signal committed Oct 7, 2020
1 parent ed0be6f commit 63746bb
Show file tree
Hide file tree
Showing 19 changed files with 274 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,20 @@ public RecipientReader getRecipientsWithNotificationChannels() {
return null;
}

public void markNeedsSync(@NonNull Collection<RecipientId> recipientIds) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();

db.beginTransaction();
try {
for (RecipientId recipientId : recipientIds) {
markDirty(recipientId, DirtyState.UPDATE);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}

public void markNeedsSync(@NonNull RecipientId recipientId) {
markDirty(recipientId, DirtyState.UPDATE);
}
Expand Down Expand Up @@ -768,7 +782,7 @@ public void applyStorageSyncUpdates(@NonNull Collection<SignalContactRecord>
}
}

threadDatabase.setArchived(recipientId, insert.isArchived());
threadDatabase.applyStorageSyncUpdate(recipientId, insert);
needsRefresh.add(recipientId);
}

Expand Down Expand Up @@ -821,7 +835,7 @@ public void applyStorageSyncUpdates(@NonNull Collection<SignalContactRecord>
Log.w(TAG, "Failed to process identity key during update! Skipping.", e);
}

threadDatabase.setArchived(recipientId, update.getNew().isArchived());
threadDatabase.applyStorageSyncUpdate(recipientId, update.getNew());
needsRefresh.add(recipientId);
}

Expand All @@ -830,7 +844,7 @@ public void applyStorageSyncUpdates(@NonNull Collection<SignalContactRecord>

Recipient recipient = Recipient.externalGroup(context, GroupId.v1orThrow(insert.getGroupId()));

threadDatabase.setArchived(recipient.getId(), insert.isArchived());
threadDatabase.applyStorageSyncUpdate(recipient.getId(), insert);
needsRefresh.add(recipient.getId());
}

Expand All @@ -844,7 +858,7 @@ public void applyStorageSyncUpdates(@NonNull Collection<SignalContactRecord>

Recipient recipient = Recipient.externalGroup(context, GroupId.v1orThrow(update.getOld().getGroupId()));

threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived());
threadDatabase.applyStorageSyncUpdate(recipient.getId(), update.getNew());
needsRefresh.add(recipient.getId());
}

Expand Down Expand Up @@ -872,7 +886,7 @@ public void applyStorageSyncUpdates(@NonNull Collection<SignalContactRecord>

ApplicationDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId));

threadDatabase.setArchived(recipient.getId(), insert.isArchived());
threadDatabase.applyStorageSyncUpdate(recipient.getId(), insert);
needsRefresh.add(recipient.getId());
}

Expand All @@ -887,7 +901,7 @@ public void applyStorageSyncUpdates(@NonNull Collection<SignalContactRecord>
GroupMasterKey masterKey = update.getOld().getMasterKeyOrThrow();
Recipient recipient = Recipient.externalGroup(context, GroupId.v2(masterKey));

threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived());
threadDatabase.applyStorageSyncUpdate(recipient.getId(), update.getNew());
needsRefresh.add(recipient.getId());
}

Expand Down Expand Up @@ -936,6 +950,8 @@ public void applyStorageSyncUpdates(@NonNull StorageId storageId, SignalAccountR
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
}

DatabaseFactory.getThreadDatabase(context).applyStorageSyncUpdate(Recipient.self().getId(), update);

Recipient.self().live().refresh();
}

Expand Down Expand Up @@ -1236,12 +1252,13 @@ public List<StorageId> getContactStorageSyncIds() {
String storageProtoRaw = CursorUtil.getString(cursor, STORAGE_PROTO).orNull();
byte[] storageProto = storageProtoRaw != null ? Base64.decodeOrThrow(storageProtoRaw) : null;
boolean archived = CursorUtil.getBoolean(cursor, ThreadDatabase.ARCHIVED).or(false);
boolean forcedUnread = CursorUtil.getInt(cursor, ThreadDatabase.READ).transform(status -> status == ThreadDatabase.ReadStatus.FORCED_UNREAD.serialize()).or(false);
GroupMasterKey groupMasterKey = CursorUtil.getBlob(cursor, GroupDatabase.V2_MASTER_KEY).transform(GroupUtil::requireMasterKey).orNull();
byte[] identityKey = CursorUtil.getString(cursor, IDENTITY_KEY).transform(Base64::decodeOrThrow).orNull();
VerifiedStatus identityStatus = CursorUtil.getInt(cursor, IDENTITY_STATUS).transform(VerifiedStatus::forState).or(VerifiedStatus.DEFAULT);


return new RecipientSettings.SyncExtras(storageProto, groupMasterKey, identityKey, identityStatus, archived);
return new RecipientSettings.SyncExtras(storageProto, groupMasterKey, identityKey, identityStatus, archived, forcedUnread);
}

public BulkOperationsHandle beginBulkSystemContactUpdate() {
Expand Down Expand Up @@ -1422,7 +1439,7 @@ public boolean setProfileKey(@NonNull RecipientId id, @NonNull ProfileKey profil
valuesToSet.putNull(PROFILE_KEY_CREDENTIAL);
valuesToSet.put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.getMode());

SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare);

if (update(updateQuery, valuesToSet)) {
markDirty(id, DirtyState.UPDATE);
Expand Down Expand Up @@ -1471,7 +1488,7 @@ public void setProfileKeyCredential(@NonNull RecipientId id,

values.put(PROFILE_KEY_CREDENTIAL, Base64.encodeBytes(profileKeyCredential.serialize()));

SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);

if (update(updateQuery, values)) {
// TODO [greyson] If we sync this in future, mark dirty
Expand Down Expand Up @@ -2250,9 +2267,7 @@ void markDirty(@NonNull RecipientId recipientId, @NonNull DirtyState dirtyState)
* query such that this will only return true if a row was *actually* updated.
*/
private boolean update(@NonNull RecipientId id, @NonNull ContentValues contentValues) {
String selection = ID + " = ?";
String[] args = new String[]{id.serialize()};
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, contentValues);
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(ID_WHERE, SqlUtil.buildArgs(id), contentValues);

return update(updateQuery, contentValues);
}
Expand All @@ -2262,7 +2277,7 @@ private boolean update(@NonNull RecipientId id, @NonNull ContentValues contentVa
* <p>
* This will only return true if a row was *actually* updated with respect to the where clause of the {@param updateQuery}.
*/
private boolean update(@NonNull SqlUtil.UpdateQuery updateQuery, @NonNull ContentValues contentValues) {
private boolean update(@NonNull SqlUtil.Query updateQuery, @NonNull ContentValues contentValues) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();

return database.update(TABLE_NAME, contentValues, updateQuery.getWhere(), updateQuery.getWhereArgs()) > 0;
Expand Down Expand Up @@ -2816,18 +2831,21 @@ public static class SyncExtras {
private final byte[] identityKey;
private final VerifiedStatus identityStatus;
private final boolean archived;
private final boolean forcedUnread;

public SyncExtras(@Nullable byte[] storageProto,
@Nullable GroupMasterKey groupMasterKey,
@Nullable byte[] identityKey,
@NonNull VerifiedStatus identityStatus,
boolean archived)
boolean archived,
boolean forcedUnread)
{
this.storageProto = storageProto;
this.groupMasterKey = groupMasterKey;
this.identityKey = identityKey;
this.identityStatus = identityStatus;
this.archived = archived;
this.forcedUnread = forcedUnread;
}

public @Nullable byte[] getStorageProto() {
Expand All @@ -2849,6 +2867,10 @@ public boolean isArchived() {
public @NonNull VerifiedStatus getIdentityStatus() {
return identityStatus;
}

public boolean isForcedUnread() {
return forcedUnread;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
import net.sqlcipher.database.SQLiteDatabase;

import org.jsoup.helper.StringUtil;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
Expand All @@ -56,11 +54,15 @@
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -70,7 +72,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;

public class ThreadDatabase extends Database {

Expand Down Expand Up @@ -102,7 +103,7 @@ public class ThreadDatabase extends Database {
public static final String LAST_SEEN = "last_seen";
public static final String HAS_SENT = "has_sent";
private static final String LAST_SCROLLED = "last_scrolled";
private static final String PINNED = "pinned";
static final String PINNED = "pinned";

public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
DATE + " INTEGER DEFAULT 0, " +
Expand Down Expand Up @@ -405,6 +406,7 @@ private List<MarkedMessageInfo> setReadInternal(Collection<Long> threadIds, bool

List<MarkedMessageInfo> smsRecords = new LinkedList<>();
List<MarkedMessageInfo> mmsRecords = new LinkedList<>();
boolean needsSync = false;

db.beginTransaction();

Expand All @@ -417,6 +419,8 @@ private List<MarkedMessageInfo> setReadInternal(Collection<Long> threadIds, bool
}

for (long threadId : threadIds) {
ThreadRecord previous = getThreadRecord(threadId);

smsRecords.addAll(DatabaseFactory.getSmsDatabase(context).setMessagesReadSince(threadId, sinceTimestamp));
mmsRecords.addAll(DatabaseFactory.getMmsDatabase(context).setMessagesReadSince(threadId, sinceTimestamp));

Expand All @@ -427,7 +431,12 @@ private List<MarkedMessageInfo> setReadInternal(Collection<Long> threadIds, bool

contentValues.put(UNREAD_COUNT, unreadCount);

db.update(TABLE_NAME, contentValues, ID_WHERE, new String[]{threadId + ""});
db.update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(threadId));

if (previous != null && previous.isForcedUnread()) {
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(previous.getRecipient().getId());
needsSync = true;
}
}

db.setTransactionSuccessful();
Expand All @@ -437,6 +446,11 @@ private List<MarkedMessageInfo> setReadInternal(Collection<Long> threadIds, bool

notifyConversationListeners(new HashSet<>(threadIds));
notifyConversationListListeners();

if (needsSync) {
StorageSyncHelper.scheduleSyncForDataChange();
}

return Util.concatenatedList(smsRecords, mmsRecords);
}

Expand All @@ -445,19 +459,22 @@ public void setForcedUnread(@NonNull Collection<Long> threadIds) {

db.beginTransaction();
try {
ContentValues contentValues = new ContentValues();
List<RecipientId> recipientIds = getRecipientIdsForThreadIds(threadIds);
SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, threadIds);
ContentValues contentValues = new ContentValues();

contentValues.put(READ, ReadStatus.FORCED_UNREAD.serialize());

for (long threadId : threadIds) {
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] { String.valueOf(threadId) });
}
db.update(TABLE_NAME, contentValues, query.getWhere(), query.getWhereArgs());
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientIds);

db.setTransactionSuccessful();
} finally {
db.endTransaction();
}

notifyConversationListListeners();
StorageSyncHelper.scheduleSyncForDataChange();
notifyConversationListListeners();
}
}


Expand Down Expand Up @@ -949,6 +966,20 @@ public long getThreadIdFor(@NonNull Recipient recipient, int distributionType) {
return Recipient.resolved(id);
}

public @NonNull List<RecipientId> getRecipientIdsForThreadIds(Collection<Long> threadIds) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, threadIds);
List<RecipientId> ids = new ArrayList<>(threadIds.size());

try (Cursor cursor = db.query(TABLE_NAME, new String[] { RECIPIENT_ID }, query.getWhere(), query.getWhereArgs(), null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
ids.add(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)));
}
}

return ids;
}

public boolean hasThread(@NonNull RecipientId recipientId) {
return getThreadIdIfExistsFor(recipientId) > -1;
}
Expand All @@ -964,16 +995,56 @@ public void setHasSent(long threadId, boolean hasSent) {
}

void updateReadState(long threadId) {
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);
ThreadRecord previous = getThreadRecord(threadId);
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);

ContentValues contentValues = new ContentValues();
contentValues.put(READ, unreadCount == 0);
contentValues.put(READ, unreadCount == 0 ? ReadStatus.READ.serialize() : ReadStatus.UNREAD.serialize());
contentValues.put(UNREAD_COUNT, unreadCount);

databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,ID_WHERE,
new String[] {String.valueOf(threadId)});
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(threadId));

notifyConversationListListeners();

if (previous != null && previous.isForcedUnread()) {
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(previous.getRecipient().getId());
StorageSyncHelper.scheduleSyncForDataChange();
}
}

public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalContactRecord record) {
applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread());
}

public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalGroupV1Record record) {
applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread());
}

public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalGroupV2Record record) {
applyStorageSyncUpdate(recipientId, record.isArchived(), record.isForcedUnread());
}

public void applyStorageSyncUpdate(@NonNull RecipientId recipientId, @NonNull SignalAccountRecord record) {
applyStorageSyncUpdate(recipientId, record.isNoteToSelfArchived(), record.isNoteToSelfForcedUnread());
}

private void applyStorageSyncUpdate(@NonNull RecipientId recipientId, boolean archived, boolean forcedUnread) {
ContentValues values = new ContentValues();
values.put(ARCHIVED, archived);

if (forcedUnread) {
values.put(READ, ReadStatus.FORCED_UNREAD.serialize());
} else {
Long threadId = getThreadIdFor(recipientId);
if (threadId != null) {
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);

values.put(READ, unreadCount == 0 ? ReadStatus.READ.serialize() : ReadStatus.UNREAD.serialize());
values.put(UNREAD_COUNT, unreadCount);
}
}

databaseHelper.getWritableDatabase().update(TABLE_NAME, values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(recipientId));
}

public boolean update(long threadId, boolean unarchive) {
Expand Down Expand Up @@ -1404,7 +1475,7 @@ public boolean isGv2Invite() {
}
}

private enum ReadStatus {
enum ReadStatus {
READ(1), UNREAD(0), FORCED_UNREAD(2);

private final int value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,15 @@ private boolean performSync() throws IOException, RetryLaterException, InvalidKe
}

remoteManifestVersion = writeOperationResult.getManifest().getVersion();

needsMultiDeviceSync = true;
} else {
Log.i(TAG, "[Remote Newer] After resolving the conflict, all changes are local. No remote writes needed.");
}

recipientDatabase.applyStorageSyncUpdates(mergeResult.getLocalContactInserts(), mergeResult.getLocalContactUpdates(), mergeResult.getLocalGroupV1Inserts(), mergeResult.getLocalGroupV1Updates(), mergeResult.getLocalGroupV2Inserts(), mergeResult.getLocalGroupV2Updates());
storageKeyDatabase.applyStorageSyncUpdates(mergeResult.getLocalUnknownInserts(), mergeResult.getLocalUnknownDeletes());
StorageSyncHelper.applyAccountStorageSyncUpdates(context, mergeResult.getLocalAccountUpdate());
needsMultiDeviceSync = true;

Log.i(TAG, "[Remote Newer] Updating local manifest version to: " + remoteManifestVersion);
TextSecurePreferences.setStorageManifestVersion(context, remoteManifestVersion);
Expand Down

0 comments on commit 63746bb

Please sign in to comment.