~chris.gagnon/+junk/qtpim-coverage

« back to all changes in this revision

Viewing changes to src/versitorganizer/qversitorganizerimporter_p.cpp

  • Committer: chris.gagnon
  • Date: 2013-12-10 23:09:37 UTC
  • Revision ID: chris.gagnon@canonical.com-20131210230937-2akf1ft1edcttk87
first post

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
 
4
** Contact: http://www.qt-project.org/legal
 
5
**
 
6
** This file is part of the QtVersitOrganizer module of the Qt Toolkit.
 
7
**
 
8
** $QT_BEGIN_LICENSE:LGPL$
 
9
** Commercial License Usage
 
10
** Licensees holding valid commercial Qt licenses may use this file in
 
11
** accordance with the commercial license agreement provided with the
 
12
** Software or, alternatively, in accordance with the terms contained in
 
13
** a written agreement between you and Digia.  For licensing terms and
 
14
** conditions see http://qt.digia.com/licensing.  For further information
 
15
** use the contact form at http://qt.digia.com/contact-us.
 
16
**
 
17
** GNU Lesser General Public License Usage
 
18
** Alternatively, this file may be used under the terms of the GNU Lesser
 
19
** General Public License version 2.1 as published by the Free Software
 
20
** Foundation and appearing in the file LICENSE.LGPL included in the
 
21
** packaging of this file.  Please review the following information to
 
22
** ensure the GNU Lesser General Public License version 2.1 requirements
 
23
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
 
24
**
 
25
** In addition, as a special exception, Digia gives you certain additional
 
26
** rights.  These rights are described in the Digia Qt LGPL Exception
 
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
 
28
**
 
29
** GNU General Public License Usage
 
30
** Alternatively, this file may be used under the terms of the GNU
 
31
** General Public License version 3.0 as published by the Free Software
 
32
** Foundation and appearing in the file LICENSE.GPL included in the
 
33
** packaging of this file.  Please review the following information to
 
34
** ensure the GNU General Public License version 3.0 requirements will be
 
35
** met: http://www.gnu.org/copyleft/gpl.html.
 
36
**
 
37
**
 
38
** $QT_END_LICENSE$
 
39
**
 
40
****************************************************************************/
 
41
 
 
42
#include "qversitorganizerimporter_p.h"
 
43
#include <qversitdocument.h>
 
44
#include <qversitproperty.h>
 
45
#include <qorganizer.h>
 
46
#include "qversitorganizerdefs_p.h"
 
47
#include "qversitorganizerpluginloader_p.h"
 
48
#include <private/qversitutils_p.h>
 
49
 
 
50
 
 
51
QT_BEGIN_NAMESPACE_VERSITORGANIZER
 
52
 
 
53
QVersitOrganizerImporterPrivate::QVersitOrganizerImporterPrivate(const QString& profile) :
 
54
    mPropertyHandler(NULL),
 
55
    mTimeZoneHandler(NULL),
 
56
    mDurationSpecified(false)
 
57
{
 
58
    int versitPropertyCount =
 
59
        sizeof(versitOrganizerDetailMappings)/sizeof(VersitOrganizerDetailMapping);
 
60
    for (int i = 0; i < versitPropertyCount; i++) {
 
61
        mPropertyMappings.insert(
 
62
                QLatin1String(versitOrganizerDetailMappings[i].versitPropertyName),
 
63
                QPair<QOrganizerItemDetail::DetailType, int>(
 
64
                    versitOrganizerDetailMappings[i].detailType,
 
65
                    versitOrganizerDetailMappings[i].detailField));
 
66
    }
 
67
 
 
68
    mPluginPropertyHandlers = QVersitOrganizerPluginLoader::instance()->createOrganizerHandlers(profile);
 
69
    mTimeZoneHandler = QVersitOrganizerPluginLoader::instance()->timeZoneHandler();
 
70
}
 
71
 
 
72
QVersitOrganizerImporterPrivate::~QVersitOrganizerImporterPrivate()
 
73
{
 
74
    foreach (QVersitOrganizerHandler* pluginHandler, mPluginPropertyHandlers) {
 
75
        delete pluginHandler;
 
76
    }
 
77
}
 
78
 
 
79
bool QVersitOrganizerImporterPrivate::importDocument(
 
80
        const QVersitDocument& topLevel,
 
81
        const QVersitDocument& subDocument,
 
82
        QOrganizerItem* item,
 
83
        QVersitOrganizerImporter::Error* error)
 
84
{
 
85
    if (subDocument.componentType() == QStringLiteral("VEVENT")) {
 
86
        item->setType(QOrganizerItemType::TypeEvent);
 
87
    } else if (subDocument.componentType() == QStringLiteral("VTODO")) {
 
88
        item->setType(QOrganizerItemType::TypeTodo);
 
89
    } else if (subDocument.componentType() == QStringLiteral("VJOURNAL")) {
 
90
        item->setType(QOrganizerItemType::TypeJournal);
 
91
    } else if (subDocument.componentType() == QStringLiteral("VTIMEZONE")) {
 
92
        mTimeZones.addTimeZone(importTimeZone(subDocument));
 
93
        *error = QVersitOrganizerImporter::NoError;
 
94
        return false;
 
95
    } else {
 
96
        *error = QVersitOrganizerImporter::InvalidDocumentError;
 
97
        return false;
 
98
    }
 
99
    const QList<QVersitProperty> properties = subDocument.properties();
 
100
    if ( (subDocument.subDocuments().isEmpty()) && (properties.isEmpty())) {
 
101
        *error = QVersitOrganizerImporter::EmptyDocumentError;
 
102
        return false;
 
103
    }
 
104
    foreach (const QVersitProperty& property, properties) {
 
105
        importProperty(subDocument, property, item);
 
106
    }
 
107
 
 
108
    if (!subDocument.subDocuments().isEmpty()) {
 
109
        foreach (const QVersitDocument &nestedSubDoc, subDocument.subDocuments())
 
110
            foreach (const QVersitProperty &nestedProp, nestedSubDoc.properties()) {
 
111
                importProperty(nestedSubDoc, nestedProp, item);
 
112
                if (nestedSubDoc.componentType() == QStringLiteral("VALARM"))
 
113
                    break;
 
114
            }
 
115
    }
 
116
    // run plugin handlers
 
117
    foreach (QVersitOrganizerImporterPropertyHandler* handler, mPluginPropertyHandlers) {
 
118
        handler->subDocumentProcessed(topLevel, subDocument, item);
 
119
    }
 
120
    // run property handlers
 
121
    if (mPropertyHandler) {
 
122
        mPropertyHandler->subDocumentProcessed(topLevel, subDocument, item);
 
123
    }
 
124
    return true;
 
125
}
 
126
 
 
127
void QVersitOrganizerImporterPrivate::importProperty(
 
128
        const QVersitDocument& document,
 
129
        const QVersitProperty& property,
 
130
        QOrganizerItem* item)
 
131
{
 
132
    QList<QOrganizerItemDetail> updatedDetails;
 
133
 
 
134
    bool success = false;
 
135
    if (property.name() == QStringLiteral("CREATED")) {
 
136
        success = createTimestampCreated(property, item, &updatedDetails);
 
137
    } else if (property.name() == QStringLiteral("LAST-MODIFIED")) {
 
138
        success = createTimestampModified(property, item, &updatedDetails);
 
139
    } else if (property.name() == QStringLiteral("X-QTPROJECT-VERSION")) {
 
140
        success = createVersion(property, item, &updatedDetails);
 
141
    } else if (property.name() == QStringLiteral("PRIORITY")) {
 
142
        success = createPriority(property, item, &updatedDetails);
 
143
    } else if (property.name() == QStringLiteral("COMMENT")) {
 
144
        success = createComment(property, &updatedDetails);
 
145
    } else if (property.name() == QStringLiteral("X-QTPROJECT-EXTENDED-DETAIL")) {
 
146
        success = createExtendedDetail(property, &updatedDetails);
 
147
    } else if (mPropertyMappings.contains(property.name())) {
 
148
        success = createSimpleDetail(property, item, &updatedDetails);
 
149
    } else if (document.componentType() == QStringLiteral("VEVENT")) {
 
150
        if (property.name() == QStringLiteral("DTSTART")) {
 
151
            success = createStartDateTime(property, item, &updatedDetails);
 
152
        } else if (property.name() == QStringLiteral("DTEND")) {
 
153
            success = createEndDateTime(property, item, &updatedDetails);
 
154
        } else if (property.name() == QStringLiteral("DURATION")) {
 
155
            success = createDuration(property, item, &updatedDetails);
 
156
        } else if (property.name() == QStringLiteral("RRULE")
 
157
               || (property.name() == QStringLiteral("EXRULE"))) {
 
158
            success = createRecurrenceRule(property, item, &updatedDetails);
 
159
        } else if (property.name() == QStringLiteral("RDATE")
 
160
               || (property.name() == QStringLiteral("EXDATE"))) {
 
161
            success = createRecurrenceDates(property, item, &updatedDetails);
 
162
        } else if (property.name() == QStringLiteral("RECURRENCE-ID")) {
 
163
            success = createRecurrenceId(property, item, &updatedDetails);
 
164
        }
 
165
    } else if (document.componentType() == QStringLiteral("VTODO")) {
 
166
        if (property.name() == QStringLiteral("DTSTART")) {
 
167
            success = createTodoStartDateTime(property, item, &updatedDetails);
 
168
        } else if (property.name() == QStringLiteral("DUE")) {
 
169
            success = createDueDateTime(property, item, &updatedDetails);
 
170
        } else if (property.name() == QStringLiteral("RRULE")
 
171
               || (property.name() == QStringLiteral("EXRULE"))) {
 
172
            success = createRecurrenceRule(property, item, &updatedDetails);
 
173
        } else if (property.name() == QStringLiteral("RDATE")
 
174
               || (property.name() == QStringLiteral("EXDATE"))) {
 
175
            success = createRecurrenceDates(property, item, &updatedDetails);
 
176
        } else if (property.name() == QStringLiteral("STATUS")) {
 
177
            success = createStatus(property, item, &updatedDetails);
 
178
        } else if (property.name() == QStringLiteral("PERCENT-COMPLETE")) {
 
179
            success = createPercentageComplete(property, item, &updatedDetails);
 
180
        } else if (property.name() == QStringLiteral("COMPLETED")) {
 
181
            success = createFinishedDateTime(property, item, &updatedDetails);
 
182
        } else if (property.name() == QStringLiteral("RECURRENCE-ID")) {
 
183
            success = createRecurrenceId(property, item, &updatedDetails);
 
184
        }
 
185
    } else if (document.componentType() == QStringLiteral("VALARM")) {
 
186
            success = createItemReminder(document, item, &updatedDetails);
 
187
    } else if (document.componentType() == QStringLiteral("VJOURNAL")) {
 
188
        if (property.name() == QStringLiteral("DTSTART")) {
 
189
            success = createJournalEntryDateTime(property, item, &updatedDetails);
 
190
        }
 
191
    }
 
192
 
 
193
    // run plugin handlers
 
194
    foreach (QVersitOrganizerImporterPropertyHandler* handler, mPluginPropertyHandlers) {
 
195
        handler->propertyProcessed(document, property, *item, &success, &updatedDetails);
 
196
    }
 
197
    // run the handler, if set
 
198
    if (mPropertyHandler) {
 
199
        mPropertyHandler->propertyProcessed(document, property, *item, &success, &updatedDetails);
 
200
    }
 
201
 
 
202
    foreach (QOrganizerItemDetail detail, updatedDetails) {
 
203
        item->saveDetail(&detail);
 
204
    }
 
205
}
 
206
 
 
207
bool QVersitOrganizerImporterPrivate::createSimpleDetail(
 
208
        const QVersitProperty& property,
 
209
        QOrganizerItem* item,
 
210
        QList<QOrganizerItemDetail>* updatedDetails)
 
211
{
 
212
    if (property.value().isEmpty())
 
213
        return false;
 
214
    QPair<QOrganizerItemDetail::DetailType, int> mapping = mPropertyMappings[property.name()];
 
215
    QOrganizerItemDetail::DetailType definitionName = mapping.first;
 
216
    int fieldName = mapping.second;
 
217
    QOrganizerItemDetail detail(item->detail(definitionName));
 
218
    if (detail.isEmpty())
 
219
        detail = QOrganizerItemDetail(definitionName);
 
220
    detail.setValue(fieldName, property.value());
 
221
    updatedDetails->append(detail);
 
222
    return true;
 
223
}
 
224
 
 
225
bool QVersitOrganizerImporterPrivate::createTimestampCreated(
 
226
        const QVersitProperty& property,
 
227
        QOrganizerItem* item,
 
228
        QList<QOrganizerItemDetail>* updatedDetails) {
 
229
    if (property.value().isEmpty())
 
230
        return false;
 
231
    QDateTime datetime = parseDateTime(property);
 
232
    if (!datetime.isValid())
 
233
        return false;
 
234
    QOrganizerItemTimestamp timestamp(item->detail(QOrganizerItemDetail::TypeTimestamp));
 
235
    timestamp.setCreated(datetime);
 
236
    updatedDetails->append(timestamp);
 
237
    return true;
 
238
}
 
239
 
 
240
bool QVersitOrganizerImporterPrivate::createTimestampModified(
 
241
        const QVersitProperty& property,
 
242
        QOrganizerItem* item,
 
243
        QList<QOrganizerItemDetail>* updatedDetails) {
 
244
    if (property.value().isEmpty())
 
245
        return false;
 
246
    QDateTime datetime = parseDateTime(property);
 
247
    if (!datetime.isValid())
 
248
        return false;
 
249
    QOrganizerItemTimestamp timestamp(item->detail(QOrganizerItemDetail::TypeTimestamp));
 
250
    timestamp.setLastModified(datetime);
 
251
    updatedDetails->append(timestamp);
 
252
    return true;
 
253
}
 
254
 
 
255
/*!
 
256
 * Takes the first value in \a list, or an empty QString is if the list is empty.
 
257
 */
 
258
QString QVersitOrganizerImporterPrivate::takeFirst(QList<QString>& list) const
 
259
{
 
260
    return list.empty() ? QString() : list.takeFirst();
 
261
}
 
262
 
 
263
bool QVersitOrganizerImporterPrivate::createVersion(
 
264
        const QVersitProperty& property,
 
265
        QOrganizerItem* item,
 
266
        QList<QOrganizerItemDetail>* updatedDetails) {
 
267
 
 
268
    QVariant variant = property.variantValue();
 
269
    if (property.valueType() != QVersitProperty::CompoundType
 
270
            || variant.type() != QVariant::StringList) {
 
271
        return false;
 
272
    }
 
273
    QStringList values = variant.toStringList();
 
274
 
 
275
    bool conversionOk;
 
276
    QOrganizerItemVersion version(item->detail(QOrganizerItemDetail::TypeVersion));
 
277
    version.setVersion(takeFirst(values).toInt(&conversionOk));
 
278
    QString extendedVersionString = takeFirst(values);
 
279
    if (!extendedVersionString.isEmpty())
 
280
        version.setExtendedVersion(extendedVersionString.toLocal8Bit());
 
281
 
 
282
    if (conversionOk) {
 
283
        updatedDetails->append(version);
 
284
        return true;
 
285
    } else {
 
286
        return false;
 
287
    }
 
288
}
 
