Merge branch 'backup' of git://github.com/liesen/AntennaPod into liesen-backup

Conflicts:
	src/de/danoeh/antennapod/activity/MainActivity.java

Moved calls to BackupManager from PodDBAdapter to DBWriter
This commit is contained in:
daniel oeh 2014-04-06 19:35:52 +02:00
commit dc06e81d95
7 changed files with 426 additions and 189 deletions

View File

@ -35,8 +35,13 @@
android:name="de.danoeh.antennapod.PodcastApp" android:name="de.danoeh.antennapod.PodcastApp"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:backupAgent=".backup.OpmlBackupAgent"
android:restoreAnyVersion="true"
android:logo="@drawable/ic_launcher" android:logo="@drawable/ic_launcher"
android:theme="@style/Theme.AntennaPod.Light"> android:theme="@style/Theme.AntennaPod.Light">
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIRUmY27PIgwf_FtdHiT1_QLRM-7VLPnnFQ2Y_zw" />
<activity <activity
android:name=".activity.MainActivity" android:name=".activity.MainActivity"
android:configChanges="keyboardHidden|orientation" android:configChanges="keyboardHidden|orientation"

View File

@ -354,6 +354,9 @@
<string name="new_episodes_count_label">Number of new episodes</string> <string name="new_episodes_count_label">Number of new episodes</string>
<string name="in_progress_episodes_count_label">Number of episodes you have started listening to</string> <string name="in_progress_episodes_count_label">Number of episodes you have started listening to</string>
<!-- OPML backup -->
<string name="backup_restored">"Restored feed subscriptions from backup"</string>
<!-- AntennaPodSP --> <!-- AntennaPodSP -->
<string name="sp_apps_importing_feeds_msg">Importing subscriptions from single-purpose apps&#8230;</string> <string name="sp_apps_importing_feeds_msg">Importing subscriptions from single-purpose apps&#8230;</string>

View File

