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 @@ -6,6 +6,7 @@

package at.bitfire.ical4android

import at.bitfire.ical4android.impl.testProdId
import at.bitfire.ical4android.util.DateUtils
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime
Expand Down Expand Up @@ -76,7 +77,7 @@ class EventTest {
e.alarms += VAlarm(Duration.ofMinutes(-30))
e.attendees += Attendee("mailto:[email protected]")
val baos = ByteArrayOutputStream()
e.write(baos)
e.write(baos, testProdId)
val ical = baos.toString()

assertTrue("BEGIN:VTIMEZONE.+BEGIN:STANDARD.+END:STANDARD.+END:VTIMEZONE".toRegex(RegexOption.DOT_MATCHES_ALL).containsMatchIn(ical))
Expand Down Expand Up @@ -141,7 +142,7 @@ class EventTest {
)
}
val baos = ByteArrayOutputStream()
event.write(baos)
event.write(baos, testProdId)
val iCal = baos.toString()
assertTrue(iCal.contains("UID:test1\r\n"))
assertTrue(iCal.contains("DTSTART;TZID=Europe/Berlin:20190117T083000\r\n"))
Expand Down Expand Up @@ -250,10 +251,10 @@ class EventTest {
e.alarms += VAlarm(Duration.ofHours(-1))

val os = ByteArrayOutputStream()
e.write(os)
e.write(os, testProdId)
val raw = os.toString(Charsets.UTF_8.name())

assertTrue(raw.contains("PRODID:${ICalendar.prodId.value}"))
assertTrue(raw.contains("PRODID:${testProdId.value}"))
assertTrue(raw.contains("UID:SAMPLEUID"))
assertTrue(raw.contains("DTSTART;TZID=Europe/Berlin:20190101T100000"))
assertTrue(raw.contains("DTSTAMP:"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.ContentProviderClient
import android.content.ContentValues
import androidx.test.platform.app.InstrumentationRegistry
import at.bitfire.ical4android.impl.TestJtxCollection
import at.bitfire.ical4android.impl.testProdId
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.GrantPermissionOrSkipRule
import at.techbee.jtx.JtxContract
Expand Down Expand Up @@ -148,9 +149,10 @@ class JtxCollectionTest {
client.insert(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv1)
client.insert(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv2)

val ics = collections[0].getICSForCollection()
val ics = collections[0].getICSForCollection(testProdId)
assertTrue(ics.contains(Regex("BEGIN:VCALENDAR(\\n*|\\r*|\\t*|.*)*END:VCALENDAR")))
assertTrue(ics.contains("PRODID:+//IDN bitfire.at//ical4android"))
System.err.println(ics)
assertTrue(ics.contains("PRODID:${testProdId.value}"))
assertTrue(ics.contains("SUMMARY:summary"))
assertTrue(ics.contains("SUMMARY:entry2"))
assertTrue(ics.contains(Regex("BEGIN:VJOURNAL(\\n*|\\r*|\\t*|.*)*END:VJOURNAL")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.os.ParcelFileDescriptor
import androidx.core.content.pm.PackageInfoCompat
import androidx.test.platform.app.InstrumentationRegistry
import at.bitfire.ical4android.impl.TestJtxCollection
import at.bitfire.ical4android.impl.testProdId
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.GrantPermissionOrSkipRule
import at.techbee.jtx.JtxContract
Expand Down Expand Up @@ -881,7 +882,7 @@ class JtxICalObjectTest {

val os = ByteArrayOutputStream()

iCalObject[0].write(os)
iCalObject[0].write(os, testProdId)

val iCalOut = ICalendar.fromReader(os.toByteArray().inputStream().reader())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

package at.bitfire.ical4android

import at.bitfire.ical4android.impl.testProdId
import at.bitfire.ical4android.util.DateUtils
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateList
import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.Parameter
import net.fortuna.ical4j.model.TimeZone
import net.fortuna.ical4j.model.TimeZoneRegistryFactory
import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.parameter.RelType
import net.fortuna.ical4j.model.parameter.Value
Expand Down Expand Up @@ -207,10 +207,10 @@ class TaskTest {
t.alarms += alarm

val os = ByteArrayOutputStream()
t.write(os)
t.write(os, testProdId)
val raw = os.toString(Charsets.UTF_8.name())

assertTrue(raw.contains("PRODID:${t.prodId().value}"))
assertTrue(raw.contains("PRODID:${testProdId.value}"))
assertTrue(raw.contains("UID:SAMPLEUID"))
assertTrue(raw.contains("DTSTAMP:"))
assertTrue(raw.contains("DTSTART;TZID=Europe/Berlin:20190101T100000"))
Expand Down Expand Up @@ -267,7 +267,7 @@ class TaskTest {

private fun regenerate(t: Task): Task {
val os = ByteArrayOutputStream()
t.write(os)
t.write(os, testProdId)
return Task.tasksFromReader(InputStreamReader(ByteArrayInputStream(os.toByteArray()), Charsets.UTF_8)).first()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.ical4android.impl

import net.fortuna.ical4j.model.property.ProdId

val testProdId = ProdId("bitfireAT/synctools test")
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import at.bitfire.synctools.storage.LocalStorageException
import at.bitfire.vcard4android.impl.TestAddressBook
import at.bitfire.vcard4android.impl.testProductId
import at.bitfire.vcard4android.property.XAbDate
import ezvcard.VCardVersion
import ezvcard.property.Address
import ezvcard.property.Birthday
import ezvcard.property.Email
import ezvcard.util.PartialDate
import org.junit.*
import org.junit.Assert.*
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.StringReader
import java.time.LocalDate
Expand Down Expand Up @@ -206,8 +212,8 @@ class AndroidContactTest {
* So, ADR value components may contain DQUOTE (0x22) and don't have to be encoded as defined in RFC 6868 */

val os = ByteArrayOutputStream()
contact.writeVCard(VCardVersion.V4_0, os)
contact.writeVCard(VCardVersion.V4_0, os, testProductId)
assertTrue(os.toString().contains("ADR;LABEL=My ^'Label^'\\nLine 2:;;Street \"Address\";;;;"))
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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.vcard4android.impl

val testProductId = "bitfireAT/synctools test"
11 changes: 9 additions & 2 deletions lib/src/main/kotlin/at/bitfire/ical4android/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,16 @@ data class Event(
val unknownProperties: LinkedList<Property> = LinkedList()
) : ICalendar() {

fun write(os: OutputStream) {
/**
* Generates an iCalendar from the Event.
*
* @param os stream that the iCalendar is written to
* @param prodId `PRODID` that identifies the app
*/
fun write(os: OutputStream, prodId: ProdId) {
val ical = Calendar()
ical.properties += Version.VERSION_2_0
ical.properties += prodId()
ical.properties += prodId.withUserAgents(userAgents)

val dtStart = dtStart ?: throw InvalidCalendarException("Won't generate event without start time")

Expand Down Expand Up @@ -374,4 +380,5 @@ data class Event(
return e
}
}

}
25 changes: 16 additions & 9 deletions lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,29 @@ open class ICalendar {
get() = Logger.getLogger(ICalendar::class.java.name)

// known iCalendar properties

const val CALENDAR_NAME = "X-WR-CALNAME"
const val CALENDAR_COLOR = "X-APPLE-CALENDAR-COLOR"


// PRODID generation

/**
* Default PRODID used when generating iCalendars. If you want another value, set it
* statically before writing the first iCalendar.
* Extends the given `PRODID` with the user agents (typically calendar app name and version).
* This way the `PRODID` does not only identify the app that actually produces the iCalendar,
* but also the used front-end app, which may be helpful when debugging the iCalendar.
*
* @param userAgents list of involved user agents
* (preferably in `package name/version` format, for instance `com.example.mycalendar/1.0`)
*
* @return original `PRODID` with user agents in parentheses
*/
var prodId = ProdId("+//IDN bitfire.at//ical4android")

fun prodId(userAgents: List<String>): ProdId =
fun ProdId.withUserAgents(userAgents: List<String>) =
if (userAgents.isEmpty())
prodId
this
else
ProdId(prodId.value + " (" + userAgents.joinToString(",") + ")")
ProdId(value + " (${userAgents.joinToString(", ")})")


// parser

Expand Down Expand Up @@ -373,6 +382,4 @@ open class ICalendar {
uid = UUID.randomUUID().toString()
}

fun prodId(): ProdId = prodId(userAgents)

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import at.techbee.jtx.JtxContract.asSyncAdapter
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.component.VJournal
import net.fortuna.ical4j.model.component.VToDo
import net.fortuna.ical4j.model.property.ProdId
import net.fortuna.ical4j.model.property.Version
import java.util.LinkedList
import java.util.logging.Level
Expand Down Expand Up @@ -243,9 +244,11 @@ open class JtxCollection<out T: JtxICalObject>(val account: Account,


/**
* @param prodId `PRODID` that identifies the app
*
* @return a string with all JtxICalObjects within the collection as iCalendar
*/
fun getICSForCollection(): String {
fun getICSForCollection(prodId: ProdId): String {
client.query(
JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account),
null,
Expand All @@ -257,12 +260,12 @@ open class JtxCollection<out T: JtxICalObject>(val account: Account,

val ical = Calendar()
ical.properties += Version.VERSION_2_0
ical.properties += ICalendar.prodId
ical.properties += prodId

while (cursor?.moveToNext() == true) {
val jtxIcalObject = JtxICalObject(this)
jtxIcalObject.populateFromContentValues(cursor.toValues())
val singleICS = jtxIcalObject.getICalendarFormat()
val singleICS = jtxIcalObject.getICalendarFormat(prodId)
singleICS?.components?.forEach { component ->
if(component is VToDo || component is VJournal)
ical.components += component
Expand Down
14 changes: 10 additions & 4 deletions lib/src/main/kotlin/at/bitfire/ical4android/JtxICalObject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.net.ParseException
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.util.Base64
import at.bitfire.ical4android.ICalendar.Companion.withUserAgents
import at.bitfire.ical4android.util.MiscUtils.toValues
import at.bitfire.synctools.storage.BatchOperation
import at.bitfire.synctools.storage.JtxBatchOperation
Expand Down Expand Up @@ -629,12 +630,15 @@ open class JtxICalObject(

/**
* Takes the current JtxICalObject and transforms it to a Calendar (ical4j)
*
* @param prodId `PRODID` that identifies the app
*
* @return The current JtxICalObject transformed into a ical4j Calendar
*/
fun getICalendarFormat(): Calendar? {
fun getICalendarFormat(prodId: ProdId): Calendar? {
val ical = Calendar()
ical.properties += Version.VERSION_2_0
ical.properties += ICalendar.prodId(listOf(TaskProvider.ProviderName.JtxBoard.packageName))
ical.properties += prodId.withUserAgents(listOf(TaskProvider.ProviderName.JtxBoard.packageName))

val calComponent = when (component) {
JtxContract.JtxICalObject.Component.VTODO.name -> VToDo(true /* generates DTSTAMP */)
Expand Down Expand Up @@ -731,10 +735,12 @@ open class JtxICalObject(

/**
* Takes the current JtxICalObject, transforms it to an iCalendar and writes it in an OutputStream
*
* @param [os] OutputStream where iCalendar should be written to
* @param prodId `PRODID` that identifies the app
*/
fun write(os: OutputStream) {
CalendarOutputter(false).output(this.getICalendarFormat(), os)
fun write(os: OutputStream, prodId: ProdId) {
CalendarOutputter(false).output(this.getICalendarFormat(prodId), os)
}

/**
Expand Down
10 changes: 8 additions & 2 deletions lib/src/main/kotlin/at/bitfire/ical4android/Task.kt
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,16 @@ data class Task(
}


fun write(os: OutputStream) {
/**
* Generates an iCalendar from the Task.
*
* @param os stream that the iCalendar is written to
* @param prodId `PRODID` that identifies the app
*/
fun write(os: OutputStream, prodId: ProdId) {
val ical = Calendar()
ical.properties += Version.VERSION_2_0
ical.properties += prodId()
ical.properties += prodId.withUserAgents(userAgents)

val vTodo = VToDo(true /* generates DTSTAMP */)
ical.components += vTodo
Expand Down
25 changes: 15 additions & 10 deletions lib/src/main/kotlin/at/bitfire/vcard4android/Contact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ import com.google.common.base.MoreObjects
import ezvcard.VCardVersion
import ezvcard.io.json.JCardReader
import ezvcard.io.text.VCardReader
import ezvcard.property.*
import ezvcard.property.Address
import ezvcard.property.Anniversary
import ezvcard.property.Birthday
import ezvcard.property.Email
import ezvcard.property.Impp
import ezvcard.property.Nickname
import ezvcard.property.Organization
import ezvcard.property.Related
import ezvcard.property.Telephone
import ezvcard.property.Url
import java.io.IOException
import java.io.OutputStream
import java.io.Reader
import java.util.*
import java.util.LinkedList

/**
* Data class for a contact; between vCards and the Android contacts provider.
Expand Down Expand Up @@ -77,10 +86,6 @@ data class Contact(
) {

companion object {
// productID (if set) will be used to generate a PRODID property.
// You may set this statically from the calling application.
var productID: String? = null

const val DATE_PARAMETER_OMIT_YEAR = "X-APPLE-OMIT-YEAR"
const val DATE_PARAMETER_OMIT_YEAR_DEFAULT = 1604

Expand Down Expand Up @@ -120,14 +125,14 @@ data class Contact(


@Throws(IOException::class)
fun writeJCard(os: OutputStream) {
val generator = ContactWriter.fromContact(this, VCardVersion.V4_0)
fun writeJCard(os: OutputStream, productId: String) {
val generator = ContactWriter(this, VCardVersion.V4_0, productId)
generator.writeCard(os, true)
}

@Throws(IOException::class)
fun writeVCard(vCardVersion: VCardVersion, os: OutputStream) {
val generator = ContactWriter.fromContact(this, vCardVersion)
fun writeVCard(vCardVersion: VCardVersion, os: OutputStream, productId: String) {
val generator = ContactWriter(this, vCardVersion, productId)
generator.writeCard(os, false)
}

Expand Down
Loading