90 lines
3.0 KiB
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!");
|
|
}
|
|
}
|
|
}
|