Backup file database table.

New backup util class.
This commit is contained in:
Anton Chekulaev 2020-09-11 16:29:08 +10:00
parent 8b98f85470
commit 5a5702302f
10 changed files with 188 additions and 17 deletions

View File

@ -57,7 +57,7 @@ import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.registration.CaptchaActivity; import org.thoughtcrime.securesms.registration.CaptchaActivity;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.BackupUtil; import org.thoughtcrime.securesms.util.BackupUtilOld;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ServiceUtil;
@ -76,7 +76,6 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.internal.push.LockedException; import org.whispersystems.signalservice.internal.push.LockedException;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -275,11 +274,11 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
if (getIntent().getBooleanExtra(RE_REGISTRATION_EXTRA, false)) return; if (getIntent().getBooleanExtra(RE_REGISTRATION_EXTRA, false)) return;
new AsyncTask<Void, Void, BackupUtil.BackupInfo>() { new AsyncTask<Void, Void, BackupUtilOld.BackupInfo>() {
@Override @Override
protected @Nullable BackupUtil.BackupInfo doInBackground(Void... voids) { protected @Nullable BackupUtilOld.BackupInfo doInBackground(Void... voids) {
try { try {
return BackupUtil.getLatestBackup(RegistrationActivity.this); return BackupUtilOld.getLatestBackup(RegistrationActivity.this);
} catch (NoExternalStorageException e) { } catch (NoExternalStorageException e) {
Log.w(TAG, e); Log.w(TAG, e);
return null; return null;
@ -287,7 +286,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
} }
@Override @Override
protected void onPostExecute(@Nullable BackupUtil.BackupInfo backup) { protected void onPostExecute(@Nullable BackupUtilOld.BackupInfo backup) {
if (backup != null) displayRestoreView(backup); if (backup != null) displayRestoreView(backup);
} }
}.execute(); }.execute();
@ -304,7 +303,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
} }
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private void handleRestore(BackupUtil.BackupInfo backup) { private void handleRestore(BackupUtilOld.BackupInfo backup) {
View view = LayoutInflater.from(this).inflate(R.layout.enter_backup_passphrase_dialog, null); View view = LayoutInflater.from(this).inflate(R.layout.enter_backup_passphrase_dialog, null);
EditText prompt = view.findViewById(R.id.restore_passphrase_input); EditText prompt = view.findViewById(R.id.restore_passphrase_input);
@ -661,7 +660,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
finish(); finish();
} }
private void displayRestoreView(@NonNull BackupUtil.BackupInfo backup) { private void displayRestoreView(@NonNull BackupUtilOld.BackupInfo backup) {
title.animate().translationX(title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() { title.animate().translationX(title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {

View File

@ -15,6 +15,7 @@ import network.loki.messenger.R;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.service.LocalBackupListener; import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.util.BackupUtil; import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.BackupUtilOld;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
@ -77,7 +78,7 @@ public class BackupDialog {
.setPositiveButton(R.string.BackupDialog_delete_backups_statement, (dialog, which) -> { .setPositiveButton(R.string.BackupDialog_delete_backups_statement, (dialog, which) -> {
BackupPassphrase.set(context, null); BackupPassphrase.set(context, null);
TextSecurePreferences.setBackupEnabled(context, false); TextSecurePreferences.setBackupEnabled(context, false);
BackupUtil.deleteAllBackups(context); BackupUtilOld.deleteAllBackups(context);
preference.setChecked(false); preference.setChecked(false);
}) })
.create() .create()

View File

@ -74,6 +74,7 @@ public class DatabaseFactory {
private final LokiMessageDatabase lokiMessageDatabase; private final LokiMessageDatabase lokiMessageDatabase;
private final LokiThreadDatabase lokiThreadDatabase; private final LokiThreadDatabase lokiThreadDatabase;
private final LokiUserDatabase lokiUserDatabase; private final LokiUserDatabase lokiUserDatabase;
private final LokiBackupFilesDatabase lokiBackupFilesDatabase;
private final SharedSenderKeysDatabase sskDatabase; private final SharedSenderKeysDatabase sskDatabase;
public static DatabaseFactory getInstance(Context context) { public static DatabaseFactory getInstance(Context context) {
@ -190,6 +191,10 @@ public class DatabaseFactory {
return getInstance(context).lokiUserDatabase; return getInstance(context).lokiUserDatabase;
} }
public static LokiBackupFilesDatabase getLokiBackupFilesDatabase(Context context) {
return getInstance(context).lokiBackupFilesDatabase;
}
public static SharedSenderKeysDatabase getSSKDatabase(Context context) { public static SharedSenderKeysDatabase getSSKDatabase(Context context) {
return getInstance(context).sskDatabase; return getInstance(context).sskDatabase;
} }
@ -232,6 +237,7 @@ public class DatabaseFactory {
this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper); this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper);
this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper); this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper);
this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper); this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper);
this.lokiBackupFilesDatabase = new LokiBackupFilesDatabase(context, databaseHelper);
this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper); this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper);
} }

