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">
+
Автор
Язык
Настройки
- Изображение
+ Обложка
Ошибка
Произошла ошибка:
Обновить
@@ -36,7 +36,7 @@
Разделы
Заметки к эпизоду
Описание
- Следующий эпизод:\u0020
+ Последний выпуск:\u0020
\u0020выпуск(ов)
Опубликовано:\u0020
Продолжительность:\u0020
@@ -71,7 +71,7 @@
Добавить в очередь
Удалить из очереди
Посетить сайт
- Поддержать посредством Flattr
+ Поддержать через Flattr
Добавить все в очередь
Загрузить все
Пропустить выпуск
@@ -89,6 +89,7 @@
Неподдерживаемый тип канала
Ошибка соединения
Неизвестный хост
+ Ошибка авторизации
Отменить все загрузки
Загрузка отменена
Загрузки завершены
@@ -104,6 +105,8 @@
Медиа файл
Изображение
Ошибка при загрузки файла:\u0020
+ Необходима авторизация
+ Для доступа к ресурсу необходимо ввести имя пользователя и пароль
Ошибка!
Ничего не воспроизводится
@@ -141,7 +144,17 @@
Доступ отозван
Вы успешно отключили AntennaPod от вашего аккаунта в Flattr. Чтобы завершить этот процесс вам нужно удалить AntennaPod из списка приложений подключенных к вашему аккаунту на сайте Flattr.
+ Один поддержан через Flattr!
+ Поддержано через Flattr: %d.
Поддержано через Flattr: %s.
+ Не удалось поддержать через Flattr: %d!
+ Не поддержано через Flattr: %s.
+ Будет поддержано через Flattr потом
+ %s поддерживается через Flattr
+ AntennaPod поддерживает через Flattr
+ Вы поддержали AntennaPod через Flattr
+ Ошибка
+ Получение списка поддержаного через Flattr
Загрузить плагин
Плагин не установлен
@@ -216,7 +229,7 @@
Найдено в заголовке
OPML файлы позволяют перемещать ваши подкасты из одного менеджера подкастов в другой.
- Для импорта файла OPML его нужно поместить каталог указанный ниже и нажать кнопку чтобы начать процесс импорта.
+ Для импорта файла OPML его нужно поместить в указанный каталог и нажать кнопку внизу для запуска импорта.
Начать импорт
Импорт OPML
ОШИБКА!
@@ -312,4 +325,5 @@
Количество новых эпизодов
Количество начатых эпизодов
+ Импорт подписок из одноцелевых приложений…
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 @@
Number of episodes you have started listening to
Drag to change the position of this item
+
+ "Restored feed subscriptions from backup"
+
Importing subscriptions from single-purpose apps…
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 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();