diff --git a/lib/src/androidTest/kotlin/at/bitfire/ical4android/EventTest.kt b/lib/src/androidTest/kotlin/at/bitfire/ical4android/EventTest.kt index f2f4d725..0104df36 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/ical4android/EventTest.kt +++ b/lib/src/androidTest/kotlin/at/bitfire/ical4android/EventTest.kt @@ -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 @@ -76,7 +77,7 @@ class EventTest { e.alarms += VAlarm(Duration.ofMinutes(-30)) e.attendees += Attendee("mailto:test@example.com") 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)) @@ -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")) @@ -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:")) diff --git a/lib/src/androidTest/kotlin/at/bitfire/ical4android/JtxCollectionTest.kt b/lib/src/androidTest/kotlin/at/bitfire/ical4android/JtxCollectionTest.kt index b863d3db..6bba2a25 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/ical4android/JtxCollectionTest.kt +++ b/lib/src/androidTest/kotlin/at/bitfire/ical4android/JtxCollectionTest.kt @@ -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 @@ -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"))) diff --git a/lib/src/androidTest/kotlin/at/bitfire/ical4android/JtxICalObjectTest.kt b/lib/src/androidTest/kotlin/at/bitfire/ical4android/JtxICalObjectTest.kt index 73a1c980..506565d0 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/ical4android/JtxICalObjectTest.kt +++ b/lib/src/androidTest/kotlin/at/bitfire/ical4android/JtxICalObjectTest.kt @@ -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 @@ -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()) diff --git a/lib/src/androidTest/kotlin/at/bitfire/ical4android/TaskTest.kt b/lib/src/androidTest/kotlin/at/bitfire/ical4android/TaskTest.kt index d2d58da8..fcc8a6f2 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/ical4android/TaskTest.kt +++ b/lib/src/androidTest/kotlin/at/bitfire/ical4android/TaskTest.kt @@ -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 @@ -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")) @@ -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() } diff --git a/lib/src/androidTest/kotlin/at/bitfire/ical4android/impl/Constants.kt b/lib/src/androidTest/kotlin/at/bitfire/ical4android/impl/Constants.kt new file mode 100644 index 00000000..6666d401 --- /dev/null +++ b/lib/src/androidTest/kotlin/at/bitfire/ical4android/impl/Constants.kt @@ -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") diff --git a/lib/src/androidTest/kotlin/at/bitfire/vcard4android/AndroidContactTest.kt b/lib/src/androidTest/kotlin/at/bitfire/vcard4android/AndroidContactTest.kt index b3474753..2e82630b 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/vcard4android/AndroidContactTest.kt +++ b/lib/src/androidTest/kotlin/at/bitfire/vcard4android/AndroidContactTest.kt @@ -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 @@ -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\";;;;")) } -} +} \ No newline at end of file diff --git a/lib/src/androidTest/kotlin/at/bitfire/vcard4android/impl/Constants.kt b/lib/src/androidTest/kotlin/at/bitfire/vcard4android/impl/Constants.kt new file mode 100644 index 00000000..0b881e44 --- /dev/null +++ b/lib/src/androidTest/kotlin/at/bitfire/vcard4android/impl/Constants.kt @@ -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" \ No newline at end of file diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/Event.kt b/lib/src/main/kotlin/at/bitfire/ical4android/Event.kt index ec12145b..f8a3c372 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/Event.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/Event.kt @@ -97,10 +97,16 @@ data class Event( val unknownProperties: LinkedList = 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") @@ -374,4 +380,5 @@ data class Event( return e } } + } \ No newline at end of file diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt b/lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt index a8f4b1cc..7c9a51c5 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt @@ -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): ProdId = + fun ProdId.withUserAgents(userAgents: List) = if (userAgents.isEmpty()) - prodId + this else - ProdId(prodId.value + " (" + userAgents.joinToString(",") + ")") + ProdId(value + " (${userAgents.joinToString(", ")})") + // parser @@ -373,6 +382,4 @@ open class ICalendar { uid = UUID.randomUUID().toString() } - fun prodId(): ProdId = prodId(userAgents) - } \ No newline at end of file diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/JtxCollection.kt b/lib/src/main/kotlin/at/bitfire/ical4android/JtxCollection.kt index 9f12ba9b..0d2f54a2 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/JtxCollection.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/JtxCollection.kt @@ -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 @@ -243,9 +244,11 @@ open class JtxCollection(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, @@ -257,12 +260,12 @@ open class JtxCollection(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 diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/JtxICalObject.kt b/lib/src/main/kotlin/at/bitfire/ical4android/JtxICalObject.kt index 40f0ead7..916421af 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/JtxICalObject.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/JtxICalObject.kt @@ -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 @@ -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 */) @@ -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) } /** diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/Task.kt b/lib/src/main/kotlin/at/bitfire/ical4android/Task.kt index 40638860..6274904a 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/Task.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/Task.kt @@ -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 diff --git a/lib/src/main/kotlin/at/bitfire/vcard4android/Contact.kt b/lib/src/main/kotlin/at/bitfire/vcard4android/Contact.kt index 77573b98..3642f52d 100644 --- a/lib/src/main/kotlin/at/bitfire/vcard4android/Contact.kt +++ b/lib/src/main/kotlin/at/bitfire/vcard4android/Contact.kt @@ -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. @@ -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 @@ -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) } diff --git a/lib/src/main/kotlin/at/bitfire/vcard4android/ContactWriter.kt b/lib/src/main/kotlin/at/bitfire/vcard4android/ContactWriter.kt index 2cfc0450..826563f4 100644 --- a/lib/src/main/kotlin/at/bitfire/vcard4android/ContactWriter.kt +++ b/lib/src/main/kotlin/at/bitfire/vcard4android/ContactWriter.kt @@ -31,6 +31,7 @@ import ezvcard.property.DateOrTimeProperty import ezvcard.property.Kind import ezvcard.property.Member import ezvcard.property.Photo +import ezvcard.property.ProductId import ezvcard.property.Related import ezvcard.property.Revision import ezvcard.property.StructuredName @@ -47,15 +48,16 @@ import java.util.logging.Logger * to the vCard that is actually sent to the server. * * Properties which are not supported by the target vCard version have to be converted appropriately. + * + * @param contact contact data to be converted into a vCard + * @param version vCard version to generate + * @param productId product ID that identifies your app (will be used as PRODID; ez-vcard version will be appended) */ -class ContactWriter private constructor(val contact: Contact, val version: VCardVersion) { - - companion object { - - fun fromContact(contact: Contact, version: VCardVersion) = - ContactWriter(contact, version) - - } +class ContactWriter( + val contact: Contact, + val version: VCardVersion, + val productId: String +) { private val unknownProperties = LinkedList() val vCard = VCard() @@ -73,8 +75,8 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard } private fun addProperties() { + vCard.productId = ProductId("$productId (ez-vcard/${Ezvcard.VERSION})") contact.uid?.let { vCard.uid = Uid(it) } - Contact.productID?.let { vCard.setProductId(it) } addKindAndMembers() addFormattedName() @@ -355,7 +357,7 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard val writer = if (jCard) JCardWriter(stream).apply { - isAddProdId = Contact.productID == null + isAddProdId = false // we handle PRODID ourselves registerCustomScribes() // allow properties that are not defined in this vCard version @@ -363,7 +365,7 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard } else VCardWriter(stream, version).apply { - isAddProdId = Contact.productID == null + isAddProdId = false // we handle PRODID ourselves registerCustomScribes() /* include trailing semicolons for maximum compatibility diff --git a/lib/src/test/kotlin/at/bitfire/vcard4android/Constants.kt b/lib/src/test/kotlin/at/bitfire/vcard4android/Constants.kt new file mode 100644 index 00000000..d5c9f939 --- /dev/null +++ b/lib/src/test/kotlin/at/bitfire/vcard4android/Constants.kt @@ -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 + +val testProductId = "bitfireAT/synctools test" \ No newline at end of file diff --git a/lib/src/test/kotlin/at/bitfire/vcard4android/ContactTest.kt b/lib/src/test/kotlin/at/bitfire/vcard4android/ContactTest.kt index eb575700..71fd92cc 100644 --- a/lib/src/test/kotlin/at/bitfire/vcard4android/ContactTest.kt +++ b/lib/src/test/kotlin/at/bitfire/vcard4android/ContactTest.kt @@ -40,7 +40,7 @@ class ContactTest { private fun regenerate(c: Contact, vCardVersion: VCardVersion): Contact { val os = ByteArrayOutputStream() - c.writeVCard(vCardVersion, os) + c.writeVCard(vCardVersion, os, testProductId) return Contact.fromReader(InputStreamReader(ByteArrayInputStream(os.toByteArray()), Charsets.UTF_8), false,null).first() } @@ -85,7 +85,7 @@ class ContactTest { // TEL assertEquals(2, c.phoneNumbers.size) - var phone = c.phoneNumbers.first + var phone = c.phoneNumbers.first() assertEquals("Useless", phone.label) assertTrue(phone.property.types.contains(TelephoneType.VOICE)) assertTrue(phone.property.types.contains(TelephoneType.HOME)) @@ -99,7 +99,7 @@ class ContactTest { // EMAIL assertEquals(2, c.emails.size) - var email = c.emails.first + var email = c.emails.first() assertNull(email.label) assertTrue(email.property.types.contains(EmailType.HOME)) assertTrue(email.property.types.contains(EmailType.PREF)) @@ -120,7 +120,7 @@ class ContactTest { // IMPP assertEquals(3, c.impps.size) - var impp = c.impps.first + var impp = c.impps.first() assertEquals("MyIM", impp.label) assertTrue(impp.property.types.contains(ImppType.PERSONAL)) assertTrue(impp.property.types.contains(ImppType.MOBILE)) @@ -146,7 +146,7 @@ class ContactTest { // ADR assertEquals(2, c.addresses.size) - var addr = c.addresses.first + var addr = c.addresses.first() assertNull(addr.label) assertTrue(addr.property.types.contains(AddressType.WORK)) assertTrue(addr.property.types.contains(AddressType.POSTAL)) @@ -203,14 +203,14 @@ class ContactTest { assertEquals(LocalDate.of(2014, 8, 12), c.anniversary!!.date) // X-ABDATE assertEquals(1, c.customDates.size) - c.customDates.first.also { date -> + c.customDates.first().also { date -> assertEquals("Custom Date", date.label) assertEquals(LocalDate.of(2021, 7, 29), date.property.date) } // RELATED assertEquals(2, c.relations.size) - var rel = c.relations.first + var rel = c.relations.first() assertTrue(rel.types.contains(RelatedType.CO_WORKER)) assertTrue(rel.types.contains(RelatedType.CRUSH)) assertEquals("Ägidius", rel.text) @@ -229,19 +229,19 @@ class ContactTest { val c = regenerate(parseContact("allfields-vcard3.vcf"), VCardVersion.V4_0) // let's check only things that should be different when VCard 4.0 is generated - val phone = c.phoneNumbers.first.property + val phone = c.phoneNumbers.first().property assertFalse(phone.types.contains(TelephoneType.PREF)) assertNotNull(phone.pref) - val email = c.emails.first.property + val email = c.emails.first().property assertFalse(email.types.contains(EmailType.PREF)) assertNotNull(email.pref) - val impp = c.impps.first.property + val impp = c.impps.first().property assertFalse(impp.types.contains(ImppType.PREF)) assertNotNull(impp.pref) - val addr = c.addresses.first.property + val addr = c.addresses.first().property assertFalse(addr.types.contains(AddressType.PREF)) assertNotNull(addr.pref) } diff --git a/lib/src/test/kotlin/at/bitfire/vcard4android/ContactWriterTest.kt b/lib/src/test/kotlin/at/bitfire/vcard4android/ContactWriterTest.kt index 5b817f0b..301e1883 100644 --- a/lib/src/test/kotlin/at/bitfire/vcard4android/ContactWriterTest.kt +++ b/lib/src/test/kotlin/at/bitfire/vcard4android/ContactWriterTest.kt @@ -6,24 +6,44 @@ package at.bitfire.vcard4android -import at.bitfire.vcard4android.property.* +import at.bitfire.vcard4android.property.CustomType +import at.bitfire.vcard4android.property.XAbDate +import at.bitfire.vcard4android.property.XAbLabel +import at.bitfire.vcard4android.property.XAbRelatedNames +import at.bitfire.vcard4android.property.XAddressBookServerKind +import at.bitfire.vcard4android.property.XAddressBookServerMember +import at.bitfire.vcard4android.property.XPhoneticFirstName +import at.bitfire.vcard4android.property.XPhoneticLastName +import at.bitfire.vcard4android.property.XPhoneticMiddleName +import ezvcard.Ezvcard import ezvcard.VCard import ezvcard.VCardVersion import ezvcard.parameter.ImageType import ezvcard.parameter.RelatedType -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.Kind +import ezvcard.property.Nickname +import ezvcard.property.Organization +import ezvcard.property.Photo +import ezvcard.property.Related +import ezvcard.property.Revision +import ezvcard.property.StructuredName +import ezvcard.property.Telephone +import ezvcard.property.Url import ezvcard.util.PartialDate -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test import java.io.ByteArrayOutputStream import java.net.URI -import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime -import java.time.ZoneId import java.time.ZoneOffset import java.time.ZonedDateTime -import java.util.* class ContactWriterTest { @@ -451,7 +471,7 @@ class ContactWriterTest { values.add("nick1") }) } - assertEquals(3 /* NICK + REV + FN */, vCard.properties.size) + assertEquals(4 /* PRODID + NICK + REV + FN */, vCard.properties.size) assertEquals("nick1", vCard.nickname.values.first()) } @@ -462,7 +482,7 @@ class ContactWriterTest { values.add("nick1") }, "label1") } - assertEquals(4 /* NICK + X-ABLABEL + FN + REV */, vCard.properties.size) + assertEquals(5 /* PRODID + NICK + X-ABLABEL + FN + REV */, vCard.properties.size) vCard.nickname.apply { assertEquals("nick1", values.first()) assertEquals("item1", group) @@ -483,7 +503,7 @@ class ContactWriterTest { values.add("nick1") }, "label1") } - assertEquals(5 /* X-TEST + NICK + X-ABLABEL + FN + REV */, vCard.properties.size) + assertEquals(6 /* PRODID + X-TEST + NICK + X-ABLABEL + FN + REV */, vCard.properties.size) vCard.nickname.apply { assertEquals("nick1", values.first()) assertEquals("item2", group) @@ -497,7 +517,7 @@ class ContactWriterTest { @Test fun testRewritePartialDate_vCard3_Date() { - val generator = ContactWriter.fromContact(Contact(), VCardVersion.V3_0) + val generator = ContactWriter(Contact(), VCardVersion.V3_0, testProductId) val date = Birthday(LocalDate.of(121, 6, 30)) generator.rewritePartialDate(date) assertEquals(LocalDate.of(121, 6, 30), date.date) @@ -506,7 +526,7 @@ class ContactWriterTest { @Test fun testRewritePartialDate_vCard4_Date() { - val generator = ContactWriter.fromContact(Contact(), VCardVersion.V4_0) + val generator = ContactWriter(Contact(), VCardVersion.V4_0, testProductId) val date = Birthday(LocalDate.of(121, 6, 30)) generator.rewritePartialDate(date) assertEquals(LocalDate.of(121, 6, 30), date.date) @@ -516,7 +536,7 @@ class ContactWriterTest { @Test fun testRewritePartialDate_vCard3_PartialDateWithYear() { - val generator = ContactWriter.fromContact(Contact(), VCardVersion.V3_0) + val generator = ContactWriter(Contact(), VCardVersion.V3_0, testProductId) val date = Birthday(PartialDate.parse("20210730")) generator.rewritePartialDate(date) assertEquals(LocalDate.of(2021, 7, 30), date.date) @@ -526,7 +546,7 @@ class ContactWriterTest { @Test fun testRewritePartialDate_vCard4_PartialDateWithYear() { - val generator = ContactWriter.fromContact(Contact(), VCardVersion.V4_0) + val generator = ContactWriter(Contact(), VCardVersion.V4_0, testProductId) val date = Birthday(PartialDate.parse("20210730")) generator.rewritePartialDate(date) assertNull(date.date) @@ -536,7 +556,7 @@ class ContactWriterTest { @Test fun testRewritePartialDate_vCard3_PartialDateWithoutYear() { - val generator = ContactWriter.fromContact(Contact(), VCardVersion.V3_0) + val generator = ContactWriter(Contact(), VCardVersion.V3_0, testProductId) val date = Birthday(PartialDate.parse("--0730")) generator.rewritePartialDate(date) assertEquals(LocalDate.of(1604, 7, 30), date.date) @@ -547,7 +567,7 @@ class ContactWriterTest { @Test fun testRewritePartialDate_vCard4_PartialDateWithoutYear() { - val generator = ContactWriter.fromContact(Contact(), VCardVersion.V4_0) + val generator = ContactWriter(Contact(), VCardVersion.V4_0, testProductId) val date = Birthday(PartialDate.parse("--0730")) generator.rewritePartialDate(date) assertNull(date.date) @@ -558,27 +578,30 @@ class ContactWriterTest { @Test fun testWriteJCard() { - val generator = ContactWriter.fromContact(Contact(), VCardVersion.V4_0) + val generator = ContactWriter(Contact(), VCardVersion.V4_0, testProductId) generator.vCard.revision = Revision( ZonedDateTime.of(2021, 7, 30, 1, 2, 3, 0, ZoneOffset.UTC) ) val stream = ByteArrayOutputStream() generator.writeCard(stream, true) - assertEquals("[\"vcard\",[[\"version\",{},\"text\",\"4.0\"],[\"prodid\",{},\"text\",\"ez-vcard 0.12.1\"],[\"fn\",{},\"text\",\"\"],[\"rev\",{},\"timestamp\",\"2021-07-30T01:02:03+00:00\"]]]", stream.toString()) + assertEquals( + "[\"vcard\",[[\"version\",{},\"text\",\"4.0\"],[\"prodid\",{},\"text\",\"$testProductId (ez-vcard/${Ezvcard.VERSION})\"],[\"fn\",{},\"text\",\"\"],[\"rev\",{},\"timestamp\",\"2021-07-30T01:02:03+00:00\"]]]", + stream.toString() + ) } @Test fun testWriteVCard() { - val generator = ContactWriter.fromContact(Contact(), VCardVersion.V4_0) + val generator = ContactWriter(Contact(), VCardVersion.V4_0, testProductId) generator.vCard.revision = Revision(ZonedDateTime.of(2021, 7, 30, 1, 2, 3, 0, ZoneOffset.UTC)) val stream = ByteArrayOutputStream() generator.writeCard(stream, false) assertEquals("BEGIN:VCARD\r\n" + "VERSION:4.0\r\n" + - "PRODID:ez-vcard 0.12.1\r\n" + + "PRODID:$testProductId (ez-vcard/${Ezvcard.VERSION})\r\n" + "FN:\r\n" + "REV:20210730T010203+0000\r\n" + "END:VCARD\r\n", stream.toString()) @@ -594,9 +617,8 @@ class ContactWriterTest { country = "Line2" }) } - ContactWriter - .fromContact(contact, VCardVersion.V4_0) - .writeCard(stream, false) + ContactWriter(contact, VCardVersion.V4_0, testProductId) + .writeCard(stream, false) assertTrue(stream.toString().contains("ADR;LABEL=\"Li^^ne 1,1 - ^' -\":;;Line1;;;;Line2")) } @@ -606,7 +628,7 @@ class ContactWriterTest { private fun generate(version: VCardVersion = VCardVersion.V4_0, prepare: Contact.() -> Unit): VCard { val contact = Contact() contact.run(prepare) - return ContactWriter.fromContact(contact, version).vCard + return ContactWriter(contact, version, testProductId).vCard } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a0e20df4..bbf8ff3a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +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 + */ + pluginManagement { repositories { google() @@ -14,6 +20,5 @@ dependencyResolutionManagement { } } -rootProject.name = "root" -include(":lib") -project(":lib").name = "synctools" \ No newline at end of file +rootProject.name = "synctools" +include(":lib") \ No newline at end of file