289
 
 
290
bool QVersitOrganizerImporterPrivate::createPriority(
 
291
        const QVersitProperty& property,
 
292
        QOrganizerItem* item,
 
293
        QList<QOrganizerItemDetail>* updatedDetails) {
 
294
    if (property.value().length() != 1)
 
295
        return false;
 
296
 
 
297
    bool ok;
 
298
    int p = property.value().toInt(&ok);
 
299
    if (!ok)
 
300
        return false;
 
301
    QOrganizerItemPriority priority(item->detail(QOrganizerItemDetail::TypePriority));
 
302
    priority.setPriority(QOrganizerItemPriority::Priority(p));
 
303
    updatedDetails->append(priority);
 
304
    return true;
 
305
}
 
306
 
 
307
bool QVersitOrganizerImporterPrivate::createComment(
 
308
        const QVersitProperty& property,
 
309
        QList<QOrganizerItemDetail>* updatedDetails) {
 
310
    if (property.value().isEmpty())
 
311
        return false;
 
312
    QOrganizerItemComment comment;
 
313
    comment.setComment(property.value());
 
314
    updatedDetails->append(comment);
 
315
    return true;
 
316
}
 
317
 
 
318
bool QVersitOrganizerImporterPrivate::createItemReminder(
 
319
        const QVersitDocument& valarmDocument,
 
320
        QOrganizerItem* item,
 
321
        QList<QOrganizerItemDetail>* updatedDetails)
 
322
{
 
323
    QOrganizerItemReminder itemReminder;
 
324
    int repetitionCount;
 
325
    int repetitionDelay;
 
326
    int secondsBeforeStart;
 
327
    bool alreadySetSecondsBeforeStart = false;
 
328
    const QList<QVersitProperty> valarmProperties = valarmDocument.properties();
 
329
    QString actionValue;
 
330
    QVariantList attachValues;
 
331
    QString descriptionValue;
 
332
    QString summaryValue;
 
333
    QStringList attendees;
 
334
 
 
335
    foreach (const QVersitProperty &valarmProperty, valarmProperties) {
 
336
        if (valarmProperty.name() == QStringLiteral("TRIGGER")) {
 
337
            secondsBeforeStart = triggerToSecondsBeforeStart(valarmProperty, *item);
 
338
            alreadySetSecondsBeforeStart = true;
 
339
        } else if (valarmProperty.name() == QStringLiteral("REPEAT")) {
 
340
            repetitionCount = valarmProperty.value().toInt();
 
341
        } else if (valarmProperty.name() == QStringLiteral("DURATION")) {
 
342
            repetitionDelay = Duration::parseDuration(valarmProperty.value()).toSeconds();
 
343
        } else if (valarmProperty.name() == QStringLiteral("ACTION")) {
 
344
            actionValue = valarmProperty.value().toUpper();
 
345
        } else if (valarmProperty.name() == QStringLiteral("ATTACH")) {
 
346
            attachValues.append(valarmProperty.variantValue());
 
347
        } else if (valarmProperty.name() == QStringLiteral("DESCRIPTION")) {
 
348
            descriptionValue = valarmProperty.value();
 
349
        } else if (valarmProperty.name() == QStringLiteral("SUMMARY")) {
 
350
            summaryValue = valarmProperty.value();
 
351
        } else if (valarmProperty.name() == QStringLiteral("ATTENDEE")) {
 
352
            attendees << valarmProperty.value();
 
353
        }
 
354
    }
 
355
    if ((actionValue.isEmpty()) || (!alreadySetSecondsBeforeStart) ) {
 
356
        //ACTION and TRIGGER are mandatory in a VALARM component.
 
357
        return false;
 
358
    } else if (actionValue == QStringLiteral("AUDIO")) {
 
359
        QOrganizerItemAudibleReminder audibleReminder;
 
360
        audibleReminder.setRepetition(repetitionCount, repetitionDelay);
 
361
        audibleReminder.setSecondsBeforeStart(secondsBeforeStart);
 
362
        if (!attachValues.isEmpty())
 
363
            audibleReminder.setDataUrl(QUrl(attachValues.first().toString()));
 
364
        updatedDetails->append(audibleReminder);
 
365
        return true;
 
366
    } else if (actionValue == QStringLiteral("DISPLAY")) {
 
367
        if (descriptionValue.isNull())
 
368
            return false;//Invalid since REQUIRED properties are not found
 
369
        QOrganizerItemVisualReminder visualReminder;
 
370
        visualReminder.setRepetition(repetitionCount, repetitionDelay);
 
371
        visualReminder.setSecondsBeforeStart(secondsBeforeStart);
 
372
        if (!descriptionValue.isEmpty()) {
 
373
            visualReminder.setMessage(descriptionValue);
 
374
            updatedDetails->append(visualReminder);
 
375
            return true;
 
376
        } else {
 
377
            return false;
 
378
        }
 
379
    } else if (actionValue == QStringLiteral("EMAIL")) {
 
380
        if (descriptionValue.isNull() || summaryValue.isNull() || attendees.isEmpty())
 
381
            return false;//Invalid since REQUIRED properties are not found
 
382
        QOrganizerItemEmailReminder emailReminder;
 
383
        emailReminder.setRepetition(repetitionCount, repetitionDelay);
 
384
        emailReminder.setSecondsBeforeStart(secondsBeforeStart);
 
385
        emailReminder.setValue(QOrganizerItemEmailReminder::FieldBody, descriptionValue);
 
386
        emailReminder.setValue(QOrganizerItemEmailReminder::FieldSubject, summaryValue);
 
387
        if (!attachValues.isEmpty())
 
388
            emailReminder.setValue(QOrganizerItemEmailReminder::FieldAttachments, attachValues);
 
389
        emailReminder.setContents(summaryValue, descriptionValue, attachValues);
 
390
        emailReminder.setRecipients(attendees);
 
391
        updatedDetails->append(emailReminder);
 
392
        return true;
 
393
    } else { //ACTION property had an invalid value
 
394
        return false;
 
395
    }
 
396
}
 
397
 
 
398
int QVersitOrganizerImporterPrivate::triggerToSecondsBeforeStart(const QVersitProperty &triggerProperty, const QOrganizerItem &item)
 
