backup upd

This commit is contained in:
Yuriy Liskov
2025-11-29 07:39:16 +02:00
parent a78a30105a
commit aee4c51d79
5 changed files with 394 additions and 5 deletions

View File

@@ -117,8 +117,10 @@ public class BackupSettingsPresenter extends BasePresenter<Void> {
BackupAndRestoreManager backupManager = new BackupAndRestoreManager(getContext());
String backupPath = backupManager.getBackupPathRoot();
options.add(UiOptionItem.from(
String.format("%s:\n%s", getContext().getString(R.string.app_backup), backupManager.getBackupPath()),
String.format("%s:\n%s", getContext().getString(R.string.app_backup), backupPath),
option -> {
AppDialogUtil.showConfirmationDialog(getContext(), getContext().getString(R.string.app_backup), () -> {
mSidebarService.enableSection(MediaGroup.TYPE_SETTINGS, true); // prevent Settings lock
@@ -127,9 +129,8 @@ public class BackupSettingsPresenter extends BasePresenter<Void> {
});
}));
String backupPathCheck = backupManager.getBackupPathCheck();
options.add(UiOptionItem.from(
String.format("%s:\n%s", getContext().getString(R.string.app_restore), backupPathCheck != null ? backupPathCheck : ""),
String.format("%s:\n%s", getContext().getString(R.string.app_restore), backupPath),
option -> {
backupManager.getBackupNames(names -> showLocalRestoreDialog(backupManager, names));
}));

View File

@@ -0,0 +1,220 @@
package com.liskovsoft.smartyoutubetv2.common.misc;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Environment;
import android.provider.OpenableColumns;
import androidx.core.content.FileProvider;
import com.liskovsoft.smartyoutubetv2.common.misc.MotherActivity.OnResult;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class BackupAndRestoreHelper implements OnResult {
private static final int REQ_PICK_FILES = 1001;
private final Context mContext;
private Runnable mOnSuccess;
public BackupAndRestoreHelper(Context context) {
mContext = context;
}
public void exportAppMediaFolder() {
if (VERSION.SDK_INT < 30) {
return;
}
File mediaDir = getExternalStorageDirectory();
File dataDir = new File(mediaDir, "data");
if (!dataDir.exists()) return;
File zipFile = new File(mediaDir, "backup.zip");
zipDirectory(dataDir, zipFile);
Uri uri = FileProvider.getUriForFile(
mContext,
mContext.getPackageName() + ".update_provider",
zipFile
);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
mContext.startActivity(Intent.createChooser(intent, "Send to Total Commander"));
}
public void importAppMediaFolder(Runnable onSuccess) {
if (VERSION.SDK_INT < 30 || onSuccess == null) {
return;
}
mOnSuccess = onSuccess;
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*");
//intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
((MotherActivity) mContext).addOnResult(this);
((Activity) mContext).startActivityForResult(intent, REQ_PICK_FILES);
}
@Override
public void onResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQ_PICK_FILES && resultCode == Activity.RESULT_OK) {
if (data == null) return;
File mediaDir = getExternalStorageDirectory();
File dataDir = new File(mediaDir, "data");
if (!mediaDir.exists()) mediaDir.mkdirs();
Uri uri = data.getData();
if (uri == null && data.getClipData() != null) {
uri = data.getClipData().getItemAt(0).getUri();
}
if (uri == null) return;
File zipFile = new File(mediaDir, "restore.zip");
copyUriToFile(uri, zipFile);
// Cleanup previous data
deleteRecursive(dataDir);
dataDir.mkdirs();
unzip(zipFile, mediaDir);
zipFile.delete();
mOnSuccess.run();
}
}
private void copyUriToFile(Uri uri, File targetDir) {
try {
String fileName = getFileName(uri);
if (fileName == null) fileName = "imported_" + System.currentTimeMillis();
File outFile = new File(targetDir, fileName);
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) {
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
cursor.moveToFirst();
String name = cursor.getString(nameIndex);
cursor.close();
return name;
}
return null;
}
@SuppressWarnings("deprecation")
private File getExternalStorageDirectory() {
File result;
if (VERSION.SDK_INT > 29) {
result = mContext.getExternalMediaDirs()[0];
if (!result.exists()) {
result.mkdirs();
}
} else {
result = Environment.getExternalStorageDirectory();
}
return result;
}
private void zipDirectory(File sourceDir, File zipFile) {
try {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
zipFileRecursive(zos, sourceDir, "data/");
zos.close();
} catch (Exception e) { e.printStackTrace(); }
}
private void zipFileRecursive(ZipOutputStream zos, File file, String base) throws Exception {
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
zipFileRecursive(zos, child, base + child.getName() + "/");
}
}
} else {
FileInputStream fis = new FileInputStream(file);
zos.putNextEntry(new ZipEntry(base.substring(5, base.length() -1))); // strip "data/" prefix and "/" at the end
byte[] buf = new byte[8192];
int len;
while ((len = fis.read(buf)) > 0) zos.write(buf, 0, len);
fis.close();
zos.closeEntry();
}
}
private void unzip(File zipFile, File targetRoot) {
try {
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
ZipEntry entry;
byte[] buffer = new byte[8192];
while ((entry = zis.getNextEntry()) != null) {
File out = new File(targetRoot, entry.getName());
if (entry.isDirectory()) {
out.mkdirs();
} else {
out.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(out);
int len;
while ((len = zis.read(buffer)) > 0) fos.write(buffer, 0, len);
fos.close();
}
zis.closeEntry();
}
zis.close();
} catch (Exception e) { e.printStackTrace(); }
}
private void deleteRecursive(File f) {
if (!f.exists()) return;
if (f.isDirectory()) {
File[] children = f.listFiles();
if (children != null) {
for (File c : children) deleteRecursive(c);
}
}
f.delete();
}
}

