diff options
author | Martin Fietz <Martin.Fietz@gmail.com> | 2018-01-21 10:51:16 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-21 10:51:16 +0100 |
commit | e6498393a691367c031c5a09ff53d086a497d530 (patch) | |
tree | 777bce0831d93e02ba7fe7d813ba3a593a57921b | |
parent | 9adbf83182aa905d7e662783801fb87842716c93 (diff) | |
parent | dc316074e8b5c408c7dc7eff7b16f9ffd8978ac7 (diff) | |
download | AntennaPod-e6498393a691367c031c5a09ff53d086a497d530.zip |
Merge pull request #2445 from ByteHamster/export
Database import/export
7 files changed, 241 insertions, 1 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6be5bd1b3..8c3c86358 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -168,6 +168,13 @@ android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> </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 android:name=".activity.OpmlImportFromPathActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/opml_import_label"> diff --git a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java new file mode 100644 index 000000000..6a97adcc3 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java @@ -0,0 +1,185 @@ +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 android.support.design.widget.Snackbar; +import android.support.v4.content.IntentCompat; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.MenuItem; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.PodDBAdapter; +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; + +/** + * 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); + getSupportActionBar().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) { + File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME); + InputStream inputStream = null; + try { + 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 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 = IntentCompat.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); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java index 58c7f39a1..5da6f1fb0 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java @@ -38,6 +38,7 @@ import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; +import de.danoeh.antennapod.activity.ImportExportActivity; import org.apache.commons.lang3.ArrayUtils; import java.io.File; @@ -91,6 +92,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc private static final String PREF_OPML_EXPORT = "prefOpmlExport"; private static final String PREF_HTML_EXPORT = "prefHtmlExport"; private static final String STATISTICS = "statistics"; + private static final String IMPORT_EXPORT = "importExport"; private static final String PREF_ABOUT = "prefAbout"; private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings"; @@ -174,6 +176,12 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc return true; } ); + ui.findPreference(PreferenceController.IMPORT_EXPORT).setOnPreferenceClickListener( + preference -> { + activity.startActivity(new Intent(activity, ImportExportActivity.class)); + return true; + } + ); ui.findPreference(PreferenceController.PREF_OPML_EXPORT).setOnPreferenceClickListener( preference -> export(new OpmlWriter())); ui.findPreference(PreferenceController.PREF_HTML_EXPORT).setOnPreferenceClickListener( diff --git a/app/src/main/res/layout/import_export_activity.xml b/app/src/main/res/layout/import_export_activity.xml new file mode 100644 index 000000000..6614a8710 --- /dev/null +++ b/app/src/main/res/layout/import_export_activity.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:id="@+id/import_export_layout" + android:padding="8dp"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/import_export_warning" + android:gravity="center_horizontal"/> + + <Button + android:text="@string/label_export" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/button_export" + android:layout_marginTop="24dp"/> + + <Button + android:text="@string/label_import" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/button_import"/> + +</LinearLayout> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index e81115627..8ed7da731 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -316,6 +316,9 @@ android:key="prefHtmlExport" android:title="@string/html_export_label"/> <Preference + android:key="importExport" + android:title="@string/import_export"/> + <Preference android:key="statistics" android:title="@string/statistics_label"/> </PreferenceCategory> diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 32cf44690..31499c33e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -45,7 +45,7 @@ import de.greenrobot.event.EventBus; public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; - private static final String DATABASE_NAME = "Antennapod.db"; + public static final String DATABASE_NAME = "Antennapod.db"; /** * Maximum number of arguments for IN-operator. diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index a940c031b..2ee295997 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -661,6 +661,15 @@ <string name="proxy_host_invalid_error">Host is not a valid IP address or domain</string> <string name="proxy_port_invalid_error">Port not valid</string> + <!-- Database import/export --> + <string name="import_export">Database import/export</string> + <string name="import_export_warning">This experimental function can be used to transfer your subscriptions and played episodes to another device.\n\nExported databases can only be imported when using the same version of AntennaPod. Otherwise, this function will lead to unexpected behavior.\n\nAfter importing, episodes might be displayed as downloaded even though they are not. Just press the play button of the episodes to make AntennaPod detect this.</string> + <string name="label_import">Import</string> + <string name="label_export">Export</string> + <string name="import_select_file">Select file to import</string> + <string name="export_ok">Export successful. The database was written to the SD card.</string> + <string name="import_ok">Import successful.\n\nPlease press OK to restart AntennaPod</string> + <!-- Casting --> <string name="cast_media_route_menu_title">Play on…</string> <string name="cast_disconnect_label">Disconnect the cast session</string> |