399
{
 
400
    int result = 0;
 
401
 
 
402
    //The default value type for TRIGGER property is DURATION.
 
403
    bool encodedAsDuration = true;
 
404
 
 
405
    if (!triggerProperty.parameters().isEmpty()) {
 
406
        const QString triggerValue = triggerProperty.parameters().value(QStringLiteral("VALUE")).toUpper();
 
407
        if (triggerValue == QStringLiteral("DATE-TIME"))
 
408
            encodedAsDuration = false;
 
409
        else if ( (!triggerValue.isEmpty()) &&
 
410
                  (triggerValue != QStringLiteral("DURATION")) ) {
 
411
            return 0;//Invalid trigger property...just return default value.
 
412
        }
 
413
    }
 
414
 
 
415
    if (encodedAsDuration) {
 
416
        const QString related = triggerProperty.parameters().value(QStringLiteral("RELATED")).toUpper();
 
417
        result = Duration::parseDuration(triggerProperty.value()).toSeconds();
 
418
        switch (item.type()) {
 
419
        case QOrganizerItemType::TypeTodo:
 
420
        case QOrganizerItemType::TypeTodoOccurrence: {
 
421
            if (related == QStringLiteral("START")) {
 
422
                QOrganizerTodoTime todoTime = item.detail(QOrganizerItemDetail::TypeTodoTime);
 
423
                QDateTime relativeTrigger = todoTime.startDateTime().addSecs(result);
 
424
                result = relativeTrigger.secsTo(todoTime.dueDateTime());
 
425
            } else if ( (related.isEmpty()) || (related == QStringLiteral("END")) ) {
 
426
                result = (-1 * result);
 
427
            }
 
428
            break;
 
429
        }
 
430
        case QOrganizerItemType::TypeEvent:
 
431
        case QOrganizerItemType::TypeEventOccurrence: {
 
432
            if (related == QStringLiteral("END")) {
 
433
                QOrganizerEventTime eventTime = item.detail(QOrganizerItemDetail::TypeEventTime);
 
434
                QDateTime relativeTrigger = eventTime.endDateTime().addSecs(result);
 
435
                result = relativeTrigger.secsTo(eventTime.startDateTime());
 
436
            } else if ( (related.isEmpty()) || (related == QStringLiteral("START")) ) {
 
437
                result = (-1 * result);
 
438
            }
 
439
            break;
 
440
        }
 
441
        default:
 
442
            break;
 
443
        }
 
444
    } else {
 
445
        QDateTime absoluteTrigger = parseDateTime(triggerProperty.value());
 
446
        switch (item.type()) {
 
447
        case QOrganizerItemType::TypeTodo:
 
448
        case QOrganizerItemType::TypeTodoOccurrence: {
 
449
            QOrganizerTodoTime todoTime = item.detail(QOrganizerItemDetail::TypeTodoTime);
 
450
            result = absoluteTrigger.secsTo(todoTime.dueDateTime());
 
451
            break;
 
452
        }
 
453
        case QOrganizerItemType::TypeEvent:
 
454
        case QOrganizerItemType::TypeEventOccurrence: {
 
455
            QOrganizerEventTime eventTime = item.detail(QOrganizerItemDetail::TypeEventTime);
 
456
            result = absoluteTrigger.secsTo(eventTime.startDateTime());
 
457
            break;
 
458
        }
 
459
        default:
 
460
            break;
 
461
        }
 
462
    }
 
463
    return result >= 0 ? result: 0;
 
464
}
 
465
 
 
466
bool QVersitOrganizerImporterPrivate::createExtendedDetail(
 
467
        const QVersitProperty &property,
 
468
        QList<QOrganizerItemDetail> *updatedDetails) {
 
469
    QOrganizerItemExtendedDetail extendedDetail;
 
470
    const QVariant variant = property.variantValue();
 
471
    if (property.valueType() != QVersitProperty::CompoundType
 
472
            || variant.type() != QVariant::StringList)
 
473
        return false;
 
474
 
 
475
    QStringList values = variant.toStringList();
 
476
    extendedDetail.setName(takeFirst(values));
 
477
    QVariant data;
 
478
    if (VersitUtils::convertFromJson(takeFirst(values), &data))
 
479
        extendedDetail.setData(data);
 
480
    else
 
481
        return false;
 
482
 
 
483
    updatedDetails->append(extendedDetail);
 
484
    return true;
 
485
}
 
486
 
 
487
bool QVersitOrganizerImporterPrivate::createRecurrenceId(
 
488
        const QVersitProperty& property,
 
489
        QOrganizerItem* item,
 
490
        QList<QOrganizerItemDetail>* updatedDetails) {
 
491
    QDate date = parseDate(property.value());
 
492
    if (!date.isValid())
 
493
        return false;
 
494
    QOrganizerItemParent origin(item->detail(QOrganizerItemDetail::TypeParent));
 
495
    origin.setOriginalDate(date);
 
496
    updatedDetails->append(origin);
 
497
    item->setType(QOrganizerItemType::TypeEventOccurrence);
 
498
    return true;
 
499
}
 
500
 
 
501
/*! Set the startDateTime field of the EventTimeRange detail.  If the end date has been set from a
 
502
 * DURATION, it will be updated.
 
503
 */
 
504
bool QVersitOrganizerImporterPrivate::createStartDateTime(
 
505
        const QVersitProperty& property,
 
506
        QOrganizerItem* item,
 
507
        QList<QOrganizerItemDetail>* updatedDetails) {
 
508
    if (property.value().isEmpty())
 
509
        return false;
 
510
    bool hasTime;
 
511
    QDateTime newStart = parseDateTime(property, &hasTime);
 
512
    if (!newStart.isValid())
 
513
        return false;
 
514
    QOrganizerEventTime etr(item->detail(QOrganizerItemDetail::TypeEventTime));
 
515
    if (mDurationSpecified) {
 
516
        // Need to fix up the end date to match the duration of the event
 
517
        QDateTime start = etr.startDateTime();
 
518
        QDateTime end = etr.endDateTime();
 
519
        if (!start.isValid()) {
 
520
            // not having a start date set is treated as a start date of epoch
 
521
            start = QDateTime(QDate(1970, 1, 1));
 
522
        }
 
523
        // newEnd = end + (newStart - start)
 
524
        int durationDays = start.daysTo(newStart);
 
525
        QDateTime newEnd = end.addDays(durationDays);
 
526
        int durationSecs = start.addDays(durationDays).secsTo(newStart);
 
527
        newEnd = newEnd.addSecs(durationSecs);
 
528
        etr.setEndDateTime(newEnd);
 
529
    }
 
530
    etr.setStartDateTime(newStart);
 
531
    if (!etr.isAllDay() && !hasTime)
 
532
        etr.setAllDay(true);
 
533
    updatedDetails->append(etr);
 
534
    return true;
 
535
}
 
536
 
 
537
/*! Set the endDateTime field of the EventTimeRange detail.
 
538
 */
 
539
bool QVersitOrganizerImporterPrivate::createEndDateTime(
 
540
        const QVersitProperty& property,
 
541
        QOrganizerItem* item,
 
542
        QList<QOrganizerItemDetail>* updatedDetails) {
 
543
    if (property.value().isEmpty())
 
544
        return false;
 
545
    bool hasTime;
 
546
    QDateTime newEnd = parseDateTime(property, &hasTime);
 
547
    if (!newEnd.isValid())
 
548
        return false;
 
549
    QOrganizerEventTime etr(item->detail(QOrganizerItemDetail::TypeEventTime));
 
550
    if (!etr.isAllDay() && !hasTime)
 
551
        etr.setAllDay(true);
 
552
 
 
553
    // In iCalendar, the end date is exclusive while in Qt Organizer, it is inclusive.
 
554
    if (etr.isAllDay())
 
555
        etr.setEndDateTime(newEnd.addDays(-1));
 
556
    else
 
557
        etr.setEndDateTime(newEnd);
 
558
 
 
559
    updatedDetails->append(etr);
 
560
    mDurationSpecified = false;
 
561
    return true;
 
562
}
 
563
 
 
564
 
 
565
/*! Sets the endDateTime field of the EventTimeRange detail using a DURATION property.
 
566
 */
 
567
bool QVersitOrganizerImporterPrivate::createDuration(
 
568
        const QVersitProperty& property,
 
569
        QOrganizerItem* item,
 
570
        QList<QOrganizerItemDetail>* updatedDetails) {
 
571
    if (property.value().isEmpty())
 
572
        return false;
 
573
    Duration duration = Duration::parseDuration(property.value());
 
574
    if (!duration.isValid())
 
575
        return false;
 
576
    QOrganizerEventTime etr(item->detail(QOrganizerItemDetail::TypeEventTime));
 
577
    QDateTime startTime = etr.startDateTime();
 
578
    if (!startTime.isValid()) {
 
579
        // not having a start date set is treated as a start date of epoch
 
580
        startTime = QDateTime(QDate(1970, 1, 1));
 
581
    }
 
582
    etr.setEndDateTime(
 
583
            startTime.addDays(7*duration.weeks() + duration.days())
 
584
                     .addSecs(3600*duration.hours() + 60*duration.minutes() + duration.seconds()));
 
585
    updatedDetails->append(etr);
 
586
    mDurationSpecified = true;
 
587
    return true;
 
588
}
 
589
 
 
590
/*! Set the StartDateTime field of the TodoTimeRange detail.
 
591
 */
 
592
bool QVersitOrganizerImporterPrivate::createTodoStartDateTime(
 
593
        const QVersitProperty& property,
 
594
        QOrganizerItem* item,
 
595
        QList<QOrganizerItemDetail>* updatedDetails) {
 
596
    if (property.value().isEmpty())
 
597
        return false;
 
598
    bool hasTime;
 
599
    QDateTime newStart = parseDateTime(property, &hasTime);
 
600
    if (!newStart.isValid())
 
601
        return false;
 
602
    QOrganizerTodoTime ttr(item->detail(QOrganizerItemDetail::TypeTodoTime));
 
603
    ttr.setStartDateTime(newStart);
 
604
    if (!ttr.isAllDay() && !hasTime)
 
605
        ttr.setAllDay(true);
 
606
    updatedDetails->append(ttr);
 
607
    return true;
 
608
}
 
609
 
 
610
/*! Set the DueDateTime field of the TodoTimeRange detail.
 
611
 */
 
612
bool QVersitOrganizerImporterPrivate::createDueDateTime(
 
613
        const QVersitProperty& property,
 
614
        QOrganizerItem* item,
 
615
        QList<QOrganizerItemDetail>* updatedDetails) {
 
616
    if (property.value().isEmpty())
 
617
        return false;
 
618
    bool hasTime;
 
619
    QDateTime newEnd = parseDateTime(property, &hasTime);
 
620
    if (!newEnd.isValid())
 
621
        return false;
 
622
    QOrganizerTodoTime ttr(item->detail(QOrganizerItemDetail::TypeTodoTime));
 
623
    ttr.setDueDateTime(newEnd);
 
624
    if (!ttr.isAllDay() && !hasTime)
 
625
        ttr.setAllDay(true);
 
626
    updatedDetails->append(ttr);
 
627
    mDurationSpecified = false;
 
628
    return true;
 
629
}
 
630
 
 
631
/*! Set the EntryDateTime field of the JournalTimeRange detail.
 
632
 */
 
633
bool QVersitOrganizerImporterPrivate::createJournalEntryDateTime(
 
634
        const QVersitProperty& property,
 
635
        QOrganizerItem* item,
 
636
        QList<QOrganizerItemDetail>* updatedDetails) {
 
637
    if (property.value().isEmpty())
 
638
        return false;
 
639
    QDateTime dateTime = parseDateTime(property);
 
640
    if (!dateTime.isValid())
 
641
        return false;
 
642
    QOrganizerJournalTime jtr(item->detail(QOrganizerItemDetail::TypeJournalTime));
 
643
    jtr.setEntryDateTime(dateTime);
 
644
    updatedDetails->append(jtr);
 
645
    return true;
 
646
}
 
647
 
 
648
/*! Parses a datetime stored in the \a property as an ISO 8601 datetime in basic format, either in
 
649
 * UTC time zone, floating time zone, or (if a TZID parameter exists in \a property), as a foreign
 
650
 * time zone (returned as a UTC datetime).  Returns an invalid QDateTime if the string cannot be
 
651
 * parsed.
 
652
 *
 
653
 * \a hasTime is set to true if the parsed date-time has a time, or false if it is a date only.
 
654
 * (the time portion is set to some valid but arbitrary value).
 
655
 */
 
656
QDateTime QVersitOrganizerImporterPrivate::parseDateTime(const QVersitProperty& property,
 
657
                                                         bool* hasTime) const
 
658
{
 
659
    const QMultiHash<QString, QString> parameters = property.parameters();
 
660
    if (parameters.find(QStringLiteral("VALUE"), QStringLiteral("DATE")) == parameters.constEnd()) {
 
661
        // try parsing a datetime
 
662
        if (hasTime)
 
663
            *hasTime = true;
 
664
        QDateTime datetime(parseDateTime(property.value()));
 
665
        if (datetime.isValid() && datetime.timeSpec() == Qt::LocalTime) {
 
666
            QMultiHash<QString, QString> params = property.parameters();
 
667
            QString tzid = params.value(QStringLiteral("TZID"));
 
668
            if (!tzid.isEmpty()) {
 
669
                if (tzid.at(0) == QLatin1Char('/') && mTimeZoneHandler)
 
670
                    datetime = mTimeZoneHandler->convertTimeZoneToUtc(datetime, tzid);
 
671
                else
 
672
                    datetime = mTimeZones.convert(datetime, tzid);
 
673
            }
 
674
        }
 
675
        return datetime;
 
676
    } else {
 
677
        if (hasTime)
 
678
            *hasTime = false;
 
679
        QDateTime retn;
 
680
        retn.setDate(QDate::fromString(property.value(), QStringLiteral("yyyyMMdd")));
 
681
        retn.setTime(QTime(0, 0, 0));
 
682
        return retn;
 
683
    }
 
684
}
 
685
 
 
686
/*! Parses \a str as an ISO 8601 datetime in basic format, either in UTC timezone or floating
 
687
 * timezone.  Returns an invalid QDateTime if the string cannot be parsed.
 
688
 */
 
689
QDateTime QVersitOrganizerImporterPrivate::parseDateTime(QString str) const
 
690
{
 
691
    bool utc = str.endsWith(QLatin1Char('Z'), Qt::CaseInsensitive);
 
692
    if (utc)
 
693
        str.chop(1); // take away z from end;
 
694
    QDateTime dt(QDateTime::fromString(str, QStringLiteral("yyyyMMddTHHmmss")));
 
695
    if (utc)
 
696
        dt.setTimeSpec(Qt::UTC);
 
697
    return dt;
 
698
}
 
699
 
 
700
/*!
 
701
 * Imports a RRULE, EXRULE, RDATE or EXDATE property
 
702
 */
 
703
bool QVersitOrganizerImporterPrivate::createRecurrenceRule(
 
704
        const QVersitProperty& property,
 
705
        QOrganizerItem* item,
 
706
        QList<QOrganizerItemDetail>* updatedDetails) {
 
707
    if (property.value().isEmpty())
 
708
        return false;
 
709
    QOrganizerRecurrenceRule rule;
 
710
    if (!parseRecurRule(property.value(), &rule))
 
711
        return false;
 
712
    QOrganizerItemRecurrence detail(item->detail(QOrganizerItemDetail::TypeRecurrence));
 
713
    if (property.name() == QStringLiteral("RRULE")) {
 
714
        detail.setRecurrenceRules(detail.recurrenceRules() << rule);
 
715
    } else if (property.name() == QStringLiteral("EXRULE")) {
 
716
        detail.setExceptionRules(detail.exceptionRules() << rule);
 
717
    } 
 
718
    updatedDetails->append(detail);
 
719
    return true;
 
720
}
 
721
 
 
722
/*!
 
723
 * Parses an iCalendar recurrence rule string \a str and puts the result in \a rule.
 
724
 * Return true on success, false on failure.
 
725
 */
 
726
bool QVersitOrganizerImporterPrivate::parseRecurRule(const QString& str, QOrganizerRecurrenceRule* rule) const
 
727
{
 
728
    QStringList parts = str.split(QLatin1Char(';'));
 
729
    if (parts.size() == 0)
 
730
        return false;
 
731
    
 
732
    QString freqPart = parts.takeFirst();
 
733
    QStringList freqParts = freqPart.split(QLatin1Char('='));
 
734
    if (freqParts.size() != 2)
 
735
        return false;
 
736
    if (freqParts.at(0) != QStringLiteral("FREQ"))
 
737
        return false;
 
738
    QString freqValue = freqParts.at(1);
 
739
    if (freqValue == QStringLiteral("DAILY")) {
 
740
        rule->setFrequency(QOrganizerRecurrenceRule::Daily);
 
741
    } else if (freqValue == QStringLiteral("WEEKLY")) {
 
742
        rule->setFrequency(QOrganizerRecurrenceRule::Weekly);
 
743
    } else if (freqValue == QStringLiteral("MONTHLY")) {
 
744
        rule->setFrequency(QOrganizerRecurrenceRule::Monthly);
 
745
    } else if (freqValue == QStringLiteral("YEARLY")) {
 
746
        rule->setFrequency(QOrganizerRecurrenceRule::Yearly);
 
747
    } else {
 
748
        return false;
 
749
    }
 
750
 
 
751
    foreach (const QString& part, parts) {
 
752
        QStringList keyValue = part.split(QLatin1Char('='));
 
753
        if (keyValue.size() != 2)
 
754
            return false;
 
755
        parseRecurFragment(keyValue.at(0), keyValue.at(1), rule);
 
756
    }
 
757
    return true;
 
758
}
 
759
 
 
760
/*!
 
761
 * Parses a fragment of an iCalendar string (the part between the semicolons) and updates \a rule.
 
762
 * \a key is the part of the fragment before the equals sign and \a value is the part after.
 
763
 */
 
764
void QVersitOrganizerImporterPrivate::parseRecurFragment(const QString& key, const QString& value,
 
765
                                                         QOrganizerRecurrenceRule* rule) const
 
766
{
 
767
    if (key == QStringLiteral("INTERVAL")) {
 
768
        bool ok;
 
769
        int n = value.toInt(&ok);
 
770
        if (ok && n >= 1)
 
771
            rule->setInterval(n);
 
772
    } else if (key == QStringLiteral("COUNT")) {
 
773
        bool ok;
 
774
        int count = value.toInt(&ok);
 
775
        if (ok && count >= 0) {
 
776
            rule->setLimit(count);
 
777
        }
 
778
    } else if (key == QStringLiteral("UNTIL")) {
 
779
        QDate date;
 
780
        if (value.contains(QLatin1Char('T'))) {
 
781
            QDateTime dt = parseDateTime(value);
 
782
            date = dt.date();
 
783
        } else {
 
784
            date = QDate::fromString(value, QStringLiteral("yyyyMMdd"));
 
785
        }
 
786
        if (date.isValid())
 
787
            rule->setLimit(date);
 
788
    } else if (key == QStringLiteral("BYDAY")) {
 
789
        QSet<Qt::DayOfWeek> days;
 
790
        QStringList dayParts = value.split(QLatin1Char(','));
 
791
        foreach (QString dayStr, dayParts) {
 
792
            if (dayStr.length() < 2) {
 
793
                // bad day specifier
 
794
                continue;
 
795
            } else if (dayStr.length() > 2) {
 
796
                // parse something like -2SU, meaning the second-last Sunday
 
797
                QString posStr = dayStr;
 
798
                dayStr = dayStr.right(2); // dayStr = last two chars
 
799
                posStr.chop(2); // posStr = all except last two chars
 
800
                bool ok;
 
801
                int pos = posStr.toInt(&ok);
 
802
                if (!ok)
 
803
                    continue;
 
804
                rule->setPositions(QSet<int>() << pos);
 
805
            }
 
806
            int day = parseDayOfWeek(dayStr);
 
807
            if (day != -1) {
 
808
                days << (Qt::DayOfWeek)day;
 
809
            }
 
810
        }
 
811
        if (!days.isEmpty()) {
 
812
            rule->setDaysOfWeek(days);
 
813
        }
 
814
    } else if (key == QStringLiteral("BYMONTHDAY")) {
 
815
        QSet<int> days = parseInts(value, -31, 31);
 
816
        if (!days.isEmpty()) {
 
817
            rule->setDaysOfMonth(days);
 
818
        }
 
819
    } else if (key == QStringLiteral("BYWEEKNO")) {
 
820
        QSet<int> weeks = parseInts(value, -53, 53);
 
821
        if (!weeks.isEmpty()) {
 
822
            rule->setWeeksOfYear(weeks);
 
823
        }
 
824
    } else if (key == QStringLiteral("BYMONTH")) {
 
825
        QSet<QOrganizerRecurrenceRule::Month> months;
 
826
        QStringList monthParts = value.split(QLatin1Char(','));
 
827
        foreach (const QString& monthPart, monthParts) {
 
828
            bool ok;
 
829
            int month = monthPart.toInt(&ok);
 
830
            if (ok && month >= 1 && month <= 12) {
 
831
                months << (QOrganizerRecurrenceRule::Month)month;
 
832
            }
 
833
        }
 
834
        if (!months.isEmpty()) {
 
835
            rule->setMonthsOfYear(months);
 
836
        }
 
837
    } else if (key == QStringLiteral("BYYEARDAY")) {
 
838
        QSet<int> days = parseInts(value, -366, 366);
 
839
        if (!days.isEmpty()) {
 
840
            rule->setDaysOfYear(days);
 
841
        }
 
842
    } else if (key == QStringLiteral("BYSETPOS")) {
 
843
        QSet<int> poss = parseInts(value, -366, 366);
 
844
        if (!poss.isEmpty()) {
 
845
            rule->setPositions(poss);
 
846
        }
 
847
    } else if (key == QStringLiteral("WKST")) {
 
848
        int day = parseDayOfWeek(value);
 
849
        if (day != -1) {
 
850
            rule->setFirstDayOfWeek((Qt::DayOfWeek)day);
 
851
        }
 
852
    }
 
853
}
 
854
 
 
855
/*!
 
856
 * Parses and returns a comma-separated list of integers.  Only non-zero values between \a min and
 
857
 * \a max (inclusive) are added
 
858
 */
 
859
QSet<int> QVersitOrganizerImporterPrivate::parseInts(const QString& str, int min, int max) const
 
860
{
 
861
    QSet<int> values;
 
862
    QStringList parts = str.split(QLatin1Char(','));
 
863
    foreach (const QString& part, parts) {
 
864
        bool ok;
 
865
        int value = part.toInt(&ok);
 
866
        if (ok && value >= min && value <= max && value != 0) {
 
867
            values << value;
 
868
        }
 
869
    }
 
870
    return values;
 
871
}
 
872
 
 
873
/*!
 
874
 * Parses an iCalendar two-character string representing a day of week and returns an int
 
875
 * corresponding to Qt::DayOfWeek.  Returns -1 on parse failure.
 
876
 */
 
877
int QVersitOrganizerImporterPrivate::parseDayOfWeek(const QString& str) const
 