View File

@@ -0,0 +1,159 @@
package com.liskovsoft.smartyoutubetv2.common.misc;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Environment;
import android.provider.OpenableColumns;
import androidx.core.content.FileProvider;
import com.liskovsoft.smartyoutubetv2.common.misc.MotherActivity.OnResult;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
public class BackupAndRestoreHelperOld implements OnResult {
private static final int REQ_PICK_FILES = 1001;
private final Context mContext;
public BackupAndRestoreHelperOld(Context context) {
mContext = context;
}
public void exportAppMediaFolder(Context context) {
if (VERSION.SDK_INT < 30) {
return;
}
File mediaDir = getExternalStorageDirectory();
if (mediaDir == null || !mediaDir.exists()) return;
// TODO: create zip file and send only one file zip
File[] files = mediaDir.listFiles();
if (files == null || files.length == 0) return;
ArrayList<Uri> uris = new ArrayList<>();
for (File file : files) {
if (file.isFile()) {
Uri uri = FileProvider.getUriForFile(
context,
context.getPackageName() + ".update_provider",
file
);
uris.add(uri);
}
}
if (uris.isEmpty()) return;
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType("*/*");
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(
Intent.createChooser(intent, "Send to Total Commander")
);
}
public void importAppMediaFolder(Context context) {
if (VERSION.SDK_INT < 30) {
return;
}
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
((MotherActivity) mContext).addOnResult(this);
((Activity) context).startActivityForResult(intent, REQ_PICK_FILES);
}
@Override
public void onResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQ_PICK_FILES && resultCode == Activity.RESULT_OK) {
if (data == null) return;
File targetDir = getExternalStorageDirectory();
if (!targetDir.exists()) targetDir.mkdirs();
// Несколько файлов (ClipData)
if (data.getClipData() != null) {
ClipData clip = data.getClipData();
for (int i = 0; i < clip.getItemCount(); i++) {
Uri uri = clip.getItemAt(i).getUri();
copyUriToFile(mContext, uri, targetDir);
}
}
// Один файл
else if (data.getData() != null) {
Uri uri = data.getData();
copyUriToFile(mContext, uri, targetDir);
}
}
}
private static void copyUriToFile(Context context, Uri uri, File targetDir) {
try {
String fileName = getFileName(context, uri);
if (fileName == null) fileName = "imported_" + System.currentTimeMillis();
File outFile = new File(targetDir, fileName);
InputStream in = context.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 static String getFileName(Context context, Uri uri) {
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
cursor.moveToFirst();
String name = cursor.getString(nameIndex);
cursor.close();
return name;
}
return null;
}
@SuppressWarnings("deprecation")
private File getExternalStorageDirectory() {
File result;
if (VERSION.SDK_INT > 29) {
result = mContext.getExternalMediaDirs()[0];
if (!result.exists()) {
result.mkdirs();
}
} else {
result = Environment.getExternalStorageDirectory();
}
return result;
}
}

View File

@@ -26,6 +26,7 @@ public class BackupAndRestoreManager implements MotherActivity.OnPermissions {
private static final String SHARED_PREFS_SUBDIR = "shared_prefs";
private final List<File> mDataDirs;
private final List<File> mBackupDirs;
private final BackupAndRestoreHelper mHelper;
private Runnable mPendingHandler;
public interface OnBackupNames {
@@ -35,6 +36,8 @@ public class BackupAndRestoreManager implements MotherActivity.OnPermissions {
public BackupAndRestoreManager(Context context) {
mContext = context;
mHelper = new BackupAndRestoreHelper(context);
mDataDirs = new ArrayList<>();
mDataDirs.add(new File(mContext.getApplicationInfo().dataDir, SHARED_PREFS_SUBDIR));
@@ -148,6 +151,8 @@ public class BackupAndRestoreManager implements MotherActivity.OnPermissions {
FileHelpers.delete(new File(destination, HiddenPrefs.SHARED_PREFERENCES_NAME + ".xml"));
}
}
mHelper.exportAppMediaFolder();
}
private void restoreData(String backupName) {
@@ -266,6 +271,10 @@ public class BackupAndRestoreManager implements MotherActivity.OnPermissions {
return currentBackup != null ? currentBackup.toString() : null;
}
public String getBackupPathRoot() {
return String.format("%s/data", getExternalStorageDirectory());
}
public String getBackupPathCheck() {
File currentBackup = getBackupCheck();

View File

@@ -50,8 +50,8 @@ android {
applicationId "org.smarttube"
minSdkVersion project.properties.minSdkVersion
targetSdkVersion project.properties.targetSdkVersion
versionCode 2245
versionName "30.55"
versionCode 2246
versionName "30.56"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "long", "TIMESTAMP", System.currentTimeMillis() + "L"