diff options
author | daniel oeh <daniel.oeh@gmail.com> | 2014-04-26 23:58:00 +0200 |
---|---|---|
committer | daniel oeh <daniel.oeh@gmail.com> | 2014-04-26 23:58:00 +0200 |
commit | 2e22685c79500567db453358b94a3c5a7afcfa38 (patch) | |
tree | 57ad8657ec0ee714d9c8de81d224465ad257b3af | |
parent | 68494fd022529778a11991a12c249f27b6e9d0c9 (diff) | |
parent | df55792d27b2753a04c2f545cbfc52a2f72fa9b1 (diff) | |
download | AntennaPod-2e22685c79500567db453358b94a3c5a7afcfa38.zip |
Merge branch 'develop' into newgui
Conflicts:
src/de/danoeh/antennapod/activity/MainActivity.java
-rw-r--r-- | .tx/config | 2 | ||||
-rw-r--r-- | AndroidManifest.xml | 5 | ||||
-rw-r--r-- | build.gradle | 6 | ||||
-rw-r--r-- | res/values-iw-rIL/strings.xml (renamed from res/values-he-rIL/strings.xml) | 0 | ||||
-rw-r--r-- | res/values-ru/strings.xml | 22 | ||||
-rw-r--r-- | res/values/strings.xml | 3 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/backup/OpmlBackupAgent.java | 212 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/feed/FeedItem.java | 13 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/feed/FeedMedia.java | 12 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/preferences/UserPreferences.java | 6 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/DBWriter.java | 7 | ||||
-rw-r--r-- | src/de/danoeh/antennapod/storage/PodDBAdapter.java | 3 |
12 files changed, 276 insertions, 15 deletions
diff --git a/.tx/config b/.tx/config index 1bf937efc..e45dcd70c 100644 --- a/.tx/config +++ b/.tx/config @@ -12,7 +12,7 @@ trans.de = res/values-de/strings.xml trans.es = res/values-es/strings.xml trans.es_ES = res/values-es-rES/strings.xml trans.fr = res/values-fr/strings.xml -trans.he_IL = res/values-he-rIL/strings.xml +trans.he_IL = res/values-iw-rIL/strings.xml trans.hi_IN = res/values-hi-rIN/strings.xml trans.it_IT = res/values-it-rIT/strings.xml trans.ko = res/values-ko/strings.xml diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5e7ba5b84..cce892747 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -35,8 +35,13 @@ android:name="de.danoeh.antennapod.PodcastApp" android:icon="@drawable/ic_launcher" android:label="@string/app_name" + android:backupAgent=".backup.OpmlBackupAgent" + android:restoreAnyVersion="true" android:logo="@drawable/ic_launcher" android:theme="@style/Theme.AntennaPod.Light"> + <meta-data + android:name="com.google.android.backup.api_key" + android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA" /> <activity android:name=".activity.MainActivity" android:configChanges="keyboardHidden|orientation" diff --git a/build.gradle b/build.gradle index 8c12c60fd..64727cfc0 100644 --- a/build.gradle +++ b/build.gradle @@ -17,9 +17,9 @@ dependencies { println "Creating libs directory" libsdir.mkdir() } - - compile 'com.android.support:appcompat-v7:19.0.+' - compile 'org.apache.commons:commons-lang3:3.2.1' + compile 'com.android.support:support-v4:19.1.+' + compile 'com.android.support:appcompat-v7:19.1.+' + compile 'org.apache.commons:commons-lang3:3.3.2' compile ('org.shredzone.flattr4j:flattr4j-core:2.8') { exclude group: 'org.apache.httpcomponents', module: 'httpcore' exclude group: 'org.apache.httpcomponents', module: 'httpclient' diff --git a/res/values-he-rIL/strings.xml b/res/values-iw-rIL/strings.xml index af9f546ea..af9f546ea 100644 --- a/res/values-he-rIL/strings.xml +++ b/res/values-iw-rIL/strings.xml diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 351dd8a1d..d9d091869 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -28,7 +28,7 @@ <string name="author_label">Автор</string> <string name="language_label">Язык</string> <string name="podcast_settings_label">Настройки</string> - <string name="cover_label">Изображение</string> + <string name="cover_label">Обложка</string> <string name="error_label">Ошибка</string> <string name="error_msg_prefix">Произошла ошибка:</string> <string name="refresh_label">Обновить</string> @@ -36,7 +36,7 @@ <string name="chapters_label">Разделы</string> <string name="shownotes_label">Заметки к эпизоду</string> <string name="description_label">Описание</string> - <string name="most_recent_prefix">Следующий эпизод:\u0020</string> + <string name="most_recent_prefix">Последний выпуск:\u0020</string> <string name="episodes_suffix">\u0020выпуск(ов)</string> <string name="published_prefix">Опубликовано:\u0020</string> <string name="length_prefix">Продолжительность:\u0020</string> @@ -71,7 +71,7 @@ <string name="add_to_queue_label">Добавить в очередь</string> <string name="remove_from_queue_label">Удалить из очереди</string> <string name="visit_website_label">Посетить сайт</string> - <string name="support_label">Поддержать посредством Flattr</string> + <string name="support_label">Поддержать через Flattr</string> <string name="enqueue_all_new">Добавить все в очередь</string> <string name="download_all">Загрузить все</string> <string name="skip_episode_label">Пропустить выпуск</string> @@ -89,6 +89,7 @@ <string name="download_error_unsupported_type">Неподдерживаемый тип канала</string> <string name="download_error_connection_error">Ошибка соединения</string> <string name="download_error_unknown_host">Неизвестный хост</string> + <string name="download_error_unauthorized">Ошибка авторизации</string> <string name="cancel_all_downloads_label">Отменить все загрузки</string> <string name="download_cancelled_msg">Загрузка отменена</string> <string name="download_report_title">Загрузки завершены</string> @@ -104,6 +105,8 @@ <string name="download_type_media">Медиа файл</string> <string name="download_type_image">Изображение</string> <string name="download_request_error_dialog_message_prefix">Ошибка при загрузки файла:\u0020</string> + <string name="authentication_notification_title">Необходима авторизация</string> + <string name="authentication_notification_msg">Для доступа к ресурсу необходимо ввести имя пользователя и пароль</string> <!--Mediaplayer messages--> <string name="player_error_msg">Ошибка!</string> <string name="player_stopped_msg">Ничего не воспроизводится</string> @@ -141,7 +144,17 @@ <string name="access_revoked_title">Доступ отозван</string> <string name="access_revoked_info">Вы успешно отключили AntennaPod от вашего аккаунта в Flattr. Чтобы завершить этот процесс вам нужно удалить AntennaPod из списка приложений подключенных к вашему аккаунту на сайте Flattr.</string> <!--Flattr--> + <string name="flattr_click_success">Один поддержан через Flattr!</string> + <string name="flattr_click_success_count">Поддержано через Flattr: %d.</string> <string name="flattr_click_success_queue">Поддержано через Flattr: %s.</string> + <string name="flattr_click_failure_count">Не удалось поддержать через Flattr: %d!</string> + <string name="flattr_click_failure">Не поддержано через Flattr: %s.</string> + <string name="flattr_click_enqueued">Будет поддержано через Flattr потом</string> + <string name="flattring_thing">%s поддерживается через Flattr</string> + <string name="flattring_label">AntennaPod поддерживает через Flattr</string> + <string name="flattrd_label">Вы поддержали AntennaPod через Flattr</string> + <string name="flattrd_failed_label">Ошибка</string> + <string name="flattr_retrieving_status">Получение списка поддержаного через Flattr</string> <!--Variable Speed--> <string name="download_plugin_label">Загрузить плагин</string> <string name="no_playback_plugin_title">Плагин не установлен</string> @@ -216,7 +229,7 @@ <string name="found_in_title_label">Найдено в заголовке</string> <!--OPML import and export--> <string name="opml_import_txtv_button_lable">OPML файлы позволяют перемещать ваши подкасты из одного менеджера подкастов в другой.</string> - <string name="opml_import_explanation">Для импорта файла OPML его нужно поместить каталог указанный ниже и нажать кнопку чтобы начать процесс импорта.</string> + <string name="opml_import_explanation">Для импорта файла OPML его нужно поместить в указанный каталог и нажать кнопку внизу для запуска импорта.</string> <string name="start_import_label">Начать импорт</string> <string name="opml_import_label">Импорт OPML</string> <string name="opml_directory_error">ОШИБКА!</string> @@ -312,4 +325,5 @@ <string name="new_episodes_count_label">Количество новых эпизодов</string> <string name="in_progress_episodes_count_label">Количество начатых эпизодов</string> <!--AntennaPodSP--> + <string name="sp_apps_importing_feeds_msg">Импорт подписок из одноцелевых приложений…</string> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 76928bde8..cf3b02148 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -371,6 +371,9 @@ <string name="in_progress_episodes_count_label">Number of episodes you have started listening to</string> <string name="drag_handle_content_description">Drag to change the position of this item</string> + <!-- OPML backup --> + <string name="backup_restored">"Restored feed subscriptions from backup"</string> + <!-- AntennaPodSP --> <string name="sp_apps_importing_feeds_msg">Importing subscriptions from single-purpose apps…</string> diff --git a/src/de/danoeh/antennapod/backup/OpmlBackupAgent.java b/src/de/danoeh/antennapod/backup/OpmlBackupAgent.java new file mode 100644 index 000000000..56d1ca092 --- /dev/null +++ b/src/de/danoeh/antennapod/backup/OpmlBackupAgent.java @@ -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); + } + } + } +} diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java index 921a03bff..956131ab2 100644 --- a/src/de/danoeh/antennapod/feed/FeedItem.java +++ b/src/de/danoeh/antennapod/feed/FeedItem.java @@ -280,7 +280,7 @@ public class FeedItem extends FeedComponent implements @Override public InputStream openImageInputStream() { InputStream out = null; - if (hasItemImage()) { + if (hasItemImageDownloaded()) { out = image.openImageInputStream(); } else if (hasMedia()) { out = media.openImageInputStream(); @@ -293,7 +293,7 @@ public class FeedItem extends FeedComponent implements @Override public InputStream reopenImageInputStream(InputStream input) { InputStream out = null; - if (hasItemImage()) { + if (hasItemImageDownloaded()) { out = image.reopenImageInputStream(input); } else if (hasMedia()) { out = media.reopenImageInputStream(input); @@ -306,7 +306,7 @@ public class FeedItem extends FeedComponent implements @Override public String getImageLoaderCacheKey() { String out = null; - if (hasItemImage()) { + if (hasItemImageDownloaded()) { out = image.getImageLoaderCacheKey(); } else if (hasMedia()) { out = media.getImageLoaderCacheKey(); @@ -346,6 +346,13 @@ public class FeedItem extends FeedComponent implements return image != null; } + /** + * Returns true if this FeedItem has its own image and the image has been downloaded. + */ + public boolean hasItemImageDownloaded() { + return image != null && image.isDownloaded(); + } + @Override public String getHumanReadableIdentifier() { return title; diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java index f38e92398..1f8e7f8f8 100644 --- a/src/de/danoeh/antennapod/feed/FeedMedia.java +++ b/src/de/danoeh/antennapod/feed/FeedMedia.java @@ -204,7 +204,7 @@ public class FeedMedia extends FeedFile implements Playable { public FeedImage getImage() { if (item != null) { - return (item.hasItemImage()) ? item.getImage() : item.getFeed().getImage(); + return (item.hasItemImageDownloaded()) ? item.getImage() : item.getFeed().getImage(); } return null; } @@ -384,7 +384,7 @@ public class FeedMedia extends FeedFile implements Playable { @Override public InputStream openImageInputStream() { InputStream out; - if (item.hasItemImage()) { + if (item.hasItemImageDownloaded()) { out = item.openImageInputStream(); } else { out = new Playable.DefaultPlayableImageLoader(this) @@ -401,7 +401,7 @@ public class FeedMedia extends FeedFile implements Playable { @Override public String getImageLoaderCacheKey() { String out; - if (item.hasItemImage()) { + if (item.hasItemImageDownloaded()) { out = item.getImageLoaderCacheKey(); } else { out = new Playable.DefaultPlayableImageLoader(this) @@ -418,7 +418,11 @@ public class FeedMedia extends FeedFile implements Playable { @Override public InputStream reopenImageInputStream(InputStream input) { if (input instanceof FileInputStream) { - return item.getImage().reopenImageInputStream(input); + if (item.hasItemImageDownloaded()) { + return item.getImage().reopenImageInputStream(input); + } else { + return item.getFeed().getImage().reopenImageInputStream(input); + } } else { return new Playable.DefaultPlayableImageLoader(this) .reopenImageInputStream(input); diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java index 3662b646e..31250bcd9 100644 --- a/src/de/danoeh/antennapod/preferences/UserPreferences.java +++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java @@ -74,6 +74,7 @@ public class UserPreferences implements private String playbackSpeed; private String[] playbackSpeedArray; private boolean pauseForFocusLoss; + private boolean isFreshInstall; private UserPreferences(Context context) { this.context = context; @@ -282,6 +283,11 @@ public class UserPreferences implements return instance.pauseForFocusLoss; } + public static boolean isFreshInstall() { + instanceAvailable(); + return instance.isFreshInstall; + } + @Override public void onSharedPreferenceChanged(SharedPreferences sp, String key) { if (BuildConfig.DEBUG) diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index c1ce9da36..f2586cdcb 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.storage; +import android.app.backup.BackupManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -197,6 +198,9 @@ public class DBWriter { GpodnetPreferences.addRemovedFeed(feed.getDownload_url()); EventDistributor.getInstance().sendFeedUpdateBroadcast(); + + BackupManager backupManager = new BackupManager(context); + backupManager.dataChanged(); } } }); @@ -695,6 +699,9 @@ public class DBWriter { GpodnetPreferences.addAddedFeed(feed.getDownload_url()); EventDistributor.getInstance().sendFeedUpdateBroadcast(); + + BackupManager backupManager = new BackupManager(context); + backupManager.dataChanged(); } }); } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index f5ee7a83f..40a71a75d 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.storage; +import android.app.backup.BackupManager; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -386,6 +387,7 @@ public class PodDBAdapter { Log.d(this.toString(), "Updating existing Feed in db"); db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); + } return feed.getId(); } @@ -844,6 +846,7 @@ public class PodDBAdapter { removeFeedItem(item); } } + db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); db.setTransactionSuccessful(); |