Make sync service interface more standard (#7479)
This commit is contained in:
parent
f9bea87adc
commit
4a92a5e019
|
@ -7,8 +7,8 @@ import de.danoeh.antennapod.net.download.service.episode.autodownload.AutoDownlo
|
|||
import de.danoeh.antennapod.net.download.service.feed.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.net.sync.service.SynchronizationQueueImpl;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationCredentials;
|
||||
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
|
||||
|
@ -50,7 +50,7 @@ public class ClientConfigurator {
|
|||
DownloadServiceInterface.setImpl(new DownloadServiceInterfaceImpl());
|
||||
FeedUpdateManager.setInstance(new FeedUpdateManagerImpl());
|
||||
AutoDownloadManager.setInstance(new AutoDownloadManagerImpl());
|
||||
SynchronizationQueueSink.setServiceStarterImpl(() -> SyncService.sync(context));
|
||||
SynchronizationQueue.setInstance(new SynchronizationQueueImpl(context));
|
||||
AntennapodHttpClient.setCacheDirectory(new File(context.getCacheDir(), "okhttp"));
|
||||
AntennapodHttpClient.setProxyConfig(UserPreferences.getProxyConfig());
|
||||
SleepTimerPreferences.init(context);
|
||||
|
|
|
@ -52,7 +52,7 @@ import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
|||
import de.danoeh.antennapod.net.download.service.feed.FeedUpdateManagerImpl;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.playback.cast.CastEnabledActivity;
|
||||
import de.danoeh.antennapod.storage.database.DBReader;
|
||||
import de.danoeh.antennapod.storage.importexport.AutomaticDatabaseExportWorker;
|
||||
|
@ -205,7 +205,7 @@ public class MainActivity extends CastEnabledActivity {
|
|||
sheetBehavior.setBottomSheetCallback(bottomSheetCallback);
|
||||
|
||||
FeedUpdateManager.getInstance().restartUpdateAlarm(this, false);
|
||||
SynchronizationQueueSink.syncNowIfNotSyncedRecently();
|
||||
SynchronizationQueue.getInstance().syncIfNotSyncedRecently();
|
||||
AutomaticDatabaseExportWorker.enqueueIfNeeded(this, false);
|
||||
|
||||
WorkManager.getInstance(this)
|
||||
|
|
|
@ -18,7 +18,7 @@ import java.util.List;
|
|||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackServiceInterface;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
|
@ -195,7 +195,7 @@ public class FeedItemMenuHandler {
|
|||
.position(media.getDuration() / 1000)
|
||||
.total(media.getDuration() / 1000)
|
||||
.build();
|
||||
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, actionPlay);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodeAction(actionPlay);
|
||||
}
|
||||
}
|
||||
} else if (menuItemId == R.id.mark_unread_item) {
|
||||
|
@ -203,10 +203,10 @@ public class FeedItemMenuHandler {
|
|||
DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false);
|
||||
if (!selectedItem.getFeed().isLocalFeed() && selectedItem.getMedia() != null
|
||||
&& selectedItem.getFeed().getState() == Feed.STATE_SUBSCRIBED) {
|
||||
EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW)
|
||||
.currentTimestamp()
|
||||
.build();
|
||||
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, actionNew);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodeAction(
|
||||
new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW)
|
||||
.currentTimestamp()
|
||||
.build());
|
||||
}
|
||||
} else if (menuItemId == R.id.add_to_queue_item) {
|
||||
DBWriter.addQueueItem(context, selectedItem);
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 4e155a5f2af1cb4e83d6e48eba056b3a19466e96
|
||||
Subproject commit f88328d81d6509487cba71bc4b2bbe96cc47206f
|
|
@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import de.danoeh.antennapod.model.MediaMetadataRetrieverCompat;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.ui.chapters.ChapterUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
@ -116,10 +116,10 @@ public class MediaDownloadedHandler implements Runnable {
|
|||
}
|
||||
|
||||
if (item != null && item.getFeed().getState() == Feed.STATE_SUBSCRIBED) {
|
||||
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD)
|
||||
.currentTimestamp()
|
||||
.build();
|
||||
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodeAction(
|
||||
new EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD)
|
||||
.currentTimestamp()
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import android.content.Context;
|
|||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueStub;
|
||||
import de.danoeh.antennapod.storage.database.DBReader;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
|
||||
|
@ -45,6 +47,7 @@ public class DbTasksTest {
|
|||
context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
UserPreferences.init(context);
|
||||
PlaybackPreferences.init(context);
|
||||
SynchronizationQueue.setInstance(new SynchronizationQueueStub());
|
||||
|
||||
// create new database
|
||||
PodDBAdapter.init(context);
|
||||
|
|
|
@ -13,6 +13,8 @@ import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
|||
import de.danoeh.antennapod.model.feed.SortOrder;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterfaceStub;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueStub;
|
||||
import de.danoeh.antennapod.storage.database.DBReader;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
|
@ -62,6 +64,7 @@ public class DbWriterTest {
|
|||
UserPreferences.init(context);
|
||||
PlaybackPreferences.init(context);
|
||||
DownloadServiceInterface.setImpl(new DownloadServiceInterfaceStub());
|
||||
SynchronizationQueue.setInstance(new SynchronizationQueueStub());
|
||||
|
||||
// create new database
|
||||
PodDBAdapter.init(context);
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package de.danoeh.antennapod.net.sync.serviceinterface;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
|
||||
public abstract class SynchronizationQueue {
|
||||
private static SynchronizationQueue instance;
|
||||
|
||||
public static SynchronizationQueue getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void setInstance(SynchronizationQueue instance) {
|
||||
SynchronizationQueue.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync bundled events after some delay to avoid spamming the sync server.
|
||||
*/
|
||||
public abstract void sync();
|
||||
|
||||
public abstract void syncImmediately();
|
||||
|
||||
public abstract void fullSync();
|
||||
|
||||
public abstract void syncIfNotSyncedRecently();
|
||||
|
||||
public abstract void clear();
|
||||
|
||||
public abstract void enqueueFeedAdded(String downloadUrl);
|
||||
|
||||
public abstract void enqueueFeedRemoved(String downloadUrl);
|
||||
|
||||
public abstract void enqueueEpisodeAction(EpisodeAction action);
|
||||
|
||||
public abstract void enqueueEpisodePlayed(FeedMedia media, boolean completed);
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package de.danoeh.antennapod.net.sync.serviceinterface;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
|
||||
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()
|
||||
|| media.getItem().getFeed().getState() != Feed.STATE_SUBSCRIBED) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package de.danoeh.antennapod.net.sync.serviceinterface;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
|
||||
public class SynchronizationQueueStub extends SynchronizationQueue {
|
||||
@Override
|
||||
public void sync() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncImmediately() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullSync() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncIfNotSyncedRecently() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueFeedAdded(String downloadUrl) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueFeedRemoved(String downloadUrl) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueEpisodeAction(EpisodeAction action) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueEpisodePlayed(FeedMedia media, boolean completed) {
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package de.danoeh.antennapod.net.sync.serviceinterface;
|
||||
package de.danoeh.antennapod.net.sync.service;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
|
@ -9,32 +9,39 @@ 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.event.FeedUpdateRunningEvent;
|
||||
import de.danoeh.antennapod.event.MessageEvent;
|
||||
import de.danoeh.antennapod.event.SyncServiceEvent;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
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.net.common.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.net.common.UrlChecker;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.LockingAsyncExecutor;
|
||||
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.net.sync.nextcloud.NextcloudSyncService;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.EpisodeAction;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.EpisodeActionChanges;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.ISyncService;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SubscriptionChanges;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SyncServiceException;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationProvider;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueStorage;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.UploadChangesResponse;
|
||||
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.LongList;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationCredentials;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.ui.notifications.NotificationUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
@ -43,31 +50,11 @@ 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.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.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.serviceinterface.EpisodeAction;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.EpisodeActionChanges;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.ISyncService;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SubscriptionChanges;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SyncServiceException;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.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 static boolean currentlyActive = false;
|
||||
private final SynchronizationQueueStorage synchronizationQueueStorage;
|
||||
|
||||
public SyncService(@NonNull Context context, @NonNull WorkerParameters params) {
|
||||
|
@ -84,7 +71,7 @@ public class SyncService extends Worker {
|
|||
}
|
||||
|
||||
SynchronizationSettings.updateLastSynchronizationAttempt();
|
||||
setCurrentlyActive(true);
|
||||
currentlyActive = true;
|
||||
try {
|
||||
activeSyncProvider.login();
|
||||
syncSubscriptions(activeSyncProvider);
|
||||
|
@ -111,34 +98,12 @@ public class SyncService extends Worker {
|
|||
return Result.failure();
|
||||
}
|
||||
} finally {
|
||||
setCurrentlyActive(false);
|
||||
currentlyActive = 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);
|
||||
});
|
||||
/* package-private */ static boolean isCurrentlyActive() {
|
||||
return currentlyActive;
|
||||
}
|
||||
|
||||
private void syncSubscriptions(ISyncService syncServiceImpl) throws SyncServiceException {
|
||||
|
@ -179,7 +144,7 @@ public class SyncService extends Worker {
|
|||
queuedRemovedFeeds.removeAll(subscriptionChanges.getRemoved());
|
||||
}
|
||||
|
||||
if (queuedAddedFeeds.size() > 0 || queuedRemovedFeeds.size() > 0) {
|
||||
if (!queuedAddedFeeds.isEmpty() || !queuedRemovedFeeds.isEmpty()) {
|
||||
Log.d(TAG, "Added: " + StringUtils.join(queuedAddedFeeds, ", "));
|
||||
Log.d(TAG, "Removed: " + StringUtils.join(queuedRemovedFeeds, ", "));
|
||||
|
||||
|
@ -260,7 +225,7 @@ public class SyncService extends Worker {
|
|||
|
||||
private synchronized void processEpisodeActions(List<EpisodeAction> remoteActions) {
|
||||
Log.d(TAG, "Processing " + remoteActions.size() + " actions");
|
||||
if (remoteActions.size() == 0) {
|
||||
if (remoteActions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -344,29 +309,6 @@ public class SyncService extends Worker {
|
|||
}
|
||||
}
|
||||
|
||||
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();
|
||||
SynchronizationProvider selectedService = SynchronizationProvider
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package de.danoeh.antennapod.net.sync.service;
|
||||
|
||||
import android.content.Context;
|
||||
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 de.danoeh.antennapod.event.SyncServiceEvent;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.EpisodeAction;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SynchronizationQueueImpl extends SynchronizationQueue {
|
||||
private static final String WORK_ID_SYNC = "SyncServiceWorkId";
|
||||
private final Context context;
|
||||
|
||||
public SynchronizationQueueImpl(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void sync() {
|
||||
OneTimeWorkRequest workRequest = getWorkRequest().build();
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
|
||||
}
|
||||
|
||||
public void syncIfNotSyncedRecently() {
|
||||
if (System.currentTimeMillis() - SynchronizationSettings.getLastSyncAttempt() > 1000 * 60 * 10) {
|
||||
sync();
|
||||
}
|
||||
}
|
||||
|
||||
public void syncImmediately() {
|
||||
OneTimeWorkRequest workRequest = getWorkRequest()
|
||||
.setInitialDelay(0L, TimeUnit.SECONDS)
|
||||
.build();
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
|
||||
}
|
||||
|
||||
public void fullSync() {
|
||||
LockingAsyncExecutor.executeLockedAsync(() -> {
|
||||
SynchronizationSettings.resetTimestamps();
|
||||
syncImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
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 (SyncService.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;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
LockingAsyncExecutor.executeLockedAsync(new SynchronizationQueueStorage(context)::clearQueue);
|
||||
}
|
||||
|
||||
public void enqueueFeedAdded(String downloadUrl) {
|
||||
if (!SynchronizationSettings.isProviderConnected()) {
|
||||
return;
|
||||
}
|
||||
LockingAsyncExecutor.executeLockedAsync(() -> {
|
||||
new SynchronizationQueueStorage(context).enqueueFeedAdded(downloadUrl);
|
||||
sync();
|
||||
});
|
||||
}
|
||||
|
||||
public void enqueueFeedRemoved(String downloadUrl) {
|
||||
if (!SynchronizationSettings.isProviderConnected()) {
|
||||
return;
|
||||
}
|
||||
LockingAsyncExecutor.executeLockedAsync(() -> {
|
||||
new SynchronizationQueueStorage(context).enqueueFeedRemoved(downloadUrl);
|
||||
sync();
|
||||
});
|
||||
}
|
||||
|
||||
public void enqueueEpisodeAction(EpisodeAction action) {
|
||||
if (!SynchronizationSettings.isProviderConnected()) {
|
||||
return;
|
||||
}
|
||||
LockingAsyncExecutor.executeLockedAsync(() -> {
|
||||
new SynchronizationQueueStorage(context).enqueueEpisodeAction(action);
|
||||
sync();
|
||||
});
|
||||
}
|
||||
|
||||
public void enqueueEpisodePlayed(FeedMedia media, boolean completed) {
|
||||
if (!SynchronizationSettings.isProviderConnected()) {
|
||||
return;
|
||||
}
|
||||
if (media.getItem() == null || media.getItem().getFeed().isLocalFeed()
|
||||
|| media.getItem().getFeed().getState() != Feed.STATE_SUBSCRIBED) {
|
||||
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();
|
||||
enqueueEpisodeAction(action);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
package de.danoeh.antennapod.net.sync.serviceinterface;
|
||||
package de.danoeh.antennapod.net.sync.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.EpisodeAction;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
|
@ -54,7 +54,7 @@ import androidx.lifecycle.Observer;
|
|||
import androidx.media.MediaBrowserServiceCompat;
|
||||
|
||||
import de.danoeh.antennapod.event.PlayerStatusEvent;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.playback.service.internal.LocalPSMP;
|
||||
import de.danoeh.antennapod.playback.service.internal.PlayableUtils;
|
||||
import de.danoeh.antennapod.playback.service.internal.PlaybackServiceNotificationBuilder;
|
||||
|
@ -943,8 +943,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
// Don't store position after position is already reset
|
||||
saveCurrentPosition(position == Playable.INVALID_TIME, playable, position);
|
||||
}
|
||||
SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(getApplicationContext(),
|
||||
media, false);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodePlayed(media, false);
|
||||
}
|
||||
playable.onPlaybackPause(getApplicationContext());
|
||||
}
|
||||
|
@ -1141,12 +1140,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
|
||||
if (ended || almostEnded) {
|
||||
SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(
|
||||
getApplicationContext(), media, true);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodePlayed(media, true);
|
||||
media.onPlaybackCompleted(getApplicationContext());
|
||||
} else {
|
||||
SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(
|
||||
getApplicationContext(), media, false);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodePlayed(media, false);
|
||||
media.onPlaybackPause(getApplicationContext());
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
|||
import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
|
@ -151,11 +151,10 @@ public class DBWriter {
|
|||
FeedUpdateManager.getInstance().runOnce(context, media.getItem().getFeed());
|
||||
} else {
|
||||
if (media.getItem().getFeed().getState() == Feed.STATE_SUBSCRIBED) {
|
||||
FeedItem item = media.getItem();
|
||||
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE)
|
||||
.currentTimestamp()
|
||||
.build();
|
||||
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodeAction(
|
||||
new EpisodeAction.Builder(media.getItem(), EpisodeAction.DELETE)
|
||||
.currentTimestamp()
|
||||
.build());
|
||||
}
|
||||
|
||||
EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
|
||||
|
@ -185,7 +184,7 @@ public class DBWriter {
|
|||
adapter.close();
|
||||
|
||||
if (!feed.isLocalFeed() && feed.getState() == Feed.STATE_SUBSCRIBED) {
|
||||
SynchronizationQueueSink.enqueueFeedRemovedIfSynchronizationIsActive(context, feed.getDownloadUrl());
|
||||
SynchronizationQueue.getInstance().enqueueFeedRemoved(feed.getDownloadUrl());
|
||||
}
|
||||
EventBus.getDefault().post(new FeedListUpdateEvent(feed));
|
||||
});
|
||||
|
@ -779,7 +778,7 @@ public class DBWriter {
|
|||
|
||||
for (Feed feed : feeds) {
|
||||
if (!feed.isLocalFeed() && feed.getState() == Feed.STATE_SUBSCRIBED) {
|
||||
SynchronizationQueueSink.enqueueFeedAddedIfSynchronizationIsActive(context, feed.getDownloadUrl());
|
||||
SynchronizationQueue.getInstance().enqueueFeedAdded(feed.getDownloadUrl());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -931,13 +930,12 @@ public class DBWriter {
|
|||
feed.getPreferences().setKeepUpdated(true);
|
||||
DBWriter.setFeedPreferences(feed.getPreferences());
|
||||
FeedUpdateManager.getInstance().runOnceOrAsk(context, feed);
|
||||
SynchronizationQueueSink.enqueueFeedAddedIfSynchronizationIsActive(context, feed.getDownloadUrl());
|
||||
SynchronizationQueue.getInstance().enqueueFeedAdded(feed.getDownloadUrl());
|
||||
DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(),
|
||||
SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE);
|
||||
for (FeedItem item : feed.getItems()) {
|
||||
if (item.isPlayed()) {
|
||||
SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(
|
||||
context, item.getMedia(), true);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodePlayed(item.getMedia(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
|||
import de.danoeh.antennapod.model.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.model.feed.SortOrder;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.EpisodeAction;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
|
@ -165,7 +165,7 @@ public abstract class FeedDatabaseWriter {
|
|||
.position(oldItem.getMedia().getDuration() / 1000)
|
||||
.total(oldItem.getMedia().getDuration() / 1000)
|
||||
.build();
|
||||
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
|
||||
SynchronizationQueue.getInstance().enqueueEpisodeAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|||
import androidx.fragment.app.DialogFragment;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.net.sync.service.SyncService;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationProvider;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationCredentials;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
|
||||
import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService;
|
||||
|
@ -84,7 +83,7 @@ public class GpodderAuthenticationFragment extends DialogFragment {
|
|||
return;
|
||||
}
|
||||
SynchronizationCredentials.clear();
|
||||
SynchronizationQueueSink.clearQueue(getContext());
|
||||
SynchronizationQueue.getInstance().clear();
|
||||
SynchronizationCredentials.setHosturl(serverUrlText.getText().toString());
|
||||
service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
|
||||
SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceId(),
|
||||
|
@ -235,7 +234,7 @@ public class GpodderAuthenticationFragment extends DialogFragment {
|
|||
|
||||
sync.setOnClickListener(v -> {
|
||||
dismiss();
|
||||
SyncService.sync(getContext());
|
||||
SynchronizationQueue.getInstance().syncImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -12,9 +12,8 @@ import androidx.annotation.Nullable;
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.net.sync.service.SyncService;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationProvider;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationCredentials;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
|
||||
import de.danoeh.antennapod.net.sync.nextcloud.NextcloudLoginFlow;
|
||||
|
@ -93,11 +92,11 @@ public class NextcloudAuthenticationFragment extends DialogFragment
|
|||
SynchronizationSettings.setSelectedSyncProvider(
|
||||
SynchronizationProvider.NEXTCLOUD_GPODDER.getIdentifier());
|
||||
SynchronizationCredentials.clear();
|
||||
SynchronizationQueueSink.clearQueue(getContext());
|
||||
SynchronizationQueue.getInstance().clear();
|
||||
SynchronizationCredentials.setPassword(password);
|
||||
SynchronizationCredentials.setHosturl(server);
|
||||
SynchronizationCredentials.setUsername(username);
|
||||
SyncService.fullSync(getContext());
|
||||
SynchronizationQueue.getInstance().fullSync();
|
||||
if (isResumed()) {
|
||||
dismiss();
|
||||
} else {
|
||||
|
|
|
@ -22,9 +22,8 @@ import androidx.preference.Preference;
|
|||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import de.danoeh.antennapod.net.sync.service.SyncService;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationProvider;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.ui.preferences.R;
|
||||
import de.danoeh.antennapod.ui.preferences.screen.AnimatedPreferenceFragment;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
@ -95,16 +94,16 @@ public class SynchronizationPreferencesFragment extends AnimatedPreferenceFragme
|
|||
return true;
|
||||
});
|
||||
findPreference(PREFERENCE_SYNC).setOnPreferenceClickListener(preference -> {
|
||||
SyncService.syncImmediately(getActivity().getApplicationContext());
|
||||
SynchronizationQueue.getInstance().syncImmediately();
|
||||
return true;
|
||||
});
|
||||
findPreference(PREFERENCE_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> {
|
||||
SyncService.fullSync(getContext());
|
||||
SynchronizationQueue.getInstance().fullSync();
|
||||
return true;
|
||||
});
|
||||
findPreference(PREFERENCE_LOGOUT).setOnPreferenceClickListener(preference -> {
|
||||
SynchronizationCredentials.clear();
|
||||
SynchronizationQueueSink.clearQueue(getContext());
|
||||
SynchronizationQueue.getInstance().clear();
|
||||
Snackbar.make(getView(), R.string.pref_synchronization_logout_toast, Snackbar.LENGTH_LONG).show();
|
||||
SynchronizationSettings.setSelectedSyncProvider(null);
|
||||
updateScreen();
|
||||
|
@ -168,12 +167,10 @@ public class SynchronizationPreferencesFragment extends AnimatedPreferenceFragme
|
|||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
if (convertView == null) {
|
||||
convertView = inflater.inflate(
|
||||
R.layout.alertdialog_sync_provider_chooser, null);
|
||||
|
||||
convertView = inflater.inflate(R.layout.alertdialog_sync_provider_chooser, null);
|
||||
holder = new ViewHolder();
|
||||
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
|
||||
holder.title = (TextView) convertView.findViewById(R.id.title);
|
||||
holder.icon = convertView.findViewById(R.id.icon);
|
||||
holder.title = convertView.findViewById(R.id.title);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
|
|
Loading…
Reference in New Issue