provide progress bar for import backup. fixes #3809
This commit is contained in:
parent
71a56002fe
commit
9ab0fbe48c
|
@ -1,5 +1,6 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
@ -10,9 +11,21 @@ import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.provider.OpenableColumns;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.NotificationManagerCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.CountingInputStream;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.engines.AESEngine;
|
||||||
|
import org.bouncycastle.crypto.io.CipherInputStream;
|
||||||
|
import org.bouncycastle.crypto.modes.AEADBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.params.AEADParameters;
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -33,10 +46,6 @@ import java.util.zip.GZIPInputStream;
|
||||||
import java.util.zip.ZipException;
|
import java.util.zip.ZipException;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.CipherInputStream;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
@ -44,14 +53,9 @@ import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.ui.ManageAccountActivity;
|
import eu.siacs.conversations.ui.ManageAccountActivity;
|
||||||
import eu.siacs.conversations.utils.BackupFileHeader;
|
import eu.siacs.conversations.utils.BackupFileHeader;
|
||||||
import eu.siacs.conversations.utils.Compatibility;
|
|
||||||
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
import static eu.siacs.conversations.services.ExportBackupService.CIPHERMODE;
|
|
||||||
import static eu.siacs.conversations.services.ExportBackupService.KEYTYPE;
|
|
||||||
import static eu.siacs.conversations.services.ExportBackupService.PROVIDER;
|
|
||||||
|
|
||||||
public class ImportBackupService extends Service {
|
public class ImportBackupService extends Service {
|
||||||
|
|
||||||
private static final int NOTIFICATION_ID = 21;
|
private static final int NOTIFICATION_ID = 21;
|
||||||
|
@ -117,9 +121,9 @@ public class ImportBackupService extends Service {
|
||||||
return running.get();
|
return running.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) {
|
public void loadBackupFiles(final OnBackupFilesLoaded onBackupFilesLoaded) {
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
|
final List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
|
||||||
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
|
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
|
||||||
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
|
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
|
||||||
for (String app : apps) {
|
for (String app : apps) {
|
||||||
|
@ -128,7 +132,12 @@ public class ImportBackupService extends Service {
|
||||||
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
|
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (File file : directory.listFiles()) {
|
final File[] files = directory.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final File file : files) {
|
||||||
if (file.isFile() && file.getName().endsWith(".ceb")) {
|
if (file.isFile() && file.getName().endsWith(".ceb")) {
|
||||||
try {
|
try {
|
||||||
final BackupFile backupFile = BackupFile.read(file);
|
final BackupFile backupFile = BackupFile.read(file);
|
||||||
|
@ -149,24 +158,67 @@ public class ImportBackupService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startForegroundService() {
|
private void startForegroundService() {
|
||||||
|
startForeground(NOTIFICATION_ID, createImportBackupNotification(1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateImportBackupNotification(final long total, final long current) {
|
||||||
|
final int max;
|
||||||
|
final int progress;
|
||||||
|
if (total == 0) {
|
||||||
|
max = 1;
|
||||||
|
progress = 0;
|
||||||
|
} else {
|
||||||
|
max = 100;
|
||||||
|
progress = (int) (current * 100 / total);
|
||||||
|
}
|
||||||
|
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||||
|
try {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, createImportBackupNotification(max, progress));
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
Log.d(Config.LOGTAG, "unable to make notification", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notification createImportBackupNotification(final int max, final int progress) {
|
||||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
|
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
|
||||||
mBuilder.setContentTitle(getString(R.string.restoring_backup))
|
mBuilder.setContentTitle(getString(R.string.restoring_backup))
|
||||||
.setSmallIcon(R.drawable.ic_unarchive_white_24dp)
|
.setSmallIcon(R.drawable.ic_unarchive_white_24dp)
|
||||||
.setProgress(1, 0, true);
|
.setProgress(max, progress, max == 1 && progress == 0);
|
||||||
startForeground(NOTIFICATION_ID, mBuilder.build());
|
return mBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean importBackup(Uri uri, String password) {
|
private boolean importBackup(final Uri uri, final String password) {
|
||||||
Log.d(Config.LOGTAG, "importing backup from " + uri);
|
Log.d(Config.LOGTAG, "importing backup from " + uri);
|
||||||
try {
|
try {
|
||||||
SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
|
final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
|
||||||
final InputStream inputStream;
|
final InputStream inputStream;
|
||||||
if ("file".equals(uri.getScheme())) {
|
final String path = uri.getPath();
|
||||||
inputStream = new FileInputStream(new File(uri.getPath()));
|
final long fileSize;
|
||||||
|
if ("file".equals(uri.getScheme()) && path != null) {
|
||||||
|
final File file = new File(path);
|
||||||
|
inputStream = new FileInputStream(file);
|
||||||
|
fileSize = file.length();
|
||||||
} else {
|
} else {
|
||||||
|
final Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
|
||||||
|
if (returnCursor == null) {
|
||||||
|
fileSize = 0;
|
||||||
|
} else {
|
||||||
|
returnCursor.moveToFirst();
|
||||||
|
fileSize = returnCursor.getLong(returnCursor.getColumnIndex(OpenableColumns.SIZE));
|
||||||
|
returnCursor.close();
|
||||||
|
}
|
||||||
inputStream = getContentResolver().openInputStream(uri);
|
inputStream = getContentResolver().openInputStream(uri);
|
||||||
}
|
}
|
||||||
final DataInputStream dataInputStream = new DataInputStream(inputStream);
|
if (inputStream == null) {
|
||||||
|
synchronized (mOnBackupProcessedListeners) {
|
||||||
|
for (final OnBackupProcessed l : mOnBackupProcessedListeners) {
|
||||||
|
l.onBackupRestoreFailed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CountingInputStream countingInputStream = new CountingInputStream(inputStream);
|
||||||
|
final DataInputStream dataInputStream = new DataInputStream(countingInputStream);
|
||||||
final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
|
final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
|
||||||
Log.d(Config.LOGTAG, backupFileHeader.toString());
|
Log.d(Config.LOGTAG, backupFileHeader.toString());
|
||||||
|
|
||||||
|
@ -179,15 +231,14 @@ public class ImportBackupService extends Service {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
|
final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
|
||||||
byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
|
|
||||||
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
|
||||||
IvParameterSpec ivSpec = new IvParameterSpec(backupFileHeader.getIv());
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
|
||||||
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
|
|
||||||
|
|
||||||
GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
|
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8"));
|
cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv()));
|
||||||
|
final CipherInputStream cipherInputStream = new CipherInputStream(countingInputStream, cipher);
|
||||||
|
|
||||||
|
final GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8));
|
||||||
String line;
|
String line;
|
||||||
StringBuilder multiLineQuery = null;
|
StringBuilder multiLineQuery = null;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
|
@ -198,10 +249,12 @@ public class ImportBackupService extends Service {
|
||||||
if (count % 2 == 1) {
|
if (count % 2 == 1) {
|
||||||
db.execSQL(multiLineQuery.toString());
|
db.execSQL(multiLineQuery.toString());
|
||||||
multiLineQuery = null;
|
multiLineQuery = null;
|
||||||
|
updateImportBackupNotification(fileSize, countingInputStream.getCount());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (count % 2 == 0) {
|
if (count % 2 == 0) {
|
||||||
db.execSQL(line);
|
db.execSQL(line);
|
||||||
|
updateImportBackupNotification(fileSize, countingInputStream.getCount());
|
||||||
} else {
|
} else {
|
||||||
multiLineQuery = new StringBuilder(line);
|
multiLineQuery = new StringBuilder(line);
|
||||||
}
|
}
|
||||||
|
@ -220,7 +273,7 @@ public class ImportBackupService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (final Exception e) {
|
||||||
Throwable throwable = e.getCause();
|
Throwable throwable = e.getCause();
|
||||||
final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException;
|
final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException;
|
||||||
synchronized (mOnBackupProcessedListeners) {
|
synchronized (mOnBackupProcessedListeners) {
|
||||||
|
|
Loading…
Reference in New Issue