View File

@ -0,0 +1,105 @@
package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.database.model.BackupFileRecord
import java.util.*
import kotlin.collections.ArrayList
/**
* Keeps track of the backup files saved by the app.
* Uses [BackupFileRecord] as an entry data projection.
*/
class LokiBackupFilesDatabase(context: Context?, databaseHelper: SQLCipherOpenHelper?)
: Database(context, databaseHelper) {
companion object {
private const val TABLE_NAME = "backup_files"
private const val COLUMN_ID = "_id"
private const val COLUMN_URI = "uri"
private const val COLUMN_FILE_SIZE = "file_size"
private const val COLUMN_TIMESTAMP = "timestamp"
private val allColumns = arrayOf(COLUMN_ID, COLUMN_URI, COLUMN_FILE_SIZE, COLUMN_TIMESTAMP)
@JvmStatic
val createTableCommand = """
CREATE TABLE $TABLE_NAME (
$COLUMN_ID INTEGER PRIMARY KEY,
$COLUMN_URI TEXT NOT NULL,
$COLUMN_FILE_SIZE INTEGER NOT NULL,
$COLUMN_TIMESTAMP INTEGER NOT NULL
);
""".trimIndent()
private fun mapCursorToRecord(cursor: Cursor): BackupFileRecord {
val id = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID))
val uriRaw = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_URI))
val fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_FILE_SIZE))
val timestampRaw = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_TIMESTAMP))
return BackupFileRecord(id, Uri.parse(uriRaw), fileSize, Date(timestampRaw))
}
private fun mapRecordToValues(record: BackupFileRecord): ContentValues {
val contentValues = ContentValues()
if (record.id >= 0) { contentValues.put(COLUMN_ID, record.id) }
contentValues.put(COLUMN_URI, record.uri.toString())
contentValues.put(COLUMN_FILE_SIZE, record.fileSize)
contentValues.put(COLUMN_TIMESTAMP, record.timestamp.time)
return contentValues
}
}
fun getBackupFiles(): List<BackupFileRecord> {
databaseHelper.readableDatabase.query(TABLE_NAME, allColumns, null, null, null, null, null).use {
val records = ArrayList<BackupFileRecord>()
while (it != null && it.moveToFirst()) {
val record = mapCursorToRecord(it)
records.add(record)
}
return records
}
}
fun insertBackupFile(record: BackupFileRecord): Long {
val contentValues = mapRecordToValues(record)
return databaseHelper.writableDatabase.insertOrThrow(TABLE_NAME, null, contentValues)
}
fun getLastBackupFileTime(): Date? {
// SELECT $COLUMN_TIMESTAMP FROM $TABLE_NAME ORDER BY $COLUMN_TIMESTAMP DESC LIMIT 1
databaseHelper.readableDatabase.query(
TABLE_NAME,
arrayOf(COLUMN_TIMESTAMP),
null, null, null, null,
"$COLUMN_TIMESTAMP DESC",
"1"
).use {
if (it !== null && it.moveToFirst()) {
return Date(it.getLong(0))
} else {
return null
}
}
}
fun getLastBackupFile(): BackupFileRecord? {
// SELECT * FROM $TABLE_NAME ORDER BY $COLUMN_TIMESTAMP DESC LIMIT 1
databaseHelper.readableDatabase.query(
TABLE_NAME,
allColumns,
null, null, null, null,
"$COLUMN_TIMESTAMP DESC",
"1"
).use {
if (it != null && it.moveToFirst()) {
return mapCursorToRecord(it)
} else {
return null
}
}
}
}

View File

