[Sync] Replace extras Bundle by explicit arguments (#1475)

* Remove extras parameter from sync managers

* Refactor sync worker parameters and improve documentation

* Adapt tests

* Rename resyncType to resync in syncers, adapt KDoc

* Remove legacy sync result comment
This commit is contained in:
Ricki Hirner
2025-05-19 15:37:50 +02:00
committed by GitHub
parent 7bf9172bdc
commit eeb94d4039
20 changed files with 186 additions and 176 deletions

View File

@@ -93,12 +93,12 @@ class JtxSyncManagerTest {
localJtxCollection = localJtxCollectionStore.create(provider, dbCollection)!!
syncManager = jtxSyncManagerFactory.jtxSyncManager(
account = account,
extras = arrayOf(),
httpClient = httpClientBuilder.build(),
authority = JtxContract.AUTHORITY,
syncResult = SyncResult(),
localCollection = localJtxCollection,
collection = dbCollection
collection = dbCollection,
resync = null
)
}

View File

@@ -494,7 +494,6 @@ class SyncManagerTest {
}
) = syncManagerFactory.create(
account,
arrayOf(),
"TestAuthority",
httpClientBuilder.build(),
syncResult,

View File

@@ -37,7 +37,7 @@ class SyncerTest {
@SpyK
@InjectMockKs
var syncer = TestSyncer(mockk(relaxed = true), emptyArray(), SyncResult(), dataStore)
var syncer = TestSyncer(mockk(relaxed = true), null, SyncResult(), dataStore)
@Test
@@ -155,12 +155,12 @@ class SyncerTest {
// Test helpers
class TestSyncer (
class TestSyncer(
account: Account,
extras: Array<String>,
resyncType: ResyncType?,
syncResult: SyncResult,
theDataStore: LocalTestStore
) : Syncer<LocalTestStore, LocalTestCollection>(account, extras, syncResult) {
) : Syncer<LocalTestStore, LocalTestCollection>(account, resyncType, syncResult) {
override val dataStore: LocalTestStore =
theDataStore

View File

@@ -26,7 +26,6 @@ import org.junit.Assert.assertEquals
class TestSyncManager @AssistedInject constructor(
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted authority: String,
@Assisted httpClient: HttpClient,
@Assisted syncResult: SyncResult,
@@ -36,11 +35,11 @@ class TestSyncManager @AssistedInject constructor(
): SyncManager<LocalTestResource, LocalTestCollection, DavCollection>(
account,
httpClient,
extras,
authority,
syncResult,
localCollection,
collection,
resync = null,
syncDispatcher
) {
@@ -48,7 +47,6 @@ class TestSyncManager @AssistedInject constructor(
interface Factory {
fun create(
account: Account,
extras: Array<String>,
authority: String,
httpClient: HttpClient,
syncResult: SyncResult,

View File

@@ -13,7 +13,6 @@ import at.bitfire.dav4jvm.UrlUtils
import at.bitfire.dav4jvm.exception.DavException
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.dav4jvm.exception.UnauthorizedException
import at.bitfire.dav4jvm.property.*
import at.bitfire.dav4jvm.property.caldav.CalendarColor
import at.bitfire.dav4jvm.property.caldav.CalendarDescription
import at.bitfire.dav4jvm.property.caldav.CalendarHomeSet
@@ -43,7 +42,7 @@ import java.io.InterruptedIOException
import java.net.SocketTimeoutException
import java.net.URI
import java.net.URISyntaxException
import java.util.*
import java.util.LinkedList
import java.util.logging.Level
import java.util.logging.Logger

View File

@@ -26,16 +26,22 @@ import java.util.logging.Level
*/
class AddressBookSyncer @AssistedInject constructor(
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted resync: ResyncType?,
@Assisted val syncFrameworkUpload: Boolean,
@Assisted syncResult: SyncResult,
addressBookStore: LocalAddressBookStore,
private val accountSettingsFactory: AccountSettings.Factory,
private val contactsSyncManagerFactory: ContactsSyncManager.Factory
): Syncer<LocalAddressBookStore, LocalAddressBook>(account, extras, syncResult) {
): Syncer<LocalAddressBookStore, LocalAddressBook>(account, resync, syncResult) {
@AssistedFactory
interface Factory {
fun create(account: Account, extras: Array<String>, syncResult: SyncResult): AddressBookSyncer
fun create(
account: Account,
resyncType: ResyncType?,
syncFrameworkUpload: Boolean,
syncResult: SyncResult
): AddressBookSyncer
}
override val dataStore = addressBookStore
@@ -52,7 +58,6 @@ class AddressBookSyncer @AssistedInject constructor(
syncAddressBook(
account = account,
addressBook = localCollection,
extras = extras,
httpClient = httpClient,
provider = provider,
syncResult = syncResult,
@@ -64,8 +69,6 @@ class AddressBookSyncer @AssistedInject constructor(
* Synchronizes an address book
*
* @param addressBook local address book
* @param extras Sync specific instructions. IE [Syncer.SYNC_EXTRAS_FULL_RESYNC]
* @param httpClient
* @param provider Content provider to access android contacts
* @param syncResult Stores hard and soft sync errors
* @param collection The database collection associated with this address book
@@ -73,7 +76,6 @@ class AddressBookSyncer @AssistedInject constructor(
private fun syncAddressBook(
account: Account,
addressBook: LocalAddressBook,
extras: Array<String>,
httpClient: Lazy<HttpClient>,
provider: ContentProviderClient,
syncResult: SyncResult,
@@ -102,12 +104,13 @@ class AddressBookSyncer @AssistedInject constructor(
val syncManager = contactsSyncManagerFactory.contactsSyncManager(
account,
httpClient.value,
extras,
dataStore.authority,
syncResult,
provider,
addressBook,
collection
collection,
resync,
syncFrameworkUpload
)
runBlocking {
syncManager.performSync()

View File

@@ -53,22 +53,22 @@ import java.util.logging.Level
*/
class CalendarSyncManager @AssistedInject constructor(
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted httpClient: HttpClient,
@Assisted authority: String,
@Assisted syncResult: SyncResult,
@Assisted localCalendar: LocalCalendar,
@Assisted collection: Collection,
@Assisted resync: ResyncType?,
accountSettingsFactory: AccountSettings.Factory,
@SyncDispatcher syncDispatcher: CoroutineDispatcher
): SyncManager<LocalEvent, LocalCalendar, DavCalendar>(
account,
httpClient,
extras,
authority,
syncResult,
localCalendar,
collection,
resync,
syncDispatcher
) {
@@ -76,12 +76,12 @@ class CalendarSyncManager @AssistedInject constructor(
interface Factory {
fun calendarSyncManager(
account: Account,
extras: Array<String>,
httpClient: HttpClient,
authority: String,
syncResult: SyncResult,
localCalendar: LocalCalendar,
collection: Collection
collection: Collection,
resync: ResyncType?
): CalendarSyncManager
}

View File

@@ -22,16 +22,16 @@ import kotlinx.coroutines.runBlocking
*/
class CalendarSyncer @AssistedInject constructor(
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted resync: ResyncType?,
@Assisted syncResult: SyncResult,
calendarStore: LocalCalendarStore,
private val accountSettingsFactory: AccountSettings.Factory,
private val calendarSyncManagerFactory: CalendarSyncManager.Factory
): Syncer<LocalCalendarStore, LocalCalendar>(account, extras, syncResult) {
): Syncer<LocalCalendarStore, LocalCalendar>(account, resync, syncResult) {
@AssistedFactory
interface Factory {
fun create(account: Account, extras: Array<String>, syncResult: SyncResult): CalendarSyncer
fun create(account: Account, resyncType: ResyncType?, syncResult: SyncResult): CalendarSyncer
}
override val dataStore = calendarStore
@@ -58,12 +58,12 @@ class CalendarSyncer @AssistedInject constructor(
val syncManager = calendarSyncManagerFactory.calendarSyncManager(
account,
extras,
httpClient.value,
dataStore.authority,
syncResult,
localCollection,
remoteCollection
remoteCollection,
resync
)
runBlocking {
syncManager.performSync()

View File

@@ -6,7 +6,6 @@ package at.bitfire.davdroid.sync
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.text.format.Formatter
import at.bitfire.dav4jvm.DavAddressBook
import at.bitfire.dav4jvm.MultiResponseCallback
@@ -95,16 +94,19 @@ import kotlin.jvm.optionals.getOrNull
* are received. This is done by caching the member UIDs of each group in
* [LocalGroup.COLUMN_PENDING_MEMBERS]. In [postProcess],
* these "pending memberships" are assigned to the actual contacts and then cleaned up.
*
* @param syncFrameworkUpload set when this sync is caused by the sync framework and [android.content.ContentResolver.SYNC_EXTRAS_UPLOAD] was set
*/
class ContactsSyncManager @AssistedInject constructor(
@Assisted account: Account,
@Assisted httpClient: HttpClient,
@Assisted extras: Array<String>,
@Assisted authority: String,
@Assisted syncResult: SyncResult,
@Assisted val provider: ContentProviderClient,
@Assisted localAddressBook: LocalAddressBook,
@Assisted collection: Collection,
@Assisted resync: ResyncType?,
@Assisted val syncFrameworkUpload: Boolean,
val dirtyVerifier: Optional<ContactDirtyVerifier>,
accountSettingsFactory: AccountSettings.Factory,
private val httpClientBuilder: HttpClient.Builder,
@@ -112,11 +114,11 @@ class ContactsSyncManager @AssistedInject constructor(
): SyncManager<LocalAddress, LocalAddressBook, DavAddressBook>(
account,
httpClient,
extras,
authority,
syncResult,
localAddressBook,
collection,
resync,
syncDispatcher
) {
@@ -125,12 +127,13 @@ class ContactsSyncManager @AssistedInject constructor(
fun contactsSyncManager(
account: Account,
httpClient: HttpClient,
extras: Array<String>,
authority: String,
syncResult: SyncResult,
provider: ContentProviderClient,
localAddressBook: LocalAddressBook,
collection: Collection
collection: Collection,
resync: ResyncType?,
syncFrameworkUpload: Boolean
): ContactsSyncManager
}
@@ -156,7 +159,7 @@ class ContactsSyncManager @AssistedInject constructor(
override fun prepare(): Boolean {
if (dirtyVerifier.isPresent) {
logger.info("Sync will verify dirty contacts (Android 7.x workaround)")
if (!dirtyVerifier.get().prepareAddressBook(localCollection, isUpload = extras.contains(ContentResolver.SYNC_EXTRAS_UPLOAD)))
if (!dirtyVerifier.get().prepareAddressBook(localCollection, isUpload = syncFrameworkUpload))
return false
}

View File

@@ -42,21 +42,21 @@ import java.util.logging.Level
class JtxSyncManager @AssistedInject constructor(
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted httpClient: HttpClient,
@Assisted authority: String,
@Assisted syncResult: SyncResult,
@Assisted localCollection: LocalJtxCollection,
@Assisted collection: Collection,
@Assisted resync: ResyncType?,
@SyncDispatcher syncDispatcher: CoroutineDispatcher
): SyncManager<LocalJtxICalObject, LocalJtxCollection, DavCalendar>(
account,
httpClient,
extras,
authority,
syncResult,
localCollection,
collection,
resync,
syncDispatcher
) {
@@ -64,12 +64,12 @@ class JtxSyncManager @AssistedInject constructor(
interface Factory {
fun jtxSyncManager(
account: Account,
extras: Array<String>,
httpClient: HttpClient,
authority: String,
syncResult: SyncResult,
localCollection: LocalJtxCollection,
collection: Collection
collection: Collection,
resync: ResyncType?
): JtxSyncManager
}

View File

@@ -23,16 +23,16 @@ import kotlinx.coroutines.runBlocking
*/
class JtxSyncer @AssistedInject constructor(
@Assisted account: Account,
@Assisted extras: Array<String>,
@Assisted resync: ResyncType?,
@Assisted syncResult: SyncResult,
localJtxCollectionStore: LocalJtxCollectionStore,
private val jtxSyncManagerFactory: JtxSyncManager.Factory,
private val tasksAppManager: dagger.Lazy<TasksAppManager>
): Syncer<LocalJtxCollectionStore, LocalJtxCollection>(account, extras, syncResult) {
): Syncer<LocalJtxCollectionStore, LocalJtxCollection>(account, resync, syncResult) {
@AssistedFactory
interface Factory {
fun create(account: Account, extras: Array<String>, syncResult: SyncResult): JtxSyncer
fun create(account: Account, resyncType: ResyncType?, syncResult: SyncResult): JtxSyncer
}
override val dataStore = localJtxCollectionStore
@@ -71,12 +71,12 @@ class JtxSyncer @AssistedInject constructor(
val syncManager = jtxSyncManagerFactory.jtxSyncManager(
account,
extras,
httpClient.value,
dataStore.authority,
syncResult,
localCollection,
remoteCollection
remoteCollection,
resync
)
runBlocking {
syncManager.performSync()

View File

@@ -0,0 +1,34 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.sync
/**
* Used to signal that re-synchronization is requested during a sync.
*
* Re-synchronization means that synchronization shouldn't skip listing/downloading
* the entries even when then `sync-token` (or CTag) didn't change since the last sync.
*/
enum class ResyncType {
/**
* **(Normal) re-synchronization**: all remote entries shall be listed regardless of the
* sync-token (or CTag) of the collection. Modified entries will then be downloaded as usual.
*
* Sample use-case: the past event time range setting has been modified, and we want
* to get the new list of all events (regardless of the sync-token).
*/
RESYNC_LIST,
/**
* **Full re-synchronization**: all remote entries shall be listed regardless of the
* sync-token (or CTag) of the collection, and all entries will be downloaded again,
* either if they were not changed on the server since the last sync.
*
* Sample use-case: Contact group type setting is changed, and all vCards have to
* be downloaded and parsed again to determine their group memberships.
*/
RESYNC_ENTRIES
}

View File

@@ -158,7 +158,7 @@ abstract class SyncAdapterService: Service() {
}
logger.fine("Starting OneTimeSyncWorker for $account $authority and waiting for it")
val workerName = syncWorkerManager.enqueueOneTime(account, dataType = SyncDataType.fromAuthority(authority), upload = upload)
val workerName = syncWorkerManager.enqueueOneTime(account, dataType = SyncDataType.fromAuthority(authority), syncFrameworkUpload = upload)
/* Because we are not allowed to observe worker state on a background thread, we can not
use it to block the sync adapter. Instead we use a Flow to get notified when the sync

View File

@@ -66,22 +66,22 @@ import javax.net.ssl.SSLHandshakeException
* @param CollectionType type of local collection
* @param RemoteType type of remote collection
*
* @param account account to synchronize
* @param httpClient HTTP client to use for network requests, already authenticated with credentials from [account]
* @param extras additional sync parameters
* @param authority authority of the content provider the collection shall be synchronized with
* @param syncResult receiver for result of the synchronization (will be updated by [performSync])
* @param localCollection local collection to synchronize (interface to content provider)
* @param collection collection info in the database
* @param account account to synchronize
* @param httpClient HTTP client to use for network requests, already authenticated with credentials from [account]
* @param authority authority of the content provider the collection shall be synchronized with
* @param syncResult receiver for result of the synchronization (will be updated by [performSync])
* @param localCollection local collection to synchronize (interface to content provider)
* @param collection collection info in the database
* @param resync whether re-synchronization is requested
*/
abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: LocalCollection<ResourceType>, RemoteType: DavCollection>(
val account: Account,
val httpClient: HttpClient,
val extras: Array<String>,
val authority: String,
val syncResult: SyncResult,
val localCollection: CollectionType,
val collection: Collection,
val resync: ResyncType?,
val syncDispatcher: CoroutineDispatcher
) {
@@ -150,7 +150,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
val modificationsPresent =
processLocallyDeleted() or uploadDirty() // bitwise OR guarantees that both expressions are evaluated
if (extras.contains(Syncer.SYNC_EXTRAS_FULL_RESYNC)) {
if (resync == ResyncType.RESYNC_ENTRIES) {
logger.info("Forcing re-synchronization of all entries")
// forget sync state of collection (→ initial sync in case of SyncAlgorithm.COLLECTION_SYNC)
@@ -493,8 +493,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
* [uploadDirty] were true), a sync is always required and this method
* should *not* be evaluated.
*
* Will return _true_ if [Syncer.SYNC_EXTRAS_RESYNC] and/or
* [Syncer.SYNC_EXTRAS_FULL_RESYNC] is set in [extras].
* Will return _true_ if [resync] is non-null and thus indicates re-synchronization.
*
* @param state remote sync state to compare local sync state with
*
@@ -502,8 +501,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
* sync algorithm is required
*/
protected open fun syncRequired(state: SyncState?): Boolean {
if (extras.contains(Syncer.SYNC_EXTRAS_RESYNC) ||
extras.contains(Syncer.SYNC_EXTRAS_FULL_RESYNC))
if (resync != null)
return true
val localState = localCollection.lastSyncState

View File

@@ -27,36 +27,17 @@ import javax.inject.Inject
* Base class for sync code.
*
* Contains generic sync code, equal for all sync authorities.
*
* @param account account to synchronize
* @param resync whether re-synchronization is requested (`null` for normal sync)
* @param syncResult synchronization result, to be modified during sync
*/
abstract class Syncer<StoreType: LocalDataStore<CollectionType>, CollectionType: LocalCollection<*>>(
protected val account: Account,
protected val extras: Array<String>,
protected val resync: ResyncType?,
protected val syncResult: SyncResult
) {
companion object {
/**
* Requests a re-synchronization of all entries. For instance, if this extra is
* set for a calendar sync, all remote events will be listed and checked for remote
* changes again.
*
* Useful if settings which modify the remote resource list (like the CalDAV setting
* "sync events n days in the past") have been changed.
*/
const val SYNC_EXTRAS_RESYNC = "resync"
/**
* Requests a full re-synchronization of all entries. For instance, if this extra is
* set for an address book sync, all contacts will be downloaded again and updated in the
* local storage.
*
* Useful if settings which modify parsing/local behavior have been changed.
*/
const val SYNC_EXTRAS_FULL_RESYNC = "full_resync"
}
abstract val dataStore: StoreType
@Inject @ApplicationContext
@@ -250,7 +231,7 @@ abstract class Syncer<StoreType: LocalDataStore<CollectionType>, CollectionType:
* - handle occurring sync errors
*/
operator fun invoke() {
logger.log(Level.INFO, "${dataStore.authority} sync of $account initiated", extras.joinToString(", "))
logger.info("${dataStore.authority} sync of $account initiated (resync=$resync)")
try {
dataStore.acquireContentProvider()
@@ -294,10 +275,7 @@ abstract class Syncer<StoreType: LocalDataStore<CollectionType>, CollectionType:
} finally {
if (httpClient.isInitialized())
httpClient.value.close()
logger.log(
Level.INFO,
"${dataStore.authority} sync of $account finished",
extras.joinToString(", "))
logger.info("${dataStore.authority} sync of $account finished")
}
}
}

View File

@@ -24,16 +24,16 @@ import kotlinx.coroutines.runBlocking
class TaskSyncer @AssistedInject constructor(
@Assisted account: Account,
@Assisted val providerName: TaskProvider.ProviderName,
@Assisted extras: Array<String>,
@Assisted resync: ResyncType,
@Assisted syncResult: SyncResult,
localTaskListStoreFactory: LocalTaskListStore.Factory,
private val tasksAppManager: dagger.Lazy<TasksAppManager>,
private val tasksSyncManagerFactory: TasksSyncManager.Factory,
): Syncer<LocalTaskListStore, LocalTaskList>(account, extras, syncResult) {
): Syncer<LocalTaskListStore, LocalTaskList>(account, resync, syncResult) {
@AssistedFactory
interface Factory {
fun create(account: Account, providerName: TaskProvider.ProviderName, extras: Array<String>, syncResult: SyncResult): TaskSyncer
fun create(account: Account, providerName: TaskProvider.ProviderName, resyncType: ResyncType?, syncResult: SyncResult): TaskSyncer
}
override val dataStore = localTaskListStoreFactory.create(providerName)
@@ -73,11 +73,11 @@ class TaskSyncer @AssistedInject constructor(
val syncManager = tasksSyncManagerFactory.tasksSyncManager(
account,
httpClient.value,
extras,
dataStore.authority,
syncResult,
localCollection,
remoteCollection
remoteCollection,
resync
)
runBlocking {
syncManager.performSync()

View File

@@ -45,20 +45,20 @@ import java.util.logging.Level
class TasksSyncManager @AssistedInject constructor(
@Assisted account: Account,
@Assisted httpClient: HttpClient,
@Assisted extras: Array<String>,
@Assisted authority: String,
@Assisted syncResult: SyncResult,
@Assisted localCollection: LocalTaskList,
@Assisted collection: Collection,
@Assisted resync: ResyncType?,
@SyncDispatcher syncDispatcher: CoroutineDispatcher
): SyncManager<LocalTask, LocalTaskList, DavCalendar>(
account,
httpClient,
extras,
authority,
syncResult,
localCollection,
collection,
resync,
syncDispatcher
) {
@@ -67,11 +67,11 @@ class TasksSyncManager @AssistedInject constructor(
fun tasksSyncManager(
account: Account,
httpClient: HttpClient,
extras: Array<String>,
authority: String,
syncResult: SyncResult,
localCollection: LocalTaskList,
collection: Collection
collection: Collection,
resync: ResyncType?
): TasksSyncManager
}

View File

@@ -5,7 +5,6 @@
package at.bitfire.davdroid.sync.worker
import android.accounts.Account
import android.content.ContentResolver
import android.content.Context
import android.os.Build
import androidx.annotation.IntDef
@@ -22,16 +21,16 @@ import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.sync.AddressBookSyncer
import at.bitfire.davdroid.sync.CalendarSyncer
import at.bitfire.davdroid.sync.JtxSyncer
import at.bitfire.davdroid.sync.ResyncType
import at.bitfire.davdroid.sync.SyncConditions
import at.bitfire.davdroid.sync.SyncDataType
import at.bitfire.davdroid.sync.SyncResult
import at.bitfire.davdroid.sync.Syncer
import at.bitfire.davdroid.sync.TaskSyncer
import at.bitfire.davdroid.sync.TasksAppManager
import at.bitfire.davdroid.sync.account.InvalidAccountException
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.FULL_RESYNC
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.NO_RESYNC
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.RESYNC
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.RESYNC_ENTRIES
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.RESYNC_LIST
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.commonTag
import at.bitfire.davdroid.ui.NotificationRegistry
import at.bitfire.ical4android.TaskProvider
@@ -141,35 +140,32 @@ abstract class BaseSyncWorker(
suspend fun doSyncWork(account: Account, dataType: SyncDataType): Result {
logger.info("Running ${javaClass.name}: account=$account, dataType=$dataType")
// pass possibly supplied flags to the selected syncer
val extrasList = mutableListOf<String>()
when (inputData.getInt(INPUT_RESYNC, NO_RESYNC)) {
RESYNC -> extrasList.add(Syncer.SYNC_EXTRAS_RESYNC)
FULL_RESYNC -> extrasList.add(Syncer.SYNC_EXTRAS_FULL_RESYNC)
// pass supplied parameters to the selected syncer
val resyncType: ResyncType? = when (inputData.getInt(INPUT_RESYNC, NO_RESYNC)) {
RESYNC_ENTRIES -> ResyncType.RESYNC_ENTRIES
RESYNC_LIST -> ResyncType.RESYNC_LIST
else -> null
}
if (inputData.getBoolean(INPUT_UPLOAD, false))
// Comes in through SyncAdapterService and is used only by ContactsSyncManager for an Android 7 workaround.
extrasList.add(ContentResolver.SYNC_EXTRAS_UPLOAD)
val extras = extrasList.toTypedArray()
// We still use the sync adapter framework's SyncResult to pass the sync results, but this
// is only for legacy reasons and can be replaced by our own result class in the future.
// Comes in through SyncAdapterService and is used only by ContactsSyncManager for an Android 7 workaround.
val syncFrameworkUpload = inputData.getBoolean(INPUT_UPLOAD, false)
val syncResult = SyncResult()
// What are we going to sync? Select syncer based on authority
val syncer = when (dataType) {
SyncDataType.CONTACTS ->
addressBookSyncer.create(account, extras, syncResult)
addressBookSyncer.create(account, resyncType, syncFrameworkUpload, syncResult)
SyncDataType.EVENTS ->
calendarSyncer.create(account, extras, syncResult)
calendarSyncer.create(account, resyncType, syncResult)
SyncDataType.TASKS -> {
val currentProvider = tasksAppManager.get().currentProvider()
when (currentProvider) {
TaskProvider.ProviderName.JtxBoard ->
jtxSyncer.create(account, extras, syncResult)
jtxSyncer.create(account, resyncType, syncResult)
TaskProvider.ProviderName.OpenTasks,
TaskProvider.ProviderName.TasksOrg ->
taskSyncer.create(account, currentProvider, extras, syncResult)
taskSyncer.create(account, currentProvider, resyncType, syncResult)
else -> {
logger.warning("No valid tasks provider found, aborting sync")
return Result.failure()
@@ -245,25 +241,25 @@ abstract class BaseSyncWorker(
companion object {
// common worker input parameters
const val INPUT_ACCOUNT_NAME = "accountName"
const val INPUT_ACCOUNT_TYPE = "accountType"
const val INPUT_DATA_TYPE = "dataType"
internal const val INPUT_ACCOUNT_NAME = "accountName"
internal const val INPUT_ACCOUNT_TYPE = "accountType"
internal const val INPUT_DATA_TYPE = "dataType"
/** set to `true` for user-initiated sync that skips network checks */
const val INPUT_MANUAL = "manual"
internal const val INPUT_MANUAL = "manual"
/** set to `true` for syncs that are caused by local changes */
const val INPUT_UPLOAD = "upload"
/** set to `true` for syncs that are caused because the sync framework notified us about local changes */
internal const val INPUT_UPLOAD = "upload"
/** Whether re-synchronization is requested. One of [NO_RESYNC] (default), [RESYNC] or [FULL_RESYNC]. */
const val INPUT_RESYNC = "resync"
@IntDef(NO_RESYNC, RESYNC, FULL_RESYNC)
/** Whether re-synchronization is requested. One of [NO_RESYNC] (default), [RESYNC_LIST] or [RESYNC_ENTRIES]. */
internal const val INPUT_RESYNC = "resync"
@IntDef(NO_RESYNC, RESYNC_LIST, RESYNC_ENTRIES)
annotation class InputResync
const val NO_RESYNC = 0
/** Re-synchronization is requested. See [Syncer.SYNC_EXTRAS_RESYNC] for details. */
const val RESYNC = 1
/** Full re-synchronization is requested. See [Syncer.SYNC_EXTRAS_FULL_RESYNC] for details. */
const val FULL_RESYNC = 2
internal const val NO_RESYNC = 0
/** Re-synchronization is requested. See [ResyncType.RESYNC_LIST] for details. */
internal const val RESYNC_LIST = 1
/** Full re-synchronization is requested. See [ResyncType.RESYNC_ENTRIES] for details. */
internal const val RESYNC_ENTRIES = 2
const val OUTPUT_TOO_MANY_RETRIES = "tooManyRetries"

View File

@@ -7,7 +7,6 @@ package at.bitfire.davdroid.sync.worker
import android.accounts.Account
import android.content.ContentResolver
import android.content.Context
import android.provider.CalendarContract
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.Data
@@ -25,6 +24,7 @@ import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.WorkRequest
import at.bitfire.davdroid.push.PushNotificationManager
import at.bitfire.davdroid.sync.ResyncType
import at.bitfire.davdroid.sync.SyncDataType
import at.bitfire.davdroid.sync.TasksAppManager
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.INPUT_ACCOUNT_NAME
@@ -33,8 +33,8 @@ import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.INPUT_DATA_TYPE
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.INPUT_MANUAL
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.INPUT_RESYNC
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.INPUT_UPLOAD
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.InputResync
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.NO_RESYNC
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.RESYNC_ENTRIES
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.RESYNC_LIST
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.commonTag
import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -69,19 +69,25 @@ class SyncWorkerManager @Inject constructor(
account: Account,
dataType: SyncDataType,
manual: Boolean = false,
@InputResync resync: Int = NO_RESYNC,
upload: Boolean = false
resync: ResyncType? = null,
syncFrameworkUpload: Boolean = false
): OneTimeWorkRequest {
// worker arguments
val argumentsBuilder = Data.Builder()
.putString(INPUT_DATA_TYPE, dataType.toString())
.putString(INPUT_ACCOUNT_NAME, account.name)
.putString(INPUT_ACCOUNT_TYPE, account.type)
if (manual)
argumentsBuilder.putBoolean(INPUT_MANUAL, true)
if (resync != NO_RESYNC)
argumentsBuilder.putInt(INPUT_RESYNC, resync)
argumentsBuilder.putBoolean(INPUT_UPLOAD, upload)
when (resync) {
ResyncType.RESYNC_ENTRIES -> argumentsBuilder.putInt(INPUT_RESYNC, RESYNC_ENTRIES)
ResyncType.RESYNC_LIST -> argumentsBuilder.putInt(INPUT_RESYNC, RESYNC_LIST)
else -> { /* no explicit re-synchronization */ }
}
argumentsBuilder.putBoolean(INPUT_UPLOAD, syncFrameworkUpload)
// build work request
val constraints = Constraints.Builder()
@@ -109,12 +115,12 @@ class SyncWorkerManager @Inject constructor(
/**
* Requests immediate synchronization of an account with a specific authority.
*
* @param account account to sync
* @param dataType type of data to synchronize
* @param manual user-initiated sync (ignores network checks)
* @param resync whether to request (full) re-synchronization or not
* @param upload see [ContentResolver.SYNC_EXTRAS_UPLOAD] only used for contacts sync and Android 7 workaround
* @param fromPush whether this sync is initiated by a push notification
* @param account account to sync
* @param dataType type of data to synchronize
* @param manual user-initiated sync (ignores network checks)
* @param resync whether to request (full) re-synchronization (`null` for normal sync)
* @param syncFrameworkUpload see [ContentResolver.SYNC_EXTRAS_UPLOAD] only used for contacts sync and Android 7 workaround
* @param fromPush whether this sync is initiated by a push notification
*
* @return existing or newly created worker name
*/
@@ -122,11 +128,11 @@ class SyncWorkerManager @Inject constructor(
account: Account,
dataType: SyncDataType,
manual: Boolean = false,
@InputResync resync: Int = NO_RESYNC,
upload: Boolean = false,
resync: ResyncType? = null,
syncFrameworkUpload: Boolean = false,
fromPush: Boolean = false
): String {
logger.info("Enqueueing unique worker for account=$account, dataType=$dataType, manual=$manual, resync=$resync, upload=$upload, fromPush=$fromPush")
logger.info("Enqueueing unique worker for account=$account, dataType=$dataType, manual=$manual, resync=$resync, upload=$syncFrameworkUpload, fromPush=$fromPush")
// enqueue and start syncing
val name = OneTimeSyncWorker.workerName(account, dataType)
@@ -135,7 +141,7 @@ class SyncWorkerManager @Inject constructor(
dataType = dataType,
manual = manual,
resync = resync,
upload = upload
syncFrameworkUpload = syncFrameworkUpload
)
if (fromPush) {
logger.fine("Showing push sync pending notification for $name")
@@ -161,8 +167,8 @@ class SyncWorkerManager @Inject constructor(
fun enqueueOneTimeAllAuthorities(
account: Account,
manual: Boolean = false,
@InputResync resync: Int = NO_RESYNC,
upload: Boolean = false,
resync: ResyncType? = null,
syncFrameworkUpload: Boolean = false,
fromPush: Boolean = false
) {
for (dataType in SyncDataType.entries)
@@ -171,7 +177,7 @@ class SyncWorkerManager @Inject constructor(
dataType = dataType,
manual = manual,
resync = resync,
upload = upload,
syncFrameworkUpload = syncFrameworkUpload,
fromPush = fromPush
)
}
@@ -260,7 +266,7 @@ class SyncWorkerManager @Inject constructor(
*
* @param workStates list of states of workers to match
* @param account the account which the workers belong to
* @param authorities type of sync work, ie [CalendarContract.AUTHORITY]
* @param dataTypes data types of sync work
* @param whichTag function to generate tag that should be observed for given account and authority
*
* @return flow that emits `true` if at least one worker with matching query was found; `false` otherwise

View File

@@ -13,9 +13,9 @@ import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.sync.ResyncType
import at.bitfire.davdroid.sync.SyncDataType
import at.bitfire.davdroid.sync.TasksAppManager
import at.bitfire.davdroid.sync.worker.BaseSyncWorker
import at.bitfire.davdroid.sync.worker.SyncWorkerManager
import at.bitfire.vcard4android.GroupMethod
import dagger.assisted.Assisted
@@ -181,66 +181,62 @@ class AccountSettingsModel @AssistedInject constructor(
However, if the new setting is "all events", collection sync may/should be used, so
the last sync-token has to be reset, which is done by setting fullResync=true.
*/
resyncCalendars(fullResync = days == null, tasks = false)
resyncCalendars(
resync = if (days == null) ResyncType.RESYNC_ENTRIES else ResyncType.RESYNC_LIST,
tasks = false
)
}
fun updateDefaultAlarm(minBefore: Int?) = CoroutineScope(Dispatchers.Default).launch {
accountSettings.setDefaultAlarm(minBefore)
reload()
resyncCalendars(fullResync = true, tasks = false)
resyncCalendars(resync = ResyncType.RESYNC_ENTRIES, tasks = false)
}
fun updateManageCalendarColors(manage: Boolean) = CoroutineScope(Dispatchers.Default).launch {
accountSettings.setManageCalendarColors(manage)
reload()
resyncCalendars(fullResync = false, tasks = true)
resyncCalendars(resync = ResyncType.RESYNC_LIST, tasks = true)
}
fun updateEventColors(manageColors: Boolean) = CoroutineScope(Dispatchers.Default).launch {
accountSettings.setEventColors(manageColors)
reload()
resyncCalendars(fullResync = true, tasks = false)
resyncCalendars(resync = ResyncType.RESYNC_ENTRIES, tasks = false)
}
fun updateContactGroupMethod(groupMethod: GroupMethod) = CoroutineScope(Dispatchers.Default).launch {
accountSettings.setGroupMethod(groupMethod)
reload()
resync(SyncDataType.CONTACTS, fullResync = true)
resync(SyncDataType.CONTACTS, ResyncType.RESYNC_ENTRIES)
}
/**
* Initiates calendar re-synchronization.
*
* @param fullResync whether sync shall download all events again
* (_true_: sets [BaseSyncWorker.FULL_RESYNC],
* _false_: sets [BaseSyncWorker.RESYNC])
* @param tasks whether tasks shall be synchronized, too (false: only events, true: events and tasks)
* @param resync whether only the list of entries (resync) or also all entries
* themselves (full resync) shall be downloaded again
* @param tasks whether tasks shall be synchronized, too (false: only events, true: events and tasks)
*/
private fun resyncCalendars(fullResync: Boolean, tasks: Boolean) {
resync(SyncDataType.EVENTS, fullResync)
private fun resyncCalendars(resync: ResyncType, tasks: Boolean) {
resync(SyncDataType.EVENTS, resync)
if (tasks)
resync(SyncDataType.TASKS, fullResync)
resync(SyncDataType.TASKS, resync)
}
/**
* Initiates re-synchronization for given authority.
*
* @param dataType type of data to synchronize
* @param fullResync whether sync shall download all events again
* (_true_: sets [BaseSyncWorker.FULL_RESYNC],
* _false_: sets [BaseSyncWorker.RESYNC])
* @param dataType type of data to synchronize
* @param resync whether only the list of entries (resync) or also all entries
* themselves (full resync) shall be downloaded again
*/
private fun resync(dataType: SyncDataType, fullResync: Boolean) {
val resync: Int =
if (fullResync)
BaseSyncWorker.FULL_RESYNC
else
BaseSyncWorker.RESYNC
syncWorkerManager.enqueueOneTime(account, dataType, resync = resync)
private fun resync(dataType: SyncDataType, resync: ResyncType) {
syncWorkerManager.enqueueOneTime(account, dataType = dataType, resync = resync)
}
}