Search in sources :

Example 1 with SQLiteDatabase

use of net.zetetic.database.sqlcipher.SQLiteDatabase in project Signal-Android by WhisperSystems.

the class MegaphoneDatabase method getAllAndDeleteMissing.

@NonNull
public List<MegaphoneRecord> getAllAndDeleteMissing() {
    SQLiteDatabase db = getWritableDatabase();
    List<MegaphoneRecord> records = new ArrayList<>();
    db.beginTransaction();
    try {
        Set<String> missingKeys = new HashSet<>();
        try (Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null)) {
            while (cursor != null && cursor.moveToNext()) {
                String event = cursor.getString(cursor.getColumnIndexOrThrow(EVENT));
                int seenCount = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_COUNT));
                long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(LAST_SEEN));
                long firstVisible = cursor.getLong(cursor.getColumnIndexOrThrow(FIRST_VISIBLE));
                boolean finished = cursor.getInt(cursor.getColumnIndexOrThrow(FINISHED)) == 1;
                if (Event.hasKey(event)) {
                    records.add(new MegaphoneRecord(Event.fromKey(event), seenCount, lastSeen, firstVisible, finished));
                } else {
                    Log.w(TAG, "No in-app handing for event '" + event + "'! Deleting it from the database.");
                    missingKeys.add(event);
                }
            }
        }
        for (String missing : missingKeys) {
            String query = EVENT + " = ?";
            String[] args = new String[] { missing };
            db.delete(TABLE_NAME, query, args);
        }
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }
    return records;
}
Also used : MegaphoneRecord(org.thoughtcrime.securesms.database.model.MegaphoneRecord) SQLiteDatabase(net.zetetic.database.sqlcipher.SQLiteDatabase) ArrayList(java.util.ArrayList) Cursor(android.database.Cursor) HashSet(java.util.HashSet) NonNull(androidx.annotation.NonNull)

Example 2 with SQLiteDatabase

use of net.zetetic.database.sqlcipher.SQLiteDatabase in project Signal-Android by WhisperSystems.

the class MegaphoneDatabase method insert.

public void insert(@NonNull Collection<Event> events) {
    SQLiteDatabase db = getWritableDatabase();
    db.beginTransaction();
    try {
        for (Event event : events) {
            ContentValues values = new ContentValues();
            values.put(EVENT, event.getKey());
            db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
        }
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }
}
Also used : ContentValues(android.content.ContentValues) SQLiteDatabase(net.zetetic.database.sqlcipher.SQLiteDatabase) Event(org.thoughtcrime.securesms.megaphone.Megaphones.Event)

Example 3 with SQLiteDatabase

use of net.zetetic.database.sqlcipher.SQLiteDatabase in project Signal-Android by WhisperSystems.

the class JobDatabase method updateJobs.

public synchronized void updateJobs(@NonNull List<JobSpec> jobs) {
    if (Stream.of(jobs).allMatch(JobSpec::isMemoryOnly)) {
        return;
    }
    SQLiteDatabase db = getWritableDatabase();
    db.beginTransaction();
    try {
        Stream.of(jobs).filterNot(JobSpec::isMemoryOnly).forEach(job -> {
            ContentValues values = new ContentValues();
            values.put(Jobs.JOB_SPEC_ID, job.getId());
            values.put(Jobs.FACTORY_KEY, job.getFactoryKey());
            values.put(Jobs.QUEUE_KEY, job.getQueueKey());
            values.put(Jobs.CREATE_TIME, job.getCreateTime());
            values.put(Jobs.NEXT_RUN_ATTEMPT_TIME, job.getNextRunAttemptTime());
            values.put(Jobs.RUN_ATTEMPT, job.getRunAttempt());
            values.put(Jobs.MAX_ATTEMPTS, job.getMaxAttempts());
            values.put(Jobs.LIFESPAN, job.getLifespan());
            values.put(Jobs.SERIALIZED_DATA, job.getSerializedData());
            values.put(Jobs.SERIALIZED_INPUT_DATA, job.getSerializedInputData());
            values.put(Jobs.IS_RUNNING, job.isRunning() ? 1 : 0);
            String query = Jobs.JOB_SPEC_ID + " = ?";
            String[] args = new String[] { job.getId() };
            db.update(Jobs.TABLE_NAME, values, query, args);
        });
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }
}
Also used : ContentValues(android.content.ContentValues) SQLiteDatabase(net.zetetic.database.sqlcipher.SQLiteDatabase) JobSpec(org.thoughtcrime.securesms.jobmanager.persistence.JobSpec)

