Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ class DmfsTaskBuilderTest (
private val tzRegistry = TimeZoneRegistryFactory.getInstance().createRegistry()!!
private val tzVienna = tzRegistry.getTimeZone("Europe/Vienna")!!
private val tzChicago = tzRegistry.getTimeZone("America/Chicago")!!
private val tzDefault = tzRegistry.getTimeZone(ZoneId.systemDefault().id)!!

private val testAccount = Account(javaClass.name, TaskContract.LOCAL_ACCOUNT_TYPE)

Expand Down Expand Up @@ -845,33 +844,6 @@ class DmfsTaskBuilderTest (
}


// other methods

@Test
fun testGetTimeZone_noDateOrDateTime() {
val builder = DmfsTaskBuilder(taskList!!, Task(), 0, "9468a4cf-0d5b-4379-a704-12f1f84100ba", null, 0)
assertEquals(tzDefault, builder.getTimeZone())
}

@Test
fun testGetTimeZone_dtstart_with_date_and_no_time() {
val task = Task()
val builder = DmfsTaskBuilder(taskList!!, task, 0, "410c19d7-df79-4d65-8146-40b7bec5923b", null, 0)
val dmfsTask = DmfsTask(taskList!!, task, "410c19d7-df79-4d65-8146-40b7bec5923b", null, 0)
dmfsTask.task!!.dtStart = DtStart(LocalDate.of(2015, 1, 1))
assertEquals(tzDefault, builder.getTimeZone())
}

@Test
fun testGetTimeZone_dtstart_with_time() {
val task = Task()
val builder = DmfsTaskBuilder(taskList!!, task, 0, "9468a4cf-0d5b-4379-a704-12f1f84100ba", null, 0)
val dmfsTask = DmfsTask(taskList!!, task, "9dc64544-1816-4f04-b952-e894164467f6", null, 0)
dmfsTask.task!!.dtStart = DtStart(LocalDate.of(2015, 1, 1).atStartOfDay(tzVienna.toZoneId()))
assertEquals(tzVienna, builder.getTimeZone())
}


// helpers

private fun buildTask(taskBuilder: Task.() -> Unit): ContentValues {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import android.content.ContentValues
import android.content.Entity
import at.bitfire.ical4android.Task
import at.bitfire.ical4android.UnknownProperty
import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate
import at.bitfire.synctools.mapping.tasks.builder.AllDayBuilder
import at.bitfire.synctools.mapping.tasks.builder.DmfsTaskFieldBuilder
import at.bitfire.synctools.mapping.tasks.builder.DueBuilder
import at.bitfire.synctools.mapping.tasks.builder.DurationBuilder
import at.bitfire.synctools.mapping.tasks.builder.RecurrenceFieldsBuilder
import at.bitfire.synctools.mapping.tasks.builder.StartTimeBuilder
import at.bitfire.synctools.mapping.tasks.builder.TitleBuilder
import at.bitfire.synctools.storage.BatchOperation.CpoBuilder
import at.bitfire.synctools.storage.tasks.DmfsTask.Companion.COLUMN_ETAG
Expand All @@ -20,30 +24,21 @@ import at.bitfire.synctools.storage.tasks.DmfsTask.Companion.UNKNOWN_PROPERTY_DA
import at.bitfire.synctools.storage.tasks.DmfsTaskList
import at.bitfire.synctools.storage.tasks.TasksBatchOperation
import at.bitfire.synctools.util.AlarmTriggerCalculator
import at.bitfire.synctools.util.AndroidTimeUtils
import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp
import net.fortuna.ical4j.model.Parameter
import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.model.TimeZone
import net.fortuna.ical4j.model.TimeZoneRegistryFactory
import net.fortuna.ical4j.model.parameter.Email
import net.fortuna.ical4j.model.parameter.RelType
import net.fortuna.ical4j.model.parameter.Related
import net.fortuna.ical4j.model.parameter.TzId
import net.fortuna.ical4j.model.property.Action
import net.fortuna.ical4j.model.property.DtStart
import net.fortuna.ical4j.model.property.Due
import net.fortuna.ical4j.model.property.immutable.ImmutableAction
import net.fortuna.ical4j.model.property.immutable.ImmutableClazz
import net.fortuna.ical4j.model.property.immutable.ImmutableStatus
import net.fortuna.ical4j.util.TimeZones
import org.dmfs.tasks.contract.TaskContract.Properties
import org.dmfs.tasks.contract.TaskContract.Property.Alarm
import org.dmfs.tasks.contract.TaskContract.Property.Category
import org.dmfs.tasks.contract.TaskContract.Property.Comment
import org.dmfs.tasks.contract.TaskContract.Property.Relation
import org.dmfs.tasks.contract.TaskContract.Tasks
import java.time.ZoneId
import java.util.Locale
import java.util.logging.Level
import java.util.logging.Logger
Expand All @@ -65,14 +60,19 @@ class DmfsTaskBuilder(
) {

private val fieldBuilders: Array<DmfsTaskFieldBuilder> = arrayOf(
TitleBuilder()
// time fields and recurrence
TitleBuilder(),
AllDayBuilder(),
StartTimeBuilder(),
DueBuilder(),
DurationBuilder(),
RecurrenceFieldsBuilder(),
// property sub-rows (still inline below via insertProperties)
)

private val logger
get() = Logger.getLogger(javaClass.name)

private val tzRegistry by lazy { TimeZoneRegistryFactory.getInstance().createRegistry() }

fun addRows(batch: TasksBatchOperation): Int {
val builder = CpoBuilder.newInsert(taskList.tasksUri())
buildTask(builder, false)
Expand Down Expand Up @@ -156,64 +156,13 @@ class DmfsTaskBuilder(
else -> Tasks.STATUS_DEFAULT // == Tasks.STATUS_NEEDS_ACTION
}
builder.withValue(Tasks.STATUS, status)

// Time related
val allDay = task.isAllDay()
if (allDay) {
builder .withValue(Tasks.IS_ALLDAY, 1)
.withValue(Tasks.TZ, null)
} else {
task.dtStart = task.dtStart?.normalizedDate()?.let { DtStart(it) }
task.due = task.due?.normalizedDate()?.let { Due(it) }
builder .withValue(Tasks.IS_ALLDAY, 0)
.withValue(Tasks.TZ, getTimeZone().id)
}
builder
.withValue(Tasks.CREATED, task.createdAt)
.withValue(Tasks.LAST_MODIFIED, task.lastModified)

.withValue(Tasks.DTSTART, task.dtStart?.date?.toTimestamp())
.withValue(Tasks.DUE, task.due?.date?.toTimestamp())
.withValue(Tasks.DURATION, task.duration?.value)

.withValue(Tasks.RDATE,
if (task.rDates.isEmpty())
null
else
AndroidTimeUtils.recurrenceSetsToOpenTasksString(task.rDates, if (allDay) null else getTimeZone()))
.withValue(Tasks.RRULE, task.rRule?.value)

.withValue(Tasks.EXDATE,
if (task.exDates.isEmpty())
null
else
AndroidTimeUtils.recurrenceSetsToOpenTasksString(task.exDates, if (allDay) null else getTimeZone()))

logger.log(Level.FINE, "Built task object", builder.build())
}

fun getTimeZone(): TimeZone {
var tzId = task.dtStart?.let { dtStart ->
if (dtStart.isUtc)
TimeZones.UTC_ID
else
dtStart.getParameter<TzId>(Parameter.TZID).getOrNull()?.value
} ?:
task.due?.let { due ->
if (due.isUtc)
TimeZones.UTC_ID
else
due.getParameter<TzId>(Parameter.TZID).getOrNull()?.value
} ?:
ZoneId.systemDefault().id

// 'Z' is not a valid timezone id, replace it by the UTC definition
if (tzId == "Z") tzId = TimeZones.UTC_ID

val timeZone: TimeZone? = tzRegistry.getTimeZone(tzId)
return timeZone ?: throw NullPointerException("Could not find timezone '$tzId' in registry.")
}

fun insertProperties(batch: TasksBatchOperation, idxTask: Int?) {
insertAlarms(batch, idxTask)
insertCategories(batch, idxTask)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* This file is part of bitfireAT/synctools which is released under GPLv3.
* Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details.
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package at.bitfire.synctools.mapping.tasks.builder

import android.content.Entity
import at.bitfire.ical4android.Task
import net.fortuna.ical4j.model.Parameter
import net.fortuna.ical4j.model.TimeZone
import net.fortuna.ical4j.model.TimeZoneRegistryFactory
import net.fortuna.ical4j.model.parameter.TzId
import net.fortuna.ical4j.util.TimeZones
import org.dmfs.tasks.contract.TaskContract.Tasks
import java.time.ZoneId
import kotlin.jvm.optionals.getOrNull

class AllDayBuilder : DmfsTaskFieldBuilder {

private val tzRegistry by lazy { TimeZoneRegistryFactory.getInstance().createRegistry() }

override fun build(from: Task, to: Entity) {
val allDay = from.isAllDay()
if (allDay) {
to.entityValues.put(Tasks.IS_ALLDAY, 1)
to.entityValues.putNull(Tasks.TZ)
} else {
to.entityValues.put(Tasks.IS_ALLDAY, 0)
to.entityValues.put(Tasks.TZ, getTimeZone(from).id)
}
}

fun getTimeZone(task: Task): TimeZone {
var tzId = task.dtStart?.let { dtStart ->
if (dtStart.isUtc)
TimeZones.UTC_ID
else
dtStart.getParameter<TzId>(Parameter.TZID).getOrNull()?.value
} ?:
task.due?.let { due ->
if (due.isUtc)
TimeZones.UTC_ID
else
due.getParameter<TzId>(Parameter.TZID).getOrNull()?.value
} ?:
ZoneId.systemDefault().id

// 'Z' is not a valid timezone id, replace it by the UTC definition
if (tzId == "Z") tzId = TimeZones.UTC_ID

val timeZone: TimeZone? = tzRegistry.getTimeZone(tzId)
return timeZone ?: throw NullPointerException("Could not find timezone '$tzId' in registry.")
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* This file is part of bitfireAT/synctools which is released under GPLv3.
* Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details.
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package at.bitfire.synctools.mapping.tasks.builder

import android.content.Entity
import at.bitfire.ical4android.Task
import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate
import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp
import org.dmfs.tasks.contract.TaskContract.Tasks

class DueBuilder : DmfsTaskFieldBuilder {

override fun build(from: Task, to: Entity) {
to.entityValues.put(Tasks.DUE, from.due?.normalizedDate()?.toTimestamp())
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* This file is part of bitfireAT/synctools which is released under GPLv3.
* Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details.
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package at.bitfire.synctools.mapping.tasks.builder

import android.content.Entity
import at.bitfire.ical4android.Task
import org.dmfs.tasks.contract.TaskContract.Tasks

class DurationBuilder : DmfsTaskFieldBuilder {

override fun build(from: Task, to: Entity) {
to.entityValues.put(Tasks.DURATION, from.duration?.value)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* This file is part of bitfireAT/synctools which is released under GPLv3.
* Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details.
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package at.bitfire.synctools.mapping.tasks.builder

import android.content.Entity
import at.bitfire.ical4android.Task
import at.bitfire.synctools.util.AndroidTimeUtils
import org.dmfs.tasks.contract.TaskContract.Tasks

class RecurrenceFieldsBuilder : DmfsTaskFieldBuilder {

private val allDayBuilder = AllDayBuilder()

override fun build(from: Task, to: Entity) {
val allDay = from.isAllDay()
val tz = if (allDay) null else allDayBuilder.getTimeZone(from)

to.entityValues.put(Tasks.RRULE, from.rRule?.value)

to.entityValues.put(Tasks.RDATE,
if (from.rDates.isEmpty())
null
else
AndroidTimeUtils.recurrenceSetsToOpenTasksString(from.rDates, tz)
)

to.entityValues.put(Tasks.EXDATE,
if (from.exDates.isEmpty())
null
else
AndroidTimeUtils.recurrenceSetsToOpenTasksString(from.exDates, tz)
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* This file is part of bitfireAT/synctools which is released under GPLv3.
* Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details.
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package at.bitfire.synctools.mapping.tasks.builder

import android.content.Entity
import at.bitfire.ical4android.Task
import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate
import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp
import org.dmfs.tasks.contract.TaskContract.Tasks

class StartTimeBuilder : DmfsTaskFieldBuilder {

override fun build(from: Task, to: Entity) {
to.entityValues.put(Tasks.DTSTART, from.dtStart?.normalizedDate()?.toTimestamp())
}

}
Loading
Loading