Synchronize VALARMs

pull/1767/head
Alex Baker 3 years ago
parent 09da1dcc42
commit 3b2b16f0c6

@ -5,6 +5,8 @@ import at.bitfire.ical4android.Task.Companion.tasksFromReader
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.tasks.caldav.iCalendar.Companion.applyRemote import org.tasks.caldav.iCalendar.Companion.applyRemote
import org.tasks.caldav.iCalendar.Companion.reminders
import org.tasks.data.Alarm
import org.tasks.data.CaldavTask import org.tasks.data.CaldavTask
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.time.DateTime import org.tasks.time.DateTime
@ -44,6 +46,9 @@ object TestUtilities {
return task return task
} }
val String.alarms: List<Alarm>
get() = fromResource(this).reminders
fun setup(path: String): Triple<Task, CaldavTask, at.bitfire.ical4android.Task> { fun setup(path: String): Triple<Task, CaldavTask, at.bitfire.ical4android.Task> {
val task = Task() val task = Task()
val vtodo = readFile(path) val vtodo = readFile(path)

@ -0,0 +1,32 @@
@file:Suppress("ClassName")
package com.todoroo.astrid.service
import org.tasks.caldav.iCalendar
import org.tasks.caldav.iCalendar.Companion.reminders
import org.tasks.data.AlarmDao
import org.tasks.data.TaskDao
import org.tasks.data.UpgraderDao
import javax.inject.Inject
class Upgrade_12_4 @Inject constructor(
private val alarmDao: AlarmDao,
private val taskDao: TaskDao,
private val upgraderDao: UpgraderDao,
) {
internal suspend fun syncExistingAlarms() {
val existingAlarms = alarmDao.getActiveAlarms().map { it.task }
upgraderDao.tasksWithVtodos().forEach { caldav ->
val remoteTask = caldav.vtodo?.let(iCalendar::fromVtodo) ?: return@forEach
remoteTask
.reminders
.onEach { alarm -> alarm.task = caldav.id }
.let { alarms -> alarmDao.insert(alarms) }
}
taskDao.touch(existingAlarms)
}
companion object {
const val VERSION = 120400
}
}

@ -59,6 +59,7 @@ class Upgrader @Inject constructor(
private val upgraderDao: UpgraderDao, private val upgraderDao: UpgraderDao,
private val upgrade_11_3: Lazy<Upgrade_11_3>, private val upgrade_11_3: Lazy<Upgrade_11_3>,
private val upgrade_11_12_3: Lazy<Upgrade_11_12_3>, private val upgrade_11_12_3: Lazy<Upgrade_11_12_3>,
private val upgrade_12_4: Lazy<Upgrade_12_4>,
) { ) {
fun upgrade(from: Int, to: Int) { fun upgrade(from: Int, to: Int) {
@ -95,13 +96,14 @@ class Upgrader @Inject constructor(
} }
} }
run(from, Upgrade_11_12_3.VERSION) { run(from, Upgrade_11_12_3.VERSION) {
with(upgrade_11_12_3.get()) { upgrade_11_12_3.get().migrateDefaultReminderPreference()
migrateDefaultReminderPreference()
}
} }
run(from, V11_13) { run(from, V11_13) {
preferences.setString(R.string.p_completion_ringtone, "") preferences.setString(R.string.p_completion_ringtone, "")
} }
run(from, Upgrade_12_4.VERSION) {
upgrade_12_4.get().syncExistingAlarms()
}
preferences.setBoolean(R.string.p_just_updated, true) preferences.setBoolean(R.string.p_just_updated, true)
} }
preferences.setCurrentVersion(to) preferences.setCurrentVersion(to)

