Fix auto-download retry backoff
The new value never got stored in the database. Also, it only got increased by certain types of errors - all other errors could be retried indefinitely. Also added a unit test.
This commit is contained in:
parent
345aad4148
commit
524e5c95fc
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -65,7 +65,7 @@ public class AutomaticDownloadAlgorithm {
|
|||
Iterator<FeedItem> 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();
|
||||
}
|
||||
|
|
|
@ -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 + ")");
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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<FeedItem> favorites) {
|
||||
ContentValues values = new ContentValues();
|
||||
try {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,12 +64,6 @@ public class FeedItem extends FeedComponent implements Serializable {
|
|||
private transient List<Chapter> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue