summaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorByteHamster <ByteHamster@users.noreply.github.com>2024-03-29 17:45:14 +0100
committerGitHub <noreply@github.com>2024-03-29 17:45:14 +0100
commit6f3a9b16764a57e43994ccbeeada5224dee93f44 (patch)
tree24bd2a3ed120dd61b009e5a2b5b9a1c550579997 /core/src
parent0c8c9a89a371d6515c34d1c4f7417c26059ee969 (diff)
downloadAntennaPod-6f3a9b16764a57e43994ccbeeada5224dee93f44.zip
Create module for sync service and move DBWriter to database module (#7040)
Diffstat (limited to 'core/src')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ClientConfigurator.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java17
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManagerImpl.java (renamed from core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManager.java)7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java1065
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithm.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/FeedDatabaseWriter.java262
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesser.java83
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java93
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/EpisodeActionFilter.java77
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/GuidValidator.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/LockingAsyncExecutor.java35
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java386
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/SynchronizationProviderViewData.java47
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/queue/SynchronizationQueueSink.java82
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/queue/SynchronizationQueueStorage.java158
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java27
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManagerImpl.java (renamed from core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java)21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/download/MediaSizeLoader.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlayableUtils.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java2
-rw-r--r--core/src/main/res/drawable-nodpi/gpodder_icon.pngbin32098 -> 0 bytes
-rw-r--r--core/src/main/res/drawable-nodpi/nextcloud_logo.pngbin3432 -> 0 bytes
-rw-r--r--core/src/main/res/values/ids.xml2
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java2
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java11
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java5
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java4
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java1
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java2
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java1
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java8
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java1
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java1
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/sync/EpisodeActionFilterTest.java212
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/sync/GuidValidatorTest.java19
46 files changed, 83 insertions, 2625 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ClientConfigurator.java b/core/src/main/java/de/danoeh/antennapod/core/ClientConfigurator.java
index 48d937266..8b5f9f286 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/ClientConfigurator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/ClientConfigurator.java
@@ -3,6 +3,12 @@ package de.danoeh.antennapod.core;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import de.danoeh.antennapod.core.storage.AutoDownloadManagerImpl;
+import de.danoeh.antennapod.core.util.download.FeedUpdateManagerImpl;
+import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
+import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
+import de.danoeh.antennapod.net.sync.service.SyncService;
+import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
import de.danoeh.antennapod.storage.preferences.SynchronizationCredentials;
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
@@ -13,8 +19,6 @@ import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.core.service.download.DownloadServiceInterfaceImpl;
-import de.danoeh.antennapod.core.sync.SyncService;
-import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
import de.danoeh.antennapod.net.common.NetworkUtils;
import de.danoeh.antennapod.core.util.download.NetworkConnectionChangeHandler;
import de.danoeh.antennapod.net.ssl.SslProviderInstaller;
@@ -46,6 +50,8 @@ public class ClientConfigurator {
NetworkUtils.init(context);
NetworkConnectionChangeHandler.init(context);
DownloadServiceInterface.setImpl(new DownloadServiceInterfaceImpl());
+ FeedUpdateManager.setInstance(new FeedUpdateManagerImpl());
+ AutoDownloadManager.setInstance(new AutoDownloadManagerImpl());
SynchronizationQueueSink.setServiceStarterImpl(() -> SyncService.sync(context));
AntennapodHttpClient.setCacheDirectory(new File(context.getCacheDir(), "okhttp"));
AntennapodHttpClient.setProxyConfig(UserPreferences.getProxyConfig());
diff --git a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
index 358b75e9c..b30f657a1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
@@ -8,8 +8,8 @@ import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import de.danoeh.antennapod.core.storage.FeedDatabaseWriter;
-import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
+import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
+import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
import de.danoeh.antennapod.storage.importexport.OpmlElement;
import de.danoeh.antennapod.storage.importexport.OpmlReader;
import de.danoeh.antennapod.storage.importexport.OpmlWriter;
@@ -148,7 +148,7 @@ public class OpmlBackupAgent extends BackupAgentHelper {
feed.setItems(Collections.emptyList());
FeedDatabaseWriter.updateFeed(mContext, feed, false);
}
- FeedUpdateManager.runOnce(mContext);
+ FeedUpdateManager.getInstance().runOnce(mContext);
} catch (XmlPullParserException e) {
Log.e(TAG, "Error while parsing the OPML file", e);
} catch (IOException e) {
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 a4dab1996..8230924f9 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
@@ -31,8 +31,8 @@ import de.danoeh.antennapod.core.util.FastDocumentFile;
import de.danoeh.antennapod.model.MediaMetadataRetrieverCompat;
import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.core.storage.FeedDatabaseWriter;
-import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.parser.feed.util.DateUtils;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.feed.Feed;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
index e30b49280..098c9bfa4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
@@ -6,7 +6,7 @@ import android.content.Intent;
import android.util.Log;
import de.danoeh.antennapod.core.ClientConfigurator;
-import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
+import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
/**
* Refreshes all feeds when it receives an intent
@@ -20,7 +20,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
Log.d(TAG, "Received intent");
ClientConfigurator.initialize(context);
- FeedUpdateManager.runOnce(context);
+ FeedUpdateManager.getInstance().runOnce(context);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java
index 45b6aee04..e5828ac6e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java
@@ -24,12 +24,12 @@ import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.service.download.NewEpisodesNotification;
import de.danoeh.antennapod.core.service.download.handler.FeedParserTask;
-import de.danoeh.antennapod.core.storage.AutoDownloadManager;
+import de.danoeh.antennapod.core.util.download.FeedUpdateManagerImpl;
+import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.core.storage.FeedDatabaseWriter;
-import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.net.common.NetworkUtils;
-import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.model.feed.Feed;
@@ -62,7 +62,7 @@ public class FeedUpdateWorker extends Worker {
newEpisodesNotification.loadCountersBeforeRefresh();
List<Feed> toUpdate;
- long feedId = getInputData().getLong(FeedUpdateManager.EXTRA_FEED_ID, -1);
+ long feedId = getInputData().getLong(FeedUpdateManagerImpl.EXTRA_FEED_ID, -1);
boolean allAreLocal = true;
boolean force = false;
if (feedId == -1) { // Update all
@@ -91,7 +91,7 @@ public class FeedUpdateWorker extends Worker {
force = true;
}
- if (!getInputData().getBoolean(FeedUpdateManager.EXTRA_EVEN_ON_MOBILE, false) && !allAreLocal) {
+ if (!getInputData().getBoolean(FeedUpdateManagerImpl.EXTRA_EVEN_ON_MOBILE, false) && !allAreLocal) {
if (!NetworkUtils.networkAvailable() || !NetworkUtils.isFeedRefreshAllowed()) {
Log.d(TAG, "Blocking automatic update");
return Result.retry();
@@ -100,7 +100,7 @@ public class FeedUpdateWorker extends Worker {
refreshFeeds(toUpdate, force);
notificationManager.cancel(R.id.notification_updating_feeds);
- AutoDownloadManager.autodownloadUndownloadedItems(getApplicationContext());
+ AutoDownloadManager.getInstance().autodownloadUndownloadedItems(getApplicationContext());
return Result.success();
}
@@ -164,7 +164,7 @@ public class FeedUpdateWorker extends Worker {
}
void refreshFeed(Feed feed, boolean force) throws Exception {
- boolean nextPage = getInputData().getBoolean(FeedUpdateManager.EXTRA_NEXT_PAGE, false)
+ boolean nextPage = getInputData().getBoolean(FeedUpdateManagerImpl.EXTRA_NEXT_PAGE, false)
&& feed.getNextPageLink() != null;
if (nextPage) {
feed.setPageNr(feed.getPageNr() + 1);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java
index 6c595388a..e2489b493 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java
@@ -9,7 +9,7 @@ import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
-import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java
index 9c073713f..a2b4ed100 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/EpisodeDownloadWorker.java
@@ -23,7 +23,7 @@ import de.danoeh.antennapod.core.ClientConfigurator;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.event.MessageEvent;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.download.DownloadResult;
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 62ce395f7..24b157c88 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
@@ -7,6 +7,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.model.MediaMetadataRetrieverCompat;
+import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
@@ -17,8 +18,7 @@ import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.model.download.DownloadRequest;
import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.feed.FeedItem;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index 6f7068d24..4cd4931e1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -51,6 +51,7 @@ import androidx.core.content.ContextCompat;
import androidx.media.MediaBrowserServiceCompat;
import de.danoeh.antennapod.event.PlayerStatusEvent;
+import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
import de.danoeh.antennapod.ui.notifications.NotificationUtils;
import de.danoeh.antennapod.ui.widget.WidgetUpdater;
import org.greenrobot.eventbus.EventBus;
@@ -71,10 +72,8 @@ import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.service.QuickSettingsTileService;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceTaskManager.SleepTimer;
import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.core.util.ChapterUtils;
-import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.FeedUtil;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.net.common.NetworkUtils;
@@ -1110,8 +1109,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
FeedMedia media = (FeedMedia) playable;
FeedItem item = media.getItem();
- boolean smartMarkAsPlayed = FeedItemUtil.hasAlmostEnded(media);
- if (!ended && smartMarkAsPlayed) {
+ int smartMarkAsPlayedSecs = UserPreferences.getSmartMarkAsPlayedSecs();
+ boolean almostEnded = media.getDuration() > 0
+ && media.getPosition() >= media.getDuration() - smartMarkAsPlayedSecs * 1000;
+ if (!ended && almostEnded) {
Log.d(TAG, "smart mark as played");
}
@@ -1121,7 +1122,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
autoSkipped = true;
}
- if (ended || smartMarkAsPlayed) {
+ if (ended || almostEnded) {
SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(
getApplicationContext(), media, true);
media.onPlaybackCompleted(getApplicationContext());
@@ -1132,11 +1133,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
if (item != null) {
- if (ended || smartMarkAsPlayed
+ if (ended || almostEnded
|| autoSkipped
|| (skipped && !UserPreferences.shouldSkipKeepEpisode())) {
// only mark the item as played if we're not keeping it anyways
- DBWriter.markItemPlayed(item, FeedItem.PLAYED, ended || (skipped && smartMarkAsPlayed));
+ DBWriter.markItemPlayed(item, FeedItem.PLAYED, ended || (skipped && almostEnded));
// don't know if it actually matters to not autodownload when smart mark as played is triggered
DBWriter.removeQueueItem(PlaybackService.this, ended, item);
// Delete episode if enabled
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
index 49170c40f..d1de1f616 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
@@ -18,6 +18,7 @@ import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.storage.database.DBReader;
+import de.danoeh.antennapod.storage.database.DBWriter;
/**
* Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPod.
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
index cd24ca03d..74ee43cf0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
@@ -15,6 +15,7 @@ import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.storage.database.DBReader;
+import de.danoeh.antennapod.storage.database.DBWriter;
/**
* A cleanup algorithm that removes any item that isn't in the queue and isn't a favorite
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManager.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManagerImpl.java
index bc5244381..b00375ffe 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutoDownloadManagerImpl.java
@@ -2,12 +2,13 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.util.Log;
+import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-public abstract class AutoDownloadManager {
+public class AutoDownloadManagerImpl extends AutoDownloadManager {
private static final String TAG = "AutoDownloadManager";
/**
@@ -35,7 +36,7 @@ public abstract class AutoDownloadManager {
* @param context Used for accessing the DB.
* @return A Future that can be used for waiting for the methods completion.
*/
- public static Future<?> autodownloadUndownloadedItems(final Context context) {
+ public Future<?> autodownloadUndownloadedItems(final Context context) {
Log.d(TAG, "autodownloadUndownloadedItems");
return autodownloadExec.submit(downloadAlgorithm.autoDownloadUndownloadedItems(context));
}
@@ -48,7 +49,7 @@ public abstract class AutoDownloadManager {
*
* @param context Used for accessing the DB.
*/
- public static void performAutoCleanup(final Context context) {
+ public void performAutoCleanup(final Context context) {
EpisodeCleanupAlgorithmFactory.build().performCleanup(context);
}
}
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
deleted file mode 100644
index 1e5b416e2..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
+++ /dev/null
@@ -1,1065 +0,0 @@
-package de.danoeh.antennapod.core.storage;
-
-import android.app.backup.BackupManager;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.app.NotificationManagerCompat;
-import androidx.documentfile.provider.DocumentFile;
-
-import com.google.common.util.concurrent.Futures;
-
-import de.danoeh.antennapod.event.DownloadLogEvent;
-import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
-import de.danoeh.antennapod.core.service.playback.PlaybackServiceInterface;
-import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.storage.database.PodDBAdapter;
-
-import org.greenrobot.eventbus.EventBus;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.event.FavoritesEvent;
-import de.danoeh.antennapod.event.FeedItemEvent;
-import de.danoeh.antennapod.event.FeedListUpdateEvent;
-import de.danoeh.antennapod.event.MessageEvent;
-import de.danoeh.antennapod.event.playback.PlaybackHistoryEvent;
-import de.danoeh.antennapod.event.QueueEvent;
-import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
-import de.danoeh.antennapod.event.FeedEvent;
-import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import de.danoeh.antennapod.model.download.DownloadResult;
-import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
-import de.danoeh.antennapod.storage.database.FeedItemPermutors;
-import de.danoeh.antennapod.core.util.IntentUtils;
-import de.danoeh.antennapod.storage.database.LongList;
-import de.danoeh.antennapod.storage.database.Permutor;
-import de.danoeh.antennapod.model.feed.Feed;
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.model.feed.FeedMedia;
-import de.danoeh.antennapod.model.feed.FeedPreferences;
-import de.danoeh.antennapod.model.feed.SortOrder;
-import de.danoeh.antennapod.model.playback.Playable;
-import de.danoeh.antennapod.net.sync.model.EpisodeAction;
-
-/**
- * Provides methods for writing data to AntennaPod's database.
- * In general, DBWriter-methods will be executed on an internal ExecutorService.
- * Some methods return a Future-object which the caller can use for waiting for the method's completion. The returned Future's
- * will NOT contain any results.
- */
-public class DBWriter {
-
- private static final String TAG = "DBWriter";
-
- private static final ExecutorService dbExec;
-
- static {
- dbExec = Executors.newSingleThreadExecutor(r -> {
- Thread t = new Thread(r);
- t.setName("DatabaseExecutor");
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- });
- }
-
- private DBWriter() {
- }
-
- /**
- * Wait until all threads are finished to avoid the "Illegal connection pointer" error of
- * Robolectric. Call this method only for unit tests.
- */
- public static void tearDownTests() {
- try {
- dbExec.awaitTermination(1, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- // ignore error
- }
- }
-
- /**
- * Deletes a downloaded FeedMedia file from the storage device.
- *
- * @param context A context that is used for opening a database connection.
- */
- public static Future<?> deleteFeedMediaOfItem(@NonNull final Context context,
- final FeedMedia media) {
- return runOnDbThread(() -> {
- if (media == null) {
- return;
- }
- boolean result = deleteFeedMediaSynchronous(context, media);
- if (result && UserPreferences.shouldDeleteRemoveFromQueue()) {
- DBWriter.removeQueueItemSynchronous(context, false, media.getItemId());
- }
- });
- }
-
- private static boolean deleteFeedMediaSynchronous(@NonNull Context context, @NonNull FeedMedia media) {
- Log.i(TAG, String.format(Locale.US, "Requested to delete FeedMedia [id=%d, title=%s, downloaded=%s",
- media.getId(), media.getEpisodeTitle(), media.isDownloaded()));
- boolean localDelete = false;
- if (media.getLocalFileUrl() != null && media.getLocalFileUrl().startsWith("content://")) {
- // Local feed
- DocumentFile documentFile = DocumentFile.fromSingleUri(context, Uri.parse(media.getLocalFileUrl()));
- if (documentFile == null || !documentFile.exists() || !documentFile.delete()) {
- EventBus.getDefault().post(new MessageEvent(context.getString(R.string.delete_local_failed)));
- return false;
- }
- media.setLocalFileUrl(null);
- localDelete = true;
- } else if (media.getLocalFileUrl() != null) {
- // delete downloaded media file
- File mediaFile = new File(media.getLocalFileUrl());
- if (mediaFile.exists() && !mediaFile.delete()) {
- MessageEvent evt = new MessageEvent(context.getString(R.string.delete_failed));
- EventBus.getDefault().post(evt);
- return false;
- }
- media.setDownloaded(false);
- media.setLocalFileUrl(null);
- media.setHasEmbeddedPicture(false);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setMedia(media);
- adapter.close();
- }
-
- if (media.getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId()) {
- PlaybackPreferences.writeNoMediaPlaying();
- IntentUtils.sendLocalBroadcast(context, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
-
- NotificationManagerCompat nm = NotificationManagerCompat.from(context);
- nm.cancel(R.id.notification_playing);
- }
-
- if (localDelete) {
- // Do full update of this feed to get rid of the item
- LocalFeedUpdater.updateFeed(media.getItem().getFeed(), context.getApplicationContext(), null);
- } else {
- // Gpodder: queue delete action for synchronization
- FeedItem item = media.getItem();
- EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE)
- .currentTimestamp()
- .build();
- SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
-
- EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
- }
- return true;
- }
-
- /**
- * Deletes a Feed and all downloaded files of its components like images and downloaded episodes.
- *
- * @param context A context that is used for opening a database connection.
- * @param feedId ID of the Feed that should be deleted.
- */
- public static Future<?> deleteFeed(final Context context, final long feedId) {
- return runOnDbThread(() -> {
- final Feed feed = DBReader.getFeed(feedId, false);
- if (feed == null) {
- return;
- }
-
- deleteFeedItemsSynchronous(context, feed.getItems());
-
- // delete feed
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.removeFeed(feed);
- adapter.close();
-
- if (!feed.isLocalFeed()) {
- SynchronizationQueueSink.enqueueFeedRemovedIfSynchronizationIsActive(context, feed.getDownloadUrl());
- }
- EventBus.getDefault().post(new FeedListUpdateEvent(feed));
- });
- }
-
- /**
- * Remove the listed items and their FeedMedia entries.
- * Deleting media also removes the download log entries.
- */
- @NonNull
- public static Future<?> deleteFeedItems(@NonNull Context context, @NonNull List<FeedItem> items) {
- return runOnDbThread(() -> deleteFeedItemsSynchronous(context, items));
- }
-
- /**
- * Remove the listed items and their FeedMedia entries.
- * Deleting media also removes the download log entries.
- */
- private static void deleteFeedItemsSynchronous(@NonNull Context context, @NonNull List<FeedItem> items) {
- List<FeedItem> queue = DBReader.getQueue();
- List<FeedItem> removedFromQueue = new ArrayList<>();
- for (FeedItem item : items) {
- if (queue.remove(item)) {
- removedFromQueue.add(item);
- }
- if (item.getMedia() != null) {
- if (item.getMedia().getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId()) {
- // Applies to both downloaded and streamed media
- PlaybackPreferences.writeNoMediaPlaying();
- IntentUtils.sendLocalBroadcast(context, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
- }
- if (!item.getFeed().isLocalFeed()) {
- if (DownloadServiceInterface.get().isDownloadingEpisode(item.getMedia().getDownloadUrl())) {
- DownloadServiceInterface.get().cancel(context, item.getMedia());
- }
- if (item.getMedia().isDownloaded()) {
- deleteFeedMediaSynchronous(context, item.getMedia());
- }
- }
- }
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- if (!removedFromQueue.isEmpty()) {
- adapter.setQueue(queue);
- }
- adapter.removeFeedItems(items);
- adapter.close();
-
- for (FeedItem item : removedFromQueue) {
- EventBus.getDefault().post(QueueEvent.irreversibleRemoved(item));
- }
-
- // we assume we also removed download log entries for the feed or its media files.
- // especially important if download or refresh failed, as the user should not be able
- // to retry these
- EventBus.getDefault().post(DownloadLogEvent.listUpdated());
-
- BackupManager backupManager = new BackupManager(context);
- backupManager.dataChanged();
- }
-
- /**
- * Deletes the entire playback history.
- */
- public static Future<?> clearPlaybackHistory() {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.clearPlaybackHistory();
- adapter.close();
- EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated());
- });
- }
-
- /**
- * Deletes the entire download log.
- */
- public static Future<?> clearDownloadLog() {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.clearDownloadLog();
- adapter.close();
- EventBus.getDefault().post(DownloadLogEvent.listUpdated());
- });
- }
-
- public static Future<?> deleteFromPlaybackHistory(FeedItem feedItem) {
- return addItemToPlaybackHistory(feedItem.getMedia(), new Date(0));
- }
-
- /**
- * Adds a FeedMedia object to the playback history. A FeedMedia object is in the playback history if
- * its playback completion date is set to a non-null value. This method will set the playback completion date to the
- * current date regardless of the current value.
- *
- * @param media FeedMedia that should be added to the playback history.
- */
- public static Future<?> addItemToPlaybackHistory(FeedMedia media) {
- return addItemToPlaybackHistory(media, new Date());
- }
-
- /**
- * Adds a FeedMedia object to the playback history. A FeedMedia object is in the playback history if
- * its playback completion date is set to a non-null value. This method will set the playback completion date to the
- * current date regardless of the current value.
- *
- * @param media FeedMedia that should be added to the playback history.
- * @param date PlaybackCompletionDate for <code>media</code>
- */
- public static Future<?> addItemToPlaybackHistory(final FeedMedia media, Date date) {
- return runOnDbThread(() -> {
- Log.d(TAG, "Adding item to playback history");
- media.setPlaybackCompletionDate(date);
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedMediaPlaybackCompletionDate(media);
- adapter.close();
- EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated());
-
- });
- }
-
- /**
- * Adds a Download status object to the download log.
- *
- * @param status The DownloadStatus object.
- */
- public static Future<?> addDownloadStatus(final DownloadResult status) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setDownloadStatus(status);
- adapter.close();
- EventBus.getDefault().post(DownloadLogEvent.listUpdated());
- });
-
- }
-
- /**
- * Inserts a FeedItem in the queue at the specified index. The 'read'-attribute of the FeedItem will be set to
- * true. If the FeedItem is already in the queue, the queue will not be modified.
- *
- * @param context A context that is used for opening a database connection.
- * @param itemId ID of the FeedItem that should be added to the queue.
- * @param index Destination index. Must be in range 0..queue.size()
- * @param performAutoDownload True if an auto-download process should be started after the operation
- * @throws IndexOutOfBoundsException if index < 0 || index >= queue.size()
- */
- public static Future<?> addQueueItemAt(final Context context, final long itemId,
- final int index, final boolean performAutoDownload) {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(adapter);
- FeedItem item;
-
- if (queue != null) {
- if (!itemListContains(queue, itemId)) {
- item = DBReader.getFeedItem(itemId);
- if (item != null) {
- queue.add(index, item);
- adapter.setQueue(queue);
- item.addTag(FeedItem.TAG_QUEUE);
- EventBus.getDefault().post(QueueEvent.added(item, index));
- EventBus.getDefault().post(FeedItemEvent.updated(item));
- if (item.isNew()) {
- DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
- }
- }
- }
- }
-
- adapter.close();
- if (performAutoDownload) {
- AutoDownloadManager.autodownloadUndownloadedItems(context);
- }
-
- });
-
- }
-
- public static Future<?> addQueueItem(final Context context, final FeedItem... items) {
- return addQueueItem(context, true, items);
- }
-
- public static Future<?> addQueueItem(final Context context, boolean markAsUnplayed, final FeedItem... items) {
- LongList itemIds = new LongList(items.length);
- for (FeedItem item : items) {
- if (!item.hasMedia()) {
- continue;
- }
- itemIds.add(item.getId());
- item.addTag(FeedItem.TAG_QUEUE);
- }
- return addQueueItem(context, false, markAsUnplayed, itemIds.toArray());
- }
-
- /**
- * Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
- * If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
- *
- * @param context A context that is used for opening a database connection.
- * @param performAutoDownload true if an auto-download process should be started after the operation.
- * @param itemIds IDs of the FeedItem objects that should be added to the queue.
- */
- public static Future<?> addQueueItem(final Context context, final boolean performAutoDownload,
- final long... itemIds) {
- return addQueueItem(context, performAutoDownload, true, itemIds);
- }
-
- /**
- * Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
- * If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
- *
- * @param context A context that is used for opening a database connection.
- * @param performAutoDownload true if an auto-download process should be started after the operation.
- * @param markAsUnplayed true if the items should be marked as unplayed when enqueueing
- * @param itemIds IDs of the FeedItem objects that should be added to the queue.
- */
- public static Future<?> addQueueItem(final Context context, final boolean performAutoDownload,
- final boolean markAsUnplayed, final long... itemIds) {
- return runOnDbThread(() -> {
- if (itemIds.length < 1) {
- return;
- }
-
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(adapter);
-
- boolean queueModified = false;
- LongList markAsUnplayedIds = new LongList();
- List<QueueEvent> events = new ArrayList<>();
- List<FeedItem> updatedItems = new ArrayList<>();
- ItemEnqueuePositionCalculator positionCalculator =
- new ItemEnqueuePositionCalculator(UserPreferences.getEnqueueLocation());
- Playable currentlyPlaying = DBReader.getFeedMedia(PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
- int insertPosition = positionCalculator.calcPosition(queue, currentlyPlaying);
- for (long itemId : itemIds) {
- if (!itemListContains(queue, itemId)) {
- final FeedItem item = DBReader.getFeedItem(itemId);
- if (item != null) {
- queue.add(insertPosition, item);
- events.add(QueueEvent.added(item, insertPosition));
-
- item.addTag(FeedItem.TAG_QUEUE);
- updatedItems.add(item);
- queueModified = true;
- if (item.isNew()) {
- markAsUnplayedIds.add(item.getId());
- }
- insertPosition++;
- }
- }
- }
- if (queueModified) {
- applySortOrder(queue, events);
- adapter.setQueue(queue);
- for (QueueEvent event : events) {
- EventBus.getDefault().post(event);
- }
- EventBus.getDefault().post(FeedItemEvent.updated(updatedItems));
- if (markAsUnplayed && markAsUnplayedIds.size() > 0) {
- DBWriter.markItemPlayed(FeedItem.UNPLAYED, markAsUnplayedIds.toArray());
- }
- }
- adapter.close();
- if (performAutoDownload) {
- AutoDownloadManager.autodownloadUndownloadedItems(context);
- }
- });
- }
-
- /**
- * Sorts the queue depending on the configured sort order.
- * If the queue is not in keep sorted mode, nothing happens.
- *
- * @param queue The queue to be sorted.
- * @param events Replaces the events by a single SORT event if the list has to be sorted automatically.
- */
- private static void applySortOrder(List<FeedItem> queue, List<QueueEvent> events) {
- if (!UserPreferences.isQueueKeepSorted()) {
- // queue is not in keep sorted mode, there's nothing to do
- return;
- }
-
- // Sort queue by configured sort order
- SortOrder sortOrder = UserPreferences.getQueueKeepSortedOrder();
- if (sortOrder == SortOrder.RANDOM) {
- // do not shuffle the list on every change
- return;
- }
- Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(sortOrder);
- permutor.reorder(queue);
-
- // Replace ADDED events by a single SORTED event
- events.clear();
- events.add(QueueEvent.sorted(queue));
- }
-
- /**
- * Removes all FeedItem objects from the queue.
- */
- public static Future<?> clearQueue() {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.clearQueue();
- adapter.close();
-
- EventBus.getDefault().post(QueueEvent.cleared());
- });
- }
-
- /**
- * Removes a FeedItem object from the queue.
- *
- * @param context A context that is used for opening a database connection.
- * @param performAutoDownload true if an auto-download process should be started after the operation.
- * @param item FeedItem that should be removed.
- */
- public static Future<?> removeQueueItem(final Context context,
- final boolean performAutoDownload, final FeedItem item) {
- return runOnDbThread(() -> removeQueueItemSynchronous(context, performAutoDownload, item.getId()));
- }
-
- public static Future<?> removeQueueItem(final Context context, final boolean performAutoDownload,
- final long... itemIds) {
- return runOnDbThread(() -> removeQueueItemSynchronous(context, performAutoDownload, itemIds));
- }
-
- private static void removeQueueItemSynchronous(final Context context,
- final boolean performAutoDownload,
- final long... itemIds) {
- if (itemIds.length < 1) {
- return;
- }
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(adapter);
-
- if (queue != null) {
- boolean queueModified = false;
- List<QueueEvent> events = new ArrayList<>();
- List<FeedItem> updatedItems = new ArrayList<>();
- for (long itemId : itemIds) {
- int position = indexInItemList(queue, itemId);
- if (position >= 0) {
- final FeedItem item = DBReader.getFeedItem(itemId);
- if (item == null) {
- Log.e(TAG, "removeQueueItem - item in queue but somehow cannot be loaded." +
- " Item ignored. It should never happen. id:" + itemId);
- continue;
- }
- queue.remove(position);
- item.removeTag(FeedItem.TAG_QUEUE);
- events.add(QueueEvent.removed(item));
- updatedItems.add(item);
- queueModified = true;
- } else {
- Log.v(TAG, "removeQueueItem - item not in queue:" + itemId);
- }
- }
- if (queueModified) {
- adapter.setQueue(queue);
- for (QueueEvent event : events) {
- EventBus.getDefault().post(event);
- }
- EventBus.getDefault().post(FeedItemEvent.updated(updatedItems));
- } else {
- Log.w(TAG, "Queue was not modified by call to removeQueueItem");
- }
- } else {
- Log.e(TAG, "removeQueueItem: Could not load queue");
- }
- adapter.close();
- if (performAutoDownload) {
- AutoDownloadManager.autodownloadUndownloadedItems(context);
- }
- }
-
- public static Future<?> toggleFavoriteItem(final FeedItem item) {
- if (item.isTagged(FeedItem.TAG_FAVORITE)) {
- return removeFavoriteItem(item);
- } else {
- return addFavoriteItem(item);
- }
- }
-
- public static Future<?> addFavoriteItem(final FeedItem item) {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
- adapter.addFavoriteItem(item);
- adapter.close();
- item.addTag(FeedItem.TAG_FAVORITE);
- EventBus.getDefault().post(new FavoritesEvent());
- EventBus.getDefault().post(FeedItemEvent.updated(item));
- });
- }
-
- public static Future<?> removeFavoriteItem(final FeedItem item) {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
- adapter.removeFavoriteItem(item);
- adapter.close();
- item.removeTag(FeedItem.TAG_FAVORITE);
- EventBus.getDefault().post(new FavoritesEvent());
- EventBus.getDefault().post(FeedItemEvent.updated(item));
- });
- }
-
- /**
- * Moves the specified item to the top of the queue.
- *
- * @param itemId The item to move to the top of the queue
- * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
- */
- public static Future<?> moveQueueItemToTop(final long itemId, final boolean broadcastUpdate) {
- return runOnDbThread(() -> {
- LongList queueIdList = DBReader.getQueueIDList();
- int index = queueIdList.indexOf(itemId);
- if (index >= 0) {
- moveQueueItemHelper(index, 0, broadcastUpdate);
- } else {
- Log.e(TAG, "moveQueueItemToTop: item not found");
- }
- });
- }
-
- /**
- * Moves the specified item to the bottom of the queue.
- *
- * @param itemId The item to move to the bottom of the queue
- * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
- */
- public static Future<?> moveQueueItemToBottom(final long itemId,
- final boolean broadcastUpdate) {
- return runOnDbThread(() -> {
- LongList queueIdList = DBReader.getQueueIDList();
- int index = queueIdList.indexOf(itemId);
- if (index >= 0) {
- moveQueueItemHelper(index, queueIdList.size() - 1,
- broadcastUpdate);
- } else {
- Log.e(TAG, "moveQueueItemToBottom: item not found");
- }
- });
- }
-
- /**
- * Changes the position of a FeedItem in the queue.
- *
- * @param from Source index. Must be in range 0..queue.size()-1.
- * @param to Destination index. Must be in range 0..queue.size()-1.
- * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
- * false if the caller wants to avoid unexpected updates of the GUI.
- * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
- */
- public static Future<?> moveQueueItem(final int from,
- final int to, final boolean broadcastUpdate) {
- return runOnDbThread(() -> moveQueueItemHelper(from, to, broadcastUpdate));
- }
-
- /**
- * Changes the position of a FeedItem in the queue.
- * <p/>
- * This function must be run using the ExecutorService (dbExec).
- *
- * @param from Source index. Must be in range 0..queue.size()-1.
- * @param to Destination index. Must be in range 0..queue.size()-1.
- * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
- * false if the caller wants to avoid unexpected updates of the GUI.
- * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
- */
- private static void moveQueueItemHelper(final int from,
- final int to, final boolean broadcastUpdate) {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(adapter);
-
- if (queue != null) {
- if (from >= 0 && from < queue.size() && to >= 0 && to < queue.size()) {
- final FeedItem item = queue.remove(from);
- queue.add(to, item);
-
- adapter.setQueue(queue);
- if (broadcastUpdate) {
- EventBus.getDefault().post(QueueEvent.moved(item, to));
- }
- }
- } else {
- Log.e(TAG, "moveQueueItemHelper: Could not load queue");
- }
- adapter.close();
- }
-
- public static Future<?> resetPagedFeedPage(Feed feed) {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.resetPagedFeedPage(feed);
- adapter.close();
- });
- }
-
- /*
- * Sets the 'read'-attribute of all specified FeedItems
- *
- * @param played New value of the 'read'-attribute, one of FeedItem.PLAYED, FeedItem.NEW,
- * FeedItem.UNPLAYED
- * @param itemIds IDs of the FeedItems.
- */
- public static Future<?> markItemPlayed(final int played, final long... itemIds) {
- return markItemPlayed(played, true, itemIds);
- }
-
- /*
- * Sets the 'read'-attribute of all specified FeedItems
- *
- * @param played New value of the 'read'-attribute, one of FeedItem.PLAYED, FeedItem.NEW,
- * FeedItem.UNPLAYED
- * @param broadcastUpdate true if this operation should trigger a UnreadItemsUpdate broadcast.
- * This option is usually set to true
- * @param itemIds IDs of the FeedItems.
- */
- public static Future<?> markItemPlayed(final int played, final boolean broadcastUpdate,
- final long... itemIds) {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItemRead(played, itemIds);
- adapter.close();
- if (broadcastUpdate) {
- EventBus.getDefault().post(new UnreadItemsUpdateEvent());
- }
- });
- }
-
- /**
- * Sets the 'read'-attribute of a FeedItem to the specified value.
- *
- * @param item The FeedItem object
- * @param played New value of the 'read'-attribute one of FeedItem.PLAYED,
- * FeedItem.NEW, FeedItem.UNPLAYED
- * @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object.
- */
- @NonNull
- public static Future<?> markItemPlayed(FeedItem item, int played, boolean resetMediaPosition) {
- long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0;
- return markItemPlayed(item.getId(), played, mediaId, resetMediaPosition);
- }
-
- @NonNull
- private static Future<?> markItemPlayed(final long itemId,
- final int played,
- final long mediaId,
- final boolean resetMediaPosition) {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItemRead(played, itemId, mediaId,
- resetMediaPosition);
- adapter.close();
-
- EventBus.getDefault().post(new UnreadItemsUpdateEvent());
- });
- }
-
- /**
- * Sets the 'read'-attribute of all NEW FeedItems of a specific Feed to UNPLAYED.
- *
- * @param feedId ID of the Feed.
- */
- public static Future<?> removeFeedNewFlag(final long feedId) {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItems(FeedItem.NEW, FeedItem.UNPLAYED, feedId);
- adapter.close();
-
- EventBus.getDefault().post(new UnreadItemsUpdateEvent());
- });
- }
-
- /**
- * Sets the 'read'-attribute of all NEW FeedItems to UNPLAYED.
- */
- public static Future<?> removeAllNewFlags() {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItems(FeedItem.NEW, FeedItem.UNPLAYED);
- adapter.close();
-
- EventBus.getDefault().post(new UnreadItemsUpdateEvent());
- });
- }
-
- static Future<?> addNewFeed(final Context context, final Feed... feeds) {
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feeds);
- adapter.close();
-
- for (Feed feed : feeds) {
- if (!feed.isLocalFeed()) {
- SynchronizationQueueSink.enqueueFeedAddedIfSynchronizationIsActive(context, feed.getDownloadUrl());
- }
- }
-
- BackupManager backupManager = new BackupManager(context);
- backupManager.dataChanged();
- });
- }
-
- static Future<?> setCompleteFeed(final Feed... feeds) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feeds);
- adapter.close();
- });
- }
-
- public static Future<?> setItemList(final List<FeedItem> items) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.storeFeedItemlist(items);
- adapter.close();
- EventBus.getDefault().post(FeedItemEvent.updated(items));
- });
- }
-
- /**
- * Saves a FeedMedia object in the database. This method will save all attributes of the FeedMedia object. The
- * contents of FeedComponent-attributes (e.g. the FeedMedia's 'item'-attribute) will not be saved.
- *
- * @param media The FeedMedia object.
- */
- public static Future<?> setFeedMedia(final FeedMedia media) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setMedia(media);
- adapter.close();
- });
- }
-
- /**
- * Saves the 'position', 'duration' and 'last played time' attributes of a FeedMedia object
- *
- * @param media The FeedMedia object.
- */
- public static Future<?> setFeedMediaPlaybackInformation(final FeedMedia media) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedMediaPlaybackInformation(media);
- adapter.close();
- });
- }
-
- /**
- * Saves a FeedItem object in the database. This method will save all attributes of the FeedItem object including
- * the content of FeedComponent-attributes.
- *
- * @param item The FeedItem object.
- */
- public static Future<?> setFeedItem(final FeedItem item) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setSingleFeedItem(item);
- adapter.close();
- EventBus.getDefault().post(FeedItemEvent.updated(item));
- });
- }
-
- /**
- * Updates download URL of a feed
- */
- public static Future<?> updateFeedDownloadURL(final String original, final String updated) {
- Log.d(TAG, "updateFeedDownloadURL(original: " + original + ", updated: " + updated + ")");
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedDownloadUrl(original, updated);
- adapter.close();
- });
- }
-
- /**
- * Saves a FeedPreferences object in the database. The Feed ID of the FeedPreferences-object MUST NOT be 0.
- *
- * @param preferences The FeedPreferences object.
- */
- public static Future<?> setFeedPreferences(final FeedPreferences preferences) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedPreferences(preferences);
- adapter.close();
- EventBus.getDefault().post(new FeedListUpdateEvent(preferences.getFeedID()));
- });
- }
-
- private static boolean itemListContains(List<FeedItem> items, long itemId) {
- return indexInItemList(items, itemId) >= 0;
- }
-
- private static int indexInItemList(List<FeedItem> items, long itemId) {
- for (int i = 0; i < items.size(); i++) {
- FeedItem item = items.get(i);
- if (item.getId() == itemId) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Saves if a feed's last update failed
- *
- * @param lastUpdateFailed true if last update failed
- */
- public static Future<?> setFeedLastUpdateFailed(final long feedId,
- final boolean lastUpdateFailed) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed);
- adapter.close();
- EventBus.getDefault().post(new FeedListUpdateEvent(feedId));
- });
- }
-
- public static Future<?> setFeedCustomTitle(Feed feed) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedCustomTitle(feed.getId(), feed.getCustomTitle());
- adapter.close();
- EventBus.getDefault().post(new FeedListUpdateEvent(feed));
- });
- }
-
- /**
- * Sort the FeedItems in the queue with the given the named sort order.
- *
- * @param broadcastUpdate <code>true</code> if this operation should trigger a
- * QueueUpdateBroadcast. This option should be set to <code>false</code>
- * if the caller wants to avoid unexpected updates of the GUI.
- */
- public static Future<?> reorderQueue(@Nullable SortOrder sortOrder, final boolean broadcastUpdate) {
- if (sortOrder == null) {
- Log.w(TAG, "reorderQueue() - sortOrder is null. Do nothing.");
- return runOnDbThread(() -> { });
- }
- final Permutor<FeedItem> permutor = FeedItemPermutors.getPermutor(sortOrder);
- return runOnDbThread(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(adapter);
-
- if (queue != null) {
- permutor.reorder(queue);
- adapter.setQueue(queue);
- if (broadcastUpdate) {
- EventBus.getDefault().post(QueueEvent.sorted(queue));
- }
- } else {
- Log.e(TAG, "reorderQueue: Could not load queue");
- }
- adapter.close();
- });
- }
-
- /**
- * Set filter of the feed
- *
- * @param feedId The feed's ID
- * @param filterValues Values that represent properties to filter by
- */
- public static Future<?> setFeedItemsFilter(final long feedId,
- final Set<String> filterValues) {
- Log.d(TAG, "setFeedItemsFilter() called with: " + "feedId = [" + feedId + "], filterValues = [" + filterValues + "]");
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItemFilter(feedId, filterValues);
- adapter.close();
- EventBus.getDefault().post(new FeedEvent(FeedEvent.Action.FILTER_CHANGED, feedId));
- });
- }
-
- /**
- * Set item sort order of the feed
- *
- */
- public static Future<?> setFeedItemSortOrder(long feedId, @Nullable SortOrder sortOrder) {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItemSortOrder(feedId, sortOrder);
- adapter.close();
- EventBus.getDefault().post(new FeedEvent(FeedEvent.Action.SORT_ORDER_CHANGED, feedId));
- });
- }
-
- /**
- * Reset the statistics in DB
- */
- @NonNull
- public static Future<?> resetStatistics() {
- return runOnDbThread(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.resetAllMediaPlayedDuration();
- adapter.close();
- });
- }
-
- /**
- * Removes the feed with the given download url. This method should NOT be executed on the GUI thread.
- *
- * @param context Used for accessing the db
- * @param downloadUrl URL of the feed.
- */
- public static void removeFeedWithDownloadUrl(Context context, String downloadUrl) {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor cursor = adapter.getFeedCursorDownloadUrls();
- long feedId = 0;
- if (cursor.moveToFirst()) {
- do {
- if (cursor.getString(1).equals(downloadUrl)) {
- feedId = cursor.getLong(0);
- }
- } while (cursor.moveToNext());
- }
- cursor.close();
- adapter.close();
-
- if (feedId != 0) {
- try {
- deleteFeed(context, feedId).get();
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- }
- } else {
- Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: " + downloadUrl);
- }
- }
-
- /**
- * Submit to the DB thread only if caller is not already on the DB thread. Otherwise,
- * just execute synchronously
- */
- private static Future<?> runOnDbThread(Runnable runnable) {
- if ("DatabaseExecutor".equals(Thread.currentThread().getName())) {
- runnable.run();
- return Futures.immediateFuture(null);
- } else {
- return dbExec.submit(runnable);
- }
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithm.java
index 86c27ffdc..2fc2ef902 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithm.java
@@ -16,6 +16,7 @@ import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.storage.database.DBReader;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedDatabaseWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedDatabaseWriter.java
deleted file mode 100644
index 7c21bd7c9..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedDatabaseWriter.java
+++ /dev/null
@@ -1,262 +0,0 @@
-package de.danoeh.antennapod.core.storage;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
-import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
-import de.danoeh.antennapod.event.FeedListUpdateEvent;
-import de.danoeh.antennapod.model.download.DownloadError;
-import de.danoeh.antennapod.model.download.DownloadResult;
-import de.danoeh.antennapod.model.feed.Feed;
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.model.feed.FeedPreferences;
-import de.danoeh.antennapod.net.sync.model.EpisodeAction;
-import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.storage.database.PodDBAdapter;
-import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import org.greenrobot.eventbus.EventBus;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-/**
- * Creates and updates feeds in the database.
- */
-public abstract class FeedDatabaseWriter {
- private static final String TAG = "FeedDbWriter";
-
- private static Feed searchFeedByIdentifyingValueOrID(Feed feed) {
- if (feed.getId() != 0) {
- return DBReader.getFeed(feed.getId());
- } else {
- List<Feed> feeds = DBReader.getFeedList();
- for (Feed f : feeds) {
- if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) {
- f.setItems(DBReader.getFeedItemList(f));
- return f;
- }
- }
- }
- return null;
- }
-
- /**
- * Get a FeedItem by its identifying value.
- */
- private static FeedItem searchFeedItemByIdentifyingValue(List<FeedItem> items, FeedItem searchItem) {
- for (FeedItem item : items) {
- if (TextUtils.equals(item.getIdentifyingValue(), searchItem.getIdentifyingValue())) {
- return item;
- }
- }
- return null;
- }
-
- /**
- * Guess if one of the items could actually mean the searched item, even if it uses another identifying value.
- * This is to work around podcasters breaking their GUIDs.
- */
- private static FeedItem searchFeedItemGuessDuplicate(List<FeedItem> items, FeedItem searchItem) {
- // First, see if it is a well-behaving feed that contains an item with the same identifier
- for (FeedItem item : items) {
- if (FeedItemDuplicateGuesser.sameAndNotEmpty(item.getItemIdentifier(), searchItem.getItemIdentifier())) {
- return item;
- }
- }
- // Not found yet, start more expensive guessing
- for (FeedItem item : items) {
- if (FeedItemDuplicateGuesser.seemDuplicates(item, searchItem)) {
- return item;
- }
- }
- return null;
- }
-
- /**
- * Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same
- * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed.
- * These FeedItems will be marked as unread with the exception of the most recent FeedItem.
- *
- * @param context Used for accessing the DB.
- * @param newFeed The new Feed object.
- * @param removeUnlistedItems The item list in the new Feed object is considered to be exhaustive.
- * I.e. items are removed from the database if they are not in this item list.
- * @return The updated Feed from the database if it already existed, or the new Feed from the parameters otherwise.
- */
- public static synchronized Feed updateFeed(Context context, Feed newFeed, boolean removeUnlistedItems) {
- Feed resultFeed;
- List<FeedItem> unlistedItems = new ArrayList<>();
- List<FeedItem> itemsToAddToQueue = new ArrayList<>();
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
-
- // Look up feed in the feedslist
- final Feed savedFeed = searchFeedByIdentifyingValueOrID(newFeed);
- if (savedFeed == null) {
- Log.d(TAG, "Found no existing Feed with title "
- + newFeed.getTitle() + ". Adding as new one.");
-
- resultFeed = newFeed;
- } else {
- Log.d(TAG, "Feed with title " + newFeed.getTitle()
- + " already exists. Syncing new with existing one.");
-
- Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
-
- if (newFeed.getPageNr() == savedFeed.getPageNr()) {
- savedFeed.updateFromOther(newFeed);
- savedFeed.getPreferences().updateFromOther(newFeed.getPreferences());
- } else {
- Log.d(TAG, "New feed has a higher page number.");
- savedFeed.setNextPageLink(newFeed.getNextPageLink());
- }
-
- // get the most recent date now, before we start changing the list
- FeedItem priorMostRecent = savedFeed.getMostRecentItem();
- Date priorMostRecentDate = new Date();
- if (priorMostRecent != null) {
- priorMostRecentDate = priorMostRecent.getPubDate();
- }
-
- // Look for new or updated Items
- for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
- final FeedItem item = newFeed.getItems().get(idx);
-
- FeedItem possibleDuplicate = searchFeedItemGuessDuplicate(newFeed.getItems(), item);
- if (!newFeed.isLocalFeed() && possibleDuplicate != null && item != possibleDuplicate) {
- // Canonical episode is the first one returned (usually oldest)
- DBWriter.addDownloadStatus(new DownloadResult(item.getTitle(),
- savedFeed.getId(), Feed.FEEDFILETYPE_FEED, false,
- DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE,
- "The podcast host appears to have added the same episode twice. "
- + "AntennaPod still refreshed the feed and attempted to repair it."
- + "\n\nOriginal episode:\n" + duplicateEpisodeDetails(item)
- + "\n\nSecond episode that is also in the feed:\n"
- + duplicateEpisodeDetails(possibleDuplicate)));
- continue;
- }
-
- FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed.getItems(), item);
- if (!newFeed.isLocalFeed() && oldItem == null) {
- oldItem = searchFeedItemGuessDuplicate(savedFeed.getItems(), item);
- if (oldItem != null) {
- Log.d(TAG, "Repaired duplicate: " + oldItem + ", " + item);
- DBWriter.addDownloadStatus(new DownloadResult(item.getTitle(),
- savedFeed.getId(), Feed.FEEDFILETYPE_FEED, false,
- DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE,
- "The podcast host changed the ID of an existing episode instead of just "
- + "updating the episode itself. AntennaPod still refreshed the feed and "
- + "attempted to repair it."
- + "\n\nOriginal episode:\n" + duplicateEpisodeDetails(oldItem)
- + "\n\nNow the feed contains:\n" + duplicateEpisodeDetails(item)));
- oldItem.setItemIdentifier(item.getItemIdentifier());
-
- if (oldItem.isPlayed() && oldItem.getMedia() != null) {
- EpisodeAction action = new EpisodeAction.Builder(oldItem, EpisodeAction.PLAY)
- .currentTimestamp()
- .started(oldItem.getMedia().getDuration() / 1000)
- .position(oldItem.getMedia().getDuration() / 1000)
- .total(oldItem.getMedia().getDuration() / 1000)
- .build();
- SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
- }
- }
- }
-
- if (oldItem != null) {
- oldItem.updateFromOther(item);
- } else {
- Log.d(TAG, "Found new item: " + item.getTitle());
- item.setFeed(savedFeed);
-
- if (idx >= savedFeed.getItems().size()) {
- savedFeed.getItems().add(item);
- } else {
- savedFeed.getItems().add(idx, item);
- }
-
- if (item.getPubDate() == null
- || priorMostRecentDate == null
- || priorMostRecentDate.before(item.getPubDate())
- || priorMostRecentDate.equals(item.getPubDate())) {
- Log.d(TAG, "Performing new episode action for item published on " + item.getPubDate()
- + ", prior most recent date = " + priorMostRecentDate);
- FeedPreferences.NewEpisodesAction action = savedFeed.getPreferences().getNewEpisodesAction();
- if (action == FeedPreferences.NewEpisodesAction.GLOBAL) {
- action = UserPreferences.getNewEpisodesAction();
- }
- switch (action) {
- case ADD_TO_INBOX:
- item.setNew();
- break;
- case ADD_TO_QUEUE:
- itemsToAddToQueue.add(item);
- break;
- default:
- break;
- }
- }
- }
- }
-
- // identify items to be removed
- if (removeUnlistedItems) {
- Iterator<FeedItem> it = savedFeed.getItems().iterator();
- while (it.hasNext()) {
- FeedItem feedItem = it.next();
- if (searchFeedItemByIdentifyingValue(newFeed.getItems(), feedItem) == null) {
- unlistedItems.add(feedItem);
- it.remove();
- }
- }
- }
-
- // update attributes
- savedFeed.setLastModified(newFeed.getLastModified());
- savedFeed.setType(newFeed.getType());
- savedFeed.setLastUpdateFailed(false);
-
- resultFeed = savedFeed;
- }
-
- try {
- if (savedFeed == null) {
- DBWriter.addNewFeed(context, newFeed).get();
- // Update with default values that are set in database
- resultFeed = searchFeedByIdentifyingValueOrID(newFeed);
- } else {
- DBWriter.setCompleteFeed(savedFeed).get();
- }
- if (removeUnlistedItems) {
- DBWriter.deleteFeedItems(context, unlistedItems).get();
- }
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- }
-
- // We need to add to queue after items are saved to database
- DBWriter.addQueueItem(context, itemsToAddToQueue.toArray(new FeedItem[0]));
-
- adapter.close();
-
- if (savedFeed != null) {
- EventBus.getDefault().post(new FeedListUpdateEvent(savedFeed));
- } else {
- EventBus.getDefault().post(new FeedListUpdateEvent(Collections.emptyList()));
- }
-
- return resultFeed;
- }
-
- private static String duplicateEpisodeDetails(FeedItem item) {
- return "Title: " + item.getTitle()
- + "\nID: " + item.getItemIdentifier()
- + ((item.getMedia() == null) ? "" : "\nURL: " + item.getMedia().getDownloadUrl());
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesser.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesser.java
deleted file mode 100644
index 1eb8b0577..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesser.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package de.danoeh.antennapod.core.storage;
-
-import android.text.TextUtils;
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.model.feed.FeedMedia;
-
-import java.text.DateFormat;
-import java.util.Locale;
-
-/**
- * Publishers sometimes mess up their feed by adding episodes twice or by changing the ID of existing episodes.
- * This class tries to guess if publishers actually meant another episode,
- * even if their feed explicitly says that the episodes are different.
- */
-public class FeedItemDuplicateGuesser {
- public static boolean seemDuplicates(FeedItem item1, FeedItem item2) {
- if (sameAndNotEmpty(item1.getItemIdentifier(), item2.getItemIdentifier())) {
- return true;
- }
- FeedMedia media1 = item1.getMedia();
- FeedMedia media2 = item2.getMedia();
- if (media1 == null || media2 == null) {
- return false;
- }
- if (sameAndNotEmpty(media1.getStreamUrl(), media2.getStreamUrl())) {
- return true;
- }
- return titlesLookSimilar(item1, item2)
- && datesLookSimilar(item1, item2)
- && durationsLookSimilar(media1, media2)
- && mimeTypeLooksSimilar(media1, media2);
- }
-
- public static boolean sameAndNotEmpty(String string1, String string2) {
- if (TextUtils.isEmpty(string1) || TextUtils.isEmpty(string2)) {
- return false;
- }
- return string1.equals(string2);
- }
-
- private static boolean datesLookSimilar(FeedItem item1, FeedItem item2) {
- if (item1.getPubDate() == null || item2.getPubDate() == null) {
- return false;
- }
- DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US); // MM/DD/YY
- String dateOriginal = dateFormat.format(item2.getPubDate());
- String dateNew = dateFormat.format(item1.getPubDate());
- return TextUtils.equals(dateOriginal, dateNew); // Same date; time is ignored.
- }
-
- private static boolean durationsLookSimilar(FeedMedia media1, FeedMedia media2) {
- return Math.abs(media1.getDuration() - media2.getDuration()) < 10 * 60L * 1000L;
- }
-
- private static boolean mimeTypeLooksSimilar(FeedMedia media1, FeedMedia media2) {
- String mimeType1 = media1.getMimeType();
- String mimeType2 = media2.getMimeType();
- if (mimeType1 == null || mimeType2 == null) {
- return true;
- }
- if (mimeType1.contains("/") && mimeType2.contains("/")) {
- mimeType1 = mimeType1.substring(0, mimeType1.indexOf("/"));
- mimeType2 = mimeType2.substring(0, mimeType2.indexOf("/"));
- }
- return TextUtils.equals(mimeType1, mimeType2);
- }
-
- private static boolean titlesLookSimilar(FeedItem item1, FeedItem item2) {
- return sameAndNotEmpty(canonicalizeTitle(item1.getTitle()), canonicalizeTitle(item2.getTitle()));
- }
-
- private static String canonicalizeTitle(String title) {
- if (title == null) {
- return "";
- }
- return title
- .trim()
- .replace('“', '"')
- .replace('”', '"')
- .replace('„', '"')
- .replace('—', '-');
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java b/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
deleted file mode 100644
index 3731c4069..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package de.danoeh.antennapod.core.storage;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.List;
-import java.util.Random;
-
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.model.feed.FeedMedia;
-import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
-import de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation;
-import de.danoeh.antennapod.model.playback.Playable;
-
-/**
- * @see DBWriter#addQueueItem(Context, boolean, long...) it uses the class to determine
- * the positions of the {@link FeedItem} in the queue.
- */
-class ItemEnqueuePositionCalculator {
-
- @NonNull
- private final EnqueueLocation enqueueLocation;
-
- public ItemEnqueuePositionCalculator(@NonNull EnqueueLocation enqueueLocation) {
- this.enqueueLocation = enqueueLocation;
- }
-
- /**
- * Determine the position (0-based) that the item(s) should be inserted to the named queue.
- *
- * @param curQueue the queue to which the item is to be inserted
- * @param currentPlaying the currently playing media
- */
- public int calcPosition(@NonNull List<FeedItem> curQueue, @Nullable Playable currentPlaying) {
- switch (enqueueLocation) {
- case BACK:
- return curQueue.size();
- case FRONT:
- // Return not necessarily 0, so that when a list of items are downloaded and enqueued
- // in succession of calls (e.g., users manually tapping download one by one),
- // the items enqueued are kept the same order.
- // Simply returning 0 will reverse the order.
- return getPositionOfFirstNonDownloadingItem(0, curQueue);
- case AFTER_CURRENTLY_PLAYING:
- int currentlyPlayingPosition = getCurrentlyPlayingPosition(curQueue, currentPlaying);
- return getPositionOfFirstNonDownloadingItem(
- currentlyPlayingPosition + 1, curQueue);
- case RANDOM:
- Random random = new Random();
- return random.nextInt(curQueue.size() + 1);
- default:
- throw new AssertionError("calcPosition() : unrecognized enqueueLocation option: " + enqueueLocation);
- }
- }
-
- private int getPositionOfFirstNonDownloadingItem(int startPosition, List<FeedItem> curQueue) {
- final int curQueueSize = curQueue.size();
- for (int i = startPosition; i < curQueueSize; i++) {
- if (!isItemAtPositionDownloading(i, curQueue)) {
- return i;
- } // else continue to search;
- }
- return curQueueSize;
- }
-
- private boolean isItemAtPositionDownloading(int position, List<FeedItem> curQueue) {
- FeedItem curItem;
- try {
- curItem = curQueue.get(position);
- } catch (IndexOutOfBoundsException e) {
- curItem = null;
- }
- return curItem != null
- && curItem.getMedia() != null
- && DownloadServiceInterface.get().isDownloadingEpisode(curItem.getMedia().getDownloadUrl());
- }
-
- private static int getCurrentlyPlayingPosition(@NonNull List<FeedItem> curQueue,
- @Nullable Playable currentPlaying) {
- if (!(currentPlaying instanceof FeedMedia)) {
- return -1;
- }
- final long curPlayingItemId = ((FeedMedia) currentPlaying).getItem().getId();
- for (int i = 0; i < curQueue.size(); i++) {
- if (curPlayingItemId == curQueue.get(i).getId()) {
- return i;
- }
- }
- return -1;
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/EpisodeActionFilter.java b/core/src/main/java/de/danoeh/antennapod/core/sync/EpisodeActionFilter.java
deleted file mode 100644
index f616b9c4e..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/sync/EpisodeActionFilter.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package de.danoeh.antennapod.core.sync;
-
-import android.util.Log;
-
-import androidx.collection.ArrayMap;
-import androidx.core.util.Pair;
-
-import java.util.List;
-import java.util.Map;
-
-import de.danoeh.antennapod.net.sync.model.EpisodeAction;
-
-public class EpisodeActionFilter {
-
- public static final String TAG = "EpisodeActionFilter";
-
- public static Map<Pair<String, String>, EpisodeAction> getRemoteActionsOverridingLocalActions(
- List<EpisodeAction> remoteActions,
- List<EpisodeAction> queuedEpisodeActions) {
- // make sure more recent local actions are not overwritten by older remote actions
- Map<Pair<String, String>, EpisodeAction> remoteActionsThatOverrideLocalActions = new ArrayMap<>();
- Map<Pair<String, String>, EpisodeAction> localMostRecentPlayActions =
- createUniqueLocalMostRecentPlayActions(queuedEpisodeActions);
- for (EpisodeAction remoteAction : remoteActions) {
- Pair<String, String> key = new Pair<>(remoteAction.getPodcast(), remoteAction.getEpisode());
- switch (remoteAction.getAction()) {
- case NEW:
- case DOWNLOAD:
- break;
- case PLAY:
- EpisodeAction localMostRecent = localMostRecentPlayActions.get(key);
- if (secondActionOverridesFirstAction(remoteAction, localMostRecent)) {
- break;
- }
- EpisodeAction remoteMostRecentAction = remoteActionsThatOverrideLocalActions.get(key);
- if (secondActionOverridesFirstAction(remoteAction, remoteMostRecentAction)) {
- break;
- }
- remoteActionsThatOverrideLocalActions.put(key, remoteAction);
- break;
- case DELETE:
- // NEVER EVER call DBWriter.deleteFeedMediaOfItem() here, leads to an infinite loop
- break;
- default:
- Log.e(TAG, "Unknown remoteAction: " + remoteAction);
- break;
- }
- }
-
- return remoteActionsThatOverrideLocalActions;
- }
-
- private static Map<Pair<String, String>, EpisodeAction> createUniqueLocalMostRecentPlayActions(
- List<EpisodeAction> queuedEpisodeActions) {
- Map<Pair<String, String>, EpisodeAction> localMostRecentPlayAction;
- localMostRecentPlayAction = new ArrayMap<>();
- for (EpisodeAction action : queuedEpisodeActions) {
- Pair<String, String> key = new Pair<>(action.getPodcast(), action.getEpisode());
- EpisodeAction mostRecent = localMostRecentPlayAction.get(key);
- if (mostRecent == null || mostRecent.getTimestamp() == null) {
- localMostRecentPlayAction.put(key, action);
- } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
- localMostRecentPlayAction.put(key, action);
- }
- }
- return localMostRecentPlayAction;
- }
-
- private static boolean secondActionOverridesFirstAction(EpisodeAction firstAction,
- EpisodeAction secondAction) {
- return secondAction != null
- && secondAction.getTimestamp() != null
- && (firstAction.getTimestamp() == null
- || secondAction.getTimestamp().after(firstAction.getTimestamp()));
- }
-
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/GuidValidator.java b/core/src/main/java/de/danoeh/antennapod/core/sync/GuidValidator.java
deleted file mode 100644
index 74e5d5cdf..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/sync/GuidValidator.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package de.danoeh.antennapod.core.sync;
-
-public class GuidValidator {
-
- public static boolean isValidGuid(String guid) {
- return guid != null
- && !guid.trim().isEmpty()
- && !guid.equals("null");
- }
-}
-
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/LockingAsyncExecutor.java b/core/src/main/java/de/danoeh/antennapod/core/sync/LockingAsyncExecutor.java
deleted file mode 100644
index e7dbbbd3c..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/sync/LockingAsyncExecutor.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.danoeh.antennapod.core.sync;
-
-import java.util.concurrent.locks.ReentrantLock;
-
-import io.reactivex.Completable;
-import io.reactivex.schedulers.Schedulers;
-
-public class LockingAsyncExecutor {
-
- static final ReentrantLock lock = new ReentrantLock();
-
- /**
- * Take the lock and execute runnable (to prevent changes to preferences being lost when enqueueing while sync is
- * in progress). If the lock is free, the runnable is directly executed in the calling thread to prevent overhead.
- */
- public static void executeLockedAsync(Runnable runnable) {
- if (lock.tryLock()) {
- try {
- runnable.run();
- } finally {
- lock.unlock();
- }
- } else {
- Completable.fromRunnable(() -> {
- lock.lock();
- try {
- runnable.run();
- } finally {
- lock.unlock();
- }
- }).subscribeOn(Schedulers.io())
- .subscribe();
- }
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java
deleted file mode 100644
index 30178edf9..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java
+++ /dev/null
@@ -1,386 +0,0 @@
-package de.danoeh.antennapod.core.sync;
-
-import android.Manifest;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
-import androidx.core.content.ContextCompat;
-import androidx.core.util.Pair;
-import androidx.work.BackoffPolicy;
-import androidx.work.Constraints;
-import androidx.work.ExistingWorkPolicy;
-import androidx.work.NetworkType;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.WorkManager;
-import androidx.work.Worker;
-import androidx.work.WorkerParameters;
-
-import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
-import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
-import de.danoeh.antennapod.event.MessageEvent;
-import de.danoeh.antennapod.model.feed.FeedItemFilter;
-import de.danoeh.antennapod.model.feed.SortOrder;
-import de.danoeh.antennapod.storage.preferences.SynchronizationCredentials;
-import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
-import de.danoeh.antennapod.ui.notifications.NotificationUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.greenrobot.eventbus.EventBus;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.event.SyncServiceEvent;
-import de.danoeh.antennapod.storage.preferences.UserPreferences;
-import de.danoeh.antennapod.net.common.AntennapodHttpClient;
-import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.core.storage.FeedDatabaseWriter;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueStorage;
-import de.danoeh.antennapod.core.util.FeedItemUtil;
-import de.danoeh.antennapod.storage.database.LongList;
-import de.danoeh.antennapod.net.common.UrlChecker;
-import de.danoeh.antennapod.model.feed.Feed;
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.model.feed.FeedMedia;
-import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
-import de.danoeh.antennapod.net.sync.model.EpisodeAction;
-import de.danoeh.antennapod.net.sync.model.EpisodeActionChanges;
-import de.danoeh.antennapod.net.sync.model.ISyncService;
-import de.danoeh.antennapod.net.sync.model.SubscriptionChanges;
-import de.danoeh.antennapod.net.sync.model.SyncServiceException;
-import de.danoeh.antennapod.net.sync.model.UploadChangesResponse;
-import de.danoeh.antennapod.net.sync.nextcloud.NextcloudSyncService;
-
-public class SyncService extends Worker {
- public static final String TAG = "SyncService";
- private static final String WORK_ID_SYNC = "SyncServiceWorkId";
-
- private static boolean isCurrentlyActive = false;
- private final SynchronizationQueueStorage synchronizationQueueStorage;
-
- public SyncService(@NonNull Context context, @NonNull WorkerParameters params) {
- super(context, params);
- synchronizationQueueStorage = new SynchronizationQueueStorage(context);
- }
-
- @Override
- @NonNull
- public Result doWork() {
- ISyncService activeSyncProvider = getActiveSyncProvider();
- if (activeSyncProvider == null) {
- return Result.success();
- }
-
- SynchronizationSettings.updateLastSynchronizationAttempt();
- setCurrentlyActive(true);
- try {
- activeSyncProvider.login();
- syncSubscriptions(activeSyncProvider);
- waitForDownloadServiceCompleted();
- syncEpisodeActions(activeSyncProvider);
- activeSyncProvider.logout();
- clearErrorNotifications();
- EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_success));
- SynchronizationSettings.setLastSynchronizationAttemptSuccess(true);
- return Result.success();
- } catch (Exception e) {
- EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_error));
- SynchronizationSettings.setLastSynchronizationAttemptSuccess(false);
- Log.e(TAG, Log.getStackTraceString(e));
-
- if (e instanceof SyncServiceException) {
- if (getRunAttemptCount() % 3 == 2) {
- // Do not spam users with notification and retry before notifying
- updateErrorNotification(e);
- }
- return Result.retry();
- } else {
- updateErrorNotification(e);
- return Result.failure();
- }
- } finally {
- setCurrentlyActive(false);
- }
- }
-
- private static void setCurrentlyActive(boolean active) {
- isCurrentlyActive = active;
- }
-
- public static void sync(Context context) {
- OneTimeWorkRequest workRequest = getWorkRequest().build();
- WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
- }
-
- public static void syncImmediately(Context context) {
- OneTimeWorkRequest workRequest = getWorkRequest()
- .setInitialDelay(0L, TimeUnit.SECONDS)
- .build();
- WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
- }
-
- public static void fullSync(Context context) {
- LockingAsyncExecutor.executeLockedAsync(() -> {
- SynchronizationSettings.resetTimestamps();
- OneTimeWorkRequest workRequest = getWorkRequest()
- .setInitialDelay(0L, TimeUnit.SECONDS)
- .build();
- WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
- });
- }
-
- private void syncSubscriptions(ISyncService syncServiceImpl) throws SyncServiceException {
- final long lastSync = SynchronizationSettings.getLastSubscriptionSynchronizationTimestamp();
- EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_subscriptions));
- final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls();
- SubscriptionChanges subscriptionChanges = syncServiceImpl.getSubscriptionChanges(lastSync);
- long newTimeStamp = subscriptionChanges.getTimestamp();
-
- List<String> queuedRemovedFeeds = synchronizationQueueStorage.getQueuedRemovedFeeds();
- List<String> queuedAddedFeeds = synchronizationQueueStorage.getQueuedAddedFeeds();
-
- Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
- for (String downloadUrl : subscriptionChanges.getAdded()) {
- if (!downloadUrl.startsWith("http")) { // Also matches https
- Log.d(TAG, "Skipping url: " + downloadUrl);
- continue;
- }
- if (!UrlChecker.containsUrl(localSubscriptions, downloadUrl) && !queuedRemovedFeeds.contains(downloadUrl)) {
- Feed feed = new Feed(downloadUrl, null, "Unknown podcast");
- feed.setItems(Collections.emptyList());
- Feed newFeed = FeedDatabaseWriter.updateFeed(getApplicationContext(), feed, false);
- FeedUpdateManager.runOnce(getApplicationContext(), newFeed);
- }
- }
-
- // remove subscription if not just subscribed (again)
- for (String downloadUrl : subscriptionChanges.getRemoved()) {
- if (!queuedAddedFeeds.contains(downloadUrl)) {
- DBWriter.removeFeedWithDownloadUrl(getApplicationContext(), downloadUrl);
- }
- }
-
- if (lastSync == 0) {
- Log.d(TAG, "First sync. Adding all local subscriptions.");
- queuedAddedFeeds = localSubscriptions;
- queuedAddedFeeds.removeAll(subscriptionChanges.getAdded());
- queuedRemovedFeeds.removeAll(subscriptionChanges.getRemoved());
- }
-
- if (queuedAddedFeeds.size() > 0 || queuedRemovedFeeds.size() > 0) {
- Log.d(TAG, "Added: " + StringUtils.join(queuedAddedFeeds, ", "));
- Log.d(TAG, "Removed: " + StringUtils.join(queuedRemovedFeeds, ", "));
-
- LockingAsyncExecutor.lock.lock();
- try {
- UploadChangesResponse uploadResponse = syncServiceImpl
- .uploadSubscriptionChanges(queuedAddedFeeds, queuedRemovedFeeds);
- synchronizationQueueStorage.clearFeedQueues();
- newTimeStamp = uploadResponse.timestamp;
- } finally {
- LockingAsyncExecutor.lock.unlock();
- }
- }
- SynchronizationSettings.setLastSubscriptionSynchronizationAttemptTimestamp(newTimeStamp);
- }
-
- private void waitForDownloadServiceCompleted() {
- EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_wait_for_downloads));
- try {
- while (true) {
- //noinspection BusyWait
- Thread.sleep(1000);
- FeedUpdateRunningEvent event = EventBus.getDefault().getStickyEvent(FeedUpdateRunningEvent.class);
- if (event == null || !event.isFeedUpdateRunning) {
- return;
- }
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- private void syncEpisodeActions(ISyncService syncServiceImpl) throws SyncServiceException {
- final long lastSync = SynchronizationSettings.getLastEpisodeActionSynchronizationTimestamp();
- EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_episodes_download));
- EpisodeActionChanges getResponse = syncServiceImpl.getEpisodeActionChanges(lastSync);
- long newTimeStamp = getResponse.getTimestamp();
- List<EpisodeAction> remoteActions = getResponse.getEpisodeActions();
- processEpisodeActions(remoteActions);
-
- // upload local actions
- EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_episodes_upload));
- List<EpisodeAction> queuedEpisodeActions = synchronizationQueueStorage.getQueuedEpisodeActions();
- if (lastSync == 0) {
- EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_upload_played));
- List<FeedItem> readItems = DBReader.getEpisodes(0, Integer.MAX_VALUE,
- new FeedItemFilter(FeedItemFilter.PLAYED), SortOrder.DATE_NEW_OLD);
- Log.d(TAG, "First sync. Upload state for all " + readItems.size() + " played episodes");
- for (FeedItem item : readItems) {
- FeedMedia media = item.getMedia();
- if (media == null) {
- continue;
- }
- EpisodeAction played = new EpisodeAction.Builder(item, EpisodeAction.PLAY)
- .currentTimestamp()
- .started(media.getDuration() / 1000)
- .position(media.getDuration() / 1000)
- .total(media.getDuration() / 1000)
- .build();
- queuedEpisodeActions.add(played);
- }
- }
- if (queuedEpisodeActions.size() > 0) {
- LockingAsyncExecutor.lock.lock();
- try {
- Log.d(TAG, "Uploading " + queuedEpisodeActions.size() + " actions: "
- + StringUtils.join(queuedEpisodeActions, ", "));
- UploadChangesResponse postResponse = syncServiceImpl.uploadEpisodeActions(queuedEpisodeActions);
- newTimeStamp = postResponse.timestamp;
- Log.d(TAG, "Upload episode response: " + postResponse);
- synchronizationQueueStorage.clearEpisodeActionQueue();
- } finally {
- LockingAsyncExecutor.lock.unlock();
- }
- }
- SynchronizationSettings.setLastEpisodeActionSynchronizationAttemptTimestamp(newTimeStamp);
- }
-
- private synchronized void processEpisodeActions(List<EpisodeAction> remoteActions) {
- Log.d(TAG, "Processing " + remoteActions.size() + " actions");
- if (remoteActions.size() == 0) {
- return;
- }
-
- Map<Pair<String, String>, EpisodeAction> playActionsToUpdate = EpisodeActionFilter
- .getRemoteActionsOverridingLocalActions(remoteActions,
- synchronizationQueueStorage.getQueuedEpisodeActions());
- LongList queueToBeRemoved = new LongList();
- List<FeedItem> updatedItems = new ArrayList<>();
- for (EpisodeAction action : playActionsToUpdate.values()) {
- String guid = GuidValidator.isValidGuid(action.getGuid()) ? action.getGuid() : null;
- FeedItem feedItem = DBReader.getFeedItemByGuidOrEpisodeUrl(guid, action.getEpisode());
- if (feedItem == null) {
- Log.i(TAG, "Unknown feed item: " + action);
- continue;
- }
- if (feedItem.getMedia() == null) {
- Log.i(TAG, "Feed item has no media: " + action);
- continue;
- }
- feedItem.getMedia().setPosition(action.getPosition() * 1000);
- if (FeedItemUtil.hasAlmostEnded(feedItem.getMedia())) {
- Log.d(TAG, "Marking as played: " + action);
- feedItem.setPlayed(true);
- feedItem.getMedia().setPosition(0);
- queueToBeRemoved.add(feedItem.getId());
- } else {
- Log.d(TAG, "Setting position: " + action);
- }
- updatedItems.add(feedItem);
- }
- DBWriter.removeQueueItem(getApplicationContext(), false, queueToBeRemoved.toArray());
- DBReader.loadAdditionalFeedItemListData(updatedItems);
- DBWriter.setItemList(updatedItems);
- }
-
- private void clearErrorNotifications() {
- NotificationManager nm = (NotificationManager) getApplicationContext()
- .getSystemService(Context.NOTIFICATION_SERVICE);
- nm.cancel(R.id.notification_gpodnet_sync_error);
- nm.cancel(R.id.notification_gpodnet_sync_autherror);
- }
-
- private void updateErrorNotification(Exception exception) {
- Log.d(TAG, "Posting sync error notification");
- final String description = getApplicationContext().getString(R.string.gpodnetsync_error_descr)
- + exception.getMessage();
-
- if (!UserPreferences.gpodnetNotificationsEnabled()) {
- Log.d(TAG, "Skipping sync error notification because of user setting");
- return;
- }
- if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent.class)) {
- EventBus.getDefault().post(new MessageEvent(description));
- return;
- }
-
- Intent intent = getApplicationContext().getPackageManager().getLaunchIntentForPackage(
- getApplicationContext().getPackageName());
- PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),
- R.id.pending_intent_sync_error, intent, PendingIntent.FLAG_UPDATE_CURRENT
- | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
- Notification notification = new NotificationCompat.Builder(getApplicationContext(),
- NotificationUtils.CHANNEL_ID_SYNC_ERROR)
- .setContentTitle(getApplicationContext().getString(R.string.gpodnetsync_error_title))
- .setContentText(description)
- .setStyle(new NotificationCompat.BigTextStyle().bigText(description))
- .setContentIntent(pendingIntent)
- .setSmallIcon(R.drawable.ic_notification_sync_error)
- .setAutoCancel(true)
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- .build();
- NotificationManager nm = (NotificationManager) getApplicationContext()
- .getSystemService(Context.NOTIFICATION_SERVICE);
- if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.POST_NOTIFICATIONS)
- == PackageManager.PERMISSION_GRANTED) {
- nm.notify(R.id.notification_gpodnet_sync_error, notification);
- }
- }
-
- private static OneTimeWorkRequest.Builder getWorkRequest() {
- Constraints.Builder constraints = new Constraints.Builder();
- if (UserPreferences.isAllowMobileSync()) {
- constraints.setRequiredNetworkType(NetworkType.CONNECTED);
- } else {
- constraints.setRequiredNetworkType(NetworkType.UNMETERED);
- }
-
- OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(SyncService.class)
- .setConstraints(constraints.build())
- .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.MINUTES);
-
- if (isCurrentlyActive) {
- // Debounce: don't start sync again immediately after it was finished.
- builder.setInitialDelay(2L, TimeUnit.MINUTES);
- } else {
- // Give it some time, so other possible actions can be queued.
- builder.setInitialDelay(20L, TimeUnit.SECONDS);
- EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_started));
- }
- return builder;
- }
-
- private ISyncService getActiveSyncProvider() {
- String selectedSyncProviderKey = SynchronizationSettings.getSelectedSyncProviderKey();
- SynchronizationProviderViewData selectedService = SynchronizationProviderViewData
- .fromIdentifier(selectedSyncProviderKey);
- if (selectedService == null) {
- return null;
- }
- switch (selectedService) {
- case GPODDER_NET:
- return new GpodnetService(AntennapodHttpClient.getHttpClient(),
- SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceId(),
- SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword());
- case NEXTCLOUD_GPODDER:
- return new NextcloudSyncService(AntennapodHttpClient.getHttpClient(),
- SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getUsername(),
- SynchronizationCredentials.getPassword());
- default:
- return null;
- }
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/SynchronizationProviderViewData.java b/core/src/main/java/de/danoeh/antennapod/core/sync/SynchronizationProviderViewData.java
deleted file mode 100644
index cba713f60..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/sync/SynchronizationProviderViewData.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package de.danoeh.antennapod.core.sync;
-
-import de.danoeh.antennapod.core.R;
-
-public enum SynchronizationProviderViewData {
- GPODDER_NET(
- "GPODDER_NET",
- R.string.gpodnet_description,
- R.drawable.gpodder_icon
- ),
- NEXTCLOUD_GPODDER(
- "NEXTCLOUD_GPODDER",
- R.string.synchronization_summary_nextcloud,
- R.drawable.nextcloud_logo
- );
-
- public static SynchronizationProviderViewData fromIdentifier(String provider) {
- for (SynchronizationProviderViewData synchronizationProvider : SynchronizationProviderViewData.values()) {
- if (synchronizationProvider.getIdentifier().equals(provider)) {
- return synchronizationProvider;
- }
- }
- return null;
- }
-
- private final String identifier;
- private final int iconResource;
- private final int summaryResource;
-
- SynchronizationProviderViewData(String identifier, int summaryResource, int iconResource) {
- this.identifier = identifier;
- this.iconResource = iconResource;
- this.summaryResource = summaryResource;
- }
-
- public String getIdentifier() {
- return identifier;
- }
-
- public int getIconResource() {
- return iconResource;
- }
-
- public int getSummaryResource() {
- return summaryResource;
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/queue/SynchronizationQueueSink.java b/core/src/main/java/de/danoeh/antennapod/core/sync/queue/SynchronizationQueueSink.java
deleted file mode 100644
index 8d59c9146..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/sync/queue/SynchronizationQueueSink.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package de.danoeh.antennapod.core.sync.queue;
-
-import android.content.Context;
-
-import de.danoeh.antennapod.core.sync.LockingAsyncExecutor;
-import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
-import de.danoeh.antennapod.model.feed.FeedMedia;
-import de.danoeh.antennapod.net.sync.model.EpisodeAction;
-
-public class SynchronizationQueueSink {
- // To avoid a dependency loop of every class to SyncService, and from SyncService back to every class.
- private static Runnable serviceStarterImpl = () -> { };
-
- public static void setServiceStarterImpl(Runnable serviceStarter) {
- serviceStarterImpl = serviceStarter;
- }
-
- public static void syncNow() {
- serviceStarterImpl.run();
- }
-
- public static void syncNowIfNotSyncedRecently() {
- if (System.currentTimeMillis() - SynchronizationSettings.getLastSyncAttempt() > 1000 * 60 * 10) {
- syncNow();
- }
- }
-
- public static void clearQueue(Context context) {
- LockingAsyncExecutor.executeLockedAsync(new SynchronizationQueueStorage(context)::clearQueue);
- }
-
- public static void enqueueFeedAddedIfSynchronizationIsActive(Context context, String downloadUrl) {
- if (!SynchronizationSettings.isProviderConnected()) {
- return;
- }
- LockingAsyncExecutor.executeLockedAsync(() -> {
- new SynchronizationQueueStorage(context).enqueueFeedAdded(downloadUrl);
- syncNow();
- });
- }
-
- public static void enqueueFeedRemovedIfSynchronizationIsActive(Context context, String downloadUrl) {
- if (!SynchronizationSettings.isProviderConnected()) {
- return;
- }
- LockingAsyncExecutor.executeLockedAsync(() -> {
- new SynchronizationQueueStorage(context).enqueueFeedRemoved(downloadUrl);
- syncNow();
- });
- }
-
- public static void enqueueEpisodeActionIfSynchronizationIsActive(Context context, EpisodeAction action) {
- if (!SynchronizationSettings.isProviderConnected()) {
- return;
- }
- LockingAsyncExecutor.executeLockedAsync(() -> {
- new SynchronizationQueueStorage(context).enqueueEpisodeAction(action);
- syncNow();
- });
- }
-
- public static void enqueueEpisodePlayedIfSynchronizationIsActive(Context context, FeedMedia media,
- boolean completed) {
- if (!SynchronizationSettings.isProviderConnected()) {
- return;
- }
- if (media.getItem() == null || media.getItem().getFeed().isLocalFeed()) {
- return;
- }
- if (media.getStartPosition() < 0 || (!completed && media.getStartPosition() >= media.getPosition())) {
- return;
- }
- EpisodeAction action = new EpisodeAction.Builder(media.getItem(), EpisodeAction.PLAY)
- .currentTimestamp()
- .started(media.getStartPosition() / 1000)
- .position((completed ? media.getDuration() : media.getPosition()) / 1000)
- .total(media.getDuration() / 1000)
- .build();
- enqueueEpisodeActionIfSynchronizationIsActive(context, action);
- }
-
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/queue/SynchronizationQueueStorage.java b/core/src/main/java/de/danoeh/antennapod/core/sync/queue/SynchronizationQueueStorage.java
deleted file mode 100644
index 407d69fd6..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/sync/queue/SynchronizationQueueStorage.java
+++ /dev/null
@@ -1,158 +0,0 @@
-package de.danoeh.antennapod.core.sync.queue;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-
-import java.util.ArrayList;
-
-import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
-import de.danoeh.antennapod.net.sync.model.EpisodeAction;
-
-public class SynchronizationQueueStorage {
-
- private static final String NAME = "synchronization";
- private static final String QUEUED_EPISODE_ACTIONS = "sync_queued_episode_actions";
- private static final String QUEUED_FEEDS_REMOVED = "sync_removed";
- private static final String QUEUED_FEEDS_ADDED = "sync_added";
- private final SharedPreferences sharedPreferences;
-
- public SynchronizationQueueStorage(Context context) {
- this.sharedPreferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE);
- }
-
- public ArrayList<EpisodeAction> getQueuedEpisodeActions() {
- ArrayList<EpisodeAction> actions = new ArrayList<>();
- try {
- String json = getSharedPreferences()
- .getString(QUEUED_EPISODE_ACTIONS, "[]");
- JSONArray queue = new JSONArray(json);
- for (int i = 0; i < queue.length(); i++) {
- actions.add(EpisodeAction.readFromJsonObject(queue.getJSONObject(i)));
- }
- } catch (JSONException e) {
- e.printStackTrace();
- }
- return actions;
- }
-
- public ArrayList<String> getQueuedRemovedFeeds() {
- ArrayList<String> removedFeedUrls = new ArrayList<>();
- try {
- String json = getSharedPreferences()
- .getString(QUEUED_FEEDS_REMOVED, "[]");
- JSONArray queue = new JSONArray(json);
- for (int i = 0; i < queue.length(); i++) {
- removedFeedUrls.add(queue.getString(i));
- }
- } catch (JSONException e) {
- e.printStackTrace();
- }
- return removedFeedUrls;
-
- }
-
- public ArrayList<String> getQueuedAddedFeeds() {
- ArrayList<String> addedFeedUrls = new ArrayList<>();
- try {
- String json = getSharedPreferences()
- .getString(QUEUED_FEEDS_ADDED, "[]");
- JSONArray queue = new JSONArray(json);
- for (int i = 0; i < queue.length(); i++) {
- addedFeedUrls.add(queue.getString(i));
- }
- } catch (JSONException e) {
- e.printStackTrace();
- }
- return addedFeedUrls;
- }
-
- public void clearEpisodeActionQueue() {
- getSharedPreferences().edit()
- .putString(QUEUED_EPISODE_ACTIONS, "[]").apply();
-
- }
-
- public void clearFeedQueues() {
- getSharedPreferences().edit()
- .putString(QUEUED_FEEDS_ADDED, "[]")
- .putString(QUEUED_FEEDS_REMOVED, "[]")
- .apply();
- }
-
- protected void clearQueue() {
- SynchronizationSettings.resetTimestamps();
- getSharedPreferences().edit()
- .putString(QUEUED_EPISODE_ACTIONS, "[]")
- .putString(QUEUED_FEEDS_ADDED, "[]")
- .putString(QUEUED_FEEDS_REMOVED, "[]")
- .apply();
-
- }
-
- protected void enqueueFeedAdded(String downloadUrl) {
- SharedPreferences sharedPreferences = getSharedPreferences();
- try {
- JSONArray addedQueue = new JSONArray(sharedPreferences.getString(QUEUED_FEEDS_ADDED, "[]"));
- addedQueue.put(downloadUrl);
- JSONArray removedQueue = new JSONArray(sharedPreferences.getString(QUEUED_FEEDS_REMOVED, "[]"));
- removedQueue.remove(indexOf(downloadUrl, removedQueue));
- sharedPreferences.edit()
- .putString(QUEUED_FEEDS_ADDED, addedQueue.toString())
- .putString(QUEUED_FEEDS_REMOVED, removedQueue.toString())
- .apply();
-
- } catch (JSONException jsonException) {
- jsonException.printStackTrace();
- }
- }
-
- protected void enqueueFeedRemoved(String downloadUrl) {
- SharedPreferences sharedPreferences = getSharedPreferences();
- try {
- JSONArray removedQueue = new JSONArray(sharedPreferences.getString(QUEUED_FEEDS_REMOVED, "[]"));
- removedQueue.put(downloadUrl);
- JSONArray addedQueue = new JSONArray(sharedPreferences.getString(QUEUED_FEEDS_ADDED, "[]"));
- addedQueue.remove(indexOf(downloadUrl, addedQueue));
- sharedPreferences.edit()
- .putString(QUEUED_FEEDS_ADDED, addedQueue.toString())
- .putString(QUEUED_FEEDS_REMOVED, removedQueue.toString())
- .apply();
- } catch (JSONException jsonException) {
- jsonException.printStackTrace();
- }
- }
-
- private int indexOf(String string, JSONArray array) {
- try {
- for (int i = 0; i < array.length(); i++) {
- if (array.getString(i).equals(string)) {
- return i;
- }
- }
- } catch (JSONException jsonException) {
- jsonException.printStackTrace();
- }
- return -1;
- }
-
- protected void enqueueEpisodeAction(EpisodeAction action) {
- SharedPreferences sharedPreferences = getSharedPreferences();
- String json = sharedPreferences.getString(QUEUED_EPISODE_ACTIONS, "[]");
- try {
- JSONArray queue = new JSONArray(json);
- queue.put(action.writeToJsonObject());
- sharedPreferences.edit().putString(
- QUEUED_EPISODE_ACTIONS, queue.toString()
- ).apply();
- } catch (JSONException jsonException) {
- jsonException.printStackTrace();
- }
- }
-
- private SharedPreferences getSharedPreferences() {
- return sharedPreferences;
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
index f72589a38..db8a33576 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
@@ -2,8 +2,6 @@ package de.danoeh.antennapod.core.util;
import androidx.annotation.NonNull;
-import de.danoeh.antennapod.model.feed.FeedMedia;
-import de.danoeh.antennapod.storage.preferences.UserPreferences;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
@@ -68,9 +66,4 @@ public class FeedItemUtil {
}
return null;
}
-
- public static boolean hasAlmostEnded(FeedMedia media) {
- int smartMarkAsPlayedSecs = UserPreferences.getSmartMarkAsPlayedSecs();
- return media.getDuration() > 0 && media.getPosition() >= media.getDuration() - smartMarkAsPlayedSecs * 1000;
- }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java
deleted file mode 100644
index b16f0f1aa..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package de.danoeh.antennapod.core.util.comparator;
-
-import java.util.Comparator;
-
-import de.danoeh.antennapod.model.feed.FeedItem;
-
-/**
- * Compares the pubDate of two FeedItems for sorting.
- */
-public class FeedItemPubdateComparator implements Comparator<FeedItem> {
-
- /**
- * Returns a new instance of this comparator in reverse order.
- */
- @Override
- public int compare(FeedItem lhs, FeedItem rhs) {
- if (rhs.getPubDate() == null && lhs.getPubDate() == null) {
- return 0;
- } else if (rhs.getPubDate() == null) {
- return 1;
- } else if (lhs.getPubDate() == null) {
- return -1;
- }
- return rhs.getPubDate().compareTo(lhs.getPubDate());
- }
-
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManagerImpl.java
index 89097d9ee..17077c237 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManagerImpl.java
@@ -19,12 +19,13 @@ import de.danoeh.antennapod.core.service.FeedUpdateWorker;
import de.danoeh.antennapod.net.common.NetworkUtils;
import de.danoeh.antennapod.event.MessageEvent;
import de.danoeh.antennapod.model.feed.Feed;
+import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import org.greenrobot.eventbus.EventBus;
import java.util.concurrent.TimeUnit;
-public class FeedUpdateManager {
+public class FeedUpdateManagerImpl extends FeedUpdateManager {
public static final String WORK_TAG_FEED_UPDATE = "feedUpdate";
private static final String WORK_ID_FEED_UPDATE = "de.danoeh.antennapod.core.service.FeedUpdateWorker";
private static final String WORK_ID_FEED_UPDATE_MANUAL = "feedUpdateManual";
@@ -33,15 +34,11 @@ public class FeedUpdateManager {
public static final String EXTRA_EVEN_ON_MOBILE = "even_on_mobile";
private static final String TAG = "AutoUpdateManager";
- private FeedUpdateManager() {
-
- }
-
/**
* Start / restart periodic auto feed refresh
* @param context Context
*/
- public static void restartUpdateAlarm(Context context, boolean replace) {
+ public void restartUpdateAlarm(Context context, boolean replace) {
if (UserPreferences.isAutoUpdateDisabled()) {
WorkManager.getInstance(context).cancelUniqueWork(WORK_ID_FEED_UPDATE);
} else {
@@ -56,15 +53,15 @@ public class FeedUpdateManager {
}
}
- public static void runOnce(Context context) {
+ public void runOnce(Context context) {
runOnce(context, null, false);
}
- public static void runOnce(Context context, Feed feed) {
+ public void runOnce(Context context, Feed feed) {
runOnce(context, feed, false);
}
- public static void runOnce(Context context, Feed feed, boolean nextPage) {
+ public void runOnce(Context context, Feed feed, boolean nextPage) {
OneTimeWorkRequest.Builder workRequest = new OneTimeWorkRequest.Builder(FeedUpdateWorker.class)
.setInitialDelay(0L, TimeUnit.MILLISECONDS)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
@@ -84,11 +81,11 @@ public class FeedUpdateManager {
ExistingWorkPolicy.REPLACE, workRequest.build());
}
- public static void runOnceOrAsk(@NonNull Context context) {
+ public void runOnceOrAsk(@NonNull Context context) {
runOnceOrAsk(context, null);
}
- public static void runOnceOrAsk(@NonNull Context context, @Nullable Feed feed) {
+ public void runOnceOrAsk(@NonNull Context context, @Nullable Feed feed) {
Log.d(TAG, "Run auto update immediately in background.");
if (feed != null && feed.isLocalFeed()) {
runOnce(context, feed);
@@ -101,7 +98,7 @@ public class FeedUpdateManager {
}
}
- private static void confirmMobileRefresh(final Context context, @Nullable Feed feed) {
+ private void confirmMobileRefresh(final Context context, @Nullable Feed feed) {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context)
.setTitle(R.string.feed_refresh_title)
.setPositiveButton(R.string.confirm_mobile_streaming_button_once,
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/MediaSizeLoader.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/MediaSizeLoader.java
index dcfff0af2..779f3b947 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/download/MediaSizeLoader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/MediaSizeLoader.java
@@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.util.download;
import android.text.TextUtils;
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
-import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.net.common.NetworkUtils;
import de.danoeh.antennapod.model.feed.FeedMedia;
import io.reactivex.Single;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java
index cafbde368..79c6e76e1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/NetworkConnectionChangeHandler.java
@@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.util.download;
import android.content.Context;
import android.util.Log;
-import de.danoeh.antennapod.core.storage.AutoDownloadManager;
+import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.net.common.NetworkUtils;
@@ -17,7 +17,7 @@ public abstract class NetworkConnectionChangeHandler {
public static void networkChangedDetected() {
if (NetworkUtils.isAutoDownloadAllowed()) {
Log.d(TAG, "auto-dl network available, starting auto-download");
- AutoDownloadManager.autodownloadUndownloadedItems(context);
+ AutoDownloadManager.getInstance().autodownloadUndownloadedItems(context);
} else { // if new network is Wi-Fi, finish ongoing downloads,
// otherwise cancel all downloads
if (NetworkUtils.isNetworkRestricted()) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlayableUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlayableUtils.java
index 90751cbdb..9a622d440 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlayableUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlayableUtils.java
@@ -1,6 +1,6 @@
package de.danoeh.antennapod.core.util.playback;
-import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.playback.Playable;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
index b56a0481e..41342cb1f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
@@ -14,7 +14,7 @@ import android.view.SurfaceHolder;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceInterface;
import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.event.playback.PlaybackServiceEvent;
diff --git a/core/src/main/res/drawable-nodpi/gpodder_icon.png b/core/src/main/res/drawable-nodpi/gpodder_icon.png
deleted file mode 100644
index cd133aa98..000000000
--- a/core/src/main/res/drawable-nodpi/gpodder_icon.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/drawable-nodpi/nextcloud_logo.png b/core/src/main/res/drawable-nodpi/nextcloud_logo.png
deleted file mode 100644
index 2164e37fb..000000000
--- a/core/src/main/res/drawable-nodpi/nextcloud_logo.png
+++ /dev/null
Binary files differ
diff --git a/core/src/main/res/values/ids.xml b/core/src/main/res/values/ids.xml
index 90d143d38..7bb78c1c9 100644
--- a/core/src/main/res/values/ids.xml
+++ b/core/src/main/res/values/ids.xml
@@ -16,8 +16,6 @@
<item name="view_type_episode_item" type="id"/>
<!-- Notifications need unique IDs to update/cancel them -->
- <item name="notification_gpodnet_sync_error" type="id"/>
- <item name="notification_gpodnet_sync_autherror" type="id"/>
<item name="notification_downloading" type="id"/>
<item name="notification_updating_feeds" type="id"/>
<item name="notification_download_report" type="id"/>
diff --git a/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
index 12256264a..9703894f5 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
@@ -35,7 +35,7 @@ import java.util.Objects;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.storage.database.DBReader;
-import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.storage.database.DBWriter;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.is;
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java
index a7d435aed..a29a87b05 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbCleanupTests.java
@@ -15,6 +15,8 @@ import androidx.test.platform.app.InstrumentationRegistry;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
+import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
@@ -80,6 +82,7 @@ public class DbCleanupTests {
UserPreferences.init(context);
PlaybackPreferences.init(context);
SynchronizationSettings.init(context);
+ AutoDownloadManager.setInstance(new AutoDownloadManagerImpl());
}
@After
@@ -108,7 +111,7 @@ public class DbCleanupTests {
List<File> files = new ArrayList<>();
populateItems(numItems, feed, items, files, FeedItem.PLAYED, false, false);
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
if (i < EPISODE_CACHE_SIZE) {
assertTrue(files.get(i).exists());
@@ -167,7 +170,7 @@ public class DbCleanupTests {
List<File> files = new ArrayList<>();
populateItems(numItems, feed, items, files, FeedItem.UNPLAYED, false, false);
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (File file : files) {
assertTrue(file.exists());
}
@@ -183,7 +186,7 @@ public class DbCleanupTests {
List<File> files = new ArrayList<>();
populateItems(numItems, feed, items, files, FeedItem.PLAYED, true, false);
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (File file : files) {
assertTrue(file.exists());
}
@@ -223,7 +226,7 @@ public class DbCleanupTests {
List<File> files = new ArrayList<>();
populateItems(numItems, feed, items, files, FeedItem.PLAYED, false, true);
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (File file : files) {
assertTrue(file.exists());
}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java
index 243bef941..bbcf362b9 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java
@@ -14,6 +14,8 @@ import androidx.test.platform.app.InstrumentationRegistry;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
+import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
import org.junit.After;
@@ -61,6 +63,7 @@ public class DbNullCleanupAlgorithmTest {
prefEdit.commit();
UserPreferences.init(context);
+ AutoDownloadManager.setInstance(new AutoDownloadManagerImpl());
}
@After
@@ -114,7 +117,7 @@ public class DbNullCleanupAlgorithmTest {
//noinspection ConstantConditions
assertTrue(item.getMedia().getId() != 0);
}
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
assertTrue(files.get(i).exists());
}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java
index c14c3c2aa..04762b123 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbQueueCleanupAlgorithmTest.java
@@ -7,6 +7,7 @@ import java.util.List;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import org.junit.Test;
@@ -24,6 +25,7 @@ public class DbQueueCleanupAlgorithmTest extends DbCleanupTests {
public DbQueueCleanupAlgorithmTest() {
setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_QUEUE);
+ AutoDownloadManager.setInstance(new AutoDownloadManagerImpl());
}
/**
@@ -40,7 +42,7 @@ public class DbQueueCleanupAlgorithmTest extends DbCleanupTests {
List<File> files = new ArrayList<>();
populateItems(numItems, feed, items, files, FeedItem.UNPLAYED, false, false);
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
if (i < EPISODE_CACHE_SIZE) {
assertTrue(files.get(i).exists());
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java
index 99db467bf..d37e0d7e1 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java
@@ -19,6 +19,7 @@ import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.FeedOrder;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.storage.database.DBReader;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.database.NavDrawerData;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.storage.database.LongList;
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java
index 075e7a559..1f90dd7a9 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java
@@ -5,6 +5,8 @@ import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import de.danoeh.antennapod.storage.database.DBReader;
+import de.danoeh.antennapod.storage.database.DBWriter;
+import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
import org.junit.After;
import org.junit.Before;
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java
index 4fea67f79..90876cc11 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbWriterTest.java
@@ -12,6 +12,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterfaceStub;
import de.danoeh.antennapod.storage.database.DBReader;
+import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
import org.awaitility.Awaitility;
import org.junit.After;
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java
index b464a2508..df26b9e4f 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/ExceptFavoriteCleanupAlgorithmTest.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.storage;
+import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -25,6 +26,7 @@ public class ExceptFavoriteCleanupAlgorithmTest extends DbCleanupTests {
public ExceptFavoriteCleanupAlgorithmTest() {
setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_EXCEPT_FAVORITE);
+ AutoDownloadManager.setInstance(new AutoDownloadManagerImpl());
}
@Test
@@ -35,7 +37,7 @@ public class ExceptFavoriteCleanupAlgorithmTest extends DbCleanupTests {
List<File> files = new ArrayList<>();
populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, false, false);
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
if (i < EPISODE_CACHE_SIZE) {
assertTrue("Only enough items should be deleted", files.get(i).exists());
@@ -53,7 +55,7 @@ public class ExceptFavoriteCleanupAlgorithmTest extends DbCleanupTests {
List<File> files = new ArrayList<>();
populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, true, false);
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
if (i < EPISODE_CACHE_SIZE) {
assertTrue("Only enough items should be deleted", files.get(i).exists());
@@ -71,7 +73,7 @@ public class ExceptFavoriteCleanupAlgorithmTest extends DbCleanupTests {
List<File> files = new ArrayList<>();
populateItems(numberOfItems, feed, items, files, FeedItem.UNPLAYED, false, true);
- AutoDownloadManager.performAutoCleanup(context);
+ AutoDownloadManager.getInstance().performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
assertTrue("Favorite episodes should should not be deleted", files.get(i).exists());
}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java
index f3c993066..38dbe8caa 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/FeedItemDuplicateGuesserTest.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.storage;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
+import de.danoeh.antennapod.storage.database.FeedItemDuplicateGuesser;
import org.junit.Test;
import java.util.Date;
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java
index f0f81eee1..dfad714a8 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculatorTest.java
@@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.storage;
import de.danoeh.antennapod.model.playback.RemoteMedia;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterfaceStub;
+import de.danoeh.antennapod.storage.database.ItemEnqueuePositionCalculator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
diff --git a/core/src/test/java/de/danoeh/antennapod/core/sync/EpisodeActionFilterTest.java b/core/src/test/java/de/danoeh/antennapod/core/sync/EpisodeActionFilterTest.java
deleted file mode 100644
index 1f638bf32..000000000
--- a/core/src/test/java/de/danoeh/antennapod/core/sync/EpisodeActionFilterTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-package de.danoeh.antennapod.core.sync;
-
-
-import androidx.core.util.Pair;
-
-import junit.framework.TestCase;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import de.danoeh.antennapod.net.sync.model.EpisodeAction;
-
-
-public class EpisodeActionFilterTest extends TestCase {
-
- EpisodeActionFilter episodeActionFilter = new EpisodeActionFilter();
-
- public void testGetRemoteActionsHappeningAfterLocalActions() throws ParseException {
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date morning = format.parse("2021-01-01 08:00:00");
- Date lateMorning = format.parse("2021-01-01 09:00:00");
-
- List<EpisodeAction> episodeActions = new ArrayList<>();
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(morning)
- .position(10)
- .build()
- );
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(lateMorning)
- .position(20)
- .build()
- );
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
- .timestamp(morning)
- .position(5)
- .build()
- );
-
- Date morningFiveMinutesLater = format.parse("2021-01-01 08:05:00");
- List<EpisodeAction> remoteActions = new ArrayList<>();
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(morningFiveMinutesLater)
- .position(10)
- .build()
- );
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
- .timestamp(morningFiveMinutesLater)
- .position(5)
- .build()
- );
-
- Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
- .getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
- assertSame(1, uniqueList.size());
- }
-
- public void testGetRemoteActionsHappeningBeforeLocalActions() throws ParseException {
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date morning = format.parse("2021-01-01 08:00:00");
- Date lateMorning = format.parse("2021-01-01 09:00:00");
-
- List<EpisodeAction> episodeActions = new ArrayList<>();
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(morning)
- .position(10)
- .build()
- );
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(lateMorning)
- .position(20)
- .build()
- );
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
- .timestamp(morning)
- .position(5)
- .build()
- );
-
- Date morningFiveMinutesEarlier = format.parse("2021-01-01 07:55:00");
- List<EpisodeAction> remoteActions = new ArrayList<>();
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(morningFiveMinutesEarlier)
- .position(10)
- .build()
- );
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
- .timestamp(morningFiveMinutesEarlier)
- .position(5)
- .build()
- );
-
- Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
- .getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
- assertSame(0, uniqueList.size());
- }
-
- public void testGetMultipleRemoteActionsHappeningAfterLocalActions() throws ParseException {
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date morning = format.parse("2021-01-01 08:00:00");
-
- List<EpisodeAction> episodeActions = new ArrayList<>();
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(morning)
- .position(10)
- .build()
- );
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
- .timestamp(morning)
- .position(5)
- .build()
- );
-
- Date morningFiveMinutesLater = format.parse("2021-01-01 08:05:00");
- List<EpisodeAction> remoteActions = new ArrayList<>();
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(morningFiveMinutesLater)
- .position(10)
- .build()
- );
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
- .timestamp(morningFiveMinutesLater)
- .position(5)
- .build()
- );
-
- Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
- .getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
- assertEquals(2, uniqueList.size());
- }
-
- public void testGetMultipleRemoteActionsHappeningBeforeLocalActions() throws ParseException {
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date morning = format.parse("2021-01-01 08:00:00");
-
- List<EpisodeAction> episodeActions = new ArrayList<>();
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(morning)
- .position(10)
- .build()
- );
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
- .timestamp(morning)
- .position(5)
- .build()
- );
-
- Date morningFiveMinutesEarlier = format.parse("2021-01-01 07:55:00");
- List<EpisodeAction> remoteActions = new ArrayList<>();
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(morningFiveMinutesEarlier)
- .position(10)
- .build()
- );
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY)
- .timestamp(morningFiveMinutesEarlier)
- .position(5)
- .build()
- );
-
- Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
- .getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
- assertEquals(0, uniqueList.size());
- }
-
- public void testPresentRemoteTimestampOverridesMissingLocalTimestamp() throws ParseException {
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date arbitraryTime = format.parse("2021-01-01 08:00:00");
-
- List<EpisodeAction> episodeActions = new ArrayList<>();
- episodeActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- // no timestamp
- .position(10)
- .build()
- );
-
- List<EpisodeAction> remoteActions = new ArrayList<>();
- remoteActions.add(new EpisodeAction
- .Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY)
- .timestamp(arbitraryTime)
- .position(10)
- .build()
- );
-
- Map<Pair<String, String>, EpisodeAction> uniqueList = episodeActionFilter
- .getRemoteActionsOverridingLocalActions(remoteActions, episodeActions);
- assertSame(1, uniqueList.size());
- }
-}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/sync/GuidValidatorTest.java b/core/src/test/java/de/danoeh/antennapod/core/sync/GuidValidatorTest.java
deleted file mode 100644
index 552f7d70a..000000000
--- a/core/src/test/java/de/danoeh/antennapod/core/sync/GuidValidatorTest.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package de.danoeh.antennapod.core.sync;
-
-import junit.framework.TestCase;
-
-public class GuidValidatorTest extends TestCase {
-
- public void testIsValidGuid() {
- assertTrue(GuidValidator.isValidGuid("skfjsdvgsd"));
- }
-
- public void testIsInvalidGuid() {
- assertFalse(GuidValidator.isValidGuid(""));
- assertFalse(GuidValidator.isValidGuid(" "));
- assertFalse(GuidValidator.isValidGuid("\n"));
- assertFalse(GuidValidator.isValidGuid(" \n"));
- assertFalse(GuidValidator.isValidGuid(null));
- assertFalse(GuidValidator.isValidGuid("null"));
- }
-} \ No newline at end of file