Show backup progress as a percentage.

This commit is contained in:
Cody Henthorne
2021-12-14 14:31:44 -05:00
committed by Greyson Parrelli
parent 4f73e36d72
commit 8014a70134
15 changed files with 310 additions and 46 deletions

View File

@@ -0,0 +1,30 @@
package org.thoughtcrime.securesms.backup
import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.GroupReceiptDatabase
import org.thoughtcrime.securesms.database.MmsDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
/**
* Queries used by backup exporter to estimate total counts for various complicated tables.
*/
object BackupCountQueries {
const val mmsCount: String = "SELECT COUNT(*) FROM ${MmsDatabase.TABLE_NAME} WHERE ${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.VIEW_ONCE} <= 0"
const val smsCount: String = "SELECT COUNT(*) FROM ${SmsDatabase.TABLE_NAME} WHERE ${SmsDatabase.EXPIRES_IN} <= 0"
@get:JvmStatic
val groupReceiptCount: String = """
SELECT COUNT(*) FROM ${GroupReceiptDatabase.TABLE_NAME}
INNER JOIN ${MmsDatabase.TABLE_NAME} ON ${GroupReceiptDatabase.TABLE_NAME}.${GroupReceiptDatabase.MMS_ID} = ${MmsDatabase.TABLE_NAME}.${MmsDatabase.ID}
WHERE ${MmsDatabase.TABLE_NAME}.${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.TABLE_NAME}.${MmsDatabase.VIEW_ONCE} <= 0
""".trimIndent()
@get:JvmStatic
val attachmentCount: String = """
SELECT COUNT(*) FROM ${AttachmentDatabase.TABLE_NAME}
INNER JOIN ${MmsDatabase.TABLE_NAME} ON ${AttachmentDatabase.TABLE_NAME}.${AttachmentDatabase.MMS_ID} = ${MmsDatabase.TABLE_NAME}.${MmsDatabase.ID}
WHERE ${MmsDatabase.TABLE_NAME}.${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.TABLE_NAME}.${MmsDatabase.VIEW_ONCE} <= 0
""".trimIndent()
}

View File