@ -35,7 +35,9 @@ import de.danoeh.antennapod.util.StorageUtils;
import java.util.ArrayList; import java.util.ArrayList;
/** The activity that is shown when the user launches the app. */ /**
* The activity that is shown when the user launches the app.
*/
public class MainActivity extends ActionBarActivity { public class MainActivity extends ActionBarActivity {
private static final String TAG = "MainActivity"; private static final String TAG = "MainActivity";
@ -112,7 +114,6 @@ public class MainActivity extends ActionBarActivity {
StorageUtils.checkStorageAvailability(this); StorageUtils.checkStorageAvailability(this);
updateProgressBarVisibility(); updateProgressBarVisibility();
EventDistributor.getInstance().register(contentUpdate); EventDistributor.getInstance().register(contentUpdate);
} }
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {

View File

@ -0,0 +1,212 @@
package de.danoeh.antennapod.backup;
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.BuildConfig;
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.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.Date;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.opml.OpmlElement;
import de.danoeh.antennapod.opml.OpmlReader;
import de.danoeh.antennapod.opml.OpmlWriter;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.LangUtils;
public class OpmlBackupAgent extends BackupAgentHelper {
private static final String OPML_BACKUP_KEY = "opml";
@Override
public void onCreate() {
addHelper(OPML_BACKUP_KEY, new OpmlBackupHelper(this));
}
private static final void LOGD(String tag, String msg) {
if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, msg);
}
}
private static final void LOGD(String tag, String msg, Throwable tr) {
if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) {
Log.d(tag, msg, tr);
}
}
/** 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),
LangUtils.UTF_8);
} catch (NoSuchAlgorithmException e) {
writer = new OutputStreamWriter(byteStream, LangUtils.UTF_8);
}
try {
// Write OPML
new OpmlWriter().writeDocument(DBReader.getFeedList(mContext), 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();
LOGD(TAG, "New checksum: " + new BigInteger(1, newChecksum).toString(16));
// Get the old checksum
if (oldState != null) {
FileInputStream inState = new FileInputStream(oldState.getFileDescriptor());
int len = inState.read();
if (len != -1) {
byte[] oldChecksum = new byte[len];
inState.read(oldChecksum);
LOGD(TAG, "Old checksum: " + new BigInteger(1, oldChecksum).toString(16));
if (Arrays.equals(oldChecksum, newChecksum)) {
LOGD(TAG, "Checksums are the same; won't backup");
return;
}
}
}
writeNewStateDescription(newState, newChecksum);
}
LOGD(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 {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
}
}
}
}
@Override
public void restoreEntity(BackupDataInputStream data) {
LOGD(TAG, "Backup restore");
if (!OPML_ENTITY_KEY.equals(data.getKey())) {
LOGD(TAG, "Unknown entity key: " + data.getKey());
return;
}
MessageDigest digester = null;
Reader reader;
try {
digester = MessageDigest.getInstance("MD5");
reader = new InputStreamReader(new DigestInputStream(data, digester),
LangUtils.UTF_8);
} catch (NoSuchAlgorithmException e) {
reader = new InputStreamReader(data, LangUtils.UTF_8);
}
try {
ArrayList<OpmlElement> opmlElements = new OpmlReader().readDocument(reader);
mChecksum = digester == null ? null : digester.digest();
DownloadRequester downloader = DownloadRequester.getInstance();
Date lastUpdated = new Date();
for (OpmlElement opmlElem : opmlElements) {
Feed feed = new Feed(opmlElem.getXmlUrl(), lastUpdated, opmlElem.getText());
try {
downloader.downloadFeed(mContext, feed);
} catch (DownloadRequestException e) {
LOGD(TAG, "Error while restoring/downloading feed", e);
}
}
} 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 {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
}
@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);
}
}
}
}

View File

@ -74,6 +74,7 @@ public class UserPreferences implements
private String playbackSpeed; private String playbackSpeed;
private String[] playbackSpeedArray; private String[] playbackSpeedArray;
private boolean pauseForFocusLoss; private boolean pauseForFocusLoss;
private boolean isFreshInstall;
private UserPreferences(Context context) { private UserPreferences(Context context) {
this.context = context; this.context = context;
@ -282,6 +283,11 @@ public class UserPreferences implements
return instance.pauseForFocusLoss; return instance.pauseForFocusLoss;
} }
public static boolean isFreshInstall() {
instanceAvailable();
return instance.isFreshInstall;
}
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sp, String key) { public void onSharedPreferenceChanged(SharedPreferences sp, String key) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.storage; package de.danoeh.antennapod.storage;
import android.app.backup.BackupManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -197,6 +198,9 @@ public class DBWriter {
GpodnetPreferences.addRemovedFeed(feed.getDownload_url()); GpodnetPreferences.addRemovedFeed(feed.getDownload_url());
EventDistributor.getInstance().sendFeedUpdateBroadcast(); EventDistributor.getInstance().sendFeedUpdateBroadcast();
BackupManager backupManager = new BackupManager(context);
backupManager.dataChanged();
} }
} }
}); });
@ -695,6 +699,9 @@ public class DBWriter {
GpodnetPreferences.addAddedFeed(feed.getDownload_url()); GpodnetPreferences.addAddedFeed(feed.getDownload_url());
EventDistributor.getInstance().sendFeedUpdateBroadcast(); EventDistributor.getInstance().sendFeedUpdateBroadcast();
BackupManager backupManager = new BackupManager(context);
backupManager.dataChanged();
} }
}); });
} }

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.storage; package de.danoeh.antennapod.storage;
import android.app.backup.BackupManager;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -386,6 +387,7 @@ public class PodDBAdapter {
Log.d(this.toString(), "Updating existing Feed in db"); Log.d(this.toString(), "Updating existing Feed in db");
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())}); new String[]{String.valueOf(feed.getId())});
} }
return feed.getId(); return feed.getId();
} }
@ -844,6 +846,7 @@ public class PodDBAdapter {
removeFeedItem(item); removeFeedItem(item);
} }
} }
db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())}); new String[]{String.valueOf(feed.getId())});
db.setTransactionSuccessful(); db.setTransactionSuccessful();