Further simplify storage service syncing.
This commit is contained in:
parent
1493581a4d
commit
cdc7f1565e
18 changed files with 261 additions and 507 deletions
|
@ -166,7 +166,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||
return STATE_UI_BLOCKING_UPGRADE;
|
||||
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
|
||||
return STATE_WELCOME_PUSH_SCREEN;
|
||||
} else if (SignalStore.storageServiceValues().needsAccountRestore()) {
|
||||
} else if (SignalStore.storageService().needsAccountRestore()) {
|
||||
return STATE_ENTER_SIGNAL_PIN;
|
||||
} else if (userMustSetProfileName()) {
|
||||
return STATE_CREATE_PROFILE_NAME;
|
||||
|
|
|
@ -143,7 +143,7 @@ public class IdentityDatabase extends Database {
|
|||
boolean firstUse, long timestamp, boolean nonBlockingApproval)
|
||||
{
|
||||
saveIdentityInternal(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||
DatabaseFactory.getRecipientDatabase(context).markDirty(recipientId, RecipientDatabase.DirtyState.UPDATE);
|
||||
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
|
||||
}
|
||||
|
||||
public void setApproval(@NonNull RecipientId recipientId, boolean nonBlockingApproval) {
|
||||
|
@ -154,7 +154,7 @@ public class IdentityDatabase extends Database {
|
|||
|
||||
database.update(TABLE_NAME, contentValues, RECIPIENT_ID + " = ?", new String[] {recipientId.serialize()});
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).markDirty(recipientId, RecipientDatabase.DirtyState.UPDATE);
|
||||
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
|
||||
}
|
||||
|
||||
public void setVerified(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
|
@ -169,7 +169,7 @@ public class IdentityDatabase extends Database {
|
|||
if (updated > 0) {
|
||||
Optional<IdentityRecord> record = getIdentity(recipientId);
|
||||
if (record.isPresent()) EventBus.getDefault().post(record.get());
|
||||
DatabaseFactory.getRecipientDatabase(context).markDirty(recipientId, RecipientDatabase.DirtyState.UPDATE);
|
||||
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import android.text.TextUtils;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.arch.core.util.Function;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
@ -90,6 +89,7 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RecipientDatabase extends Database {
|
||||
|
||||
|
@ -132,7 +132,6 @@ public class RecipientDatabase extends Database {
|
|||
static final String FORCE_SMS_SELECTION = "force_sms_selection";
|
||||
private static final String CAPABILITIES = "capabilities";
|
||||
private static final String STORAGE_SERVICE_ID = "storage_service_key";
|
||||
private static final String DIRTY = "dirty";
|
||||
private static final String PROFILE_GIVEN_NAME = "signal_profile_name";
|
||||
private static final String PROFILE_FAMILY_NAME = "profile_family_name";
|
||||
private static final String PROFILE_JOINED_NAME = "profile_joined_name";
|
||||
|
@ -169,7 +168,7 @@ public class RecipientDatabase extends Database {
|
|||
UNIDENTIFIED_ACCESS_MODE,
|
||||
FORCE_SMS_SELECTION,
|
||||
CAPABILITIES,
|
||||
STORAGE_SERVICE_ID, DIRTY,
|
||||
STORAGE_SERVICE_ID,
|
||||
MENTION_SETTING, WALLPAPER, WALLPAPER_URI,
|
||||
MENTION_SETTING,
|
||||
ABOUT, ABOUT_EMOJI,
|
||||
|
@ -188,7 +187,6 @@ public class RecipientDatabase extends Database {
|
|||
private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME};
|
||||
|
||||
public static final String[] CREATE_INDEXS = new String[] {
|
||||
"CREATE INDEX IF NOT EXISTS recipient_dirty_index ON " + TABLE_NAME + " (" + DIRTY + ");",
|
||||
"CREATE INDEX IF NOT EXISTS recipient_group_type_index ON " + TABLE_NAME + " (" + GROUP_TYPE + ");",
|
||||
};
|
||||
|
||||
|
@ -272,24 +270,6 @@ public class RecipientDatabase extends Database {
|
|||
}
|
||||
}
|
||||
|
||||
public enum DirtyState {
|
||||
CLEAN(0), UPDATE(1), INSERT(2), DELETE(3);
|
||||
|
||||
private final int id;
|
||||
|
||||
DirtyState(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static DirtyState fromId(int id) {
|
||||
return values()[id];
|
||||
}
|
||||
}
|
||||
|
||||
public enum GroupType {
|
||||
NONE(0), MMS(1), SIGNAL_V1(2), SIGNAL_V2(3);
|
||||
|
||||
|
@ -365,7 +345,6 @@ public class RecipientDatabase extends Database {
|
|||
UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " +
|
||||
FORCE_SMS_SELECTION + " INTEGER DEFAULT 0, " +
|
||||
STORAGE_SERVICE_ID + " TEXT UNIQUE DEFAULT NULL, " +
|
||||
DIRTY + " INTEGER DEFAULT " + DirtyState.CLEAN.getId() + ", " +
|
||||
MENTION_SETTING + " INTEGER DEFAULT " + MentionSetting.ALWAYS_NOTIFY.getId() + ", " +
|
||||
STORAGE_PROTO + " TEXT DEFAULT NULL, " +
|
||||
CAPABILITIES + " INTEGER DEFAULT 0, " +
|
||||
|
@ -404,24 +383,29 @@ public class RecipientDatabase extends Database {
|
|||
}
|
||||
}
|
||||
|
||||
public @NonNull Optional<RecipientId> getByE164(@NonNull String e164) {
|
||||
public @NonNull
|
||||
Optional<RecipientId> getByE164(@NonNull String e164) {
|
||||
return getByColumn(PHONE, e164);
|
||||
}
|
||||
|
||||
public @NonNull Optional<RecipientId> getByEmail(@NonNull String email) {
|
||||
public @NonNull
|
||||
Optional<RecipientId> getByEmail(@NonNull String email) {
|
||||
return getByColumn(EMAIL, email);
|
||||
}
|
||||
|
||||
public @NonNull Optional<RecipientId> getByGroupId(@NonNull GroupId groupId) {
|
||||
public @NonNull
|
||||
Optional<RecipientId> getByGroupId(@NonNull GroupId groupId) {
|
||||
return getByColumn(GROUP_ID, groupId.toString());
|
||||
|
||||
}
|
||||
|
||||
public @NonNull Optional<RecipientId> getByUuid(@NonNull UUID uuid) {
|
||||
public @NonNull
|
||||
Optional<RecipientId> getByUuid(@NonNull UUID uuid) {
|
||||
return getByColumn(UUID, uuid.toString());
|
||||
}
|
||||
|
||||
public @NonNull Optional<RecipientId> getByUsername(@NonNull String username) {
|
||||
public @NonNull
|
||||
Optional<RecipientId> getByUsername(@NonNull String username) {
|
||||
return getByColumn(USERNAME, username);
|
||||
}
|
||||
|
||||
|
@ -568,7 +552,6 @@ public class RecipientDatabase extends Database {
|
|||
if (uuid != null) {
|
||||
values.put(UUID, uuid.toString().toLowerCase());
|
||||
values.put(REGISTERED, RegisteredState.REGISTERED.getId());
|
||||
values.put(DIRTY, DirtyState.INSERT.getId());
|
||||
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()));
|
||||
}
|
||||
|
||||
|
@ -626,7 +609,6 @@ public class RecipientDatabase extends Database {
|
|||
} else {
|
||||
groupUpdates.put(GROUP_TYPE, GroupType.SIGNAL_V1.getId());
|
||||
}
|
||||
groupUpdates.put(DIRTY, DirtyState.INSERT.getId());
|
||||
groupUpdates.put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()));
|
||||
}
|
||||
|
||||
|
@ -718,18 +700,6 @@ public class RecipientDatabase extends Database {
|
|||
}
|
||||
}
|
||||
|
||||
public @NonNull DirtyState getDirtyState(@NonNull RecipientId recipientId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] { DIRTY }, ID_WHERE, new String[] { recipientId.serialize() }, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return DirtyState.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(DIRTY)));
|
||||
}
|
||||
}
|
||||
|
||||
return DirtyState.CLEAN;
|
||||
}
|
||||
|
||||
public @Nullable RecipientSettings getRecipientSettingsForSync(@NonNull RecipientId id) {
|
||||
String query = TABLE_NAME + "." + ID + " = ?";
|
||||
String[] args = new String[]{id.serialize()};
|
||||
|
@ -747,27 +717,6 @@ public class RecipientDatabase extends Database {
|
|||
return recipientSettingsForSync.get(0);
|
||||
}
|
||||
|
||||
public @NonNull List<RecipientSettings> getPendingRecipientSyncUpdates() {
|
||||
String query = TABLE_NAME + "." + DIRTY + " = ? AND " + TABLE_NAME + "." + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?";
|
||||
String[] args = new String[] { String.valueOf(DirtyState.UPDATE.getId()), Recipient.self().getId().serialize() };
|
||||
|
||||
return getRecipientSettingsForSync(query, args);
|
||||
}
|
||||
|
||||
public @NonNull List<RecipientSettings> getPendingRecipientSyncInsertions() {
|
||||
String query = TABLE_NAME + "." + DIRTY + " = ? AND " + TABLE_NAME + "." + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?";
|
||||
String[] args = new String[] { String.valueOf(DirtyState.INSERT.getId()), Recipient.self().getId().serialize() };
|
||||
|
||||
return getRecipientSettingsForSync(query, args);
|
||||
}
|
||||
|
||||
public @NonNull List<RecipientSettings> getPendingRecipientSyncDeletions() {
|
||||
String query = TABLE_NAME + "." + DIRTY + " = ? AND " + TABLE_NAME + "." + STORAGE_SERVICE_ID + " NOT NULL AND " + TABLE_NAME + "." + ID + " != ?";
|
||||
String[] args = new String[] { String.valueOf(DirtyState.DELETE.getId()), Recipient.self().getId().serialize() };
|
||||
|
||||
return getRecipientSettingsForSync(query, args);
|
||||
}
|
||||
|
||||
public @Nullable RecipientSettings getByStorageId(@NonNull byte[] storageId) {
|
||||
List<RecipientSettings> result = getRecipientSettingsForSync(TABLE_NAME + "." + STORAGE_SERVICE_ID + " = ?", new String[] { Base64.encodeBytes(storageId) });
|
||||
|
||||
|
@ -784,7 +733,8 @@ public class RecipientDatabase extends Database {
|
|||
db.beginTransaction();
|
||||
try {
|
||||
for (RecipientId recipientId : recipientIds) {
|
||||
markDirty(recipientId, DirtyState.UPDATE);
|
||||
rotateStorageId(recipientId);
|
||||
Recipient.live(recipientId).refresh();
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
|
@ -793,7 +743,8 @@ public class RecipientDatabase extends Database {
|
|||
}
|
||||
|
||||
public void markNeedsSync(@NonNull RecipientId recipientId) {
|
||||
markDirty(recipientId, DirtyState.UPDATE);
|
||||
rotateStorageId(recipientId);
|
||||
Recipient.live(recipientId).refresh();
|
||||
}
|
||||
|
||||
public void applyStorageIdUpdates(@NonNull Map<RecipientId, StorageId> storageIds) {
|
||||
|
@ -806,7 +757,6 @@ public class RecipientDatabase extends Database {
|
|||
for (Map.Entry<RecipientId, StorageId> entry : storageIds.entrySet()) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(entry.getValue().getRaw()));
|
||||
values.put(DIRTY, DirtyState.CLEAN.getId());
|
||||
|
||||
db.update(TABLE_NAME, values, query, new String[] { entry.getKey().serialize() });
|
||||
}
|
||||
|
@ -996,7 +946,6 @@ public class RecipientDatabase extends Database {
|
|||
values.put(PROFILE_JOINED_NAME, profileName.toString());
|
||||
values.put(PROFILE_KEY, profileKey);
|
||||
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(update.getId().getRaw()));
|
||||
values.put(DIRTY, DirtyState.CLEAN.getId());
|
||||
|
||||
if (update.hasUnknownFields()) {
|
||||
values.put(STORAGE_PROTO, Base64.encodeBytes(update.serializeUnknownFields()));
|
||||
|
@ -1075,7 +1024,6 @@ public class RecipientDatabase extends Database {
|
|||
values.put(BLOCKED, contact.isBlocked() ? "1" : "0");
|
||||
values.put(MUTE_UNTIL, contact.getMuteUntil());
|
||||
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(contact.getId().getRaw()));
|
||||
values.put(DIRTY, DirtyState.CLEAN.getId());
|
||||
|
||||
if (contact.isProfileSharingEnabled() && isInsert && !profileName.isEmpty()) {
|
||||
values.put(COLOR, ContactColors.generateFor(profileName.toString()).serialize());
|
||||
|
@ -1098,7 +1046,6 @@ public class RecipientDatabase extends Database {
|
|||
values.put(BLOCKED, groupV1.isBlocked() ? "1" : "0");
|
||||
values.put(MUTE_UNTIL, groupV1.getMuteUntil());
|
||||
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(groupV1.getId().getRaw()));
|
||||
values.put(DIRTY, DirtyState.CLEAN.getId());
|
||||
|
||||
if (groupV1.hasUnknownFields()) {
|
||||
values.put(STORAGE_PROTO, Base64.encodeBytes(groupV1.serializeUnknownFields()));
|
||||
|
@ -1117,7 +1064,6 @@ public class RecipientDatabase extends Database {
|
|||
values.put(BLOCKED, groupV2.isBlocked() ? "1" : "0");
|
||||
values.put(MUTE_UNTIL, groupV2.getMuteUntil());
|
||||
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(groupV2.getId().getRaw()));
|
||||
values.put(DIRTY, DirtyState.CLEAN.getId());
|
||||
|
||||
if (groupV2.hasUnknownFields()) {
|
||||
values.put(STORAGE_PROTO, Base64.encodeBytes(groupV2.serializeUnknownFields()));
|
||||
|
@ -1166,8 +1112,8 @@ public class RecipientDatabase extends Database {
|
|||
*/
|
||||
public @NonNull Map<RecipientId, StorageId> getContactStorageSyncIdsMap() {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
String query = STORAGE_SERVICE_ID + " NOT NULL AND " + DIRTY + " != ? AND " + ID + " != ? AND " + GROUP_TYPE + " != ?";
|
||||
String[] args = { String.valueOf(DirtyState.DELETE.getId()), Recipient.self().getId().serialize(), String.valueOf(GroupType.SIGNAL_V2.getId()) };
|
||||
String query = STORAGE_SERVICE_ID + " NOT NULL AND " + ID + " != ? AND " + GROUP_TYPE + " != ?";
|
||||
String[] args = SqlUtil.buildArgs(Recipient.self().getId(), String.valueOf(GroupType.SIGNAL_V2.getId()));
|
||||
Map<RecipientId, StorageId> out = new HashMap<>();
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] { ID, STORAGE_SERVICE_ID, GROUP_TYPE }, query, args, null, null, null)) {
|
||||
|
@ -1432,7 +1378,7 @@ public class RecipientDatabase extends Database {
|
|||
ContentValues values = new ContentValues();
|
||||
values.put(BLOCKED, blocked ? 1 : 0);
|
||||
if (update(id, values)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -1473,8 +1419,8 @@ public class RecipientDatabase extends Database {
|
|||
ContentValues values = new ContentValues();
|
||||
values.put(MUTE_UNTIL, until);
|
||||
if (update(id, values)) {
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
}
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
|
@ -1514,7 +1460,7 @@ public class RecipientDatabase extends Database {
|
|||
ContentValues values = new ContentValues(1);
|
||||
values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode());
|
||||
if (update(id, values)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -1612,7 +1558,7 @@ public class RecipientDatabase extends Database {
|
|||
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare);
|
||||
|
||||
if (update(updateQuery, valuesToSet)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
return true;
|
||||
|
@ -1637,7 +1583,7 @@ public class RecipientDatabase extends Database {
|
|||
valuesToSet.put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.getMode());
|
||||
|
||||
if (database.update(TABLE_NAME, valuesToSet, selection, args) > 0) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
return true;
|
||||
} else {
|
||||
|
@ -1678,7 +1624,7 @@ public class RecipientDatabase extends Database {
|
|||
ContentValues values = new ContentValues(1);
|
||||
values.putNull(PROFILE_KEY_CREDENTIAL);
|
||||
if (update(id, values)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -1760,7 +1706,7 @@ public class RecipientDatabase extends Database {
|
|||
contentValues.put(PROFILE_FAMILY_NAME, profileName.getFamilyName());
|
||||
contentValues.put(PROFILE_JOINED_NAME, profileName.toString());
|
||||
if (update(id, contentValues)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
|
@ -1773,7 +1719,7 @@ public class RecipientDatabase extends Database {
|
|||
Recipient.live(id).refresh();
|
||||
|
||||
if (id.equals(Recipient.self().getId())) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
}
|
||||
|
@ -1805,7 +1751,7 @@ public class RecipientDatabase extends Database {
|
|||
}
|
||||
|
||||
if (profiledUpdated || colorUpdated) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
|
@ -1986,7 +1932,7 @@ public class RecipientDatabase extends Database {
|
|||
contentValues.put(PHONE, e164);
|
||||
|
||||
if (update(id, contentValues)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
|
@ -2069,7 +2015,7 @@ public class RecipientDatabase extends Database {
|
|||
contentValues.put(UUID, uuid.toString().toLowerCase());
|
||||
|
||||
if (update(id, contentValues)) {
|
||||
markDirty(id, DirtyState.INSERT);
|
||||
setStorageIdIfNotSet(id);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -2082,8 +2028,9 @@ public class RecipientDatabase extends Database {
|
|||
public void markRegistered(@NonNull RecipientId id) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(REGISTERED, RegisteredState.REGISTERED.getId());
|
||||
|
||||
if (update(id, contentValues)) {
|
||||
markDirty(id, DirtyState.INSERT);
|
||||
setStorageIdIfNotSet(id);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -2091,8 +2038,9 @@ public class RecipientDatabase extends Database {
|
|||
public void markUnregistered(@NonNull RecipientId id) {
|
||||
ContentValues contentValues = new ContentValues(2);
|
||||
contentValues.put(REGISTERED, RegisteredState.NOT_REGISTERED.getId());
|
||||
contentValues.putNull(STORAGE_SERVICE_ID);
|
||||
|
||||
if (update(id, contentValues)) {
|
||||
markDirty(id, DirtyState.DELETE);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -2112,7 +2060,7 @@ public class RecipientDatabase extends Database {
|
|||
|
||||
try {
|
||||
if (update(entry.getKey(), values)) {
|
||||
markDirty(entry.getKey(), DirtyState.INSERT);
|
||||
setStorageIdIfNotSet(entry.getKey());
|
||||
}
|
||||
} catch (SQLiteConstraintException e) {
|
||||
Log.w(TAG, "[bulkUpdateRegisteredStatus] Hit a conflict when trying to update " + entry.getKey() + ". Possibly merging.");
|
||||
|
@ -2126,9 +2074,9 @@ public class RecipientDatabase extends Database {
|
|||
for (RecipientId id : unregistered) {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(REGISTERED, RegisteredState.NOT_REGISTERED.getId());
|
||||
if (update(id, values)) {
|
||||
markDirty(id, DirtyState.DELETE);
|
||||
}
|
||||
values.putNull(STORAGE_SERVICE_ID);
|
||||
|
||||
update(id, values);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
|
@ -2142,11 +2090,13 @@ public class RecipientDatabase extends Database {
|
|||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(REGISTERED, registeredState.getId());
|
||||
|
||||
if (registeredState == RegisteredState.NOT_REGISTERED) {
|
||||
contentValues.putNull(STORAGE_SERVICE_ID);
|
||||
}
|
||||
|
||||
if (update(id, contentValues)) {
|
||||
if (registeredState == RegisteredState.REGISTERED) {
|
||||
markDirty(id, DirtyState.INSERT);
|
||||
} else if (registeredState == RegisteredState.NOT_REGISTERED) {
|
||||
markDirty(id, DirtyState.DELETE);
|
||||
setStorageIdIfNotSet(id);
|
||||
}
|
||||
|
||||
Recipient.live(id).refresh();
|
||||
|
@ -2162,7 +2112,7 @@ public class RecipientDatabase extends Database {
|
|||
registeredValues.put(REGISTERED, RegisteredState.REGISTERED.getId());
|
||||
|
||||
if (update(activeId, registeredValues)) {
|
||||
markDirty(activeId, DirtyState.INSERT);
|
||||
setStorageIdIfNotSet(activeId);
|
||||
Recipient.live(activeId).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -2170,9 +2120,9 @@ public class RecipientDatabase extends Database {
|
|||
for (RecipientId inactiveId : inactiveIds) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(REGISTERED, RegisteredState.NOT_REGISTERED.getId());
|
||||
contentValues.putNull(STORAGE_SERVICE_ID);
|
||||
|
||||
if (update(inactiveId, contentValues)) {
|
||||
markDirty(inactiveId, DirtyState.DELETE);
|
||||
Recipient.live(inactiveId).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -2619,46 +2569,6 @@ public class RecipientDatabase extends Database {
|
|||
}
|
||||
}
|
||||
|
||||
public void clearDirtyStateForStorageIds(@NonNull Collection<StorageId> ids) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
|
||||
Preconditions.checkArgument(db.inTransaction(), "Database should already be in a transaction.");
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DIRTY, DirtyState.CLEAN.getId());
|
||||
|
||||
String query = STORAGE_SERVICE_ID + " = ?";
|
||||
|
||||
for (StorageId id : ids) {
|
||||
String[] args = SqlUtil.buildArgs(Base64.encodeBytes(id.getRaw()));
|
||||
db.update(TABLE_NAME, values, query, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearDirtyState(@NonNull List<RecipientId> recipients) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DIRTY, DirtyState.CLEAN.getId());
|
||||
|
||||
for (RecipientId id : recipients) {
|
||||
Optional<RecipientId> remapped = RemappedRecords.getInstance().getRecipient(context, id);
|
||||
if (remapped.isPresent()) {
|
||||
Log.w(TAG, "While clearing dirty state, noticed we have a remapped contact (" + id + " to " + remapped.get() + "). Safe to delete now.");
|
||||
db.delete(TABLE_NAME, ID_WHERE, new String[]{id.serialize()});
|
||||
} else {
|
||||
db.update(TABLE_NAME, values, ID_WHERE, new String[]{id.serialize()});
|
||||
}
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public void markPreMessageRequestRecipientsAsProfileSharingEnabled(long messageRequestEnableTime) {
|
||||
String[] whereArgs = SqlUtil.buildArgs(messageRequestEnableTime, messageRequestEnableTime);
|
||||
|
||||
|
@ -2750,31 +2660,27 @@ public class RecipientDatabase extends Database {
|
|||
Recipient.live(recipientId).refresh();
|
||||
}
|
||||
|
||||
void markDirty(@NonNull RecipientId recipientId, @NonNull DirtyState dirtyState) {
|
||||
Log.d(TAG, "Attempting to mark " + recipientId + " with dirty state " + dirtyState);
|
||||
/**
|
||||
* Does not trigger any recipient refreshes -- it is assumed the caller handles this.
|
||||
*/
|
||||
void rotateStorageId(@NonNull RecipientId recipientId) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()));
|
||||
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(DIRTY, dirtyState.getId());
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId));
|
||||
}
|
||||
|
||||
String query = ID + " = ? AND (" + UUID + " NOT NULL OR " + PHONE + " NOT NULL OR " + GROUP_ID + " NOT NULL) AND ";
|
||||
String[] args = new String[] { recipientId.serialize(), String.valueOf(dirtyState.id) };
|
||||
/**
|
||||
* Does not trigger any recipient refreshes -- it is assumed the caller handles this.
|
||||
*/
|
||||
void setStorageIdIfNotSet(@NonNull RecipientId recipientId) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()));
|
||||
|
||||
switch (dirtyState) {
|
||||
case INSERT:
|
||||
query += "(" + DIRTY + " < ? OR " + DIRTY + " = ?)";
|
||||
args = SqlUtil.appendArg(args, String.valueOf(DirtyState.DELETE.getId()));
|
||||
String query = ID + " = ? AND " + STORAGE_SERVICE_ID + " IS NULL";
|
||||
String[] args = SqlUtil.buildArgs(recipientId);
|
||||
|
||||
contentValues.put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()));
|
||||
break;
|
||||
case DELETE:
|
||||
query += "(" + DIRTY + " < ? OR " + DIRTY + " = ?)";
|
||||
args = SqlUtil.appendArg(args, String.valueOf(DirtyState.INSERT.getId()));
|
||||
break;
|
||||
default:
|
||||
query += DIRTY + " < ?";
|
||||
}
|
||||
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, query, args);
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2790,7 +2696,7 @@ public class RecipientDatabase extends Database {
|
|||
|
||||
if (update(query, values)) {
|
||||
RecipientId id = getByGroupId(v2Id).get();
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
rotateStorageId(id);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
@ -2816,7 +2722,8 @@ public class RecipientDatabase extends Database {
|
|||
return database.update(TABLE_NAME, contentValues, updateQuery.getWhere(), updateQuery.getWhereArgs()) > 0;
|
||||
}
|
||||
|
||||
private @NonNull Optional<RecipientId> getByColumn(@NonNull String column, String value) {
|
||||
private @NonNull
|
||||
Optional<RecipientId> getByColumn(@NonNull String column, String value) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
String query = column + " = ?";
|
||||
String[] args = new String[] { value };
|
||||
|
@ -2873,17 +2780,8 @@ public class RecipientDatabase extends Database {
|
|||
RecipientSettings e164Settings = getRecipientSettings(byE164);
|
||||
|
||||
// Recipient
|
||||
if (e164Settings.getStorageId() == null) {
|
||||
Log.w(TAG, "No storageId on the E164 recipient. Can delete right away.");
|
||||
db.delete(TABLE_NAME, ID_WHERE, SqlUtil.buildArgs(byE164));
|
||||
} else {
|
||||
Log.w(TAG, "The E164 recipient has a storageId. Clearing data and marking for deletion.");
|
||||
ContentValues values = new ContentValues();
|
||||
values.putNull(PHONE);
|
||||
values.put(REGISTERED, RegisteredState.NOT_REGISTERED.getId());
|
||||
values.put(DIRTY, DirtyState.DELETE.getId());
|
||||
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(byE164));
|
||||
}
|
||||
Log.w(TAG, "Deleting recipient " + byE164);
|
||||
db.delete(TABLE_NAME, ID_WHERE, SqlUtil.buildArgs(byE164));
|
||||
RemappedRecords.getInstance().addRecipient(context, byE164, byUuid);
|
||||
|
||||
ContentValues uuidValues = new ContentValues();
|
||||
|
@ -3020,18 +2918,12 @@ public class RecipientDatabase extends Database {
|
|||
int systemPhoneType,
|
||||
@Nullable String systemContactUri)
|
||||
{
|
||||
ContentValues dirtyQualifyingValues = new ContentValues();
|
||||
String joinedName = Util.firstNonNull(systemDisplayName, systemProfileName.toString());
|
||||
|
||||
dirtyQualifyingValues.put(SYSTEM_GIVEN_NAME, systemProfileName.getGivenName());
|
||||
dirtyQualifyingValues.put(SYSTEM_FAMILY_NAME, systemProfileName.getFamilyName());
|
||||
dirtyQualifyingValues.put(SYSTEM_JOINED_NAME, joinedName);
|
||||
|
||||
if (update(id, dirtyQualifyingValues)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
}
|
||||
String joinedName = Util.firstNonNull(systemDisplayName, systemProfileName.toString());
|
||||
|
||||
ContentValues refreshQualifyingValues = new ContentValues();
|
||||
refreshQualifyingValues.put(SYSTEM_GIVEN_NAME, systemProfileName.getGivenName());
|
||||
refreshQualifyingValues.put(SYSTEM_FAMILY_NAME, systemProfileName.getFamilyName());
|
||||
refreshQualifyingValues.put(SYSTEM_JOINED_NAME, joinedName);
|
||||
refreshQualifyingValues.put(SYSTEM_PHOTO_URI, photoUri);
|
||||
refreshQualifyingValues.put(SYSTEM_PHONE_LABEL, systemPhoneLabel);
|
||||
refreshQualifyingValues.put(SYSTEM_PHONE_TYPE, systemPhoneType);
|
||||
|
@ -3060,13 +2952,15 @@ public class RecipientDatabase extends Database {
|
|||
}
|
||||
|
||||
private void markAllRelevantEntriesDirty() {
|
||||
String query = SYSTEM_INFO_PENDING + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL AND " + DIRTY + " < ?";
|
||||
String[] args = new String[] { "1", String.valueOf(DirtyState.UPDATE.getId()) };
|
||||
String query = SYSTEM_INFO_PENDING + " = ? AND " + STORAGE_SERVICE_ID + " NOT NULL";
|
||||
String[] args = SqlUtil.buildArgs("1");
|
||||
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(DIRTY, DirtyState.UPDATE.getId());
|
||||
|
||||
database.update(TABLE_NAME, values, query, args);
|
||||
try (Cursor cursor = database.query(TABLE_NAME, ID_PROJECTION, query, args, null, null, null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
RecipientId id = RecipientId.from(CursorUtil.requireString(cursor, ID));
|
||||
rotateStorageId(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearSystemDataForPendingInfo() {
|
||||
|
|
|
@ -178,8 +178,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
|||
private static final int BLUR_AVATARS = 94;
|
||||
private static final int CLEAN_STORAGE_IDS_WITHOUT_INFO = 95;
|
||||
private static final int CLEAN_REACTION_NOTIFICATIONS = 96;
|
||||
private static final int STORAGE_SERVICE_REFACTOR = 97;
|
||||
|
||||
private static final int DATABASE_VERSION = 96;
|
||||
private static final int DATABASE_VERSION = 97;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
|
@ -1396,6 +1397,47 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
|||
}
|
||||
}
|
||||
|
||||
if (oldVersion < STORAGE_SERVICE_REFACTOR) {
|
||||
int deleteCount;
|
||||
int insertCount;
|
||||
int updateCount;
|
||||
int dirtyCount;
|
||||
|
||||
ContentValues deleteValues = new ContentValues();
|
||||
deleteValues.putNull("storage_service_key");
|
||||
deleteCount = db.update("recipient", deleteValues, "storage_service_key NOT NULL AND (dirty = 3 OR group_type = 1 OR (group_type = 0 AND registered = 2))", null);
|
||||
|
||||
try (Cursor cursor = db.query("recipient", new String[]{"_id"}, "storage_service_key IS NULL AND (dirty = 2 OR registered = 1)", null, null, null, null)) {
|
||||
insertCount = cursor.getCount();
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues insertValues = new ContentValues();
|
||||
insertValues.put("storage_service_key", Base64.encodeBytes(StorageSyncHelper.generateKey()));
|
||||
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
|
||||
db.update("recipient", insertValues, "_id = ?", SqlUtil.buildArgs(id));
|
||||
}
|
||||
}
|
||||
|
||||
try (Cursor cursor = db.query("recipient", new String[]{"_id"}, "storage_service_key NOT NULL AND dirty = 1", null, null, null, null)) {
|
||||
updateCount = cursor.getCount();
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues updateValues = new ContentValues();
|
||||
updateValues.put("storage_service_key", Base64.encodeBytes(StorageSyncHelper.generateKey()));
|
||||
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
|
||||
db.update("recipient", updateValues, "_id = ?", SqlUtil.buildArgs(id));
|
||||
}
|
||||
}
|
||||
|
||||
ContentValues clearDirtyValues = new ContentValues();
|
||||
clearDirtyValues.put("dirty", 0);
|
||||
dirtyCount = db.update("recipient", clearDirtyValues, "dirty != 0", null);
|
||||
|
||||
Log.d(TAG, String.format(Locale.US, "For storage service refactor migration, there were %d inserts, %d updated, and %d deletes. Cleared the dirty status on %d rows.", insertCount, updateCount, deleteCount, dirtyCount));
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
|
|
@ -59,7 +59,7 @@ public class MultiDeviceKeysUpdateJob extends BaseJob {
|
|||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageKey();
|
||||
StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
|
||||
|
||||
messageSender.sendMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.fromNullable(storageServiceKey))),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
|
|
|
@ -65,7 +65,7 @@ public class StorageAccountRestoreJob extends BaseJob {
|
|||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageKey();
|
||||
StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
|
||||
|
||||
Log.i(TAG, "Retrieving manifest...");
|
||||
Optional<SignalStorageManifest> manifest = accountManager.getStorageManifest(storageServiceKey);
|
||||
|
@ -76,8 +76,8 @@ public class StorageAccountRestoreJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Updating local manifest version to 0.");
|
||||
TextSecurePreferences.setStorageManifestVersion(context, 0);
|
||||
Log.i(TAG, "Resetting the local manifest to an empty state so that it will sync later.");
|
||||
SignalStore.storageService().setManifest(SignalStorageManifest.EMPTY);
|
||||
|
||||
Optional<StorageId> accountId = manifest.get().getAccountStorageId();
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ public class StorageForcePushJob extends BaseJob {
|
|||
|
||||
@Override
|
||||
protected void onRun() throws IOException, RetryLaterException {
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageKey();
|
||||
StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
UnknownStorageIdDatabase storageIdDatabase = DatabaseFactory.getUnknownStorageIdDatabase(context);
|
||||
|
@ -117,7 +117,7 @@ public class StorageForcePushJob extends BaseJob {
|
|||
}
|
||||
|
||||
Log.i(TAG, "Force push succeeded. Updating local manifest version to: " + newVersion);
|
||||
TextSecurePreferences.setStorageManifestVersion(context, newVersion);
|
||||
SignalStore.storageService().setManifest(manifest);
|
||||
recipientDatabase.applyStorageIdUpdates(newContactStorageIds);
|
||||
recipientDatabase.applyStorageIdUpdates(Collections.singletonMap(Recipient.self().getId(), accountRecord.getId()));
|
||||
storageIdDatabase.deleteAll();
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.storage.GroupV2RecordProcessor;
|
|||
import org.thoughtcrime.securesms.storage.StorageRecordUpdate;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper.LocalWriteResult;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper.WriteOperationResult;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncModels;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncValidations;
|
||||
|
@ -52,6 +51,7 @@ import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
|
|||
import org.whispersystems.signalservice.api.storage.StorageId;
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.SignalStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -105,10 +105,9 @@ import java.util.concurrent.TimeUnit;
|
|||
* the diff in IDs.
|
||||
* - Then, we fetch the actual records that correspond to the remote-only IDs.
|
||||
* - Afterwards, we take those records and merge them into our local data store.
|
||||
* - The merging process could result in changes that need to be written back to the service, so
|
||||
* we write those back.
|
||||
* - Finally, we look at any other local changes that were made (independent of the ID diff) and
|
||||
* make sure those are written to the service.
|
||||
* - Finally, we assume that our local state represents the most up-to-date information, and so we
|
||||
* calculate and write a change set that represents the diff between our state and the remote
|
||||
* state.
|
||||
*
|
||||
* Of course, you'll notice that there's a lot of code to support that goal. That's mostly because
|
||||
* converting local data into a format that can be compared with, merged, and eventually written
|
||||
|
@ -130,8 +129,8 @@ import java.util.concurrent.TimeUnit;
|
|||
* - Update builder usage in StorageSyncModels
|
||||
* - Handle the new data when writing to the local storage
|
||||
* (i.e. {@link RecipientDatabase#applyStorageSyncContactUpdate(StorageRecordUpdate)}).
|
||||
* - Make sure that whenever you change the field in the UI, we mark the row as dirty and call
|
||||
* {@link StorageSyncHelper#scheduleSyncForDataChange()}.
|
||||
* - Make sure that whenever you change the field in the UI, we rotate the storageId for that row
|
||||
* and call {@link StorageSyncHelper#scheduleSyncForDataChange()}.
|
||||
* - If you're syncing a field that was otherwise already present in the UI, you'll probably want
|
||||
* to enqueue a {@link StorageServiceMigrationJob} as an app migration to make sure it gets
|
||||
* synced.
|
||||
|
@ -185,7 +184,7 @@ public class StorageSyncJob extends BaseJob {
|
|||
ApplicationDependencies.getJobManager().add(new MultiDeviceStorageSyncRequestJob());
|
||||
}
|
||||
|
||||
SignalStore.storageServiceValues().onSyncCompleted();
|
||||
SignalStore.storageService().onSyncCompleted();
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w(TAG, "Failed to decrypt remote storage! Force-pushing and syncing the storage key to linked devices.", e);
|
||||
|
||||
|
@ -206,29 +205,28 @@ public class StorageSyncJob extends BaseJob {
|
|||
}
|
||||
|
||||
private boolean performSync() throws IOException, RetryLaterException, InvalidKeyException {
|
||||
Stopwatch stopwatch = new Stopwatch("StorageSync");
|
||||
Recipient self = Recipient.self().fresh();
|
||||
SQLiteDatabase db = DatabaseFactory.getInstance(context).getRawDatabase();
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
UnknownStorageIdDatabase storageIdDatabase = DatabaseFactory.getUnknownStorageIdDatabase(context);
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageKey();
|
||||
final Stopwatch stopwatch = new Stopwatch("StorageSync");
|
||||
final SQLiteDatabase db = DatabaseFactory.getInstance(context).getRawDatabase();
|
||||
final SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
final UnknownStorageIdDatabase storageIdDatabase = DatabaseFactory.getUnknownStorageIdDatabase(context);
|
||||
final StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
|
||||
|
||||
boolean needsMultiDeviceSync = false;
|
||||
boolean needsForcePush = false;
|
||||
long localManifestVersion = TextSecurePreferences.getStorageManifestVersion(context);
|
||||
Optional<SignalStorageManifest> remoteManifest = accountManager.getStorageManifestIfDifferentVersion(storageServiceKey, localManifestVersion);
|
||||
long remoteManifestVersion = remoteManifest.transform(SignalStorageManifest::getVersion).or(localManifestVersion);
|
||||
final SignalStorageManifest localManifest = SignalStore.storageService().getManifest();
|
||||
final SignalStorageManifest remoteManifest = accountManager.getStorageManifestIfDifferentVersion(storageServiceKey, localManifest.getVersion()).or(localManifest);
|
||||
|
||||
Recipient self = Recipient.self().fresh();
|
||||
boolean needsMultiDeviceSync = false;
|
||||
boolean needsForcePush = false;
|
||||
|
||||
stopwatch.split("remote-manifest");
|
||||
|
||||
Log.i(TAG, "Our version: " + localManifestVersion + ", their version: " + remoteManifestVersion);
|
||||
Log.i(TAG, "Our version: " + localManifest.getVersion() + ", their version: " + remoteManifest.getVersion());
|
||||
|
||||
if (remoteManifest.isPresent() && remoteManifestVersion > localManifestVersion) {
|
||||
if (remoteManifest.getVersion() > localManifest.getVersion()) {
|
||||
Log.i(TAG, "[Remote Sync] Newer manifest version found!");
|
||||
|
||||
List<StorageId> localStorageIdsBeforeMerge = getAllLocalStorageIds(context, Recipient.self().fresh());
|
||||
IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.get().getStorageIds(), localStorageIdsBeforeMerge);
|
||||
IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.getStorageIds(), localStorageIdsBeforeMerge);
|
||||
|
||||
if (idDifference.hasTypeMismatches()) {
|
||||
Log.w(TAG, "[Remote Sync] Found type mismatches in the ID sets! Scheduling a force push after this sync completes.");
|
||||
|
@ -247,8 +245,7 @@ public class StorageSyncJob extends BaseJob {
|
|||
stopwatch.split("remote-records");
|
||||
|
||||
if (remoteOnly.size() != idDifference.getRemoteOnlyIds().size()) {
|
||||
Log.w(TAG, "[Remote Sync] Could not find all remote-only records! Requested: " + idDifference.getRemoteOnlyIds().size() + ", Found: " + remoteOnly.size() + ". Scheduling a force push after this sync completes.");
|
||||
needsForcePush = true;
|
||||
Log.w(TAG, "[Remote Sync] Could not find all remote-only records! Requested: " + idDifference.getRemoteOnlyIds().size() + ", Found: " + remoteOnly.size() + ". These stragglers should naturally get deleted during the sync.");
|
||||
}
|
||||
|
||||
List<SignalContactRecord> remoteContacts = new LinkedList<>();
|
||||
|
@ -271,8 +268,6 @@ public class StorageSyncJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
WriteOperationResult mergeWriteOperation;
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
new ContactRecordProcessor(context, self).process(remoteContacts, StorageSyncHelper.KEY_GENERATOR);
|
||||
|
@ -285,150 +280,73 @@ public class StorageSyncJob extends BaseJob {
|
|||
List<SignalStorageRecord> unknownInserts = remoteUnknown;
|
||||
List<StorageId> unknownDeletes = Stream.of(idDifference.getLocalOnlyIds()).filter(StorageId::isUnknown).toList();
|
||||
|
||||
storageIdDatabase.insert(unknownInserts);
|
||||
storageIdDatabase.delete(unknownDeletes);
|
||||
|
||||
Log.i(TAG, "[Remote Sync] Unknowns :: " + unknownInserts.size() + " inserts, " + unknownDeletes.size() + " deletes");
|
||||
|
||||
List<StorageId> localStorageIdsAfterMerge = getAllLocalStorageIds(context, Recipient.self().fresh());
|
||||
Set<StorageId> localIdsAdded = SetUtil.difference(localStorageIdsAfterMerge, localStorageIdsBeforeMerge);
|
||||
Set<StorageId> localIdsRemoved = SetUtil.difference(localStorageIdsBeforeMerge, localStorageIdsAfterMerge);
|
||||
|
||||
Log.i(TAG, "[Remote Sync] Local ID Changes :: " + localIdsAdded.size() + " inserts, " + localIdsRemoved.size() + " deletes");
|
||||
|
||||
IdDifferenceResult postMergeIdDifference = StorageSyncHelper.findIdDifference(remoteManifest.get().getStorageIds(), localStorageIdsAfterMerge);
|
||||
List<SignalStorageRecord> remoteInserts = buildLocalStorageRecords(context, self, postMergeIdDifference.getLocalOnlyIds());
|
||||
List<byte[]> remoteDeletes = Stream.of(postMergeIdDifference.getRemoteOnlyIds()).map(StorageId::getRaw).toList();
|
||||
|
||||
Log.i(TAG, "[Remote Sync] Post-Merge ID Difference :: " + postMergeIdDifference);
|
||||
|
||||
mergeWriteOperation = new WriteOperationResult(new SignalStorageManifest(remoteManifestVersion + 1, localStorageIdsAfterMerge),
|
||||
remoteInserts,
|
||||
remoteDeletes);
|
||||
|
||||
recipientDatabase.clearDirtyStateForStorageIds(Util.concatenatedList(localIdsAdded, localIdsRemoved));
|
||||
storageIdDatabase.insert(unknownInserts);
|
||||
storageIdDatabase.delete(unknownDeletes);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners();
|
||||
stopwatch.split("remote-merge-transaction");
|
||||
}
|
||||
|
||||
stopwatch.split("remote-merge-transaction");
|
||||
|
||||
Log.i(TAG, "[Remote Sync] WriteOperationResult :: " + mergeWriteOperation);
|
||||
|
||||
if (!mergeWriteOperation.isEmpty()) {
|
||||
Log.i(TAG, "[Remote Sync] We have something to write remotely.");
|
||||
|
||||
StorageSyncValidations.validate(mergeWriteOperation, remoteManifest, needsForcePush, self);
|
||||
|
||||
Optional<SignalStorageManifest> conflict = accountManager.writeStorageRecords(storageServiceKey, mergeWriteOperation.getManifest(), mergeWriteOperation.getInserts(), mergeWriteOperation.getDeletes());
|
||||
|
||||
if (conflict.isPresent()) {
|
||||
Log.w(TAG, "[Remote Sync] Hit a conflict when trying to resolve the conflict! Retrying.");
|
||||
throw new RetryLaterException();
|
||||
}
|
||||
|
||||
stopwatch.split("remote-merge-write");
|
||||
|
||||
remoteManifestVersion = mergeWriteOperation.getManifest().getVersion();
|
||||
remoteManifest = Optional.of(mergeWriteOperation.getManifest());
|
||||
|
||||
needsMultiDeviceSync = true;
|
||||
} else {
|
||||
Log.i(TAG, "[Remote Sync] No remote writes needed.");
|
||||
}
|
||||
|
||||
Log.i(TAG, "[Remote Sync] Updating local manifest version to: " + remoteManifestVersion);
|
||||
TextSecurePreferences.setStorageManifestVersion(context, remoteManifestVersion);
|
||||
} else {
|
||||
Log.i(TAG, "[Remote Sync] Remote version was newer, there were no remote-only IDs.");
|
||||
Log.i(TAG, "[Remote Sync] Updating local manifest version to: " + remoteManifest.get().getVersion());
|
||||
TextSecurePreferences.setStorageManifestVersion(context, remoteManifest.get().getVersion());
|
||||
Log.i(TAG, "[Remote Sync] Remote version was newer, but there were no remote-only IDs.");
|
||||
}
|
||||
} else if (remoteManifest.isPresent() && remoteManifestVersion < localManifestVersion) {
|
||||
Log.w(TAG, "[Remote Sync] Remote version was older. User might have switched accounts. Making our version match.");
|
||||
TextSecurePreferences.setStorageManifestVersion(context, remoteManifestVersion);
|
||||
} else if (remoteManifest.getVersion() < localManifest.getVersion()) {
|
||||
Log.w(TAG, "[Remote Sync] Remote version was older. User might have switched accounts.");
|
||||
}
|
||||
|
||||
localManifestVersion = TextSecurePreferences.getStorageManifestVersion(context);
|
||||
if (remoteManifest != localManifest) {
|
||||
Log.i(TAG, "[Remote Sync] Saved new manifest. Now at version: " + remoteManifest.getVersion());
|
||||
SignalStore.storageService().setManifest(remoteManifest);
|
||||
}
|
||||
|
||||
List<StorageId> allLocalStorageIds;
|
||||
List<RecipientSettings> pendingUpdates;
|
||||
List<RecipientSettings> pendingInsertions;
|
||||
List<RecipientSettings> pendingDeletions;
|
||||
Optional<SignalAccountRecord> pendingAccountInsert;
|
||||
Optional<SignalAccountRecord> pendingAccountUpdate;
|
||||
Optional<LocalWriteResult> localWriteResult;
|
||||
Log.i(TAG, "We are up-to-date with the remote storage state.");
|
||||
|
||||
final WriteOperationResult remoteWriteOperation;
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
allLocalStorageIds = getAllLocalStorageIds(context, self);
|
||||
pendingUpdates = recipientDatabase.getPendingRecipientSyncUpdates();
|
||||
pendingInsertions = recipientDatabase.getPendingRecipientSyncInsertions();
|
||||
pendingDeletions = recipientDatabase.getPendingRecipientSyncDeletions();
|
||||
pendingAccountInsert = StorageSyncHelper.getPendingAccountSyncInsert(context, self);
|
||||
pendingAccountUpdate = StorageSyncHelper.getPendingAccountSyncUpdate(context, self);
|
||||
localWriteResult = StorageSyncHelper.buildStorageUpdatesForLocal(localManifestVersion,
|
||||
allLocalStorageIds,
|
||||
pendingUpdates,
|
||||
pendingInsertions,
|
||||
pendingDeletions,
|
||||
pendingAccountUpdate,
|
||||
pendingAccountInsert);
|
||||
List<StorageId> localStorageIds = getAllLocalStorageIds(context, Recipient.self().fresh());
|
||||
IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.getStorageIds(), localStorageIds);
|
||||
List<SignalStorageRecord> remoteInserts = buildLocalStorageRecords(context, self, idDifference.getLocalOnlyIds());
|
||||
List<byte[]> remoteDeletes = Stream.of(idDifference.getRemoteOnlyIds()).map(StorageId::getRaw).toList();
|
||||
|
||||
Log.i(TAG, "ID Difference :: " + idDifference);
|
||||
|
||||
remoteWriteOperation = new WriteOperationResult(new SignalStorageManifest(remoteManifest.getVersion() + 1, localStorageIds),
|
||||
remoteInserts,
|
||||
remoteDeletes);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
stopwatch.split("local-data-transaction");
|
||||
}
|
||||
|
||||
stopwatch.split("local-changes-transaction");
|
||||
if (!remoteWriteOperation.isEmpty()) {
|
||||
Log.i(TAG, "We have something to write remotely.");
|
||||
Log.i(TAG, "WriteOperationResult :: " + remoteWriteOperation);
|
||||
|
||||
if (localWriteResult.isPresent()) {
|
||||
Log.i(TAG, String.format(Locale.ENGLISH, "[Local Changes] Local changes present. %d updates, %d inserts, %d deletes, account update: %b, account insert: %b.", pendingUpdates.size(), pendingInsertions.size(), pendingDeletions.size(), pendingAccountUpdate.isPresent(), pendingAccountInsert.isPresent()));
|
||||
StorageSyncValidations.validate(remoteWriteOperation, remoteManifest, needsForcePush, self);
|
||||
|
||||
WriteOperationResult localWrite = localWriteResult.get().getWriteResult();
|
||||
|
||||
Log.i(TAG, "[Local Changes] WriteOperationResult :: " + localWrite);
|
||||
|
||||
if (localWrite.isEmpty()) {
|
||||
throw new AssertionError("Decided there were local writes, but our write result was empty!");
|
||||
}
|
||||
|
||||
StorageSyncValidations.validate(localWrite, Optional.absent(), needsForcePush, self);
|
||||
|
||||
Optional<SignalStorageManifest> conflict = accountManager.writeStorageRecords(storageServiceKey, localWrite.getManifest(), localWrite.getInserts(), localWrite.getDeletes());
|
||||
Optional<SignalStorageManifest> conflict = accountManager.writeStorageRecords(storageServiceKey, remoteWriteOperation.getManifest(), remoteWriteOperation.getInserts(), remoteWriteOperation.getDeletes());
|
||||
|
||||
if (conflict.isPresent()) {
|
||||
Log.w(TAG, "[Local Changes] Hit a conflict when trying to upload our local writes! Retrying.");
|
||||
Log.w(TAG, "Hit a conflict when trying to resolve the conflict! Retrying.");
|
||||
throw new RetryLaterException();
|
||||
}
|
||||
|
||||
stopwatch.split("remote-change-write");
|
||||
Log.i(TAG, "Saved new manifest. Now at version: " + remoteWriteOperation.getManifest().getVersion());
|
||||
SignalStore.storageService().setManifest(remoteWriteOperation.getManifest());
|
||||
|
||||
List<RecipientId> clearIds = Util.concatenatedList(Stream.of(pendingUpdates).map(RecipientSettings::getId).toList(),
|
||||
Stream.of(pendingInsertions).map(RecipientSettings::getId).toList(),
|
||||
Stream.of(pendingDeletions).map(RecipientSettings::getId).toList(),
|
||||
Collections.singletonList(Recipient.self().getId()));
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
recipientDatabase.clearDirtyState(clearIds);
|
||||
recipientDatabase.updateStorageIds(localWriteResult.get().getStorageKeyUpdates());
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
stopwatch.split("local-db-clean");
|
||||
stopwatch.split("remote-write");
|
||||
|
||||
needsMultiDeviceSync = true;
|
||||
|
||||
Log.i(TAG, "[Local Changes] Updating local manifest version to: " + localWriteResult.get().getWriteResult().getManifest().getVersion());
|
||||
TextSecurePreferences.setStorageManifestVersion(context, localWriteResult.get().getWriteResult().getManifest().getVersion());
|
||||
} else {
|
||||
Log.i(TAG, "[Local Changes] No local changes.");
|
||||
Log.i(TAG, "No remote writes needed. Still at version: " + remoteManifest.getVersion());
|
||||
}
|
||||
|
||||
if (needsForcePush) {
|
||||
|
|
|
@ -62,7 +62,7 @@ public final class SignalStore {
|
|||
registrationValues().onFirstEverAppLaunch();
|
||||
pinValues().onFirstEverAppLaunch();
|
||||
remoteConfigValues().onFirstEverAppLaunch();
|
||||
storageServiceValues().onFirstEverAppLaunch();
|
||||
storageService().onFirstEverAppLaunch();
|
||||
uiHints().onFirstEverAppLaunch();
|
||||
tooltips().onFirstEverAppLaunch();
|
||||
misc().onFirstEverAppLaunch();
|
||||
|
@ -83,7 +83,7 @@ public final class SignalStore {
|
|||
keys.addAll(registrationValues().getKeysToIncludeInBackup());
|
||||
keys.addAll(pinValues().getKeysToIncludeInBackup());
|
||||
keys.addAll(remoteConfigValues().getKeysToIncludeInBackup());
|
||||
keys.addAll(storageServiceValues().getKeysToIncludeInBackup());
|
||||
keys.addAll(storageService().getKeysToIncludeInBackup());
|
||||
keys.addAll(uiHints().getKeysToIncludeInBackup());
|
||||
keys.addAll(tooltips().getKeysToIncludeInBackup());
|
||||
keys.addAll(misc().getKeysToIncludeInBackup());
|
||||
|
@ -124,7 +124,7 @@ public final class SignalStore {
|
|||
return INSTANCE.remoteConfigValues;
|
||||
}
|
||||
|
||||
public static @NonNull StorageServiceValues storageServiceValues() {
|
||||
public static @NonNull StorageServiceValues storageService() {
|
||||
return INSTANCE.storageServiceValues;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -11,6 +13,7 @@ public class StorageServiceValues extends SignalStoreValues {
|
|||
|
||||
private static final String LAST_SYNC_TIME = "storage.last_sync_time";
|
||||
private static final String NEEDS_ACCOUNT_RESTORE = "storage.needs_account_restore";
|
||||
private static final String MANIFEST = "storage.manifest";
|
||||
|
||||
StorageServiceValues(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
|
@ -44,4 +47,18 @@ public class StorageServiceValues extends SignalStoreValues {
|
|||
public void setNeedsAccountRestore(boolean value) {
|
||||
putBoolean(NEEDS_ACCOUNT_RESTORE, value);
|
||||
}
|
||||
|
||||
public void setManifest(@NonNull SignalStorageManifest manifest) {
|
||||
putBlob(MANIFEST, manifest.serialize());
|
||||
}
|
||||
|
||||
public @NonNull SignalStorageManifest getManifest() {
|
||||
byte[] data = getBlob(MANIFEST, null);
|
||||
|
||||
if (data != null) {
|
||||
return SignalStorageManifest.deserialize(data);
|
||||
} else {
|
||||
return SignalStorageManifest.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public class LogSectionPin implements LogSection {
|
|||
.append("Signal PIN: ").append(SignalStore.kbsValues().hasPin()).append("\n")
|
||||
.append("Opted Out: ").append(SignalStore.kbsValues().hasOptedOut()).append("\n")
|
||||
.append("Last Creation Failed: ").append(SignalStore.kbsValues().lastPinCreateFailed()).append("\n")
|
||||
.append("Needs Account Restore: ").append(SignalStore.storageServiceValues().needsAccountRestore()).append("\n")
|
||||
.append("Needs Account Restore: ").append(SignalStore.storageService().needsAccountRestore()).append("\n")
|
||||
.append("PIN Required at Registration: ").append(SignalStore.registrationValues().pinWasRequiredAtRegistration()).append("\n")
|
||||
.append("Registration Complete: ").append(SignalStore.registrationValues().isRegistrationComplete());
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ public class PinRestoreViewModel extends ViewModel {
|
|||
switch (result.getResult()) {
|
||||
case SUCCESS:
|
||||
SignalStore.pinValues().setKeyboardType(pinKeyboardType);
|
||||
SignalStore.storageServiceValues().setNeedsAccountRestore(false);
|
||||
SignalStore.storageService().setNeedsAccountRestore(false);
|
||||
event.postValue(Event.SUCCESS);
|
||||
break;
|
||||
case LOCKED:
|
||||
|
|
|
@ -126,7 +126,7 @@ public final class PinState {
|
|||
Log.i(TAG, "Has a PIN to restore.");
|
||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
|
||||
SignalStore.storageServiceValues().setNeedsAccountRestore(true);
|
||||
SignalStore.storageService().setNeedsAccountRestore(true);
|
||||
} else {
|
||||
Log.i(TAG, "No registration lock or PIN at all.");
|
||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||
|
@ -145,7 +145,7 @@ public final class PinState {
|
|||
SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
SignalStore.storageServiceValues().setNeedsAccountRestore(false);
|
||||
SignalStore.storageService().setNeedsAccountRestore(false);
|
||||
resetPinRetryCount(context, pin);
|
||||
ClearFallbackKbsEnclaveJob.clearAll();
|
||||
|
||||
|
@ -157,7 +157,7 @@ public final class PinState {
|
|||
*/
|
||||
public static synchronized void onPinRestoreForgottenOrSkipped() {
|
||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||
SignalStore.storageServiceValues().setNeedsAccountRestore(false);
|
||||
SignalStore.storageService().setNeedsAccountRestore(false);
|
||||
|
||||
updateState(buildInferredStateFromOtherFields());
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ public final class RegistrationCompleteFragment extends BaseRegistrationFragment
|
|||
|
||||
FragmentActivity activity = requireActivity();
|
||||
|
||||
if (SignalStore.storageServiceValues().needsAccountRestore()) {
|
||||
if (SignalStore.storageService().needsAccountRestore()) {
|
||||
activity.startActivity(new Intent(activity, PinRestoreActivity.class));
|
||||
} else if (!isReregister()) {
|
||||
final Intent main = MainActivity.clearTop(activity);
|
||||
|
|
|
@ -57,123 +57,6 @@ public final class StorageSyncHelper {
|
|||
|
||||
private static final long REFRESH_INTERVAL = TimeUnit.HOURS.toMillis(2);
|
||||
|
||||
/**
|
||||
* Given the local state of pending storage mutations, this will generate a result that will
|
||||
* include that data that needs to be written to the storage service, as well as any changes you
|
||||
* need to write back to local storage (like storage keys that might have changed for updated
|
||||
* contacts).
|
||||
*
|
||||
* @param currentManifestVersion What you think the version is locally.
|
||||
* @param currentLocalKeys All local keys you have. This assumes that 'inserts' were given keys
|
||||
* already, and that deletes still have keys.
|
||||
* @param updates Contacts that have been altered.
|
||||
* @param inserts Contacts that have been inserted (or newly marked as registered).
|
||||
* @param deletes Contacts that are no longer registered.
|
||||
*
|
||||
* @return If changes need to be written, then it will return those changes. If no changes need
|
||||
* to be written, this will return {@link Optional#absent()}.
|
||||
*/
|
||||
public static @NonNull Optional<LocalWriteResult> buildStorageUpdatesForLocal(long currentManifestVersion,
|
||||
@NonNull List<StorageId> currentLocalKeys,
|
||||
@NonNull List<RecipientSettings> updates,
|
||||
@NonNull List<RecipientSettings> inserts,
|
||||
@NonNull List<RecipientSettings> deletes,
|
||||
@NonNull Optional<SignalAccountRecord> accountUpdate,
|
||||
@NonNull Optional<SignalAccountRecord> accountInsert)
|
||||
{
|
||||
int accountCount = Stream.of(currentLocalKeys)
|
||||
.filter(id -> id.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE)
|
||||
.toList()
|
||||
.size();
|
||||
|
||||
if (accountCount > 1) {
|
||||
throw new MultipleExistingAccountsException();
|
||||
}
|
||||
|
||||
Optional<StorageId> accountId = Optional.fromNullable(Stream.of(currentLocalKeys)
|
||||
.filter(id -> id.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE)
|
||||
.findFirst()
|
||||
.orElse(null));
|
||||
|
||||
|
||||
if (accountId.isPresent() && accountInsert.isPresent() && !accountInsert.get().getId().equals(accountId.get())) {
|
||||
throw new InvalidAccountInsertException();
|
||||
}
|
||||
|
||||
if (accountId.isPresent() && accountUpdate.isPresent() && !accountUpdate.get().getId().equals(accountId.get())) {
|
||||
throw new InvalidAccountUpdateException();
|
||||
}
|
||||
|
||||
if (accountUpdate.isPresent() && accountInsert.isPresent()) {
|
||||
throw new InvalidAccountDualInsertUpdateException();
|
||||
}
|
||||
|
||||
Set<StorageId> completeIds = new LinkedHashSet<>(currentLocalKeys);
|
||||
Set<SignalStorageRecord> storageInserts = new LinkedHashSet<>();
|
||||
Set<ByteBuffer> storageDeletes = new LinkedHashSet<>();
|
||||
Map<RecipientId, byte[]> storageKeyUpdates = new HashMap<>();
|
||||
|
||||
for (RecipientSettings insert : inserts) {
|
||||
if (insert.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && insert.getSyncExtras().getGroupMasterKey() == null) {
|
||||
Log.w(TAG, "Missing master key on gv2 recipient");
|
||||
continue;
|
||||
}
|
||||
|
||||
SignalStorageRecord insertRecord = StorageSyncModels.localToRemoteRecord(insert);
|
||||
storageInserts.add(insertRecord);
|
||||
completeIds.add(insertRecord.getId());
|
||||
}
|
||||
|
||||
if (accountInsert.isPresent()) {
|
||||
storageInserts.add(SignalStorageRecord.forAccount(accountInsert.get()));
|
||||
completeIds.add(accountInsert.get().getId());
|
||||
}
|
||||
|
||||
for (RecipientSettings delete : deletes) {
|
||||
byte[] key = Objects.requireNonNull(delete.getStorageId());
|
||||
storageDeletes.add(ByteBuffer.wrap(key));
|
||||
completeIds.removeIf(id -> Arrays.equals(id.getRaw(), key));
|
||||
}
|
||||
|
||||
for (RecipientSettings update : updates) {
|
||||
byte[] oldId = update.getStorageId();
|
||||
byte[] newId = generateKey();
|
||||
|
||||
SignalStorageRecord insert = StorageSyncModels.localToRemoteRecord(update, newId);
|
||||
|
||||
storageInserts.add(insert);
|
||||
storageDeletes.add(ByteBuffer.wrap(oldId));
|
||||
|
||||
completeIds.add(insert.getId());
|
||||
completeIds.removeIf(id -> Arrays.equals(id.getRaw(), oldId));
|
||||
|
||||
storageKeyUpdates.put(update.getId(), newId);
|
||||
}
|
||||
|
||||
if (accountUpdate.isPresent()) {
|
||||
StorageId oldId = accountUpdate.get().getId();
|
||||
StorageId newId = StorageId.forAccount(generateKey());
|
||||
|
||||
storageInserts.add(SignalStorageRecord.forAccount(newId, accountUpdate.get()));
|
||||
storageDeletes.add(ByteBuffer.wrap(oldId.getRaw()));
|
||||
|
||||
completeIds.remove(oldId);
|
||||
completeIds.add(newId);
|
||||
|
||||
storageKeyUpdates.put(Recipient.self().getId(), newId.getRaw());
|
||||
}
|
||||
|
||||
if (storageInserts.isEmpty() && storageDeletes.isEmpty()) {
|
||||
return Optional.absent();
|
||||
} else {
|
||||
List<byte[]> storageDeleteBytes = Stream.of(storageDeletes).map(ByteBuffer::array).toList();
|
||||
SignalStorageManifest manifest = new SignalStorageManifest(currentManifestVersion + 1, new ArrayList<>(completeIds));
|
||||
WriteOperationResult writeOperationResult = new WriteOperationResult(manifest, new ArrayList<>(storageInserts), storageDeleteBytes);
|
||||
|
||||
return Optional.of(new LocalWriteResult(writeOperationResult, storageKeyUpdates));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of all the local and remote keys you know about, this will return a result telling
|
||||
* you which keys are exclusively remote and which are exclusively local.
|
||||
|
@ -227,20 +110,6 @@ public final class StorageSyncHelper {
|
|||
return !OptionalUtil.byteArrayEquals(update.getOld().getProfileKey(), update.getNew().getProfileKey());
|
||||
}
|
||||
|
||||
public static Optional<SignalAccountRecord> getPendingAccountSyncUpdate(@NonNull Context context, @NonNull Recipient self) {
|
||||
if (DatabaseFactory.getRecipientDatabase(context).getDirtyState(self.getId()) != RecipientDatabase.DirtyState.UPDATE) {
|
||||
return Optional.absent();
|
||||
}
|
||||
return Optional.of(buildAccountRecord(context, self).getAccount().get());
|
||||
}
|
||||
|
||||
public static Optional<SignalAccountRecord> getPendingAccountSyncInsert(@NonNull Context context, @NonNull Recipient self) {
|
||||
if (DatabaseFactory.getRecipientDatabase(context).getDirtyState(self.getId()) != RecipientDatabase.DirtyState.INSERT) {
|
||||
return Optional.absent();
|
||||
}
|
||||
return Optional.of(buildAccountRecord(context, self).getAccount().get());
|
||||
}
|
||||
|
||||
public static SignalStorageRecord buildAccountRecord(@NonNull Context context, @NonNull Recipient self) {
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
RecipientSettings settings = recipientDatabase.getRecipientSettingsForSync(self.getId());
|
||||
|
@ -296,7 +165,7 @@ public final class StorageSyncHelper {
|
|||
}
|
||||
|
||||
public static void scheduleRoutineSync() {
|
||||
long timeSinceLastSync = System.currentTimeMillis() - SignalStore.storageServiceValues().getLastSyncTime();
|
||||
long timeSinceLastSync = System.currentTimeMillis() - SignalStore.storageService().getLastSyncTime();
|
||||
|
||||
if (timeSinceLastSync > REFRESH_INTERVAL) {
|
||||
Log.d(TAG, "Scheduling a sync. Last sync was " + timeSinceLastSync + " ms ago.");
|
||||
|
@ -390,27 +259,4 @@ public final class StorageSyncHelper {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocalWriteResult {
|
||||
private final WriteOperationResult writeResult;
|
||||
private final Map<RecipientId, byte[]> storageKeyUpdates;
|
||||
|
||||
private LocalWriteResult(WriteOperationResult writeResult, Map<RecipientId, byte[]> storageKeyUpdates) {
|
||||
this.writeResult = writeResult;
|
||||
this.storageKeyUpdates = storageKeyUpdates;
|
||||
}
|
||||
|
||||
public @NonNull WriteOperationResult getWriteResult() {
|
||||
return writeResult;
|
||||
}
|
||||
|
||||
public @NonNull Map<RecipientId, byte[]> getStorageKeyUpdates() {
|
||||
return storageKeyUpdates;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MultipleExistingAccountsException extends IllegalArgumentException {}
|
||||
private static final class InvalidAccountInsertException extends IllegalArgumentException {}
|
||||
private static final class InvalidAccountUpdateException extends IllegalArgumentException {}
|
||||
private static final class InvalidAccountDualInsertUpdateException extends IllegalArgumentException {}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public final class StorageSyncValidations {
|
|||
private StorageSyncValidations() {}
|
||||
|
||||
public static void validate(@NonNull StorageSyncHelper.WriteOperationResult result,
|
||||
@NonNull Optional<SignalStorageManifest> previousManifest,
|
||||
@NonNull SignalStorageManifest previousManifest,
|
||||
boolean forcePushPending,
|
||||
@NonNull Recipient self)
|
||||
{
|
||||
|
@ -47,12 +47,12 @@ public final class StorageSyncValidations {
|
|||
}
|
||||
}
|
||||
|
||||
if (!previousManifest.isPresent()) {
|
||||
Log.i(TAG, "No previous manifest, not bothering with additional validations around the diffs between the two manifests.");
|
||||
if (previousManifest.getVersion() == 0) {
|
||||
Log.i(TAG, "Previous manifest is empty, not bothering with additional validations around the diffs between the two manifests.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.getManifest().getVersion() != previousManifest.get().getVersion() + 1) {
|
||||
if (result.getManifest().getVersion() != previousManifest.getVersion() + 1) {
|
||||
throw new IncorrectManifestVersionError();
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ public final class StorageSyncValidations {
|
|||
return;
|
||||
}
|
||||
|
||||
Set<ByteBuffer> previousIds = Stream.of(previousManifest.get().getStorageIds()).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet());
|
||||
Set<ByteBuffer> previousIds = Stream.of(previousManifest.getStorageIds()).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet());
|
||||
Set<ByteBuffer> newIds = Stream.of(result.getManifest().getStorageIds()).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet());
|
||||
|
||||
Set<ByteBuffer> manifestInserts = SetUtil.difference(newIds, previousIds);
|
||||
|
|
|
@ -1224,10 +1224,6 @@ public class TextSecurePreferences {
|
|||
setBooleanPreference(context, HAS_SEEN_VIDEO_RECORDING_TOOLTIP, value);
|
||||
}
|
||||
|
||||
public static long getStorageManifestVersion(Context context) {
|
||||
return getLongPreference(context, STORAGE_MANIFEST_VERSION, 0);
|
||||
}
|
||||
|
||||
public static void setStorageManifestVersion(Context context, long version) {
|
||||
setLongPreference(context, STORAGE_MANIFEST_VERSION, version);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StorageManifest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SignalStorageManifest {
|
||||
public static final SignalStorageManifest EMPTY = new SignalStorageManifest(0, Collections.emptyList());
|
||||
|
||||
private final long version;
|
||||
private final List<StorageId> storageIds;
|
||||
private final Map<Integer, List<StorageId>> storageIdsByType;
|
||||
|
@ -30,6 +36,22 @@ public class SignalStorageManifest {
|
|||
}
|
||||
}
|
||||
|
||||
public static SignalStorageManifest deserialize(byte[] serialized) {
|
||||
try {
|
||||
StorageManifest manifest = StorageManifest.parseFrom(serialized);
|
||||
ManifestRecord manifestRecord = ManifestRecord.parseFrom(manifest.getValue());
|
||||
List<StorageId> ids = new ArrayList<>(manifestRecord.getIdentifiersCount());
|
||||
|
||||
for (ManifestRecord.Identifier id : manifestRecord.getIdentifiersList()) {
|
||||
ids.add(StorageId.forType(id.getRaw().toByteArray(), id.getTypeValue()));
|
||||
}
|
||||
|
||||
return new SignalStorageManifest(manifest.getVersion(), ids);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
@ -47,4 +69,23 @@ public class SignalStorageManifest {
|
|||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
List<ManifestRecord.Identifier> ids = new ArrayList<>(storageIds.size());
|
||||
|
||||
for (StorageId id : storageIds) {
|
||||
ids.add(ManifestRecord.Identifier.newBuilder()
|
||||
.setTypeValue(id.getType())
|
||||
.setRaw(ByteString.copyFrom(id.getRaw()))
|
||||
.build());
|
||||
}
|
||||
|
||||
ManifestRecord manifestRecord = ManifestRecord.newBuilder().addAllIdentifiers(ids).build();
|
||||
|
||||
return StorageManifest.newBuilder()
|
||||
.setVersion(version)
|
||||
.setValue(manifestRecord.toByteString())
|
||||
.build()
|
||||
.toByteArray();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue