Move import/export to its own module (#6986)

Also clean up ImportExportPreferencesFragment a bit.
This commit is contained in:
ByteHamster 2024-03-11 23:10:09 +01:00 committed by GitHub
parent 5c98a33ed2
commit 2f3f1fd186
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 184 additions and 334 deletions

View File

@ -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')

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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();
}
});
}
}

View File

@ -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();
}
});
}
}

View File

@ -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 {

View File

@ -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')

View File

@ -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) {

View File

@ -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";
}

View File

@ -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();
}

View File

@ -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 "";

View File

@ -34,6 +34,7 @@ include ':playback:base'
include ':playback:cast'
include ':storage:database'
include ':storage:importexport'
include ':storage:preferences'
include ':ui:app-start-intent'

View File

@ -0,0 +1,3 @@
# :storage:importexport
Import/Export of the AntennaPod database.

View 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'
}

View File

@ -16,6 +16,7 @@
background-image: linear-gradient(180deg, #0f9cff, #0682ff);
text-align: center;
padding: 10px;
min-height: 100%;
}
h1 {
color: #fff;

View File

@ -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;

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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.

View 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());
}

View File

@ -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() {
}
}

View File

@ -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);
}
}