878
{
 
879
    if (str == QStringLiteral("MO")) {
 
880
        return Qt::Monday;
 
881
    } else if (str == QStringLiteral("TU")) {
 
882
        return Qt::Tuesday;
 
883
    } else if (str == QStringLiteral("WE")) {
 
884
        return Qt::Wednesday;
 
885
    } else if (str == QStringLiteral("TH")) {
 
886
        return Qt::Thursday;
 
887
    } else if (str == QStringLiteral("FR")) {
 
888
        return Qt::Friday;
 
889
    } else if (str == QStringLiteral("SA")) {
 
890
        return Qt::Saturday;
 
891
    } else if (str == QStringLiteral("SU")) {
 
892
        return Qt::Sunday;
 
893
    } else {
 
894
        return -1;
 
895
    }
 
896
}
 
897
 
 
898
/*!
 
899
 * Parses an iCalendar RDATE or EXDATE property and updates the recurrenceDates or
 
900
 * exceptionDates in the Recurrence detail.
 
901
 */
 
902
bool QVersitOrganizerImporterPrivate::createRecurrenceDates(
 
903
        const QVersitProperty& property,
 
904
        QOrganizerItem* item,
 
905
        QList<QOrganizerItemDetail>* updatedDetails)
 
906
{
 
907
    if (property.value().isEmpty())
 
908
        return false;
 
909
    QSet<QDate> dates;
 
910
    if (!parseDates(property.value(), &dates))
 
911
        return false;
 
912
    QOrganizerItemRecurrence detail(item->detail(QOrganizerItemDetail::TypeRecurrence));
 
913
    if (property.name() == QStringLiteral("RDATE")) {
 
914
        detail.setRecurrenceDates(detail.recurrenceDates() + dates);
 
915
    } else if (property.name() == QStringLiteral("EXDATE")) {
 
916
        detail.setExceptionDates(detail.exceptionDates() + dates);
 
917
    } 
 
918
    updatedDetails->append(detail);
 
919
    return true;
 
920
}
 
921
 
 
922
/*!
 
923
 * Parses a string like "19970304,19970504,19970704" into a list of QDates
 
924
 */
 
925
bool QVersitOrganizerImporterPrivate::parseDates(const QString& str, QSet<QDate>* dates) const
 
926
{
 
927
    QStringList parts = str.split(QLatin1Char(','));
 
928
    if (parts.size() == 0)
 
929
        return false;
 
930
 
 
931
    foreach (QString part, parts) {
 
932
        QDate date = parseDate(part);
 
933
        if (date.isValid())
 
934
            *dates << date;
 
935
        else
 
936
            return false;
 
937
    }
 
938
    return true;
 
939
}
 
940
 
 
941
/*!
 
942
 * Parses a date in either yyyyMMdd or yyyyMMddTHHmmss format (in the latter case, ignoring the
 
943
 * time)
 
944
 */
 
945
QDate QVersitOrganizerImporterPrivate::parseDate(QString str) const
 
946
{
 
947
    int tIndex = str.indexOf(QLatin1Char('T'));
 
948
    if (tIndex >= 0) {
 
949
        str = str.left(tIndex);
 
950
    }
 
951
    return QDate::fromString(str, QStringLiteral("yyyyMMdd"));
 
952
}
 
953
 
 
954
bool QVersitOrganizerImporterPrivate::createStatus(
 
955
        const QVersitProperty& property,
 
956
        QOrganizerItem* item,
 
957
        QList<QOrganizerItemDetail>* updatedDetails) {
 
958
    QOrganizerTodoProgress::Status status;
 
959
    if (property.value() == QStringLiteral("COMPLETED"))
 
960
        status = QOrganizerTodoProgress::StatusComplete;
 
961
    else if (property.value() == QStringLiteral("NEEDS-ACTION"))
 
962
        status = QOrganizerTodoProgress::StatusNotStarted;
 
963
    else if (property.value() == QStringLiteral("IN-PROCESS"))
 
964
        status = QOrganizerTodoProgress::StatusInProgress;
 
965
    else
 
966
        return false;
 
967
 
 
968
    QOrganizerTodoProgress progress(item->detail(QOrganizerItemDetail::TypeTodoProgress));
 
969
    progress.setStatus(status);
 
970
    updatedDetails->append(progress);
 
971
    return true;
 
972
}
 
973
 
 
974
bool QVersitOrganizerImporterPrivate::createPercentageComplete(
 
975
        const QVersitProperty& property,
 
976
        QOrganizerItem* item,
 
977
        QList<QOrganizerItemDetail>* updatedDetails) {
 
978
    bool ok = false;
 
979
    int percent = property.value().toInt(&ok);
 
980
    if (!ok)
 
981
        return false;
 
982
 
 
983
    QOrganizerTodoProgress progress(item->detail(QOrganizerItemDetail::TypeTodoProgress));
 
984
    progress.setPercentageComplete(percent);
 
985
    updatedDetails->append(progress);
 
986
    return true;
 
987
}
 
988
 
 
989
bool QVersitOrganizerImporterPrivate::createFinishedDateTime(
 
990
        const QVersitProperty& property,
 
991
        QOrganizerItem* item,
 
992
        QList<QOrganizerItemDetail>* updatedDetails) {
 
993
    if (property.value().isEmpty())
 
994
        return false;
 
995
    QDateTime datetime = parseDateTime(property);
 
996
    if (!datetime.isValid())
 
997
        return false;
 
998
    QOrganizerTodoProgress progress(item->detail(QOrganizerItemDetail::TypeTodoProgress));
 
999
    progress.setFinishedDateTime(datetime);
 
1000
    updatedDetails->append(progress);
 
1001
    return true;
 
1002
}
 
1003
 
 
1004
/*! Parse the iCalendar duration string \a str in an RDP fashion with a two symbol lookahead, and
 
1005
 * returns a Duration that represents it. */
 
1006
Duration Duration::parseDuration(QString str)
 
1007
{
 
1008
    QString token = nextToken(&str);
 
1009
    if (token.isEmpty())
 
1010
        return invalidDuration();
 
1011
 
 
1012
    Duration dur;
 
1013
    // Accept a + or - if present
 
1014
    if (token == QStringLiteral("+")) {
 
1015
        token = nextToken(&str);
 
1016
    } else if (token == QStringLiteral("-")) {
 
1017
        dur.setNegative(true);
 
1018
        token = nextToken(&str);
 
1019
    } else if (token.isEmpty()) {
 
1020
        return invalidDuration();
 
1021
    } else {
 
1022
        // There was no + or - so keep parsing
 
1023
    }
 
1024
 
 
1025
    // Accept a P
 
1026
    if (token != QStringLiteral("P")) {
 
1027
        return invalidDuration();
 
1028
    }
 
1029
 
 
1030
    token = nextToken(&str);
 
1031
    if (token.isEmpty()) {
 
1032
        return invalidDuration();
 
1033
    } else if (token == QStringLiteral("T")) {
 
1034
        // we see a time
 
1035
        parseDurationTime(&str, &dur);
 
1036
    } else if (token.at(0).isDigit()) {
 
1037
        // it's either a date or a week - we're not sure yet
 
1038
        int value = token.toInt(); // always succeeds because nextToken next returns a mix of digits/nondigits
 
1039
        token = nextToken(&str);
 
1040
        if (token == QStringLiteral("D")) {
 
1041
            // it's a date
 
1042
            dur.setDays(value);
 
1043
            token = nextToken(&str);
 
1044
            // dates optionally define a time
 
1045
            if (token == QStringLiteral("T"))
 
1046
                parseDurationTime(&str, &dur);
 
1047
        } else if (token == QStringLiteral("W")) {
 
1048
            dur.setWeeks(value);
 
1049
        } else {
 
1050
            return invalidDuration();
 
1051
        }
 
1052
    } else {
 
1053
        return invalidDuration();
 
1054
    }
 
1055
 
 
1056
    // check that there aren't extra characters on the end
 
1057
    if (!str.isEmpty())
 
1058
        dur.setValid(false);
 
1059
    return dur;
 
1060
 
 
1061
}
 