@ -12,9 +12,17 @@ import at.bitfire.dav4jvm.exception.DavException
import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.dav4jvm.exception.ServiceUnavailableException import at.bitfire.dav4jvm.exception.ServiceUnavailableException
import at.bitfire.dav4jvm.exception.UnauthorizedException import at.bitfire.dav4jvm.exception.UnauthorizedException
import at.bitfire.dav4jvm.property.* import at.bitfire.dav4jvm.property.CalendarColor
import at.bitfire.dav4jvm.property.CalendarData
import at.bitfire.dav4jvm.property.CurrentUserPrincipal
import at.bitfire.dav4jvm.property.CurrentUserPrivilegeSet
import at.bitfire.dav4jvm.property.DisplayName
import at.bitfire.dav4jvm.property.GetCTag
import at.bitfire.dav4jvm.property.GetETag
import at.bitfire.dav4jvm.property.GetETag.Companion.fromResponse import at.bitfire.dav4jvm.property.GetETag.Companion.fromResponse
import at.bitfire.dav4jvm.property.SyncToken
import at.bitfire.ical4android.ICalendar.Companion.prodId import at.bitfire.ical4android.ICalendar.Companion.prodId
import com.todoroo.astrid.alarms.AlarmService
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import com.todoroo.astrid.helper.UUIDHelper import com.todoroo.astrid.helper.UUIDHelper
@ -32,18 +40,26 @@ import org.tasks.Strings.isNullOrEmpty
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.caldav.iCalendar.Companion.fromVtodo import org.tasks.caldav.iCalendar.Companion.fromVtodo
import org.tasks.caldav.property.* import org.tasks.caldav.iCalendar.Companion.reminders
import org.tasks.caldav.property.Invite
import org.tasks.caldav.property.OCAccess
import org.tasks.caldav.property.OCInvite
import org.tasks.caldav.property.OCOwnerPrincipal
import org.tasks.caldav.property.OCUser
import org.tasks.caldav.property.PropertyUtils.register import org.tasks.caldav.property.PropertyUtils.register
import org.tasks.caldav.property.ShareAccess
import org.tasks.caldav.property.ShareAccess.Companion.READ import org.tasks.caldav.property.ShareAccess.Companion.READ
import org.tasks.caldav.property.ShareAccess.Companion.READ_WRITE import org.tasks.caldav.property.ShareAccess.Companion.READ_WRITE
import org.tasks.caldav.property.ShareAccess.Companion.SHARED_OWNER import org.tasks.caldav.property.ShareAccess.Companion.SHARED_OWNER
import org.tasks.data.* import org.tasks.caldav.property.Sharee
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.ERROR_UNAUTHORIZED import org.tasks.data.CaldavAccount.Companion.ERROR_UNAUTHORIZED
import org.tasks.data.CaldavAccount.Companion.SERVER_OPEN_XCHANGE import org.tasks.data.CaldavAccount.Companion.SERVER_OPEN_XCHANGE
import org.tasks.data.CaldavAccount.Companion.SERVER_OWNCLOUD import org.tasks.data.CaldavAccount.Companion.SERVER_OWNCLOUD
import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV
import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS
import org.tasks.data.CaldavAccount.Companion.SERVER_UNKNOWN import org.tasks.data.CaldavAccount.Companion.SERVER_UNKNOWN
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavCalendar.Companion.ACCESS_OWNER import org.tasks.data.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_ONLY import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_ONLY
import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_WRITE import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_WRITE
@ -53,6 +69,10 @@ import org.tasks.data.CaldavCalendar.Companion.INVITE_DECLINED
import org.tasks.data.CaldavCalendar.Companion.INVITE_INVALID import org.tasks.data.CaldavCalendar.Companion.INVITE_INVALID
import org.tasks.data.CaldavCalendar.Companion.INVITE_NO_RESPONSE import org.tasks.data.CaldavCalendar.Companion.INVITE_NO_RESPONSE
import org.tasks.data.CaldavCalendar.Companion.INVITE_UNKNOWN import org.tasks.data.CaldavCalendar.Companion.INVITE_UNKNOWN
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.PrincipalAccess
import org.tasks.data.PrincipalDao
import timber.log.Timber import timber.log.Timber
import java.io.IOException import java.io.IOException
import java.net.ConnectException import java.net.ConnectException
@ -60,7 +80,6 @@ import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
import java.security.KeyManagementException import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.net.ssl.SSLException import javax.net.ssl.SSLException
@ -75,6 +94,7 @@ class CaldavSynchronizer @Inject constructor(
private val provider: CaldavClientProvider, private val provider: CaldavClientProvider,
private val iCal: iCalendar, private val iCal: iCalendar,
private val principalDao: PrincipalDao, private val principalDao: PrincipalDao,
private val alarmService: AlarmService,
) { ) {
suspend fun sync(account: CaldavAccount) { suspend fun sync(account: CaldavAccount) {
Thread.currentThread().contextClassLoader = context.classLoader Thread.currentThread().contextClassLoader = context.classLoader
@ -254,8 +274,11 @@ class CaldavSynchronizer @Inject constructor(
Timber.e("Invalid VCALENDAR: %s", fileName) Timber.e("Invalid VCALENDAR: %s", fileName)
return return
} }
val caldavTask = caldavDao.getTask(caldavCalendar.uuid!!, fileName) var caldavTask = caldavDao.getTask(caldavCalendar.uuid!!, fileName)
iCal.fromVtodo(caldavCalendar, caldavTask, remote, vtodo, fileName, eTag) iCal.fromVtodo(caldavCalendar, caldavTask, remote, vtodo, fileName, eTag)
caldavTask = caldavTask ?: caldavDao.getTask(caldavCalendar.uuid!!, fileName)
?: continue
alarmService.synchronizeAlarms(caldavTask.task, remote.reminders.toMutableSet())
} }
} }
caldavDao caldavDao

