Tom Hacohen 5 years ago
parent 7209d634a5
commit 88dad0a538

@ -199,7 +199,7 @@ class LocalAddressBook(
accountManager.removeAccount(account, null, null)
}
fun findAll(): List<LocalAddress> = queryContacts(RawContacts.DELETED + "== 0", null)
override fun findAll(): List<LocalAddress> = queryContacts(RawContacts.DELETED + "== 0", null)
/**
* Returns an array of local contacts/groups which have been deleted locally. (DELETED != 0).

@ -13,82 +13,114 @@ import android.content.ContentProviderClient
import android.content.ContentProviderOperation
import android.content.ContentUris
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
import android.os.RemoteException
import android.provider.CalendarContract
import android.provider.CalendarContract.Calendars
import android.provider.CalendarContract.Events
import android.provider.CalendarContract.Reminders
import android.text.TextUtils
import android.provider.CalendarContract.*
import at.bitfire.ical4android.*
import com.etesync.syncadapter.App
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity
import org.apache.commons.lang3.StringUtils
import java.io.FileNotFoundException
import java.util.*
import java.util.logging.Level
import net.fortuna.ical4j.model.component.VTimeZone
class LocalCalendar private constructor(
account: Account,
provider: ContentProviderClient,
id: Long
): AndroidCalendar<LocalEvent>(account, provider, LocalEvent.Factory, id), LocalCollection<LocalEvent> {
import org.apache.commons.lang3.StringUtils
companion object {
val defaultColor = -0x743cb6 // light green 500
import java.io.FileNotFoundException
import java.util.LinkedList
import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.AndroidCalendarFactory
import at.bitfire.ical4android.BatchOperation
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.ical4android.DateUtils
class LocalCalendar protected constructor(account: Account, provider: ContentProviderClient, id: Long) : AndroidCalendar(account, provider, LocalEvent.Factory.INSTANCE, id), LocalCollection<LocalEvent> {
override val deleted: Array<LocalEvent>
@Throws(CalendarStorageException::class)
get() = queryEvents(Events.DELETED + "!=0 AND " + Events.ORIGINAL_ID + " IS NULL", null) as Array<LocalEvent>
override val withoutFileName: Array<LocalEvent>
@Throws(CalendarStorageException::class)
get() = queryEvents(Events._SYNC_ID + " IS NULL AND " + Events.ORIGINAL_ID + " IS NULL", null) as Array<LocalEvent>
val all: Array<LocalEvent>
@Throws(CalendarStorageException::class)
get() = queryEvents(null, null) as Array<LocalEvent>
override// get dirty events which are required to have an increased SEQUENCE value
// sequence has not been assigned yet (i.e. this event was just locally created)
val dirty: Array<LocalEvent>
@Throws(CalendarStorageException::class, FileNotFoundException::class)
get() {
val dirty = LinkedList<LocalEvent>()
for (event in queryEvents(Events.DIRTY + "!=0 AND " + Events.DELETED + "==0 AND " + Events.ORIGINAL_ID + " IS NULL", null) as Array<LocalEvent>) {
if (event.event.sequence == null)
event.event.sequence = 0
else if (event.weAreOrganizer)
event.event.sequence++
dirty.add(event)
}
val COLUMN_CTAG = Calendars.CAL_SYNC1
fun create(account: Account, provider: ContentProviderClient, journalEntity: JournalEntity): Uri {
val values = valuesFromCollectionInfo(journalEntity, true)
return dirty.toTypedArray()
// ACCOUNT_NAME and ACCOUNT_TYPE are required (see docs)! If it's missing, other apps will crash.
values.put(Calendars.ACCOUNT_NAME, account.name)
values.put(Calendars.ACCOUNT_TYPE, account.type)
values.put(Calendars.OWNER_ACCOUNT, account.name)
// flag as visible & synchronizable at creation, might be changed by user at any time
values.put(Calendars.VISIBLE, 1)
values.put(Calendars.SYNC_EVENTS, 1)
return AndroidCalendar.create(account, provider, values)
}
override fun eventBaseInfoColumns(): Array<String> {
return BASE_INFO_COLUMNS
}
fun findByName(account: Account, provider: ContentProviderClient, factory: Factory, name: String): LocalCalendar?
= AndroidCalendar.find(account, provider, factory, Calendars.NAME + "==?", arrayOf(name)).firstOrNull()
@Throws(CalendarStorageException::class)
fun update(journalEntity: JournalEntity, updateColor: Boolean) {
update(valuesFromCollectionInfo(journalEntity, updateColor))
private fun valuesFromCollectionInfo(journalEntity: JournalEntity, withColor: Boolean): ContentValues {
val info = journalEntity.info
val values = ContentValues()
values.put(Calendars.NAME, info.uid)
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.displayName)
if (withColor)
values.put(Calendars.CALENDAR_COLOR, if (info.color != null) info.color else defaultColor)
if (journalEntity.isReadOnly)
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ)
else {
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER)
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1)
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1)
}
info.timeZone?.let { tzData ->
try {
val timeZone = DateUtils.parseVTimeZone(tzData)
timeZone.timeZoneId?.let { tzId ->
values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(tzId.value))
}
} catch(e: IllegalArgumentException) {
App.log.log(Level.WARNING, "Couldn't parse calendar default time zone", e)
}
}
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT)
values.put(Calendars.ALLOWED_AVAILABILITY, StringUtils.join(intArrayOf(Reminders.AVAILABILITY_TENTATIVE, Reminders.AVAILABILITY_FREE, Reminders.AVAILABILITY_BUSY), ","))
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, StringUtils.join(intArrayOf(CalendarContract.Attendees.TYPE_OPTIONAL, CalendarContract.Attendees.TYPE_REQUIRED, CalendarContract.Attendees.TYPE_RESOURCE), ", "))
return values
}
}
@Throws(CalendarStorageException::class)
override fun findByUid(uid: String): LocalEvent? {
val ret = queryEvents(Events._SYNC_ID + " =? ", arrayOf(uid)) as Array<LocalEvent>
return if (ret != null && ret.size > 0) {
ret[0]
} else null
fun update(journalEntity: JournalEntity, updateColor: Boolean) =
update(valuesFromCollectionInfo(journalEntity, updateColor))
override fun findDeleted() =
queryEvents("${Events.DELETED}!=0 AND ${Events.ORIGINAL_ID} IS NULL", null)
override fun findDirty(): List<LocalEvent> {
val dirty = LinkedList<LocalEvent>()
// get dirty events which are required to have an increased SEQUENCE value
for (localEvent in queryEvents("${Events.DIRTY}!=0 AND ${Events.ORIGINAL_ID} IS NULL", null)) {
val event = localEvent.event!!
val sequence = event.sequence
if (event.sequence == null) // sequence has not been assigned yet (i.e. this event was just locally created)
event.sequence = 0
else if (localEvent.weAreOrganizer)
event.sequence = sequence!! + 1
dirty += localEvent
}
return dirty
}
@Throws(CalendarStorageException::class)
override fun findWithoutFileName(): List<LocalEvent>
= queryEvents(Events._SYNC_ID + " IS NULL AND " + Events.ORIGINAL_ID + " IS NULL", null)
override fun findAll(): List<LocalEvent>
= queryEvents(null, null)
override fun findByUid(uid: String): LocalEvent?
= queryEvents(Events._SYNC_ID + " =? ", arrayOf(uid)).firstOrNull()
fun processDirtyExceptions() {
// process deleted exceptions
App.log.info("Processing deleted exceptions")
@ -163,19 +195,15 @@ class LocalCalendar protected constructor(account: Account, provider: ContentPro
}
@Throws(CalendarStorageException::class)
override fun count(): Long {
val where = Events.CALENDAR_ID + "=?"
val whereArgs = arrayOf(id.toString())
try {
val cursor = provider.query(
syncAdapterURI(Events.CONTENT_URI), null,
where, whereArgs, null)
Events.CALENDAR_ID + "=?", arrayOf(id.toString()), null)
try {
return cursor.count.toLong()
return cursor?.count.toLong()
} finally {
cursor.close()
cursor?.close()
}
} catch (e: RemoteException) {
throw CalendarStorageException("Couldn't query calendar events", e)
@ -183,20 +211,6 @@ class LocalCalendar protected constructor(account: Account, provider: ContentPro
}
class Factory : AndroidCalendarFactory {
override fun newInstance(account: Account, provider: ContentProviderClient, id: Long): AndroidCalendar {
return LocalCalendar(account, provider, id)
}
override fun newArray(size: Int): Array<AndroidCalendar?> {
return arrayOfNulls<LocalCalendar>(size) as Array<AndroidCalendar?>
}
companion object {
val INSTANCE = Factory()
}
}
/** Fix all of the etags of all of the non-dirty events to be non-null.
* Currently set to all ones.. */
@ -218,67 +232,9 @@ class LocalCalendar protected constructor(account: Account, provider: ContentPro
}
companion object {
val defaultColor = -0x743cb6 // light green 500
val COLUMN_CTAG = Calendars.CAL_SYNC1
object Factory: AndroidCalendarFactory<LocalCalendar> {
internal var BASE_INFO_COLUMNS = arrayOf(Events._ID, Events._SYNC_ID, LocalEvent.COLUMN_ETAG)
@Throws(CalendarStorageException::class)
fun create(account: Account, provider: ContentProviderClient, journalEntity: JournalEntity): Uri {
val values = valuesFromCollectionInfo(journalEntity, true)
// ACCOUNT_NAME and ACCOUNT_TYPE are required (see docs)! If it's missing, other apps will crash.
values.put(Calendars.ACCOUNT_NAME, account.name)
values.put(Calendars.ACCOUNT_TYPE, account.type)
values.put(Calendars.OWNER_ACCOUNT, account.name)
// flag as visible & synchronizable at creation, might be changed by user at any time
values.put(Calendars.VISIBLE, 1)
values.put(Calendars.SYNC_EVENTS, 1)
return AndroidCalendar.create(account, provider, values)
}
@Throws(FileNotFoundException::class, CalendarStorageException::class)
fun findByName(account: Account, provider: ContentProviderClient, factory: AndroidCalendarFactory, name: String): LocalCalendar? {
val ret = LocalCalendar.find(account, provider, factory, Calendars.NAME + "==?", arrayOf(name))
if (ret.size == 1) {
return ret[0]
} else {
App.log.severe("No calendar found for name $name")
return null
}
}
private fun valuesFromCollectionInfo(journalEntity: JournalEntity, withColor: Boolean): ContentValues {
val info = journalEntity.info
val values = ContentValues()
values.put(Calendars.NAME, info.uid)
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.displayName)
if (withColor)
values.put(Calendars.CALENDAR_COLOR, if (info.color != null) info.color else defaultColor)
if (journalEntity.isReadOnly)
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ)
else {
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER)
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1)
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1)
}
if (!TextUtils.isEmpty(info.timeZone)) {
val timeZone = DateUtils.parseVTimeZone(info.timeZone)
if (timeZone != null && timeZone.timeZoneId != null)
values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(timeZone.timeZoneId.value))
}
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT)
values.put(Calendars.ALLOWED_AVAILABILITY, StringUtils.join(intArrayOf(Reminders.AVAILABILITY_TENTATIVE, Reminders.AVAILABILITY_FREE, Reminders.AVAILABILITY_BUSY), ","))
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, StringUtils.join(intArrayOf(CalendarContract.Attendees.TYPE_OPTIONAL, CalendarContract.Attendees.TYPE_REQUIRED, CalendarContract.Attendees.TYPE_RESOURCE), ", "))
return values
}
override fun newInstance(account: Account, provider: ContentProviderClient, id: Long) =
LocalCalendar(account, provider, id)
}
}

@ -12,6 +12,7 @@ interface LocalCollection<out T: LocalResource<*>> {
fun findDeleted(): List<T>
fun findDirty(): List<T>
fun findWithoutFileName(): List<T>
fun findAll(): List<T>
fun findByUid(uid: String): T?

@ -9,161 +9,103 @@
package com.etesync.syncadapter.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.RemoteException
import com.etesync.syncadapter.model.CollectionInfo
import org.dmfs.provider.tasks.TaskContract.TaskLists
import org.dmfs.provider.tasks.TaskContract.Tasks
import java.io.FileNotFoundException
import at.bitfire.ical4android.AndroidTaskList
import at.bitfire.ical4android.AndroidTaskListFactory
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.ical4android.TaskProvider
class LocalTaskList protected constructor(account: Account, provider: TaskProvider, id: Long) : AndroidTaskList(account, provider, LocalTask.Factory.INSTANCE, id), LocalCollection<LocalTask> {
override val deleted: Array<LocalTask>
@Throws(CalendarStorageException::class)
get() = queryTasks(Tasks._DELETED + "!=0", null) as Array<LocalTask>
override val withoutFileName: Array<LocalTask>
@Throws(CalendarStorageException::class)
get() = queryTasks(Tasks._SYNC_ID + " IS NULL", null) as Array<LocalTask>
override// sequence has not been assigned yet (i.e. this task was just locally created)
val dirty: Array<LocalTask>
@Throws(CalendarStorageException::class, FileNotFoundException::class)
get() {
val tasks = queryTasks(Tasks._DIRTY + "!=0 AND " + Tasks._DELETED + "== 0", null) as Array<LocalTask>
for (task in tasks) {
if (task.task.sequence == null)
task.task.sequence = 0
else
task.task.sequence++
}
return tasks
}
override fun taskBaseInfoColumns(): Array<String> {
return BASE_INFO_COLUMNS
}
@Throws(CalendarStorageException::class)
fun update(info: CollectionInfo, updateColor: Boolean) {
update(valuesFromCollectionInfo(info, updateColor))
}
@Throws(CalendarStorageException::class)
override fun findByUid(uid: String): LocalTask? {
val ret = queryTasks(Tasks._SYNC_ID + " =? ", arrayOf(uid)) as Array<LocalTask>
return if (ret != null && ret.size > 0) {
ret[0]
} else null
}
@Throws(CalendarStorageException::class)
override fun count(): Long {
val where = Tasks.LIST_ID + "=?"
val whereArgs = arrayOf(id.toString())
try {
val cursor = provider.client.query(
syncAdapterURI(provider.tasksUri()), null,
where, whereArgs, null)
try {
return cursor.count.toLong()
} finally {
cursor.close()
}
} catch (e: RemoteException) {
throw CalendarStorageException("Couldn't query calendar events", e)
}
}
class Factory : AndroidTaskListFactory {
override fun newInstance(account: Account, provider: TaskProvider, id: Long): AndroidTaskList {
return LocalTaskList(account, provider, id)
}
override fun newArray(size: Int): Array<AndroidTaskList?> {
return arrayOfNulls<LocalTaskList>(size) as Array<AndroidTaskList?>
}
companion object {
val INSTANCE = Factory()
}
}
import com.etesync.syncadapter.model.JournalEntity
import org.dmfs.tasks.contract.TaskContract.TaskLists
import org.dmfs.tasks.contract.TaskContract.Tasks
class LocalTaskList private constructor(
account: Account,
provider: TaskProvider,
id: Long
): AndroidTaskList<LocalTask>(account, provider, LocalTask.Factory, id), LocalCollection<LocalTask> {
companion object {
val defaultColor = -0x3c1592 // "DAVdroid green"
val COLUMN_CTAG = TaskLists.SYNC_VERSION
internal var BASE_INFO_COLUMNS = arrayOf(Tasks._ID, Tasks._SYNC_ID, LocalTask.COLUMN_ETAG)
fun tasksProviderAvailable(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return context.packageManager.resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null
else {
val provider = TaskProvider.acquire(context, TaskProvider.ProviderName.OpenTasks)
provider?.use { return true }
return false
}
}
@Throws(CalendarStorageException::class)
fun create(account: Account, provider: TaskProvider, info: CollectionInfo): Uri {
val values = valuesFromCollectionInfo(info, true)
fun create(account: Account, provider: TaskProvider, journalEntity: JournalEntity): Uri {
val values = valuesFromCollectionInfo(journalEntity, true)
values.put(TaskLists.OWNER, account.name)
values.put(TaskLists.SYNC_ENABLED, 1)
values.put(TaskLists.VISIBLE, 1)
return AndroidTaskList.create(account, provider, values)
return create(account, provider, values)
}
private fun valuesFromCollectionInfo(info: CollectionInfo, withColor: Boolean): ContentValues {
val values = ContentValues()
private fun valuesFromCollectionInfo(journalEntity: JournalEntity, withColor: Boolean): ContentValues {
val info = journalEntity.info
val values = ContentValues(3)
values.put(TaskLists._SYNC_ID, info.uid)
values.put(TaskLists.LIST_NAME, info.displayName)
values.put(TaskLists.LIST_NAME, if (info.displayName.isNullOrBlank()) info.uid else info.displayName)
if (withColor)
values.put(TaskLists.LIST_COLOR, if (info.color != null) info.color else defaultColor)
values.put(TaskLists.LIST_COLOR, info.color ?: defaultColor)
return values
}
// helpers
}
fun tasksProviderAvailable(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return context.packageManager.resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null
else {
val provider = TaskProvider.acquire(context.contentResolver, TaskProvider.ProviderName.OpenTasks)
try {
return provider != null
} finally {
provider?.close()
}
}
fun update(journalEntity: JournalEntity, updateColor: Boolean) =
update(valuesFromCollectionInfo(journalEntity, updateColor))
override fun findDeleted() = queryTasks("${Tasks._DELETED}!=0", null)
override fun findDirty(): List<LocalTask> {
val tasks = queryTasks("${Tasks._DIRTY}!=0", null)
for (localTask in tasks) {
val task = requireNotNull(localTask.task)
val sequence = task.sequence
if (sequence == null) // sequence has not been assigned yet (i.e. this task was just locally created)
task.sequence = 0
else
task.sequence = sequence + 1
}
return tasks
}
override fun findWithoutFileName(): List<LocalTask>
= queryTasks(Tasks._SYNC_ID + " IS NULL", null)
// HELPERS
override fun findByUid(uid: String): LocalTask?
= queryTasks(Tasks._SYNC_ID + " =? ", arrayOf(uid)).firstOrNull()
@Throws(RemoteException::class)
fun onRenameAccount(resolver: ContentResolver, oldName: String, newName: String) {
val client = resolver.acquireContentProviderClient(TaskProvider.ProviderName.OpenTasks.authority)
if (client != null) {
val values = ContentValues(1)
values.put(Tasks.ACCOUNT_NAME, newName)
client.update(Tasks.getContentUri(TaskProvider.ProviderName.OpenTasks.authority), values, Tasks.ACCOUNT_NAME + "=?", arrayOf(oldName))
client.release()
override fun count(): Long {
try {
val cursor = provider.client.query(
TaskProvider.syncAdapterUri(provider.tasksUri()), null,
Tasks.LIST_ID + "=?", arrayOf(id.toString()), null)
try {
return cursor?.count.toLong()
} finally {
cursor?.close()
}
} catch (e: RemoteException) {
throw CalendarStorageException("Couldn't query calendar events", e)
}
}
object Factory: AndroidTaskListFactory<LocalTaskList> {
override fun newInstance(account: Account, provider: TaskProvider, id: Long) =
LocalTaskList(account, provider, id)
}
}

Loading…
Cancel
Save