@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.JobDatabase; import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.database.LokiBackupFilesDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase; import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.PushDatabase;
@ -89,8 +90,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV11 = 32; private static final int lokiV11 = 32;
private static final int lokiV12 = 33; private static final int lokiV12 = 33;
private static final int lokiV13 = 34; private static final int lokiV13 = 34;
private static final int lokiV14_BACKUP_FILES = 35;
private static final int DATABASE_VERSION = lokiV13; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes private static final int DATABASE_VERSION = lokiV14_BACKUP_FILES; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Session makes any database changes
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@ -161,6 +163,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand()); db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand()); db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand()); db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupRatchetTableCommand()); db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupRatchetTableCommand());
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand()); db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand());
@ -619,6 +622,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTable3Command()); db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTable3Command());
} }
if (oldVersion < lokiV14_BACKUP_FILES) {
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.database.model
import android.net.Uri
import java.util.*
/**
* Represents a record for a backup file in the [org.thoughtcrime.securesms.database.LokiBackupFilesDatabase].
*/
data class BackupFileRecord(val id: Long, val uri: Uri, val fileSize: Long, val timestamp: Date) {
constructor(uri: Uri, fileSize: Long, timestamp: Date): this(-1, uri, fileSize, timestamp)
}

View File

@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.service.GenericForegroundService; import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.util.BackupUtil; import org.thoughtcrime.securesms.util.BackupUtilOld;
import org.thoughtcrime.securesms.util.ExternalStorageUtil; import org.thoughtcrime.securesms.util.ExternalStorageUtil;
import java.io.File; import java.io.File;
@ -98,7 +98,7 @@ public class LocalBackupJob extends BaseJob {
throw new IOException("Renaming temporary backup file failed!"); throw new IOException("Renaming temporary backup file failed!");
} }
BackupUtil.deleteOldBackups(context); BackupUtilOld.deleteOldBackups(context);
} finally { } finally {
GenericForegroundService.stopForegroundTask(context); GenericForegroundService.stopForegroundTask(context);
} }

View File

@ -5,12 +5,13 @@ import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.preference.EditTextPreference; import androidx.preference.EditTextPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import android.text.TextUtils;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
@ -106,8 +107,9 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
} }
private void setBackupSummary() { private void setBackupSummary() {
findPreference(TextSecurePreferences.BACKUP_NOW) findPreference(TextSecurePreferences.BACKUP_NOW).setSummary(
.setSummary(String.format(getString(R.string.ChatsPreferenceFragment_last_backup_s), BackupUtil.getLastBackupTime(getContext(), Locale.US))); String.format(getString(R.string.ChatsPreferenceFragment_last_backup_s),
BackupUtil.getLastBackupTimeString(getContext(), Locale.getDefault())));
} }
private void setMediaDownloadSummaries() { private void setMediaDownloadSummaries() {

View File

@ -0,0 +1,38 @@
package org.thoughtcrime.securesms.util
import android.content.Context
import network.loki.messenger.R
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.BackupFileRecord
import org.whispersystems.libsignal.util.ByteUtil
import java.security.SecureRandom
import java.util.*
object BackupUtil {
@JvmStatic
fun getLastBackupTimeString(context: Context, locale: Locale): String {
val timestamp = DatabaseFactory.getLokiBackupFilesDatabase(context).getLastBackupFileTime()
if (timestamp == null) {
return context.getString(R.string.BackupUtil_never)
}
return DateUtils.getExtendedRelativeTimeSpanString(context, locale, timestamp.time)
}
@JvmStatic
fun getLastBackup(context: Context): BackupFileRecord? {
return DatabaseFactory.getLokiBackupFilesDatabase(context).getLastBackupFile()
}
@JvmStatic
fun generateBackupPassphrase(): Array<String> {
val result = arrayOfNulls<String>(6)
val random = ByteArray(30)
SecureRandom().nextBytes(random)
for (i in 0..5) {
result[i] = String.format("%05d", ByteUtil.byteArray5ToLong(random, i * 5) % 100000)
}
@Suppress("UNCHECKED_CAST")
return result as Array<String>
}
}

View File

@ -17,9 +17,10 @@ import java.util.Calendar;
import java.util.Locale; import java.util.Locale;
//TODO AC: Needs to be refactored to use Storage Access Framework or Media Store API. //TODO AC: Needs to be refactored to use Storage Access Framework or Media Store API.
public class BackupUtil { /** @deprecated in favor of {@link BackupUtil} */
public class BackupUtilOld {
private static final String TAG = BackupUtil.class.getSimpleName(); private static final String TAG = BackupUtilOld.class.getSimpleName();
public static @NonNull String getLastBackupTime(@NonNull Context context, @NonNull Locale locale) { public static @NonNull String getLastBackupTime(@NonNull Context context, @NonNull Locale locale) {
try { try {