@ -3,6 +3,7 @@ package org.tasks.caldav
import at.bitfire.ical4android.DateUtils.ical4jTimeZone import at.bitfire.ical4android.DateUtils.ical4jTimeZone
import at.bitfire.ical4android.Task import at.bitfire.ical4android.Task
import at.bitfire.ical4android.Task.Companion.tasksFromReader import at.bitfire.ical4android.Task.Companion.tasksFromReader
import at.bitfire.ical4android.util.TimeApiExtensions.toDuration
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_SPECIFIC_DAY import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_SPECIFIC_DAY
@ -11,17 +12,44 @@ import com.todoroo.astrid.data.Task.Companion.URGENCY_SPECIFIC_DAY
import com.todoroo.astrid.data.Task.Companion.URGENCY_SPECIFIC_DAY_TIME import com.todoroo.astrid.data.Task.Companion.URGENCY_SPECIFIC_DAY_TIME
import com.todoroo.astrid.helper.UUIDHelper import com.todoroo.astrid.helper.UUIDHelper
import com.todoroo.astrid.service.TaskCreator import com.todoroo.astrid.service.TaskCreator
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.Parameter import net.fortuna.ical4j.model.Parameter
import net.fortuna.ical4j.model.ParameterList
import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.parameter.RelType import net.fortuna.ical4j.model.parameter.RelType
import net.fortuna.ical4j.model.property.* import net.fortuna.ical4j.model.parameter.Related
import net.fortuna.ical4j.model.parameter.Related.*
import net.fortuna.ical4j.model.property.Action
import net.fortuna.ical4j.model.property.Completed
import net.fortuna.ical4j.model.property.DateProperty
import net.fortuna.ical4j.model.property.Description
import net.fortuna.ical4j.model.property.DtStart
import net.fortuna.ical4j.model.property.Due
import net.fortuna.ical4j.model.property.Geo
import net.fortuna.ical4j.model.property.RelatedTo
import net.fortuna.ical4j.model.property.Repeat
import net.fortuna.ical4j.model.property.Status
import net.fortuna.ical4j.model.property.Trigger
import net.fortuna.ical4j.model.property.XProperty
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.caldav.GeoUtils.equalish import org.tasks.caldav.GeoUtils.equalish
import org.tasks.caldav.GeoUtils.toGeo import org.tasks.caldav.GeoUtils.toGeo
import org.tasks.caldav.GeoUtils.toLikeString import org.tasks.caldav.GeoUtils.toLikeString
import org.tasks.data.* import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
import org.tasks.data.Alarm.Companion.TYPE_REL_END
import org.tasks.data.Alarm.Companion.TYPE_REL_START
import org.tasks.data.AlarmDao
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.Geofence
import org.tasks.data.LocationDao
import org.tasks.data.Place
import org.tasks.data.TagDao
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.jobs.WorkManager import org.tasks.jobs.WorkManager
@ -37,6 +65,9 @@ import timber.log.Timber
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.StringReader import java.io.StringReader
import java.text.ParseException import java.text.ParseException
import java.time.Duration
import java.time.Instant
import java.time.temporal.TemporalAmount
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.max import kotlin.math.max
@ -52,7 +83,9 @@ class iCalendar @Inject constructor(
private val taskCreator: TaskCreator, private val taskCreator: TaskCreator,
private val tagDao: TagDao, private val tagDao: TagDao,
private val taskDao: TaskDao, private val taskDao: TaskDao,
private val caldavDao: CaldavDao) { private val caldavDao: CaldavDao,
private val alarmDao: AlarmDao,
) {
suspend fun setPlace(taskId: Long, geo: Geo?) { suspend fun setPlace(taskId: Long, geo: Geo?) {
if (geo == null) { if (geo == null) {
@ -136,6 +169,42 @@ class iCalendar @Inject constructor(
if (localGeo == null || !localGeo.equalish(remoteModel.geoPosition)) { if (localGeo == null || !localGeo.equalish(remoteModel.geoPosition)) {
remoteModel.geoPosition = localGeo remoteModel.geoPosition = localGeo
} }
remoteModel.alarms.removeAll(remoteModel.alarms.filtered)
remoteModel.alarms.addAll(
alarmDao
.getAlarms(task.id)
.mapNotNull {
val trigger = when (it.type) {
TYPE_DATE_TIME ->
Trigger(getDateTime(it.time))
TYPE_REL_START,
TYPE_REL_END ->
Trigger(
ParameterList().apply {
add(if (it.type == TYPE_REL_END) END else START)
},
Duration.ofMillis(it.time)
)
else -> return@mapNotNull null
}
VAlarm().apply {
with(properties) {
add(trigger)
add(Action.DISPLAY)
add(Description("Default Tasks.org description"))
if (it.repeat > 0) {
add(Repeat(it.repeat))
add(
net.fortuna.ical4j.model.property.Duration(
Duration.ofMillis(it.interval)
)
)
}
}
}
}
)
} }
suspend fun fromVtodo( suspend fun fromVtodo(
@ -177,6 +246,8 @@ class iCalendar @Inject constructor(
private const val MOZ_SNOOZE_TIME = "X-MOZ-SNOOZE-TIME" private const val MOZ_SNOOZE_TIME = "X-MOZ-SNOOZE-TIME"
private const val MOZ_LASTACK = "X-MOZ-LASTACK" private const val MOZ_LASTACK = "X-MOZ-LASTACK"
private const val HIDE_SUBTASKS = "1" private const val HIDE_SUBTASKS = "1"
// VALARM extensions: https://datatracker.ietf.org/doc/html/rfc9074
private val IGNORE_ALARM = DateTime("19760401T005545Z")
private val IS_PARENT = { r: RelatedTo -> private val IS_PARENT = { r: RelatedTo ->
r.parameters.getParameter<RelType>(Parameter.RELTYPE).let { r.parameters.getParameter<RelType>(Parameter.RELTYPE).let {
it === RelType.PARENT || it == null || it.value.isNullOrBlank() it === RelType.PARENT || it == null || it.value.isNullOrBlank()
@ -210,8 +281,7 @@ class iCalendar @Inject constructor(
} }
} }
@JvmStatic private fun getLocal(property: DateProperty): Long =
fun getLocal(property: DateProperty): Long =
org.tasks.time.DateTime.from(property.date)?.toLocal()?.millis ?: 0 org.tasks.time.DateTime.from(property.date)?.toLocal()?.millis ?: 0
fun fromVtodo(vtodo: String): Task? { fun fromVtodo(vtodo: String): Task? {
@ -389,11 +459,41 @@ class iCalendar @Inject constructor(
snooze = task.reminderSnooze snooze = task.reminderSnooze
} }
val List<VAlarm>.filtered: List<VAlarm>
get() =
filter { it.action == Action.DISPLAY || it.action == Action.AUDIO }
.filterNot { it.trigger.dateTime == IGNORE_ALARM }
val Task.reminders: List<Alarm>
get() = alarms.filtered.mapNotNull {
val (type, time) = when {
it.trigger.date != null ->
Pair(TYPE_DATE_TIME, getLocal(it.trigger))
it.trigger.duration != null ->
Pair(
if (it.trigger.parameters.getParameter<Related>(RELATED) == END) {
TYPE_REL_END
} else {
TYPE_REL_START
},
it.trigger.duration.toMillis()
)
else -> return@mapNotNull null
}
Alarm(0L, time, type, it.repeat?.count ?: 0, it.duration?.toMillis() ?: 0)
}
private fun getDateTime(timestamp: Long): DateTime { private fun getDateTime(timestamp: Long): DateTime {
val tz = ical4jTimeZone(TimeZone.getDefault().id) val tz = ical4jTimeZone(TimeZone.getDefault().id)
val dateTime = DateTime(if (tz != null) timestamp else org.tasks.time.DateTime(timestamp).toUTC().millis) val dateTime = DateTime(if (tz != null) timestamp else org.tasks.time.DateTime(timestamp).toUTC().millis)
dateTime.timeZone = tz dateTime.timeZone = tz
return dateTime return dateTime
} }
private fun net.fortuna.ical4j.model.property.Duration.toMillis() =
duration.toMillis()
private fun TemporalAmount.toMillis(): Long =
toDuration(Instant.EPOCH).toSeconds() * 1_000
} }
} }

@ -65,7 +65,8 @@ class Alarm : Parcelable {
override fun describeContents() = 0 override fun describeContents() = 0
override fun toString(): String { override fun toString(): String {
return "Alarm(id=$id, task=$task, time=${printTimestamp(time)}, type=$type, repeat=$repeat, interval=$interval)" val timestamp = if (type == TYPE_DATE_TIME) printTimestamp(time) else time
return "Alarm(id=$id, task=$task, time=$timestamp, type=$type, repeat=$repeat, interval=$interval)"
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

@ -3,9 +3,12 @@ package org.tasks.caldav
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import org.junit.After import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.tasks.TestUtilities.alarms
import org.tasks.TestUtilities.vtodo import org.tasks.TestUtilities.vtodo
import org.tasks.data.Alarm
import org.tasks.time.DateTime import org.tasks.time.DateTime
import java.util.* import java.util.*
@ -77,4 +80,17 @@ class AppleRemindersTests {
fun highPriority() { fun highPriority() {
assertEquals(Task.Priority.HIGH, vtodo("apple/priority_high.txt").priority) assertEquals(Task.Priority.HIGH, vtodo("apple/priority_high.txt").priority)
} }
@Test
fun dateTimeReminder() {
assertEquals(
listOf(Alarm(0, 1642568400000)),
"apple/date_time_reminder.txt".alarms
)
}
@Test
fun ignoreLocationReminders() {
assertTrue("apple/geofence_arrival.txt".alarms.isEmpty())
}
} }

@ -6,11 +6,18 @@ import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.tasks.TestUtilities.alarms
import org.tasks.TestUtilities.setup import org.tasks.TestUtilities.setup
import org.tasks.TestUtilities.vtodo import org.tasks.TestUtilities.vtodo
import org.tasks.caldav.iCalendar.Companion.applyLocal import org.tasks.caldav.iCalendar.Companion.applyLocal
import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
import org.tasks.data.Alarm.Companion.TYPE_REL_END
import org.tasks.data.Alarm.Companion.TYPE_REL_START
import org.tasks.time.DateTime import org.tasks.time.DateTime
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.MINUTES
class ThunderbirdTests { class ThunderbirdTests {
private val defaultTimeZone = TimeZone.getDefault() private val defaultTimeZone = TimeZone.getDefault()
@ -110,6 +117,62 @@ class ThunderbirdTests {
vtodo("thunderbird/start_date_time.txt").hideUntil) vtodo("thunderbird/start_date_time.txt").hideUntil)
} }
@Test
fun dateTimeReminder() {
assertEquals(
listOf(Alarm(task = 0, time = 1642791600000, type = TYPE_DATE_TIME)),
"thunderbird/date_time_reminder.txt".alarms
)
}
@Test
fun reminderBeforeStart() {
assertEquals(
listOf(Alarm(task = 0, time = -TimeUnit.HOURS.toMillis(1), type = TYPE_REL_START)),
"thunderbird/reminder_before_start.txt".alarms
)
}
@Test
fun reminderAfterStart() {
assertEquals(
listOf(Alarm(task = 0, time = MINUTES.toMillis(15), type = TYPE_REL_START)),
"thunderbird/reminder_after_start.txt".alarms
)
}
@Test
fun reminderBeforeEnd() {
assertEquals(
listOf(Alarm(task = 0, time = -MINUTES.toMillis(15), type = TYPE_REL_END)),
"thunderbird/reminder_before_end.txt".alarms
)
}
@Test
fun reminderAfterEnd() {
assertEquals(
listOf(Alarm(task = 0, time = MINUTES.toMillis(15), type = TYPE_REL_END)),
"thunderbird/reminder_after_end.txt".alarms
)
}
@Test
fun reminderAtStart() {
assertEquals(
listOf(Alarm(task = 0, time = 0, type = TYPE_REL_START)),
"thunderbird/reminder_at_start.txt".alarms
)
}
@Test
fun reminder50Days() {
assertEquals(
listOf(Alarm(task = 0, time = -TimeUnit.DAYS.toMillis(50), type = TYPE_REL_START)),
"thunderbird/reminder_50_days.txt".alarms
)
}
@Test @Test
@Ignore @Ignore
fun dontCrashOnMultipleTasks() { fun dontCrashOnMultipleTasks() {

@ -0,0 +1,39 @@
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Apple Inc.//iOS 12.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
DTSTART:20070311T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
TZNAME:CDT
TZOFFSETFROM:-0600
TZOFFSETTO:-0500
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:20071104T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
TZNAME:CST
TZOFFSETFROM:-0500
TZOFFSETTO:-0600
END:STANDARD
END:VTIMEZONE
BEGIN:VTODO
CREATED:20220119T045003Z
DTSTAMP:20220119T045013Z
DTSTART;TZID=America/Chicago:20220118T230000
DUE;TZID=America/Chicago:20220118T230000
LAST-MODIFIED:20220119T045013Z
STATUS:NEEDS-ACTION
SUMMARY:Test
UID:A11D558C-3BC3-45FE-B95E-8B1F0023CB17
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Reminder
TRIGGER;VALUE=DATE-TIME:20220119T050000Z
UID:2CBB42D2-039E-4F6B-BFDF-761632A15305
X-WR-ALARMUID:2CBB42D2-039E-4F6B-BFDF-761632A15305
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,25 @@
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Apple Inc.//iOS 12.1//EN
VERSION:2.0
BEGIN:VTODO
CREATED:20220119T050820Z
DTSTAMP:20220119T050843Z
LAST-MODIFIED:20220119T050843Z
STATUS:NEEDS-ACTION
SUMMARY:Test
UID:5F1A1AD2-6412-4F61-AE56-7D0EE8CE4CE5
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Reminder
TRIGGER;VALUE=DATE-TIME:19760401T005545Z
UID:9EF67428-7209-4EA4-9029-4049F577F4FF
X-APPLE-PROXIMITY:ARRIVE
X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=1600 Amphitheatre Pkwy\\
nMountain View CA 94043\\nUnited States;X-APPLE-RADIUS=141.1748408176572
;X-APPLE-REFERENCEFRAME=1;X-TITLE=Google Mountain View - Headquarters:ge
o:37.422338,-122.084370
X-WR-ALARMUID:9EF67428-7209-4EA4-9029-4049F577F4FF
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,25 @@
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Apple Inc.//iOS 12.1//EN
VERSION:2.0
BEGIN:VTODO
CREATED:20220119T050930Z
DTSTAMP:20220119T050938Z
LAST-MODIFIED:20220119T050937Z
STATUS:NEEDS-ACTION
SUMMARY:Test
UID:E41BC37A-6261-4172-96F6-1B8FF9F74380
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Reminder
TRIGGER;VALUE=DATE-TIME:19760401T005545Z
UID:9E6BDF76-8FA4-4B22-AE14-FD2A43BC4D18
X-APPLE-PROXIMITY:DEPART
X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=1600 Amphitheatre Pkwy\\
nMountain View CA 94043\\nUnited States;X-APPLE-RADIUS=141.1748408176572
;X-APPLE-REFERENCEFRAME=1;X-TITLE=Google Mountain View - Headquarters:ge
o:37.422338,-122.084370
X-WR-ALARMUID:9E6BDF76-8FA4-4B22-AE14-FD2A43BC4D18
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,17 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTODO
CREATED:20220121T053659Z
LAST-MODIFIED:20220121T053753Z
DTSTAMP:20220121T053753Z
UID:e7cd55dc-9519-2a45-8630-4b632a4ca297
SUMMARY:Test
PERCENT-COMPLETE:0
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DATE-TIME:20220121T190000Z
DESCRIPTION:Default Mozilla Description
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,35 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
TZOFFSETTO:-0500
TZNAME:CDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
TZOFFSETTO:-0600
TZNAME:CST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
END:STANDARD
END:VTIMEZONE
BEGIN:VTODO
CREATED:20220206T055100Z
LAST-MODIFIED:20220206T055130Z
DTSTAMP:20220206T055130Z
UID:796d08f4-dd49-4749-94ba-6ce2ff0aae22
SUMMARY:Test
DTSTART;TZID=America/Chicago:20230201T230000
PERCENT-COMPLETE:0
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DURATION:-P50D
DESCRIPTION:Default Mozilla Description
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,37 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
TZOFFSETTO:-0500
TZNAME:CDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
TZOFFSETTO:-0600
TZNAME:CST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
END:STANDARD
END:VTIMEZONE
BEGIN:VTODO
CREATED:20220121T054934Z
LAST-MODIFIED:20220121T055843Z
DTSTAMP:20220121T055843Z
UID:5b3a42d5-e99c-2f47-87d5-8ba0e2b753ab
SUMMARY:Test
DUE;TZID=America/Chicago:20220121T130000
PERCENT-COMPLETE:0
X-MOZ-GENERATION:3
SEQUENCE:1
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DURATION;RELATED=END:PT15M
DESCRIPTION:Default Mozilla Description
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,37 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
TZOFFSETTO:-0500
TZNAME:CDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
TZOFFSETTO:-0600
TZNAME:CST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
END:STANDARD
END:VTIMEZONE
BEGIN:VTODO
CREATED:20220121T054934Z
LAST-MODIFIED:20220121T055642Z
DTSTAMP:20220121T055642Z
UID:5b3a42d5-e99c-2f47-87d5-8ba0e2b753ab
SUMMARY:Test
DTSTART;TZID=America/Chicago:20220121T130000
DUE;TZID=America/Chicago:20220121T130000
PERCENT-COMPLETE:0
X-MOZ-GENERATION:1
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DURATION:PT15M
DESCRIPTION:Default Mozilla Description
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,35 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
TZOFFSETTO:-0500
TZNAME:CDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
TZOFFSETTO:-0600
TZNAME:CST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
END:STANDARD
END:VTIMEZONE
BEGIN:VTODO
CREATED:20220205T213726Z
LAST-MODIFIED:20220205T213750Z
DTSTAMP:20220205T213750Z
UID:7d5eca48-9faa-294c-8155-052a4e49f342
SUMMARY:Test
DTSTART;TZID=America/Chicago:20220205T160000
PERCENT-COMPLETE:0
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DURATION:PT0S
DESCRIPTION:Default Mozilla Description
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,37 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
TZOFFSETTO:-0500
TZNAME:CDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
TZOFFSETTO:-0600
TZNAME:CST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
END:STANDARD
END:VTIMEZONE
BEGIN:VTODO
CREATED:20220121T054934Z
LAST-MODIFIED:20220121T055751Z
DTSTAMP:20220121T055751Z
UID:5b3a42d5-e99c-2f47-87d5-8ba0e2b753ab
SUMMARY:Test
DUE;TZID=America/Chicago:20220121T130000
PERCENT-COMPLETE:0
X-MOZ-GENERATION:2
SEQUENCE:1
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DURATION;RELATED=END:-PT15M
DESCRIPTION:Default Mozilla Description
END:VALARM
END:VTODO
END:VCALENDAR

@ -0,0 +1,36 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
TZOFFSETTO:-0500
TZNAME:CDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
TZOFFSETTO:-0600
TZNAME:CST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
END:STANDARD
END:VTIMEZONE
BEGIN:VTODO
CREATED:20220121T054934Z
LAST-MODIFIED:20220121T054956Z
DTSTAMP:20220121T054956Z
UID:5b3a42d5-e99c-2f47-87d5-8ba0e2b753ab
SUMMARY:Test
DTSTART;TZID=America/Chicago:20220121T130000
DUE;TZID=America/Chicago:20220121T130000
PERCENT-COMPLETE:0
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DURATION:-PT1H
DESCRIPTION:Default Mozilla Description
END:VALARM
END:VTODO
END:VCALENDAR
Loading…
Cancel
Save