Skip to content

Commit a2eada8

Browse files
authored
Extract task reading and writing from Task data class (#176)
* Extract task reading/parsing from data class * Extract task writing/generation from data class * Rename method
1 parent ee1b4b0 commit a2eada8

File tree

7 files changed

+573
-464
lines changed

7 files changed

+573
-464
lines changed

lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ open class ICalendar {
287287
val trigger = alarm.trigger ?: return null
288288

289289
val minutes: Int // minutes before/after the event
290-
var related = trigger.getParameter<Related>(Parameter.RELATED) ?: Related.START
290+
var related = trigger.getParameter(Parameter.RELATED) ?: Related.START
291291

292292
// event/task start time
293293
val start: java.util.Date? = refStart?.date
@@ -351,7 +351,7 @@ open class ICalendar {
351351
}
352352

353353

354-
protected fun generateUID() {
354+
fun generateUID() {
355355
uid = UUID.randomUUID().toString()
356356
}
357357

lib/src/main/kotlin/at/bitfire/ical4android/Task.kt

Lines changed: 9 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -8,54 +8,29 @@ package at.bitfire.ical4android
88

99
import androidx.annotation.IntRange
1010
import 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
1711
import net.fortuna.ical4j.model.Property
18-
import net.fortuna.ical4j.model.TextList
19-
import net.fortuna.ical4j.model.TimeZone
2012
import net.fortuna.ical4j.model.component.VAlarm
21-
import net.fortuna.ical4j.model.component.VToDo
22-
import net.fortuna.ical4j.model.property.Categories
2313
import net.fortuna.ical4j.model.property.Clazz
24-
import net.fortuna.ical4j.model.property.Color
25-
import net.fortuna.ical4j.model.property.Comment
2614
import 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
3015
import net.fortuna.ical4j.model.property.DtStart
3116
import net.fortuna.ical4j.model.property.Due
3217
import net.fortuna.ical4j.model.property.Duration
3318
import net.fortuna.ical4j.model.property.ExDate
3419
import net.fortuna.ical4j.model.property.Geo
35-
import net.fortuna.ical4j.model.property.LastModified
36-
import net.fortuna.ical4j.model.property.Location
3720
import net.fortuna.ical4j.model.property.Organizer
38-
import net.fortuna.ical4j.model.property.PercentComplete
3921
import net.fortuna.ical4j.model.property.Priority
40-
import net.fortuna.ical4j.model.property.ProdId
4122
import net.fortuna.ical4j.model.property.RDate
4223
import net.fortuna.ical4j.model.property.RRule
4324
import net.fortuna.ical4j.model.property.RelatedTo
44-
import net.fortuna.ical4j.model.property.Sequence
4525
import 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
5526
import 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+
*/
5934
data 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

Comments
 (0)