Merge branch 'develop' into newgui
Conflicts: src/de/danoeh/antennapod/activity/MainActivity.java
This commit is contained in:
commit
2e22685c79
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
if (item.hasItemImageDownloaded()) {
|
||||
return item.getImage().reopenImageInputStream(input);
|
||||
} else {
|
||||
return item.getFeed().getImage().reopenImageInputStream(input);
|
||||
}
|
||||
} else {
|
||||
return new Playable.DefaultPlayableImageLoader(this)
|
||||
.reopenImageInputStream(input);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue