1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtOrganizer module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
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 The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/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 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34#include "qorganizeritemmemorybackend_p.h"
35
36#include <QtOrganizer/qorganizeritemrecurrence.h>
37#include <QtOrganizer/qorganizeritems.h>
38#include <QtOrganizer/qorganizeritemdetails.h>
39#include <QtOrganizer/qorganizeritemfilters.h>
40#include <QtOrganizer/qorganizeritemrequests.h>
41
42#ifndef QT_NO_DEBUG_STREAM
43#include <QtCore/qdebug.h>
44#endif
45#include <QtCore/qstringbuilder.h>
46#include <QtCore/quuid.h>
47
48QT_BEGIN_NAMESPACE_ORGANIZER
49
50QOrganizerManagerEngine* QOrganizerItemMemoryFactory::engine(const QMap<QString, QString>& parameters, QOrganizerManager::Error* error)
51{
52 Q_UNUSED(error);
53
54 QOrganizerItemMemoryEngine *ret = QOrganizerItemMemoryEngine::createMemoryEngine(parameters);
55 return ret;
56}
57
58QString QOrganizerItemMemoryFactory::managerName() const
59{
60 return QString::fromLatin1(str: "memory");
61}
62
63/*!
64 \class QOrganizerItemMemoryEngine
65 \brief The QOrganizerItemMemoryEngine class provides an in-memory implementation
66 of an organizer item backend.
67 \inmodule QtOrganizer
68 \internal
69
70 It may be used as a reference implementation, or when persistent storage is not required.
71
72 During construction, it will load the in-memory data associated with the memory store
73 identified by the "id" parameter from the given parameters if it exists, or a new,
74 anonymous store if it does not.
75
76 Data stored in this engine is only available in the current process.
77
78 This engine supports sharing, so an internal reference count is increased
79 whenever a manager uses this backend, and is decreased when the manager
80 no longer requires this engine.
81 */
82
83typedef QHash<QString, QOrganizerItemMemoryEngineData *> EngineDatas;
84Q_GLOBAL_STATIC(EngineDatas, theEngineDatas);
85
86/*! Constructor of a QOrganizerItemMemoryEngineData object
87*/
88QOrganizerItemMemoryEngineData::QOrganizerItemMemoryEngineData()
89 : QSharedData(),
90 m_nextOrganizerItemId(1),
91 m_nextOrganizerCollectionId(2)
92{
93
94}
95
96/*!
97 * Factory function for creating a new in-memory backend, based
98 * on the given \a parameters.
99 *
100 * The same engine will be returned for multiple calls with the
101 * same value for the "id" parameter, while one of them is in scope.
102 */
103QOrganizerItemMemoryEngine* QOrganizerItemMemoryEngine::createMemoryEngine(const QMap<QString, QString>& parameters)
104{
105 QString idValue = parameters.value(QStringLiteral("id"));
106
107 EngineDatas &engineDatas = *theEngineDatas();
108 QOrganizerItemMemoryEngineData* data = engineDatas.value(akey: idValue);
109 if (!data) {
110 data = new QOrganizerItemMemoryEngineData();
111 // no store given? new, anonymous store.
112 if (!idValue.isEmpty()) {
113 data->m_id = idValue;
114 engineDatas.insert(akey: idValue, avalue: data);
115 }
116 }
117 data->ref.ref();
118 return new QOrganizerItemMemoryEngine(data);
119}
120
121/*!
122 * Constructs a new in-memory backend which shares the given \a data with
123 * other shared memory engines.
124 */
125QOrganizerItemMemoryEngine::QOrganizerItemMemoryEngine(QOrganizerItemMemoryEngineData* data)
126 : d(data)
127{
128 d->m_sharedEngines.append(t: this);
129
130 // the default collection always exists.
131 if (d->m_idToCollectionHash.isEmpty()) {
132 d->m_managerUri = managerUri();
133 const QOrganizerCollectionId defaultId = defaultCollectionId();
134 QOrganizerCollection defaultCollection;
135 defaultCollection.setId(defaultId);
136 defaultCollection.setMetaData(key: QOrganizerCollection::KeyName, value: QString(QStringLiteral("Default Collection")));
137 d->m_idToCollectionHash.insert(akey: defaultId, avalue: defaultCollection);
138 }
139}
140
141/*! Frees any memory used by this engine
142*/
143QOrganizerItemMemoryEngine::~QOrganizerItemMemoryEngine()
144{
145 d->m_sharedEngines.removeAll(t: this);
146 if (!d->ref.deref()) {
147 if (!d->m_id.isEmpty()) {
148 EngineDatas &engineDatas = *theEngineDatas();
149 engineDatas.remove(akey: d->m_id);
150 }
151 delete d;
152 }
153}
154
155/*! \reimp
156*/
157QString QOrganizerItemMemoryEngine::managerName() const
158{
159 return QStringLiteral("memory");
160}
161
162/*! \reimp
163*/
164QMap<QString, QString> QOrganizerItemMemoryEngine::managerParameters() const
165{
166 QMap<QString, QString> params;
167 params.insert(QStringLiteral("id"), avalue: d->m_id);
168 return params;
169}
170
171/*! \reimp
172*/
173QMap<QString, QString> QOrganizerItemMemoryEngine::idInterpretationParameters() const
174{
175 return managerParameters();
176}
177
178QList<QOrganizerItem> QOrganizerItemMemoryEngine::items(const QList<QOrganizerItemId> &itemIds, const QOrganizerItemFetchHint &fetchHint,
179 QMap<int, QOrganizerManager::Error> *errorMap, QOrganizerManager::Error *error)
180{
181 Q_UNUSED(fetchHint)
182
183 QList<QOrganizerItem> items;
184 items.reserve(alloc: itemIds.size());
185 QOrganizerItem tmp;
186 for (int i = 0; i < itemIds.size(); ++i) {
187 tmp = item(organizeritemId: itemIds.at(i));
188 items.append(t: tmp);
189 if (tmp.isEmpty())
190 errorMap->insert(akey: i, avalue: QOrganizerManager::DoesNotExistError);
191 }
192 *error = errorMap->isEmpty() ? QOrganizerManager::NoError : QOrganizerManager::DoesNotExistError;
193 return items;
194}
195
196QList<QOrganizerItemId> QOrganizerItemMemoryEngine::itemIds(const QOrganizerItemFilter &filter,
197 const QDateTime &startDateTime,
198 const QDateTime &endDateTime,
199 const QList<QOrganizerItemSortOrder> &sortOrders,
200 QOrganizerManager::Error *error)
201{
202 if (startDateTime.isNull() && endDateTime.isNull() && filter.type() == QOrganizerItemFilter::DefaultFilter && sortOrders.count() == 0)
203 return d->m_idToItemHash.keys();
204 else
205 return QOrganizerManager::extractIds(items: itemsForExport(startDateTime, endDateTime, filter, sortOrders, fetchHint: QOrganizerItemFetchHint(), error));
206}
207
208QList<QOrganizerItem> QOrganizerItemMemoryEngine::internalItemOccurrences(const QOrganizerItem& parentItem, const QDateTime& periodStart, const QDateTime& periodEnd, int maxCount, bool includeExceptions, bool sortItems, QList<QDate> *exceptionDates, QOrganizerManager::Error* error) const
209{
210 // given the generating item, grab it's QOrganizerItemRecurrence detail (if it exists), and calculate all of the dates within the given period.
211 // how would a real backend do this?
212 // Also, should this also return the exception instances (ie, return any persistent instances with parent information == parent item?)
213 // XXX TODO: in detail validation, ensure that the referenced parent Id exists...
214
215 QDateTime realPeriodStart(periodStart);
216 QDateTime realPeriodEnd(periodEnd);
217 QDateTime initialDateTime;
218 if (parentItem.type() == QOrganizerItemType::TypeEvent) {
219 QOrganizerEvent evt = parentItem;
220 initialDateTime = evt.startDateTime().isValid() ? evt.startDateTime() : evt.endDateTime();
221 } else if (parentItem.type() == QOrganizerItemType::TypeTodo) {
222 QOrganizerTodo todo = parentItem;
223 initialDateTime = todo.startDateTime().isValid() ? todo.startDateTime() : todo.dueDateTime();
224 } else {
225 // erm... not a recurring item in our schema...
226 return QList<QOrganizerItem>();
227 }
228
229 if (realPeriodStart.isValid() && initialDateTime.isValid()) {
230 if (initialDateTime > realPeriodStart)
231 realPeriodStart = initialDateTime;
232 } else if (initialDateTime.isValid()) {
233 realPeriodStart = initialDateTime;
234 }
235
236 if (!periodEnd.isValid()) {
237 // If no endDateTime is given, we'll only generate items that occur within the next 4 years of realPeriodStart.
238 realPeriodEnd.setDate(realPeriodStart.date().addDays(days: 1461));
239 realPeriodEnd.setTime(realPeriodStart.time());
240 }
241 if (realPeriodStart > realPeriodEnd) {
242 *error = QOrganizerManager::BadArgumentError;
243 return QList<QOrganizerItem>();
244 }
245
246 QList<QOrganizerItem> retn;
247 QList<QOrganizerItem> xoccurrences;
248 QOrganizerItemRecurrence recur = parentItem.detail(detailType: QOrganizerItemDetail::TypeRecurrence);
249
250 if (includeExceptions) {
251 // first, retrieve all persisted instances (exceptions) which occur between the specified datetimes.
252 foreach (const QOrganizerItem& item, d->m_idToItemHash) {
253 if (item.detail(detailType: QOrganizerItemDetail::TypeParent).value<QOrganizerItemId>(field: QOrganizerItemParent::FieldParentId) == parentItem.id()) {
254 QDateTime lowerBound;
255 QDateTime upperBound;
256 if (item.type() == QOrganizerItemType::TypeEventOccurrence) {
257 QOrganizerEventOccurrence instance = item;
258 lowerBound = instance.startDateTime();
259 upperBound = instance.endDateTime();
260 } else {
261 QOrganizerTodoOccurrence instance = item;
262 lowerBound = instance.startDateTime();
263 upperBound = instance.dueDateTime();
264 }
265
266 if ((lowerBound.isNull() || lowerBound >= realPeriodStart) && (upperBound.isNull() || upperBound <= realPeriodEnd)) {
267 // this occurrence fulfils the criteria.
268 xoccurrences.append(t: item);
269 }
270 }
271 }
272 }
273
274 // then, generate the required (unchanged) instances from the parentItem.
275 // before doing that, we have to find out all of the exception dates.
276 QList<QDate> xdates;
277 foreach (const QDate& xdate, recur.exceptionDates()) {
278 xdates += xdate;
279 }
280 if (realPeriodStart.isValid()) {
281 // Dates are interpreted as local time, but realPeriodStart is UTC
282 const QDate localStartDate(realPeriodStart.toLocalTime().date());
283 QSet<QOrganizerRecurrenceRule> xrules = recur.exceptionRules();
284 foreach (const QOrganizerRecurrenceRule& xrule, xrules) {
285 if (xrule.frequency() != QOrganizerRecurrenceRule::Invalid
286 && ((xrule.limitType() != QOrganizerRecurrenceRule::DateLimit) || (xrule.limitDate() >= localStartDate))) {
287 // we cannot skip it, since it applies in the given time period.
288 QList<QDateTime> xdatetimes = generateDateTimes(initialDateTime, rrule: xrule, periodStart: realPeriodStart, periodEnd: realPeriodEnd, maxCount: 50); // max count of 50 is arbitrary...
289 foreach (const QDateTime& xdatetime, xdatetimes)
290 xdates += xdatetime.toLocalTime().date();
291 }
292 }
293 }
294 // now generate a list of rdates (from the recurrenceDates and recurrenceRules)
295
296 // QMap is used for storing dates, because we don't want to have duplicate dates and
297 // we want to have dates sorted
298 // Only key of the map is relevant (QDateTime), the value (int) is not used
299 QMap<QDateTime, int> rdateMap;
300 foreach (const QDate& rdate, recur.recurrenceDates()) {
301 QDateTime dt(initialDateTime.toLocalTime());
302 dt.setDate(rdate);
303 rdateMap.insert(akey: dt.toUTC(), avalue: 0);
304 }
305
306 if (realPeriodStart.isValid()) {
307 const QDate localStartDate(realPeriodStart.toLocalTime().date());
308 QSet<QOrganizerRecurrenceRule> rrules = recur.recurrenceRules();
309 foreach (const QOrganizerRecurrenceRule& rrule, rrules) {
310 if (rrule.frequency() != QOrganizerRecurrenceRule::Invalid
311 && ((rrule.limitType() != QOrganizerRecurrenceRule::DateLimit) || (rrule.limitDate() >= localStartDate))) {
312 // we cannot skip it, since it applies in the given time period.
313 QList<QDateTime> rdatetimes = generateDateTimes(initialDateTime, rrule, periodStart: realPeriodStart, periodEnd: realPeriodEnd, maxCount: 50); // max count of 50 is arbitrary...
314 foreach (const QDateTime& rdatetime, rdatetimes)
315 rdateMap.insert(akey: rdatetime, avalue: 0);
316 }
317 }
318 }
319 // now order the contents of retn by date
320 QList<QDateTime> rdates = rdateMap.keys();
321
322 if (initialDateTime.isValid() && !recur.recurrenceDates().isEmpty() && qBinaryFind(container: rdates, value: initialDateTime) == rdates.constEnd()) {
323 rdates.prepend(t: initialDateTime);
324 }
325
326 // now for each rdate which isn't also an xdate
327 foreach (const QDateTime& rdate, rdates) {
328 if (rdate >= realPeriodStart && rdate <= realPeriodEnd) {
329 const QDate localRDate(rdate.toLocalTime().date());
330 if (!xdates.contains(t: localRDate)) {
331 // generate the required instance and add it to the return list.
332 retn.append(t: QOrganizerManagerEngine::generateOccurrence(parentItem, rdate));
333 } else if (includeExceptions) {
334 for (int i = 0; i < xoccurrences.size(); i++) {
335 QOrganizerItemParent parentDetail = xoccurrences[i].detail(detailType: QOrganizerItemDetail::TypeParent);
336 if (parentDetail.originalDate() == localRDate)
337 retn.append(t: xoccurrences[i]);
338 }
339 } else if (exceptionDates) {
340 exceptionDates->append(t: localRDate);
341 }
342 }
343 }
344
345 if (sortItems) {
346 // should we always sort if a maxCount is given?
347 QMultiMap<QDateTime, QOrganizerItem> defaultSorted;
348 foreach (QOrganizerItem item, retn)
349 QOrganizerManagerEngine::addDefaultSorted(defaultSorted: &defaultSorted, toAdd: item);
350
351 retn = defaultSorted.values();
352 }
353
354 // and return the first maxCount entries.
355 return retn.mid(pos: 0, alength: maxCount);
356}
357
358QList<QOrganizerItem> QOrganizerItemMemoryEngine::itemOccurrences(const QOrganizerItem &parentItem,
359 const QDateTime &startDateTime,
360 const QDateTime &endDateTime, int maxCount,
361 const QOrganizerItemFetchHint &fetchHint,
362 QOrganizerManager::Error *error)
363{
364 Q_UNUSED(fetchHint);
365 return internalItemOccurrences(parentItem, periodStart: startDateTime, periodEnd: endDateTime, maxCount, includeExceptions: true, sortItems: true, exceptionDates: 0, error);
366}
367
368QList<QOrganizerItem> QOrganizerItemMemoryEngine::items(const QOrganizerItemFilter &filter, const QDateTime &startDateTime,
369 const QDateTime &endDateTime, int maxCount,
370 const QList<QOrganizerItemSortOrder> &sortOrders,
371 const QOrganizerItemFetchHint &fetchHint, QOrganizerManager::Error *error)
372{
373 QList<QOrganizerItem> list;
374 if (sortOrders.size() > 0) {
375 list = internalItems(startDate: startDateTime, endDate: endDateTime, filter, sortOrders, fetchHint, error, forExport: false);
376 } else {
377 QOrganizerItemSortOrder sortOrder;
378 sortOrder.setDetail(detailType: QOrganizerItemDetail::TypeEventTime, field: QOrganizerEventTime::FieldStartDateTime);
379 sortOrder.setDirection(Qt::AscendingOrder);
380
381 QList<QOrganizerItemSortOrder> sortOrders;
382 sortOrders.append(t: sortOrder);
383
384 sortOrder.setDetail(detailType: QOrganizerItemDetail::TypeTodoTime, field: QOrganizerTodoTime::FieldStartDateTime);
385 sortOrders.append(t: sortOrder);
386
387 sortOrder.setDetail(detailType: QOrganizerItemDetail::TypeTodoTime, field: QOrganizerTodoTime::FieldStartDateTime);
388 sortOrders.append(t: sortOrder);
389
390 list = internalItems(startDate: startDateTime, endDate: endDateTime, filter, sortOrders, fetchHint, error, forExport: false);
391 }
392
393 if (maxCount < 0)
394 return list;
395 else
396 return list.mid(pos: 0, alength: maxCount);
397}
398
399QList<QOrganizerItem> QOrganizerItemMemoryEngine::itemsForExport(const QDateTime &startDateTime,
400 const QDateTime &endDateTime,
401 const QOrganizerItemFilter &filter,
402 const QList<QOrganizerItemSortOrder> &sortOrders,
403 const QOrganizerItemFetchHint &fetchHint,
404 QOrganizerManager::Error *error)
405{
406 return internalItems(startDate: startDateTime, endDate: endDateTime, filter, sortOrders, fetchHint, error, forExport: true);
407}
408
409QList<QOrganizerItem> QOrganizerItemMemoryEngine::itemsForExport(const QList<QOrganizerItemId> &ids, const QOrganizerItemFetchHint &fetchHint, QMap<int, QOrganizerManager::Error> *errorMap, QOrganizerManager::Error *error)
410{
411 QOrganizerItemIdFilter filter;
412 filter.setIds(ids);
413
414 QList<QOrganizerItem> unsorted = itemsForExport(startDateTime: QDateTime(), endDateTime: QDateTime(), filter, sortOrders: QOrganizerItemSortOrder(), fetchHint, error);
415
416 // Build an index into the results
417 QHash<QOrganizerItemId, int> idMap; // value is index into unsorted
418 if (*error == QOrganizerManager::NoError) {
419 for (int i = 0; i < unsorted.size(); i++) {
420 idMap.insert(akey: unsorted[i].id(), avalue: i);
421 }
422 }
423
424 // Build up the results and errors
425 QList<QOrganizerItem> results;
426 for (int i = 0; i < ids.count(); i++) {
427 QOrganizerItemId id(ids[i]);
428 if (!idMap.contains(akey: id)) {
429 if (errorMap)
430 errorMap->insert(akey: i, avalue: QOrganizerManager::DoesNotExistError);
431 if (*error == QOrganizerManager::NoError)
432 *error = QOrganizerManager::DoesNotExistError;
433 results.append(t: QOrganizerItem());
434 } else {
435 results.append(t: unsorted[idMap[id]]);
436 }
437 }
438
439 return results;
440}
441
442QOrganizerItem QOrganizerItemMemoryEngine::item(const QOrganizerItemId& organizeritemId) const
443{
444 return d->m_idToItemHash.value(akey: organizeritemId);
445}
446
447QList<QOrganizerItem> QOrganizerItemMemoryEngine::internalItems(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error, bool forExport) const
448{
449 Q_UNUSED(fetchHint); // no optimisations are possible in the memory backend; ignore the fetch hint.
450 Q_UNUSED(error);
451
452 QList<QOrganizerItem> sorted;
453 QSet<QOrganizerItemId> parentsAdded;
454 bool isDefFilter = (filter.type() == QOrganizerItemFilter::DefaultFilter);
455
456 foreach(const QOrganizerItem& c, d->m_idToItemHash) {
457 if (itemHasReccurence(oi: c)) {
458 addItemRecurrences(sorted, c, startDate, endDate, filter, sortOrders, forExport, parentsAdded: &parentsAdded);
459 } else {
460 if ((isDefFilter || QOrganizerManagerEngine::testFilter(filter, item: c)) && QOrganizerManagerEngine::isItemBetweenDates(item: c, startPeriod: startDate, endPeriod: endDate)) {
461 QOrganizerManagerEngine::addSorted(sorted: &sorted,toAdd: c, sortOrders);
462 if (forExport
463 && (c.type() == QOrganizerItemType::TypeEventOccurrence
464 || c.type() == QOrganizerItemType::TypeTodoOccurrence)) {
465 QOrganizerItemId parentId(c.detail(detailType: QOrganizerItemDetail::TypeParent).value<QOrganizerItemId>(field: QOrganizerItemParent::FieldParentId));
466 if (!parentsAdded.contains(value: parentId)) {
467 parentsAdded.insert(value: parentId);
468 QOrganizerManagerEngine::addSorted(sorted: &sorted, toAdd: item(organizeritemId: parentId), sortOrders);
469 }
470 }
471 }
472 }
473 }
474
475 return sorted;
476}
477
478
479void QOrganizerItemMemoryEngine::addItemRecurrences(QList<QOrganizerItem>& sorted, const QOrganizerItem& c, const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, bool forExport, QSet<QOrganizerItemId>* parentsAdded) const
480{
481 QOrganizerManager::Error error = QOrganizerManager::NoError;
482 if (forExport && parentsAdded->contains(value: c.id()))
483 return;
484
485 QList<QOrganizerItem> recItems = internalItemOccurrences(parentItem: c, periodStart: startDate, periodEnd: endDate, maxCount: forExport ? 1 : 50, includeExceptions: false, sortItems: false, exceptionDates: 0, error: &error); // XXX TODO: why maxcount of 50?
486 if (filter.type() == QOrganizerItemFilter::DefaultFilter) {
487 foreach(const QOrganizerItem& oi, recItems) {
488 QOrganizerManagerEngine::addSorted(sorted: &sorted, toAdd: forExport ? c : oi, sortOrders);
489 if (forExport)
490 parentsAdded->insert(value: c.id());
491 }
492 } else {
493 foreach(const QOrganizerItem& oi, recItems) {
494 if (QOrganizerManagerEngine::testFilter(filter, item: oi)) {
495 QOrganizerManagerEngine::addSorted(sorted: &sorted, toAdd: forExport ? c : oi, sortOrders);
496 if (forExport)
497 parentsAdded->insert(value: c.id());
498 }
499 }
500 }
501}
502
503/*! Saves the given organizeritem \a theOrganizerItem, storing any error to \a error and
504 filling the \a changeSet with ids of changed organizeritems as required */
505bool QOrganizerItemMemoryEngine::storeItem(QOrganizerItem* theOrganizerItem, QOrganizerItemChangeSet& changeSet, const QList<QOrganizerItemDetail::DetailType> &detailMask, QOrganizerManager::Error* error)
506{
507 QOrganizerCollectionId targetCollectionId = theOrganizerItem->collectionId();
508
509 // check that the collection exists (or is null :. default collection):
510 if (!targetCollectionId.isNull() && !d->m_idToCollectionHash.contains(akey: targetCollectionId)) {
511 *error = QOrganizerManager::InvalidCollectionError;
512 return false;
513 }
514
515 if (theOrganizerItem->type() == QOrganizerItemType::TypeUndefined) {
516 *error = QOrganizerManager::InvalidItemTypeError;
517 return false;
518 }
519
520 // check to see if this organizer item already exists
521 QOrganizerItemId theOrganizerItemId = theOrganizerItem->id();
522 QHash<QOrganizerItemId, QOrganizerItem>::const_iterator hashIterator = d->m_idToItemHash.find(akey: theOrganizerItemId);
523 if (hashIterator != d->m_idToItemHash.constEnd()) {
524 /* We also need to check that there are no modified create only details */
525 QOrganizerItem oldOrganizerItem = hashIterator.value();
526
527 if (oldOrganizerItem.type() != theOrganizerItem->type()) {
528 *error = QOrganizerManager::AlreadyExistsError;
529 return false;
530 }
531
532 // check that the old and new collection is the same (ie, not attempting to save to a different collection)
533 if (targetCollectionId.isNull()) {
534 // it already exists, so save it where it already exists.
535 targetCollectionId = d->m_itemsInCollectionsHash.key(avalue: theOrganizerItemId);
536 } else if (!d->m_itemsInCollectionsHash.values(akey: targetCollectionId).contains(t: theOrganizerItemId)) {
537 // the given collection id was non-null but doesn't already contain this item. error.
538 *error = QOrganizerManager::InvalidCollectionError;
539 return false;
540 }
541
542 QOrganizerItemTimestamp ts = theOrganizerItem->detail(detailType: QOrganizerItemDetail::TypeTimestamp);
543 ts.setLastModified(QDateTime::currentDateTime());
544 theOrganizerItem->saveDetail(detail: &ts);
545
546 if (!fixOccurrenceReferences(item: theOrganizerItem, error)) {
547 return false;
548 }
549 // Looks ok, so continue
550 d->m_idToItemHash.insert(akey: theOrganizerItemId, avalue: *theOrganizerItem); // replacement insert.
551 changeSet.insertChangedItem(itemId: theOrganizerItemId, typesChanged: detailMask);
552
553 // cross-check if stored exception occurrences are still valid
554 if (itemHasReccurence(oi: oldOrganizerItem)) {
555 // if we are updating an existing item and the item had recurrence defined, it might
556 // have some exception occurrences which don't match the current recurrence.
557
558 // should we also check and remove exception dates if e.g. limit date or limit count has been changed
559 // to be earlier (or smaller) than before thus invelidating an exception date..?
560 QList<QOrganizerItemId> occurrenceIds = d->m_parentIdToChildIdHash.values(akey: theOrganizerItemId);
561 QOrganizerManager::Error occurrenceError = QOrganizerManager::NoError;
562 if (!occurrenceIds.isEmpty()) {
563 if (itemHasReccurence(oi: *theOrganizerItem)) {
564 // generate occurrences to get the dates when there can be an exception occurrence
565 // if the new item does not have recurrence, all exception occurrences of this item
566 // are removed
567 QList<QDate> exceptionDates;
568 QList<QOrganizerItem> occurrences = internalItemOccurrences(parentItem: *theOrganizerItem, periodStart: QDateTime(), periodEnd: QDateTime(), maxCount: -1, includeExceptions: false, sortItems: false, exceptionDates: &exceptionDates, error: &occurrenceError);
569 foreach (const QOrganizerItemId &occurrenceId, occurrenceIds) {
570 // remove all occurrence ids from the list which have valid exception date
571 QOrganizerItemParent parentDetail = d->m_idToItemHash.value(akey: occurrenceId).detail(detailType: QOrganizerItemDetail::TypeParent);
572 if (!parentDetail.isEmpty() && exceptionDates.contains(t: parentDetail.originalDate()))
573 occurrenceIds.removeOne(t: occurrenceId);
574 }
575 }
576 foreach (const QOrganizerItemId &occurrenceId, occurrenceIds)
577 removeItem(organizeritemId: occurrenceId, changeSet, error: &occurrenceError);
578 }
579 }
580 } else {
581 // id does not exist; if not zero, fail.
582 if (!theOrganizerItemId.isNull()) {
583 // the ID is not empty, and it doesn't identify an existing organizer item in our database either.
584 *error = QOrganizerManager::DoesNotExistError;
585 return false;
586 }
587 /* New organizer item */
588 QOrganizerItemTimestamp ts = theOrganizerItem->detail(detailType: QOrganizerItemDetail::TypeTimestamp);
589 ts.setLastModified(QDateTime::currentDateTime());
590 ts.setCreated(ts.lastModified());
591 theOrganizerItem->saveDetail(detail: &ts);
592
593 if (!fixOccurrenceReferences(item: theOrganizerItem, error)) {
594 return false;
595 }
596 // set the guid if not set
597 if (theOrganizerItem->guid().isEmpty())
598 theOrganizerItem->setGuid(QUuid::createUuid().toString());
599
600 // if we're saving an exception occurrence, we need to add it's original date as an exdate to the parent.
601 QOrganizerItemId parentId;
602 if (theOrganizerItem->type() == QOrganizerItemType::TypeEventOccurrence
603 || theOrganizerItem->type() == QOrganizerItemType::TypeTodoOccurrence) {
604 // update the event or the todo by adding an EX-DATE which corresponds to the original date of the occurrence being saved.
605 QOrganizerItemParent origin = theOrganizerItem->detail(detailType: QOrganizerItemDetail::TypeParent);
606 parentId = origin.parentId();
607
608 // for occurrences, if given a null collection id, save it in the same collection as the parent.
609 // otherwise, ensure that the parent is in the same collection. You cannot save an exception to a different collection than the parent.
610 if (targetCollectionId.isNull()) {
611 targetCollectionId = d->m_itemsInCollectionsHash.key(avalue: parentId);
612 if (targetCollectionId.isNull()) {
613 *error = QOrganizerManager::UnspecifiedError; // this should never occur; parent should _always_ be in a collection.
614 return false;
615 }
616 } else if (!d->m_itemsInCollectionsHash.values(akey: targetCollectionId).contains(t: parentId)) {
617 // nope, the specified collection doesn't contain the parent. error.
618 *error = QOrganizerManager::InvalidCollectionError;
619 return false;
620 }
621
622 QMap<int, QOrganizerManager::Error> tempErrorMap;
623 QOrganizerManager::Error tempError = QOrganizerManager::NoError;
624 const QList<QOrganizerItem> candidates = items(itemIds: QList<QOrganizerItemId>() << parentId, fetchHint: QOrganizerItemFetchHint(), errorMap: &tempErrorMap, error: &tempError);
625 if (tempError != QOrganizerManager::NoError || candidates.isEmpty()) {
626 *error = tempError != QOrganizerManager::NoError ? tempError : QOrganizerManager::UnspecifiedError;
627 return false;
628 }
629 QOrganizerItem parentItem = candidates.first();
630 QDate originalDate = origin.originalDate();
631 QOrganizerItemRecurrence recurrence = parentItem.detail(detailType: QOrganizerItemDetail::TypeRecurrence);
632 QSet<QDate> currentExceptionDates = recurrence.exceptionDates();
633 if (!currentExceptionDates.contains(value: originalDate)) {
634 currentExceptionDates << originalDate;
635 recurrence.setExceptionDates(currentExceptionDates);
636 parentItem.saveDetail(detail: &recurrence);
637 d->m_idToItemHash.insert(akey: parentId, avalue: parentItem); // replacement insert
638 changeSet.insertChangedItem(itemId: parentId, typesChanged: detailMask); // is this correct? it's an exception, so change parent?
639 }
640 }
641
642 // if target collection id is null, set to default id.
643 if (targetCollectionId.isNull())
644 targetCollectionId = defaultCollectionId();
645
646 // update the organizer item - set its ID
647 theOrganizerItemId = itemId(localId: QByteArray(reinterpret_cast<const char *>(&d->m_nextOrganizerItemId), sizeof(quint32)));
648 ++(d->m_nextOrganizerItemId);
649 theOrganizerItem->setId(theOrganizerItemId);
650 // finally, add the organizer item to our internal lists and return
651 theOrganizerItem->setCollectionId(targetCollectionId);
652 d->m_idToItemHash.insert(akey: theOrganizerItemId, avalue: *theOrganizerItem); // add organizer item to hash
653 if (!parentId.isNull()) {
654 // if it was an occurrence, we need to add it to the children hash.
655 d->m_parentIdToChildIdHash.insert(akey: parentId, avalue: theOrganizerItemId);
656 }
657 d->m_itemsInCollectionsHash.insert(akey: targetCollectionId, avalue: theOrganizerItemId);
658 changeSet.insertAddedItem(itemId: theOrganizerItemId);
659 }
660
661 *error = QOrganizerManager::NoError; // successful.
662 return true;
663}
664
665/*!
666 * For Occurrence type items, ensure the ParentId and the Guid are set consistently. Returns
667 * false and sets \a error on error, returns true otherwise.
668 */
669bool QOrganizerItemMemoryEngine::fixOccurrenceReferences(QOrganizerItem* theItem, QOrganizerManager::Error* error)
670{
671 if (theItem->type() == QOrganizerItemType::TypeEventOccurrence
672 || theItem->type() == QOrganizerItemType::TypeTodoOccurrence) {
673 const QString guid = theItem->guid();
674 QOrganizerItemParent instanceOrigin = theItem->detail(detailType: QOrganizerItemDetail::TypeParent);
675 if (!instanceOrigin.originalDate().isValid()) {
676 *error = QOrganizerManager::InvalidOccurrenceError;
677 return false;
678 }
679 QOrganizerItemId parentId = instanceOrigin.parentId();
680 if (!guid.isEmpty()) {
681 if (!parentId.isNull()) {
682 QOrganizerManager::Error tempError;
683 QMap<int, QOrganizerManager::Error> tempErrorMap;
684 QOrganizerItem parentItem = items(itemIds: QList<QOrganizerItemId>() << parentId, fetchHint: QOrganizerItemFetchHint(), errorMap: &tempErrorMap, error: &tempError).at(i: 0);
685 if (guid != parentItem.guid()
686 || !typesAreRelated(occurrenceType: theItem->type(), parentType: parentItem.type())) {
687 // parentId and guid are both set and inconsistent, or the parent is the wrong
688 // type
689 *error = QOrganizerManager::InvalidOccurrenceError;
690 return false;
691 }
692 } else {
693 // guid set but not parentId
694 // find an item with the given guid
695 foreach (const QOrganizerItem& item, d->m_idToItemHash) {
696 if (item.guid() == guid) {
697 parentId = item.id();
698 break;
699 }
700 }
701 if (parentId.isNull()) {
702 // couldn't find an item with the given guid
703 *error = QOrganizerManager::InvalidOccurrenceError;
704 return false;
705 }
706 QOrganizerManager::Error tempError;
707 QMap<int, QOrganizerManager::Error> tempErrorMap;
708 QOrganizerItem parentItem = items(itemIds: QList<QOrganizerItemId>() << parentId, fetchHint: QOrganizerItemFetchHint(), errorMap: &tempErrorMap, error: &tempError).at(i: 0);
709 if (!typesAreRelated(occurrenceType: theItem->type(), parentType: parentItem.type())) {
710 // the parent is the wrong type
711 *error = QOrganizerManager::InvalidOccurrenceError;
712 return false;
713 }
714 // found a matching item - set the parentId of the occurrence
715 QOrganizerItemParent origin = theItem->detail(detailType: QOrganizerItemDetail::TypeParent);
716 origin.setParentId(parentId);
717 theItem->saveDetail(detail: &origin);
718 }
719 } else if (!parentId.isNull()) {
720 QOrganizerManager::Error tempError;
721 QMap<int, QOrganizerManager::Error> tempErrorMap;
722 QOrganizerItem parentItem = items(itemIds: QList<QOrganizerItemId>() << parentId, fetchHint: QOrganizerItemFetchHint(), errorMap: &tempErrorMap, error: &tempError).at(i: 0);
723 if (parentItem.guid().isEmpty()
724 || !typesAreRelated(occurrenceType: theItem->type(), parentType: parentItem.type())) {
725 // found the matching item but it has no guid, or it isn't the right type
726 *error = QOrganizerManager::InvalidOccurrenceError;
727 return false;
728 }
729 theItem->setGuid(parentItem.guid());
730 } else {
731 // neither parentId or guid is supplied
732 *error = QOrganizerManager::InvalidOccurrenceError;
733 return false;
734 }
735 }
736 return true;
737}
738
739/*!
740 * Returns true if and only if \a occurrenceType is the "Occurrence" version of \a parentType.
741 */
742bool QOrganizerItemMemoryEngine::typesAreRelated(QOrganizerItemType::ItemType occurrenceType, QOrganizerItemType::ItemType parentType)
743{
744 return ((parentType == QOrganizerItemType::TypeEvent
745 && occurrenceType == QOrganizerItemType::TypeEventOccurrence)
746 || (parentType == QOrganizerItemType::TypeTodo
747 && occurrenceType == QOrganizerItemType::TypeTodoOccurrence));
748}
749
750bool QOrganizerItemMemoryEngine::storeItems(QList<QOrganizerItem>* organizeritems, const QList<QOrganizerItemDetail::DetailType> &detailMask,
751 QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error)
752{
753 Q_ASSERT(errorMap);
754
755 errorMap->clear();
756
757 if (!organizeritems) {
758 *error = QOrganizerManager::BadArgumentError;
759 return false;
760 }
761
762 QOrganizerItemChangeSet changeSet;
763 QOrganizerItem current;
764 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
765 for (int i = 0; i < organizeritems->count(); i++) {
766 current = organizeritems->at(i);
767 if (!storeItem(theOrganizerItem: &current, changeSet, detailMask, error)) {
768 operationError = *error;
769 errorMap->insert(akey: i, avalue: operationError);
770 } else {
771 (*organizeritems)[i] = current;
772 }
773 }
774
775 *error = operationError;
776 d->emitSharedSignals(cs: &changeSet);
777 // return false if some error occurred
778 return (*error == QOrganizerManager::NoError);
779}
780
781/*! \reimp
782*/
783bool QOrganizerItemMemoryEngine::saveItems(QList<QOrganizerItem> *items, const QList<QOrganizerItemDetail::DetailType> &detailMask,
784 QMap<int, QOrganizerManager::Error> *errorMap, QOrganizerManager::Error *error)
785{
786 // TODO should the default implementation do the right thing, or return false?
787 if (detailMask.isEmpty()) {
788 // Non partial, just pass it on
789 return storeItems(organizeritems: items, detailMask, errorMap, error);
790 } else {
791 // Partial item save.
792 // Basically
793
794 // Need to:
795 // 1) fetch existing items
796 // 2) strip out details in definitionMask for existing items
797 // 3) copy the details from the passed in list for existing items
798 // 4) for any new items, copy the masked details to a blank item
799 // 5) save the modified ones
800 // 6) update the id of any new items
801 // 7) transfer any errors from saving to errorMap
802
803 QList<QOrganizerItemId> existingItemIds;
804
805 // Error conditions:
806 // 1) bad id passed in (can't fetch)
807 // 2) bad fetch (can't save partial update)
808 // 3) bad save error
809 // all of which needs to be returned in the error map
810
811 QHash<int, int> existingIdMap; // items index to existingItems index
812
813 // Try to figure out which of our arguments are new items
814 for (int i = 0; i < items->count(); i++) {
815 // See if there's a itemId that's not from this manager
816 const QOrganizerItem item = items->at(i);
817 if (item.id().managerUri() == managerUri()) {
818 if (!item.id().isNull()) {
819 existingIdMap.insert(akey: i, avalue: existingItemIds.count());
820 existingItemIds.append(t: item.id());
821 } else {
822 // Strange. it's just a new item
823 }
824 } else if (!item.id().managerUri().isEmpty() || !item.id().isNull()) {
825 // Hmm, error (wrong manager)
826 errorMap->insert(akey: i, avalue: QOrganizerManager::DoesNotExistError);
827 } // else new item
828 }
829
830 // Now fetch the existing items
831 QMap<int, QOrganizerManager::Error> fetchErrors;
832 QOrganizerManager::Error fetchError = QOrganizerManager::NoError;
833 QList<QOrganizerItem> existingItems = this->itemsForExport(ids: existingItemIds, fetchHint: QOrganizerItemFetchHint(), errorMap: &fetchErrors, error: &fetchError);
834
835 // Prepare the list to save
836 QList<QOrganizerItem> itemsToSave;
837 QList<int> savedToOriginalMap; // itemsToSave index to items index
838
839 for (int i = 0; i < items->count(); i++) {
840 // See if this is an existing item or a new one
841 const int fetchedIdx = existingIdMap.value(akey: i, adefaultValue: -1);
842 QOrganizerItem itemToSave;
843 if (fetchedIdx >= 0) {
844 // See if we had an error
845 if (fetchErrors[fetchedIdx] != QOrganizerManager::NoError) {
846 errorMap->insert(akey: i, avalue: fetchErrors[fetchedIdx]);
847 continue;
848 }
849
850 // Existing item we should have fetched
851 itemToSave = existingItems.at(i: fetchedIdx);
852
853 // QOrganizerItemData::removeOnly() is not exported, so we can only do this...
854 foreach (QOrganizerItemDetail::DetailType mask, detailMask) {
855 QList<QOrganizerItemDetail> details(itemToSave.details(detailType: mask));
856 foreach (QOrganizerItemDetail detail, details)
857 itemToSave.removeDetail(detail: &detail);
858 }
859 } else if (errorMap->contains(akey: i)) {
860 // A bad argument. Leave it out of the itemsToSave list
861 continue;
862 } else {
863 // new item
864 itemToSave.setType(items->at(i).type());
865 }
866
867 // Now copy in the details from the arguments
868 const QOrganizerItem& item = items->at(i);
869
870 // Perhaps this could do this directly rather than through saveDetail
871 // but that would duplicate the checks for display label etc
872 foreach (QOrganizerItemDetail::DetailType name, detailMask) {
873 QList<QOrganizerItemDetail> details = item.details(detailType: name);
874 foreach (QOrganizerItemDetail detail, details)
875 itemToSave.saveDetail(detail: &detail);
876 }
877 savedToOriginalMap.append(t: i);
878 itemsToSave.append(t: itemToSave);
879 }
880
881 // Now save them
882 QMap<int, QOrganizerManager::Error> saveErrors;
883 QOrganizerManager::Error saveError = QOrganizerManager::NoError;
884 storeItems(organizeritems: &itemsToSave, detailMask, errorMap: &saveErrors, error: &saveError);
885 // Now update the passed in arguments, where necessary
886
887 // Update IDs of the items list
888 for (int i = 0; i < itemsToSave.count(); i++) {
889 (*items)[savedToOriginalMap[i]].setId(itemsToSave[i].id());
890 }
891 // Populate the errorMap with the errorMap of the attempted save
892 QMap<int, QOrganizerManager::Error>::iterator it(saveErrors.begin());
893 while (it != saveErrors.end()) {
894 if (it.value() != QOrganizerManager::NoError) {
895 errorMap->insert(akey: savedToOriginalMap[it.key()], avalue: it.value());
896 }
897 it++;
898 }
899 return errorMap->isEmpty();
900 }
901}
902
903/*! Removes the organizer item identified by the given \a organizeritemId, storing any error to \a error and
904 filling the \a changeSet with ids of changed organizer items as required
905*/
906bool QOrganizerItemMemoryEngine::removeItem(const QOrganizerItemId& organizeritemId, QOrganizerItemChangeSet& changeSet, QOrganizerManager::Error* error)
907{
908 QHash<QOrganizerItemId, QOrganizerItem>::const_iterator hashIterator = d->m_idToItemHash.find(akey: organizeritemId);
909 if (hashIterator == d->m_idToItemHash.constEnd()) {
910 *error = QOrganizerManager::DoesNotExistError;
911 return false;
912 }
913
914 // if it is a child item, remove itself from the children hash
915 QOrganizerItem thisItem = hashIterator.value();
916 QOrganizerItemParent parentDetail = thisItem.detail(detailType: QOrganizerItemDetail::TypeParent);
917 if (!parentDetail.parentId().isNull()) {
918 d->m_parentIdToChildIdHash.remove(key: parentDetail.parentId(), value: organizeritemId);
919 }
920
921 // if it is a parent item, remove any children.
922 QList<QOrganizerItemId> childrenIds = d->m_parentIdToChildIdHash.values(akey: organizeritemId);
923 foreach (const QOrganizerItemId& childId, childrenIds) {
924 // remove the child occurrence from our lists.
925 d->m_idToItemHash.remove(akey: childId);
926 d->m_itemsInCollectionsHash.remove(key: d->m_itemsInCollectionsHash.key(avalue: childId), value: childId);
927 changeSet.insertRemovedItem(itemId: childId);
928 }
929
930 // remove the organizer item from the lists.
931 d->m_idToItemHash.remove(akey: organizeritemId);
932 d->m_parentIdToChildIdHash.remove(akey: organizeritemId);
933 d->m_itemsInCollectionsHash.remove(key: d->m_itemsInCollectionsHash.key(avalue: organizeritemId), value: organizeritemId);
934 *error = QOrganizerManager::NoError;
935
936 changeSet.insertRemovedItem(itemId: organizeritemId);
937 return true;
938}
939
940/*! Removes the organizer item occurrence identified by the given \a organizeritem. Removing a generated occurrence means
941 adding a new exception date to parent items exception date list. Stores any error to \a error and
942 fills the \a changeSet with ids of changed organizer items as required
943*/
944bool QOrganizerItemMemoryEngine::removeOccurrence(const QOrganizerItem &organizeritem, QOrganizerItemChangeSet &changeSet, QOrganizerManager::Error *error)
945{
946 QOrganizerItemParent parentDetail = organizeritem.detail(detailType: QOrganizerItemDetail::TypeParent);
947 if (parentDetail.parentId().isNull()) {
948 *error = QOrganizerManager::InvalidOccurrenceError;
949 return false;
950 }
951
952 QHash<QOrganizerItemId, QOrganizerItem>::const_iterator hashIterator = d->m_idToItemHash.find(akey: parentDetail.parentId());
953 if (hashIterator == d->m_idToItemHash.constEnd()) {
954 *error = QOrganizerManager::InvalidOccurrenceError;
955 return false;
956 } else {
957 QOrganizerItem parentItem = hashIterator.value();
958 QOrganizerItemRecurrence recurrenceDetail = parentItem.detail(detailType: QOrganizerItemDetail::TypeRecurrence);
959 QSet<QDate> exceptionDates = recurrenceDetail.exceptionDates();
960 exceptionDates.insert(value: parentDetail.originalDate());
961 recurrenceDetail.setExceptionDates(exceptionDates);
962 parentItem.saveDetail(detail: &recurrenceDetail);
963 d->m_idToItemHash.insert(akey: parentDetail.parentId(), avalue: parentItem);
964 changeSet.insertChangedItem(itemId: parentDetail.parentId(), typesChanged: QList<QOrganizerItemDetail::DetailType>());
965 }
966 *error = QOrganizerManager::NoError;
967 return true;
968}
969
970/*! \reimp
971*/
972bool QOrganizerItemMemoryEngine::removeItems(const QList<QOrganizerItemId> &itemIds, QMap<int, QOrganizerManager::Error> *errorMap,
973 QOrganizerManager::Error *error)
974{
975 Q_ASSERT(errorMap);
976
977 if (itemIds.count() == 0) {
978 *error = QOrganizerManager::BadArgumentError;
979 return false;
980 }
981
982 QOrganizerItemChangeSet changeSet;
983 QOrganizerItemId current;
984 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
985 for (int i = 0; i < itemIds.count(); i++) {
986 current = itemIds.at(i);
987 if (!removeItem(organizeritemId: current, changeSet, error)) {
988 operationError = *error;
989 errorMap->insert(akey: i, avalue: operationError);
990 }
991 }
992
993 *error = operationError;
994 d->emitSharedSignals(cs: &changeSet);
995
996 // return false if some errors occurred
997 return (*error == QOrganizerManager::NoError);
998}
999
1000/*! \reimp
1001*/
1002bool QOrganizerItemMemoryEngine::removeItems(const QList<QOrganizerItem> *items, QMap<int, QOrganizerManager::Error> *errorMap, QOrganizerManager::Error *error)
1003{
1004 Q_ASSERT(errorMap);
1005 if (items->count() == 0) {
1006 *error = QOrganizerManager::BadArgumentError;
1007 return false;
1008 }
1009
1010 QOrganizerItemChangeSet changeSet;
1011 QOrganizerItem current;
1012 QSet<QOrganizerItemId> removedParentIds;
1013 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1014 for (int i = 0; i < items->count(); i++) {
1015 current = items->at(i);
1016 QOrganizerManager::Error tempError = QOrganizerManager::NoError;
1017 if ((current.type() == QOrganizerItemType::TypeEventOccurrence
1018 || current.type() == QOrganizerItemType::TypeTodoOccurrence)
1019 && current.id().isNull()) {
1020 // this is a generated occurrence, modify parent items exception dates
1021 QOrganizerItemParent parentDetail = current.detail(detailType: QOrganizerItemDetail::TypeParent);
1022 if (removedParentIds.isEmpty() || !removedParentIds.contains(value: parentDetail.parentId()))
1023 removeOccurrence(organizeritem: current, changeSet, error: &tempError);
1024 } else {
1025 removeItem(organizeritemId: current.id(), changeSet, error: &tempError);
1026 if (tempError == QOrganizerManager::NoError && itemHasReccurence(oi: current))
1027 removedParentIds.insert(value: current.id());
1028 }
1029 if (tempError != QOrganizerManager::NoError) {
1030 errorMap->insert(akey: i, avalue: tempError);
1031 operationError = tempError;
1032 }
1033 }
1034
1035 *error = operationError;
1036 d->emitSharedSignals(cs: &changeSet);
1037
1038 // return false if some errors occurred
1039 return (*error == QOrganizerManager::NoError);
1040}
1041
1042QOrganizerCollectionId QOrganizerItemMemoryEngine::defaultCollectionId() const
1043{
1044 const uint id(QOrganizerItemMemoryEngineData::DefaultCollectionLocalId);
1045 return collectionId(localId: QByteArray(reinterpret_cast<const char *>(&id), sizeof(uint)));
1046}
1047
1048QOrganizerCollection QOrganizerItemMemoryEngine::collection(const QOrganizerCollectionId& collectionId, QOrganizerManager::Error* error)
1049{
1050 if (d->m_idToCollectionHash.contains(akey: collectionId)) {
1051 *error = QOrganizerManager::NoError;
1052 return d->m_idToCollectionHash.value(akey: collectionId);
1053 }
1054
1055 *error = QOrganizerManager::DoesNotExistError;
1056 return QOrganizerCollection();
1057}
1058
1059QList<QOrganizerCollection> QOrganizerItemMemoryEngine::collections(QOrganizerManager::Error* error)
1060{
1061 Q_ASSERT(!d->m_idToCollectionHash.isEmpty());
1062 *error = QOrganizerManager::NoError;
1063 return d->m_idToCollectionHash.values();
1064}
1065
1066bool QOrganizerItemMemoryEngine::saveCollection(QOrganizerCollection* collection, QOrganizerManager::Error* error)
1067{
1068 QOrganizerCollectionId collectionId = collection->id();
1069
1070 QOrganizerCollectionChangeSet cs;
1071 if (d->m_idToCollectionHash.contains(akey: collectionId)) {
1072 // this collection already exists. update our internal list
1073 // if the collection has been modified.
1074 if (d->m_idToCollectionHash.value(akey: collectionId) == *collection) {
1075 *error = QOrganizerManager::NoError;
1076 return true;
1077 }
1078
1079 cs.insertChangedCollection(collectionId);
1080 } else {
1081 // this must be a new collection. check that the id is null.
1082 if (!collectionId.isNull() && collectionId.managerUri() != d->m_managerUri) {
1083 // nope, this collection belongs in another manager, or has been deleted.
1084 *error = QOrganizerManager::DoesNotExistError;
1085 return false;
1086 }
1087
1088 // this is a new collection with a null id; create a new id, add it to our list.
1089 collectionId = this->collectionId(localId: QByteArray(reinterpret_cast<const char *>(&d->m_nextOrganizerCollectionId), sizeof(quint32)));
1090 ++(d->m_nextOrganizerCollectionId);
1091 collection->setId(collectionId);
1092 cs.insertAddedCollection(collectionId);
1093 }
1094
1095 d->m_idToCollectionHash.insert(akey: collectionId, avalue: *collection);
1096 d->emitSharedSignals(cs: &cs);
1097 *error = QOrganizerManager::NoError;
1098 return true;
1099}
1100
1101bool QOrganizerItemMemoryEngine::removeCollection(const QOrganizerCollectionId& collectionId, QOrganizerManager::Error* error)
1102{
1103 if (collectionId == defaultCollectionId()) {
1104 // attempting to remove the default collection. this is not allowed in the memory engine.
1105 *error = QOrganizerManager::PermissionsError;
1106 return false;
1107 }
1108
1109 // try to find the collection to remove it (and the items it contains)
1110 if (d->m_idToCollectionHash.contains(akey: collectionId)) {
1111 // found the collection to remove. remove the items in the collection.
1112 const QList<QOrganizerItemId> itemsToRemove = d->m_itemsInCollectionsHash.values(akey: collectionId);
1113 if (!itemsToRemove.isEmpty()) {
1114 QMap<int, QOrganizerManager::Error> errorMap;
1115 if (!removeItems(itemIds: itemsToRemove, errorMap: &errorMap, error)) {
1116 // without transaction support, we can't back out. but the operation should fail.
1117 return false;
1118 }
1119 }
1120
1121 // now remove the collection from our lists.
1122 d->m_idToCollectionHash.remove(akey: collectionId);
1123 d->m_itemsInCollectionsHash.remove(akey: collectionId);
1124 QOrganizerCollectionChangeSet cs;
1125 cs.insertRemovedCollection(collectionId);
1126 d->emitSharedSignals(cs: &cs);
1127 *error = QOrganizerManager::NoError;
1128 return true;
1129 }
1130
1131 // the collection doesn't exist...
1132 *error = QOrganizerManager::DoesNotExistError;
1133 return false;
1134}
1135
1136/*! \reimp
1137*/
1138void QOrganizerItemMemoryEngine::requestDestroyed(QOrganizerAbstractRequest* req)
1139{
1140 Q_UNUSED(req);
1141}
1142
1143/*! \reimp
1144*/
1145bool QOrganizerItemMemoryEngine::startRequest(QOrganizerAbstractRequest* req)
1146{
1147 updateRequestState(request: req, state: QOrganizerAbstractRequest::ActiveState);
1148 performAsynchronousOperation(request: req);
1149
1150 return true;
1151}
1152
1153/*! \reimp
1154*/
1155bool QOrganizerItemMemoryEngine::cancelRequest(QOrganizerAbstractRequest* req)
1156{
1157 Q_UNUSED(req); // we can't cancel since we complete immediately
1158 return false;
1159}
1160
1161/*! \reimp
1162*/
1163bool QOrganizerItemMemoryEngine::waitForRequestFinished(QOrganizerAbstractRequest* req, int msecs)
1164{
1165 // in our implementation, we always complete any operation we start.
1166 Q_UNUSED(msecs);
1167 Q_UNUSED(req);
1168
1169 return true;
1170}
1171
1172QList<QOrganizerItemDetail::DetailType> QOrganizerItemMemoryEngine::supportedItemDetails(QOrganizerItemType::ItemType itemType) const
1173{
1174 QList<QOrganizerItemDetail::DetailType> supportedDetails;
1175 supportedDetails << QOrganizerItemDetail::TypeItemType
1176 << QOrganizerItemDetail::TypeGuid
1177 << QOrganizerItemDetail::TypeTimestamp
1178 << QOrganizerItemDetail::TypeDisplayLabel
1179 << QOrganizerItemDetail::TypeDescription
1180 << QOrganizerItemDetail::TypeComment
1181 << QOrganizerItemDetail::TypeTag
1182 << QOrganizerItemDetail::TypeClassification
1183 << QOrganizerItemDetail::TypeExtendedDetail;
1184
1185 if (itemType == QOrganizerItemType::TypeEvent) {
1186 supportedDetails << QOrganizerItemDetail::TypeRecurrence
1187 << QOrganizerItemDetail::TypeEventTime
1188 << QOrganizerItemDetail::TypePriority
1189 << QOrganizerItemDetail::TypeLocation
1190 << QOrganizerItemDetail::TypeReminder
1191 << QOrganizerItemDetail::TypeAudibleReminder
1192 << QOrganizerItemDetail::TypeEmailReminder
1193 << QOrganizerItemDetail::TypeVisualReminder;
1194 } else if (itemType == QOrganizerItemType::TypeTodo) {
1195 supportedDetails << QOrganizerItemDetail::TypeRecurrence
1196 << QOrganizerItemDetail::TypeTodoTime
1197 << QOrganizerItemDetail::TypePriority
1198 << QOrganizerItemDetail::TypeTodoProgress
1199 << QOrganizerItemDetail::TypeReminder
1200 << QOrganizerItemDetail::TypeAudibleReminder
1201 << QOrganizerItemDetail::TypeEmailReminder
1202 << QOrganizerItemDetail::TypeVisualReminder;
1203 } else if (itemType == QOrganizerItemType::TypeEventOccurrence) {
1204 supportedDetails << QOrganizerItemDetail::TypeParent
1205 << QOrganizerItemDetail::TypeEventTime
1206 << QOrganizerItemDetail::TypePriority
1207 << QOrganizerItemDetail::TypeLocation
1208 << QOrganizerItemDetail::TypeReminder
1209 << QOrganizerItemDetail::TypeAudibleReminder
1210 << QOrganizerItemDetail::TypeEmailReminder
1211 << QOrganizerItemDetail::TypeVisualReminder;
1212 } else if (itemType == QOrganizerItemType::TypeTodoOccurrence) {
1213 supportedDetails << QOrganizerItemDetail::TypeParent
1214 << QOrganizerItemDetail::TypeTodoTime
1215 << QOrganizerItemDetail::TypePriority
1216 << QOrganizerItemDetail::TypeTodoProgress
1217 << QOrganizerItemDetail::TypeReminder
1218 << QOrganizerItemDetail::TypeAudibleReminder
1219 << QOrganizerItemDetail::TypeEmailReminder
1220 << QOrganizerItemDetail::TypeVisualReminder;
1221 } else if (itemType == QOrganizerItemType::TypeJournal) {
1222 supportedDetails << QOrganizerItemDetail::TypeJournalTime;
1223 } else if (itemType == QOrganizerItemType::TypeNote) {
1224 // nothing ;)
1225 } else {
1226 supportedDetails.clear();
1227 }
1228
1229 return supportedDetails;
1230}
1231
1232/*!
1233 * This slot is called some time after an asynchronous request is started.
1234 * It performs the required operation, sets the result and returns.
1235 */
1236void QOrganizerItemMemoryEngine::performAsynchronousOperation(QOrganizerAbstractRequest *currentRequest)
1237{
1238 // store up changes, and emit signals once at the end of the (possibly batch) operation.
1239 QOrganizerItemChangeSet changeSet;
1240
1241 // Now perform the active request and emit required signals.
1242 Q_ASSERT(currentRequest->state() == QOrganizerAbstractRequest::ActiveState);
1243 switch (currentRequest->type()) {
1244 case QOrganizerAbstractRequest::ItemFetchRequest:
1245 {
1246 QOrganizerItemFetchRequest* r = static_cast<QOrganizerItemFetchRequest*>(currentRequest);
1247 QOrganizerItemFilter filter = r->filter();
1248 QList<QOrganizerItemSortOrder> sorting = r->sorting();
1249 QOrganizerItemFetchHint fetchHint = r->fetchHint();
1250 QDateTime startDate = r->startDate();
1251 QDateTime endDate = r->endDate();
1252
1253 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1254 QList<QOrganizerItem> requestedOrganizerItems = items(filter, startDateTime: startDate, endDateTime: endDate, maxCount: -1, sortOrders: sorting, fetchHint, error: &operationError);
1255
1256 // update the request with the results.
1257 if (!requestedOrganizerItems.isEmpty() || operationError != QOrganizerManager::NoError)
1258 updateItemFetchRequest(request: r, result: requestedOrganizerItems, error: operationError, newState: QOrganizerAbstractRequest::FinishedState);
1259 else
1260 updateRequestState(request: currentRequest, state: QOrganizerAbstractRequest::FinishedState);
1261 }
1262 break;
1263
1264 case QOrganizerAbstractRequest::ItemFetchByIdRequest: {
1265 QOrganizerItemFetchByIdRequest* r = static_cast<QOrganizerItemFetchByIdRequest*>(currentRequest);
1266 // fetch hint cannot be used in memory backend
1267
1268 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1269 QMap<int, QOrganizerManager::Error> errorMap;
1270
1271 QList<QOrganizerItem> requestedOrganizerItems;
1272
1273 for (int i = 0; i < r->ids().size(); i++) {
1274 QOrganizerItem item = d->m_idToItemHash.value(akey: r->ids().at(i), adefaultValue: QOrganizerItem());
1275 requestedOrganizerItems.append(t: item);
1276 if (item.isEmpty())
1277 errorMap.insert(akey: i, avalue: QOrganizerManager::DoesNotExistError);
1278 }
1279
1280 // update the request with the results.
1281 if (!requestedOrganizerItems.isEmpty() || operationError != QOrganizerManager::NoError || !errorMap.isEmpty())
1282 QOrganizerManagerEngine::updateItemFetchByIdRequest(request: r, result: requestedOrganizerItems, error: operationError, errorMap, QOrganizerAbstractRequest::FinishedState);
1283 else
1284 updateRequestState(request: currentRequest, state: QOrganizerAbstractRequest::FinishedState);
1285 }
1286 break;
1287
1288 case QOrganizerAbstractRequest::ItemFetchForExportRequest:
1289 {
1290 QOrganizerItemFetchForExportRequest* r = static_cast<QOrganizerItemFetchForExportRequest*>(currentRequest);
1291 QOrganizerItemFilter filter = r->filter();
1292 QList<QOrganizerItemSortOrder> sorting = r->sorting();
1293 QOrganizerItemFetchHint fetchHint = r->fetchHint();
1294 QDateTime startDate = r->startDate();
1295 QDateTime endDate = r->endDate();
1296
1297 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1298 QList<QOrganizerItem> requestedOrganizerItems = itemsForExport(startDateTime: startDate, endDateTime: endDate, filter, sortOrders: sorting, fetchHint, error: &operationError);
1299
1300 // update the request with the results.
1301 if (!requestedOrganizerItems.isEmpty() || operationError != QOrganizerManager::NoError)
1302 updateItemFetchForExportRequest(request: r, result: requestedOrganizerItems, error: operationError, newState: QOrganizerAbstractRequest::FinishedState);
1303 else
1304 updateRequestState(request: currentRequest, state: QOrganizerAbstractRequest::FinishedState);
1305 }
1306 break;
1307
1308 case QOrganizerAbstractRequest::ItemOccurrenceFetchRequest:
1309 {
1310 QOrganizerItemOccurrenceFetchRequest* r = static_cast<QOrganizerItemOccurrenceFetchRequest*>(currentRequest);
1311 QOrganizerItem parentItem(r->parentItem());
1312 QDateTime startDate(r->startDate());
1313 QDateTime endDate(r->endDate());
1314 int countLimit = r->maxOccurrences();
1315 QOrganizerItemFetchHint fetchHint = r->fetchHint();
1316
1317 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1318 QList<QOrganizerItem> requestedOrganizerItems = itemOccurrences(parentItem, startDateTime: startDate, endDateTime: endDate, maxCount: countLimit, fetchHint, error: &operationError);
1319
1320 // update the request with the results.
1321 if (!requestedOrganizerItems.isEmpty() || operationError != QOrganizerManager::NoError)
1322 updateItemOccurrenceFetchRequest(request: r, result: requestedOrganizerItems, error: operationError, newState: QOrganizerAbstractRequest::FinishedState);
1323 else
1324 updateRequestState(request: currentRequest, state: QOrganizerAbstractRequest::FinishedState);
1325 }
1326 break;
1327
1328
1329 case QOrganizerAbstractRequest::ItemIdFetchRequest:
1330 {
1331 QOrganizerItemIdFetchRequest* r = static_cast<QOrganizerItemIdFetchRequest*>(currentRequest);
1332 QOrganizerItemFilter filter = r->filter();
1333 QList<QOrganizerItemSortOrder> sorting = r->sorting();
1334 QDateTime startDate = r->startDate();
1335 QDateTime endDate = r->endDate();
1336
1337 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1338 QList<QOrganizerItemId> requestedOrganizerItemIds = itemIds(filter, startDateTime: startDate, endDateTime: endDate, sortOrders: sorting, error: &operationError);
1339
1340 if (!requestedOrganizerItemIds.isEmpty() || operationError != QOrganizerManager::NoError)
1341 updateItemIdFetchRequest(request: r, result: requestedOrganizerItemIds, error: operationError, newState: QOrganizerAbstractRequest::FinishedState);
1342 else
1343 updateRequestState(request: currentRequest, state: QOrganizerAbstractRequest::FinishedState);
1344 }
1345 break;
1346
1347 case QOrganizerAbstractRequest::ItemSaveRequest:
1348 {
1349 QOrganizerItemSaveRequest* r = static_cast<QOrganizerItemSaveRequest*>(currentRequest);
1350 QList<QOrganizerItem> organizeritems = r->items();
1351
1352 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1353 QMap<int, QOrganizerManager::Error> errorMap;
1354 saveItems(items: &organizeritems, detailMask: r->detailMask(), errorMap: &errorMap, error: &operationError);
1355
1356 updateItemSaveRequest(request: r, result: organizeritems, error: operationError, errorMap, newState: QOrganizerAbstractRequest::FinishedState);
1357 }
1358 break;
1359
1360 case QOrganizerAbstractRequest::ItemRemoveRequest:
1361 {
1362 QOrganizerItemRemoveRequest* r = static_cast<QOrganizerItemRemoveRequest*>(currentRequest);
1363 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1364 QList<QOrganizerItem> organizeritemsToRemove = r->items();
1365 QSet<QOrganizerItemId> removedParentIds;
1366 QMap<int, QOrganizerManager::Error> errorMap;
1367
1368 for (int i = 0; i < organizeritemsToRemove.size(); i++) {
1369 QOrganizerItem item = organizeritemsToRemove[i];
1370 QOrganizerManager::Error tempError = QOrganizerManager::NoError;
1371 if ((item.type() == QOrganizerItemType::TypeEventOccurrence
1372 || item.type() == QOrganizerItemType::TypeTodoOccurrence)
1373 && item.id().isNull()) {
1374 // this is a generated occurrence, modify parent items exception dates
1375 QOrganizerItemParent parentDetail = item.detail(detailType: QOrganizerItemDetail::TypeParent);
1376 if (removedParentIds.isEmpty() || !removedParentIds.contains(value: parentDetail.parentId()))
1377 removeOccurrence(organizeritem: item, changeSet, error: &tempError);
1378 } else {
1379 removeItem(organizeritemId: item.id(), changeSet, error: &tempError);
1380 if (tempError == QOrganizerManager::NoError && itemHasReccurence(oi: item))
1381 removedParentIds.insert(value: item.id());
1382 }
1383 if (tempError != QOrganizerManager::NoError) {
1384 errorMap.insert(akey: i, avalue: tempError);
1385 operationError = tempError;
1386 }
1387 }
1388 if (!errorMap.isEmpty() || operationError != QOrganizerManager::NoError)
1389 updateItemRemoveRequest(request: r, error: operationError, errorMap, newState: QOrganizerAbstractRequest::FinishedState);
1390 else
1391 updateRequestState(request: currentRequest, state: QOrganizerAbstractRequest::FinishedState);
1392 }
1393 break;
1394
1395 case QOrganizerAbstractRequest::ItemRemoveByIdRequest:
1396 {
1397 QOrganizerItemRemoveByIdRequest* r = static_cast<QOrganizerItemRemoveByIdRequest*>(currentRequest);
1398 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1399 QList<QOrganizerItemId> organizeritemsToRemove = r->itemIds();
1400 QMap<int, QOrganizerManager::Error> errorMap;
1401
1402 for (int i = 0; i < organizeritemsToRemove.size(); i++) {
1403 QOrganizerManager::Error tempError = QOrganizerManager::NoError;
1404 removeItem(organizeritemId: organizeritemsToRemove.at(i), changeSet, error: &tempError);
1405
1406 if (tempError != QOrganizerManager::NoError) {
1407 errorMap.insert(akey: i, avalue: tempError);
1408 operationError = tempError;
1409 }
1410 }
1411
1412 if (!errorMap.isEmpty() || operationError != QOrganizerManager::NoError)
1413 updateItemRemoveByIdRequest(request: r, error: operationError, errorMap, newState: QOrganizerAbstractRequest::FinishedState);
1414 else
1415 updateRequestState(request: currentRequest, state: QOrganizerAbstractRequest::FinishedState);
1416 }
1417 break;
1418
1419 case QOrganizerAbstractRequest::CollectionFetchRequest:
1420 {
1421 QOrganizerCollectionFetchRequest* r = static_cast<QOrganizerCollectionFetchRequest*>(currentRequest);
1422 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1423 QList<QOrganizerCollection> requestedOrganizerCollections = collections(error: &operationError);
1424
1425 // update the request with the results.
1426 updateCollectionFetchRequest(request: r, result: requestedOrganizerCollections, error: operationError, newState: QOrganizerAbstractRequest::FinishedState);
1427 }
1428 break;
1429
1430 case QOrganizerAbstractRequest::CollectionSaveRequest:
1431 {
1432 QOrganizerCollectionSaveRequest* r = static_cast<QOrganizerCollectionSaveRequest*>(currentRequest);
1433 QList<QOrganizerCollection> collections = r->collections();
1434 QList<QOrganizerCollection> retn;
1435
1436 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1437 QMap<int, QOrganizerManager::Error> errorMap;
1438 for (int i = 0; i < collections.size(); ++i) {
1439 QOrganizerManager::Error tempError = QOrganizerManager::NoError;
1440 QOrganizerCollection curr = collections.at(i);
1441 if (!saveCollection(collection: &curr, error: &tempError)) {
1442 errorMap.insert(akey: i, avalue: tempError);
1443 operationError = tempError;
1444 }
1445 retn.append(t: curr);
1446 }
1447
1448 updateCollectionSaveRequest(request: r, result: retn, error: operationError, errorMap, newState: QOrganizerAbstractRequest::FinishedState);
1449 }
1450 break;
1451
1452 case QOrganizerAbstractRequest::CollectionRemoveRequest:
1453 {
1454 // removes the collections identified in the list of ids.
1455 QOrganizerCollectionRemoveRequest* r = static_cast<QOrganizerCollectionRemoveRequest*>(currentRequest);
1456 QOrganizerManager::Error operationError = QOrganizerManager::NoError;
1457 QList<QOrganizerCollectionId> collectionsToRemove = r->collectionIds();
1458 QMap<int, QOrganizerManager::Error> errorMap;
1459
1460 for (int i = 0; i < collectionsToRemove.size(); i++) {
1461 QOrganizerManager::Error tempError = QOrganizerManager::NoError;
1462 removeCollection(collectionId: collectionsToRemove.at(i), error: &tempError);
1463
1464 if (tempError != QOrganizerManager::NoError) {
1465 errorMap.insert(akey: i, avalue: tempError);
1466 operationError = tempError;
1467 }
1468 }
1469
1470 if (!errorMap.isEmpty() || operationError != QOrganizerManager::NoError)
1471 updateCollectionRemoveRequest(request: r, error: operationError, errorMap, newState: QOrganizerAbstractRequest::FinishedState);
1472 else
1473 updateRequestState(request: currentRequest, state: QOrganizerAbstractRequest::FinishedState);
1474 }
1475 break;
1476
1477 default: // unknown request type.
1478 break;
1479 }
1480
1481 // now emit any signals we have to emit
1482 d->emitSharedSignals(cs: &changeSet);
1483}
1484
1485#include "moc_qorganizeritemmemorybackend_p.cpp"
1486
1487QT_END_NAMESPACE_ORGANIZER
1488

source code of qtpim/src/plugins/organizer/memory/qorganizeritemmemorybackend.cpp