mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-23 20:03:47 +00:00
Support for full backup/restore to sdcard
This commit is contained in:
160
src/org/thoughtcrime/securesms/util/BackupUtil.java
Normal file
160
src/org/thoughtcrime/securesms/util/BackupUtil.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
public class BackupUtil {
|
||||
|
||||
private static final String TAG = BackupUtil.class.getSimpleName();
|
||||
|
||||
public static @NonNull String getLastBackupTime(@NonNull Context context, @NonNull Locale locale) {
|
||||
try {
|
||||
BackupInfo backup = getLatestBackup();
|
||||
|
||||
if (backup == null) return context.getString(R.string.BackupUtil_never);
|
||||
else return DateUtils.getExtendedRelativeTimeSpanString(context, locale, backup.getTimestamp());
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w(TAG, e);
|
||||
return context.getString(R.string.BackupUtil_unknown);
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable BackupInfo getLatestBackup() throws NoExternalStorageException {
|
||||
File backupDirectory = StorageUtil.getBackupDirectory();
|
||||
File[] backups = backupDirectory.listFiles();
|
||||
BackupInfo latestBackup = null;
|
||||
|
||||
for (File backup : backups) {
|
||||
long backupTimestamp = getBackupTimestamp(backup);
|
||||
|
||||
if (latestBackup == null || (backupTimestamp != -1 && backupTimestamp > latestBackup.getTimestamp())) {
|
||||
latestBackup = new BackupInfo(backupTimestamp, backup.length(), backup);
|
||||
}
|
||||
}
|
||||
|
||||
return latestBackup;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static void deleteAllBackups() {
|
||||
try {
|
||||
File backupDirectory = StorageUtil.getBackupDirectory();
|
||||
File[] backups = backupDirectory.listFiles();
|
||||
|
||||
for (File backup : backups) {
|
||||
if (backup.isFile()) backup.delete();
|
||||
}
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteOldBackups() {
|
||||
try {
|
||||
File backupDirectory = StorageUtil.getBackupDirectory();
|
||||
File[] backups = backupDirectory.listFiles();
|
||||
|
||||
if (backups != null && backups.length > 5) {
|
||||
Arrays.sort(backups, (left, right) -> {
|
||||
long leftTimestamp = getBackupTimestamp(left);
|
||||
long rightTimestamp = getBackupTimestamp(right);
|
||||
|
||||
if (leftTimestamp == -1 && rightTimestamp == -1) return 0;
|
||||
else if (leftTimestamp == -1) return 1;
|
||||
else if (rightTimestamp == -1) return -1;
|
||||
|
||||
return (int)(rightTimestamp - leftTimestamp);
|
||||
});
|
||||
|
||||
for (int i=5;i<backups.length;i++) {
|
||||
Log.w(TAG, "Deleting: " + backups[i].getAbsolutePath());
|
||||
|
||||
if (!backups[i].delete()) {
|
||||
Log.w(TAG, "Delete failed: " + backups[i].getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull String[] generateBackupPassphrase() {
|
||||
String[] result = new String[6];
|
||||
byte[] random = new byte[30];
|
||||
|
||||
new SecureRandom().nextBytes(random);
|
||||
|
||||
for (int i=0;i<30;i+=5) {
|
||||
result[i/5] = String.format("%05d", ByteUtil.byteArray5ToLong(random, i) % 100000);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long getBackupTimestamp(File backup) {
|
||||
String name = backup.getName();
|
||||
String[] prefixSuffix = name.split("[.]");
|
||||
|
||||
if (prefixSuffix.length == 2) {
|
||||
String[] parts = prefixSuffix[0].split("\\-");
|
||||
|
||||
if (parts.length == 7) {
|
||||
try {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.YEAR, Integer.parseInt(parts[1]));
|
||||
calendar.set(Calendar.MONTH, Integer.parseInt(parts[2]) - 1);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(parts[3]));
|
||||
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(parts[4]));
|
||||
calendar.set(Calendar.MINUTE, Integer.parseInt(parts[5]));
|
||||
calendar.set(Calendar.SECOND, Integer.parseInt(parts[6]));
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
return calendar.getTimeInMillis();
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static class BackupInfo {
|
||||
|
||||
private final long timestamp;
|
||||
private final long size;
|
||||
private final File file;
|
||||
|
||||
BackupInfo(long timestamp, long size, File file) {
|
||||
this.timestamp = timestamp;
|
||||
this.size = size;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -21,12 +21,11 @@ import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -34,6 +33,9 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public class DateUtils extends android.text.format.DateUtils {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = DateUtils.class.getSimpleName();
|
||||
|
||||
private static boolean isWithin(final long millis, final long span, final TimeUnit unit) {
|
||||
return System.currentTimeMillis() - millis <= unit.toMillis(span);
|
||||
}
|
||||
|
@@ -9,6 +9,27 @@ import java.io.File;
|
||||
|
||||
public class StorageUtil
|
||||
{
|
||||
|
||||
public static File getBackupDirectory() throws NoExternalStorageException {
|
||||
File storage = Environment.getExternalStorageDirectory();
|
||||
|
||||
if (!storage.canWrite()) {
|
||||
throw new NoExternalStorageException();
|
||||
}
|
||||
|
||||
File signal = new File(storage, "Signal");
|
||||
File backups = new File(signal, "Backups");
|
||||
|
||||
if (!backups.exists()) {
|
||||
if (!backups.mkdirs()) {
|
||||
throw new NoExternalStorageException("Unable to create backup directory...");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return backups;
|
||||
}
|
||||
|
||||
private static File getSignalStorageDir() throws NoExternalStorageException {
|
||||
final File storage = Environment.getExternalStorageDirectory();
|
||||
|
||||
@@ -31,7 +52,7 @@ public class StorageUtil
|
||||
return storage.canWrite();
|
||||
}
|
||||
|
||||
public static File getBackupDir() throws NoExternalStorageException {
|
||||
public static File getLegacyBackupDirectory() throws NoExternalStorageException {
|
||||
return getSignalStorageDir();
|
||||
}
|
||||
|
||||
|
@@ -137,6 +137,35 @@ public class TextSecurePreferences {
|
||||
private static final String ACTIVE_SIGNED_PRE_KEY_ID = "pref_active_signed_pre_key_id";
|
||||
private static final String NEXT_SIGNED_PRE_KEY_ID = "pref_next_signed_pre_key_id";
|
||||
|
||||
public static final String BACKUP_ENABLED = "pref_backup_enabled";
|
||||
private static final String BACKUP_PASSPHRASE = "pref_backup_passphrase";
|
||||
private static final String BACKUP_TIME = "pref_backup_next_time";
|
||||
public static final String BACKUP_NOW = "pref_backup_create";
|
||||
|
||||
public static void setBackupPassphrase(@NonNull Context context, @Nullable String passphrase) {
|
||||
setStringPreference(context, BACKUP_PASSPHRASE, passphrase);
|
||||
}
|
||||
|
||||
public static @Nullable String getBackupPassphrase(@NonNull Context context) {
|
||||
return getStringPreference(context, BACKUP_PASSPHRASE, null);
|
||||
}
|
||||
|
||||
public static void setBackupEnabled(@NonNull Context context, boolean value) {
|
||||
setBooleanPreference(context, BACKUP_ENABLED, value);
|
||||
}
|
||||
|
||||
public static boolean isBackupEnabled(@NonNull Context context) {
|
||||
return getBooleanPreference(context, BACKUP_ENABLED, false);
|
||||
}
|
||||
|
||||
public static void setNextBackupTime(@NonNull Context context, long time) {
|
||||
setLongPreference(context, BACKUP_TIME, time);
|
||||
}
|
||||
|
||||
public static long getNextBackupTime(@NonNull Context context) {
|
||||
return getLongPreference(context, BACKUP_TIME, -1);
|
||||
}
|
||||
|
||||
public static int getNextPreKeyId(@NonNull Context context) {
|
||||
return getIntegerPreference(context, NEXT_PRE_KEY_ID, new SecureRandom().nextInt(Medium.MAX_VALUE));
|
||||
}
|
||||
|
@@ -207,6 +207,18 @@ public class Util {
|
||||
return TextSecurePreferences.getLocalNumber(context).equals(address.toPhoneString());
|
||||
}
|
||||
|
||||
public static void readFully(InputStream in, byte[] buffer) throws IOException {
|
||||
int offset = 0;
|
||||
|
||||
for (;;) {
|
||||
int read = in.read(buffer, offset, buffer.length - offset);
|
||||
if (read == -1) throw new IOException("Stream ended early");
|
||||
|
||||
if (read + offset < buffer.length) offset += read;
|
||||
else return;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readFully(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
@@ -351,11 +363,7 @@ public class Util {
|
||||
}
|
||||
|
||||
public static SecureRandom getSecureRandom() {
|
||||
try {
|
||||
return SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return new SecureRandom();
|
||||
}
|
||||
|
||||
public static int getDaysTillBuildExpiry() {
|
||||
|
Reference in New Issue
Block a user