diff --git a/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncManagerTest.kt b/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncManagerTest.kt index f57fc1b0d..1fe9d9a7f 100644 --- a/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncManagerTest.kt +++ b/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/SyncManagerTest.kt @@ -20,6 +20,7 @@ import at.bitfire.dav4jvm.property.webdav.GetETag import at.bitfire.davdroid.R import at.bitfire.davdroid.TestUtils.assertWithin import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.network.HttpClient @@ -27,6 +28,7 @@ import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.NotificationUtils import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest +import io.mockk.mockk import okhttp3.Protocol import okhttp3.internal.http.StatusLine import okhttp3.mockwebserver.MockResponse @@ -98,13 +100,18 @@ class SyncManagerTest { } - private fun syncManager(collection: LocalTestCollection, syncResult: SyncResult = SyncResult()) = + private fun syncManager( + localCollection: LocalTestCollection, + syncResult: SyncResult = SyncResult(), + collection: Collection = mockk() + ) = TestSyncManager( account, arrayOf(), "TestAuthority", HttpClient.Builder(InstrumentationRegistry.getInstrumentation().targetContext).build(), syncResult, + localCollection, collection, server, context, db diff --git a/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/TestSyncManager.kt b/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/TestSyncManager.kt index 04178d084..6b2ffaeaa 100644 --- a/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/TestSyncManager.kt +++ b/app/src/androidTestOse/kotlin/at/bitfire/davdroid/sync/TestSyncManager.kt @@ -12,6 +12,7 @@ import at.bitfire.dav4jvm.MultiResponseCallback import at.bitfire.dav4jvm.Response import at.bitfire.dav4jvm.property.caldav.GetCTag import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.network.HttpClient import at.bitfire.davdroid.resource.LocalResource @@ -30,10 +31,11 @@ class TestSyncManager( httpClient: HttpClient, syncResult: SyncResult, localCollection: LocalTestCollection, + collection: Collection, val mockWebServer: MockWebServer, context: Context, db: AppDatabase -): SyncManager(account, AccountSettings(context, account), httpClient, extras, authority, syncResult, localCollection, context, db) { +): SyncManager(account, AccountSettings(context, account), httpClient, extras, authority, syncResult, localCollection, collection, context, db) { override fun prepare(): Boolean { collectionURL = mockWebServer.url("/") diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt index 863b5cd74..9b9ddb124 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt @@ -50,60 +50,43 @@ class AddressBookSyncer @Inject constructor( provider: ContentProviderClient, // for noop address book provider (not for contacts provider) syncResult: SyncResult ) { - if (updateLocalAddressBooks(account, syncResult)) { - context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.use { contactsProvider -> - for (addressBookAccount in LocalAddressBook.findAll(context, null, account).map { it.account }) { - Logger.log.info("Synchronizing address book $addressBookAccount") - syncAddresBook( - addressBookAccount, - extras, - ContactsContract.AUTHORITY, - httpClient, - contactsProvider, - syncResult - ) - } - } - } - } - - private fun updateLocalAddressBooks(account: Account, syncResult: SyncResult): Boolean { - val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CARDDAV) + // 1. find address book collections to be synced val remoteAddressBooks = mutableMapOf() + val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CARDDAV) if (service != null) for (collection in db.collectionDao().getByServiceAndSync(service.id)) remoteAddressBooks[collection.url] = collection + // permission check if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) { if (remoteAddressBooks.isEmpty()) Logger.log.info("No contacts permission, but no address book selected for synchronization") else Logger.log.warning("No contacts permission, but address books are selected for synchronization") - return false + return // Don't sync } context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY).use { contactsProvider -> if (contactsProvider == null) { Logger.log.severe("Couldn't access contacts provider") syncResult.databaseError = true - return false + return // Don't sync } + // 2. update/delete local address books val forceAllReadOnly = settingsManager.getBoolean(Settings.FORCE_READ_ONLY_ADDRESSBOOKS) - - // delete/update local address books for (addressBook in LocalAddressBook.findAll(context, contactsProvider, account)) { val url = addressBook.url.toHttpUrl() - val info = remoteAddressBooks[url] - if (info == null) { + val collection = remoteAddressBooks[url] + if (collection == null) { Logger.log.log(Level.INFO, "Deleting obsolete local address book", url) addressBook.delete() } else { // remote CollectionInfo found for this local collection, update data try { - Logger.log.log(Level.FINE, "Updating local address book $url", info) - addressBook.update(info, forceAllReadOnly) + Logger.log.log(Level.FINE, "Updating local address book $url", collection) + addressBook.update(collection, forceAllReadOnly) } catch (e: Exception) { Logger.log.log(Level.WARNING, "Couldn't rename address book account", e) } @@ -112,24 +95,42 @@ class AddressBookSyncer @Inject constructor( } } - // create new local address books + // 3. create new local address books for ((_, info) in remoteAddressBooks) { Logger.log.log(Level.INFO, "Adding local address book", info) LocalAddressBook.create(context, contactsProvider, account, info, forceAllReadOnly) } } - return true - } + // 4. sync local address books + context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.use { contactsProvider -> + for (addressBook in LocalAddressBook.findAll(context, null, account)) { + Logger.log.info("Synchronizing address book $addressBook") + val url = addressBook.url.toHttpUrl() + remoteAddressBooks[url]?.let { collection -> + syncAddressBook( + addressBook.account, + extras, + ContactsContract.AUTHORITY, + httpClient, + contactsProvider, + syncResult, + collection + ) + } + } + } + } - fun syncAddresBook( + fun syncAddressBook( account: Account, extras: Array, authority: String, httpClient: Lazy, provider: ContentProviderClient, - syncResult: SyncResult + syncResult: SyncResult, + collection: Collection ) { try { val accountSettings = AccountSettings(context, account) @@ -154,7 +155,7 @@ class AddressBookSyncer @Inject constructor( Logger.log.info("Synchronizing address book: ${addressBook.url}") Logger.log.info("Taking settings from: ${addressBook.mainAccount}") - val syncManager = contactsSyncManagerFactory.contactsSyncManager(account, accountSettings, httpClient.value, extras, authority, syncResult, provider, addressBook) + val syncManager = contactsSyncManagerFactory.contactsSyncManager(account, accountSettings, httpClient.value, extras, authority, syncResult, provider, addressBook, collection) syncManager.performSync() } catch(e: Exception) { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncManager.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncManager.kt index a15bff007..e35945ef4 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncManager.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncManager.kt @@ -21,6 +21,7 @@ import at.bitfire.dav4jvm.property.webdav.SupportedReportSet import at.bitfire.dav4jvm.property.webdav.SyncToken import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.network.HttpClient @@ -31,6 +32,7 @@ import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.util.lastSegment import at.bitfire.ical4android.Event import at.bitfire.ical4android.InvalidCalendarException +import at.bitfire.ical4android.UsesThreadContextClassLoader import at.bitfire.ical4android.util.DateUtils import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -51,8 +53,9 @@ import java.time.ZonedDateTime import java.util.logging.Level /** - * Synchronization manager for CalDAV collections; handles events (VEVENT) + * Synchronization manager for CalDAV collections; handles events (VEVENT). */ +@UsesThreadContextClassLoader class CalendarSyncManager @AssistedInject constructor( @Assisted account: Account, @Assisted accountSettings: AccountSettings, @@ -61,9 +64,10 @@ class CalendarSyncManager @AssistedInject constructor( @Assisted authority: String, @Assisted syncResult: SyncResult, @Assisted localCalendar: LocalCalendar, + @Assisted collection: Collection, @ApplicationContext context: Context, db: AppDatabase -): SyncManager(account, accountSettings, httpClient, extras, authority, syncResult, localCalendar, context, db) { +): SyncManager(account, accountSettings, httpClient, extras, authority, syncResult, localCalendar, collection, context, db) { @AssistedFactory interface Factory { @@ -74,7 +78,8 @@ class CalendarSyncManager @AssistedInject constructor( httpClient: HttpClient, authority: String, syncResult: SyncResult, - localCalendar: LocalCalendar + localCalendar: LocalCalendar, + collection: Collection ): CalendarSyncManager } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt index 32e0e84e5..5751db6c6 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt @@ -40,56 +40,66 @@ class CalendarSyncer @Inject constructor( provider: ContentProviderClient, syncResult: SyncResult ) { - val accountSettings = AccountSettings(context, account) + // 0. preparations + val accountSettings = AccountSettings(context, account) if (accountSettings.getEventColors()) AndroidCalendar.insertColors(provider, account) else AndroidCalendar.removeColors(provider, account) - updateLocalCalendars(provider, account, accountSettings) - - val calendars = AndroidCalendar - .find(account, provider, LocalCalendar.Factory, "${CalendarContract.Calendars.SYNC_EVENTS}!=0", null) - for (calendar in calendars) { - Logger.log.info("Synchronizing calendar #${calendar.id}, URL: ${calendar.name}") - - val syncManager = calendarSyncManagerFactory.calendarSyncManager(account, accountSettings, extras, httpClient.value, authority, syncResult, calendar) - syncManager.performSync() - } - } - - private fun updateLocalCalendars(provider: ContentProviderClient, account: Account, settings: AccountSettings) { - val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) - + // 1. find calendar collections to be synced val remoteCalendars = mutableMapOf() + val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) if (service != null) - for (collection in db.collectionDao().getSyncCalendars(service.id)) { + for (collection in db.collectionDao().getSyncCalendars(service.id)) remoteCalendars[collection.url] = collection - } - // delete/update local calendars - val updateColors = settings.getManageCalendarColors() + // 2. update/delete local calendars + val updateColors = accountSettings.getManageCalendarColors() for (calendar in AndroidCalendar.find(account, provider, LocalCalendar.Factory, null, null)) calendar.name?.let { val url = it.toHttpUrl() - val info = remoteCalendars[url] - if (info == null) { + val collection = remoteCalendars[url] + if (collection == null) { Logger.log.log(Level.INFO, "Deleting obsolete local calendar", url) calendar.delete() } else { // remote CollectionInfo found for this local collection, update data - Logger.log.log(Level.FINE, "Updating local calendar $url", info) - calendar.update(info, updateColors) + Logger.log.log(Level.FINE, "Updating local calendar $url", collection) + calendar.update(collection, updateColors) // we already have a local calendar for this remote collection, don't take into consideration anymore remoteCalendars -= url } } - // create new local calendars + // 3. create new local calendars for ((_, info) in remoteCalendars) { Logger.log.log(Level.INFO, "Adding local calendar", info) LocalCalendar.create(account, provider, info) } + + // 4. sync local calendars + val calendars = AndroidCalendar + .find(account, provider, LocalCalendar.Factory, "${CalendarContract.Calendars.SYNC_EVENTS}!=0", null) + for (calendar in calendars) { + val url = calendar.name?.toHttpUrl() + remoteCalendars[url]?.let { collection -> + Logger.log.info("Synchronizing calendar #${calendar.id}, URL: ${calendar.name}") + + val syncManager = calendarSyncManagerFactory.calendarSyncManager( + account, + accountSettings, + extras, + httpClient.value, + authority, + syncResult, + calendar, + collection + ) + syncManager.performSync() + } + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/ContactsSyncManager.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/ContactsSyncManager.kt index e307a7c9e..38b0d34fd 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/ContactsSyncManager.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/ContactsSyncManager.kt @@ -26,6 +26,7 @@ import at.bitfire.dav4jvm.property.webdav.SupportedReportSet import at.bitfire.dav4jvm.property.webdav.SyncToken import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.network.HttpClient @@ -104,10 +105,11 @@ class ContactsSyncManager @AssistedInject constructor( @Assisted syncResult: SyncResult, @Assisted val provider: ContentProviderClient, @Assisted localAddressBook: LocalAddressBook, + @Assisted collection: Collection, @ApplicationContext context: Context, db: AppDatabase ): SyncManager( - account, accountSettings, httpClient, extras, authority, syncResult, localAddressBook, + account, accountSettings, httpClient, extras, authority, syncResult, localAddressBook, collection, context, db ) { @@ -121,7 +123,8 @@ class ContactsSyncManager @AssistedInject constructor( authority: String, syncResult: SyncResult, provider: ContentProviderClient, - localAddressBook: LocalAddressBook + localAddressBook: LocalAddressBook, + collection: Collection ): ContactsSyncManager } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncManager.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncManager.kt index 336c646ab..ef99dbe69 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncManager.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncManager.kt @@ -19,6 +19,7 @@ import at.bitfire.dav4jvm.property.webdav.GetETag import at.bitfire.dav4jvm.property.webdav.SyncToken import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.network.HttpClient @@ -29,6 +30,7 @@ import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.util.lastSegment import at.bitfire.ical4android.InvalidCalendarException import at.bitfire.ical4android.JtxICalObject +import at.bitfire.ical4android.UsesThreadContextClassLoader import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -42,6 +44,7 @@ import java.io.Reader import java.io.StringReader import java.util.logging.Level +@UsesThreadContextClassLoader class JtxSyncManager @AssistedInject constructor( @Assisted account: Account, @Assisted accountSettings: AccountSettings, @@ -50,9 +53,10 @@ class JtxSyncManager @AssistedInject constructor( @Assisted authority: String, @Assisted syncResult: SyncResult, @Assisted localCollection: LocalJtxCollection, + @Assisted collection: Collection, @ApplicationContext context: Context, db: AppDatabase -): SyncManager(account, accountSettings, httpClient, extras, authority, syncResult, localCollection, context, db) { +): SyncManager(account, accountSettings, httpClient, extras, authority, syncResult, localCollection, collection, context, db) { @AssistedFactory interface Factory { @@ -63,7 +67,8 @@ class JtxSyncManager @AssistedInject constructor( httpClient: HttpClient, authority: String, syncResult: SyncResult, - localCollection: LocalJtxCollection + localCollection: LocalJtxCollection, + collection: Collection ): JtxSyncManager } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt index 2423161f4..1451a639a 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/JtxSyncer.kt @@ -59,55 +59,62 @@ class JtxSyncer @Inject constructor( val accountSettings = AccountSettings(context, account) - // sync list of collections - updateLocalCollections(account, provider, accountSettings) + // 1. find jtxCollection collections to be synced + val remoteCollections = mutableMapOf() + val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) + if (service != null) + for (collection in db.collectionDao().getSyncJtxCollections(service.id)) + remoteCollections[collection.url] = collection - // sync contents of collections - val collections = JtxCollection.find(account, provider, context, LocalJtxCollection.Factory, null, null) - for (collection in collections) { - Logger.log.info("Synchronizing $collection") + // 2. delete/update local jtxCollection lists + val updateColors = accountSettings.getManageCalendarColors() + for (jtxCollection in JtxCollection.find(account, provider, context, LocalJtxCollection.Factory, null, null)) + jtxCollection.url?.let { strUrl -> + val url = strUrl.toHttpUrl() + val collection = remoteCollections[url] + if (collection == null) { + Logger.log.fine("Deleting obsolete local collection $url") + jtxCollection.delete() + } else { + // remote CollectionInfo found for this local collection, update data + Logger.log.log(Level.FINE, "Updating local collection $url", collection) + val owner = collection.ownerId?.let { db.principalDao().get(it) } + jtxCollection.updateCollection(collection, owner, updateColors) + // we already have a local task list for this remote collection, don't take into consideration anymore + remoteCollections -= url + } + } - val syncManager = jtxSyncManagerFactory.jtxSyncManager(account, accountSettings, extras, httpClient.value, authority, syncResult, collection) - syncManager.performSync() + // 3. create new local jtxCollections + for ((_,info) in remoteCollections) { + Logger.log.log(Level.INFO, "Adding local collections", info) + val owner = info.ownerId?.let { db.principalDao().get(it) } + LocalJtxCollection.create(account, provider, info, owner) } - } catch (e: TaskProvider.ProviderTooOldException) { - TaskUtils.notifyProviderTooOld(context, e) - } - } - - private fun updateLocalCollections(account: Account, client: ContentProviderClient, settings: AccountSettings) { - val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) - - val remoteCollections = mutableMapOf() - if (service != null) - for (collection in db.collectionDao().getSyncJtxCollections(service.id)) - remoteCollections[collection.url] = collection - - val updateColors = settings.getManageCalendarColors() + // 4. sync local jtxCollection lists + val localCollections = JtxCollection.find(account, provider, context, LocalJtxCollection.Factory, null, null) + for (localCollection in localCollections) { + Logger.log.info("Synchronizing $localCollection") - for (jtxCollection in JtxCollection.find(account, client, context, LocalJtxCollection.Factory, null, null)) - jtxCollection.url?.let { strUrl -> - val url = strUrl.toHttpUrl() - val info = remoteCollections[url] - if (info == null) { - Logger.log.fine("Deleting obsolete local collection $url") - jtxCollection.delete() - } else { - // remote CollectionInfo found for this local collection, update data - Logger.log.log(Level.FINE, "Updating local collection $url", info) - val owner = info.ownerId?.let { db.principalDao().get(it) } - jtxCollection.updateCollection(info, owner, updateColors) - // we already have a local task list for this remote collection, don't take into consideration anymore - remoteCollections -= url + val url = localCollection.url?.toHttpUrl() + remoteCollections[url]?.let { collection -> + val syncManager = jtxSyncManagerFactory.jtxSyncManager( + account, + accountSettings, + extras, + httpClient.value, + authority, + syncResult, + localCollection, + collection + ) + syncManager.performSync() } } - // create new local collections - for ((_,info) in remoteCollections) { - Logger.log.log(Level.INFO, "Adding local collections", info) - val owner = info.ownerId?.let { db.principalDao().get(it) } - LocalJtxCollection.create(account, client, info, owner) + } catch (e: TaskProvider.ProviderTooOldException) { + TaskUtils.notifyProviderTooOld(context, e) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/SyncManager.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/SyncManager.kt index 3a00b0a0d..ee7b1b768 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/SyncManager.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/SyncManager.kt @@ -38,6 +38,7 @@ import at.bitfire.dav4jvm.property.webdav.SyncToken import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Service import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.db.SyncStats @@ -77,7 +78,22 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.logging.Level import javax.net.ssl.SSLHandshakeException -@UsesThreadContextClassLoader +/** + * Synchronizes a local collection with a remote collection. + * + * @param ResourceType type of local resources + * @param CollectionType type of local collection + * @param RemoteType type of remote collection + * + * @param account account to synchronize + * @param accountSettings account settings of 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 + */ abstract class SyncManager, out CollectionType: LocalCollection, RemoteType: DavCollection>( val account: Account, val accountSettings: AccountSettings, @@ -86,6 +102,7 @@ abstract class SyncManager, out CollectionType: L val authority: String, val syncResult: SyncResult, val localCollection: CollectionType, + val collection: Collection, // injected val context: Context, val db: AppDatabase @@ -323,10 +340,9 @@ abstract class SyncManager, out CollectionType: L */ private fun saveSyncTime() { val serviceType = when (authority) { - ContactsContract.AUTHORITY, - context.getString(R.string.address_books_authority) -> // Contacts and Address books + ContactsContract.AUTHORITY -> // contacts Service.TYPE_CARDDAV - else -> // Calendars + other (ie. tasks + else -> // calendars and tasks Service.TYPE_CALDAV } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt index b8183688e..bbf16c424 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/Syncer.kt @@ -53,6 +53,11 @@ abstract class Syncer( } + /** + * Creates and/or deletes local collections (calendars, address books, etc) and updates them + * with remote information. Then syncs the actual entries (events, tasks, contacts, etc) of all + * collections. + */ abstract fun sync(account: Account, extras: Array, authority: String, httpClient: Lazy, provider: ContentProviderClient, syncResult: SyncResult) fun onPerformSync( diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt index a80b22e8f..a9ab97628 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/TaskSyncer.kt @@ -60,15 +60,57 @@ class TaskSyncer @Inject constructor( val accountSettings = AccountSettings(context, account) - updateLocalTaskLists(taskProvider, account, accountSettings) + // 1. find task collections to be synced + val remoteTaskLists = mutableMapOf() + val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) + if (service != null) + for (collection in db.collectionDao().getSyncTaskLists(service.id)) + remoteTaskLists[collection.url] = collection - val taskLists = DmfsTaskList + // 2. delete/update local task lists + val updateColors = accountSettings.getManageCalendarColors() + for (list in DmfsTaskList.find(account, taskProvider, LocalTaskList.Factory, null, null)) + list.syncId?.let { + val url = it.toHttpUrl() + val info = remoteTaskLists[url] + if (info == null) { + Logger.log.fine("Deleting obsolete local task list $url") + list.delete() + } else { + // remote CollectionInfo found for this local collection, update data + Logger.log.log(Level.FINE, "Updating local task list $url", info) + list.update(info, updateColors) + // we already have a local task list for this remote collection, don't take into consideration anymore + remoteTaskLists -= url + } + } + + // 3. create new local task lists + for ((_,info) in remoteTaskLists) { + Logger.log.log(Level.INFO, "Adding local task list", info) + LocalTaskList.create(account, taskProvider, info) + } + + // 4. sync local task lists + val localTaskLists = DmfsTaskList .find(account, taskProvider, LocalTaskList.Factory, "${TaskContract.TaskLists.SYNC_ENABLED}!=0", null) - for (taskList in taskLists) { - Logger.log.info("Synchronizing task list #${taskList.id} [${taskList.syncId}]") + for (localTaskList in localTaskLists) { + Logger.log.info("Synchronizing task list #${localTaskList.id} [${localTaskList.syncId}]") - val syncManager = tasksSyncManagerFactory.tasksSyncManager(account, accountSettings, httpClient.value, extras, authority, syncResult, taskList) - syncManager.performSync() + val url = localTaskList.syncId?.toHttpUrl() + remoteTaskLists[url]?.let { collection -> + val syncManager = tasksSyncManagerFactory.tasksSyncManager( + account, + accountSettings, + httpClient.value, + extras, + authority, + syncResult, + localTaskList, + collection + ) + syncManager.performSync() + } } } catch (e: TaskProvider.ProviderTooOldException) { TaskUtils.notifyProviderTooOld(context, e) @@ -80,39 +122,4 @@ class TaskSyncer @Inject constructor( Logger.log.info("Task sync complete") } - - private fun updateLocalTaskLists(provider: TaskProvider, account: Account, settings: AccountSettings) { - val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) - - val remoteTaskLists = mutableMapOf() - if (service != null) - for (collection in db.collectionDao().getSyncTaskLists(service.id)) { - remoteTaskLists[collection.url] = collection - } - - // delete/update local task lists - val updateColors = settings.getManageCalendarColors() - - for (list in DmfsTaskList.find(account, provider, LocalTaskList.Factory, null, null)) - list.syncId?.let { - val url = it.toHttpUrl() - val info = remoteTaskLists[url] - if (info == null) { - Logger.log.fine("Deleting obsolete local task list $url") - list.delete() - } else { - // remote CollectionInfo found for this local collection, update data - Logger.log.log(Level.FINE, "Updating local task list $url", info) - list.update(info, updateColors) - // we already have a local task list for this remote collection, don't take into consideration anymore - remoteTaskLists -= url - } - } - - // create new local task lists - for ((_,info) in remoteTaskLists) { - Logger.log.log(Level.INFO, "Adding local task list", info) - LocalTaskList.create(account, provider, info) - } - } } \ No newline at end of file diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/TasksSyncManager.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/TasksSyncManager.kt index 86fd2c001..e5c4b4ca7 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/TasksSyncManager.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/TasksSyncManager.kt @@ -19,6 +19,7 @@ import at.bitfire.dav4jvm.property.webdav.GetETag import at.bitfire.dav4jvm.property.webdav.SyncToken import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.network.HttpClient @@ -29,6 +30,7 @@ import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.util.lastSegment import at.bitfire.ical4android.InvalidCalendarException import at.bitfire.ical4android.Task +import at.bitfire.ical4android.UsesThreadContextClassLoader import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -45,6 +47,7 @@ import java.util.logging.Level /** * Synchronization manager for CalDAV collections; handles tasks (VTODO) */ +@UsesThreadContextClassLoader class TasksSyncManager @AssistedInject constructor( @Assisted account: Account, @Assisted accountSettings: AccountSettings, @@ -53,9 +56,10 @@ class TasksSyncManager @AssistedInject constructor( @Assisted authority: String, @Assisted syncResult: SyncResult, @Assisted localCollection: LocalTaskList, + @Assisted collection: Collection, @ApplicationContext context: Context, db: AppDatabase -): SyncManager(account, accountSettings, httpClient, extras, authority, syncResult, localCollection, context, db) { +): SyncManager(account, accountSettings, httpClient, extras, authority, syncResult, localCollection, collection, context, db) { @AssistedFactory interface Factory { @@ -66,7 +70,8 @@ class TasksSyncManager @AssistedInject constructor( extras: Array, authority: String, syncResult: SyncResult, - localCollection: LocalTaskList + localCollection: LocalTaskList, + collection: Collection ): TasksSyncManager }