From a9149c5dc04433f2406311d296fcdfc5082c2a12 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Fri, 12 Mar 2021 11:28:24 -0500 Subject: [PATCH] Stop backup jobs from continuing to run if backups become disabled. Fixes #10819 --- .../securesms/backup/FullBackupExporter.java | 56 +++++++++++++------ .../securesms/jobs/LocalBackupJob.java | 6 +- .../securesms/jobs/LocalBackupJobApi29.java | 6 +- .../BackupsPreferenceFragment.java | 2 + 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.java b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.java index 369ebca1b..4b819c1be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.java @@ -79,11 +79,12 @@ public class FullBackupExporter extends FullBackupBase { @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase input, @NonNull File output, - @NonNull String passphrase) + @NonNull String passphrase, + @NonNull BackupCancellationSignal cancellationSignal) throws IOException { try (OutputStream outputStream = new FileOutputStream(output)) { - internalExport(context, attachmentSecret, input, outputStream, passphrase, true); + internalExport(context, attachmentSecret, input, outputStream, passphrase, true, cancellationSignal); } } @@ -92,11 +93,12 @@ public class FullBackupExporter extends FullBackupBase { @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase input, @NonNull DocumentFile output, - @NonNull String passphrase) + @NonNull String passphrase, + @NonNull BackupCancellationSignal cancellationSignal) throws IOException { try (OutputStream outputStream = Objects.requireNonNull(context.getContentResolver().openOutputStream(output.getUri()))) { - internalExport(context, attachmentSecret, input, outputStream, passphrase, true); + internalExport(context, attachmentSecret, input, outputStream, passphrase, true, cancellationSignal); } } @@ -107,7 +109,7 @@ public class FullBackupExporter extends FullBackupBase { @NonNull String passphrase) throws IOException { - internalExport(context, attachmentSecret, input, outputStream, passphrase, false); + internalExport(context, attachmentSecret, input, outputStream, passphrase, false, () -> false); } private static void internalExport(@NonNull Context context, @@ -115,7 +117,8 @@ public class FullBackupExporter extends FullBackupBase { @NonNull SQLiteDatabase input, @NonNull OutputStream fileOutputStream, @NonNull String passphrase, - boolean closeOutputStream) + boolean closeOutputStream, + @NonNull BackupCancellationSignal cancellationSignal) throws IOException { BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase); @@ -129,23 +132,25 @@ public class FullBackupExporter extends FullBackupBase { 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); + count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, cancellationSignal); } else if (table.equals(SmsDatabase.TABLE_NAME)) { - count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count); + count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count, 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); + count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count, cancellationSignal); } else if (table.equals(AttachmentDatabase.TABLE_NAME)) { - count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), cursor -> exportAttachment(attachmentSecret, cursor, outputStream), count); + count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), cursor -> exportAttachment(attachmentSecret, cursor, outputStream), count, cancellationSignal); } else if (table.equals(StickerDatabase.TABLE_NAME)) { - count = exportTable(table, input, outputStream, cursor -> true, cursor -> exportSticker(attachmentSecret, cursor, outputStream), count); + count = exportTable(table, input, outputStream, cursor -> true, cursor -> exportSticker(attachmentSecret, cursor, outputStream), count, cancellationSignal); } else if (!BLACKLISTED_TABLES.contains(table) && !table.startsWith("sqlite_")) { - count = exportTable(table, input, outputStream, null, null, count); + count = exportTable(table, input, outputStream, null, null, count, cancellationSignal); } stopwatch.split("table::" + table); } for (BackupProtos.SharedPreference preference : IdentityKeyUtil.getBackupRecord(context)) { + throwIfCanceled(cancellationSignal); EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count)); outputStream.write(preference); } @@ -153,6 +158,7 @@ public class FullBackupExporter extends FullBackupBase { stopwatch.split("prefs"); for (AvatarHelper.Avatar avatar : AvatarHelper.getAvatars(context)) { + throwIfCanceled(cancellationSignal); if (avatar != null) { EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count)); outputStream.write(avatar.getFilename(), avatar.getInputStream(), avatar.getLength()); @@ -171,6 +177,12 @@ public class FullBackupExporter extends FullBackupBase { } } + private static void throwIfCanceled(@NonNull BackupCancellationSignal cancellationSignal) throws BackupCanceledException { + if (cancellationSignal.isCanceled()) { + throw new BackupCanceledException(); + } + } + private static List exportSchema(@NonNull SQLiteDatabase input, @NonNull BackupFrameOutputStream outputStream) throws IOException { @@ -201,18 +213,20 @@ public class FullBackupExporter extends FullBackupBase { return tables; } - private static int exportTable(@NonNull String table, - @NonNull SQLiteDatabase input, - @NonNull BackupFrameOutputStream outputStream, - @Nullable Predicate predicate, - @Nullable Consumer postProcess, - int count) + private static int exportTable(@NonNull String table, + @NonNull SQLiteDatabase input, + @NonNull BackupFrameOutputStream outputStream, + @Nullable Predicate predicate, + @Nullable Consumer postProcess, + int count, + @NonNull BackupCancellationSignal cancellationSignal) throws IOException { String template = "INSERT INTO " + table + " VALUES "; try (Cursor cursor = input.rawQuery("SELECT * FROM " + table, null)) { while (cursor != null && cursor.moveToNext()) { + throwIfCanceled(cancellationSignal); EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count)); if (predicate == null || predicate.test(cursor)) { @@ -506,4 +520,10 @@ public class FullBackupExporter extends FullBackupBase { outputStream.close(); } } + + public interface BackupCancellationSignal { + boolean isCanceled(); + } + + public static final class BackupCanceledException extends IOException { } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java index 445f5082a..b887206de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java @@ -115,12 +115,16 @@ public final class LocalBackupJob extends BaseJob { AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), DatabaseFactory.getBackupDatabase(context), tempFile, - backupPassword); + backupPassword, + this::isCanceled); if (!tempFile.renameTo(backupFile)) { Log.w(TAG, "Failed to rename temp file"); throw new IOException("Renaming temporary backup file failed!"); } + } catch (FullBackupExporter.BackupCanceledException e) { + Log.w(TAG, "Backup cancelled"); + throw e; } catch (IOException e) { BackupFileIOError.postNotificationForException(context, e, getRunAttempt()); throw e; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java index 0fb868da6..c8f5afc4d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java @@ -109,12 +109,16 @@ public final class LocalBackupJobApi29 extends BaseJob { AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), DatabaseFactory.getBackupDatabase(context), temporaryFile, - backupPassword); + backupPassword, + this::isCanceled); if (!temporaryFile.renameTo(fileName)) { Log.w(TAG, "Failed to rename temp file"); throw new IOException("Renaming temporary backup file failed!"); } + } catch (FullBackupExporter.BackupCanceledException e) { + Log.w(TAG, "Backup cancelled"); + throw e; } catch (IOException e) { Log.w(TAG, "Error during backup!", e); BackupFileIOError.postNotificationForException(context, e, getRunAttempt()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index d9feef516..ce92c6c57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.backup.BackupDialog; import org.thoughtcrime.securesms.backup.FullBackupBase; import org.thoughtcrime.securesms.database.NoExternalStorageException; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.LocalBackupJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.permissions.Permissions; @@ -250,5 +251,6 @@ public class BackupsPreferenceFragment extends Fragment { create.setVisibility(View.GONE); folder.setVisibility(View.GONE); verify.setVisibility(View.GONE); + ApplicationDependencies.getJobManager().cancelAllInQueue(LocalBackupJob.QUEUE); } }