Convert StoryDistributionListRecordProcessor to kotlin.

This commit is contained in:
Greyson Parrelli
2024-11-07 13:31:06 -05:00
parent befb433999
commit 89767cc260
2 changed files with 194 additions and 189 deletions

View File

@@ -1,189 +0,0 @@
package org.thoughtcrime.securesms.storage;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.StringUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.RecipientRecord;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.storage.SignalStoryDistributionListRecord;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
public class StoryDistributionListRecordProcessor extends DefaultStorageRecordProcessor<SignalStoryDistributionListRecord> {
private static final String TAG = Log.tag(StoryDistributionListRecordProcessor.class);
private boolean haveSeenMyStory;
/**
* At a minimum, we require:
* <ul>
* <li>A valid identifier</li>
* <li>A non-visually-empty name field OR a deleted at timestamp</li>
* </ul>
*/
@Override
public boolean isInvalid(@NonNull SignalStoryDistributionListRecord remote) {
UUID remoteUuid = UuidUtil.parseOrNull(remote.getIdentifier());
if (remoteUuid == null) {
Log.d(TAG, "Bad distribution list identifier -- marking as invalid");
return true;
}
boolean isMyStory = remoteUuid.equals(DistributionId.MY_STORY.asUuid());
if (haveSeenMyStory && isMyStory) {
Log.w(TAG, "Found an additional MyStory record -- marking as invalid");
return true;
}
haveSeenMyStory |= isMyStory;
if (remote.getDeletedAtTimestamp() > 0L) {
if (isMyStory) {
Log.w(TAG, "Refusing to delete My Story -- marking as invalid");
return true;
} else {
return false;
}
}
if (StringUtil.isVisuallyEmpty(remote.getName())) {
Log.d(TAG, "Bad distribution list name (visually empty) -- marking as invalid");
return true;
}
return false;
}
@Override
public @NonNull Optional<SignalStoryDistributionListRecord> getMatching(@NonNull SignalStoryDistributionListRecord remote, @NonNull StorageKeyGenerator keyGenerator) {
Log.d(TAG, "Attempting to get matching record...");
RecipientId matching = SignalDatabase.distributionLists().getRecipientIdForSyncRecord(remote);
if (matching == null && UuidUtil.parseOrThrow(remote.getIdentifier()).equals(DistributionId.MY_STORY.asUuid())) {
Log.e(TAG, "Cannot find matching database record for My Story.");
throw new MyStoryDoesNotExistException();
}
if (matching != null) {
Log.d(TAG, "Found a matching RecipientId for the distribution list...");
RecipientRecord recordForSync = SignalDatabase.recipients().getRecordForSync(matching);
if (recordForSync == null) {
Log.e(TAG, "Could not find a record for the recipient id in the recipient table");
throw new IllegalStateException("Found matching recipient but couldn't generate record for sync.");
}
if (recordForSync.getRecipientType().getId() != RecipientTable.RecipientType.DISTRIBUTION_LIST.getId()) {
Log.d(TAG, "Record has an incorrect group type.");
throw new InvalidGroupTypeException();
}
Optional<SignalStoryDistributionListRecord> record = StorageSyncModels.localToRemoteRecord(recordForSync).getStoryDistributionList();
if (record.isPresent()) {
Log.d(TAG, "Found a matching record.");
return record;
} else {
Log.e(TAG, "Could not resolve the record");
throw new UnexpectedEmptyOptionalException();
}
} else {
Log.d(TAG, "Could not find a matching record. Returning an empty.");
return Optional.empty();
}
}
@Override
public @NonNull SignalStoryDistributionListRecord merge(@NonNull SignalStoryDistributionListRecord remote, @NonNull SignalStoryDistributionListRecord local, @NonNull StorageKeyGenerator keyGenerator) {
byte[] unknownFields = remote.serializeUnknownFields();
byte[] identifier = remote.getIdentifier();
String name = remote.getName();
List<SignalServiceAddress> recipients = remote.getRecipients();
long deletedAtTimestamp = remote.getDeletedAtTimestamp();
boolean allowsReplies = remote.allowsReplies();
boolean isBlockList = remote.isBlockList();
boolean matchesRemote = doParamsMatch(remote, unknownFields, identifier, name, recipients, deletedAtTimestamp, allowsReplies, isBlockList);
boolean matchesLocal = doParamsMatch(local, unknownFields, identifier, name, recipients, deletedAtTimestamp, allowsReplies, isBlockList);
if (matchesRemote) {
return remote;
} else if (matchesLocal) {
return local;
} else {
return new SignalStoryDistributionListRecord.Builder(keyGenerator.generate(), unknownFields)
.setIdentifier(identifier)
.setName(name)
.setRecipients(recipients)
.setDeletedAtTimestamp(deletedAtTimestamp)
.setAllowsReplies(allowsReplies)
.setIsBlockList(isBlockList)
.build();
}
}
@Override
public void insertLocal(@NonNull SignalStoryDistributionListRecord record) throws IOException {
SignalDatabase.distributionLists().applyStorageSyncStoryDistributionListInsert(record);
}
@Override
public void updateLocal(@NonNull StorageRecordUpdate<SignalStoryDistributionListRecord> update) {
SignalDatabase.distributionLists().applyStorageSyncStoryDistributionListUpdate(update);
}
@Override
public int compare(SignalStoryDistributionListRecord o1, SignalStoryDistributionListRecord o2) {
if (Arrays.equals(o1.getIdentifier(), o2.getIdentifier())) {
return 0;
} else {
return 1;
}
}
private boolean doParamsMatch(@NonNull SignalStoryDistributionListRecord record,
@Nullable byte[] unknownFields,
@Nullable byte[] identifier,
@Nullable String name,
@NonNull List<SignalServiceAddress> recipients,
long deletedAtTimestamp,
boolean allowsReplies,
boolean isBlockList) {
return Arrays.equals(unknownFields, record.serializeUnknownFields()) &&
Arrays.equals(identifier, record.getIdentifier()) &&
Objects.equals(name, record.getName()) &&
Objects.equals(recipients, record.getRecipients()) &&
deletedAtTimestamp == record.getDeletedAtTimestamp() &&
allowsReplies == record.allowsReplies() &&
isBlockList == record.isBlockList();
}
/**
* Thrown when the RecipientSettings object for a given distribution list is not the
* correct group type (4).
*/
private static class InvalidGroupTypeException extends RuntimeException {}
/**
* Thrown when the distribution list object returned from the storage sync helper is
* absent, even though a RecipientSettings was found.
*/
private static class UnexpectedEmptyOptionalException extends RuntimeException {}
/**
* Thrown when we try to ge the matching record for the "My Story" distribution ID but
* it isn't in the database.
*/
private static class MyStoryDoesNotExistException extends RuntimeException {}
}