Example 4 with SQLiteDatabase

use of net.zetetic.database.sqlcipher.SQLiteDatabase in project Signal-Android by WhisperSystems.

the class StorageSyncJob method performSync.

private boolean performSync() throws IOException, RetryLaterException, InvalidKeyException {
    final Stopwatch stopwatch = new Stopwatch("StorageSync");
    final SQLiteDatabase db = SignalDatabase.getRawDatabase();
    final SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
    final UnknownStorageIdDatabase storageIdDatabase = SignalDatabase.unknownStorageIds();
    final StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
    final SignalStorageManifest localManifest = SignalStore.storageService().getManifest();
    final SignalStorageManifest remoteManifest = accountManager.getStorageManifestIfDifferentVersion(storageServiceKey, localManifest.getVersion()).or(localManifest);
    stopwatch.split("remote-manifest");
    Recipient self = freshSelf();
    boolean needsMultiDeviceSync = false;
    boolean needsForcePush = false;
    if (self.getStorageServiceId() == null) {
        Log.w(TAG, "No storageId for self. Generating.");
        SignalDatabase.recipients().updateStorageId(self.getId(), StorageSyncHelper.generateKey());
        self = freshSelf();
    }
    Log.i(TAG, "Our version: " + localManifest.getVersion() + ", their version: " + remoteManifest.getVersion());
    if (remoteManifest.getVersion() > localManifest.getVersion()) {
        Log.i(TAG, "[Remote Sync] Newer manifest version found!");
        List<StorageId> localStorageIdsBeforeMerge = getAllLocalStorageIds(context, self);
        IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.getStorageIds(), localStorageIdsBeforeMerge);
        if (idDifference.hasTypeMismatches() && SignalStore.account().isPrimaryDevice()) {
            Log.w(TAG, "[Remote Sync] Found type mismatches in the ID sets! Scheduling a force push after this sync completes.");
            needsForcePush = true;
        }
        Log.i(TAG, "[Remote Sync] Pre-Merge ID Difference :: " + idDifference);
        stopwatch.split("remote-id-diff");
        if (!idDifference.isEmpty()) {
            Log.i(TAG, "[Remote Sync] Retrieving records for key difference.");
            List<SignalStorageRecord> remoteOnly = accountManager.readStorageRecords(storageServiceKey, idDifference.getRemoteOnlyIds());
            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() + ". These stragglers should naturally get deleted during the sync.");
            }
            List<SignalContactRecord> remoteContacts = new LinkedList<>();
            List<SignalGroupV1Record> remoteGv1 = new LinkedList<>();
            List<SignalGroupV2Record> remoteGv2 = new LinkedList<>();
            List<SignalAccountRecord> remoteAccount = new LinkedList<>();
            List<SignalStorageRecord> remoteUnknown = new LinkedList<>();
            for (SignalStorageRecord remote : remoteOnly) {
                if (remote.getContact().isPresent()) {
                    remoteContacts.add(remote.getContact().get());
                } else if (remote.getGroupV1().isPresent()) {
                    remoteGv1.add(remote.getGroupV1().get());
                } else if (remote.getGroupV2().isPresent()) {
                    remoteGv2.add(remote.getGroupV2().get());
                } else if (remote.getAccount().isPresent()) {
                    remoteAccount.add(remote.getAccount().get());
                } else if (remote.getId().isUnknown()) {
                    remoteUnknown.add(remote);
                } else {
                    Log.w(TAG, "Bad record! Type is a known value (" + remote.getId().getType() + "), but doesn't have a matching inner record. Dropping it.");
                }
            }
            db.beginTransaction();
            try {
                self = freshSelf();
                Log.i(TAG, "[Remote Sync] Remote-Only :: Contacts: " + remoteContacts.size() + ", GV1: " + remoteGv1.size() + ", GV2: " + remoteGv2.size() + ", Account: " + remoteAccount.size());
                new ContactRecordProcessor(context, self).process(remoteContacts, StorageSyncHelper.KEY_GENERATOR);
                new GroupV1RecordProcessor(context).process(remoteGv1, StorageSyncHelper.KEY_GENERATOR);
                new GroupV2RecordProcessor(context).process(remoteGv2, StorageSyncHelper.KEY_GENERATOR);
                self = freshSelf();
                new AccountRecordProcessor(context, self).process(remoteAccount, StorageSyncHelper.KEY_GENERATOR);
                List<SignalStorageRecord> unknownInserts = remoteUnknown;
                List<StorageId> unknownDeletes = Stream.of(idDifference.getLocalOnlyIds()).filter(StorageId::isUnknown).toList();
                Log.i(TAG, "[Remote Sync] Unknowns :: " + unknownInserts.size() + " inserts, " + unknownDeletes.size() + " deletes");
                storageIdDatabase.insert(unknownInserts);
                storageIdDatabase.delete(unknownDeletes);
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
                ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners();
                stopwatch.split("remote-merge-transaction");
            }
        } else {
            Log.i(TAG, "[Remote Sync] Remote version was newer, but there were no remote-only IDs.");
        }
    } else if (remoteManifest.getVersion() < localManifest.getVersion()) {
        Log.w(TAG, "[Remote Sync] Remote version was older. User might have switched accounts.");
    }
    if (remoteManifest != localManifest) {
        Log.i(TAG, "[Remote Sync] Saved new manifest. Now at version: " + remoteManifest.getVersion());
        SignalStore.storageService().setManifest(remoteManifest);
    }
    Log.i(TAG, "We are up-to-date with the remote storage state.");
    final WriteOperationResult remoteWriteOperation;
    db.beginTransaction();
    try {
        self = freshSelf();
        List<StorageId> localStorageIds = getAllLocalStorageIds(context, self);
        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");
    }
    if (!remoteWriteOperation.isEmpty()) {
        Log.i(TAG, "We have something to write remotely.");
        Log.i(TAG, "WriteOperationResult :: " + remoteWriteOperation);
        StorageSyncValidations.validate(remoteWriteOperation, remoteManifest, needsForcePush, self);
        Optional<SignalStorageManifest> conflict = accountManager.writeStorageRecords(storageServiceKey, remoteWriteOperation.getManifest(), remoteWriteOperation.getInserts(), remoteWriteOperation.getDeletes());
        if (conflict.isPresent()) {
            Log.w(TAG, "Hit a conflict when trying to resolve the conflict! Retrying.");
            throw new RetryLaterException();
        }
        Log.i(TAG, "Saved new manifest. Now at version: " + remoteWriteOperation.getManifest().getVersion());
        SignalStore.storageService().setManifest(remoteWriteOperation.getManifest());
        stopwatch.split("remote-write");
        needsMultiDeviceSync = true;
    } else {
        Log.i(TAG, "No remote writes needed. Still at version: " + remoteManifest.getVersion());
    }
    if (needsForcePush && SignalStore.account().isPrimaryDevice()) {
        Log.w(TAG, "Scheduling a force push.");
        ApplicationDependencies.getJobManager().add(new StorageForcePushJob());
    }
    stopwatch.stop(TAG);
    return needsMultiDeviceSync;
}
Also used : SignalContactRecord(org.whispersystems.signalservice.api.storage.SignalContactRecord) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) StorageId(org.whispersystems.signalservice.api.storage.StorageId) GroupV2RecordProcessor(org.thoughtcrime.securesms.storage.GroupV2RecordProcessor) SignalGroupV2Record(org.whispersystems.signalservice.api.storage.SignalGroupV2Record) UnknownStorageIdDatabase(org.thoughtcrime.securesms.database.UnknownStorageIdDatabase) StorageKey(org.whispersystems.signalservice.api.storage.StorageKey) SignalAccountRecord(org.whispersystems.signalservice.api.storage.SignalAccountRecord) SignalStorageManifest(org.whispersystems.signalservice.api.storage.SignalStorageManifest) IdDifferenceResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult) GroupV1RecordProcessor(org.thoughtcrime.securesms.storage.GroupV1RecordProcessor) SignalServiceAccountManager(org.whispersystems.signalservice.api.SignalServiceAccountManager) SignalStorageRecord(org.whispersystems.signalservice.api.storage.SignalStorageRecord) Recipient(org.thoughtcrime.securesms.recipients.Recipient) WriteOperationResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.WriteOperationResult) LinkedList(java.util.LinkedList) SignalGroupV1Record(org.whispersystems.signalservice.api.storage.SignalGroupV1Record) AccountRecordProcessor(org.thoughtcrime.securesms.storage.AccountRecordProcessor) SQLiteDatabase(net.zetetic.database.sqlcipher.SQLiteDatabase) ContactRecordProcessor(org.thoughtcrime.securesms.storage.ContactRecordProcessor) RetryLaterException(org.thoughtcrime.securesms.transport.RetryLaterException)

