diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java index 808cb5268..013d4db50 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java @@ -146,7 +146,7 @@ public class PlaybackServiceTaskManagerTest { FeedItem item = DBReader.getFeedItem(testItem.getId()); item.getMedia().setDownloaded(true); item.getMedia().setFile_url("file://123"); - item.setAutoDownload(false); + item.disableAutoDownload(); DBWriter.setFeedMedia(item.getMedia()).get(); DBWriter.setFeedItem(item).get(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java index dedf8e5e6..a2b0e98c3 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java @@ -34,7 +34,7 @@ public class CancelDownloadActionButton extends ItemActionButton { FeedMedia media = item.getMedia(); DownloadRequester.getInstance().cancelDownload(context, media); if (UserPreferences.isEnableAutodownload()) { - item.setAutoDownload(false); + item.disableAutoDownload(); DBWriter.setFeedItem(item); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index ddbf6c078..5602dcb78 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -113,7 +113,7 @@ public class DownloadLogFragment extends ListFragment { if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { FeedMedia media = DBReader.getFeedMedia(downloadRequest.getFeedfileId()); FeedItem feedItem = media.getItem(); - feedItem.setAutoDownload(false); + feedItem.disableAutoDownload(); DBWriter.setFeedItem(feedItem); } } else if (item instanceof DownloadStatus) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java index 82583b7b5..5d685c24f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java @@ -178,7 +178,7 @@ public class LocalFeedUpdater { private static FeedItem createFeedItem(Feed feed, DocumentFile file, Context context) { FeedItem item = new FeedItem(0, file.getName(), UUID.randomUUID().toString(), file.getName(), new Date(file.lastModified()), FeedItem.UNPLAYED, feed); - item.setAutoDownload(false); + item.disableAutoDownload(); long size = file.length(); FeedMedia media = new FeedMedia(0, item, 0, 0, size, file.getType(), diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java index f26421cdf..2da0c2db4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java @@ -321,18 +321,8 @@ public class DownloadService extends Service { if (item == null) { return; } - boolean unknownHost = status.getReason() == DownloadError.ERROR_UNKNOWN_HOST; - boolean unsupportedType = status.getReason() == DownloadError.ERROR_UNSUPPORTED_TYPE; - boolean wrongSize = status.getReason() == DownloadError.ERROR_IO_WRONG_SIZE; - - if (! (unknownHost || unsupportedType || wrongSize)) { - try { - DBWriter.saveFeedItemAutoDownloadFailed(item).get(); - } catch (ExecutionException | InterruptedException e) { - Log.d(TAG, "Ignoring exception while setting item download status"); - e.printStackTrace(); - } - } + item.increaseFailedAutoDownloadAttempts(System.currentTimeMillis()); + DBWriter.setFeedItem(item); // to make lists reload the failed item, we fake an item update EventBus.getDefault().post(FeedItemEvent.updated(item)); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java index 84d66918a..541e17cf6 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java @@ -83,7 +83,7 @@ public class MediaDownloadedHandler implements Runnable { // we've received the media, we don't want to autodownload it again if (item != null) { - item.setAutoDownload(false); + item.disableAutoDownload(); // setFeedItem() signals (via EventBus) that the item has been updated, // so we do it after the enclosing media has been updated above, // to ensure subscribers will get the updated FeedMedia as well diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java index 0dc57e0af..cf32eb838 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java @@ -65,7 +65,7 @@ public class AutomaticDownloadAlgorithm { Iterator it = candidates.iterator(); while (it.hasNext()) { FeedItem item = it.next(); - if (!item.isAutoDownloadable() || FeedItemUtil.isPlaying(item.getMedia()) + if (!item.isAutoDownloadable(System.currentTimeMillis()) || FeedItemUtil.isPlaying(item.getMedia()) || item.getFeed().isLocalFeed()) { it.remove(); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java index b3eec8c1d..4e0a6aeda 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java @@ -73,7 +73,7 @@ class DBUpgrader { } if (oldVersion <= 9) { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED + " INTEGER DEFAULT 1"); } if (oldVersion <= 10) { @@ -121,10 +121,10 @@ class DBUpgrader { } if (oldVersion <= 14) { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " INTEGER"); + + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS + " INTEGER"); db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " = " - + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD + + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS + " = " + + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + ")"); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java index 51dafc575..0e996c6c8 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java @@ -950,25 +950,6 @@ public class DBWriter { }); } - public static Future saveFeedItemAutoDownloadFailed(final FeedItem feedItem) { - return dbExec.submit(() -> { - int failedAttempts = feedItem.getFailedAutoDownloadAttempts() + 1; - long autoDownload; - if (!feedItem.getAutoDownload() || failedAttempts >= 10) { - autoDownload = 0; // giving up, disable auto download - feedItem.setAutoDownload(false); - } else { - long now = System.currentTimeMillis(); - autoDownload = (now / 10) * 10 + failedAttempts; - } - final PodDBAdapter adapter = PodDBAdapter.getInstance(); - adapter.open(); - adapter.setFeedItemAutoDownload(feedItem, autoDownload); - adapter.close(); - EventBus.getDefault().post(new UnreadItemsUpdateEvent()); - }); - } - /** * Set filter of the feed * diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index f1364255d..719e546b5 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -97,7 +97,8 @@ public class PodDBAdapter { public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; public static final String KEY_CHAPTER_TYPE = "type"; public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date"; - public static final String KEY_AUTO_DOWNLOAD = "auto_download"; + public static final String KEY_AUTO_DOWNLOAD_ATTEMPTS = "auto_download"; + public static final String KEY_AUTO_DOWNLOAD_ENABLED = "auto_download"; // Both tables use the same key public static final String KEY_KEEP_UPDATED = "keep_updated"; public static final String KEY_AUTO_DELETE_ACTION = "auto_delete_action"; public static final String KEY_FEED_VOLUME_ADAPTION = "feed_volume_adaption"; @@ -141,7 +142,7 @@ public class PodDBAdapter { + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR + " TEXT," + KEY_IMAGE_URL + " TEXT," + KEY_TYPE + " TEXT," - + KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1," + + KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD_ENABLED + " INTEGER DEFAULT 1," + KEY_USERNAME + " TEXT," + KEY_PASSWORD + " TEXT," + KEY_INCLUDE_FILTER + " TEXT DEFAULT ''," @@ -169,7 +170,7 @@ public class PodDBAdapter { + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT," + KEY_IMAGE_URL + " TEXT," - + KEY_AUTO_DOWNLOAD + " INTEGER)"; + + KEY_AUTO_DOWNLOAD_ATTEMPTS + " INTEGER)"; private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE " + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION @@ -246,7 +247,7 @@ public class PodDBAdapter { TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL, TABLE_NAME_FEEDS + "." + KEY_TYPE, TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER, - TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD, + TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD_ENABLED, TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED, TABLE_NAME_FEEDS + "." + KEY_IS_PAGED, TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK, @@ -295,7 +296,7 @@ public class PodDBAdapter { + TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL + ", " - + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD; + + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD_ATTEMPTS; private static final String KEYS_FEED_MEDIA = TABLE_NAME_FEED_MEDIA + "." + KEY_ID + " AS " + SELECT_KEY_MEDIA_ID + ", " @@ -445,7 +446,7 @@ public class PodDBAdapter { throw new IllegalArgumentException("Feed ID of preference must not be null"); } ContentValues values = new ContentValues(); - values.put(KEY_AUTO_DOWNLOAD, prefs.getAutoDownload()); + values.put(KEY_AUTO_DOWNLOAD_ENABLED, prefs.getAutoDownload()); values.put(KEY_KEEP_UPDATED, prefs.getKeepUpdated()); values.put(KEY_AUTO_DELETE_ACTION, prefs.getAutoDeleteAction().ordinal()); values.put(KEY_FEED_VOLUME_ADAPTION, prefs.getVolumeAdaptionSetting().toInteger()); @@ -649,7 +650,7 @@ public class PodDBAdapter { } values.put(KEY_HAS_CHAPTERS, item.getChapters() != null || item.hasChapters()); values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); - values.put(KEY_AUTO_DOWNLOAD, item.getAutoDownload()); + values.put(KEY_AUTO_DOWNLOAD_ATTEMPTS, item.getAutoDownloadAttemptsAndTime()); values.put(KEY_IMAGE_URL, item.getImageUrl()); if (item.getId() == 0) { @@ -765,13 +766,6 @@ public class PodDBAdapter { return status.getId(); } - public void setFeedItemAutoDownload(FeedItem feedItem, long autoDownload) { - ContentValues values = new ContentValues(); - values.put(KEY_AUTO_DOWNLOAD, autoDownload); - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", - new String[]{String.valueOf(feedItem.getId())}); - } - public void setFavorites(List favorites) { ContentValues values = new ContentValues(); try { diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemCursorMapper.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemCursorMapper.java index 19695ca95..ca0834339 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemCursorMapper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemCursorMapper.java @@ -25,7 +25,7 @@ public abstract class FeedItemCursorMapper { int indexHasChapters = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_HAS_CHAPTERS); int indexRead = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_READ); int indexItemIdentifier = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_ITEM_IDENTIFIER); - int indexAutoDownload = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_AUTO_DOWNLOAD); + int indexAutoDownload = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS); int indexImageUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_IMAGE_URL); long id = cursor.getInt(indexId); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java index cd46bcf94..f062609b6 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java @@ -21,7 +21,7 @@ public abstract class FeedPreferencesCursorMapper { @NonNull public static FeedPreferences convert(@NonNull Cursor cursor) { int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID); - int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD); + int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED); int indexAutoRefresh = cursor.getColumnIndex(PodDBAdapter.KEY_KEEP_UPDATED); int indexAutoDeleteAction = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DELETE_ACTION); int indexVolumeAdaption = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_VOLUME_ADAPTION); diff --git a/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java index c4860d818..a08d0897d 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.core.feed; import de.danoeh.antennapod.model.feed.FeedItem; +import de.danoeh.antennapod.model.feed.FeedMedia; import org.junit.Before; import org.junit.Test; @@ -10,11 +11,13 @@ import java.util.Date; import static de.danoeh.antennapod.core.feed.FeedItemMother.anyFeedItemWithImage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class FeedItemTest { private static final String TEXT_LONG = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; private static final String TEXT_SHORT = "Lorem ipsum"; + private static final long ONE_HOUR = 1000L * 3600L; private FeedItem original; private FeedItem changedFeedItem; @@ -136,4 +139,36 @@ public class FeedItemTest { item.setDescriptionIfLonger(contentEncoded); assertEquals(TEXT_LONG, item.getDescription()); } -} \ No newline at end of file + + @Test + public void testAutoDownloadBackoff() { + FeedItem item = new FeedItem(); + item.setMedia(new FeedMedia(item, "https://example.com/file.mp3", 0, "audio/mpeg")); + + long now = ONE_HOUR; // In reality, this is System.currentTimeMillis() + assertTrue(item.isAutoDownloadable(now)); + item.increaseFailedAutoDownloadAttempts(now); + assertFalse(item.isAutoDownloadable(now)); + + now += ONE_HOUR; + assertTrue(item.isAutoDownloadable(now)); + item.increaseFailedAutoDownloadAttempts(now); + assertFalse(item.isAutoDownloadable(now)); + + now += ONE_HOUR; + assertFalse(item.isAutoDownloadable(now)); // Should backoff, so more than 1 hour needed + + now += ONE_HOUR; + assertTrue(item.isAutoDownloadable(now)); // Now it's enough + item.increaseFailedAutoDownloadAttempts(now); + item.increaseFailedAutoDownloadAttempts(now); + item.increaseFailedAutoDownloadAttempts(now); + + now += 1000L * ONE_HOUR; + assertFalse(item.isAutoDownloadable(now)); // Should have given up + item.increaseFailedAutoDownloadAttempts(now); + + now += 1000L * ONE_HOUR; + assertFalse(item.isAutoDownloadable(now)); // Still given up + } +} diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java index 460f50f88..08f79252a 100644 --- a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java +++ b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java @@ -64,12 +64,6 @@ public class FeedItem extends FeedComponent implements Serializable { private transient List chapters; private String imageUrl; - /* - * 0: auto download disabled - * 1: auto download enabled (default) - * > 1: auto download enabled, (approx.) timestamp of the last failed attempt - * where last digit denotes the number of failed attempts - */ private long autoDownload = 1; /** @@ -361,15 +355,18 @@ public class FeedItem extends FeedComponent implements Serializable { return hasChapters; } - public void setAutoDownload(boolean autoDownload) { - this.autoDownload = autoDownload ? 1 : 0; + public void disableAutoDownload() { + this.autoDownload = 0; } - public boolean getAutoDownload() { - return this.autoDownload > 0; + public long getAutoDownloadAttemptsAndTime() { + return autoDownload; } public int getFailedAutoDownloadAttempts() { + // 0: auto download disabled + // 1: auto download enabled (default) + // > 1: auto download enabled, timestamp of last failed attempt, last digit denotes number of failed attempts if (autoDownload <= 1) { return 0; } @@ -380,23 +377,33 @@ public class FeedItem extends FeedComponent implements Serializable { return failedAttempts; } - public boolean isDownloaded() { - return media != null && media.isDownloaded(); + public void increaseFailedAutoDownloadAttempts(long now) { + if (autoDownload == 0) { + return; // Don't re-enable + } + int failedAttempts = getFailedAutoDownloadAttempts() + 1; + if (failedAttempts >= 5) { + disableAutoDownload(); // giving up + } else { + autoDownload = (now / 10) * 10 + failedAttempts; + } } - public boolean isAutoDownloadable() { + public boolean isAutoDownloadable(long now) { if (media == null || media.isDownloaded() || autoDownload == 0) { return false; } if (autoDownload == 1) { - return true; + return true; // Never failed } int failedAttempts = getFailedAutoDownloadAttempts(); - double magicValue = 1.767; // 1.767^(10[=#maxNumAttempts]-1) = 168 hours / 7 days - int millisecondsInHour = 3600000; - long waitingTime = (long) (Math.pow(magicValue, failedAttempts - 1) * millisecondsInHour); - long grace = TimeUnit.MINUTES.toMillis(5); - return System.currentTimeMillis() > (autoDownload + waitingTime - grace); + long waitingTime = TimeUnit.HOURS.toMillis((long) Math.pow(2, failedAttempts - 1)); + long lastAttempt = (autoDownload / 10) * 10; + return now >= (lastAttempt + waitingTime); + } + + public boolean isDownloaded() { + return media != null && media.isDownloaded(); } /**