summaryrefslogtreecommitdiff
path: root/storage/importexport/src
diff options
context:
space:
mode:
Diffstat (limited to 'storage/importexport/src')
-rw-r--r--storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/OpmlBackupAgent.java185
1 files changed, 185 insertions, 0 deletions
diff --git a/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/OpmlBackupAgent.java b/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/OpmlBackupAgent.java
new file mode 100644
index 000000000..4c9256fdc
--- /dev/null
+++ b/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/OpmlBackupAgent.java
@@ -0,0 +1,185 @@
+package de.danoeh.antennapod.storage.importexport;
+
+import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupHelper;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
+import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
+import org.apache.commons.io.IOUtils;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.security.DigestInputStream;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+import de.danoeh.antennapod.model.feed.Feed;
+import de.danoeh.antennapod.storage.database.DBReader;
+
+public class OpmlBackupAgent extends BackupAgentHelper {
+ private static final String OPML_BACKUP_KEY = "opml";
+
+ @Override
+ public void onCreate() {
+ addHelper(OPML_BACKUP_KEY, new OpmlBackupHelper(this));
+ }
+
+ /**
+ * Class for backing up and restoring the OPML file.
+ */
+ private static class OpmlBackupHelper implements BackupHelper {
+ private static final String TAG = "OpmlBackupHelper";
+
+ private static final String OPML_ENTITY_KEY = "antennapod-feeds.opml";
+
+ private final Context mContext;
+
+ /**
+ * Checksum of restored OPML file
+ */
+ private byte[] mChecksum;
+
+ public OpmlBackupHelper(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) {
+ Log.d(TAG, "Performing backup");
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ MessageDigest digester = null;
+ Writer writer;
+
+ try {
+ digester = MessageDigest.getInstance("MD5");
+ writer = new OutputStreamWriter(new DigestOutputStream(byteStream, digester),
+ Charset.forName("UTF-8"));
+ } catch (NoSuchAlgorithmException e) {
+ writer = new OutputStreamWriter(byteStream, Charset.forName("UTF-8"));
+ }
+
+ try {
+ // Write OPML
+ 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) {
+ byte[] newChecksum = digester.digest();
+ Log.d(TAG, "New checksum: " + new BigInteger(1, newChecksum).toString(16));
+
+ // Get the old checksum
+ if (oldState != null) {
+ try (final FileInputStream inState = new FileInputStream(oldState.getFileDescriptor())) {
+ int len = inState.read();
+
+ if (len != -1) {
+ byte[] oldChecksum = new byte[len];
+ IOUtils.read(inState, oldChecksum, 0, len);
+ Log.d(TAG, "Old checksum: " + new BigInteger(1, oldChecksum).toString(16));
+
+ if (Arrays.equals(oldChecksum, newChecksum)) {
+ Log.d(TAG, "Checksums are the same; won't backup");
+ return;
+ }
+ }
+ }
+ }
+
+ writeNewStateDescription(newState, newChecksum);
+ }
+
+ Log.d(TAG, "Backing up OPML");
+ byte[] bytes = byteStream.toByteArray();
+ data.writeEntityHeader(OPML_ENTITY_KEY, bytes.length);
+ data.writeEntityData(bytes, bytes.length);
+ } catch (IOException e) {
+ Log.e(TAG, "Error during backup", e);
+ } finally {
+ IOUtils.closeQuietly(writer);
+ }
+ }
+
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ Log.d(TAG, "Backup restore");
+
+ if (!OPML_ENTITY_KEY.equals(data.getKey())) {
+ Log.d(TAG, "Unknown entity key: " + data.getKey());
+ return;
+ }
+
+ MessageDigest digester = null;
+ Reader reader;
+
+ try {
+ digester = MessageDigest.getInstance("MD5");
+ reader = new InputStreamReader(new DigestInputStream(data, digester),
+ Charset.forName("UTF-8"));
+ } catch (NoSuchAlgorithmException e) {
+ reader = new InputStreamReader(data, Charset.forName("UTF-8"));
+ }
+
+ try {
+ ArrayList<OpmlElement> opmlElements = new OpmlReader().readDocument(reader);
+ mChecksum = digester == null ? null : digester.digest();
+ for (OpmlElement opmlElem : opmlElements) {
+ Feed feed = new Feed(opmlElem.getXmlUrl(), null, opmlElem.getText());
+ feed.setItems(Collections.emptyList());
+ FeedDatabaseWriter.updateFeed(mContext, feed, false);
+ }
+ FeedUpdateManager.getInstance().runOnce(mContext);
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "Error while parsing the OPML file", e);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to restore OPML backup", e);
+ } finally {
+ IOUtils.closeQuietly(reader);
+ }
+ }
+
+ @Override
+ public void writeNewStateDescription(ParcelFileDescriptor newState) {
+ writeNewStateDescription(newState, mChecksum);
+ }
+
+ /**
+ * Writes the new state description, which is the checksum of the OPML file.
+ *
+ * @param newState
+ * @param checksum
+ */
+ private void writeNewStateDescription(ParcelFileDescriptor newState, byte[] checksum) {
+ if (checksum == null) {
+ return;
+ }
+
+ try {
+ FileOutputStream outState = new FileOutputStream(newState.getFileDescriptor());
+ outState.write(checksum.length);
+ outState.write(checksum);
+ outState.flush();
+ outState.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write new state description", e);
+ }
+ }
+ }
+}