Report sync progress

This commit is contained in:
ByteHamster 2020-03-28 13:44:30 +01:00
parent a4409c7c5c
commit 67de5de8c4
5 changed files with 109 additions and 56 deletions

View File

@ -10,10 +10,15 @@ import android.text.format.DateUtils;
import android.widget.Toast;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.core.event.SyncServiceEvent;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.dialog.AuthenticationDialog;
import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate";
@ -34,28 +39,27 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
public void onStart() {
super.onStart();
((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.gpodnet_main_label);
}
@Override
public void onResume() {
super.onResume();
GpodnetPreferences.registerOnSharedPreferenceChangeListener(gpoddernetListener);
updateGpodnetPreferenceScreen();
EventBus.getDefault().register(this);
}
@Override
public void onPause() {
super.onPause();
GpodnetPreferences.unregisterOnSharedPreferenceChangeListener(gpoddernetListener);
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle("");
}
private final SharedPreferences.OnSharedPreferenceChangeListener gpoddernetListener =
(sharedPreferences, key) -> {
//if (GpodnetPreferences.PREF_LAST_SYNC_ATTEMPT_TIMESTAMP.equals(key)) {
// updateLastGpodnetSyncReport(GpodnetPreferences.getLastSyncAttemptResult(),
// GpodnetPreferences.getLastSyncAttemptTimestamp());
//}
};
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void syncStatusChanged(SyncServiceEvent event) {
if (event.getMessageResId() == R.string.sync_status_error
|| event.getMessageResId() == R.string.sync_status_success) {
updateLastGpodnetSyncReport(SyncService.isLastSyncSuccessful(getContext()),
SyncService.getLastSyncAttempt(getContext()));
} else {
((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(event.getMessageResId());
}
}
private void setupGpodderScreen() {
final Activity activity = getActivity();
@ -76,12 +80,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
});
findPreference(PREF_GPODNET_SYNC).setOnPreferenceClickListener(preference -> {
SyncService.sync(getActivity().getApplicationContext());
Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started, Toast.LENGTH_SHORT).show();
return true;
});
findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> {
SyncService.fullSync(getContext());
Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started, Toast.LENGTH_SHORT).show();
return true;
});
findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(preference -> {
@ -106,13 +108,13 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setEnabled(loggedIn);
findPreference(PREF_GPODNET_LOGOUT).setEnabled(loggedIn);
findPreference(PREF_GPODNET_NOTIFICATIONS).setEnabled(loggedIn);
if(loggedIn) {
if (loggedIn) {
String format = getActivity().getString(R.string.pref_gpodnet_login_status);
String summary = String.format(format, GpodnetPreferences.getUsername(),
GpodnetPreferences.getDeviceID());
findPreference(PREF_GPODNET_LOGOUT).setSummary(Html.fromHtml(summary));
//updateLastGpodnetSyncReport(GpodnetPreferences.getLastSyncAttemptResult(),
// GpodnetPreferences.getLastSyncAttemptTimestamp());
updateLastGpodnetSyncReport(SyncService.isLastSyncSuccessful(getContext()),
SyncService.getLastSyncAttempt(getContext()));
} else {
findPreference(PREF_GPODNET_LOGOUT).setSummary(null);
updateLastGpodnetSyncReport(false, 0);
@ -121,20 +123,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
}
private void updateLastGpodnetSyncReport(boolean successful, long lastTime) {
Preference sync = findPreference(PREF_GPODNET_SYNC);
if (lastTime != 0) {
sync.setSummary(getActivity().getString(R.string.pref_gpodnet_sync_changes_sum) + "\n" +
getActivity().getString(R.string.pref_gpodnet_sync_sum_last_sync_line,
getActivity().getString(successful ?
R.string.gpodnetsync_pref_report_successful :
R.string.gpodnetsync_pref_report_failed),
DateUtils.getRelativeDateTimeString(getActivity(),
lastTime,
DateUtils.MINUTE_IN_MILLIS,
DateUtils.WEEK_IN_MILLIS,
DateUtils.FORMAT_SHOW_TIME)));
} else {
sync.setSummary(getActivity().getString(R.string.pref_gpodnet_sync_changes_sum));
}
String status = String.format("%1$s (%2$s)", getString(successful
? R.string.gpodnetsync_pref_report_successful : R.string.gpodnetsync_pref_report_failed),
DateUtils.getRelativeDateTimeString(getContext(),
lastTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME));
((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(status);
}
}

View File

@ -0,0 +1,13 @@
package de.danoeh.antennapod.core.event;
public class SyncServiceEvent {
private final int messageResId;
public SyncServiceEvent(int messageResId) {
this.messageResId = messageResId;
}
public int getMessageResId() {
return messageResId;
}
}

View File

@ -19,6 +19,7 @@ import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.SyncServiceEvent;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@ -39,6 +40,7 @@ import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
import de.danoeh.antennapod.core.util.URLChecker;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONException;
@ -55,6 +57,7 @@ public class SyncService extends Worker {
private static final String PREF_QUEUED_FEEDS_REMOVED = "sync_removed";
private static final String PREF_QUEUED_EPISODE_ACTIONS = "sync_queued_episode_actions";
private static final String PREF_LAST_SYNC_ATTEMPT_TIMESTAMP = "last_sync_attempt_timestamp";
private static final String PREF_LAST_SYNC_ATTEMPT_SUCCESS = "last_sync_attempt_success";
private static final String TAG = "SyncService";
private static final String WORK_ID_SYNC = "SyncServiceWorkId";
private static final Object lock = new Object();
@ -69,20 +72,26 @@ public class SyncService extends Worker {
@NonNull
public Result doWork() {
syncServiceImpl = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetService.DEFAULT_BASE_HOST);
SharedPreferences.Editor prefs = getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit();
prefs.putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, System.currentTimeMillis()).apply();
try {
syncServiceImpl.login();
EventBus.getDefault().post(new SyncServiceEvent(R.string.sync_status_subscriptions));
syncSubscriptions();
EventBus.getDefault().post(new SyncServiceEvent(R.string.sync_status_episodes));
syncEpisodeActions();
syncServiceImpl.logout();
clearErrorNotifications();
EventBus.getDefault().post(new SyncServiceEvent(R.string.sync_status_success));
prefs.putBoolean(PREF_LAST_SYNC_ATTEMPT_SUCCESS, true).apply();
return Result.success();
} catch (SyncServiceException e) {
EventBus.getDefault().post(new SyncServiceEvent(R.string.sync_status_error));
prefs.putBoolean(PREF_LAST_SYNC_ATTEMPT_SUCCESS, false).apply();
e.printStackTrace();
updateErrorNotification(e);
return Result.failure();
} finally {
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
.putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, System.currentTimeMillis()).apply();
}
}
@ -145,18 +154,9 @@ public class SyncService extends Worker {
}
public static void sync(Context context) {
Constraints.Builder constraints = new Constraints.Builder();
if (UserPreferences.isAllowMobileFeedRefresh()) {
constraints.setRequiredNetworkType(NetworkType.CONNECTED);
} else {
constraints.setRequiredNetworkType(NetworkType.UNMETERED);
}
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(SyncService.class)
.setConstraints(constraints.build())
.setInitialDelay(5L, TimeUnit.SECONDS) // Give it some time, so other actions can be queued
.build();
OneTimeWorkRequest workRequest = getWorkRequest().build();
WorkManager.getInstance().enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
EventBus.getDefault().post(new SyncServiceEvent(R.string.sync_status_started));
}
public static void fullSync(Context context) {
@ -167,9 +167,35 @@ public class SyncService extends Worker {
.putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, 0)
.apply();
}
sync(context);
OneTimeWorkRequest workRequest = getWorkRequest()
.setInitialDelay(0L, TimeUnit.SECONDS)
.build();
WorkManager.getInstance().enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
EventBus.getDefault().post(new SyncServiceEvent(R.string.sync_status_started));
}
private static OneTimeWorkRequest.Builder getWorkRequest() {
Constraints.Builder constraints = new Constraints.Builder();
if (UserPreferences.isAllowMobileFeedRefresh()) {
constraints.setRequiredNetworkType(NetworkType.CONNECTED);
} else {
constraints.setRequiredNetworkType(NetworkType.UNMETERED);
}
return new OneTimeWorkRequest.Builder(SyncService.class)
.setConstraints(constraints.build())
.setInitialDelay(5L, TimeUnit.SECONDS); // Give it some time, so other actions can be queued
}
public static boolean isLastSyncSuccessful(Context context) {
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.getBoolean(PREF_LAST_SYNC_ATTEMPT_SUCCESS, false);
}
public static long getLastSyncAttempt(Context context) {
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.getLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, 0);
}
private List<EpisodeAction> getQueuedEpisodeActions() {
ArrayList<EpisodeAction> actions = new ArrayList<>();
@ -281,6 +307,7 @@ public class SyncService extends Worker {
// upload local actions
List<EpisodeAction> queuedEpisodeActions = getQueuedEpisodeActions();
if (lastSync == 0) {
EventBus.getDefault().post(new SyncServiceEvent(R.string.sync_status_upload_played));
List<FeedItem> readItems = DBReader.getPlayedItems();
Log.d(TAG, "First sync. Upload state for all " + readItems.size() + " played episodes");
for (FeedItem item : readItems) {
@ -300,7 +327,8 @@ public class SyncService extends Worker {
}
if (queuedEpisodeActions.size() > 0) {
synchronized (lock) {
Log.d(TAG, "Uploading actions: " + StringUtils.join(queuedEpisodeActions, ", "));
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);

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.sync.gpoddernet;
import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
@ -42,8 +43,10 @@ import java.util.List;
* Communicates with the gpodder.net service.
*/
public class GpodnetService implements ISyncService {
public static final String TAG = "GpodnetService";
public static final String DEFAULT_BASE_HOST = "gpodder.net";
private static final String BASE_SCHEME = "https";
private static final int UPLOAD_BULK_SIZE = 30;
private static final MediaType TEXT = MediaType.parse("plain/text; charset=utf-8");
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private final String baseHost;
@ -415,12 +418,24 @@ public class GpodnetService implements ISyncService {
@Override
public UploadChangesResponse uploadEpisodeActions(List<EpisodeAction> episodeActions) throws SyncServiceException {
requireLoggedIn();
UploadChangesResponse response = null;
for (int i = 0; i < episodeActions.size(); i += UPLOAD_BULK_SIZE) {
response = uploadEpisodeActionsPartial(episodeActions,
i, Math.min(episodeActions.size(), i + UPLOAD_BULK_SIZE));
}
return response;
}
private UploadChangesResponse uploadEpisodeActionsPartial(List<EpisodeAction> episodeActions, int from, int to)
throws SyncServiceException {
try {
Log.d(TAG, "Uploading partial actions " + from + " to " + to + " of " + episodeActions.size());
URL url = new URI(BASE_SCHEME, baseHost, String.format(
"/api/2/episodes/%s.json", username), null).toURL();
final JSONArray list = new JSONArray();
for (EpisodeAction episodeAction : episodeActions) {
for (int i = from; i < to; i++) {
EpisodeAction episodeAction = episodeActions.get(i);
JSONObject obj = episodeAction.writeToJSONObject();
if (obj != null) {
obj.put("device", GpodnetPreferences.getDeviceID());
@ -437,7 +452,6 @@ public class GpodnetService implements ISyncService {
e.printStackTrace();
throw new SyncServiceException(e);
}
}
/**

View File

@ -441,8 +441,6 @@
<string name="pref_gpodnet_sync_changes_sum">Sync subscription and episode state changes with gpodder.net.</string>
<string name="pref_gpodnet_full_sync_title">Full sync now</string>
<string name="pref_gpodnet_full_sync_sum">Sync all subscriptions and episode states with gpodder.net.</string>
<string name="pref_gpodnet_sync_sum_last_sync_line">Last sync attempt: %1$s (%2$s)</string>
<string name="pref_gpodnet_sync_started">Sync started</string>
<string name="pref_gpodnet_login_status"><![CDATA[Logged in as <i>%1$s</i> with device <i>%2$s</i>]]></string>
<string name="pref_gpodnet_notifications_title">Show sync error notifications</string>
<string name="pref_gpodnet_notifications_sum">This setting does not apply to authentication errors.</string>
@ -536,6 +534,14 @@
<string name="search_label">Search</string>
<string name="no_results_for_query">No results were found for \"%1$s\"</string>
<!-- Synchronization -->
<string name="sync_status_started">Sync started</string>
<string name="sync_status_episodes">Synchronizing episodes…</string>
<string name="sync_status_upload_played">Uploading played status…</string>
<string name="sync_status_subscriptions">Synchronizing subscriptions…</string>
<string name="sync_status_success">Synchronization successful</string>
<string name="sync_status_error">Synchronization failed</string>
<!-- import and export -->
<string name="import_export_summary">Move subscriptions and queue to another device</string>
<string name="database">Database</string>