@@ -8,54 +8,29 @@ package at.bitfire.ical4android
88
99import androidx.annotation.IntRange
1010import at.bitfire.ical4android.util.DateUtils
11- import at.bitfire.synctools.exception.InvalidICalendarException
12- import at.bitfire.synctools.icalendar.Css3Color
13- import net.fortuna.ical4j.data.CalendarOutputter
14- import net.fortuna.ical4j.model.Calendar
15- import net.fortuna.ical4j.model.Component
16- import net.fortuna.ical4j.model.DateTime
1711import net.fortuna.ical4j.model.Property
18- import net.fortuna.ical4j.model.TextList
19- import net.fortuna.ical4j.model.TimeZone
2012import net.fortuna.ical4j.model.component.VAlarm
21- import net.fortuna.ical4j.model.component.VToDo
22- import net.fortuna.ical4j.model.property.Categories
2313import net.fortuna.ical4j.model.property.Clazz
24- import net.fortuna.ical4j.model.property.Color
25- import net.fortuna.ical4j.model.property.Comment
2614import net.fortuna.ical4j.model.property.Completed
27- import net.fortuna.ical4j.model.property.Created
28- import net.fortuna.ical4j.model.property.Description
29- import net.fortuna.ical4j.model.property.DtStamp
3015import net.fortuna.ical4j.model.property.DtStart
3116import net.fortuna.ical4j.model.property.Due
3217import net.fortuna.ical4j.model.property.Duration
3318import net.fortuna.ical4j.model.property.ExDate
3419import net.fortuna.ical4j.model.property.Geo
35- import net.fortuna.ical4j.model.property.LastModified
36- import net.fortuna.ical4j.model.property.Location
3720import net.fortuna.ical4j.model.property.Organizer
38- import net.fortuna.ical4j.model.property.PercentComplete
3921import net.fortuna.ical4j.model.property.Priority
40- import net.fortuna.ical4j.model.property.ProdId
4122import net.fortuna.ical4j.model.property.RDate
4223import net.fortuna.ical4j.model.property.RRule
4324import net.fortuna.ical4j.model.property.RelatedTo
44- import net.fortuna.ical4j.model.property.Sequence
4525import net.fortuna.ical4j.model.property.Status
46- import net.fortuna.ical4j.model.property.Summary
47- import net.fortuna.ical4j.model.property.Uid
48- import net.fortuna.ical4j.model.property.Url
49- import net.fortuna.ical4j.model.property.Version
50- import java.io.IOException
51- import java.io.OutputStream
52- import java.io.Reader
53- import java.net.URI
54- import java.net.URISyntaxException
5526import java.util.LinkedList
56- import java.util.logging.Level
57- import java.util.logging.Logger
5827
28+ /* *
29+ * Data class representing a task
30+ *
31+ * - as it is extracted from an iCalendar or
32+ * - as it should be generated into an iCalendar.
33+ */
5934data class Task (
6035 var createdAt : Long? = null ,
6136 var lastModified : Long? = null ,
@@ -94,198 +69,10 @@ data class Task(
9469 val alarms : LinkedList <VAlarm > = LinkedList (),
9570) : ICalendar() {
9671
97- companion object {
98-
99- private val logger
100- get() = Logger .getLogger(Task ::class .java.name)
101-
102- /* *
103- * Parses an iCalendar resource, applies [at.bitfire.synctools.icalendar.validation.ICalPreprocessor] to increase compatibility
104- * and extracts the VTODOs.
105- *
106- * @param reader where the iCalendar is taken from
107- *
108- * @return array of filled [Task] data objects (may have size 0)
109- *
110- * @throws InvalidICalendarException when the iCalendar can't be parsed
111- * @throws IOException on I/O errors
112- */
113- fun tasksFromReader (reader : Reader ): List <Task > {
114- val ical = fromReader(reader)
115- val vToDos = ical.getComponents<VToDo >(Component .VTODO )
116- return vToDos.mapTo(LinkedList ()) { this .fromVToDo(it) }
117- }
118-
119- private fun fromVToDo (todo : VToDo ): Task {
120- val t = Task ()
121-
122- if (todo.uid != null )
123- t.uid = todo.uid.value
124- else {
125- logger.warning(" Received VTODO without UID, generating new one" )
126- t.generateUID()
127- }
128-
129- // sequence must only be null for locally created, not-yet-synchronized events
130- t.sequence = 0
131-
132- for (prop in todo.properties)
133- when (prop) {
134- is Sequence -> t.sequence = prop.sequenceNo
135- is Created -> t.createdAt = prop.dateTime.time
136- is LastModified -> t.lastModified = prop.dateTime.time
137- is Summary -> t.summary = prop.value
138- is Location -> t.location = prop.value
139- is Geo -> t.geoPosition = prop
140- is Description -> t.description = prop.value
141- is Color -> t.color = Css3Color .fromString(prop.value)?.argb
142- is Url -> t.url = prop.value
143- is Organizer -> t.organizer = prop
144- is Priority -> t.priority = prop.level
145- is Clazz -> t.classification = prop
146- is Status -> t.status = prop
147- is Due -> { t.due = prop }
148- is Duration -> t.duration = prop
149- is DtStart -> { t.dtStart = prop }
150- is Completed -> { t.completedAt = prop }
151- is PercentComplete -> t.percentComplete = prop.percentage
152- is RRule -> t.rRule = prop
153- is RDate -> t.rDates + = prop
154- is ExDate -> t.exDates + = prop
155- is Categories ->
156- for (category in prop.categories)
157- t.categories + = category
158- is Comment -> t.comment = prop.value
159- is RelatedTo -> t.relatedTo.add(prop)
160- is Uid , is ProdId , is DtStamp -> { /* don't save these as unknown properties */ }
161- else -> t.unknownProperties + = prop
162- }
163-
164- t.alarms.addAll(todo.alarms)
165-
166- // There seem to be many invalid tasks out there because of some defect clients, do some validation.
167- val dtStart = t.dtStart
168- val due = t.due
169-
170- if (dtStart != null && due != null ) {
171- if (DateUtils .isDate(dtStart) && DateUtils .isDateTime(due)) {
172- logger.warning(" DTSTART is DATE but DUE is DATE-TIME, rewriting DTSTART to DATE-TIME" )
173- t.dtStart = DtStart (DateTime (dtStart.value, due.timeZone))
174- } else if (DateUtils .isDateTime(dtStart) && DateUtils .isDate(due)) {
175- logger.warning(" DTSTART is DATE-TIME but DUE is DATE, rewriting DUE to DATE-TIME" )
176- t.due = Due (DateTime (due.value, dtStart.timeZone))
177- }
178-
179-
180- if (due.date <= dtStart.date) {
181- logger.warning(" Found invalid DUE <= DTSTART; dropping DTSTART" )
182- t.dtStart = null
183- }
184- }
185-
186- if (t.duration != null && t.dtStart == null ) {
187- logger.warning(" Found DURATION without DTSTART; ignoring" )
188- t.duration = null
189- }
190-
191- return t
192- }
193-
194- }
195-
196-
197- /* *
198- * Generates an iCalendar from the Task.
199- *
200- * @param os stream that the iCalendar is written to
201- * @param prodId `PRODID` that identifies the app
202- */
203- fun write (os : OutputStream , prodId : ProdId ) {
204- val ical = Calendar ()
205- ical.properties + = Version .VERSION_2_0
206- ical.properties + = prodId.withUserAgents(userAgents)
207-
208- val vTodo = VToDo (true /* generates DTSTAMP */ )
209- ical.components + = vTodo
210- val props = vTodo.properties
211-
212- uid?.let { props + = Uid (uid) }
213- sequence?.let {
214- if (it != 0 )
215- props + = Sequence (it)
216- }
217-
218- createdAt?.let { props + = Created (DateTime (it)) }
219- lastModified?.let { props + = LastModified (DateTime (it)) }
220-
221- summary?.let { props + = Summary (it) }
222- location?.let { props + = Location (it) }
223- geoPosition?.let { props + = it }
224- description?.let { props + = Description (it) }
225- color?.let { props + = Color (null , Css3Color .nearestMatch(it).name) }
226- url?.let {
227- try {
228- props + = Url (URI (it))
229- } catch (e: URISyntaxException ) {
230- logger.log(Level .WARNING , " Ignoring invalid task URL: $url " , e)
231- }
232- }
233- organizer?.let { props + = it }
234-
235- if (priority != Priority .UNDEFINED .level)
236- props + = Priority (priority)
237- classification?.let { props + = it }
238- status?.let { props + = it }
239-
240- rRule?.let { props + = it }
241- rDates.forEach { props + = it }
242- exDates.forEach { props + = it }
243-
244- if (categories.isNotEmpty())
245- props + = Categories (TextList (categories.toTypedArray()))
246- comment?.let { props + = Comment (it) }
247- props.addAll(relatedTo)
248- props.addAll(unknownProperties)
249-
250- // remember used time zones
251- val usedTimeZones = HashSet <TimeZone >()
252- due?.let {
253- props + = it
254- it.timeZone?.let (usedTimeZones::add)
255- }
256- duration?.let (props::add)
257- dtStart?.let {
258- props + = it
259- it.timeZone?.let (usedTimeZones::add)
260- }
261- completedAt?.let {
262- props + = it
263- it.timeZone?.let (usedTimeZones::add)
264- }
265- percentComplete?.let { props + = PercentComplete (it) }
266-
267- if (alarms.isNotEmpty())
268- vTodo.components.addAll(alarms)
269-
270- // determine earliest referenced date
271- val earliest = arrayOf(
272- dtStart?.date,
273- due?.date,
274- completedAt?.date
275- ).filterNotNull().minOrNull()
276- // add VTIMEZONE components
277- for (tz in usedTimeZones)
278- ical.components + = minifyVTimeZone(tz.vTimeZone, earliest)
279-
280- softValidate(ical)
281- CalendarOutputter (false ).output(ical, os)
282- }
283-
284-
28572 fun isAllDay (): Boolean {
286- return dtStart?.let { DateUtils .isDate(it) } ? :
287- due?.let { DateUtils .isDate(it) } ? :
288- true
73+ return dtStart?.let { DateUtils .isDate(it) }
74+ ? : due?.let { DateUtils .isDate(it) }
75+ ? : true
28976 }
29077
29178}
0 commit comments