Example 5 with SQLiteDatabase

use of net.zetetic.database.sqlcipher.SQLiteDatabase in project Signal-Android by WhisperSystems.

the class FullBackupImporter method importFile.

public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull InputStream is, @NonNull String passphrase) throws IOException {
    int count = 0;
    SQLiteDatabase keyValueDatabase = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication()).getSqlCipherDatabase();
    try {
        BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
        db.beginTransaction();
        keyValueDatabase.beginTransaction();
        dropAllTables(db);
        BackupFrame frame;
        while (!(frame = inputStream.readFrame()).getEnd()) {
            if (count % 100 == 0)
                EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count, 0));
            count++;
            if (frame.hasVersion())
                processVersion(db, frame.getVersion());
            else if (frame.hasStatement())
                processStatement(db, frame.getStatement());
            else if (frame.hasPreference())
                processPreference(context, frame.getPreference());
            else if (frame.hasAttachment())
                processAttachment(context, attachmentSecret, db, frame.getAttachment(), inputStream);
            else if (frame.hasSticker())
                processSticker(context, attachmentSecret, db, frame.getSticker(), inputStream);
            else if (frame.hasAvatar())
                processAvatar(context, db, frame.getAvatar(), inputStream);
            else if (frame.hasKeyValue())
                processKeyValue(frame.getKeyValue());
            else
                count--;
        }
        db.setTransactionSuccessful();
        keyValueDatabase.setTransactionSuccessful();
    } finally {
        db.endTransaction();
        keyValueDatabase.endTransaction();
    }
    EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, count, 0));
}
Also used : SQLiteDatabase(net.zetetic.database.sqlcipher.SQLiteDatabase) BackupFrame(org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame) SuppressLint(android.annotation.SuppressLint)

Aggregations

SQLiteDatabase (net.zetetic.database.sqlcipher.SQLiteDatabase)10 ContentValues (android.content.ContentValues)3 Cursor (android.database.Cursor)2 NonNull (androidx.annotation.NonNull)2 IOException (java.io.IOException)2 LinkedList (java.util.LinkedList)2 SuppressLint (android.annotation.SuppressLint)1 Context (android.content.Context)1 TextUtils (android.text.TextUtils)1 Nullable (androidx.annotation.Nullable)1 RequiresApi (androidx.annotation.RequiresApi)1 DocumentFile (androidx.documentfile.provider.DocumentFile)1 Predicate (com.annimon.stream.function.Predicate)1 ByteString (com.google.protobuf.ByteString)1 File (java.io.File)1 FileOutputStream (java.io.FileOutputStream)1 InputStream (java.io.InputStream)1 OutputStream (java.io.OutputStream)1 InvalidAlgorithmParameterException (java.security.InvalidAlgorithmParameterException)1 InvalidKeyException (java.security.InvalidKeyException)1