1062
 
 
1063
/*! Parse a duration string starting from after the "T" character.  Removes parsed part from \a str
 
1064
 * and updates \a dur with the findings.
 
1065
 */
 
1066
void Duration::parseDurationTime(QString* str, Duration* dur)
 
1067
{
 
1068
    QString token = nextToken(str);
 
1069
    if (token.isEmpty() || !token.at(0).isDigit())
 
1070
        dur->setValid(false);
 
1071
 
 
1072
    int value = token.toInt(); // always succeeds
 
1073
 
 
1074
    token = nextToken(str);
 
1075
    if (token == QStringLiteral("H")) {
 
1076
        dur->setHours(value);
 
1077
        if (!str->isEmpty())
 
1078
            parseDurationMinutes(str, dur);
 
1079
    } else if (token == QStringLiteral("M")) {
 
1080
        dur->setMinutes(value);
 
1081
        if (!str->isEmpty())
 
1082
            parseDurationSeconds(str, dur);
 
1083
    } else if (token == QStringLiteral("S")) {
 
1084
        dur->setSeconds(value);
 
1085
    }
 
1086
}
 
1087
 
 
1088
/*! Parse a duration string starting from the part describing the number of minutes.  Removes parsed
 
1089
 * part from \a str and updates \a dur with the findings.
 
1090
 */
 
1091
void Duration::parseDurationMinutes(QString* str, Duration* dur)
 
1092
{
 
1093
    QString token = nextToken(str);
 
1094
    if (token.isEmpty() || !token.at(0).isDigit())
 
1095
        dur->setValid(false);
 
1096
 
 
1097
    int value = token.toInt(); // always succeeds
 
1098
    token = nextToken(str);
 
1099
    if (token != QStringLiteral("M")) {
 
1100
        dur->setValid(false);
 
1101
        return;
 
1102
    }
 
1103
    dur->setMinutes(value);
 
1104
 
 
1105
    if (!str->isEmpty())
 
1106
        parseDurationSeconds(str, dur);
 
1107
}
 
1108
 
 
1109
/*! Parse a duration string starting from the part describing the number of seconds.  Removes parsed
 
1110
 * part from \a str and updates \a dur with the findings.
 
1111
 */
 
1112
void Duration::parseDurationSeconds(QString* str, Duration* dur)
 
1113
{
 
1114
    QString token = nextToken(str);
 
1115
    if (token.isEmpty() || !token.at(0).isDigit())
 
1116
        dur->setValid(false);
 
1117
 
 
1118
    int value = token.toInt(); // always succeeds
 
1119
    token = nextToken(str);
 
1120
    if (token != QStringLiteral("S")) {
 
1121
        dur->setValid(false);
 
1122
        return;
 
1123
    }
 
1124
    dur->setSeconds(value);
 
1125
}
 
1126
 
 
1127
/*! Removes and returns a "token" from the start of an iCalendar DURATION string, \a str.  A token
 
1128
 * is either a single +, - or upper-case letter, or a string of digits.  If \a str is empty, an
 
1129
 * empty string is returned.  If \a str is not empty but starts with an invalid character, a null
 
1130
 * string is returned.
 
1131
 */
 
1132
QString Duration::nextToken(QString* str)
 
1133
{
 
1134
    int len = str->length();
 
1135
    if (len == 0)
 
1136
        return QString::fromLatin1(""); // empty (not null) QString
 
1137
    QChar first = str->at(0);
 
1138
    if (first == QLatin1Char('+') || first == QLatin1Char('-') || first.isUpper()) {
 
1139
        QString ret(str->left(1));
 
1140
        *str = str->mid(1);
 
1141
        return ret;
 
1142
    } else if (first.isDigit()) {
 
1143
        // find the largest n such that the leftmost n characters are digits
 
1144
        int n = 1;
 
1145
        for (n = 1; n < len && str->at(n).isDigit(); n++) {
 
1146
        }
 
1147
        QString ret = str->left(n);
 
1148
        *str = str->mid(n);
 
1149
        return ret;
 
1150
    } else {
 
1151
        return QString(); // null QString
 
1152
    }
 
1153
}
 
1154
 
 
1155
TimeZone QVersitOrganizerImporterPrivate::importTimeZone(const QVersitDocument& document) const
 
1156
{
 
1157
    TimeZone timeZone;
 
1158
    foreach (const QVersitProperty& property, document.properties()) {
 
1159
        if (property.name() == QStringLiteral("TZID") && !property.value().isEmpty()) {
 
1160
            timeZone.setTzid(property.value());
 
1161
        }
 
1162
    }
 
1163
    foreach (const QVersitDocument& subDocument, document.subDocuments()) {
 
1164
        timeZone.addPhase(importTimeZonePhase(subDocument));
 
1165
    }
 
1166
    return timeZone;
 
1167
}
 
1168
 
 
1169
TimeZonePhase QVersitOrganizerImporterPrivate::importTimeZonePhase(const QVersitDocument& document) const
 
1170
{
 
1171
    TimeZonePhase phase;
 
1172
    phase.setStandard(document.componentType() == QStringLiteral("STANDARD"));
 
1173
 
 
1174
    foreach (const QVersitProperty& property, document.properties()) {
 
1175
        if (property.name() == QStringLiteral("TZOFFSETTO")) {
 
1176
            QString value(property.value());
 
1177
            if (value.size() == 5
 
1178
                    && (value.at(0) == QLatin1Char('+') || value.at(0) == QLatin1Char('-'))
 
1179
                    && value.at(1).isDigit()
 
1180
                    && value.at(2).isDigit()
 
1181
                    && value.at(3).isDigit()
 
1182
                    && value.at(4).isDigit()) {
 
1183
                phase.setUtcOffset((value.at(0) == QLatin1Char('+') ? 1 : -1) * ( // deal with sign
 
1184
                        value.mid(1, 2).toInt() * 3600 // hours part
 
1185
                        + value.mid(3, 2).toInt() * 60 // minutes part
 
1186
                        ));
 
1187
            }
 
1188
        } else if (property.name() == QStringLiteral("DTSTART")) {
 
1189
            QDateTime dt(parseDateTime(property.value()));
 
1190
            if (dt.isValid() && dt.timeSpec() == Qt::LocalTime) {
 
1191
                phase.setStartDateTime(dt);
 
1192
            }
 
1193
        } else if (property.name() == QStringLiteral("RRULE")) {
 
1194
            QOrganizerRecurrenceRule rrule;
 
1195
            if (parseRecurRule(property.value(), &rrule)) {
 
1196
                phase.setRecurrenceRule(rrule);
 
1197
            }
 
1198
        } else if (property.name() == QStringLiteral("RDATE")) {
 
1199
            QSet<QDate> dates;
 
1200
            if (parseDates(property.value(), &dates)) {
 
1201
                phase.setRecurrenceDates(dates);
 
1202
            }
 
1203
        }
 
1204
    }
 
1205
    return phase;
 
1206
}
 
1207
 
 
1208
QT_END_NAMESPACE_VERSITORGANIZER