Commit 3dbd5e3d authored by Ricki Hirner's avatar Ricki Hirner

Synchronization error messages / notifications

parent 7fdcb371
......@@ -55,7 +55,6 @@ android {
disable 'ImpliedQuantity', 'MissingQuantity' // quantities from Transifex may vary
disable 'MissingTranslation', 'ExtraTranslation' // translations from Transifex are not always up to date
disable "OnClick" // doesn't recognize Kotlin onClick methods
disable 'Recycle' // doesn't understand Lombok's @Cleanup
disable 'RtlEnabled'
disable 'RtlHardcoded'
disable 'Typos'
......@@ -85,7 +84,7 @@ dependencies {
compile 'com.github.yukuku:ambilwarna:2.0.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.9.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.10.0'
compile 'commons-io:commons-io:2.6'
compile 'dnsjava:dnsjava:2.1.8'
compile 'org.apache.commons:commons-lang3:3.6'
......@@ -95,8 +94,8 @@ dependencies {
androidTestCompile 'com.android.support.test:runner:1.0.1'
androidTestCompile 'com.android.support.test:rules:1.0.1'
androidTestCompile 'junit:junit:4.12'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.10.0'
testCompile 'junit:junit:4.12'
testCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
testCompile 'com.squareup.okhttp3:mockwebserver:3.10.0'
}
......@@ -10,14 +10,15 @@ package at.bitfire.davdroid
object Constants {
// notification IDs
@JvmField val NOTIFICATION_EXTERNAL_FILE_LOGGING = 1
@JvmField val NOTIFICATION_REFRESH_COLLECTIONS = 2
@JvmField val NOTIFICATION_CONTACTS_SYNC = 10
@JvmField val NOTIFICATION_CALENDAR_SYNC = 11
@JvmField val NOTIFICATION_TASK_SYNC = 12
@JvmField val NOTIFICATION_PERMISSIONS = 20
const val NOTIFICATION_EXTERNAL_FILE_LOGGING = 1
const val NOTIFICATION_REFRESH_COLLECTIONS = 2
@JvmField
val DEFAULT_SYNC_INTERVAL = 4 * 3600L // 4 hours
const val NOTIFICATION_PERMISSIONS = 20
const val NOTIFICATION_SUBSCRIPTION = 21
const val DAVDROID_GREEN_RGBA = 0xFF8bc34a.toInt()
const val DEFAULT_SYNC_INTERVAL = 4 * 3600L // 4 hours
}
......@@ -325,7 +325,7 @@ class DavService: Service() {
debugIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account)
val nm = NotificationUtils.createChannels(this)
val notify = NotificationCompat.Builder(this, NotificationUtils.CHANNEL_SYNC_PROBLEMS)
val notify = NotificationCompat.Builder(this, NotificationUtils.CHANNEL_SYNC_ERRORS)
.setSmallIcon(R.drawable.ic_sync_error_notification)
.setContentTitle(getString(R.string.dav_service_refresh_failed))
.setContentText(getString(R.string.dav_service_refresh_couldnt_refresh))
......
......@@ -94,6 +94,9 @@ class LocalAddressBook(
}
override val title = account.name
override val uniqueId = "contacts-${account.name}"
/**
* Whether contact groups ([LocalGroup]) are included in query results
* and are affected by updates/deletes on generic members.
......
......@@ -16,6 +16,7 @@ import android.content.ContentValues
import android.net.Uri
import android.provider.CalendarContract
import android.provider.CalendarContract.*
import at.bitfire.davdroid.Constants
import at.bitfire.davdroid.DavUtils
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.CollectionInfo
......@@ -35,8 +36,6 @@ class LocalCalendar private constructor(
companion object {
private const val defaultColor = 0xFF8bc34a.toInt() // light green 500
private const val COLUMN_SYNC_STATE = Calendars.CAL_SYNC1
fun create(account: Account, provider: ContentProviderClient, info: CollectionInfo): Uri {
......@@ -59,7 +58,7 @@ class LocalCalendar private constructor(
values.put(Calendars.CALENDAR_DISPLAY_NAME, if (info.displayName.isNullOrBlank()) DavUtils.lastSegmentOfUrl(info.url) else info.displayName)
if (withColor)
values.put(Calendars.CALENDAR_COLOR, info.color ?: defaultColor)
values.put(Calendars.CALENDAR_COLOR, info.color ?: Constants.DAVDROID_GREEN_RGBA)
if (info.readOnly || info.forceReadOnly)
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ)
......@@ -86,6 +85,11 @@ class LocalCalendar private constructor(
}
}
override val title: String
get() = displayName ?: id.toString()
override val uniqueId = "calendar-$id"
override var lastSyncState: SyncState?
get() = provider.query(calendarSyncURI(), arrayOf(COLUMN_SYNC_STATE), null, null, null)?.let { cursor ->
if (cursor.moveToNext())
......
......@@ -12,12 +12,12 @@ import at.bitfire.davdroid.model.SyncState
interface LocalCollection<out T: LocalResource> {
var lastSyncState: SyncState?
/** collection title (used for user notifications etc.) **/
val title: String
/** unique ID, used to distinguish notifications for different collections **/
val uniqueId: String
/**
* Unique collection ID. Used to distinguish collections in Android notifications.
*/
val uid: String
var lastSyncState: SyncState?
fun findDeleted(): List<T>
fun findDirty(): List<T>
......
......@@ -44,7 +44,7 @@ class LocalContact: AndroidContact, LocalAddress {
constructor(addressBook: AndroidAddressBook<LocalContact,*>, values: ContentValues)
: super(addressBook, values) {
flags = values.getAsInteger(COLUMN_FLAGS)
flags = values.getAsInteger(COLUMN_FLAGS) ?: 0
}
constructor(addressBook: AndroidAddressBook<LocalContact,*>, contact: Contact, fileName: String?, eTag: String?, flags: Int)
......
......@@ -49,17 +49,13 @@ class LocalEvent: AndroidEvent, LocalResource {
private constructor(calendar: AndroidCalendar<*>, values: ContentValues): super(calendar, values) {
fileName = values.getAsString(Events._SYNC_ID)
eTag = values.getAsString(COLUMN_ETAG)
flags = values.getAsInteger(COLUMN_FLAGS)
flags = values.getAsInteger(COLUMN_FLAGS) ?: 0
}
override fun populateEvent(row: ContentValues) {
super.populateEvent(row)
val event = requireNotNull(event)
fileName = row.getAsString(Events._SYNC_ID)
eTag = row.getAsString(COLUMN_ETAG)
flags = row.getAsInteger(COLUMN_FLAGS)
event.uid = row.getAsString(Events.UID_2445)
event.sequence = row.getAsInteger(COLUMN_SEQUENCE)
......
......@@ -106,7 +106,9 @@ class LocalGroup: AndroidGroup, LocalAddress {
constructor(addressBook: AndroidAddressBook<out AndroidContact, LocalGroup>, values: ContentValues):
super(addressBook, values)
super(addressBook, values) {
flags = values.getAsInteger(COLUMN_FLAGS) ?: 0
}
constructor(addressBook: AndroidAddressBook<out AndroidContact, LocalGroup>, contact: Contact, fileName: String?, eTag: String?, flags: Int)
: super(addressBook, contact, fileName, eTag) {
......
......@@ -41,6 +41,7 @@ class LocalTask: AndroidTask, LocalResource {
id = values.getAsLong(Tasks._ID)
fileName = values.getAsString(Tasks._SYNC_ID)
eTag = values.getAsString(COLUMN_ETAG)
flags = values.getAsInteger(COLUMN_FLAGS) ?: 0
}
......@@ -49,9 +50,6 @@ class LocalTask: AndroidTask, LocalResource {
override fun populateTask(values: ContentValues) {
super.populateTask(values)
fileName = values.getAsString(Events._SYNC_ID)
eTag = values.getAsString(COLUMN_ETAG)
val task = requireNotNull(task)
task.sequence = values.getAsInteger(COLUMN_SEQUENCE)
}
......
......@@ -15,6 +15,7 @@ import android.content.ContentValues
import android.content.Context
import android.net.Uri
import android.os.Build
import at.bitfire.davdroid.Constants
import at.bitfire.davdroid.DavUtils
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.CollectionInfo
......@@ -34,8 +35,6 @@ class LocalTaskList private constructor(
companion object {
private const val defaultColor = 0xFFC3EA6E.toInt() // "DAVdroid green"
fun tasksProviderAvailable(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return context.packageManager.resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null
......@@ -78,13 +77,18 @@ class LocalTaskList private constructor(
values.put(TaskLists.LIST_NAME, if (info.displayName.isNullOrBlank()) DavUtils.lastSegmentOfUrl(info.url) else info.displayName)
if (withColor)
values.put(TaskLists.LIST_COLOR, info.color ?: defaultColor)
values.put(TaskLists.LIST_COLOR, info.color ?: Constants.DAVDROID_GREEN_RGBA)
return values
}
}
override val title: String
get() = name ?: id.toString()
override val uniqueId = "tasks-$id"
override var lastSyncState: SyncState?
get() {
try {
......
......@@ -71,25 +71,27 @@ abstract class BaseDavSyncManager<ResourceType: LocalResource, out CollectionTyp
// Remove locally deleted entries from server (if they have a name, i.e. if they were uploaded before),
// but only if they don't have changed on the server. Then finally remove them from the local address book.
val localList = localCollection.findDeleted()
for (local in localList) {
abortIfCancelled()
val fileName = local.fileName
if (fileName != null) {
Logger.log.info("$fileName has been deleted locally -> deleting from server")
val remote = DavResource(httpClient.okHttpClient, collectionURL.newBuilder().addPathSegment(fileName).build())
try {
remote.delete(local.eTag)
numDeleted++
} catch (e: HttpException) {
Logger.log.warning("Couldn't delete $fileName from server; ignoring (may be downloaded again)")
}
} else
Logger.log.info("Removing local record #${local.id} which has been deleted locally and was never uploaded")
local.delete()
syncResult.stats.numDeletes++
}
for (local in localList)
useLocal(local, {
abortIfCancelled()
val fileName = local.fileName
if (fileName != null) {
Logger.log.info("$fileName has been deleted locally -> deleting from server")
useRemote(DavResource(httpClient.okHttpClient, collectionURL.newBuilder().addPathSegment(fileName).build()), { remote ->
try {
remote.delete(local.eTag)
numDeleted++
} catch (e: HttpException) {
Logger.log.warning("Couldn't delete $fileName from server; ignoring (may be downloaded again)")
}
})
} else
Logger.log.info("Removing local record #${local.id} which has been deleted locally and was never uploaded")
local.delete()
syncResult.stats.numDeletes++
})
Logger.log.info("Removed $numDeleted record(s) from server")
return numDeleted > 0
}
......@@ -104,48 +106,49 @@ abstract class BaseDavSyncManager<ResourceType: LocalResource, out CollectionTyp
var numUploaded = 0
// upload dirty contacts
for (local in localCollection.findDirty()) {
abortIfCancelled()
if (local.fileName == null) {
Logger.log.fine("Generating file name/UID for local record #${local.id}")
local.assignNameAndUID()
}
val fileName = local.fileName!!
val remote = DavResource(httpClient.okHttpClient, collectionURL.newBuilder().addPathSegment(fileName).build())
// generate entity to upload (VCard, iCal, whatever)
val body = prepareUpload(local)
for (local in localCollection.findDirty())
useLocal(local, {
abortIfCancelled()
try {
if (local.eTag == null) {
Logger.log.info("Uploading new record $fileName")
remote.put(body, null, true)
} else {
Logger.log.info("Uploading locally modified record $fileName")
remote.put(body, local.eTag, false)
if (local.fileName == null) {
Logger.log.fine("Generating file name/UID for local record #${local.id}")
local.assignNameAndUID()
}
numUploaded++
} catch(e: ConflictException) {
// we can't interact with the user to resolve the conflict, so we treat 409 like 412
Logger.log.log(Level.INFO, "Edit conflict, ignoring", e)
} catch(e: PreconditionFailedException) {
Logger.log.log(Level.INFO, "Resource has been modified on the server before upload, ignoring", e)
}
val newETag = remote.properties[GetETag::class.java]
val eTag: String?
if (newETag != null) {
eTag = newETag.eTag
Logger.log.fine("Received new ETag=$eTag after uploading")
} else {
Logger.log.fine("Didn't receive new ETag after uploading, setting to null")
eTag = null
}
local.clearDirty(eTag)
}
val fileName = local.fileName!!
useRemote(DavResource(httpClient.okHttpClient, collectionURL.newBuilder().addPathSegment(fileName).build()), { remote ->
// generate entity to upload (VCard, iCal, whatever)
val body = prepareUpload(local)
try {
if (local.eTag == null) {
Logger.log.info("Uploading new record $fileName")
remote.put(body, null, true)
} else {
Logger.log.info("Uploading locally modified record $fileName")
remote.put(body, local.eTag, false)
}
numUploaded++
} catch(e: ConflictException) {
// we can't interact with the user to resolve the conflict, so we treat 409 like 412
Logger.log.log(Level.INFO, "Edit conflict, ignoring", e)
} catch(e: PreconditionFailedException) {
Logger.log.log(Level.INFO, "Resource has been modified on the server before upload, ignoring", e)
}
val newETag = remote.properties[GetETag::class.java]
val eTag: String?
if (newETag != null) {
eTag = newETag.eTag
Logger.log.fine("Received new ETag=$eTag after uploading")
} else {
Logger.log.fine("Didn't receive new ETag after uploading, setting to null")
eTag = null
}
local.clearDirty(eTag)
})
})
Logger.log.info("Sent $numUploaded record(s) to server")
return numUploaded > 0
}
......@@ -168,14 +171,14 @@ abstract class BaseDavSyncManager<ResourceType: LocalResource, out CollectionTyp
}
}
override fun syncState(forceRefresh: Boolean): SyncState? {
override fun syncState(forceRefresh: Boolean) = useRemoteCollection { remote ->
if (forceRefresh)
davCollection.propfind(0, GetCTag.NAME, SyncToken.NAME)
remote.propfind(0, GetCTag.NAME, SyncToken.NAME)
return davCollection.properties[SyncToken::class.java]?.token?.let {
remote.properties[SyncToken::class.java]?.token?.let {
SyncState(SyncState.Type.SYNC_TOKEN, it)
} ?:
davCollection.properties[GetCTag::class.java]?.cTag?.let {
remote.properties[GetCTag::class.java]?.cTag?.let {
SyncState(SyncState.Type.CTAG, it)
}
}
......@@ -194,26 +197,25 @@ abstract class BaseDavSyncManager<ResourceType: LocalResource, out CollectionTyp
val changes = RemoteChanges(syncState, false)
for ((name, remote) in remoteResources) {
val local = localCollection.findByName(name)
if (local == null) {
Logger.log.info("$name has been added remotely")
changes.updated += remote
} else {
val localETag = local.eTag
val remoteETag = remote.properties[GetETag::class.java]?.eTag ?: throw DavException("Server didn't provide ETag")
if (localETag == remoteETag)
Logger.log.fine("$name has not been changed on server (ETag still $remoteETag)")
else {
Logger.log.info("$name has been changed on server (current ETag=$remoteETag, last known ETag=$localETag)")
for ((name, remote) in remoteResources)
useLocal(localCollection.findByName(name), { local ->
if (local == null) {
Logger.log.info("$name has been added remotely")
changes.updated += remote
} else {
val localETag = local.eTag
val remoteETag = remote.properties[GetETag::class.java]?.eTag ?: throw DavException("Server didn't provide ETag")
if (localETag == remoteETag)
Logger.log.fine("$name has not been changed on server (ETag still $remoteETag)")
else {
Logger.log.info("$name has been changed on server (current ETag=$remoteETag, last known ETag=$localETag)")
changes.updated += remote
}
// mark as remotely present, so that this resource won't be deleted at the end
local.updateFlags(LocalResource.FLAG_REMOTELY_PRESENT)
}
// mark as remotely present, so that this resource won't be deleted at the end
local.updateFlags(LocalResource.FLAG_REMOTELY_PRESENT)
}
}
})
return changes
}
......@@ -261,4 +263,26 @@ abstract class BaseDavSyncManager<ResourceType: LocalResource, out CollectionTyp
override fun postProcess() {
}
protected fun<T: LocalResource?, R> useLocal(local: T, body: (T) -> R): R {
currentLocalResource += local
val result = body(local)
currentLocalResource.pop()
return result
}
protected fun<T: DavResource, R> useRemote(remote: T, body: (T) -> R): R {
currentRemoteResource += remote
val result = body(remote)
currentRemoteResource.pop()
return result
}
protected fun<R> useRemoteCollection(body: (RemoteType) -> R): R {
currentRemoteResource += davCollection
val result = body(davCollection)
currentRemoteResource.pop()
return result
}
}
\ No newline at end of file
......@@ -20,7 +20,6 @@ import at.bitfire.dav4android.property.GetCTag
import at.bitfire.dav4android.property.GetETag
import at.bitfire.dav4android.property.SyncToken
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.Constants
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.resource.LocalCalendar
import at.bitfire.davdroid.resource.LocalEvent
......@@ -54,8 +53,6 @@ class CalendarSyncManager(
const val MULTIGET_MAX_RESOURCES = 30
}
override val notificationId = Constants.NOTIFICATION_CALENDAR_SYNC
override fun prepare(): Boolean {
if (!super.prepare())
......@@ -71,23 +68,23 @@ class CalendarSyncManager(
}
override fun queryCapabilities() {
davCollection.propfind(0, GetCTag.NAME, SyncToken.NAME)
useRemoteCollection { it.propfind(0, GetCTag.NAME, SyncToken.NAME) }
}
override fun syncAlgorithm() = SyncAlgorithm.PROPFIND_REPORT
override fun prepareUpload(resource: LocalEvent): RequestBody {
override fun prepareUpload(resource: LocalEvent): RequestBody = useLocal(resource, {
val event = requireNotNull(resource.event)
Logger.log.log(Level.FINE, "Preparing upload of event ${resource.fileName}", event)
val os = ByteArrayOutputStream()
event.write(os)
return RequestBody.create(
RequestBody.create(
DavCalendar.MIME_ICALENDAR_UTF8,
os.toByteArray()
)
}
})
override fun listAllRemote(): Map<String, DavResource> {
// calculate time range limits
......@@ -98,60 +95,62 @@ class CalendarSyncManager(
limitStart = calendar.time
}
// fetch list of remote VEVENTs and build hash table to index file name
Logger.log.info("Querying events since $limitStart")
davCollection.calendarQuery("VEVENT", limitStart, null)
return useRemoteCollection { remote ->
// fetch list of remote VEVENTs and build hash table to index file name
Logger.log.info("Querying events since $limitStart")
remote.calendarQuery("VEVENT", limitStart, null)
val result = LinkedHashMap<String, DavResource>(davCollection.members.size)
for (iCal in davCollection.members) {
val fileName = iCal.fileName()
Logger.log.fine("Found remote VEVENT: $fileName")
result[fileName] = iCal
val result = LinkedHashMap<String, DavResource>(remote.members.size)
for (iCal in remote.members) {
val fileName = iCal.fileName()
Logger.log.fine("Found remote VEVENT: $fileName")
result[fileName] = iCal
}
result
}
return result
}
override fun processRemoteChanges(changes: RemoteChanges) {
for (name in changes.deleted) {
for (name in changes.deleted)
localCollection.findByName(name)?.let {
Logger.log.info("Deleting local event $name")
it.delete()
useLocal(it, { local -> local.delete() })
syncResult.stats.numDeletes++
}
}
val toDownload = changes.updated.map { it.location }
Logger.log.info("Downloading ${toDownload.size} resources (${MULTIGET_MAX_RESOURCES} at once)")
Logger.log.info("Downloading ${toDownload.size} resources ($MULTIGET_MAX_RESOURCES at once)")
for (bunch in toDownload.chunked(MULTIGET_MAX_RESOURCES)) {
if (bunch.size == 1) {
if (bunch.size == 1)
// only one contact, use GET
val remote = DavResource(httpClient.okHttpClient, bunch.first())
val body = remote.get(DavCalendar.MIME_ICALENDAR.toString())
useRemote(DavResource(httpClient.okHttpClient, bunch.first()), { remote ->
val body = remote.get(DavCalendar.MIME_ICALENDAR.toString())
// CalDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc4791#section-5.3.4]
val eTag = remote.properties[GetETag::class.java]?.eTag
?: throw DavException("Received CalDAV GET response without ETag for ${remote.location}")
// CalDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc4791#section-5.3.4]
val eTag = remote.properties[GetETag::class.java]?.eTag
?: throw DavException("Received CalDAV GET response without ETag for ${remote.location}")
body.charStream().use { reader ->
processVEvent(remote.fileName(), eTag, reader)
}
} else {
body.charStream().use { reader ->
processVEvent(remote.fileName(), eTag, reader)
}
})
else {
// multiple contacts, use multi-get
davCollection.multiget(bunch)
useRemoteCollection { it.multiget(bunch) }
// process multiget results
for (remote in davCollection.members) {
val eTag = remote.properties[GetETag::class.java]?.eTag
?: throw DavException("Received multi-get response without ETag")
for (remote in davCollection.members)
useRemote(remote, {
val eTag = remote.properties[GetETag::class.java]?.eTag
?: throw DavException("Received multi-get response without ETag")
val calendarData = remote.properties[CalendarData::class.java]
val iCalendar = calendarData?.iCalendar
?: throw DavException("Received multi-get response without task data")
val calendarData = remote.properties[CalendarData::class.java]
val iCalendar = calendarData?.iCalendar
?: throw DavException("Received multi-get response without task data")
processVEvent(remote.fileName(), eTag, StringReader(iCalendar))
}
processVEvent(remote.fileName(), eTag, StringReader(iCalendar))
})
}
abortIfCancelled()
......@@ -174,18 +173,19 @@ class CalendarSyncManager(
val newData = events.first()
// delete local event, if it exists
val localEvent = localCollection.findByName(fileName)
if (localEvent != null) {
Logger.log.info("Updating $fileName in local calendar")
localEvent.eTag = eTag
localEvent.update(newData)
syncResult.stats.numUpdates++
} else {
Logger.log.info("Adding $fileName to local calendar")
val newEvent = LocalEvent(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)
newEvent.add()
syncResult.stats.numInserts++
}
useLocal(localCollection.findByName(fileName), { local ->
if (local != null) {
Logger.log.info("Updating $fileName in local calendar")
local.eTag = eTag
local.update(newData)
syncResult.stats.numUpdates++
} else {
Logger.log.info("Adding $fileName to local calendar")
val newEvent = LocalEvent(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)
newEvent.add()
syncResult.stats.numInserts++
}
})
} else
Logger.log.severe("Received VCALENDAR with not exactly one VEVENT with UID, but without RECURRENCE-ID; ignoring $fileName")
}
......
......@@ -18,7 +18,9 @@ import at.bitfire.dav4android.DavAddressBook
import at.bitfire.dav4android.DavResource
import at.bitfire.dav4android.exception.DavException
import at.bitfire.dav4android.property.*
import at.bitfire.davdroid.*
import at.bitfire.davdroid.AccountSettings
import at.bitfire.davdroid.HttpClient
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.resource.*
import at.bitfire.davdroid.settings.ISettings
......@@ -93,8 +95,6 @@ class ContactsSyncManager(
private var hasVCard4 = false
private val groupMethod = accountSettings.getGroupMethod()
override val notificationId = Constants.NOTIFICATION_CONTACTS_SYNC
override fun prepare(): Boolean {
if (!super.prepare())
......@@ -117,18 +117,19 @@ class ContactsSyncManager(
}
override fun queryCapabilities() {
// prepare remote address book
davCollection.propfind(0, SupportedAddressData.NAME, GetCTag.NAME, SyncToken.NAME)
useRemoteCollection { dav ->
dav.propfind(0, SupportedAddressData.NAME, GetCTag.NAME, SyncToken.NAME)
val properties = davCollection.properties
properties[SupportedAddressData::class.java]?.let {
hasVCard4 = it.hasVCard4()
}
Logger.log.info("Server advertises VCard/4 support: $hasVCard4")
val properties = dav.properties
properties[SupportedAddressData::class.java]?.let {
hasVCard4 = it.hasVCard4()
}
Logger.log.info("Server advertises VCard/4 support: $hasVCard4")
Logger.log.info("Contact group method: $groupMethod")
// in case of GROUP_VCARDs, treat groups as contacts in the local address book
localCollection.includeGroups = groupMethod == GroupMethod.GROUP_VCARDS
Logger.log.info("Contact group method: $groupMethod")
// in case of GROUP_VCARDs, treat groups as contacts in the local address book
localCollection.includeGroups = groupMethod == GroupMethod.GROUP_VCARDS
}
}