mirror of
https://github.com/AntennaPod/AntennaPod.git
synced 2025-02-02 11:46:55 +01:00
Move import/export to its own module (#6986)
Also clean up ImportExportPreferencesFragment a bit.
This commit is contained in:
parent
5c98a33ed2
commit
2f3f1fd186
@ -86,6 +86,7 @@ dependencies {
|
||||
implementation project(':playback:base')
|
||||
implementation project(':playback:cast')
|
||||
implementation project(':storage:database')
|
||||
implementation project(':storage:importexport')
|
||||
implementation project(':storage:preferences')
|
||||
implementation project(':ui:app-start-intent')
|
||||
implementation project(':ui:common')
|
||||
|
@ -6,13 +6,15 @@ import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedFunding;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.parser.feed.namespace.PodcastIndex;
|
||||
import de.danoeh.antennapod.core.util.DateFormatter;
|
||||
|
||||
/**
|
||||
* Creates RSS 2.0 feeds. See FeedGenerator for more information.
|
||||
@ -98,7 +100,7 @@ public class Rss2Generator implements FeedGenerator {
|
||||
}
|
||||
if (item.getPubDate() != null) {
|
||||
xml.startTag(null, "pubDate");
|
||||
xml.text(DateFormatter.formatRfc822Date(item.getPubDate()));
|
||||
xml.text(formatRfc822Date(item.getPubDate()));
|
||||
xml.endTag(null, "pubDate");
|
||||
}
|
||||
if ((flags & FEATURE_WRITE_GUID) != 0) {
|
||||
@ -132,4 +134,9 @@ public class Rss2Generator implements FeedGenerator {
|
||||
|
||||
xml.endDocument();
|
||||
}
|
||||
|
||||
private static String formatRfc822Date(Date date) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US);
|
||||
return format.format(date);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
@ -35,6 +35,7 @@ dependencies {
|
||||
implementation project(':playback:base')
|
||||
implementation project(':playback:cast')
|
||||
implementation project(':storage:database')
|
||||
implementation project(':storage:importexport')
|
||||
implementation project(':storage:preferences')
|
||||
implementation project(':ui:app-start-intent')
|
||||
implementation project(':ui:common')
|
||||
|
@ -10,6 +10,9 @@ import android.util.Log;
|
||||
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
|
||||
import de.danoeh.antennapod.storage.importexport.OpmlElement;
|
||||
import de.danoeh.antennapod.storage.importexport.OpmlReader;
|
||||
import de.danoeh.antennapod.storage.importexport.OpmlWriter;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@ -31,9 +34,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import de.danoeh.antennapod.core.export.opml.OpmlElement;
|
||||
import de.danoeh.antennapod.core.export.opml.OpmlReader;
|
||||
import de.danoeh.antennapod.core.export.opml.OpmlWriter;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
|
||||
@ -81,7 +81,7 @@ public class OpmlBackupAgent extends BackupAgentHelper {
|
||||
|
||||
try {
|
||||
// Write OPML
|
||||
new OpmlWriter().writeDocument(DBReader.getFeedList(), writer, mContext);
|
||||
OpmlWriter.writeDocument(DBReader.getFeedList(), writer);
|
||||
|
||||
// Compare checksum of new and old file to see if we need to perform a backup at all
|
||||
if (digester != null) {
|
||||
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package de.danoeh.antennapod.core.export;
|
||||
|
||||
public class CommonSymbols {
|
||||
|
||||
public static final String HEAD = "head";
|
||||
public static final String BODY = "body";
|
||||
public static final String TITLE = "title";
|
||||
|
||||
public static final String XML_FEATURE_INDENT_OUTPUT = "http://xmlpull.org/v1/doc/features.html#indent-output";
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package de.danoeh.antennapod.core.export;
|
||||
|
||||
import android.content.Context;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
|
||||
public interface ExportWriter {
|
||||
|
||||
void writeDocument(List<Feed> feeds, Writer writer, Context context)
|
||||
throws IllegalArgumentException, IllegalStateException, IOException;
|
||||
|
||||
String fileExtension();
|
||||
|
||||
}
|
@ -3,11 +3,9 @@ package de.danoeh.antennapod.core.util;
|
||||
import android.content.Context;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Formats dates.
|
||||
@ -17,11 +15,6 @@ public class DateFormatter {
|
||||
|
||||
}
|
||||
|
||||
public static String formatRfc822Date(Date date) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US);
|
||||
return format.format(date);
|
||||
}
|
||||
|
||||
public static String formatAbbrev(final Context context, final Date date) {
|
||||
if (date == null) {
|
||||
return "";
|
||||
|
@ -34,6 +34,7 @@ include ':playback:base'
|
||||
include ':playback:cast'
|
||||
|
||||
include ':storage:database'
|
||||
include ':storage:importexport'
|
||||
include ':storage:preferences'
|
||||
|
||||
include ':ui:app-start-intent'
|
||||
|
3
storage/importexport/README.md
Normal file
3
storage/importexport/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# :storage:importexport
|
||||
|
||||
Import/Export of the AntennaPod database.
|
21
storage/importexport/build.gradle
Normal file
21
storage/importexport/build.gradle
Normal file
@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
}
|
||||
apply from: "../../common.gradle"
|
||||
|
||||
android {
|
||||
namespace "de.danoeh.antennapod.storage.importexport"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':storage:database')
|
||||
implementation project(':storage:preferences')
|
||||
implementation project(':ui:i18n')
|
||||
implementation project(':model')
|
||||
|
||||
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
|
||||
implementation "commons-io:commons-io:$commonsioVersion"
|
||||
implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
|
||||
implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
background-image: linear-gradient(180deg, #0f9cff, #0682ff);
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
min-height: 100%;
|
||||
}
|
||||
h1 {
|
||||
color: #fff;
|
@ -1,4 +1,4 @@
|
||||
package de.danoeh.antennapod.core.storage;
|
||||
package de.danoeh.antennapod.storage.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
@ -7,7 +7,6 @@ import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.core.R;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
@ -1,9 +1,8 @@
|
||||
package de.danoeh.antennapod.core.export.favorites;
|
||||
package de.danoeh.antennapod.storage.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -14,21 +13,17 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import de.danoeh.antennapod.core.export.ExportWriter;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.model.feed.SortOrder;
|
||||
|
||||
/** Writes saved favorites to file. */
|
||||
public class FavoritesWriter implements ExportWriter {
|
||||
public class FavoritesWriter {
|
||||
private static final String TAG = "FavoritesWriter";
|
||||
private static final String FAVORITE_TEMPLATE = "html-export-favorites-item-template.html";
|
||||
private static final String FEED_TEMPLATE = "html-export-feed-template.html";
|
||||
private static final String UTF_8 = "UTF-8";
|
||||
|
||||
@Override
|
||||
public void writeDocument(List<Feed> feeds, Writer writer, Context context)
|
||||
public static void writeDocument(List<FeedItem> allFavorites, Writer writer, Context context)
|
||||
throws IllegalArgumentException, IllegalStateException, IOException {
|
||||
Log.d(TAG, "Starting to write document");
|
||||
|
||||
@ -43,8 +38,6 @@ public class FavoritesWriter implements ExportWriter {
|
||||
InputStream feedTemplateStream = context.getAssets().open(FEED_TEMPLATE);
|
||||
String feedTemplate = IOUtils.toString(feedTemplateStream, UTF_8);
|
||||
|
||||
List<FeedItem> allFavorites = DBReader.getEpisodes(0, Integer.MAX_VALUE,
|
||||
new FeedItemFilter(FeedItemFilter.IS_FAVORITE), SortOrder.DATE_NEW_OLD);
|
||||
Map<Long, List<FeedItem>> favoriteByFeed = getFeedMap(allFavorites);
|
||||
|
||||
writer.append(templateParts[0]);
|
||||
@ -72,7 +65,7 @@ public class FavoritesWriter implements ExportWriter {
|
||||
* @param favoritesList {@code List} of all favorite episodes.
|
||||
* @return A {@code Map} favorite episodes, keyed by feed ID.
|
||||
*/
|
||||
private Map<Long, List<FeedItem>> getFeedMap(List<FeedItem> favoritesList) {
|
||||
private static Map<Long, List<FeedItem>> getFeedMap(List<FeedItem> favoritesList) {
|
||||
Map<Long, List<FeedItem>> feedMap = new TreeMap<>();
|
||||
|
||||
for (FeedItem item : favoritesList) {
|
||||
@ -89,7 +82,7 @@ public class FavoritesWriter implements ExportWriter {
|
||||
return feedMap;
|
||||
}
|
||||
|
||||
private void writeFeed(Writer writer, Feed feed, String feedTemplate) throws IOException {
|
||||
private static void writeFeed(Writer writer, Feed feed, String feedTemplate) throws IOException {
|
||||
String feedInfo = feedTemplate
|
||||
.replace("{FEED_IMG}", feed.getImageUrl())
|
||||
.replace("{FEED_TITLE}", feed.getTitle())
|
||||
@ -99,7 +92,7 @@ public class FavoritesWriter implements ExportWriter {
|
||||
writer.append(feedInfo);
|
||||
}
|
||||
|
||||
private void writeFavoriteItem(Writer writer, FeedItem item, String favoriteTemplate) throws IOException {
|
||||
private static void writeFavoriteItem(Writer writer, FeedItem item, String favoriteTemplate) throws IOException {
|
||||
String favItem = favoriteTemplate.replace("{FAV_TITLE}", item.getTitle().trim());
|
||||
if (item.getLink() != null) {
|
||||
favItem = favItem.replace("{FAV_WEBSITE}", item.getLink());
|
||||
@ -114,9 +107,4 @@ public class FavoritesWriter implements ExportWriter {
|
||||
|
||||
writer.append(favItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fileExtension() {
|
||||
return "html";
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
package de.danoeh.antennapod.core.export.html;
|
||||
package de.danoeh.antennapod.storage.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.core.export.ExportWriter;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -11,15 +10,14 @@ import java.util.List;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
/** Writes HTML documents. */
|
||||
public class HtmlWriter implements ExportWriter {
|
||||
public class HtmlWriter {
|
||||
private static final String TAG = "HtmlWriter";
|
||||
|
||||
/**
|
||||
* Takes a list of feeds and a writer and writes those into an HTML
|
||||
* document.
|
||||
*/
|
||||
@Override
|
||||
public void writeDocument(List<Feed> feeds, Writer writer, Context context)
|
||||
public static void writeDocument(List<Feed> feeds, Writer writer, Context context)
|
||||
throws IllegalArgumentException, IllegalStateException, IOException {
|
||||
Log.d(TAG, "Starting to write document");
|
||||
|
||||
@ -43,9 +41,4 @@ public class HtmlWriter implements ExportWriter {
|
||||
writer.append(templateParts[1]);
|
||||
Log.d(TAG, "Finished writing document");
|
||||
}
|
||||
|
||||
public String fileExtension() {
|
||||
return "html";
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package de.danoeh.antennapod.core.export.opml;
|
||||
package de.danoeh.antennapod.storage.importexport;
|
||||
|
||||
/**
|
||||
* Represents a single feed in an OPML file.
|
@ -1,7 +1,9 @@
|
||||
package de.danoeh.antennapod.core.export.opml;
|
||||
package de.danoeh.antennapod.storage.importexport;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import de.danoeh.antennapod.storage.preferences.BuildConfig;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
@ -10,8 +12,6 @@ import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.danoeh.antennapod.core.BuildConfig;
|
||||
|
||||
/**
|
||||
* Reads OPML documents.
|
||||
*/
|
||||
@ -53,7 +53,7 @@ public class OpmlReader {
|
||||
OpmlElement element = new OpmlElement();
|
||||
|
||||
final String title = xpp.getAttributeValue(null, OpmlSymbols.TITLE);
|
||||
if (title != null) {
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
Log.i(TAG, "Using title: " + title);
|
||||
element.setText(title);
|
||||
} else {
|
||||
@ -63,8 +63,8 @@ public class OpmlReader {
|
||||
element.setXmlUrl(xpp.getAttributeValue(null, OpmlSymbols.XMLURL));
|
||||
element.setHtmlUrl(xpp.getAttributeValue(null, OpmlSymbols.HTMLURL));
|
||||
element.setType(xpp.getAttributeValue(null, OpmlSymbols.TYPE));
|
||||
if (element.getXmlUrl() != null) {
|
||||
if (element.getText() == null) {
|
||||
if (!TextUtils.isEmpty(element.getXmlUrl())) {
|
||||
if (TextUtils.isEmpty(element.getText())) {
|
||||
Log.i(TAG, "Opml element has no text attribute.");
|
||||
element.setText(element.getXmlUrl());
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
package de.danoeh.antennapod.core.export.opml;
|
||||
|
||||
import de.danoeh.antennapod.core.export.CommonSymbols;
|
||||
package de.danoeh.antennapod.storage.importexport;
|
||||
|
||||
/**
|
||||
* Contains symbols for reading and writing OPML documents.
|
||||
*/
|
||||
final class OpmlSymbols extends CommonSymbols {
|
||||
final class OpmlSymbols {
|
||||
public static final String XML_FEATURE_INDENT_OUTPUT = "http://xmlpull.org/v1/doc/features.html#indent-output";
|
||||
|
||||
public static final String HEAD = "head";
|
||||
public static final String BODY = "body";
|
||||
public static final String TITLE = "title";
|
||||
public static final String OPML = "opml";
|
||||
static final String OUTLINE = "outline";
|
||||
static final String TEXT = "text";
|
||||
@ -19,5 +21,4 @@ final class OpmlSymbols extends CommonSymbols {
|
||||
private OpmlSymbols() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +1,21 @@
|
||||
package de.danoeh.antennapod.core.export.opml;
|
||||
package de.danoeh.antennapod.storage.importexport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
import de.danoeh.antennapod.core.util.DateFormatter;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.danoeh.antennapod.core.export.ExportWriter;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
|
||||
/** Writes OPML documents. */
|
||||
public class OpmlWriter implements ExportWriter {
|
||||
public class OpmlWriter {
|
||||
|
||||
private static final String TAG = "OpmlWriter";
|
||||
private static final String ENCODING = "UTF-8";
|
||||
@ -27,8 +26,7 @@ public class OpmlWriter implements ExportWriter {
|
||||
* Takes a list of feeds and a writer and writes those into an OPML
|
||||
* document.
|
||||
*/
|
||||
@Override
|
||||
public void writeDocument(List<Feed> feeds, Writer writer, Context context)
|
||||
public static void writeDocument(List<Feed> feeds, Writer writer)
|
||||
throws IllegalArgumentException, IllegalStateException, IOException {
|
||||
Log.d(TAG, "Starting to write document");
|
||||
XmlSerializer xs = Xml.newSerializer();
|
||||
@ -44,7 +42,7 @@ public class OpmlWriter implements ExportWriter {
|
||||
xs.text(OPML_TITLE);
|
||||
xs.endTag(null, OpmlSymbols.TITLE);
|
||||
xs.startTag(null, OpmlSymbols.DATE_CREATED);
|
||||
xs.text(DateFormatter.formatRfc822Date(new Date()));
|
||||
xs.text(formatRfc822Date(new Date()));
|
||||
xs.endTag(null, OpmlSymbols.DATE_CREATED);
|
||||
xs.endTag(null, OpmlSymbols.HEAD);
|
||||
|
||||
@ -68,8 +66,8 @@ public class OpmlWriter implements ExportWriter {
|
||||
Log.d(TAG, "Finished writing document");
|
||||
}
|
||||
|
||||
public String fileExtension() {
|
||||
return "opml";
|
||||
private static String formatRfc822Date(Date date) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US);
|
||||
return format.format(date);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user