View File

@@ -0,0 +1,194 @@
package org.thoughtcrime.securesms.storage
import org.signal.core.util.StringUtil
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.api.storage.SignalStoryDistributionListRecord
import org.whispersystems.signalservice.api.util.UuidUtil
import java.io.IOException
import java.util.Optional
class StoryDistributionListRecordProcessor : DefaultStorageRecordProcessor<SignalStoryDistributionListRecord>() {
companion object {
private val TAG = Log.tag(StoryDistributionListRecordProcessor::class.java)
}
private var haveSeenMyStory = false
/**
* At a minimum, we require:
*
* - A valid identifier
* - A non-visually-empty name field OR a deleted at timestamp
*/
override fun isInvalid(remote: SignalStoryDistributionListRecord): Boolean {
val remoteUuid = UuidUtil.parseOrNull(remote.identifier)
if (remoteUuid == null) {
Log.d(TAG, "Bad distribution list identifier -- marking as invalid")
return true
}
val isMyStory = remoteUuid == DistributionId.MY_STORY.asUuid()
if (haveSeenMyStory && isMyStory) {
Log.w(TAG, "Found an additional MyStory record -- marking as invalid")
return true
}
haveSeenMyStory = haveSeenMyStory or isMyStory
if (remote.deletedAtTimestamp > 0L) {
if (isMyStory) {
Log.w(TAG, "Refusing to delete My Story -- marking as invalid")
return true
} else {
return false
}
}
if (StringUtil.isVisuallyEmpty(remote.name)) {
Log.d(TAG, "Bad distribution list name (visually empty) -- marking as invalid")
return true
}
return false
}
override fun getMatching(remote: SignalStoryDistributionListRecord, keyGenerator: StorageKeyGenerator): Optional<SignalStoryDistributionListRecord> {
Log.d(TAG, "Attempting to get matching record...")
val matching = SignalDatabase.distributionLists.getRecipientIdForSyncRecord(remote)
if (matching == null && UuidUtil.parseOrThrow(remote.identifier) == DistributionId.MY_STORY.asUuid()) {
Log.e(TAG, "Cannot find matching database record for My Story.")
throw MyStoryDoesNotExistException()
}
if (matching != null) {
Log.d(TAG, "Found a matching RecipientId for the distribution list...")
val recordForSync = SignalDatabase.recipients.getRecordForSync(matching)
if (recordForSync == null) {
Log.e(TAG, "Could not find a record for the recipient id in the recipient table")
throw IllegalStateException("Found matching recipient but couldn't generate record for sync.")
}
if (recordForSync.recipientType.id != RecipientTable.RecipientType.DISTRIBUTION_LIST.id) {
Log.d(TAG, "Record has an incorrect group type.")
throw InvalidGroupTypeException()
}
val record = StorageSyncModels.localToRemoteRecord(recordForSync).storyDistributionList
if (record.isPresent) {
Log.d(TAG, "Found a matching record.")
return record
} else {
Log.e(TAG, "Could not resolve the record")
throw UnexpectedEmptyOptionalException()
}
} else {
Log.d(TAG, "Could not find a matching record. Returning an empty.")
return Optional.empty()
}
}
override fun merge(remote: SignalStoryDistributionListRecord, local: SignalStoryDistributionListRecord, keyGenerator: StorageKeyGenerator): SignalStoryDistributionListRecord {
val unknownFields = remote.serializeUnknownFields()
val identifier = remote.identifier
val name = remote.name
val recipients = remote.recipients
val deletedAtTimestamp = remote.deletedAtTimestamp
val allowsReplies = remote.allowsReplies()
val isBlockList = remote.isBlockList
val matchesRemote = doParamsMatch(
record = remote,
unknownFields = unknownFields,
identifier = identifier,
name = name,
recipients = recipients,
deletedAtTimestamp = deletedAtTimestamp,
allowsReplies = allowsReplies,
isBlockList = isBlockList
)
val matchesLocal = doParamsMatch(
record = local,
unknownFields = unknownFields,
identifier = identifier,
name = name,
recipients = recipients,
deletedAtTimestamp = deletedAtTimestamp,
allowsReplies = allowsReplies,
isBlockList = isBlockList
)
return if (matchesRemote) {
remote
} else if (matchesLocal) {
local
} else {
SignalStoryDistributionListRecord.Builder(keyGenerator.generate(), unknownFields)
.setIdentifier(identifier)
.setName(name)
.setRecipients(recipients)
.setDeletedAtTimestamp(deletedAtTimestamp)
.setAllowsReplies(allowsReplies)
.setIsBlockList(isBlockList)
.build()
}
}
@Throws(IOException::class)
override fun insertLocal(record: SignalStoryDistributionListRecord) {
SignalDatabase.distributionLists.applyStorageSyncStoryDistributionListInsert(record)
}
override fun updateLocal(update: StorageRecordUpdate<SignalStoryDistributionListRecord>) {
SignalDatabase.distributionLists.applyStorageSyncStoryDistributionListUpdate(update)
}
override fun compare(o1: SignalStoryDistributionListRecord, o2: SignalStoryDistributionListRecord): Int {
return if (o1.identifier.contentEquals(o2.identifier)) {
0
} else {
1
}
}
private fun doParamsMatch(
record: SignalStoryDistributionListRecord,
unknownFields: ByteArray?,
identifier: ByteArray?,
name: String?,
recipients: List<SignalServiceAddress>,
deletedAtTimestamp: Long,
allowsReplies: Boolean,
isBlockList: Boolean
): Boolean {
return unknownFields.contentEquals(record.serializeUnknownFields()) &&
identifier.contentEquals(record.identifier) &&
name == record.name &&
recipients == record.recipients &&
deletedAtTimestamp == record.deletedAtTimestamp &&
allowsReplies == record.allowsReplies() &&
isBlockList == record.isBlockList
}
/**
* Thrown when the RecipientSettings object for a given distribution list is not the
* correct group type (4).
*/
private class InvalidGroupTypeException : RuntimeException()
/**
* Thrown when the distribution list object returned from the storage sync helper is
* absent, even though a RecipientSettings was found.
*/
private class UnexpectedEmptyOptionalException : RuntimeException()
/**
* Thrown when we try to ge the matching record for the "My Story" distribution ID but
* it isn't in the database.
*/
private class MyStoryDoesNotExistException : RuntimeException()
}