kopia lustrzana https://github.com/ryukoposting/Signal-Android
Convert IdentityTable to kotlin.
rodzic
380b377ed8
commit
69003dfbe2
|
@ -1,282 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityStoreRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.signal.core.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.signal.core.util.SqlUtil;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class IdentityTable extends DatabaseTable {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(IdentityTable.class);
|
||||
|
||||
static final String TABLE_NAME = "identities";
|
||||
private static final String ID = "_id";
|
||||
static final String ADDRESS = "address";
|
||||
static final String IDENTITY_KEY = "identity_key";
|
||||
private static final String FIRST_USE = "first_use";
|
||||
private static final String TIMESTAMP = "timestamp";
|
||||
static final String VERIFIED = "verified";
|
||||
private static final String NONBLOCKING_APPROVAL = "nonblocking_approval";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
ADDRESS + " INTEGER UNIQUE, " +
|
||||
IDENTITY_KEY + " TEXT, " +
|
||||
FIRST_USE + " INTEGER DEFAULT 0, " +
|
||||
TIMESTAMP + " INTEGER DEFAULT 0, " +
|
||||
VERIFIED + " INTEGER DEFAULT 0, " +
|
||||
NONBLOCKING_APPROVAL + " INTEGER DEFAULT 0);";
|
||||
|
||||
public enum VerifiedStatus {
|
||||
DEFAULT, VERIFIED, UNVERIFIED;
|
||||
|
||||
public int toInt() {
|
||||
switch (this) {
|
||||
case DEFAULT: return 0;
|
||||
case VERIFIED: return 1;
|
||||
case UNVERIFIED: return 2;
|
||||
default: throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public static VerifiedStatus forState(int state) {
|
||||
switch (state) {
|
||||
case 0: return DEFAULT;
|
||||
case 1: return VERIFIED;
|
||||
case 2: return UNVERIFIED;
|
||||
default: throw new AssertionError("No such state: " + state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IdentityTable(Context context, SignalDatabase databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public @Nullable IdentityStoreRecord getIdentityStoreRecord(@NonNull String addressName) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
String query = ADDRESS + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(addressName);
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
String serializedIdentity = CursorUtil.requireString(cursor, IDENTITY_KEY);
|
||||
long timestamp = CursorUtil.requireLong(cursor, TIMESTAMP);
|
||||
int verifiedStatus = CursorUtil.requireInt(cursor, VERIFIED);
|
||||
boolean nonblockingApproval = CursorUtil.requireBoolean(cursor, NONBLOCKING_APPROVAL);
|
||||
boolean firstUse = CursorUtil.requireBoolean(cursor, FIRST_USE);
|
||||
|
||||
return new IdentityStoreRecord(addressName,
|
||||
new IdentityKey(Base64.decode(serializedIdentity), 0),
|
||||
VerifiedStatus.forState(verifiedStatus),
|
||||
firstUse,
|
||||
timestamp,
|
||||
nonblockingApproval);
|
||||
} else if (UuidUtil.isUuid(addressName)) {
|
||||
Optional<RecipientId> byServiceId = SignalDatabase.recipients().getByServiceId(ServiceId.parseOrThrow(addressName));
|
||||
|
||||
if (byServiceId.isPresent()) {
|
||||
Recipient recipient = Recipient.resolved(byServiceId.get());
|
||||
|
||||
if (recipient.hasE164() && !UuidUtil.isUuid(recipient.requireE164())) {
|
||||
Log.i(TAG, "Could not find identity for UUID. Attempting E164.");
|
||||
return getIdentityStoreRecord(recipient.requireE164());
|
||||
} else {
|
||||
Log.i(TAG, "Could not find identity for UUID, and our recipient doesn't have an E164.");
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Could not find identity for UUID, and we don't have a recipient.");
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Could not find identity for E164 either.");
|
||||
}
|
||||
} catch (InvalidKeyException | IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void saveIdentity(@NonNull String addressName,
|
||||
@NonNull RecipientId recipientId,
|
||||
IdentityKey identityKey,
|
||||
VerifiedStatus verifiedStatus,
|
||||
boolean firstUse,
|
||||
long timestamp,
|
||||
boolean nonBlockingApproval)
|
||||
{
|
||||
saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||
SignalDatabase.recipients().markNeedsSync(recipientId);
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
|
||||
public void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, boolean nonBlockingApproval) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
ContentValues contentValues = new ContentValues(2);
|
||||
contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval);
|
||||
|
||||
database.update(TABLE_NAME, contentValues, ADDRESS + " = ?", SqlUtil.buildArgs(addressName));
|
||||
|
||||
SignalDatabase.recipients().markNeedsSync(recipientId);
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
|
||||
public void setVerified(@NonNull String addressName, @NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()));
|
||||
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(VERIFIED, verifiedStatus.toInt());
|
||||
|
||||
int updated = database.update(TABLE_NAME, contentValues, query, args);
|
||||
|
||||
if (updated > 0) {
|
||||
Optional<IdentityRecord> record = getIdentityRecord(addressName);
|
||||
if (record.isPresent()) EventBus.getDefault().post(record.get());
|
||||
SignalDatabase.recipients().markNeedsSync(recipientId);
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateIdentityAfterSync(@NonNull String addressName, @NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
Optional<IdentityRecord> existingRecord = getIdentityRecord(addressName);
|
||||
|
||||
boolean hadEntry = existingRecord.isPresent();
|
||||
boolean keyMatches = hasMatchingKey(addressName, identityKey);
|
||||
boolean statusMatches = keyMatches && hasMatchingStatus(addressName, identityKey, verifiedStatus);
|
||||
|
||||
if (!keyMatches || !statusMatches) {
|
||||
saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), true);
|
||||
|
||||
Optional<IdentityRecord> record = getIdentityRecord(addressName);
|
||||
|
||||
if (record.isPresent()) {
|
||||
EventBus.getDefault().post(record.get());
|
||||
}
|
||||
|
||||
ApplicationDependencies.getProtocolStore().aci().identities().invalidate(addressName);
|
||||
}
|
||||
|
||||
if (hadEntry && !keyMatches) {
|
||||
Log.w(TAG, "Updated identity key during storage sync for " + addressName + " | Existing: " + existingRecord.get().getIdentityKey().hashCode() + ", New: " + identityKey.hashCode());
|
||||
IdentityUtil.markIdentityUpdate(context, recipientId);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(@NonNull String addressName) {
|
||||
databaseHelper.getSignalWritableDatabase().delete(IdentityTable.TABLE_NAME, IdentityTable.ADDRESS + " = ?", SqlUtil.buildArgs(addressName));
|
||||
}
|
||||
|
||||
private Optional<IdentityRecord> getIdentityRecord(@NonNull String addressName) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
String query = ADDRESS + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(addressName);
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return Optional.of(getIdentityRecord(cursor));
|
||||
}
|
||||
} catch (InvalidKeyException | IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private boolean hasMatchingKey(@NonNull String addressName, IdentityKey identityKey) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()));
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, query, args, null, null, null)) {
|
||||
return cursor != null && cursor.moveToFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMatchingStatus(@NonNull String addressName, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ? AND " + VERIFIED + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()), verifiedStatus.toInt());
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, query, args, null, null, null)) {
|
||||
return cursor != null && cursor.moveToFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull IdentityRecord getIdentityRecord(@NonNull Cursor cursor) throws IOException, InvalidKeyException {
|
||||
String addressName = CursorUtil.requireString(cursor, ADDRESS);
|
||||
String serializedIdentity = CursorUtil.requireString(cursor, IDENTITY_KEY);
|
||||
long timestamp = CursorUtil.requireLong(cursor, TIMESTAMP);
|
||||
int verifiedStatus = CursorUtil.requireInt(cursor, VERIFIED);
|
||||
boolean nonblockingApproval = CursorUtil.requireBoolean(cursor, NONBLOCKING_APPROVAL);
|
||||
boolean firstUse = CursorUtil.requireBoolean(cursor, FIRST_USE);
|
||||
IdentityKey identity = new IdentityKey(Base64.decode(serializedIdentity), 0);
|
||||
|
||||
return new IdentityRecord(RecipientId.fromSidOrE164(addressName), identity, VerifiedStatus.forState(verifiedStatus), firstUse, timestamp, nonblockingApproval);
|
||||
}
|
||||
|
||||
private void saveIdentityInternal(@NonNull String addressName,
|
||||
@NonNull RecipientId recipientId,
|
||||
IdentityKey identityKey,
|
||||
VerifiedStatus verifiedStatus,
|
||||
boolean firstUse,
|
||||
long timestamp,
|
||||
boolean nonBlockingApproval)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(ADDRESS, addressName);
|
||||
contentValues.put(IDENTITY_KEY, identityKeyString);
|
||||
contentValues.put(TIMESTAMP, timestamp);
|
||||
contentValues.put(VERIFIED, verifiedStatus.toInt());
|
||||
contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval ? 1 : 0);
|
||||
contentValues.put(FIRST_USE, firstUse ? 1 : 0);
|
||||
|
||||
database.replace(TABLE_NAME, null, contentValues);
|
||||
|
||||
EventBus.getDefault().post(new IdentityRecord(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.contentValuesOf
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.exists
|
||||
import org.signal.core.util.firstOrNull
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.toOptional
|
||||
import org.signal.core.util.update
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.recipients
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.database.model.IdentityStoreRecord
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import java.lang.AssertionError
|
||||
import java.util.Optional
|
||||
|
||||
class IdentityTable internal constructor(context: Context?, databaseHelper: SignalDatabase?) : DatabaseTable(context, databaseHelper) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(IdentityTable::class.java)
|
||||
const val TABLE_NAME = "identities"
|
||||
private const val ID = "_id"
|
||||
const val ADDRESS = "address"
|
||||
const val IDENTITY_KEY = "identity_key"
|
||||
private const val FIRST_USE = "first_use"
|
||||
private const val TIMESTAMP = "timestamp"
|
||||
const val VERIFIED = "verified"
|
||||
private const val NONBLOCKING_APPROVAL = "nonblocking_approval"
|
||||
const val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
$ADDRESS INTEGER UNIQUE,
|
||||
$IDENTITY_KEY TEXT,
|
||||
$FIRST_USE INTEGER DEFAULT 0,
|
||||
$TIMESTAMP INTEGER DEFAULT 0,
|
||||
$VERIFIED INTEGER DEFAULT 0,
|
||||
$NONBLOCKING_APPROVAL INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
}
|
||||
|
||||
fun getIdentityStoreRecord(addressName: String): IdentityStoreRecord? {
|
||||
readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$ADDRESS = ?", addressName)
|
||||
.run()
|
||||
.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
return IdentityStoreRecord(
|
||||
addressName = addressName,
|
||||
identityKey = IdentityKey(Base64.decode(cursor.requireNonNullString(IDENTITY_KEY)), 0),
|
||||
verifiedStatus = VerifiedStatus.forState(cursor.requireInt(VERIFIED)),
|
||||
firstUse = cursor.requireBoolean(FIRST_USE),
|
||||
timestamp = cursor.requireLong(TIMESTAMP),
|
||||
nonblockingApproval = cursor.requireBoolean(NONBLOCKING_APPROVAL)
|
||||
)
|
||||
} else if (UuidUtil.isUuid(addressName)) {
|
||||
val byServiceId = recipients.getByServiceId(ServiceId.parseOrThrow(addressName))
|
||||
if (byServiceId.isPresent) {
|
||||
val recipient = Recipient.resolved(byServiceId.get())
|
||||
if (recipient.hasE164() && !UuidUtil.isUuid(recipient.requireE164())) {
|
||||
Log.i(TAG, "Could not find identity for UUID. Attempting E164.")
|
||||
return getIdentityStoreRecord(recipient.requireE164())
|
||||
} else {
|
||||
Log.i(TAG, "Could not find identity for UUID, and our recipient doesn't have an E164.")
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Could not find identity for UUID, and we don't have a recipient.")
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Could not find identity for E164 either.")
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun saveIdentity(
|
||||
addressName: String,
|
||||
recipientId: RecipientId,
|
||||
identityKey: IdentityKey,
|
||||
verifiedStatus: VerifiedStatus,
|
||||
firstUse: Boolean,
|
||||
timestamp: Long,
|
||||
nonBlockingApproval: Boolean
|
||||
) {
|
||||
saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval)
|
||||
recipients.markNeedsSync(recipientId)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
|
||||
fun setApproval(addressName: String, recipientId: RecipientId, nonBlockingApproval: Boolean) {
|
||||
val updated = writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(NONBLOCKING_APPROVAL to nonBlockingApproval)
|
||||
.where("$ADDRESS = ?", addressName)
|
||||
.run()
|
||||
|
||||
if (updated > 0) {
|
||||
recipients.markNeedsSync(recipientId)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
}
|
||||
|
||||
fun setVerified(addressName: String, recipientId: RecipientId, identityKey: IdentityKey, verifiedStatus: VerifiedStatus) {
|
||||
val updated = writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(VERIFIED to verifiedStatus.toInt())
|
||||
.where("$ADDRESS = ? AND $IDENTITY_KEY = ?", addressName, Base64.encodeBytes(identityKey.serialize()))
|
||||
.run()
|
||||
|
||||
if (updated > 0) {
|
||||
val record = getIdentityRecord(addressName)
|
||||
if (record.isPresent) {
|
||||
EventBus.getDefault().post(record.get())
|
||||
}
|
||||
recipients.markNeedsSync(recipientId)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateIdentityAfterSync(addressName: String, recipientId: RecipientId, identityKey: IdentityKey, verifiedStatus: VerifiedStatus) {
|
||||
val existingRecord = getIdentityRecord(addressName)
|
||||
val hadEntry = existingRecord.isPresent
|
||||
val keyMatches = hasMatchingKey(addressName, identityKey)
|
||||
val statusMatches = keyMatches && hasMatchingStatus(addressName, identityKey, verifiedStatus)
|
||||
|
||||
if (!keyMatches || !statusMatches) {
|
||||
saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), nonBlockingApproval = true)
|
||||
|
||||
val record = getIdentityRecord(addressName)
|
||||
if (record.isPresent) {
|
||||
EventBus.getDefault().post(record.get())
|
||||
}
|
||||
|
||||
ApplicationDependencies.getProtocolStore().aci().identities().invalidate(addressName)
|
||||
}
|
||||
|
||||
if (hadEntry && !keyMatches) {
|
||||
Log.w(TAG, "Updated identity key during storage sync for " + addressName + " | Existing: " + existingRecord.get().identityKey.hashCode() + ", New: " + identityKey.hashCode())
|
||||
IdentityUtil.markIdentityUpdate(context, recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(addressName: String) {
|
||||
writableDatabase
|
||||
.delete(TABLE_NAME)
|
||||
.where("$ADDRESS = ?", addressName)
|
||||
.run()
|
||||
}
|
||||
|
||||
private fun getIdentityRecord(addressName: String): Optional<IdentityRecord> {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$ADDRESS = ?", addressName)
|
||||
.run()
|
||||
.firstOrNull { cursor ->
|
||||
IdentityRecord(
|
||||
recipientId = RecipientId.fromSidOrE164(cursor.requireNonNullString(ADDRESS)),
|
||||
identityKey = IdentityKey(Base64.decode(cursor.requireNonNullString(IDENTITY_KEY)), 0),
|
||||
verifiedStatus = VerifiedStatus.forState(cursor.requireInt(VERIFIED)),
|
||||
firstUse = cursor.requireBoolean(FIRST_USE),
|
||||
timestamp = cursor.requireLong(TIMESTAMP),
|
||||
nonblockingApproval = cursor.requireBoolean(NONBLOCKING_APPROVAL)
|
||||
)
|
||||
}
|
||||
.toOptional()
|
||||
}
|
||||
|
||||
private fun hasMatchingKey(addressName: String, identityKey: IdentityKey): Boolean {
|
||||
return readableDatabase
|
||||
.exists(TABLE_NAME)
|
||||
.where("$ADDRESS = ? AND $IDENTITY_KEY = ?", addressName, Base64.encodeBytes(identityKey.serialize()))
|
||||
.run()
|
||||
}
|
||||
|
||||
private fun hasMatchingStatus(addressName: String, identityKey: IdentityKey, verifiedStatus: VerifiedStatus): Boolean {
|
||||
return readableDatabase
|
||||
.exists(TABLE_NAME)
|
||||
.where("$ADDRESS = ? AND $IDENTITY_KEY = ? AND $VERIFIED = ?", addressName, Base64.encodeBytes(identityKey.serialize()), verifiedStatus.toInt())
|
||||
.run()
|
||||
}
|
||||
|
||||
private fun saveIdentityInternal(
|
||||
addressName: String,
|
||||
recipientId: RecipientId,
|
||||
identityKey: IdentityKey,
|
||||
verifiedStatus: VerifiedStatus,
|
||||
firstUse: Boolean,
|
||||
timestamp: Long,
|
||||
nonBlockingApproval: Boolean
|
||||
) {
|
||||
val contentValues = contentValuesOf(
|
||||
ADDRESS to addressName,
|
||||
IDENTITY_KEY to Base64.encodeBytes(identityKey.serialize()),
|
||||
TIMESTAMP to timestamp,
|
||||
VERIFIED to verifiedStatus.toInt(),
|
||||
NONBLOCKING_APPROVAL to if (nonBlockingApproval) 1 else 0,
|
||||
FIRST_USE to if (firstUse) 1 else 0
|
||||
)
|
||||
writableDatabase.replace(TABLE_NAME, null, contentValues)
|
||||
EventBus.getDefault().post(IdentityRecord(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval))
|
||||
}
|
||||
|
||||
enum class VerifiedStatus {
|
||||
DEFAULT, VERIFIED, UNVERIFIED;
|
||||
|
||||
fun toInt(): Int {
|
||||
return when (this) {
|
||||
DEFAULT -> 0
|
||||
VERIFIED -> 1
|
||||
UNVERIFIED -> 2
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun forState(state: Int): VerifiedStatus {
|
||||
return when (state) {
|
||||
0 -> DEFAULT
|
||||
1 -> VERIFIED
|
||||
2 -> UNVERIFIED
|
||||
else -> throw AssertionError("No such state: $state")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -268,7 +268,9 @@ public abstract class MessageTable extends DatabaseTable implements MmsSmsColumn
|
|||
}
|
||||
|
||||
public boolean hasSmsExportMessage(long threadId) {
|
||||
return SQLiteDatabaseExtensionsKt.exists(getReadableDatabase(), getTableName(), THREAD_ID_WHERE + " AND " + getTypeField() + " = ?", threadId, Types.SMS_EXPORT_TYPE);
|
||||
return SQLiteDatabaseExtensionsKt.exists(getReadableDatabase(), getTableName())
|
||||
.where(THREAD_ID_WHERE + " AND " + getTypeField() + " = ?", threadId, Types.SMS_EXPORT_TYPE)
|
||||
.run();
|
||||
}
|
||||
|
||||
final int getSecureMessageCountForInsights() {
|
||||
|
|
|
@ -75,7 +75,7 @@ class PendingPniSignatureMessageTable(context: Context, databaseHelper: SignalDa
|
|||
return@withinTransaction
|
||||
}
|
||||
|
||||
val stillPending: Boolean = db.exists(TABLE_NAME, "$RECIPIENT_ID = ? AND $SENT_TIMESTAMP = ?", recipientId, sentTimestamps)
|
||||
val stillPending: Boolean = db.exists(TABLE_NAME).where("$RECIPIENT_ID = ? AND $SENT_TIMESTAMP = ?", recipientId, sentTimestamps).run()
|
||||
|
||||
if (!stillPending) {
|
||||
Log.i(TAG, "All devices for ($recipientId, $sentTimestamps) have acked the PNI signature message. Clearing flag and removing any other pending receipts.")
|
||||
|
|
|
@ -419,7 +419,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
}
|
||||
|
||||
fun isAssociated(serviceId: ServiceId, pni: PNI): Boolean {
|
||||
return readableDatabase.exists(TABLE_NAME, "$SERVICE_ID = ? AND $PNI_COLUMN = ?", serviceId.toString(), pni.toString())
|
||||
return readableDatabase.exists(TABLE_NAME).where("$SERVICE_ID = ? AND $PNI_COLUMN = ?", serviceId.toString(), pni.toString()).run()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
|
|
|
@ -12,4 +12,8 @@ fun <E> Optional<E>.or(other: Optional<E>): Optional<E> {
|
|||
|
||||
fun <E> Optional<E>.isAbsent(): Boolean {
|
||||
return !isPresent
|
||||
}
|
||||
|
||||
fun <E : Any> E?.toOptional(): Optional<E> {
|
||||
return Optional.ofNullable(this)
|
||||
}
|
|
@ -38,10 +38,8 @@ fun SupportSQLiteDatabase.getTableRowCount(table: String): Int {
|
|||
/**
|
||||
* Checks if a row exists that matches the query.
|
||||
*/
|
||||
fun SupportSQLiteDatabase.exists(table: String, query: String, vararg args: Any): Boolean {
|
||||
return this.query("SELECT EXISTS(SELECT 1 FROM $table WHERE $query)", SqlUtil.buildArgs(*args)).use { cursor ->
|
||||
cursor.moveToFirst() && cursor.getInt(0) == 1
|
||||
}
|
||||
fun SupportSQLiteDatabase.exists(table: String): ExistsBuilderPart1 {
|
||||
return ExistsBuilderPart1(this, table)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -259,3 +257,32 @@ class DeleteBuilderPart2(
|
|||
return db.delete(tableName, where, whereArgs)
|
||||
}
|
||||
}
|
||||
|
||||
class ExistsBuilderPart1(
|
||||
private val db: SupportSQLiteDatabase,
|
||||
private val tableName: String
|
||||
) {
|
||||
|
||||
fun where(@Language("sql") where: String, vararg whereArgs: Any): ExistsBuilderPart2 {
|
||||
return ExistsBuilderPart2(db, tableName, where, SqlUtil.buildArgs(*whereArgs))
|
||||
}
|
||||
|
||||
fun run(): Boolean {
|
||||
return db.query("SELECT EXISTS(SELECT 1 FROM $tableName)", null).use { cursor ->
|
||||
cursor.moveToFirst() && cursor.getInt(0) == 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExistsBuilderPart2(
|
||||
private val db: SupportSQLiteDatabase,
|
||||
private val tableName: String,
|
||||
private val where: String,
|
||||
private val whereArgs: Array<String>
|
||||
) {
|
||||
fun run(): Boolean {
|
||||
return db.query("SELECT EXISTS(SELECT 1 FROM $tableName WHERE $where)", SqlUtil.buildArgs(*whereArgs)).use { cursor ->
|
||||
cursor.moveToFirst() && cursor.getInt(0) == 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue