mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-07-08 20:16:24 +00:00
Don't upload event when calendar is read only (#587)
* Make readOnly a LocalCollection property * Move readOnly detection to SyncManager * Add readOnly state access to LocalCalendar * Add not implemented error to readOnly state access of LocalJtxCollection * Handle read-only state of calendar at dirty events upload * Handle read-only state of calendar at processing of locally deleted events * Remove todo and update kdoc * Fix indenting * Add read-only prop to LocalTestCollection * Add read-only state access to LocalTaskList * LocalTestCollection: don't set read-only * Update ical4android (for new KDoc) * Make LocalCollection readOnly-API read only and take value from content provider during populate() * SyncManager: use readOnly direct from localCollection * Lift resetDeleted up to LocalResource * Override and use resetDeleted for LocalEvent * Add resetDeleted to LocalJtxICalObject * Add resetDeleted to LocalTask * Add resetDeleted to LocalTask * Add resetDeleted to LocalTestResource * Provide default access level --------- Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
parent
df2b7d2bd0
commit
86742f5b18
|
@ -16,6 +16,9 @@ class LocalTestCollection: LocalCollection<LocalTestResource> {
|
|||
|
||||
val entries = mutableListOf<LocalTestResource>()
|
||||
|
||||
override val readOnly: Boolean
|
||||
get() = throw NotImplementedError()
|
||||
|
||||
override fun findDeleted() = entries.filter { it.deleted }
|
||||
override fun findDirty() = entries.filter { it.dirty }
|
||||
|
||||
|
|
|
@ -34,5 +34,6 @@ class LocalTestResource: LocalResource<Any> {
|
|||
override fun add() = throw NotImplementedError()
|
||||
override fun update(data: Any) = throw NotImplementedError()
|
||||
override fun delete() = throw NotImplementedError()
|
||||
override fun resetDeleted() = throw NotImplementedError()
|
||||
|
||||
}
|
|
@ -6,8 +6,4 @@ package at.bitfire.davdroid.resource
|
|||
|
||||
import at.bitfire.vcard4android.Contact
|
||||
|
||||
interface LocalAddress: LocalResource<Contact> {
|
||||
|
||||
fun resetDeleted()
|
||||
|
||||
}
|
||||
interface LocalAddress: LocalResource<Contact>
|
|
@ -91,6 +91,10 @@ class LocalCalendar private constructor(
|
|||
override val title: String
|
||||
get() = displayName ?: id.toString()
|
||||
|
||||
private var accessLevel: Int = Calendars.CAL_ACCESS_OWNER // assume full access if not specified
|
||||
override val readOnly
|
||||
get() = accessLevel <= Calendars.CAL_ACCESS_READ
|
||||
|
||||
override var lastSyncState: SyncState?
|
||||
get() = provider.query(calendarSyncURI(), arrayOf(COLUMN_SYNC_STATE), null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToNext())
|
||||
|
@ -105,6 +109,11 @@ class LocalCalendar private constructor(
|
|||
}
|
||||
|
||||
|
||||
override fun populate(info: ContentValues) {
|
||||
super.populate(info)
|
||||
accessLevel = info.getAsInteger(Calendars.CALENDAR_ACCESS_LEVEL) ?: Calendars.CAL_ACCESS_OWNER
|
||||
}
|
||||
|
||||
fun update(info: Collection, updateColor: Boolean) =
|
||||
update(valuesFromCollectionInfo(info, updateColor))
|
||||
|
||||
|
|
|
@ -17,6 +17,12 @@ interface LocalCollection<out T: LocalResource<*>> {
|
|||
|
||||
var lastSyncState: SyncState?
|
||||
|
||||
/**
|
||||
* Whether the collection should be treated as read-only on sync.
|
||||
* Stops uploading dirty events (Server side changes are still downloaded).
|
||||
*/
|
||||
val readOnly: Boolean
|
||||
|
||||
/**
|
||||
* Finds local resources of this collection which have been marked as *deleted* by the user
|
||||
* or an app acting on their behalf.
|
||||
|
|
|
@ -21,7 +21,6 @@ import at.bitfire.ical4android.Ical4Android
|
|||
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
|
||||
import net.fortuna.ical4j.model.property.ProdId
|
||||
import java.util.UUID
|
||||
|
||||
class LocalEvent: AndroidEvent, LocalResource<Event> {
|
||||
|
||||
companion object {
|
||||
|
@ -51,6 +50,7 @@ class LocalEvent: AndroidEvent, LocalResource<Event> {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the amount of direct instances this event has (without exceptions); used by [numInstances]
|
||||
* to find the number of instances of exceptions.
|
||||
|
@ -256,6 +256,10 @@ class LocalEvent: AndroidEvent, LocalResource<Event> {
|
|||
this.flags = flags
|
||||
}
|
||||
|
||||
override fun resetDeleted() {
|
||||
val values = ContentValues(1).apply { put(Events.DELETED, 0) }
|
||||
calendar.provider.update(eventSyncURI(), values, null, null)
|
||||
}
|
||||
|
||||
object Factory: AndroidEventFactory<LocalEvent> {
|
||||
override fun fromProvider(calendar: AndroidCalendar<*>, values: ContentValues) =
|
||||
|
@ -263,3 +267,4 @@ class LocalEvent: AndroidEvent, LocalResource<Event> {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,9 @@ class LocalJtxCollection(account: Account, client: ContentProviderClient, id: Lo
|
|||
}
|
||||
}
|
||||
|
||||
override val readOnly: Boolean
|
||||
get() = throw NotImplementedError()
|
||||
|
||||
override val tag: String
|
||||
get() = "jtx-${account.name}-$id"
|
||||
override val title: String
|
||||
|
|
|
@ -47,4 +47,9 @@ class LocalJtxICalObject(
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
override fun resetDeleted() {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
}
|
|
@ -90,4 +90,9 @@ interface LocalResource<in TData: Any> {
|
|||
*/
|
||||
fun delete(): Int
|
||||
|
||||
/**
|
||||
* Undoes deletion of the data object from the content provider.
|
||||
*/
|
||||
fun resetDeleted()
|
||||
|
||||
}
|
|
@ -104,6 +104,10 @@ class LocalTask: DmfsTask, LocalResource<Task> {
|
|||
this.flags = flags
|
||||
}
|
||||
|
||||
override fun resetDeleted() {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
|
||||
object Factory: DmfsTaskFactory<LocalTask> {
|
||||
override fun fromProvider(taskList: DmfsTaskList<*>, values: ContentValues) =
|
||||
|
|
|
@ -68,6 +68,12 @@ class LocalTaskList private constructor(
|
|||
|
||||
}
|
||||
|
||||
private var accessLevel: Int = TaskListColumns.ACCESS_LEVEL_UNDEFINED
|
||||
override val readOnly
|
||||
get() =
|
||||
accessLevel != TaskListColumns.ACCESS_LEVEL_UNDEFINED &&
|
||||
accessLevel <= TaskListColumns.ACCESS_LEVEL_READ
|
||||
|
||||
override val tag: String
|
||||
get() = "tasks-${account.name}-$id"
|
||||
|
||||
|
@ -96,8 +102,13 @@ class LocalTaskList private constructor(
|
|||
}
|
||||
|
||||
|
||||
override fun populate(values: ContentValues) {
|
||||
super.populate(values)
|
||||
accessLevel = values.getAsInteger(TaskListColumns.ACCESS_LEVEL)
|
||||
}
|
||||
|
||||
fun update(info: Collection, updateColor: Boolean) =
|
||||
update(valuesFromCollectionInfo(info, updateColor))
|
||||
update(valuesFromCollectionInfo(info, updateColor))
|
||||
|
||||
|
||||
override fun findDeleted() = queryTasks(Tasks._DELETED, null)
|
||||
|
|
|
@ -73,29 +73,74 @@ class CalendarSyncManager(
|
|||
}
|
||||
|
||||
override fun queryCapabilities(): SyncState? =
|
||||
remoteExceptionContext {
|
||||
var syncState: SyncState? = null
|
||||
it.propfind(0, MaxResourceSize.NAME, SupportedReportSet.NAME, GetCTag.NAME, SyncToken.NAME) { response, relation ->
|
||||
if (relation == Response.HrefRelation.SELF) {
|
||||
response[MaxResourceSize::class.java]?.maxSize?.let { maxSize ->
|
||||
Logger.log.info("Calendar accepts events up to ${FileUtils.byteCountToDisplaySize(maxSize)}")
|
||||
}
|
||||
|
||||
response[SupportedReportSet::class.java]?.let { supported ->
|
||||
hasCollectionSync = supported.reports.contains(SupportedReportSet.SYNC_COLLECTION)
|
||||
}
|
||||
syncState = syncState(response)
|
||||
remoteExceptionContext {
|
||||
var syncState: SyncState? = null
|
||||
it.propfind(0, MaxResourceSize.NAME, SupportedReportSet.NAME, GetCTag.NAME, SyncToken.NAME) { response, relation ->
|
||||
if (relation == Response.HrefRelation.SELF) {
|
||||
response[MaxResourceSize::class.java]?.maxSize?.let { maxSize ->
|
||||
Logger.log.info("Calendar accepts events up to ${FileUtils.byteCountToDisplaySize(maxSize)}")
|
||||
}
|
||||
}
|
||||
|
||||
Logger.log.info("Calendar supports Collection Sync: $hasCollectionSync")
|
||||
syncState
|
||||
response[SupportedReportSet::class.java]?.let { supported ->
|
||||
hasCollectionSync = supported.reports.contains(SupportedReportSet.SYNC_COLLECTION)
|
||||
}
|
||||
syncState = syncState(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncAlgorithm() = if (accountSettings.getTimeRangePastDays() != null || !hasCollectionSync)
|
||||
SyncAlgorithm.PROPFIND_REPORT
|
||||
else
|
||||
SyncAlgorithm.COLLECTION_SYNC
|
||||
Logger.log.info("Calendar supports Collection Sync: $hasCollectionSync")
|
||||
syncState
|
||||
}
|
||||
|
||||
override fun syncAlgorithm() =
|
||||
if (accountSettings.getTimeRangePastDays() != null || !hasCollectionSync)
|
||||
SyncAlgorithm.PROPFIND_REPORT
|
||||
else
|
||||
SyncAlgorithm.COLLECTION_SYNC
|
||||
|
||||
override fun processLocallyDeleted(): Boolean {
|
||||
if (localCollection.readOnly) {
|
||||
var modified = false
|
||||
for (event in localCollection.findDeleted()) {
|
||||
Logger.log.warning("Restoring locally deleted event (read-only calendar!)")
|
||||
localExceptionContext(event) { it.resetDeleted() }
|
||||
modified = true
|
||||
}
|
||||
|
||||
// This is unfortunately ugly: When an event has been inserted to a read-only calendar
|
||||
// it's not enough to force synchronization (by returning true),
|
||||
// but we also need to make sure all events are downloaded again.
|
||||
if (modified)
|
||||
localCollection.lastSyncState = null
|
||||
|
||||
return modified
|
||||
}
|
||||
// mirror deletions to remote collection (DELETE)
|
||||
return super.processLocallyDeleted()
|
||||
}
|
||||
|
||||
override fun uploadDirty(): Boolean {
|
||||
var modified = false
|
||||
if (localCollection.readOnly) {
|
||||
for (event in localCollection.findDirty()) {
|
||||
Logger.log.warning("Resetting locally modified event to ETag=null (read-only calendar!)")
|
||||
localExceptionContext(event) { it.clearDirty(null, null) }
|
||||
modified = true
|
||||
}
|
||||
|
||||
// This is unfortunately ugly: When an event has been inserted to a read-only calendar
|
||||
// it's not enough to force synchronization (by returning true),
|
||||
// but we also need to make sure all events are downloaded again.
|
||||
if (modified)
|
||||
localCollection.lastSyncState = null
|
||||
}
|
||||
|
||||
// generate UID/file name for newly created events
|
||||
val superModified = super.uploadDirty()
|
||||
|
||||
// return true when any operation returned true
|
||||
return modified or superModified
|
||||
}
|
||||
|
||||
override fun generateUpload(resource: LocalEvent): RequestBody = localExceptionContext(resource) {
|
||||
val event = requireNotNull(resource.event)
|
||||
|
@ -143,8 +188,7 @@ class CalendarSyncManager(
|
|||
}
|
||||
}
|
||||
|
||||
override fun postProcess() {
|
||||
}
|
||||
override fun postProcess() {}
|
||||
|
||||
|
||||
// helpers
|
||||
|
@ -195,6 +239,6 @@ class CalendarSyncManager(
|
|||
}
|
||||
|
||||
override fun notifyInvalidResourceTitle(): String =
|
||||
context.getString(R.string.sync_invalid_event)
|
||||
context.getString(R.string.sync_invalid_event)
|
||||
|
||||
}
|
||||
|
|
|
@ -105,8 +105,6 @@ class ContactsSyncManager(
|
|||
infix fun <T> Set<T>.disjunct(other: Set<T>) = (this - other) union (other - this)
|
||||
}
|
||||
|
||||
private val readOnly = localAddressBook.readOnly
|
||||
|
||||
private var hasVCard4 = false
|
||||
private var hasJCard = false
|
||||
private val groupStrategy = when (accountSettings.getGroupMethod()) {
|
||||
|
@ -177,7 +175,7 @@ class ContactsSyncManager(
|
|||
SyncAlgorithm.PROPFIND_REPORT
|
||||
|
||||
override fun processLocallyDeleted() =
|
||||
if (readOnly) {
|
||||
if (localCollection.readOnly) {
|
||||
var modified = false
|
||||
for (group in localCollection.findDeletedGroups()) {
|
||||
Logger.log.warning("Restoring locally deleted group (read-only address book!)")
|
||||
|
@ -205,7 +203,7 @@ class ContactsSyncManager(
|
|||
override fun uploadDirty(): Boolean {
|
||||
var modified = false
|
||||
|
||||
if (readOnly) {
|
||||
if (localCollection.readOnly) {
|
||||
for (group in localCollection.findDirtyGroups()) {
|
||||
Logger.log.warning("Resetting locally modified group to ETag=null (read-only address book!)")
|
||||
localExceptionContext(group) { it.clearDirty(null, null) }
|
||||
|
|
|
@ -28,7 +28,7 @@ androidx-work = "2.9.0"
|
|||
appIntro = "7.0.0-beta02"
|
||||
bitfire-cert4android = "f0964cb"
|
||||
bitfire-dav4jvm = "b30913f"
|
||||
bitfire-ical4android = "998f6b6"
|
||||
bitfire-ical4android = "31650d1"
|
||||
bitfire-vcard4android = "03ef99a"
|
||||
commons-collections = "4.4"
|
||||
commons-lang = "3.14.0"
|
||||
|
|
Loading…
Reference in New Issue
Block a user