From d3e9ad810f58e3055c851e508ff833c56af896a8 Mon Sep 17 00:00:00 2001 From: Uwe Trottmann Date: Thu, 12 Mar 2020 08:37:48 +0100 Subject: [PATCH 1/2] Display first show or movie update failure in SyncStatusView. --- .../seriesguide/sync/TmdbSyncTest.java | 2 +- .../seriesguide/sync/TvdbSyncTest.java | 2 +- .../seriesguide/sync/SgSyncAdapter.java | 4 +- .../seriesguide/sync/SyncProgress.java | 45 ++++++++++++++----- .../battlelancer/seriesguide/sync/TmdbSync.kt | 10 +++-- .../seriesguide/sync/TvdbSync.java | 21 +++++++-- .../seriesguide/widgets/SyncStatusView.java | 13 +++--- 7 files changed, 70 insertions(+), 27 deletions(-) diff --git a/app/src/androidTest/java/com/battlelancer/seriesguide/sync/TmdbSyncTest.java b/app/src/androidTest/java/com/battlelancer/seriesguide/sync/TmdbSyncTest.java index 896ce7eeaf..a7cf21ddd4 100644 --- a/app/src/androidTest/java/com/battlelancer/seriesguide/sync/TmdbSyncTest.java +++ b/app/src/androidTest/java/com/battlelancer/seriesguide/sync/TmdbSyncTest.java @@ -146,7 +146,7 @@ private void insertMovie(int tmdbId, long releaseDateMs, Long lastUpdatedMs) { private void doUpdateAndAssertSuccess() { TmdbSync tmdbSync = new TmdbSync(ApplicationProvider.getApplicationContext(), tmdbConfigService, movieTools); - boolean successful = tmdbSync.updateMovies(); + boolean successful = tmdbSync.updateMovies(new SyncProgress()); assertThat(successful).isTrue(); } } diff --git a/app/src/androidTest/java/com/battlelancer/seriesguide/sync/TvdbSyncTest.java b/app/src/androidTest/java/com/battlelancer/seriesguide/sync/TvdbSyncTest.java index d4737601fd..e8141d36f3 100644 --- a/app/src/androidTest/java/com/battlelancer/seriesguide/sync/TvdbSyncTest.java +++ b/app/src/androidTest/java/com/battlelancer/seriesguide/sync/TvdbSyncTest.java @@ -87,6 +87,6 @@ public void test_deltaNoShows() { @Nullable private SgSyncAdapter.UpdateResult sync(TvdbSync tvdbSync) { return tvdbSync.sync(ApplicationProvider.getApplicationContext(), resolver, - tvdbToolsLazy, System.currentTimeMillis()); + tvdbToolsLazy, System.currentTimeMillis(), new SyncProgress()); } } diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/SgSyncAdapter.java b/app/src/main/java/com/battlelancer/seriesguide/sync/SgSyncAdapter.java index ce1bd6df34..e3b6251813 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/sync/SgSyncAdapter.java +++ b/app/src/main/java/com/battlelancer/seriesguide/sync/SgSyncAdapter.java @@ -114,7 +114,7 @@ public void onPerformSync(Account account, Bundle extras, String authority, SyncProgress progress = new SyncProgress(); progress.publish(SyncProgress.Step.TVDB); UpdateResult resultCode = tvdbSync.sync(getContext(), getContext().getContentResolver(), - tvdbTools, currentTime); + tvdbTools, currentTime, progress); if (resultCode == null || resultCode == UpdateResult.INCOMPLETE) { progress.recordError(); } @@ -137,7 +137,7 @@ public void onPerformSync(Account account, Bundle extras, String authority, progress.recordError(); } // update data of to be released movies - if (!tmdbSync.updateMovies()) { + if (!tmdbSync.updateMovies(progress)) { progress.recordError(); } Timber.d("Syncing: TMDB...DONE"); diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/SyncProgress.java b/app/src/main/java/com/battlelancer/seriesguide/sync/SyncProgress.java index bbcac839fe..e486b3b246 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/sync/SyncProgress.java +++ b/app/src/main/java/com/battlelancer/seriesguide/sync/SyncProgress.java @@ -33,14 +33,26 @@ public enum Step { } public static class SyncEvent { - /** If {@code null} syncing has finished. */ - @Nullable public final Step step; - /** Contains any steps that had an error. */ - @NonNull public final List stepsWithError; + @Nullable private final Step step; + @NonNull private final List stepsWithError; + @Nullable private String importantErrorOrNull; - public SyncEvent(@Nullable Step step, @NonNull List stepsWithError) { + SyncEvent( + @Nullable Step step, + @NonNull List stepsWithError, + @Nullable String importantErrorOrNull + ) { this.step = step; this.stepsWithError = stepsWithError; + this.importantErrorOrNull = importantErrorOrNull; + } + + public boolean isSyncing() { + return step != null; + } + + public boolean isFinishedWithError() { + return !stepsWithError.isEmpty(); } public String getDescription(Context context) { @@ -57,6 +69,10 @@ public String getDescription(Context context) { } } + if (importantErrorOrNull != null) { + statusText.append(" - ").append(importantErrorOrNull); + } + return statusText.toString(); } @@ -75,24 +91,33 @@ private Step getStepToDisplay() { @NonNull private final List stepsWithError = new LinkedList<>(); @Nullable private Step currentStep; + @Nullable private String importantErrorOrNull; - public void publish(Step step) { + void publish(Step step) { currentStep = step; - EventBus.getDefault().postSticky(new SyncEvent(step, stepsWithError)); + EventBus.getDefault().postSticky( + new SyncEvent(step, stepsWithError, importantErrorOrNull)); Timber.d("Syncing: %s...", step.name()); } /** * Record an error for the last published step. */ - public void recordError() { + void recordError() { if (currentStep != null) { stepsWithError.add(currentStep); Timber.d("Syncing: %s...FAILED", currentStep.name()); } } - public void publishFinished() { - EventBus.getDefault().postSticky(new SyncEvent(null, stepsWithError)); + void setImportantErrorIfNone(@NonNull String message) { + if (importantErrorOrNull == null) { + importantErrorOrNull = message; + } + } + + void publishFinished() { + EventBus.getDefault().postSticky( + new SyncEvent(null, stepsWithError, importantErrorOrNull)); } } diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/TmdbSync.kt b/app/src/main/java/com/battlelancer/seriesguide/sync/TmdbSync.kt index 2f9f95919b..5d61e4af8b 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/sync/TmdbSync.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/sync/TmdbSync.kt @@ -47,7 +47,7 @@ class TmdbSync internal constructor( * Regularly updates current and future movies (or those without a release date) with data from * themoviedb.org. All other movies are updated rarely. */ - fun updateMovies(): Boolean { + fun updateMovies(progress: SyncProgress): Boolean { val currentTimeMillis = System.currentTimeMillis() // update movies released 6 months ago or newer, should cover most edits val releasedAfter = currentTimeMillis - RELEASED_AFTER_DAYS @@ -78,8 +78,12 @@ class TmdbSync internal constructor( // update local database movieTools.updateMovie(details, movie.tmdbId) } else { - result = false // report failure if updating at least one fails - Timber.e("Failed to update movie with TMDB id %d", movie.tmdbId) + // Treat as failure if updating at least one fails. + result = false + + val message = "Failed to update movie with TMDB id ${movie.tmdbId}." + progress.setImportantErrorIfNone(message) + Timber.e(message) } } diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/TvdbSync.java b/app/src/main/java/com/battlelancer/seriesguide/sync/TvdbSync.java index e6cc8208b2..d7cf3066c8 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/sync/TvdbSync.java +++ b/app/src/main/java/com/battlelancer/seriesguide/sync/TvdbSync.java @@ -1,5 +1,6 @@ package com.battlelancer.seriesguide.sync; +import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; @@ -37,9 +38,15 @@ public TvdbSync(SyncType syncType, int singleShowTvdbId) { /** * Update shows based on the sync type. */ + @SuppressLint("TimberExceptionLogging") @Nullable - public SgSyncAdapter.UpdateResult sync(Context context, ContentResolver resolver, - Lazy tvdbTools, long currentTime) { + public SgSyncAdapter.UpdateResult sync( + Context context, + ContentResolver resolver, + Lazy tvdbTools, + long currentTime, + SyncProgress progress + ) { hasUpdatedShows = false; int[] showsToUpdate = getShowsToUpdate(context, resolver, currentTime); @@ -71,7 +78,15 @@ public SgSyncAdapter.UpdateResult sync(Context context, ContentResolver resolver } catch (TvdbException e) { // failed, continue with other shows resultCode = SgSyncAdapter.UpdateResult.INCOMPLETE; - Timber.e(e, "Failed to update show with TVDB id %s.", showTvdbId); + + String message = String + .format("Failed to update show with TVDB id %s.", showTvdbId); + if (e.itemDoesNotExist()) { + message += " It no longer exists."; + } + progress.setImportantErrorIfNone(message); + Timber.e(e, message); + Throwable cause = e.getCause(); if (cause != null && cause instanceof SocketTimeoutException) { consecutiveTimeouts++; diff --git a/app/src/main/java/com/battlelancer/seriesguide/widgets/SyncStatusView.java b/app/src/main/java/com/battlelancer/seriesguide/widgets/SyncStatusView.java index 43794e7ef5..9410929472 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/widgets/SyncStatusView.java +++ b/app/src/main/java/com/battlelancer/seriesguide/widgets/SyncStatusView.java @@ -43,23 +43,22 @@ protected void onFinishInflate() { * View#GONE}. */ public void setProgress(SyncProgress.SyncEvent event) { - if (event.step != null) { - // syncing + if (event.isSyncing()) { progressBar.setVisibility(View.VISIBLE); imageView.setVisibility(GONE); setVisibility(VISIBLE); } else { - // finished + // Finished. progressBar.setVisibility(View.GONE); - if (event.stepsWithError.size() > 0) { - // has errors + + if (event.isFinishedWithError()) { imageView.setVisibility(VISIBLE); setVisibility(VISIBLE); } else { - // successful + // Successful. imageView.setVisibility(GONE); setVisibility(GONE); - return; // no need to update status text + return; // No need to update status text. } } textView.setText(event.getDescription(getContext())); From f1e47f64a9d5e81d470e5e2539284bbf43165ab8 Mon Sep 17 00:00:00 2001 From: Uwe Trottmann Date: Thu, 12 Mar 2020 09:28:37 +0100 Subject: [PATCH 2/2] SyncStatusView: use view binding. --- .../seriesguide/widgets/SyncStatusView.java | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/battlelancer/seriesguide/widgets/SyncStatusView.java b/app/src/main/java/com/battlelancer/seriesguide/widgets/SyncStatusView.java index 9410929472..cf81b68216 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/widgets/SyncStatusView.java +++ b/app/src/main/java/com/battlelancer/seriesguide/widgets/SyncStatusView.java @@ -4,20 +4,13 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; -import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import butterknife.BindView; -import butterknife.ButterKnife; -import com.battlelancer.seriesguide.R; +import com.battlelancer.seriesguide.databinding.ViewSyncStatusBinding; import com.battlelancer.seriesguide.sync.SyncProgress; public class SyncStatusView extends LinearLayout { - @BindView(R.id.progressBarSyncStatus) ProgressBar progressBar; - @BindView(R.id.imageViewSyncStatus) ImageView imageView; - @BindView(R.id.textViewSyncStatus) TextView textView; + private ViewSyncStatusBinding binding; public SyncStatusView(Context context) { this(context, null); @@ -25,42 +18,40 @@ public SyncStatusView(Context context) { public SyncStatusView(Context context, AttributeSet attrs) { super(context, attrs); - setOrientation(HORIZONTAL); - LayoutInflater.from(context).inflate(R.layout.view_sync_status, this); + + binding = ViewSyncStatusBinding.inflate(LayoutInflater.from(context), this); } @Override protected void onFinishInflate() { super.onFinishInflate(); - ButterKnife.bind(this); - - imageView.setVisibility(GONE); + binding.imageViewSyncStatus.setVisibility(GONE); } /** - * If there is progress or a failure result, displays it. Otherwise sets the view {@link - * View#GONE}. + * If there is progress or a failure result, displays it. + * Otherwise sets the view {@link View#GONE}. */ public void setProgress(SyncProgress.SyncEvent event) { if (event.isSyncing()) { - progressBar.setVisibility(View.VISIBLE); - imageView.setVisibility(GONE); + binding.progressBarSyncStatus.setVisibility(View.VISIBLE); + binding.imageViewSyncStatus.setVisibility(GONE); setVisibility(VISIBLE); } else { // Finished. - progressBar.setVisibility(View.GONE); + binding.progressBarSyncStatus.setVisibility(View.GONE); if (event.isFinishedWithError()) { - imageView.setVisibility(VISIBLE); + binding.imageViewSyncStatus.setVisibility(VISIBLE); setVisibility(VISIBLE); } else { // Successful. - imageView.setVisibility(GONE); + binding.imageViewSyncStatus.setVisibility(GONE); setVisibility(GONE); return; // No need to update status text. } } - textView.setText(event.getDescription(getContext())); + binding.textViewSyncStatus.setText(event.getDescription(getContext())); } }