Signal-Android/app/src/main/java/org/thoughtcrime/securesms/database/RemappedRecords.java

90 lines
3.0 KiB
Java

package org.thoughtcrime.securesms.database;
import android.content.Context;
import androidx.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Merging together recipients and threads is messy business. We can easily replace *almost* all of
* the references, but there are specific places (notably reactions, jobs, etc) that are really
* expensive to address. For these cases, we keep mappings of old IDs to new ones to use as a
* fallback.
*
* There should be very few of these, so we keep them in a fast, lazily-loaded memory cache.
*
* One important thing to note is that this class will often be accesses inside of database
* transactions. As a result, it cannot attempt to acquire a database lock while holding a
* separate lock. Instead, we use the database lock itself as a locking mechanism.
*/
class RemappedRecords {
private static final RemappedRecords INSTANCE = new RemappedRecords();
private Map<RecipientId, RecipientId> recipientMap;
private Map<Long, Long> threadMap;
private RemappedRecords() {}
static RemappedRecords getInstance() {
return INSTANCE;
}
@NonNull Optional<RecipientId> getRecipient(@NonNull Context context, @NonNull RecipientId oldId) {
ensureRecipientMapIsPopulated(context);
return Optional.fromNullable(recipientMap.get(oldId));
}
@NonNull Optional<Long> getThread(@NonNull Context context, long oldId) {
ensureThreadMapIsPopulated(context);
return Optional.fromNullable(threadMap.get(oldId));
}
/**
* Can only be called inside of a transaction.
*/
void addRecipient(@NonNull Context context, @NonNull RecipientId oldId, @NonNull RecipientId newId) {
ensureInTransaction(context);
ensureRecipientMapIsPopulated(context);
recipientMap.put(oldId, newId);
DatabaseFactory.getRemappedRecordsDatabase(context).addRecipientMapping(oldId, newId);
}
/**
* Can only be called inside of a transaction.
*/
void addThread(@NonNull Context context, long oldId, long newId) {
ensureInTransaction(context);
ensureThreadMapIsPopulated(context);
threadMap.put(oldId, newId);
DatabaseFactory.getRemappedRecordsDatabase(context).addThreadMapping(oldId, newId);
}
private void ensureRecipientMapIsPopulated(@NonNull Context context) {
if (recipientMap == null) {
recipientMap = DatabaseFactory.getRemappedRecordsDatabase(context).getAllRecipientMappings();
}
}
private void ensureThreadMapIsPopulated(@NonNull Context context) {
if (threadMap == null) {
threadMap = DatabaseFactory.getRemappedRecordsDatabase(context).getAllThreadMappings();
}
}
private void ensureInTransaction(@NonNull Context context) {
if (!DatabaseFactory.inTransaction(context)) {
throw new IllegalStateException("Must be in a transaction!");
}
}
}