diff options
author | ByteHamster <ByteHamster@users.noreply.github.com> | 2024-03-11 23:10:09 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-11 23:10:09 +0100 |
commit | 2f3f1fd1861f3a76b8473e0734956d7e7a417c0e (patch) | |
tree | af77295f3afdd4367095399c5b1eec742a11c963 /app/src/main/java/de/danoeh/antennapod | |
parent | 5c98a33ed2c82fa6b9b45c7234d429d356f17d8c (diff) | |
download | AntennaPod-2f3f1fd1861f3a76b8473e0734956d7e7a417c0e.zip |
Move import/export to its own module (#6986)
Also clean up ImportExportPreferencesFragment a bit.
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod')
4 files changed, 111 insertions, 212 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index 3f1c17cdc..caafe989d 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -26,14 +26,14 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.export.opml.OpmlElement; -import de.danoeh.antennapod.core.export.opml.OpmlReader; import de.danoeh.antennapod.core.preferences.ThemeSwitcher; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; import de.danoeh.antennapod.databinding.OpmlSelectionBinding; import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.storage.importexport.OpmlElement; +import de.danoeh.antennapod.storage.importexport.OpmlReader; import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java deleted file mode 100644 index 8acfcb58f..000000000 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.danoeh.antennapod.asynctask; - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.documentfile.provider.DocumentFile; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; - -import de.danoeh.antennapod.core.export.ExportWriter; -import de.danoeh.antennapod.core.storage.DBReader; -import io.reactivex.Observable; - -/** - * Writes an OPML file into the user selected export directory in the background. - */ -public class DocumentFileExportWorker { - - private final @NonNull ExportWriter exportWriter; - private @NonNull Context context; - private @NonNull Uri outputFileUri; - - public DocumentFileExportWorker(@NonNull ExportWriter exportWriter, @NonNull Context context, @NonNull Uri outputFileUri) { - this.exportWriter = exportWriter; - this.context = context; - this.outputFileUri = outputFileUri; - } - - public Observable<DocumentFile> exportObservable() { - DocumentFile output = DocumentFile.fromSingleUri(context, outputFileUri); - return Observable.create(subscriber -> { - OutputStream outputStream = null; - OutputStreamWriter writer = null; - try { - Uri uri = output.getUri(); - outputStream = context.getContentResolver().openOutputStream(uri, "wt"); - if (outputStream == null) { - throw new IOException(); - } - writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8")); - exportWriter.writeDocument(DBReader.getFeedList(), writer, context); - subscriber.onNext(output); - } catch (IOException e) { - subscriber.onError(e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - subscriber.onError(e); - } - } - if (outputStream != null) { - try { - outputStream.close(); - } catch (IOException e) { - subscriber.onError(e); - } - } - subscriber.onComplete(); - } - }); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java deleted file mode 100644 index 97a5f157b..000000000 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.danoeh.antennapod.asynctask; - -import android.content.Context; -import androidx.annotation.NonNull; -import android.util.Log; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; - -import de.danoeh.antennapod.core.export.ExportWriter; -import de.danoeh.antennapod.storage.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DBReader; -import io.reactivex.Observable; - -/** - * Writes an OPML file into the export directory in the background. - */ -public class ExportWorker { - - private static final String EXPORT_DIR = "export/"; - private static final String TAG = "ExportWorker"; - private static final String DEFAULT_OUTPUT_NAME = "antennapod-feeds"; - - private final @NonNull ExportWriter exportWriter; - private final @NonNull File output; - private final Context context; - - public ExportWorker(@NonNull ExportWriter exportWriter, Context context) { - this(exportWriter, new File(UserPreferences.getDataFolder(EXPORT_DIR), - DEFAULT_OUTPUT_NAME + "." + exportWriter.fileExtension()), context); - } - - private ExportWorker(@NonNull ExportWriter exportWriter, @NonNull File output, Context context) { - this.exportWriter = exportWriter; - this.output = output; - this.context = context; - } - - public Observable<File> exportObservable() { - if (output.exists()) { - boolean success = output.delete(); - Log.w(TAG, "Overwriting previously exported file: " + success); - } - return Observable.create(subscriber -> { - OutputStreamWriter writer = null; - try { - writer = new OutputStreamWriter(new FileOutputStream(output), Charset.forName("UTF-8")); - exportWriter.writeDocument(DBReader.getFeedList(), writer, context); - subscriber.onNext(output); - } catch (IOException e) { - subscriber.onError(e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - subscriber.onError(e); - } - } - subscriber.onComplete(); - } - }); - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java index 71ba326dd..9191825aa 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java @@ -16,6 +16,7 @@ import androidx.activity.result.contract.ActivityResultContracts.GetContent; import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.NonNull; import androidx.annotation.StringRes; +import androidx.documentfile.provider.DocumentFile; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.core.app.ShareCompat; import androidx.core.content.FileProvider; @@ -25,13 +26,15 @@ import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.OpmlImportActivity; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.asynctask.DocumentFileExportWorker; -import de.danoeh.antennapod.asynctask.ExportWorker; -import de.danoeh.antennapod.core.export.ExportWriter; -import de.danoeh.antennapod.core.export.favorites.FavoritesWriter; -import de.danoeh.antennapod.core.export.html.HtmlWriter; -import de.danoeh.antennapod.core.export.opml.OpmlWriter; -import de.danoeh.antennapod.core.storage.DatabaseExporter; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.model.feed.FeedItem; +import de.danoeh.antennapod.model.feed.FeedItemFilter; +import de.danoeh.antennapod.model.feed.SortOrder; +import de.danoeh.antennapod.storage.importexport.DatabaseExporter; +import de.danoeh.antennapod.storage.importexport.FavoritesWriter; +import de.danoeh.antennapod.storage.importexport.HtmlWriter; +import de.danoeh.antennapod.storage.importexport.OpmlWriter; +import de.danoeh.antennapod.storage.preferences.UserPreferences; import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -39,8 +42,14 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import java.util.Locale; public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { @@ -57,18 +66,29 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { private static final String CONTENT_TYPE_HTML = "text/html"; private static final String DEFAULT_FAVORITES_OUTPUT_NAME = "antennapod-favorites-%s.html"; private static final String DATABASE_EXPORT_FILENAME = "AntennaPodBackup-%s.db"; + private final ActivityResultLauncher<Intent> chooseOpmlExportPathLauncher = - registerForActivityResult(new StartActivityForResult(), this::chooseOpmlExportPathResult); + registerForActivityResult(new StartActivityForResult(), + result -> exportToDocument(result, Export.OPML)); private final ActivityResultLauncher<Intent> chooseHtmlExportPathLauncher = - registerForActivityResult(new StartActivityForResult(), this::chooseHtmlExportPathResult); + registerForActivityResult(new StartActivityForResult(), + result -> exportToDocument(result, Export.HTML)); private final ActivityResultLauncher<Intent> chooseFavoritesExportPathLauncher = - registerForActivityResult(new StartActivityForResult(), this::chooseFavoritesExportPathResult); + registerForActivityResult(new StartActivityForResult(), + result -> exportToDocument(result, Export.FAVORITES)); private final ActivityResultLauncher<Intent> restoreDatabaseLauncher = registerForActivityResult(new StartActivityForResult(), this::restoreDatabaseResult); private final ActivityResultLauncher<String> backupDatabaseLauncher = registerForActivityResult(new BackupDatabase(), this::backupDatabaseResult); private final ActivityResultLauncher<String> chooseOpmlImportPathLauncher = - registerForActivityResult(new GetContent(), this::chooseOpmlImportPathResult); + registerForActivityResult(new GetContent(), uri -> { + if (uri != null) { + final Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + }); + private Disposable disposable; private ProgressDialog progressDialog; @@ -95,20 +115,16 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { } } - private String dateStampFilename(String fname) { - return String.format(fname, new SimpleDateFormat("yyyy-MM-dd", Locale.US).format(new Date())); - } - private void setupStorageScreen() { findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( preference -> { - openExportPathPicker(Export.OPML, chooseOpmlExportPathLauncher, new OpmlWriter()); + openExportPathPicker(Export.OPML, chooseOpmlExportPathLauncher); return true; } ); findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( preference -> { - openExportPathPicker(Export.HTML, chooseHtmlExportPathLauncher, new HtmlWriter()); + openExportPathPicker(Export.HTML, chooseHtmlExportPathLauncher); return true; }); findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( @@ -132,32 +148,13 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { }); findPreference(PREF_FAVORITE_EXPORT).setOnPreferenceClickListener( preference -> { - openExportPathPicker(Export.FAVORITES, chooseFavoritesExportPathLauncher, new FavoritesWriter()); + openExportPathPicker(Export.FAVORITES, chooseFavoritesExportPathLauncher); return true; }); } - private void exportWithWriter(ExportWriter exportWriter, Uri uri, Export exportType) { - Context context = getActivity(); - progressDialog.show(); - if (uri == null) { - Observable<File> observable = new ExportWorker(exportWriter, getContext()).exportObservable(); - disposable = observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> { - Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(), - context.getString(R.string.provider_authority), output); - showExportSuccessSnackbar(fileUri, exportType.contentType); - }, this::showExportErrorDialog, progressDialog::dismiss); - } else { - DocumentFileExportWorker worker = new DocumentFileExportWorker(exportWriter, context, uri); - disposable = worker.exportObservable() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> - showExportSuccessSnackbar(output.getUri(), exportType.contentType), - this::showExportErrorDialog, progressDialog::dismiss); - } + private String dateStampFilename(String fname) { + return String.format(fname, new SimpleDateFormat("yyyy-MM-dd", Locale.US).format(new Date())); } private void exportDatabase() { @@ -211,30 +208,6 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { alert.show(); } - private void chooseOpmlExportPathResult(final ActivityResult result) { - if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { - return; - } - final Uri uri = result.getData().getData(); - exportWithWriter(new OpmlWriter(), uri, Export.OPML); - } - - private void chooseHtmlExportPathResult(final ActivityResult result) { - if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { - return; - } - final Uri uri = result.getData().getData(); - exportWithWriter(new HtmlWriter(), uri, Export.HTML); - } - - private void chooseFavoritesExportPathResult(final ActivityResult result) { - if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { - return; - } - final Uri uri = result.getData().getData(); - exportWithWriter(new FavoritesWriter(), uri, Export.FAVORITES); - } - private void restoreDatabaseResult(final ActivityResult result) { if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { return; @@ -264,16 +237,7 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { }, this::showExportErrorDialog); } - private void chooseOpmlImportPathResult(final Uri uri) { - if (uri == null) { - return; - } - final Intent intent = new Intent(getContext(), OpmlImportActivity.class); - intent.setData(uri); - startActivity(intent); - } - - private void openExportPathPicker(Export exportType, ActivityResultLauncher<Intent> result, ExportWriter writer) { + private void openExportPathPicker(Export exportType, ActivityResultLauncher<Intent> result) { String title = dateStampFilename(exportType.outputNameTemplate); Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) @@ -292,7 +256,78 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { // If we are using a SDK lower than API 21 or the implicit intent failed // fallback to the legacy export process - exportWithWriter(writer, null, exportType); + File output = new File(UserPreferences.getDataFolder("export/"), title); + exportToFile(exportType, output); + } + + private void exportToFile(Export exportType, File output) { + progressDialog.show(); + disposable = Observable.create( + subscriber -> { + if (output.exists()) { + boolean success = output.delete(); + Log.w(TAG, "Overwriting previously exported file: " + success); + } + try (FileOutputStream fileOutputStream = new FileOutputStream(output)) { + writeToStream(fileOutputStream, exportType); + subscriber.onNext(output); + } catch (IOException e) { + subscriber.onError(e); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(outputFile -> { + progressDialog.dismiss(); + Uri fileUri = FileProvider.getUriForFile(getActivity().getApplicationContext(), + getString(R.string.provider_authority), output); + showExportSuccessSnackbar(fileUri, exportType.contentType); + }, this::showExportErrorDialog, progressDialog::dismiss); + } + + private void exportToDocument(final ActivityResult result, Export exportType) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { + return; + } + progressDialog.show(); + DocumentFile output = DocumentFile.fromSingleUri(getContext(), result.getData().getData()); + disposable = Observable.create( + subscriber -> { + try (OutputStream outputStream = getContext().getContentResolver() + .openOutputStream(output.getUri(), "wt")) { + writeToStream(outputStream, exportType); + subscriber.onNext(output); + } catch (IOException e) { + subscriber.onError(e); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ignore -> { + progressDialog.dismiss(); + showExportSuccessSnackbar(output.getUri(), exportType.contentType); + }, this::showExportErrorDialog, progressDialog::dismiss); + } + + private void writeToStream(OutputStream outputStream, Export type) throws IOException { + try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"))) { + switch (type) { + case HTML: + HtmlWriter.writeDocument(DBReader.getFeedList(), writer, getContext()); + break; + case OPML: + OpmlWriter.writeDocument(DBReader.getFeedList(), writer); + break; + case FAVORITES: + List<FeedItem> allFavorites = DBReader.getEpisodes(0, Integer.MAX_VALUE, + new FeedItemFilter(FeedItemFilter.IS_FAVORITE), SortOrder.DATE_NEW_OLD); + FavoritesWriter.writeDocument(allFavorites, writer, getContext()); + break; + default: + showExportErrorDialog(new Exception("Invalid export type")); + break; + } + } } private static class BackupDatabase extends ActivityResultContracts.CreateDocument { |