diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt index 142ed8505ad6543e95bd295525767fd3b4cc5841..526ad2c4df7da0d18a6769f40acf12c5968e426d 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt @@ -125,7 +125,7 @@ open class LocalAddressBook( } override val tag: String - get() = "contacts-${account.name}" + get() = account.name override val title = account.name!! diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt index bb6e29b5395821b5f53563abb3feacd142463be8..bf22df9551c2debf42bd68d0c733a9c052c1e220 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt @@ -86,7 +86,7 @@ class LocalCalendar private constructor( } override val tag: String - get() = "events-${account.name}-$id" + get() = account.name override val title: String get() = displayName ?: id.toString() diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalJtxCollection.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalJtxCollection.kt index 53760c46f284044f2cf0140e995af64022f624ce..3a35f14b9c12ea7fec8d67d680c6f86dafc09f70 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalJtxCollection.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalJtxCollection.kt @@ -43,7 +43,7 @@ class LocalJtxCollection(account: Account, client: ContentProviderClient, id: Lo } override val tag: String - get() = "jtx-${account.name}-$id" + get() = account.name override val title: String get() = displayname ?: id.toString() override var lastSyncState: SyncState? diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.kt index 20ea115a1e207f5f5a6325f04409249b5fcb8056..911ecbf64e7747c3e56d1198dc500e3f6435fc4d 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.kt @@ -65,7 +65,7 @@ class LocalTaskList private constructor( } override val tag: String - get() = "tasks-${account.name}-$id" + get() = account.name override val title: String get() = name ?: id.toString() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloCalendarsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloCalendarsSyncAdapterService.kt index 26e1a9f044d7aefc59884abc74068eabf0e48498..60ba1088c09f0bd4dc73f347abf512a713ae3a3c 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloCalendarsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloCalendarsSyncAdapterService.kt @@ -121,16 +121,16 @@ class EeloCalendarsSyncAdapterService : SyncAdapterService() { ) object : AsyncTask() { override fun doInBackground(vararg params: Void): Void? { - it.performSync() + it.performSyncWithRetry() return null } }.execute() } } else { - it.performSync() + it.performSyncWithRetry() } } else { - it.performSync() + it.performSyncWithRetry() } } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloContactsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloContactsSyncAdapterService.kt index b5c0419d8984679be71d63cfc670c859806696cb..52ca8b17f92b79da8b119ff54157c028ef1f7d53 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloContactsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloContactsSyncAdapterService.kt @@ -83,7 +83,7 @@ class EeloContactsSyncAdapterService: SyncAdapterService() { Logger.log.info("Taking settings from: ${addressBook.mainAccount}") ContactsSyncManager(context, account, accountSettings, httpClient.value, extras, authority, syncResult, provider, addressBook).let { - it.performSync() + it.performSyncWithRetry() } } catch(e: Exception) { Logger.log.log(Level.SEVERE, "Couldn't sync contacts", e) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloTasksSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloTasksSyncAdapterService.kt index 2dbbd4c8016eb7b36723a8b63b8fbf88ac89b946..7afed9507795a057588bbc6f8dc352bafaf58d34 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloTasksSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloTasksSyncAdapterService.kt @@ -136,16 +136,16 @@ class EeloTasksSyncAdapterService : SyncAdapterService() { ) object : AsyncTask() { override fun doInBackground(vararg params: Void): Void? { - it.performSync() + it.performSyncWithRetry() return null } }.execute() }) } else { - it.performSync() + it.performSyncWithRetry() } } else { - it.performSync() + it.performSyncWithRetry() } } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleCalendarsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleCalendarsSyncAdapterService.kt index 643a649cd1ce7f9ab229cbaa717085658f14aef0..95c8d5fcd74a15e5ef6d793176336a998ae2a8f2 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleCalendarsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleCalendarsSyncAdapterService.kt @@ -124,16 +124,16 @@ class GoogleCalendarsSyncAdapterService : SyncAdapterService() { ) object : AsyncTask() { override fun doInBackground(vararg params: Void): Void? { - it.performSync() + it.performSyncWithRetry() return null } }.execute() }) } else { - it.performSync() + it.performSyncWithRetry() } } else { - it.performSync() + it.performSyncWithRetry() } } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleContactsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleContactsSyncAdapterService.kt index fd8c3ade1460f4d99a9914f5fb868f402044a205..c15def3b6230ff8cc1d16794efab0a0a937cfb27 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleContactsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleContactsSyncAdapterService.kt @@ -107,7 +107,7 @@ class GoogleContactsSyncAdapterService : SyncAdapterService() { syncResult, provider, addressBook - ).performSync() + ).performSyncWithRetry() } catch (e: Exception) { Logger.log.log(Level.SEVERE, "Couldn't sync contacts", e) } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleTasksSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleTasksSyncAdapterService.kt index 8a3d0713f4a217f2dcce91aee947fbbe824ab282..66e180fb8a0768e0388e7fb668e8687f92ac39d7 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleTasksSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleTasksSyncAdapterService.kt @@ -135,16 +135,16 @@ class GoogleTasksSyncAdapterService : SyncAdapterService() { ) object : AsyncTask() { override fun doInBackground(vararg params: Void): Void? { - it.performSync() + it.performSyncWithRetry() return null } }.execute() }) } else { - it.performSync() + it.performSyncWithRetry() } } else { - it.performSync() + it.performSyncWithRetry() } } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt index 34b4f47104b36e6410b4ee7cd2342ce1d4786985..2213d229f0c80f31474ab7b5d69fc35a5bd69d23 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt @@ -31,6 +31,7 @@ import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.* import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.DebugInfoActivity +import at.bitfire.davdroid.ui.NetworkUtils import at.bitfire.davdroid.ui.NotificationUtils import at.bitfire.davdroid.ui.account.SettingsActivity import at.bitfire.ical4android.CalendarStorageException @@ -51,6 +52,7 @@ import org.dmfs.tasks.contract.TaskContract import java.io.IOException import java.io.InterruptedIOException import java.lang.ref.WeakReference +import java.net.ConnectException import java.net.HttpURLConnection import java.security.cert.CertificateException import java.util.* @@ -130,8 +132,21 @@ abstract class SyncManager, out CollectionType: L val workDispatcher = getWorkDispatcher() + /** + * Call performSync with default retry values + */ + fun performSyncWithRetry() { + performSync(5, 8, 21) + } - fun performSync() { + /** + * Perform sync operation. + * On unhandled exceptions, retry following fibonnacci sequence (if user pass valid retry times. + * @param retryAfter optional param, in seconds. On unhandled exception `onSync`, if value > 0 && <= maxRetryTime, wait until this value & retry + * @param secondRetryAfter optional param, in seconds. Used to calculate fibonnacci sequence for rety on unhandled exception + * @param maxRetryTime optional param, in seconds. On unhandled exception, max time the method should retry. + */ + fun performSync(retryAfter: Int = Int.MIN_VALUE, secondRetryAfter: Int = Int.MIN_VALUE, maxRetryTime: Int = Int.MIN_VALUE) { // dismiss previous error notifications notificationManager.cancel(notificationTag, NotificationUtils.NOTIFY_SYNC_ERROR) @@ -252,7 +267,6 @@ abstract class SyncManager, out CollectionType: L } else Logger.log.info("Remote collection didn't change, no reason to sync") - }, { e, local, remote -> when (e) { // sync was cancelled or account has been removed: re-throw to SyncAdapterService @@ -279,13 +293,50 @@ abstract class SyncManager, out CollectionType: L } } + // sometimes sync is scheduled & kicked just the time network becomes available but not ready; in this case it throws ConnectException. + // In this case, we don't want to show notification to users. + is ConnectException -> { + Logger.log.severe("Failed to connect server for sync $e") + return@unwrapExceptions + } + // all others - else -> + else -> { + // sometimes sync is kicked in when no network is not available. + // In this case, we don't want to show notification to users. + if (!NetworkUtils.isConnectedToNetwork(context)) { + Logger.log.warning("No internet connection. Skipping sync operation") + return@unwrapExceptions + } + + if (retrySyncOperation(retryAfter, secondRetryAfter, maxRetryTime, e)) { + return@unwrapExceptions + } + notifyException(e, local, remote) + } + } }) } + private fun retrySyncOperation(retryAfter: Int, secondRetryAfter: Int, maxRetryTime: Int, e: Throwable): Boolean { + if (retryAfter > 0 && secondRetryAfter > 0 && retryAfter <= maxRetryTime) { + try { + Logger.log.severe("Faced unhandled exception $e, Will retry sync") + Logger.log.info("Retry sync after $retryAfter seconds") + Thread.sleep(retryAfter * 1000L) + performSync(secondRetryAfter, retryAfter + secondRetryAfter, maxRetryTime) + } catch (ex: Throwable) { + Logger.log.warning("Retry sync interrupted. $ex") + } + + return true + } + + return false + } + /** * Prepares synchronization. Sets the lateinit properties [collectionURL] and [davCollection]. @@ -739,10 +790,10 @@ abstract class SyncManager, out CollectionType: L val contentIntent: Intent var viewItemAction: NotificationCompat.Action? = null - if ((account.type == context.getString(R.string.account_type) || - account.type == context.getString(R.string.eelo_account_type) || - account.type == context.getString(R.string.google_account_type)) && - (e is UnauthorizedException || e is NotFoundException)) { + + if ((account.type == context.getString(R.string.account_type) || account.type == context.getString(R.string.eelo_account_type) || account.type == context.getString(R.string.google_account_type)) + && (e is UnauthorizedException || e is NotFoundException)) { + contentIntent = Intent(context, SettingsActivity::class.java) contentIntent.putExtra(SettingsActivity.EXTRA_ACCOUNT, if (authority == ContactsContract.AUTHORITY) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/NetworkUtils.kt b/app/src/main/java/at/bitfire/davdroid/ui/NetworkUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..f36e74e4b5b46e1525bb35a7da10001b4c215bc3 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/NetworkUtils.kt @@ -0,0 +1,29 @@ +/* + * Copyright MURENA SAS 2022 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package at.bitfire.davdroid.ui + +import android.content.Context +import android.net.ConnectivityManager + +object NetworkUtils { + + fun isConnectedToNetwork(context: Context): Boolean { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val nw = cm.activeNetwork ?: return false + return cm.getNetworkCapabilities(nw) != null + } +} \ No newline at end of file