Moved database import/export to settings
This commit is contained in:
parent
0c7dd3cc24
commit
8ecbe95e16
|
@ -140,13 +140,6 @@
|
||||||
|
|
||||||
<activity android:name=".activity.StorageErrorActivity">
|
<activity android:name=".activity.StorageErrorActivity">
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".activity.ImportExportActivity"
|
|
||||||
android:label="@string/import_export">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
|
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.OpmlImportFromPathActivity"
|
android:name=".activity.OpmlImportFromPathActivity"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
|
|
|
@ -1,218 +0,0 @@
|
||||||
package de.danoeh.antennapod.activity;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.channels.FileChannel;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
|
||||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the 'import/export' screen
|
|
||||||
*/
|
|
||||||
public class ImportExportActivity extends AppCompatActivity {
|
|
||||||
private static final int REQUEST_CODE_RESTORE = 43;
|
|
||||||
private static final int REQUEST_CODE_BACKUP_DOCUMENT = 44;
|
|
||||||
private static final String EXPORT_FILENAME = "AntennaPodBackup.db";
|
|
||||||
private static final String TAG = ImportExportActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
setTheme(UserPreferences.getTheme());
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
|
||||||
if (actionBar != null) {
|
|
||||||
actionBar.setDisplayShowHomeEnabled(true);
|
|
||||||
}
|
|
||||||
setContentView(R.layout.import_export_activity);
|
|
||||||
|
|
||||||
findViewById(R.id.button_export).setOnClickListener(view -> backup());
|
|
||||||
findViewById(R.id.button_import).setOnClickListener(view -> restore());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == android.R.id.home) {
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void backup() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 19) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
|
|
||||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
.setType("application/x-sqlite3")
|
|
||||||
.putExtra(Intent.EXTRA_TITLE, EXPORT_FILENAME);
|
|
||||||
|
|
||||||
startActivityForResult(intent, REQUEST_CODE_BACKUP_DOCUMENT);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
File sd = Environment.getExternalStorageDirectory();
|
|
||||||
File backupDB = new File(sd, EXPORT_FILENAME);
|
|
||||||
writeBackupTo(new FileOutputStream(backupDB));
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
|
||||||
Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restore() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 19) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
|
||||||
intent.setType("*/*");
|
|
||||||
startActivityForResult(intent, REQUEST_CODE_RESTORE);
|
|
||||||
} else {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
intent.setType("*/*");
|
|
||||||
startActivityForResult(Intent.createChooser(intent,
|
|
||||||
getString(R.string.import_select_file)), REQUEST_CODE_RESTORE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
|
|
||||||
if (resultCode != RESULT_OK || resultData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Uri uri = resultData.getData();
|
|
||||||
|
|
||||||
if (requestCode == REQUEST_CODE_RESTORE) {
|
|
||||||
restoreFrom(uri);
|
|
||||||
} else if (requestCode == REQUEST_CODE_BACKUP_DOCUMENT) {
|
|
||||||
backupToDocument(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreFrom(Uri inputUri) {
|
|
||||||
InputStream inputStream = null;
|
|
||||||
try {
|
|
||||||
if (!validateDB(inputUri)) {
|
|
||||||
displayBadFileDialog();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME);
|
|
||||||
inputStream = getContentResolver().openInputStream(inputUri);
|
|
||||||
FileUtils.copyInputStreamToFile(inputStream, currentDB);
|
|
||||||
displayImportSuccessDialog();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
|
||||||
Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show();
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(inputStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final byte[] SQLITE3_MAGIC = "SQLite format 3\0".getBytes();
|
|
||||||
private boolean validateDB(Uri inputUri) throws IOException {
|
|
||||||
try (InputStream inputStream = getContentResolver().openInputStream(inputUri)) {
|
|
||||||
byte[] magicBuf = new byte[SQLITE3_MAGIC.length];
|
|
||||||
if (inputStream.read(magicBuf) == magicBuf.length) {
|
|
||||||
return Arrays.equals(SQLITE3_MAGIC, magicBuf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void displayBadFileDialog() {
|
|
||||||
AlertDialog.Builder d = new AlertDialog.Builder(ImportExportActivity.this);
|
|
||||||
d.setMessage(R.string.import_bad_file)
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(android.R.string.ok, ((dialogInterface, i) -> {
|
|
||||||
// do nothing
|
|
||||||
}))
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void displayImportSuccessDialog() {
|
|
||||||
AlertDialog.Builder d = new AlertDialog.Builder(ImportExportActivity.this);
|
|
||||||
d.setMessage(R.string.import_ok);
|
|
||||||
d.setCancelable(false);
|
|
||||||
d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
|
|
||||||
Intent intent = new Intent(getApplicationContext(), SplashActivity.class);
|
|
||||||
ComponentName cn = intent.getComponent();
|
|
||||||
Intent mainIntent = Intent.makeRestartActivityTask(cn);
|
|
||||||
startActivity(mainIntent);
|
|
||||||
});
|
|
||||||
d.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void backupToDocument(Uri uri) {
|
|
||||||
ParcelFileDescriptor pfd = null;
|
|
||||||
FileOutputStream fileOutputStream = null;
|
|
||||||
try {
|
|
||||||
pfd = getContentResolver().openFileDescriptor(uri, "w");
|
|
||||||
fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
|
|
||||||
writeBackupTo(fileOutputStream);
|
|
||||||
|
|
||||||
Snackbar.make(findViewById(R.id.import_export_layout),
|
|
||||||
R.string.export_ok, Snackbar.LENGTH_SHORT).show();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
|
||||||
Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show();
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(fileOutputStream);
|
|
||||||
|
|
||||||
if (pfd != null) {
|
|
||||||
try {
|
|
||||||
pfd.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.d(TAG, "Unable to close ParcelFileDescriptor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeBackupTo(FileOutputStream outFileStream) {
|
|
||||||
FileChannel src = null;
|
|
||||||
FileChannel dst = null;
|
|
||||||
try {
|
|
||||||
File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME);
|
|
||||||
|
|
||||||
if (currentDB.exists()) {
|
|
||||||
src = new FileInputStream(currentDB).getChannel();
|
|
||||||
dst = outFileStream.getChannel();
|
|
||||||
dst.transferFrom(src, 0, src.size());
|
|
||||||
|
|
||||||
Snackbar.make(findViewById(R.id.import_export_layout),
|
|
||||||
R.string.export_ok, Snackbar.LENGTH_SHORT).show();
|
|
||||||
} else {
|
|
||||||
Snackbar.make(findViewById(R.id.import_export_layout),
|
|
||||||
"Can not access current database", Snackbar.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
|
||||||
Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show();
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(src);
|
|
||||||
IOUtils.closeQuietly(dst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
package de.danoeh.antennapod.fragment.preferences;
|
package de.danoeh.antennapod.fragment.preferences;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
@ -11,26 +11,30 @@ import android.content.pm.ResolveInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.ImportExportActivity;
|
|
||||||
import de.danoeh.antennapod.activity.OpmlImportFromPathActivity;
|
import de.danoeh.antennapod.activity.OpmlImportFromPathActivity;
|
||||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||||
|
import de.danoeh.antennapod.activity.SplashActivity;
|
||||||
import de.danoeh.antennapod.asynctask.DocumentFileExportWorker;
|
import de.danoeh.antennapod.asynctask.DocumentFileExportWorker;
|
||||||
import de.danoeh.antennapod.asynctask.ExportWorker;
|
import de.danoeh.antennapod.asynctask.ExportWorker;
|
||||||
import de.danoeh.antennapod.core.export.ExportWriter;
|
import de.danoeh.antennapod.core.export.ExportWriter;
|
||||||
import de.danoeh.antennapod.core.export.html.HtmlWriter;
|
import de.danoeh.antennapod.core.export.html.HtmlWriter;
|
||||||
import de.danoeh.antennapod.core.export.opml.OpmlWriter;
|
import de.danoeh.antennapod.core.export.opml.OpmlWriter;
|
||||||
|
import de.danoeh.antennapod.core.storage.DatabaseExporter;
|
||||||
|
import io.reactivex.Completable;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
|
@ -38,19 +42,27 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
private static final String PREF_OPML_EXPORT = "prefOpmlExport";
|
private static final String PREF_OPML_EXPORT = "prefOpmlExport";
|
||||||
private static final String PREF_OPML_IMPORT = "prefOpmlImport";
|
private static final String PREF_OPML_IMPORT = "prefOpmlImport";
|
||||||
private static final String PREF_HTML_EXPORT = "prefHtmlExport";
|
private static final String PREF_HTML_EXPORT = "prefHtmlExport";
|
||||||
private static final String IMPORT_EXPORT = "importExport";
|
private static final String PREF_DATABASE_IMPORT = "prefDatabaseImport";
|
||||||
private static final int CHOOSE_OPML_EXPORT_PATH = 1;
|
private static final String PREF_DATABASE_EXPORT = "prefDatabaseExport";
|
||||||
|
private static final int REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH = 1;
|
||||||
private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml";
|
private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml";
|
||||||
private static final String CONTENT_TYPE_OPML = "text/x-opml";
|
private static final String CONTENT_TYPE_OPML = "text/x-opml";
|
||||||
private static final int CHOOSE_HTML_EXPORT_PATH = 2;
|
private static final int REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH = 2;
|
||||||
private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html";
|
private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html";
|
||||||
private static final String CONTENT_TYPE_HTML = "text/html";
|
private static final String CONTENT_TYPE_HTML = "text/html";
|
||||||
|
private static final int REQUEST_CODE_RESTORE_DATABASE = 3;
|
||||||
|
private static final int REQUEST_CODE_BACKUP_DATABASE = 4;
|
||||||
|
private static final String DATABASE_EXPORT_FILENAME = "AntennaPodBackup.db";
|
||||||
private Disposable disposable;
|
private Disposable disposable;
|
||||||
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.preferences_import_export);
|
addPreferencesFromResource(R.xml.preferences_import_export);
|
||||||
setupStorageScreen();
|
setupStorageScreen();
|
||||||
|
progressDialog = new ProgressDialog(getContext());
|
||||||
|
progressDialog.setIndeterminate(true);
|
||||||
|
progressDialog.setMessage(getContext().getString(R.string.please_wait));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -69,24 +81,17 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
private void setupStorageScreen() {
|
private void setupStorageScreen() {
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
|
|
||||||
findPreference(IMPORT_EXPORT).setOnPreferenceClickListener(
|
|
||||||
preference -> {
|
|
||||||
activity.startActivity(new Intent(activity, ImportExportActivity.class));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
|
findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
|
||||||
preference -> {
|
preference -> {
|
||||||
openExportPathPicker(CONTENT_TYPE_OPML, DEFAULT_OPML_OUTPUT_NAME,
|
openExportPathPicker(CONTENT_TYPE_OPML, DEFAULT_OPML_OUTPUT_NAME,
|
||||||
CHOOSE_OPML_EXPORT_PATH, new OpmlWriter());
|
REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH, new OpmlWriter());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener(
|
findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener(
|
||||||
preference -> {
|
preference -> {
|
||||||
openExportPathPicker(CONTENT_TYPE_HTML, DEFAULT_HTML_OUTPUT_NAME,
|
openExportPathPicker(CONTENT_TYPE_HTML, DEFAULT_HTML_OUTPUT_NAME,
|
||||||
CHOOSE_HTML_EXPORT_PATH, new HtmlWriter());
|
REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH, new HtmlWriter());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener(
|
findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener(
|
||||||
|
@ -94,13 +99,20 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
activity.startActivity(new Intent(activity, OpmlImportFromPathActivity.class));
|
activity.startActivity(new Intent(activity, OpmlImportFromPathActivity.class));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
findPreference(PREF_DATABASE_IMPORT).setOnPreferenceClickListener(
|
||||||
|
preference -> {
|
||||||
|
importDatabase();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
findPreference(PREF_DATABASE_EXPORT).setOnPreferenceClickListener(
|
||||||
|
preference -> {
|
||||||
|
exportDatabase();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean export(ExportWriter exportWriter, final Uri uri) {
|
private void exportWithWriter(ExportWriter exportWriter, final Uri uri) {
|
||||||
Context context = getActivity();
|
Context context = getActivity();
|
||||||
final ProgressDialog progressDialog = new ProgressDialog(context);
|
|
||||||
progressDialog.setMessage(context.getString(R.string.exporting_label));
|
|
||||||
progressDialog.setIndeterminate(true);
|
|
||||||
progressDialog.show();
|
progressDialog.show();
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
Observable<File> observable = new ExportWorker(exportWriter, getContext()).exportObservable();
|
Observable<File> observable = new ExportWorker(exportWriter, getContext()).exportObservable();
|
||||||
|
@ -109,24 +121,73 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
.subscribe(output -> {
|
.subscribe(output -> {
|
||||||
Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(),
|
Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(),
|
||||||
context.getString(R.string.provider_authority), output);
|
context.getString(R.string.provider_authority), output);
|
||||||
showExportSuccessDialog(context.getString(R.string.export_success_sum, output.toString()), fileUri);
|
showExportSuccessDialog(output.toString(), fileUri);
|
||||||
}, this::showExportErrorDialog, progressDialog::dismiss);
|
}, this::showExportErrorDialog, progressDialog::dismiss);
|
||||||
} else {
|
} else {
|
||||||
Observable<DocumentFile> observable = new DocumentFileExportWorker(exportWriter, context, uri).exportObservable();
|
DocumentFileExportWorker worker = new DocumentFileExportWorker(exportWriter, context, uri);
|
||||||
disposable = observable.subscribeOn(Schedulers.io())
|
disposable = worker.exportObservable()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(output -> {
|
.subscribe(output ->
|
||||||
showExportSuccessDialog(context.getString(R.string.export_success_sum, output.getUri()), output.getUri());
|
showExportSuccessDialog(output.getUri().toString(), output.getUri()),
|
||||||
}, this::showExportErrorDialog, progressDialog::dismiss);
|
this::showExportErrorDialog, progressDialog::dismiss);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showExportSuccessDialog(final String message, final Uri streamUri) {
|
private void exportDatabase() {
|
||||||
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext())
|
if (Build.VERSION.SDK_INT >= 19) {
|
||||||
.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
|
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
.setType("application/x-sqlite3")
|
||||||
|
.putExtra(Intent.EXTRA_TITLE, DATABASE_EXPORT_FILENAME);
|
||||||
|
|
||||||
|
startActivityForResult(intent, REQUEST_CODE_BACKUP_DATABASE);
|
||||||
|
} else {
|
||||||
|
File sd = Environment.getExternalStorageDirectory();
|
||||||
|
File backupDB = new File(sd, DATABASE_EXPORT_FILENAME);
|
||||||
|
progressDialog.show();
|
||||||
|
disposable = Completable.fromAction(() ->
|
||||||
|
DatabaseExporter.exportToStream(new FileOutputStream(backupDB), getContext()))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
Snackbar.make(getView(), R.string.export_ok, Snackbar.LENGTH_LONG).show();
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}, this::showExportErrorDialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importDatabase() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 19) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
|
intent.setType("*/*");
|
||||||
|
startActivityForResult(intent, REQUEST_CODE_RESTORE_DATABASE);
|
||||||
|
} else {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.setType("*/*");
|
||||||
|
startActivityForResult(Intent.createChooser(intent,
|
||||||
|
getString(R.string.import_select_file)), REQUEST_CODE_RESTORE_DATABASE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDatabaseImportSuccessDialog() {
|
||||||
|
AlertDialog.Builder d = new AlertDialog.Builder(getContext());
|
||||||
|
d.setMessage(R.string.import_ok);
|
||||||
|
d.setCancelable(false);
|
||||||
|
d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
|
||||||
|
Intent intent = new Intent(getContext(), SplashActivity.class);
|
||||||
|
ComponentName cn = intent.getComponent();
|
||||||
|
Intent mainIntent = Intent.makeRestartActivityTask(cn);
|
||||||
|
startActivity(mainIntent);
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showExportSuccessDialog(final String path, final Uri streamUri) {
|
||||||
|
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
|
||||||
|
alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||||
alert.setTitle(R.string.export_success_title);
|
alert.setTitle(R.string.export_success_title);
|
||||||
alert.setMessage(message);
|
alert.setMessage(getContext().getString(R.string.export_success_sum, path));
|
||||||
alert.setPositiveButton(R.string.send_label, (dialog, which) -> {
|
alert.setPositiveButton(R.string.send_label, (dialog, which) -> {
|
||||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label));
|
sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label));
|
||||||
|
@ -147,6 +208,7 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showExportErrorDialog(final Throwable error) {
|
private void showExportErrorDialog(final Throwable error) {
|
||||||
|
progressDialog.dismiss();
|
||||||
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext())
|
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext())
|
||||||
.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||||
alert.setTitle(R.string.export_error_label);
|
alert.setTitle(R.string.export_error_label);
|
||||||
|
@ -154,16 +216,35 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
alert.show();
|
alert.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_OPML_EXPORT_PATH) {
|
if (resultCode != Activity.RESULT_OK || data == null) {
|
||||||
Uri uri = data.getData();
|
return;
|
||||||
export(new OpmlWriter(), uri);
|
|
||||||
}
|
}
|
||||||
|
Uri uri = data.getData();
|
||||||
|
|
||||||
if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_HTML_EXPORT_PATH) {
|
if (requestCode == REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH) {
|
||||||
Uri uri = data.getData();
|
exportWithWriter(new OpmlWriter(), uri);
|
||||||
export(new HtmlWriter(), uri);
|
} else if (requestCode == REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH) {
|
||||||
|
exportWithWriter(new HtmlWriter(), uri);
|
||||||
|
} else if (requestCode == REQUEST_CODE_RESTORE_DATABASE) {
|
||||||
|
progressDialog.show();
|
||||||
|
disposable = Completable.fromAction(() -> DatabaseExporter.importBackup(uri, getContext()))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
showDatabaseImportSuccessDialog();
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}, this::showExportErrorDialog);
|
||||||
|
} else if (requestCode == REQUEST_CODE_BACKUP_DATABASE) {
|
||||||
|
progressDialog.show();
|
||||||
|
disposable = Completable.fromAction(() -> DatabaseExporter.exportToDocument(uri, getContext()))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
Snackbar.make(getView(), R.string.export_ok, Snackbar.LENGTH_LONG).show();
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}, this::showExportErrorDialog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +267,6 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
// If we are using a SDK lower than API 21 or the implicit intent failed
|
// If we are using a SDK lower than API 21 or the implicit intent failed
|
||||||
// fallback to the legacy export process
|
// fallback to the legacy export process
|
||||||
export(writer, null);
|
exportWithWriter(writer, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,18 +14,23 @@
|
||||||
android:summary="@string/opml_import_summary"/>
|
android:summary="@string/opml_import_summary"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory android:title="@string/database">
|
||||||
|
<Preference
|
||||||
|
android:key="prefDatabaseExport"
|
||||||
|
search:keywords="@string/import_export_search_keywords"
|
||||||
|
android:title="@string/database_export_label"
|
||||||
|
android:summary="@string/database_export_summary"/>
|
||||||
|
<Preference
|
||||||
|
android:key="prefDatabaseImport"
|
||||||
|
search:keywords="@string/import_export_search_keywords"
|
||||||
|
android:title="@string/database_import_label"
|
||||||
|
android:summary="@string/database_import_summary"/>
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/html">
|
<PreferenceCategory android:title="@string/html">
|
||||||
<Preference
|
<Preference
|
||||||
android:key="prefHtmlExport"
|
android:key="prefHtmlExport"
|
||||||
android:title="@string/html_export_label"
|
android:title="@string/html_export_label"
|
||||||
android:summary="@string/html_export_summary"/>
|
android:summary="@string/html_export_summary"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/database">
|
|
||||||
<Preference
|
|
||||||
android:key="importExport"
|
|
||||||
search:keywords="@string/import_export_search_keywords"
|
|
||||||
android:title="@string/import_export"
|
|
||||||
android:summary="@string/database_export_summary"/>
|
|
||||||
</PreferenceCategory>
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package de.danoeh.antennapod.core.storage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.util.Log;
|
||||||
|
import de.danoeh.antennapod.core.R;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class DatabaseExporter {
|
||||||
|
private static final String TAG = "DatabaseExporter";
|
||||||
|
private static final byte[] SQLITE3_MAGIC = "SQLite format 3\0".getBytes();
|
||||||
|
|
||||||
|
public static boolean validateDB(Uri inputUri, Context context) throws IOException {
|
||||||
|
try (InputStream inputStream = context.getContentResolver().openInputStream(inputUri)) {
|
||||||
|
byte[] magicBuf = new byte[SQLITE3_MAGIC.length];
|
||||||
|
if (inputStream.read(magicBuf) == magicBuf.length) {
|
||||||
|
return Arrays.equals(SQLITE3_MAGIC, magicBuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exportToDocument(Uri uri, Context context) throws IOException {
|
||||||
|
ParcelFileDescriptor pfd = null;
|
||||||
|
FileOutputStream fileOutputStream = null;
|
||||||
|
try {
|
||||||
|
pfd = context.getContentResolver().openFileDescriptor(uri, "w");
|
||||||
|
fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
|
||||||
|
exportToStream(fileOutputStream, context);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, Log.getStackTraceString(e));
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(fileOutputStream);
|
||||||
|
|
||||||
|
if (pfd != null) {
|
||||||
|
try {
|
||||||
|
pfd.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, "Unable to close ParcelFileDescriptor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exportToStream(FileOutputStream outFileStream, Context context) throws IOException {
|
||||||
|
FileChannel src = null;
|
||||||
|
FileChannel dst = null;
|
||||||
|
try {
|
||||||
|
File currentDB = context.getDatabasePath(PodDBAdapter.DATABASE_NAME);
|
||||||
|
|
||||||
|
if (currentDB.exists()) {
|
||||||
|
src = new FileInputStream(currentDB).getChannel();
|
||||||
|
dst = outFileStream.getChannel();
|
||||||
|
dst.transferFrom(src, 0, src.size());
|
||||||
|
} else {
|
||||||
|
throw new IOException("Can not access current database");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, Log.getStackTraceString(e));
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(src);
|
||||||
|
IOUtils.closeQuietly(dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void importBackup(Uri inputUri, Context context) throws IOException {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
if (!validateDB(inputUri, context)) {
|
||||||
|
throw new IOException(context.getString(R.string.import_bad_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
File currentDB = context.getDatabasePath(PodDBAdapter.DATABASE_NAME);
|
||||||
|
inputStream = context.getContentResolver().openInputStream(inputUri);
|
||||||
|
FileUtils.copyInputStreamToFile(inputStream, currentDB);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, Log.getStackTraceString(e));
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -566,7 +566,9 @@
|
||||||
<string name="choose_file_from_filesystem">From local filesystem</string>
|
<string name="choose_file_from_filesystem">From local filesystem</string>
|
||||||
<string name="opml_export_label">OPML export</string>
|
<string name="opml_export_label">OPML export</string>
|
||||||
<string name="html_export_label">HTML export</string>
|
<string name="html_export_label">HTML export</string>
|
||||||
<string name="exporting_label">Exporting…</string>
|
<string name="database_export_label">Database export</string>
|
||||||
|
<string name="database_import_label">Database import</string>
|
||||||
|
<string name="please_wait">Please wait…</string>
|
||||||
<string name="export_error_label">Export error</string>
|
<string name="export_error_label">Export error</string>
|
||||||
<string name="export_success_title">Export successful</string>
|
<string name="export_success_title">Export successful</string>
|
||||||
<string name="export_success_sum">The exported file was written to:\n\n%1$s</string>
|
<string name="export_success_sum">The exported file was written to:\n\n%1$s</string>
|
||||||
|
|
Loading…
Reference in New Issue