@@ -13,13 +13,12 @@ import java.security.NoSuchAlgorithmException;
public abstract class FullBackupBase {
@SuppressWarnings("unused")
private static final String TAG = Log.tag(FullBackupBase.class);
private static final int DIGEST_ROUNDS = 250_000;
static class BackupStream {
static @NonNull byte[] getBackupKey(@NonNull String passphrase, @Nullable byte[] salt) {
try {
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0, 0));
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] input = passphrase.replace(" ", "").getBytes();
@@ -27,8 +26,8 @@ public abstract class FullBackupBase {
if (salt != null) digest.update(salt);
for (int i=0;i<250000;i++) {
if (i % 1000 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
for (int i = 0; i < DIGEST_ROUNDS; i++) {
if (i % 1000 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0, 0));
digest.update(hash);
hash = digest.digest(input);
}
@@ -47,20 +46,34 @@ public abstract class FullBackupBase {
}
private final Type type;
private final int count;
private final long count;
private final long estimatedTotalCount;
BackupEvent(Type type, int count) {
this.type = type;
this.count = count;
BackupEvent(Type type, long count, long estimatedTotalCount) {
this.type = type;
this.count = count;
this.estimatedTotalCount = estimatedTotalCount;
}
public Type getType() {
return type;
}
public int getCount() {
public long getCount() {
return count;
}
public long getEstimatedTotalCount() {
return estimatedTotalCount;
}
public double getCompletionPercentage() {
if (estimatedTotalCount == 0) {
return 0;
}
return Math.min(99.9f, (double) count * 100L / (double) estimatedTotalCount);
}
}
}

View File

@@ -75,6 +75,11 @@ public class FullBackupExporter extends FullBackupBase {
private static final String TAG = Log.tag(FullBackupExporter.class);
private static final long DATABASE_VERSION_RECORD_COUNT = 1L;
private static final long TABLE_RECORD_COUNT_MULTIPLIER = 3L;
private static final long IDENTITY_KEY_BACKUP_RECORD_COUNT = 2L;
private static final long FINAL_MESSAGE_COUNT = 1L;
private static final Set<String> BLACKLISTED_TABLES = SetUtil.newHashSet(
SignedPreKeyDatabase.TABLE_NAME,
OneTimePreKeyDatabase.TABLE_NAME,
@@ -134,58 +139,62 @@ public class FullBackupExporter extends FullBackupBase {
@NonNull BackupCancellationSignal cancellationSignal)
throws IOException
{
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase);
int count = 0;
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase);
int count = 0;
long estimatedCountOutside = 0L;
try {
outputStream.writeDatabaseVersion(input.getVersion());
count++;
List<String> tables = exportSchema(input, outputStream);
count += tables.size() * 3;
count += tables.size() * TABLE_RECORD_COUNT_MULTIPLIER;
final long estimatedCount = calculateCount(context, input, tables);
estimatedCountOutside = estimatedCount;
Stopwatch stopwatch = new Stopwatch("Backup");
for (String table : tables) {
throwIfCanceled(cancellationSignal);
if (table.equals(MmsDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, cancellationSignal);
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, estimatedCount, cancellationSignal);
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count, cancellationSignal);
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count, estimatedCount, cancellationSignal);
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count, cancellationSignal);
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count, estimatedCount, cancellationSignal);
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount), count, cancellationSignal);
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount), count, cancellationSignal);
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
} else if (!BLACKLISTED_TABLES.contains(table) && !table.startsWith("sqlite_")) {
count = exportTable(table, input, outputStream, null, null, count, cancellationSignal);
count = exportTable(table, input, outputStream, null, null, count, estimatedCount, cancellationSignal);
}
stopwatch.split("table::" + table);
}
for (BackupProtos.SharedPreference preference : IdentityKeyUtil.getBackupRecord(context)) {
throwIfCanceled(cancellationSignal);
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(preference);
}
for (BackupProtos.SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) {
throwIfCanceled(cancellationSignal);
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(preference);
}
stopwatch.split("prefs");
count = exportKeyValues(outputStream, SignalStore.getKeysToIncludeInBackup(), count, cancellationSignal);
count = exportKeyValues(outputStream, SignalStore.getKeysToIncludeInBackup(), count, estimatedCount, cancellationSignal);
stopwatch.split("key_values");
for (AvatarHelper.Avatar avatar : AvatarHelper.getAvatars(context)) {
throwIfCanceled(cancellationSignal);
if (avatar != null) {
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(avatar.getFilename(), avatar.getInputStream(), avatar.getLength());
}
}
@@ -198,7 +207,49 @@ public class FullBackupExporter extends FullBackupBase {
if (closeOutputStream) {
outputStream.close();
}
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count, estimatedCountOutside));
}
}
private static long calculateCount(@NonNull Context context, @NonNull SQLiteDatabase input, List<String> tables) {
long count = DATABASE_VERSION_RECORD_COUNT + TABLE_RECORD_COUNT_MULTIPLIER * tables.size();
for (String table : tables) {
if (table.equals(MmsDatabase.TABLE_NAME)) {
count += getCount(input, BackupCountQueries.mmsCount);
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
count += getCount(input, BackupCountQueries.smsCount);
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
count += getCount(input, BackupCountQueries.getGroupReceiptCount());
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
count += getCount(input, BackupCountQueries.getAttachmentCount());
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
count += getCount(input, "SELECT COUNT(*) FROM " + table);
} else if (!BLACKLISTED_TABLES.contains(table) && !table.startsWith("sqlite_")) {
count += getCount(input, "SELECT COUNT(*) FROM " + table);
}
}
count += IDENTITY_KEY_BACKUP_RECORD_COUNT;
count += TextSecurePreferences.getPreferencesToSaveToBackupCount(context);
KeyValueDataSet dataSet = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication())
.getDataSet();
for (String key : SignalStore.getKeysToIncludeInBackup()) {
if (dataSet.containsKey(key)) {
count++;
}
}
count += AvatarHelper.getAvatarCount(context);
return count + FINAL_MESSAGE_COUNT;
}
private static long getCount(@NonNull SQLiteDatabase input, @NonNull String query) {
try (Cursor cursor = input.rawQuery(query)) {
return cursor.moveToFirst() ? cursor.getLong(0) : 0;
}
}
@@ -244,6 +295,7 @@ public class FullBackupExporter extends FullBackupBase {
@Nullable Predicate<Cursor> predicate,
@Nullable PostProcessor postProcess,
int count,
long estimatedCount,
@NonNull BackupCancellationSignal cancellationSignal)
throws IOException
{
@@ -283,7 +335,7 @@ public class FullBackupExporter extends FullBackupBase {
statement.append(')');
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(statementBuilder.setStatement(statement.toString()).build());
if (postProcess != null) {
@@ -296,7 +348,7 @@ public class FullBackupExporter extends FullBackupBase {
return count;
}
private static int exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count) {
private static int exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count, long estimatedCount) {
try {
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID));
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID));
@@ -321,7 +373,7 @@ public class FullBackupExporter extends FullBackupBase {
if (random != null && random.length == 32) inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
}
} catch (IOException e) {
@@ -331,7 +383,7 @@ public class FullBackupExporter extends FullBackupBase {
return count;
}
private static int exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count) {
private static int exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count, long estimatedCount) {
try {
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID));
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH));
@@ -340,7 +392,7 @@ public class FullBackupExporter extends FullBackupBase {
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM));
if (!TextUtils.isEmpty(data) && size > 0) {
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
outputStream.writeSticker(rowId, inputStream, size);
}
@@ -371,6 +423,7 @@ public class FullBackupExporter extends FullBackupBase {
private static int exportKeyValues(@NonNull BackupFrameOutputStream outputStream,
@NonNull List<String> keysToIncludeInBackup,
int count,
long estimatedCount,
BackupCancellationSignal cancellationSignal) throws IOException
{
KeyValueDataSet dataSet = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication())
@@ -401,7 +454,7 @@ public class FullBackupExporter extends FullBackupBase {
throw new AssertionError("Unknown type: " + type);
}
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(builder.build());
}

View File

@@ -95,7 +95,7 @@ public class FullBackupImporter extends FullBackupBase {
BackupFrame frame;
while (!(frame = inputStream.readFrame()).getEnd()) {
if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count));
if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count, 0));
count++;
if (frame.hasVersion()) processVersion(db, frame.getVersion());
@@ -115,7 +115,7 @@ public class FullBackupImporter extends FullBackupBase {
keyValueDatabase.endTransaction();
}
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, count, 0));
}
private static @NonNull InputStream getInputStream(@NonNull Context context, @NonNull Uri uri) throws IOException{