Skip to content

Provide collection to SyncManager #881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ 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
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 okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Protocol
import okhttp3.internal.http.StatusLine
import okhttp3.mockwebserver.MockResponse
Expand Down Expand Up @@ -98,13 +100,18 @@ class SyncManagerTest {
}


private fun syncManager(collection: LocalTestCollection, syncResult: SyncResult = SyncResult()) =
private fun syncManager(
localCollection: LocalTestCollection,
syncResult: SyncResult = SyncResult(),
collection: Collection = Collection(0,0, type = "", url = "http://a".toHttpUrl())
) =
TestSyncManager(
account,
arrayOf(),
"TestAuthority",
HttpClient.Builder(InstrumentationRegistry.getInstrumentation().targetContext).build(),
syncResult,
localCollection,
collection,
server,
context, db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,10 +31,11 @@ class TestSyncManager(
httpClient: HttpClient,
syncResult: SyncResult,
localCollection: LocalTestCollection,
collection: Collection,
val mockWebServer: MockWebServer,
context: Context,
db: AppDatabase
): SyncManager<LocalTestResource, LocalTestCollection, DavCollection>(account, AccountSettings(context, account), httpClient, extras, authority, syncResult, localCollection, context, db) {
): SyncManager<LocalTestResource, LocalTestCollection, DavCollection>(account, AccountSettings(context, account), httpClient, extras, authority, syncResult, localCollection, collection, context, db) {

override fun prepare(): Boolean {
collectionURL = mockWebServer.url("/")
Expand Down
59 changes: 30 additions & 29 deletions app/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,7 @@ class AddressBookSyncer(
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 {
// update local address books
val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CARDDAV)

val remoteAddressBooks = mutableMapOf<HttpUrl, Collection>()
Expand All @@ -89,30 +72,30 @@ class AddressBookSyncer(
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
}

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)
}
Expand All @@ -128,17 +111,35 @@ class AddressBookSyncer(
}
}

return true
}
// Sync 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<String>,
authority: String,
httpClient: Lazy<HttpClient>,
provider: ContentProviderClient,
syncResult: SyncResult
syncResult: SyncResult,
collection: Collection
) {
try {
val accountSettings = AccountSettings(context, account)
Expand All @@ -164,7 +165,7 @@ class AddressBookSyncer(
Logger.log.info("Taking settings from: ${addressBook.mainAccount}")

val syncManagerFactory = entryPoint.contactsSyncManagerFactory()
val syncManager = syncManagerFactory.contactsSyncManager(account, accountSettings, httpClient.value, extras, authority, syncResult, provider, addressBook)
val syncManager = syncManagerFactory.contactsSyncManager(account, accountSettings, httpClient.value, extras, authority, syncResult, provider, addressBook, collection)
syncManager.performSync()

} catch(e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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<LocalEvent, LocalCalendar, DavCalendar>(account, accountSettings, httpClient, extras, authority, syncResult, localCalendar, context, db) {
): SyncManager<LocalEvent, LocalCalendar, DavCalendar>(account, accountSettings, httpClient, extras, authority, syncResult, localCalendar, collection, context, db) {

@AssistedFactory
interface Factory {
Expand All @@ -74,7 +78,8 @@ class CalendarSyncManager @AssistedInject constructor(
httpClient: HttpClient,
authority: String,
syncResult: SyncResult,
localCalendar: LocalCalendar
localCalendar: LocalCalendar,
collection: Collection
): CalendarSyncManager
}

Expand Down
53 changes: 31 additions & 22 deletions app/src/main/kotlin/at/bitfire/davdroid/sync/CalendarSyncer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,41 +53,26 @@ class CalendarSyncer(context: Context): Syncer(context) {
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 syncManagerFactory = entryPoint.calendarSyncManagerFactory()
val syncManager = syncManagerFactory.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)

// Sync remote collection info (DB) to local calendars (content provider)
val remoteCalendars = mutableMapOf<HttpUrl, Collection>()
val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV)
if (service != null)
for (collection in db.collectionDao().getSyncCalendars(service.id)) {
remoteCalendars[collection.url] = collection
}

// delete/update local calendars
val updateColors = settings.getManageCalendarColors()
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
}
Expand All @@ -98,5 +83,29 @@ class CalendarSyncer(context: Context): Syncer(context) {
Logger.log.log(Level.INFO, "Adding local calendar", info)
LocalCalendar.create(account, provider, info)
}

// 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 syncManagerFactory = entryPoint.calendarSyncManagerFactory()
val syncManager = syncManagerFactory.calendarSyncManager(
account,
accountSettings,
extras,
httpClient.value,
authority,
syncResult,
calendar,
collection
)
syncManager.performSync()
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<LocalAddress, LocalAddressBook, DavAddressBook>(
account, accountSettings, httpClient, extras, authority, syncResult, localAddressBook,
account, accountSettings, httpClient, extras, authority, syncResult, localAddressBook, collection,
context, db
) {

Expand All @@ -121,7 +123,8 @@ class ContactsSyncManager @AssistedInject constructor(
authority: String,
syncResult: SyncResult,
provider: ContentProviderClient,
localAddressBook: LocalAddressBook
localAddressBook: LocalAddressBook,
collection: Collection
): ContactsSyncManager
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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<LocalJtxICalObject, LocalJtxCollection, DavCalendar>(account, accountSettings, httpClient, extras, authority, syncResult, localCollection, context, db) {
): SyncManager<LocalJtxICalObject, LocalJtxCollection, DavCalendar>(account, accountSettings, httpClient, extras, authority, syncResult, localCollection, collection, context, db) {

@AssistedFactory
interface Factory {
Expand All @@ -63,7 +67,8 @@ class JtxSyncManager @AssistedInject constructor(
httpClient: HttpClient,
authority: String,
syncResult: SyncResult,
localCollection: LocalJtxCollection
localCollection: LocalJtxCollection,
collection: Collection
): JtxSyncManager
}

Expand Down
Loading
Loading