mirror of
https://github.com/yuliskov/SmartTube.git
synced 2026-01-05 21:40:47 -06:00
Android 11+ local restore fix
This commit is contained in:
@@ -54,6 +54,12 @@ public class BackupSettingsPresenter extends BasePresenter<Void> {
|
||||
createAndShowDialog();
|
||||
}
|
||||
|
||||
public void showLocalRestoreDialog() {
|
||||
BackupAndRestoreManager backupManager = new BackupAndRestoreManager(getContext());
|
||||
|
||||
backupManager.getBackupNames(names -> showLocalRestoreDialog(backupManager, names));
|
||||
}
|
||||
|
||||
private void createAndShowDialog() {
|
||||
AppDialogPresenter settingsPresenter = AppDialogPresenter.instance(getContext());
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import com.liskovsoft.smartyoutubetv2.common.app.presenters.settings.BackupSettingsPresenter;
|
||||
import com.liskovsoft.smartyoutubetv2.common.misc.MotherActivity.OnResult;
|
||||
|
||||
import java.io.File;
|
||||
@@ -91,7 +92,7 @@ public class BackupAndRestoreHelper implements OnResult {
|
||||
if (uri == null) return;
|
||||
|
||||
File zipFile = new File(mediaDir, "restore.zip");
|
||||
copyUriToFile(uri, zipFile);
|
||||
copyUriToDir(uri, zipFile);
|
||||
|
||||
// Cleanup previous data
|
||||
deleteRecursive(dataDir);
|
||||
@@ -127,12 +128,14 @@ public class BackupAndRestoreHelper implements OnResult {
|
||||
copyUriToFile(zipUri, tempZip);
|
||||
|
||||
// Unpack ZIP to data folder
|
||||
unzip(tempZip, dataDir);
|
||||
unzip(tempZip, mediaDir);
|
||||
|
||||
// Delete the temporary ZIP
|
||||
tempZip.delete();
|
||||
|
||||
Toast.makeText(mContext, "Backup restored successfully", Toast.LENGTH_SHORT).show();
|
||||
BackupSettingsPresenter.instance(mContext).showLocalRestoreDialog();
|
||||
|
||||
//Toast.makeText(mContext, "Backup restored successfully", Toast.LENGTH_SHORT).show();
|
||||
|
||||
// TODO: possibly launch restore dialog
|
||||
} catch (Exception e) {
|
||||
@@ -141,7 +144,7 @@ public class BackupAndRestoreHelper implements OnResult {
|
||||
}
|
||||
}
|
||||
|
||||
private void copyUriToFile(Uri uri, File targetDir) {
|
||||
private void copyUriToDir(Uri uri, File targetDir) {
|
||||
try {
|
||||
String fileName = getFileName(uri);
|
||||
if (fileName == null) fileName = "imported_" + System.currentTimeMillis();
|
||||
@@ -165,6 +168,25 @@ public class BackupAndRestoreHelper implements OnResult {
|
||||
}
|
||||
}
|
||||
|
||||
private void copyUriToFile(Uri uri, File outFile) {
|
||||
try {
|
||||
InputStream in = mContext.getContentResolver().openInputStream(uri);
|
||||
OutputStream out = new FileOutputStream(outFile);
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
|
||||
in.close();
|
||||
out.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String getFileName(Uri uri) {
|
||||
Cursor cursor = mContext.getContentResolver().query(uri, null, null, null, null);
|
||||
if (cursor != null) {
|
||||
@@ -212,7 +234,7 @@ public class BackupAndRestoreHelper implements OnResult {
|
||||
}
|
||||
} else {
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
zos.putNextEntry(new ZipEntry(base.substring(5, base.length() -1))); // strip "data/" prefix and "/" at the end
|
||||
zos.putNextEntry(new ZipEntry(base.substring(0, base.length() -1))); // strip "/" at the end to mark as file
|
||||
byte[] buf = new byte[8192];
|
||||
int len;
|
||||
while ((len = fis.read(buf)) > 0) zos.write(buf, 0, len);
|
||||
|
||||
@@ -27,6 +27,7 @@ public class BackupAndRestoreManager implements MotherActivity.OnPermissions {
|
||||
private final List<File> mDataDirs;
|
||||
private final List<File> mBackupDirs;
|
||||
private final BackupAndRestoreHelper mHelper;
|
||||
private final String[] mBackupPatterns;
|
||||
private Runnable mPendingHandler;
|
||||
|
||||
public interface OnBackupNames {
|
||||
@@ -41,6 +42,13 @@ public class BackupAndRestoreManager implements MotherActivity.OnPermissions {
|
||||
mDataDirs = new ArrayList<>();
|
||||
mDataDirs.add(new File(mContext.getApplicationInfo().dataDir, SHARED_PREFS_SUBDIR));
|
||||
|
||||
mBackupPatterns = new String[] {
|
||||
"yt_service_prefs.xml",
|
||||
"com.liskovsoft.appupdatechecker2.preferences.xml",
|
||||
"com.liskovsoft.sharedutils.prefs.GlobalPreferences.xml",
|
||||
"_preferences.xml" // before _ should be the app package name
|
||||
};
|
||||
|
||||
mBackupDirs = new ArrayList<>();
|
||||
|
||||
initBackupDirs();
|
||||
|
||||
@@ -243,6 +243,7 @@ public class GDriveBackupManager {
|
||||
String[] altPackages = new String[] {
|
||||
"org.smarttube.beta",
|
||||
"org.smarttube.stable",
|
||||
"org.smarttube.fdroid",
|
||||
"com.liskovsoft.smarttubetv.beta",
|
||||
"com.teamsmart.videomanager.tv"
|
||||
};
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
package com.liskovsoft.smartyoutubetv2.common.misc;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import com.liskovsoft.googleapi.oauth2.impl.GoogleSignInService;
|
||||
import com.liskovsoft.googleapi.service.DriveService;
|
||||
import com.liskovsoft.sharedutils.helpers.FileHelpers;
|
||||
import com.liskovsoft.sharedutils.helpers.Helpers;
|
||||
import com.liskovsoft.sharedutils.helpers.MessageHelpers;
|
||||
import com.liskovsoft.sharedutils.rx.RxHelper;
|
||||
import com.liskovsoft.smartyoutubetv2.common.R;
|
||||
import com.liskovsoft.smartyoutubetv2.common.app.presenters.GoogleSignInPresenter;
|
||||
import com.liskovsoft.smartyoutubetv2.common.prefs.GeneralData;
|
||||
import com.liskovsoft.smartyoutubetv2.common.utils.AppDialogUtil;
|
||||
import com.liskovsoft.smartyoutubetv2.common.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class GDriveBackupManagerOld {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static GDriveBackupManagerOld sInstance;
|
||||
private final Context mContext;
|
||||
private static final String SHARED_PREFS_SUBDIR = "shared_prefs";
|
||||
private static final String BACKUP_NAME = "backup.zip";
|
||||
private final GoogleSignInService mSignInService;
|
||||
private final String mDataDir;
|
||||
private final String mBackupDir;
|
||||
private final GeneralData mGeneralData;
|
||||
private Disposable mBackupAction;
|
||||
private Disposable mRestoreAction;
|
||||
private final String[] mBackupNames;
|
||||
private boolean mIsBlocking;
|
||||
|
||||
private GDriveBackupManagerOld(Context context) {
|
||||
mContext = context;
|
||||
mGeneralData = GeneralData.instance(context);
|
||||
mDataDir = String.format("%s/%s", mContext.getApplicationInfo().dataDir, SHARED_PREFS_SUBDIR);
|
||||
mBackupDir = String.format("SmartTubeBackup/%s", context.getPackageName());
|
||||
mSignInService = GoogleSignInService.instance();
|
||||
mBackupNames = new String[] {
|
||||
"yt_service_prefs.xml",
|
||||
"com.liskovsoft.appupdatechecker2.preferences.xml",
|
||||
"com.liskovsoft.sharedutils.prefs.GlobalPreferences.xml",
|
||||
"_preferences.xml" // before _ should be the app package name
|
||||
};
|
||||
}
|
||||
|
||||
public static GDriveBackupManagerOld instance(Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new GDriveBackupManagerOld(context);
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public static void unhold() {
|
||||
sInstance = null;
|
||||
}
|
||||
|
||||
public void backup() {
|
||||
mIsBlocking = false;
|
||||
backupInt();
|
||||
}
|
||||
|
||||
public void backupBlocking() {
|
||||
mIsBlocking = true;
|
||||
backupInt();
|
||||
}
|
||||
|
||||
private void backupInt() {
|
||||
if (mIsBlocking && !mSignInService.isSigned()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (RxHelper.isAnyActionRunning(mBackupAction, mRestoreAction)) {
|
||||
if (!mIsBlocking)
|
||||
MessageHelpers.showMessage(mContext, R.string.wait_data_loading);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSignInService.isSigned()) {
|
||||
startBackupConfirm();
|
||||
} else {
|
||||
logIn(this::startBackupConfirm);
|
||||
}
|
||||
}
|
||||
|
||||
public void restore() {
|
||||
if (RxHelper.isAnyActionRunning(mBackupAction, mRestoreAction)) {
|
||||
MessageHelpers.showMessage(mContext, R.string.wait_data_loading);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSignInService.isSigned()) {
|
||||
startRestoreConfirm();
|
||||
} else {
|
||||
logIn(this::startRestoreConfirm);
|
||||
}
|
||||
}
|
||||
|
||||
private void startBackupConfirm() {
|
||||
if (!mIsBlocking) {
|
||||
AppDialogUtil.showConfirmationDialog(mContext, mContext.getString(R.string.app_backup), this::startBackup);
|
||||
} else {
|
||||
startBackup();
|
||||
}
|
||||
}
|
||||
|
||||
private void startBackup() {
|
||||
String backupDir = getBackupDir();
|
||||
startBackup2(backupDir, mDataDir);
|
||||
}
|
||||
|
||||
private void startBackup(String backupDir, String dataDir) {
|
||||
Collection<File> files = FileHelpers.listFileTree(new File(dataDir));
|
||||
|
||||
Consumer<File> backupConsumer = file -> {
|
||||
if (file.isFile()) {
|
||||
if (checkFileName(file.getName())) {
|
||||
if (!mIsBlocking) MessageHelpers.showLongMessage(mContext, mContext.getString(R.string.app_backup) + "\n" + file.getName());
|
||||
|
||||
RxHelper.runBlocking(DriveService.uploadFile(file, Uri.parse(String.format("%s%s", backupDir, file.getAbsolutePath().replace(dataDir, "")))));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (mIsBlocking) {
|
||||
Observable.fromIterable(files)
|
||||
.blockingSubscribe(backupConsumer);
|
||||
} else {
|
||||
mBackupAction = Observable.fromIterable(files)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io()) // run subscribe on separate thread
|
||||
.subscribe(backupConsumer, error -> MessageHelpers.showLongMessage(mContext, error.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void startBackup2(String backupDir, String dataDir) {
|
||||
File source = new File(dataDir);
|
||||
File zipFile = new File(mContext.getCacheDir(), BACKUP_NAME);
|
||||
ZipHelper.zipFolder(source, zipFile, mBackupNames);
|
||||
|
||||
Observable<Void> uploadFile = DriveService.uploadFile(zipFile, Uri.parse(String.format("%s/%s", backupDir, BACKUP_NAME)));
|
||||
|
||||
if (mIsBlocking) {
|
||||
RxHelper.runBlocking(uploadFile);
|
||||
} else {
|
||||
MessageHelpers.showLongMessage(mContext, mContext.getString(R.string.app_backup));
|
||||
mBackupAction = uploadFile
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
unused -> {},
|
||||
error -> MessageHelpers.showLongMessage(mContext, error.getMessage()),
|
||||
() -> MessageHelpers.showMessage(mContext, R.string.msg_done)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void startRestoreConfirm() {
|
||||
AppDialogUtil.showConfirmationDialog(mContext, mContext.getString(R.string.app_restore), this::startRestore);
|
||||
}
|
||||
|
||||
private void startRestore() {
|
||||
startRestore2(getBackupDir(), mDataDir,
|
||||
() -> startRestore2(getAltBackupDir(), mDataDir,
|
||||
() -> startRestore(getBackupDir(), mDataDir,
|
||||
() -> startRestore(getAltBackupDir(), mDataDir, null))));
|
||||
}
|
||||
|
||||
private void startRestore(String backupDir, String dataDir, Runnable onError) {
|
||||
mRestoreAction = DriveService.getList(Uri.parse(backupDir))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io()) // run subscribe on separate thread
|
||||
.subscribe(names -> {
|
||||
// remove old data
|
||||
FileHelpers.delete(dataDir);
|
||||
|
||||
for (String name : names) {
|
||||
if (checkFileName(name)) {
|
||||
MessageHelpers.showLongMessage(mContext, mContext.getString(R.string.app_restore) + "\n" + name);
|
||||
|
||||
DriveService.getFile(Uri.parse(String.format("%s/%s", backupDir, name)))
|
||||
.blockingSubscribe(inputStream -> FileHelpers.copy(inputStream, new File(dataDir, fixAltPackageName(name))));
|
||||
}
|
||||
}
|
||||
|
||||
Utils.restartTheApp(mContext);
|
||||
}, error -> {
|
||||
if (onError != null)
|
||||
onError.run();
|
||||
else MessageHelpers.showLongMessage(mContext, R.string.nothing_found);
|
||||
});
|
||||
}
|
||||
|
||||
private void startRestore2(String backupDir, String dataDir, Runnable onError) {
|
||||
MessageHelpers.showLongMessage(mContext, mContext.getString(R.string.app_restore));
|
||||
mRestoreAction = DriveService.getFile(Uri.parse(String.format("%s/%s", backupDir, BACKUP_NAME)))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(inputStream -> {
|
||||
File zipFile = new File(mContext.getCacheDir(), BACKUP_NAME);
|
||||
FileHelpers.copy(inputStream, zipFile);
|
||||
|
||||
File out = new File(dataDir);
|
||||
// remove old data
|
||||
FileHelpers.delete(out);
|
||||
ZipHelper.unzipToFolder(zipFile, out);
|
||||
fixFileNames(out);
|
||||
|
||||
Utils.restartTheApp(mContext);
|
||||
}, error -> {
|
||||
if (onError != null)
|
||||
onError.run();
|
||||
else MessageHelpers.showLongMessage(mContext, R.string.nothing_found);
|
||||
}, () -> MessageHelpers.showMessage(mContext, R.string.msg_done));
|
||||
}
|
||||
|
||||
private void logIn(Runnable onDone) {
|
||||
GoogleSignInPresenter.instance(mContext).start(onDone);
|
||||
}
|
||||
|
||||
private boolean checkFileName(String name) {
|
||||
return Helpers.endsWithAny(name, mBackupNames);
|
||||
}
|
||||
|
||||
private String fixAltPackageName(String name) {
|
||||
String altPackageName = getAltPackageName();
|
||||
return name.replace(altPackageName, mContext.getPackageName());
|
||||
}
|
||||
|
||||
private String getAltPackageName() {
|
||||
String[] altPackages = new String[] {
|
||||
"org.smarttube.beta",
|
||||
"org.smarttube.stable",
|
||||
"org.smarttube.fdroid",
|
||||
"com.liskovsoft.smarttubetv.beta",
|
||||
"com.teamsmart.videomanager.tv"
|
||||
};
|
||||
return mContext.getPackageName().equals(altPackages[0]) ? altPackages[1] : altPackages[0];
|
||||
}
|
||||
|
||||
private String getDeviceSuffix() {
|
||||
return mGeneralData.isDeviceSpecificBackupEnabled() ? "_" + Build.MODEL.replace(" ", "_") : "";
|
||||
}
|
||||
|
||||
private String getAltBackupDir() {
|
||||
String backupDir = getBackupDir();
|
||||
String altPackageName = getAltPackageName();
|
||||
return backupDir.replace(mContext.getPackageName(), altPackageName);
|
||||
}
|
||||
|
||||
public String getBackupDir() {
|
||||
return mBackupDir + getDeviceSuffix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix file names from other app versions
|
||||
*/
|
||||
private void fixFileNames(File dataDir) {
|
||||
Collection<File> files = FileHelpers.listFileTree(dataDir);
|
||||
|
||||
String suffix = "_preferences.xml";
|
||||
String targetName = mContext.getPackageName() + suffix;
|
||||
|
||||
for (File file : files) {
|
||||
if (file.getName().endsWith(suffix) && !file.getName().endsWith(targetName)) {
|
||||
FileHelpers.copy(file, new File(file.getParentFile(), targetName));
|
||||
FileHelpers.delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ apply plugin: 'com.android.application'
|
||||
|
||||
// First letter of flavor name must be in Uppercase
|
||||
if (new File("${projectDir}/google-services.json").exists() && (getGradle().getStartParameter().getTaskRequests().toString().contains("Stbeta") ||
|
||||
getGradle().getStartParameter().getTaskRequests().toString().contains("Strtarmenia"))) {
|
||||
getGradle().getStartParameter().getTaskRequests().toString().contains("Ststable"))) {
|
||||
// Google Services Gradle plugin (transforms google-services.json file)
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
@@ -50,8 +50,8 @@ android {
|
||||
applicationId "org.smarttube"
|
||||
minSdkVersion project.properties.minSdkVersion
|
||||
targetSdkVersion project.properties.targetSdkVersion
|
||||
versionCode 2246
|
||||
versionName "30.56"
|
||||
versionCode 2247
|
||||
versionName "30.57"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
buildConfigField "long", "TIMESTAMP", System.currentTimeMillis() + "L"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user