Commit c1442429 authored by Ricki Hirner's avatar Ricki Hirner 🐑

Events: process CATEGORIES explicitly (and not only as unknown properties)

- process/store CATEGORIES in Event data class
- AndroidEvent reads/saves categories in the same format used by the AOSP Exchange ActiveSync adapter
- ICalPreprocessor: rewrite invalid CREATED properties which are not in UTC to UTC
parent a3dc0d06
Pipeline #102583904 passed with stages
in 4 minutes and 49 seconds
......@@ -12,11 +12,9 @@ import android.content.ContentValues
import android.database.MatrixCursor
import androidx.test.filters.SmallTest
import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues
import at.bitfire.ical4android.MiscUtils.TextListHelper.toList
import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.TextList
import net.fortuna.ical4j.model.TimeZone
import net.fortuna.ical4j.model.component.VTimeZone
import net.fortuna.ical4j.model.property.DtStart
......@@ -114,13 +112,6 @@ class MiscUtilsTest {
assertEquals("row1_val2", values.getAsString("col2"))
}
@Test
@SmallTest
fun testTextListToList() {
assertEquals(listOf("str1", "str2"), TextList(arrayOf("str1", "str2")).toList())
assertEquals(emptyList<String>(), TextList(arrayOf()).toList())
}
@Suppress("unused")
private class TestClass {
......
......@@ -57,6 +57,18 @@ abstract class AndroidEvent(
@Deprecated("New content item MIME type", ReplaceWith("UnknownProperty.CONTENT_ITEM_TYPE"))
const val EXT_UNKNOWN_PROPERTY2 = "unknown-property.v2"
/**
* VEVENT CATEGORIES will be stored as an extended property with this [ExtendedProperties.NAME].
*
* The [ExtendedProperties.VALUE] format is the same as used by the AOSP Exchange ActiveSync adapter:
* the category values are stored as list, separated by [EXT_CATEGORIES_SEPARATOR]. (If a category
* value contains [EXT_CATEGORIES_SEPARATOR], [EXT_CATEGORIES_SEPARATOR] will be dropped.)
*
* Example: `Cat1\Cat2`
*/
const val EXT_CATEGORIES = "categories"
const val EXT_CATEGORIES_SEPARATOR = '\\'
/**
* EMAIL parameter name (as used for ORGANIZER). Not declared in ical4j Parameters class yet.
*/
......@@ -340,11 +352,17 @@ abstract class AndroidEvent(
}
protected open fun populateExtended(row: ContentValues) {
Constants.log.log(Level.FINE, "Read extended property from calender provider", row.getAsString(ExtendedProperties.NAME))
val name = row.getAsString(ExtendedProperties.NAME)
Constants.log.log(Level.FINE, "Read extended property from calender provider (name=$name)")
val event = requireNotNull(event)
try {
when (row.getAsString(ExtendedProperties.NAME)) {
EXT_CATEGORIES -> {
val rawCategories = row.getAsString(ExtendedProperties.VALUE)
event.categories += rawCategories.split(EXT_CATEGORIES_SEPARATOR)
}
EXT_UNKNOWN_PROPERTY -> {
// deserialize unknown property (deprecated format)
val stream = ByteArrayInputStream(Base64.decode(row.getAsString(ExtendedProperties.VALUE), Base64.NO_WRAP))
......@@ -426,6 +444,8 @@ abstract class AndroidEvent(
// add unknown properties
retainClassification()
if (event.categories.isNotEmpty())
insertCategories(batch, idxEvent)
event.unknownProperties.forEach { insertUnknownProperty(batch, idxEvent, it) }
// add exceptions
......@@ -711,6 +731,18 @@ abstract class AndroidEvent(
batch.enqueue(BatchOperation.Operation(builder, Attendees.EVENT_ID, idxEvent))
}
protected open fun insertCategories(batch: BatchOperation, idxEvent: Int) {
val rawCategories = event!!.categories
.map { it.filter { it != EXT_CATEGORIES_SEPARATOR } } // drop backslashes
.joinToString(EXT_CATEGORIES_SEPARATOR.toString()) // concatenate, separate by backslash
val builder = ContentProviderOperation.newInsert(calendar.syncAdapterURI(ExtendedProperties.CONTENT_URI))
.withValue(ExtendedProperties.NAME, EXT_CATEGORIES)
.withValue(ExtendedProperties.VALUE, rawCategories)
Constants.log.log(Level.FINE, "Built categories", builder.build())
batch.enqueue(BatchOperation.Operation(builder, ExtendedProperties.EVENT_ID, idxEvent))
}
protected open fun insertUnknownProperty(batch: BatchOperation, idxEvent: Int, property: Property) {
if (property.value.length > UnknownProperty.MAX_UNKNOWN_PROPERTY_SIZE) {
Constants.log.warning("Ignoring unknown property with ${property.value.length} octets (too long)")
......@@ -721,6 +753,7 @@ abstract class AndroidEvent(
.withValue(ExtendedProperties.NAME, UnknownProperty.CONTENT_ITEM_TYPE)
.withValue(ExtendedProperties.VALUE, UnknownProperty.toJsonString(property))
Constants.log.log(Level.FINE, "Built unknown property: ${property.name}")
batch.enqueue(BatchOperation.Operation(builder, ExtendedProperties.EVENT_ID, idxEvent))
}
......
......@@ -55,6 +55,7 @@ class Event: ICalendar() {
var lastModified: LastModified? = null
val categories = LinkedList<String>()
val unknownProperties = LinkedList<Property>()
companion object {
......@@ -150,6 +151,9 @@ class Event: ICalendar() {
is Summary -> e.summary = prop.value
is Location -> e.location = prop.value
is Description -> e.description = prop.value
is Categories ->
for (category in prop.categories)
e.categories += category
is Color -> e.color = Css3Color.fromString(prop.value)
is DtStart -> e.dtStart = prop
is DtEnd -> e.dtEnd = prop
......@@ -271,6 +275,8 @@ class Event: ICalendar() {
organizer?.let { props += it }
props.addAll(attendees)
if (categories.isNotEmpty())
props += Categories(TextList(categories.toTypedArray()))
props.addAll(unknownProperties)
lastModified?.let { props += it }
......
......@@ -2,6 +2,7 @@ package at.bitfire.ical4android
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.transform.rfc5545.CreatedPropertyRule
import net.fortuna.ical4j.transform.rfc5545.DateListPropertyRule
import net.fortuna.ical4j.transform.rfc5545.DatePropertyRule
import net.fortuna.ical4j.transform.rfc5545.Rfc5545PropertyRule
......@@ -17,6 +18,7 @@ import java.util.logging.Level
object ICalPreprocessor {
private val propertyRules = arrayOf(
CreatedPropertyRule(), // make sure CREATED is UTC
DatePropertyRule(),
DateListPropertyRule()
)
......
......@@ -119,17 +119,4 @@ object MiscUtils {
}
object TextListHelper {
fun TextList.toList(): List<String> {
val list = LinkedList<String>()
val it = iterator()
while (it.hasNext())
list += it.next()
return list
}
}
}
\ No newline at end of file
......@@ -10,7 +10,6 @@
package at.bitfire.ical4android
import at.bitfire.ical4android.MiscUtils.TextListHelper.toList
import net.fortuna.ical4j.data.CalendarOutputter
import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.model.*
......@@ -115,7 +114,9 @@ class Task: ICalendar() {
is RRule -> t.rRule = prop
is RDate -> t.rDates += prop
is ExDate -> t.exDates += prop
is Categories -> t.categories.addAll(prop.categories.toList())
is Categories ->
for (category in prop.categories)
t.categories += category
is RelatedTo -> t.relatedTo.add(prop)
is Uid, is ProdId, is DtStamp -> { /* don't save these as unknown properties */ }
else -> t.unknownProperties += prop
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment