Skip to content

Commit 68647e3

Browse files
#1303 Hide exceptions related to temporary network issues when not pulling manually
Signed-off-by: Stefan Niedermann <info@niedermann.it>
1 parent 2579a54 commit 68647e3

3 files changed

Lines changed: 107 additions & 23 deletions

File tree

app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,14 @@ protected void onCreate(Bundle savedInstanceState) {
212212

213213
mainViewModel.hasMultipleAccountsConfigured().observe(this, hasMultipleAccountsConfigured -> canMoveNoteToAnotherAccounts = hasMultipleAccountsConfigured);
214214
mainViewModel.getSyncStatus().observe(this, syncStatus -> swipeRefreshLayout.setRefreshing(syncStatus));
215-
mainViewModel.getSyncErrors().observe(this, exceptions -> BrandedSnackbar.make(coordinatorLayout, R.string.error_synchronization, Snackbar.LENGTH_LONG)
216-
.setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(exceptions)
217-
.show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
218-
.show());
215+
mainViewModel.getSyncErrors().observe(this, exceptions -> {
216+
if (mainViewModel.containsNonInfrastructureRelatedItems(exceptions)) {
217+
BrandedSnackbar.make(coordinatorLayout, R.string.error_synchronization, Snackbar.LENGTH_LONG)
218+
.setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(exceptions)
219+
.show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
220+
.show();
221+
}
222+
});
219223
mainViewModel.getSelectedCategory().observe(this, (selectedCategory) -> {
220224
binding.activityNotesListView.emptyContentView.getRoot().setVisibility(GONE);
221225
adapter.setShowCategory(selectedCategory.getType() == RECENT || selectedCategory.getType() == FAVORITES);
@@ -306,7 +310,7 @@ protected void onCreate(Bundle savedInstanceState) {
306310
.apply(RequestOptions.circleCropTransform())
307311
.into(activityBinding.launchAccountSwitcher);
308312

309-
mainViewModel.synchronizeNotes(nextAccount, new IResponseCallback<Void>() {
313+
mainViewModel.synchronizeNotes(nextAccount, new IResponseCallback<>() {
310314
@Override
311315
public void onSuccess(Void v) {
312316
Log.d(TAG, "Successfully synchronized notes for " + nextAccount.getAccountName());

app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
package it.niedermann.owncloud.notes.main;
22

3+
import static androidx.lifecycle.Transformations.distinctUntilChanged;
4+
import static androidx.lifecycle.Transformations.map;
5+
import static androidx.lifecycle.Transformations.switchMap;
6+
import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
7+
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_RECENT;
8+
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_STARRED;
9+
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByCategory;
10+
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByInitials;
11+
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByTime;
12+
import static it.niedermann.owncloud.notes.shared.model.CategorySortingMethod.SORT_MODIFIED_DESC;
13+
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.DEFAULT_CATEGORY;
14+
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.FAVORITES;
15+
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.RECENT;
16+
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.UNCATEGORIZED;
17+
import static it.niedermann.owncloud.notes.shared.util.DisplayUtils.convertToCategoryNavigationItem;
18+
319
import android.accounts.NetworkErrorException;
420
import android.app.Application;
521
import android.content.Context;
@@ -19,13 +35,14 @@
1935
import com.nextcloud.android.sso.AccountImporter;
2036
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
2137
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
38+
import com.nextcloud.android.sso.exceptions.UnknownErrorException;
2239
import com.nextcloud.android.sso.helper.SingleAccountHelper;
23-
import com.nextcloud.android.sso.model.SingleSignOnAccount;
2440

2541
import java.util.ArrayList;
2642
import java.util.Collection;
2743
import java.util.Collections;
2844
import java.util.List;
45+
import java.util.Locale;
2946
import java.util.concurrent.ExecutorService;
3047
import java.util.concurrent.Executors;
3148
import java.util.stream.Collectors;
@@ -50,22 +67,6 @@
5067
import it.niedermann.owncloud.notes.shared.model.Item;
5168
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
5269

53-
import static androidx.lifecycle.Transformations.distinctUntilChanged;
54-
import static androidx.lifecycle.Transformations.map;
55-
import static androidx.lifecycle.Transformations.switchMap;
56-
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_RECENT;
57-
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_STARRED;
58-
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByCategory;
59-
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByInitials;
60-
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByTime;
61-
import static it.niedermann.owncloud.notes.shared.model.CategorySortingMethod.SORT_MODIFIED_DESC;
62-
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.DEFAULT_CATEGORY;
63-
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.FAVORITES;
64-
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.RECENT;
65-
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.UNCATEGORIZED;
66-
import static it.niedermann.owncloud.notes.shared.util.DisplayUtils.convertToCategoryNavigationItem;
67-
import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
68-
6970
public class MainViewModel extends AndroidViewModel {
7071

7172
private static final String TAG = MainViewModel.class.getSimpleName();
@@ -351,7 +352,7 @@ private static List<NavigationItem> fromCategoriesWithNotesCount(@NonNull Contex
351352
lastSecondaryCategory.icon = NavigationAdapter.ICON_SUB_MULTIPLE;
352353
} else if (belongsToLastPrimaryCategory) {
353354
if (isCategoryOpen) {
354-
if(currentSecondaryCategory == null) {
355+
if (currentSecondaryCategory == null) {
355356
throw new IllegalStateException("Current secondary category is null. Last primary category: " + lastPrimaryCategory);
356357
}
357358
item.label = currentSecondaryCategory;
@@ -621,4 +622,45 @@ public String collectNoteContents(@NonNull List<Long> noteIds) {
621622
}
622623
return noteContents.toString();
623624
}
625+
626+
/**
627+
* @return <code>true</code> if {@param exceptions} contains at least one exception which is not caused by flaky infrastructure.
628+
* @see <a href="https://github.com/stefan-niedermann/nextcloud-notes/issues/1303">Issue #1303</a>
629+
*/
630+
public boolean containsNonInfrastructureRelatedItems(@Nullable Collection<Throwable> exceptions) {
631+
if (exceptions == null || exceptions.isEmpty()) {
632+
return false;
633+
}
634+
635+
return exceptions.stream().anyMatch(e -> !exceptionIsInfrastructureRelated(e));
636+
}
637+
638+
private boolean exceptionIsInfrastructureRelated(@Nullable Throwable e) {
639+
if (e == null) {
640+
return false;
641+
}
642+
643+
if (e instanceof RuntimeException || e instanceof UnknownErrorException) {
644+
if (isSoftwareCausedConnectionAbort(e.getMessage()) || isNetworkUnreachable(e.getMessage())) {
645+
return true;
646+
}
647+
}
648+
649+
return exceptionIsInfrastructureRelated(e.getCause());
650+
}
651+
652+
private boolean isSoftwareCausedConnectionAbort(@Nullable String input) {
653+
if (input == null) {
654+
return false;
655+
}
656+
return input.toLowerCase(Locale.ROOT).contains("software caused connection abort");
657+
}
658+
659+
private boolean isNetworkUnreachable(@Nullable String input) {
660+
if (input == null) {
661+
return false;
662+
}
663+
final var lower = input.toLowerCase(Locale.ROOT);
664+
return lower.contains("failed to connect") && lower.contains("network is unreachable");
665+
}
624666
}

app/src/test/java/it/niedermann/owncloud/notes/main/MainViewModelTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package it.niedermann.owncloud.notes.main;
22

33
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
45
import static org.junit.Assert.assertNotNull;
6+
import static org.junit.Assert.assertTrue;
57
import static org.mockito.Mockito.mock;
68

79
import android.content.Context;
@@ -11,6 +13,8 @@
1113
import androidx.lifecycle.SavedStateHandle;
1214
import androidx.test.core.app.ApplicationProvider;
1315

16+
import com.nextcloud.android.sso.exceptions.UnknownErrorException;
17+
1418
import org.junit.Before;
1519
import org.junit.Rule;
1620
import org.junit.Test;
@@ -121,6 +125,40 @@ public void fromCategoriesWithNotesCount_only_deep_category_without_favorites()
121125
assertEquals("Bar", navigationItems.get(2).label);
122126
}
123127

128+
@Test
129+
public void containsNonInfrastructureRelatedItems() {
130+
//noinspection ConstantConditions
131+
final var vm = new MainViewModel(ApplicationProvider.getApplicationContext(), null);
132+
assertFalse(vm.containsNonInfrastructureRelatedItems(null));
133+
assertFalse(vm.containsNonInfrastructureRelatedItems(Collections.emptyList()));
134+
135+
assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new UnknownErrorException("Software caused connection abort"))));
136+
assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new UnknownErrorException("failed to connect to example.com (port 443) from /:: (port 39885) after 5000ms: connect failed: ENETUNREACH (Network is unreachable)"))));
137+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new UnknownErrorException("Foo bar"))));
138+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"))));
139+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"), new UnknownErrorException("Software caused connection abort"))));
140+
141+
assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Software caused connection abort"))));
142+
assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("failed to connect to example.com (port 443) from /:: (port 39885) after 5000ms: connect failed: ENETUNREACH (Network is unreachable)"))));
143+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo bar"))));
144+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"))));
145+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"), new RuntimeException("Software caused connection abort"))));
146+
147+
assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException(new UnknownErrorException("Software caused connection abort")))));
148+
assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException(new UnknownErrorException("failed to connect to example.com (port 443) from /:: (port 39885) after 5000ms: connect failed: ENETUNREACH (Network is unreachable)")))));
149+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException(new UnknownErrorException("Foo bar")))));
150+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"))));
151+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"), new RuntimeException(new UnknownErrorException("Software caused connection abort")))));
152+
153+
assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo", new UnknownErrorException("Software caused connection abort")))));
154+
assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo", new UnknownErrorException("failed to connect to example.com (port 443) from /:: (port 39885) after 5000ms: connect failed: ENETUNREACH (Network is unreachable)")))));
155+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo", new UnknownErrorException("Foo bar")))));
156+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"))));
157+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"), new RuntimeException("Foo", new UnknownErrorException("Software caused connection abort")))));
158+
159+
assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo", new Exception("Software caused connection abort")))));
160+
}
161+
124162
private static List<CategoryWithNotesCount> getSaneCategoriesWithNotesCount() {
125163
return List.of(
126164
new CategoryWithNotesCount(1, "Foo", 13),

0 commit comments

Comments
 (0)