diff options
author | Kevin Wu Won <kevin.wuwon@nokia.com> | 2010-11-11 16:19:56 +1000 |
---|---|---|
committer | Kevin Wu Won <kevin.wuwon@nokia.com> | 2010-11-11 16:21:44 +1000 |
commit | 72311748bc808813e9b2c70a5141407018e4cef9 (patch) | |
tree | 12b0ae4b8cfb93ba76f9c09beb0f6aece549ebe6 | |
parent | c63c926b9b4c098e1fe4c5500c2416af3c40c585 (diff) |
Finish implementing mkcal engine
This represents a squashed rebase of the harmattan branch up to
commit c5a489c9b43ad8df052e373d18895d99e55240bf
Change-Id: I698ffa23442487c8b651fc329fb42c4016c5446c
-rw-r--r-- | plugins/organizer/maemo6/qorganizermaemo6.cpp | 826 | ||||
-rw-r--r-- | plugins/organizer/maemo6/qorganizermaemo6.h | 203 | ||||
-rw-r--r-- | plugins/organizer/mkcal/mkcal.pro (renamed from plugins/organizer/maemo6/maemo6.pro) | 15 | ||||
-rw-r--r-- | plugins/organizer/mkcal/mkcalengine.cpp | 1385 | ||||
-rw-r--r-- | plugins/organizer/mkcal/mkcalengine.h | 230 | ||||
-rw-r--r-- | plugins/organizer/mkcal/mkcalid.h | 205 | ||||
-rw-r--r-- | plugins/organizer/mkcal/qorganizerasynchmanager.cpp | 489 | ||||
-rw-r--r-- | plugins/organizer/mkcal/qorganizerasynchmanager.h (renamed from plugins/organizer/maemo6/maemo6itemlocalid.h) | 89 | ||||
-rw-r--r-- | plugins/organizer/organizer.pro | 3 | ||||
-rw-r--r-- | src/organizer/items/qorganizerjournal.h | 1 | ||||
-rw-r--r-- | src/organizer/organizer.pro | 2 | ||||
-rw-r--r-- | tests/auto/qorganizermanager/tst_qorganizermanager.cpp | 636 |
12 files changed, 2874 insertions, 1210 deletions
diff --git a/plugins/organizer/maemo6/qorganizermaemo6.cpp b/plugins/organizer/maemo6/qorganizermaemo6.cpp deleted file mode 100644 index e7040957f3..0000000000 --- a/plugins/organizer/maemo6/qorganizermaemo6.cpp +++ /dev/null @@ -1,826 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the Qt Mobility Components. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qorganizermaemo6.h" -#include "qtorganizer.h" -#include "maemo6itemlocalid.h" - -//QTM_USE_NAMESPACE - -QOrganizerManagerEngine* QOrganizerItemMaemo6Factory::engine(const QMap<QString, QString>& parameters, QOrganizerManager::Error* error) -{ - Q_UNUSED(parameters); - - Q_UNUSED(error); - - /* TODO - if you understand any specific parameters. save them in the engine so that engine::managerParameters can return them */ - - QOrganizerItemMaemo6Engine* ret = new QOrganizerItemMaemo6Engine(); // manager takes ownership and will clean up. - return ret; -} - -QString QOrganizerItemMaemo6Factory::managerName() const -{ - return QLatin1String("maemo6"); -} - -QOrganizerItemEngineLocalId* QOrganizerItemMaemo6Factory::createItemEngineLocalId() const -{ - return new Maemo6ItemLocalId; -} - -QOrganizerCollectionEngineLocalId* QOrganizerItemMaemo6Factory::createCollectionEngineLocalId() const -{ - return NULL; -} - -Q_EXPORT_PLUGIN2(qtorganizer_maemo6, QOrganizerItemMaemo6Factory); - - -QOrganizerItemMaemo6Engine::QOrganizerItemMaemo6Engine() - : d(new QOrganizerItemMaemo6EngineData) -{ -} - -QOrganizerItemMaemo6Engine::~QOrganizerItemMaemo6Engine() -{ - /* TODO clean up your stuff. Perhaps a QScopedPointer or QSharedDataPointer would be in order */ -} - -QString QOrganizerItemMaemo6Engine::managerName() const -{ - return QLatin1String("maemo6"); -} - -QMap<QString, QString> QOrganizerItemMaemo6Engine::managerParameters() const -{ - /* TODO - in case you have any actual parameters that are relevant that you saved in the factory method, return them here */ - return QMap<QString, QString>(); -} - -int QOrganizerItemMaemo6Engine::managerVersion() const -{ - /* TODO - implement this appropriately. This is strictly defined by the engine, so return whatever you like */ - return 1; -} - -QList<QOrganizerItem> QOrganizerItemMaemo6Engine::itemInstances(const QOrganizerItem& generator, const QDateTime& periodStart, const QDateTime& periodEnd, int maxCount, QOrganizerManager::Error* error) const -{ - /* - TODO - - This function should create a list of instances that occur in the time period from the supplied item. - The periodStart should always be valid, and either the periodEnd or the maxCount will be valid (if periodEnd is - valid, use that. Otherwise use the count). It's permissible to limit the number of items returned... - - Basically, if the generator item is an Event, a list of EventOccurrences should be returned. Similarly for - Todo/TodoOccurrence. - - If there are no instances, return an empty list. - - The returned items should have a QOrganizerItemInstanceOrigin detail that points to the generator and the - original instance that the event would have occurred on (e.g. with an exception). - - They should not have recurrence information details in them. - - We might change the signature to split up the periodStart + periodEnd / periodStart + maxCount cases. - */ - - QOrganizerItemLocalId generatorId = generator.localId(); - QString kId = static_cast<Maemo6ItemLocalId*>(QOrganizerManagerEngine::engineLocalItemId(generatorId))->toString(); - Incidence* generatorIncidence = d->m_calendarBackend.incidence(kId); - Incidence::List generatorList; - generatorList.append(generatorIncidence); - ExtendedCalendar::ExpandedIncidenceList incidenceList = d->m_calendarBackend.expandRecurrences( - &generatorList, - KDateTime(periodStart), - KDateTime(periodEnd)); - QList<QOrganizerItem> instances; - foreach (const ExtendedCalendar::ExpandedIncidence& expandedIncidence, incidenceList) { - QDateTime incidenceDateTime = expandedIncidence.first; - Incidence* incidence = expandedIncidence.second; - IncidenceToItemConverter converter(managerUri()); - QOrganizerItem instance; - if (converter.convertIncidenceToItem(incidence, &instance)) { - if (instance.type() == QOrganizerItemType::TypeEvent) { - QOrganizerEventOccurrence* event = static_cast<QOrganizerEventOccurrence*>(&instance); - event->setType(QOrganizerItemType::TypeEventOccurrence); - event->setStartDateTime(incidenceDateTime); - event->setParentLocalId(generatorId); - } else if (instance.type() == QOrganizerItemType::TypeTodo) { - QOrganizerTodoOccurrence* todo = static_cast<QOrganizerTodoOccurrence*>(&instance); - todo->setType(QOrganizerItemType::TypeTodo); - todo->setStartDateTime(incidenceDateTime); - todo->setParentLocalId(generatorId); - } - if (incidence == generatorIncidence) { - QOrganizerItemId id; - id.setManagerUri(managerUri()); - id.setLocalId(QOrganizerItemLocalId()); - instance.setId(id); - } - instances << instance; - } - } - return instances; -} - -QList<QOrganizerItemLocalId> QOrganizerItemMaemo6Engine::itemIds(const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, QOrganizerManager::Error* error) const -{ - QList<QOrganizerItem> ret = items(filter, sortOrders, QOrganizerItemFetchHint(), error); - - // TODO: we don't have to sort again since items() has done it for us - return QOrganizerManagerEngine::sortItems(ret, sortOrders); -} - -QList<QOrganizerItem> QOrganizerItemMaemo6Engine::items(const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const -{ - Q_UNUSED(fetchHint); - // TODO: optimise by using our own filters - - // Just naively get all the incidences - d->m_calendarBackend.setFilter(0); - Incidence::List incidences = d->m_calendarBackend.incidences(); - QList<QOrganizerItem> partiallyFilteredItems; - - // Convert them all to QOrganizerItems - IncidenceToItemConverter converter(managerUri()); - foreach (Incidence* incidence, incidences) { - QOrganizerItem item; - if (converter.convertIncidenceToItem(incidence, &item)) - partiallyFilteredItems << item; - } - QList<QOrganizerItem> ret; - - // Now filter them - foreach(const QOrganizerItem& item, partiallyFilteredItems) { - if (QOrganizerManagerEngine::testFilter(filter, item)) { - QOrganizerManagerEngine::addSorted(&ret, item, sortOrders); - } - } - - return ret; -} - -QOrganizerItem QOrganizerItemMaemo6Engine::item(const QOrganizerItemLocalId& itemId, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const -{ - Q_UNUSED(fetchHint); - Incidence* theIncidence = incidence(itemId); - if (!theIncidence) { - *error = QOrganizerManager::DoesNotExistError; - return QOrganizerItem(); - } - IncidenceToItemConverter converter(managerUri()); - QOrganizerItem item; - if (converter.convertIncidenceToItem(theIncidence, &item)) { - return item; - } else { - *error = QOrganizerManager::DoesNotExistError; - return QOrganizerItem(); - } -} - -bool QOrganizerItemMaemo6Engine::saveItems(QList<QOrganizerItem>* items, const QOrganizerCollectionLocalId& collectionId, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error) -{ - if (!items || collectionId != QOrganizerCollectionLocalId()) { - *error = QOrganizerManager::BadArgumentError; - } - - /* - TODO - The item passed in should be validated according to the schema. - */ - *error = QOrganizerManager::NoError; - for (int i = 0; i < items->size(); i++) { - QOrganizerManager::Error thisError; - QOrganizerItem item = items->at(i); - if (saveItem(&item, &thisError)) { - items->replace(i, item); - } else { - *error = thisError; - errorMap->insert(i, thisError); - } - } - return *error == QOrganizerManager::NoError; -} - -bool QOrganizerItemMaemo6Engine::saveItem(QOrganizerItem* item, QOrganizerManager::Error* error) -{ - // ensure that the organizeritem's details conform to their definitions - if (!validateItem(*item, error)) { - return false; - } - - Incidence* theIncidence = softSaveItem(item, error); - if (theIncidence) { - d->m_calendarBackend.save(); - QString kId = theIncidence->uid(); - QOrganizerItemLocalId qLocalId = QOrganizerItemLocalId(new Maemo6ItemLocalId(kId)); - QOrganizerItemId qId; - qId.setManagerUri(managerUri()); - qId.setLocalId(qLocalId); - item->setId(qId); - return true; - } - return false; -} - -bool QOrganizerItemMaemo6Engine::removeItems(const QList<QOrganizerItemLocalId>& itemIds, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error) -{ - *error = QOrganizerManager::NoError; - for (int i = 0; i < itemIds.size(); i++) { - QOrganizerItemLocalId id = itemIds[i]; - Incidence* theIncidence = incidence(id); - if (!theIncidence) { - *error = QOrganizerManager::DoesNotExistError; - errorMap->insert(i, QOrganizerManager::DoesNotExistError); - continue; - } - if (!d->m_calendarBackend.deleteIncidence(theIncidence)) { - *error = QOrganizerManager::UnspecifiedError; - errorMap->insert(i, QOrganizerManager::UnspecifiedError); - } - } - return *error == QOrganizerManager::NoError; -} - -QMap<QString, QOrganizerItemDetailDefinition> QOrganizerItemMaemo6Engine::detailDefinitions(const QString& itemType, QOrganizerManager::Error* error) const -{ - *error = QOrganizerManager::NoError; - return schemaDefinitions().value(itemType); -} - -QOrganizerItemDetailDefinition QOrganizerItemMaemo6Engine::detailDefinition(const QString& definitionId, const QString& itemType, QOrganizerManager::Error* error) const -{ - /* TODO - the default implementation just calls the base detailDefinitions function. If that's inefficent, implement this */ - return QOrganizerManagerEngine::detailDefinition(definitionId, itemType, error); -} - -bool QOrganizerItemMaemo6Engine::saveDetailDefinition(const QOrganizerItemDetailDefinition& def, const QString& itemType, QOrganizerManager::Error* error) -{ - /* TODO - if you support adding custom fields, do that here. Otherwise call the base functionality. */ - return QOrganizerManagerEngine::saveDetailDefinition(def, itemType, error); -} - -bool QOrganizerItemMaemo6Engine::removeDetailDefinition(const QString& definitionId, const QString& itemType, QOrganizerManager::Error* error) -{ - /* TODO - if you support removing custom fields, do that here. Otherwise call the base functionality. */ - return QOrganizerManagerEngine::removeDetailDefinition(definitionId, itemType, error); -} - -QOrganizerCollectionLocalId QOrganizerItemMaemo6Engine::defaultCollectionId(QOrganizerManager::Error* error) const -{ - *error = QOrganizerManager::NoError; - return QOrganizerCollectionLocalId(0); -} - -QList<QOrganizerCollectionLocalId> QOrganizerItemMaemo6Engine::collectionIds(QOrganizerManager::Error* error) const -{ - *error = QOrganizerManager::NoError; - QList<QOrganizerCollectionLocalId> retn; - retn << QOrganizerCollectionLocalId(0); - return retn; -} - -QList<QOrganizerCollection> QOrganizerItemMaemo6Engine::collections(const QList<QOrganizerCollectionLocalId>& collectionIds, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error) const -{ - Q_UNUSED(errorMap); - // XXX TODO: use error map, and fix implementation as per docs. - - *error = QOrganizerManager::NoError; - QOrganizerCollection defaultCollection; - defaultCollection.setId(QOrganizerCollectionId()); - QList<QOrganizerCollection> retn; - - if (collectionIds.contains(QOrganizerCollectionLocalId(0))) - retn << defaultCollection; - - return retn; -} - -bool QOrganizerItemMaemo6Engine::saveCollection(QOrganizerCollection* collection, QOrganizerManager::Error* error) -{ - Q_UNUSED(collection) - *error = QOrganizerManager::NotSupportedError; - return false; -} - -bool QOrganizerItemMaemo6Engine::removeCollection(const QOrganizerCollectionLocalId& collectionId, QOrganizerManager::Error* error) -{ - Q_UNUSED(collectionId) - *error = QOrganizerManager::NotSupportedError; - return false; -} - -bool QOrganizerItemMaemo6Engine::startRequest(QOrganizerAbstractRequest* req) -{ - /* - TODO - - This is the entry point to the async API. The request object describes the - type of request (switch on req->type()). Req will not be null when called - by the framework. - - Generally, you can queue the request and process them at some later time - (probably in another thread). - - Once you start a request, call the updateRequestState and/or the - specific updateXXXXXRequest functions to mark it in the active state. - - If your engine is particularly fast, or the operation involves only in - memory data, you can process and complete the request here. That is - probably not the case, though. - - Note that when the client is threaded, and the request might live on a - different thread, you might need to be careful with locking. In particular, - the request might be deleted while you are still working on it. In this case, - your requestDestroyed function will be called while the request is still valid, - and you should block in that function until your worker thread (etc) has been - notified not to touch that request any more. - - We plan to provide some boiler plate code that will allow you to: - - 1) implement the sync functions, and have the async versions call the sync - in another thread - - 2) or implement the async versions of the function, and have the sync versions - call the async versions. - - It's not ready yet, though. - - Return true if the request can be started, false otherwise. You can set an error - in the request if you like. - */ - return QOrganizerManagerEngine::startRequest(req); -} - -bool QOrganizerItemMaemo6Engine::cancelRequest(QOrganizerAbstractRequest* req) -{ - /* - TODO - - Cancel an in progress async request. If not possible, return false from here. - */ - return QOrganizerManagerEngine::cancelRequest(req); -} - -bool QOrganizerItemMaemo6Engine::waitForRequestFinished(QOrganizerAbstractRequest* req, int msecs) -{ - /* - TODO - - Wait for a request to complete (up to a max of msecs milliseconds). - - Return true if the request is finished (including if it was already). False otherwise. - - You should really implement this function, if nothing else than as a delay, since clients - may call this in a loop. - - It's best to avoid processing events, if you can, or at least only process non-UI events. - */ - return QOrganizerManagerEngine::waitForRequestFinished(req, msecs); -} - -void QOrganizerItemMaemo6Engine::requestDestroyed(QOrganizerAbstractRequest* req) -{ - /* - TODO - - This is called when a request is being deleted. It lets you know: - - 1) the client doesn't care about the request any more. You can still complete it if - you feel like it. - 2) you can't reliably access any properties of the request pointer any more. The pointer will - be invalid once this function returns. - - This means that if you have a worker thread, you need to let that thread know that the - request object is not valid and block until that thread acknowledges it. One way to do this - is to have a QSet<QOIAR*> (or QMap<QOIAR, MyCustomRequestState>) that tracks active requests, and - insert into that set in startRequest, and remove in requestDestroyed (or when it finishes or is - cancelled). Protect that set/map with a mutex, and make sure you take the mutex in the worker - thread before calling any of the QOIAR::updateXXXXXXRequest functions. And be careful of lock - ordering problems :D - - */ - return QOrganizerManagerEngine::requestDestroyed(req); -} - -bool QOrganizerItemMaemo6Engine::hasFeature(QOrganizerManager::ManagerFeature feature, const QString& itemType) const -{ - // TODO - the answer to the question may depend on the type - Q_UNUSED(itemType); - switch(feature) { - case QOrganizerManager::MutableDefinitions: - // TODO If you support save/remove detail definition, return true - return false; - - case QOrganizerManager::Anonymous: - // TODO if this engine is anonymous (e.g. no other engine can share the data) return true - // (mostly for an in memory engine) - return false; - case QOrganizerManager::ChangeLogs: - // TODO if this engine supports filtering by last modified/created/removed timestamps, return true - return false; - } - return false; -} - -bool QOrganizerItemMaemo6Engine::isFilterSupported(const QOrganizerItemFilter& filter) const -{ - // TODO if you engine can natively support the filter, return true. Otherwise you should emulate support in the item{Ids} functions. - Q_UNUSED(filter); - return false; -} - -QList<int> QOrganizerItemMaemo6Engine::supportedDataTypes() const -{ - QList<int> ret; - // TODO - tweak which data types this engine understands - ret << QVariant::String; - ret << QVariant::Date; - ret << QVariant::DateTime; - ret << QVariant::Time; - - return ret; -} - -QStringList QOrganizerItemMaemo6Engine::supportedItemTypes() const -{ - // TODO - return which [predefined] types this engine supports - QStringList ret; - - ret << QOrganizerItemType::TypeEvent; - ret << QOrganizerItemType::TypeEventOccurrence; - ret << QOrganizerItemType::TypeJournal; - ret << QOrganizerItemType::TypeNote; - ret << QOrganizerItemType::TypeTodo; - ret << QOrganizerItemType::TypeTodoOccurrence; - - return ret; -} - -QMap<QString, QMap<QString, QOrganizerItemDetailDefinition> > QOrganizerItemMaemo6Engine::schemaDefinitions() const { - // lazy initialisation of schema definitions. - if (d->m_definitions.isEmpty()) { - // Loop through default schema definitions - QMap<QString, QMap<QString, QOrganizerItemDetailDefinition> > schema - = QOrganizerManagerEngine::schemaDefinitions(); - foreach (const QString& itemType, schema.keys()) { - // Only add the item types that we support - if (itemType == QOrganizerItemType::TypeEvent || - itemType == QOrganizerItemType::TypeEventOccurrence || - itemType == QOrganizerItemType::TypeTodo || - itemType == QOrganizerItemType::TypeTodoOccurrence || - itemType == QOrganizerItemType::TypeJournal || - itemType == QOrganizerItemType::TypeNote) { - QMap<QString, QOrganizerItemDetailDefinition> definitions - = schema.value(itemType); - - QMap<QString, QOrganizerItemDetailDefinition> supportedDefinitions; - - QMapIterator<QString, QOrganizerItemDetailDefinition> it(definitions); - while (it.hasNext()) { - it.next(); - // Only add the definitions that we support - if (it.key() == QOrganizerItemType::DefinitionName || - it.key() == QOrganizerItemDescription::DefinitionName || - it.key() == QOrganizerItemDisplayLabel::DefinitionName || - it.key() == QOrganizerItemRecurrence::DefinitionName || - it.key() == QOrganizerEventTime::DefinitionName || - it.key() == QOrganizerItemGuid::DefinitionName || - it.key() == QOrganizerItemInstanceOrigin::DefinitionName) { - supportedDefinitions.insert(it.key(), it.value()); - } - } - d->m_definitions.insert(itemType, supportedDefinitions); - } - } - } - return d->m_definitions; -} - -Incidence* QOrganizerItemMaemo6Engine::incidence(const QOrganizerItemLocalId& itemId) const -{ - QString kId = static_cast<Maemo6ItemLocalId*>(QOrganizerManagerEngine::engineLocalItemId(itemId))->toString(); - return d->m_calendarBackend.incidence(kId); -} - -/*! - * Saves \a item to the manager, but doesn't persist the change to disk. - * Sets \a error appropriately if if couldn't be saved. - */ -Incidence* QOrganizerItemMaemo6Engine::softSaveItem(QOrganizerItem* item, QOrganizerManager::Error* error) -{ - bool itemIsNew = (managerUri() != item->id().managerUri() - || item->localId().isNull()); - bool itemIsOccurrence = (item->type() == QOrganizerItemType::TypeEventOccurrence) || - (item->type() == QOrganizerItemType::TypeTodoOccurrence); - Incidence* newIncidence = 0; - - // valid iff itemIsOccurrence (hack!) - QOrganizerItemLocalId parentLocalId; - QDate originalDate; - - if (item->type() == QOrganizerItemType::TypeEvent) { - QOrganizerEvent* event = static_cast<QOrganizerEvent*>(item); - newIncidence = createKEvent(*event); - } else if (item->type() == QOrganizerItemType::TypeTodo) { - QOrganizerTodo* todo = static_cast<QOrganizerTodo*>(item); - newIncidence = createKTodo(*todo); - } else if (item->type() == QOrganizerItemType::TypeEventOccurrence) { - QOrganizerEvent* event = static_cast<QOrganizerEvent*>(item); - newIncidence = createKEvent(*event); - QOrganizerEventOccurrence* eventOccurrence = static_cast<QOrganizerEventOccurrence*>(item); - parentLocalId = eventOccurrence->parentLocalId(); - originalDate = eventOccurrence->originalDate(); - } else if (item->type() == QOrganizerItemType::TypeTodoOccurrence) { - QOrganizerTodo* todo = static_cast<QOrganizerTodo*>(item); - newIncidence = createKTodo(*todo); - QOrganizerTodoOccurrence* todoOccurrence = static_cast<QOrganizerTodoOccurrence*>(item); - parentLocalId = todoOccurrence->parentLocalId(); - originalDate = todoOccurrence->originalDate(); - } else if (item->type() == QOrganizerItemType::TypeNote) { - QOrganizerNote* note = static_cast<QOrganizerNote*>(item); - newIncidence = createKNote(*note); - } else if (item->type() == QOrganizerItemType::TypeJournal) { - QOrganizerJournal* journal = static_cast<QOrganizerJournal*>(item); - newIncidence = createKJournal(*journal); - } else { - *error = QOrganizerManager::InvalidItemTypeError; - return 0; - } - if (itemIsNew) { - if (itemIsOccurrence) { - Incidence* parentIncidence = incidence(parentLocalId); - if (!parentIncidence) { - *error = QOrganizerManager::InvalidOccurrenceError; - return 0; - } - Incidence* detachedIncidence = d->m_calendarBackend.dissociateOccurrence( - parentIncidence, originalDate, KDateTime::LocalZone, true); - *detachedIncidence = *newIncidence; - newIncidence = detachedIncidence; - } else { - // nothing needs to be done - } - } else { - if (itemIsOccurrence) { - // TODO - } else { - Incidence* oldIncidence = incidence(item->localId()); - if (!oldIncidence) { - *error = QOrganizerManager::DoesNotExistError; - return 0; - } - QString uid = oldIncidence->uid(); - // is this right? shouldn't we modify oldIncidence inplace rather than delete/add? - d->m_calendarBackend.deleteIncidence(oldIncidence); - newIncidence->setUid(uid); - } - } - d->m_calendarBackend.addIncidence(newIncidence); - *error = QOrganizerManager::NoError; - return newIncidence; -} - -/*! - * Converts \a qEvent into an Incidence which is of subclass Event. The caller is responsible - * for deleting the object. - */ -Event* QOrganizerItemMaemo6Engine::createKEvent(const QOrganizerEvent& qEvent) -{ - Event* kEvent = new Event; - convertCommonDetailsToIncidenceFields(qEvent, kEvent); - kEvent->setDtStart(KDateTime(qEvent.startDateTime())); - kEvent->setDtEnd(KDateTime(qEvent.endDateTime())); - convertQRecurrenceToKRecurrence(qEvent.detail<QOrganizerItemRecurrence>(), kEvent->recurrence()); - return kEvent; -} - -/*! - * Converts \a qTodo into an Incidence which is of subclass Todo. The caller is responsible - * for deleting the object. - */ -Todo* QOrganizerItemMaemo6Engine::createKTodo(const QOrganizerTodo& qTodo) -{ - Todo* kTodo = new Todo; - convertCommonDetailsToIncidenceFields(qTodo, kTodo); - kTodo->setDtStart(KDateTime(qTodo.startDateTime())); - kTodo->setDtDue(KDateTime(qTodo.dueDateTime())); - convertQRecurrenceToKRecurrence(qTodo.detail<QOrganizerItemRecurrence>(), kTodo->recurrence()); - return kTodo; -} - -/*! - * Converts \a qJournal into an Incidence which is of subclass Journal. The caller is responsible - * for deleting the object. - */ -Journal* QOrganizerItemMaemo6Engine::createKJournal(const QOrganizerJournal& qJournal) -{ - Journal* kJournal = new Journal; - convertCommonDetailsToIncidenceFields(qJournal, kJournal); - return kJournal; -} - -/*! - * Converts \a qNote into an Incidence which is of subclass Journal. The caller is responsible - * for deleting the object. - */ -Journal* QOrganizerItemMaemo6Engine::createKNote(const QOrganizerNote& qNote) -{ - Journal* kJournal = new Journal; - convertCommonDetailsToIncidenceFields(qNote, kJournal); - return kJournal; -} - -/*! - * Converts the item-common details of \a item to fields to set in \a incidence. - */ -void QOrganizerItemMaemo6Engine::convertCommonDetailsToIncidenceFields( - const QOrganizerItem& item, Incidence* incidence) -{ - incidence->setDescription(item.description()); - incidence->setSummary(item.displayLabel()); -} - -/*! Converts \a qRecurrence into the libkcal equivalent, stored in \a kRecurrence. kRecurrence must - * point to an initialized Recurrence. - */ -void QOrganizerItemMaemo6Engine::convertQRecurrenceToKRecurrence( - const QOrganizerItemRecurrence& qRecurrence, - Recurrence* kRecurrence) -{ - // Remove all recurrence rules in kRecurrence - foreach (RecurrenceRule* rrule, kRecurrence->rRules()) { - kRecurrence->deleteRRule(rrule); - } - - foreach (const QOrganizerRecurrenceRule& rrule, qRecurrence.recurrenceRules()) { - RecurrenceRule* krrule = createKRecurrenceRule(kRecurrence, rrule); - kRecurrence->addRRule(krrule); - } -} - -RecurrenceRule* QOrganizerItemMaemo6Engine::createKRecurrenceRule( - Recurrence* kRecurrence, - const QOrganizerRecurrenceRule& qRRule) -{ - RecurrenceRule* kRRule = kRecurrence->defaultRRule(true); - switch (qRRule.frequency()) { - case QOrganizerRecurrenceRule::Daily: - kRRule->setRecurrenceType(RecurrenceRule::rDaily); - break; - case QOrganizerRecurrenceRule::Weekly: - kRRule->setRecurrenceType(RecurrenceRule::rWeekly); - break; - case QOrganizerRecurrenceRule::Monthly: - kRRule->setRecurrenceType(RecurrenceRule::rMonthly); - break; - case QOrganizerRecurrenceRule::Yearly: - kRRule->setRecurrenceType(RecurrenceRule::rYearly); - break; - } - kRRule->setFrequency(qRRule.interval()); - kRRule->setDuration(qRRule.count() > 1 ? qRRule.count() : -1); - - QList<RecurrenceRule::WDayPos> daysOfWeek; - foreach (Qt::DayOfWeek dayOfWeek, qRRule.daysOfWeek()) { - // 0 argument is setByPos (unimplemented for now) - daysOfWeek.append(RecurrenceRule::WDayPos(0, (short)dayOfWeek)); - } - kRRule->setByDays(daysOfWeek); - - kRRule->setByMonthDays(qRRule.daysOfMonth()); - - kRRule->setByYearDays(qRRule.daysOfYear()); - - kRRule->setByWeekNumbers(qRRule.weeksOfYear()); - - QList<int> months; - foreach (QOrganizerRecurrenceRule::Month month, qRRule.months()) { - months.append((int)month); - } - kRRule->setByMonths(months); - - QDate endDate = qRRule.endDate(); - if (endDate.isValid()) { - // The endDate is non-inclusive, as per iCalendar - kRRule->setEndDt(KDateTime(endDate.addDays(-1))); - } - return kRRule; -} - - -/*! \class IncidenceToItemConverter: - * - * This class converts a single kcal Incidence to a QOrganizerItem - */ - -/*! - * Converts a kcal \a incidence into a QOrganizer \a item. - * \a incidence and \a item must both not be null. - * Whatever was in \a item before will be ignored and lost. - */ -bool QOrganizerItemMaemo6Engine::IncidenceToItemConverter::convertIncidenceToItem( - Incidence* incidence, QOrganizerItem* item) -{ - if (incidence->accept(m_visitor)) { - *item = m_converted; - return true; - } else { - return false; - } -} - -/*! - * Don't call this directly unless you are an Incidence::AddVisitor. - * Converts a kcal Event. - */ -bool QOrganizerItemMaemo6Engine::IncidenceToItemConverter::addEvent(Event* e) -{ - m_converted = QOrganizerEvent(); - QOrganizerEvent* event = static_cast<QOrganizerEvent*>(&m_converted); - convertCommonDetails(e, event); - event->setStartDateTime(e->dtStart().dateTime()); - event->setEndDateTime(e->dtEnd().dateTime()); - return true; -} - -/*! - * Don't call this directly unless you are an Incidence::AddVisitor. - * Converts a kcal Todo. - */ -bool QOrganizerItemMaemo6Engine::IncidenceToItemConverter::addTodo(Todo* t) -{ - m_converted = QOrganizerTodo(); - convertCommonDetails(t, &m_converted); - return true; -} - -/*! - * Don't call this directly unless you are an Incidence::AddVisitor. - * Converts a kcal Journal. - */ -bool QOrganizerItemMaemo6Engine::IncidenceToItemConverter::addJournal(Journal* j) -{ - if (j->dtStart().isValid()) - m_converted = QOrganizerJournal(); - else - m_converted = QOrganizerNote(); - convertCommonDetails(j, &m_converted); - return true; -} - -/*! - * Adds details to \a item based on fields found in \a incidence. - */ -void QOrganizerItemMaemo6Engine::IncidenceToItemConverter::convertCommonDetails( - Incidence* incidence, QOrganizerItem* item) -{ - QOrganizerItemId id; - id.setManagerUri(m_managerUri); - id.setLocalId(QOrganizerItemLocalId(new Maemo6ItemLocalId(incidence->uid()))); - item->setId(id); - item->setDisplayLabel(incidence->summary()); - item->setDescription(incidence->description()); - item->setGuid(incidence->uid()); -} - diff --git a/plugins/organizer/maemo6/qorganizermaemo6.h b/plugins/organizer/maemo6/qorganizermaemo6.h deleted file mode 100644 index 894fe6b6cc..0000000000 --- a/plugins/organizer/maemo6/qorganizermaemo6.h +++ /dev/null @@ -1,203 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the Qt Mobility Components. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QORGANIZERMAEMO6_H -#define QORGANIZERMAEMO6_H - - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <QSharedData> -#include <QMap> -#include <QMultiMap> -#include <QList> -#include <QQueue> -#include <QPair> -#include <QSet> -#include <QDateTime> -#include <QString> -#include <QObject> - -#include <extendedcalendar.h> - -#include "qorganizeritem.h" -#include "qorganizermanager.h" -#include "qorganizermanagerengine.h" -#include "qorganizermanagerenginefactory.h" -#include "qorganizeritemdetaildefinition.h" -#include "qorganizerabstractrequest.h" -#include "qorganizeritemchangeset.h" - -QTM_BEGIN_NAMESPACE -class QOrganizerEvent; -class QOrganizerTodo; -class QOrganizerNote; -class QOrganizerJournal; -class QOrganizerItemRecurrence; -class QOrganizerRecurrenceRule; -QTM_END_NAMESPACE - -QTM_USE_NAMESPACE -using namespace KCal; - -class QOrganizerItemMaemo6Factory : public QObject, public QOrganizerManagerEngineFactory -{ - Q_OBJECT - Q_INTERFACES(QtMobility::QOrganizerManagerEngineFactory) - public: - QOrganizerManagerEngine* engine(const QMap<QString, QString>& parameters, QOrganizerManager::Error*); - QString managerName() const; - QOrganizerItemEngineLocalId* createItemEngineLocalId() const; - QOrganizerCollectionEngineLocalId* createCollectionEngineLocalId() const; -}; - -class QOrganizerItemMaemo6EngineData : public QSharedData -{ -public: - QOrganizerItemMaemo6EngineData() - : QSharedData(), - m_calendarBackend(KDateTime::Spec::LocalZone()) - { - } - - ~QOrganizerItemMaemo6EngineData() - { - } - - // map of organizeritem type to map of definition name to definitions: - mutable QMap<QString, QMap<QString, QOrganizerItemDetailDefinition> > m_definitions; - - ExtendedCalendar m_calendarBackend; -}; - -class QOrganizerItemMaemo6Engine : public QOrganizerManagerEngine -{ - Q_OBJECT - -public: - ~QOrganizerItemMaemo6Engine(); - - /* URI reporting */ - QString managerName() const; - QMap<QString, QString> managerParameters() const; - int managerVersion() const; - - QList<QOrganizerItem> itemInstances(const QOrganizerItem& generator, const QDateTime& periodStart, const QDateTime& periodEnd, int maxCount, QOrganizerManager::Error* error) const; - QList<QOrganizerItemLocalId> itemIds(const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, QOrganizerManager::Error* error) const; - QList<QOrganizerItem> items(const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const; - QOrganizerItem item(const QOrganizerItemLocalId& itemId, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const; - - bool saveItems(QList<QOrganizerItem>* items, const QOrganizerCollectionLocalId& collectionId, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error); - bool saveItem(QOrganizerItem* item, QOrganizerManager::Error* error); - bool removeItems(const QList<QOrganizerItemLocalId>& itemIds, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error); - - /* Definitions - Accessors and Mutators */ - QMap<QString, QOrganizerItemDetailDefinition> detailDefinitions(const QString& itemType, QOrganizerManager::Error* error) const; - QOrganizerItemDetailDefinition detailDefinition(const QString& definitionId, const QString& itemType, QOrganizerManager::Error* error) const; - bool saveDetailDefinition(const QOrganizerItemDetailDefinition& def, const QString& itemType, QOrganizerManager::Error* error); - bool removeDetailDefinition(const QString& definitionId, const QString& itemType, QOrganizerManager::Error* error); - - /* Collections - every item belongs to exactly one collection */ - QOrganizerCollectionLocalId defaultCollectionId(QOrganizerManager::Error* error) const; - QList<QOrganizerCollectionLocalId> collectionIds(QOrganizerManager::Error* error) const; - QList<QOrganizerCollection> collections(const QList<QOrganizerCollectionLocalId>& collectionIds, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error) const; - bool saveCollection(QOrganizerCollection* collection, QOrganizerManager::Error* error); - bool removeCollection(const QOrganizerCollectionLocalId& collectionId, QOrganizerManager::Error* error); - - /* Capabilities reporting */ - bool hasFeature(QOrganizerManager::ManagerFeature feature, const QString& itemType) const; - bool isFilterSupported(const QOrganizerItemFilter& filter) const; - QList<int> supportedDataTypes() const; - QStringList supportedItemTypes() const; - - /* Asynchronous Request Support */ - void requestDestroyed(QOrganizerAbstractRequest* req); - bool startRequest(QOrganizerAbstractRequest* req); - bool cancelRequest(QOrganizerAbstractRequest* req); - bool waitForRequestFinished(QOrganizerAbstractRequest* req, int msecs); - -private: - QOrganizerItemMaemo6Engine(); - QMap<QString, QMap<QString, QOrganizerItemDetailDefinition> > schemaDefinitions() const; - Incidence* incidence(const QOrganizerItemLocalId& itemId) const; - Incidence* softSaveItem(QOrganizerItem* item, QOrganizerManager::Error* error); - Event* createKEvent(const QOrganizerEvent& note); - Todo* createKTodo(const QOrganizerTodo& note); - Journal* createKJournal(const QOrganizerJournal& note); - Journal* createKNote(const QOrganizerNote& note); - void convertCommonDetailsToIncidenceFields(const QOrganizerItem& item, Incidence* incidence); - void convertQRecurrenceToKRecurrence(const QOrganizerItemRecurrence& qRecurrence, - Recurrence* kRecurrence); - RecurrenceRule* createKRecurrenceRule(Recurrence* kRecurrence, - const QOrganizerRecurrenceRule& rrule); - - QOrganizerItemMaemo6EngineData* d; - - friend class QOrganizerItemMaemo6Factory; - - class IncidenceToItemConverter { - public: - IncidenceToItemConverter(const QString managerUri) - : m_managerUri(managerUri), m_visitor(this) {} - bool convertIncidenceToItem(Incidence* incidence, QOrganizerItem* item); - bool addEvent(Event* e); - bool addTodo(Todo* t); - bool addJournal(Journal* j); - private: - void convertCommonDetails(Incidence* incidence, QOrganizerItem* item); - - QString m_managerUri; - QOrganizerItem m_converted; - Incidence::AddVisitor<IncidenceToItemConverter> m_visitor; - }; -}; - -#endif - diff --git a/plugins/organizer/maemo6/maemo6.pro b/plugins/organizer/mkcal/mkcal.pro index 5928c856ad..ddb7528572 100644 --- a/plugins/organizer/maemo6/maemo6.pro +++ b/plugins/organizer/mkcal/mkcal.pro @@ -1,13 +1,13 @@ TEMPLATE = lib CONFIG += plugin -TARGET = $$qtLibraryTarget(qtorganizer_maemo6) +TARGET = $$qtLibraryTarget(qtorganizer_mkcal) PLUGIN_TYPE=organizer CONFIG += mobility MOBILITY = organizer CONFIG += link_pkgconfig -PKGCONFIG += libextendedkcal +PKGCONFIG += libmkcal include(../../../common.pri) @@ -18,11 +18,10 @@ INCLUDEPATH += ../../../src/organizer \ ../../../src/organizer/details HEADERS += \ - qorganizermaemo6.h \ - maemo6itemlocalid.h + mkcalengine.h \ + qorganizerasynchmanager.h \ + mkcalid.h SOURCES += \ - qorganizermaemo6.cpp + mkcalengine.cpp \ + qorganizerasynchmanager.cpp -# Once we know what the pkgconfig deps are -# CONFIG += link_pkgconfig -PKGCONFIG += diff --git a/plugins/organizer/mkcal/mkcalengine.cpp b/plugins/organizer/mkcal/mkcalengine.cpp new file mode 100644 index 0000000000..c49f902ced --- /dev/null +++ b/plugins/organizer/mkcal/mkcalengine.cpp @@ -0,0 +1,1385 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mkcalengine.h" +#include "qtorganizer.h" +#include "mkcalid.h" +#include "qorganizerasynchmanager.h" + +Q_DEFINE_LATIN1_CONSTANT(NotebookSyncDate, "SyncDate"); +Q_DEFINE_LATIN1_CONSTANT(NotebookModifiedDate, "ModifiedDate"); +Q_DEFINE_LATIN1_CONSTANT(NotebookPluginName, "PluginName"); +Q_DEFINE_LATIN1_CONSTANT(NotebookAccount, "Account"); +Q_DEFINE_LATIN1_CONSTANT(NotebookSharedWith, "SharedWith"); +Q_DEFINE_LATIN1_CONSTANT(NotebookSharedWithStr, "SharedWithStr"); +Q_DEFINE_LATIN1_CONSTANT(NotebookSyncProfile, "SyncProfile"); +Q_DEFINE_LATIN1_CONSTANT(NotebookAttachmentSize, "AttachmentSize"); +Q_DEFINE_LATIN1_CONSTANT(NotebookIsDefault, "IsDefault"); +Q_DEFINE_LATIN1_CONSTANT(NotebookIsShareable, "IsShareable"); +Q_DEFINE_LATIN1_CONSTANT(NotebookIsShared, "IsShared"); +Q_DEFINE_LATIN1_CONSTANT(NotebookIsMaster, "IsMaster"); +Q_DEFINE_LATIN1_CONSTANT(NotebookIsOviSync, "IsOviSync"); +Q_DEFINE_LATIN1_CONSTANT(NotebookIsReadOnly, "IsReadOnly"); +Q_DEFINE_LATIN1_CONSTANT(NotebookIsVisible, "IsVisible"); +Q_DEFINE_LATIN1_CONSTANT(NotebookIsRunTimeOnly, "IsRunTimeOnly"); +Q_DEFINE_LATIN1_CONSTANT(NotebookEventsAllowed, "EventsAllowed"); +Q_DEFINE_LATIN1_CONSTANT(NotebookJournalsAllowed, "JournalsAllowed"); +Q_DEFINE_LATIN1_CONSTANT(NotebookTodosAllowed, "TodosAllowed"); + +//QTM_USE_NAMESPACE + +QOrganizerManagerEngine* MKCalEngineFactory::engine(const QMap<QString, QString>& parameters, QOrganizerManager::Error* error) +{ + Q_UNUSED(parameters); + + *error = QOrganizerManager::NoError; + QString managerUri = QOrganizerManager::buildUri("mkcal", QMap<QString, QString>()); + MKCalEngine* ret = new MKCalEngine(managerUri); // manager takes ownership and will clean up. + + return ret; +} + +QString MKCalEngineFactory::managerName() const +{ + return QLatin1String("mkcal"); +} + +QOrganizerItemEngineId* MKCalEngineFactory::createItemEngineId(const QMap<QString, QString>& parameters, const QString& engineIdString) const +{ + Q_UNUSED(parameters); + int col1 = engineIdString.indexOf(QLatin1String(":"), 0); + int col2 = engineIdString.indexOf(QLatin1String(":"), col1+1); + if (col1 < 0 || col2 < 0) + return NULL; + KDateTime rid; + QString tmp = engineIdString.mid(col1+1, col2-col1-1); + if (!tmp.isEmpty()) + rid.setTime_t(tmp.toLongLong()); + //ignore the managerUri parameter + MKCalItemId* retn = new MKCalItemId(engineIdString.mid(0, col1), rid); + + return retn; +} + +QOrganizerCollectionEngineId* MKCalEngineFactory::createCollectionEngineId(const QMap<QString, QString>& parameters, const QString& engineIdString) const +{ + Q_UNUSED(parameters); + int col1 = engineIdString.indexOf(QLatin1String(":"), 0); + if (col1 < 0) + return NULL; + MKCalCollectionId* retn = new MKCalCollectionId(engineIdString.mid(0, col1)); + + return retn; +} + +Q_EXPORT_PLUGIN2(qtorganizer_mkcal, MKCalEngineFactory); + + +MKCalEngine::MKCalEngine(const QString& managerUri) + : d(new MKCalEngineData) +{ + // set our manager uri. + d->m_managerUri = managerUri; + + // ensure we build up our hashes of collections. + storageModified(d->m_storagePtr.data(), QString()); + + // register ourselves as an observer of any changes to the db. + d->m_storagePtr->registerObserver(this); + + // and start our asynchronous request helper + d->m_asynchProcess = new OrganizerAsynchManager(this); +} + +MKCalEngine::~MKCalEngine() +{ + delete d->m_asynchProcess; + d->m_storagePtr->unregisterObserver(this); +} + +QString MKCalEngine::managerName() const +{ + return QLatin1String("mkcal"); +} + +QMap<QString, QString> MKCalEngine::managerParameters() const +{ + return QMap<QString, QString>(); +} + +int MKCalEngine::managerVersion() const +{ + return 1; +} + +QList<QOrganizerItem> MKCalEngine::itemOccurrences(const QOrganizerItem& parentItem, const QDateTime& periodStart, const QDateTime& periodEnd, int maxCount, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const +{ + return internalItemOccurrences(parentItem, periodStart, periodEnd, maxCount, fetchHint, error, true); +} + +QList<QOrganizerItem> MKCalEngine::internalItemOccurrences(const QOrganizerItem& parentItem, const QDateTime& periodStart, const QDateTime& periodEnd, int maxCount, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error, bool includeInstances) const +{ + Q_UNUSED(fetchHint); // XXX TODO: why do we not use this? + + //for the moment we support generating of 1000 occurrences. 1000 is a random value + if (maxCount < 0) + maxCount = 1000; + + QOrganizerItemId generatorId = parentItem.id(); + QString kId = MKCalItemId::id_cast(generatorId)->id(); + KCalCore::Incidence::Ptr generatorIncidence; + if (kId.isEmpty() || !(generatorIncidence = d->m_calendarBackendPtr->incidence(kId))) { + *error = QOrganizerManager::DoesNotExistError; + return QList<QOrganizerItem>(); + } + + //TODO: Until QOrganizerItem will include a startDt, we will store a QPair of QDateTime and QOrganizerItem for faster mergeSort + QList<QPair<QDateTime, QOrganizerItem> > persistentExceptions; + QList<QPair<QDateTime, QOrganizerItem> > instances; + QList<QOrganizerItem> res; + + //if includeInstances is true, then first add to the list all persistent exceptions + if (includeInstances && generatorIncidence->recurs()) { + if (generatorIncidence->type() == KCalCore::Incidence::TypeEvent) { + KCalCore::Event::List events(d->m_calendarBackendPtr->eventInstances(generatorIncidence, KCalCore::EventSortStartDate)); + foreach(const KCalCore::Event::Ptr& ev, events) { + if (persistentExceptions.count() > maxCount) + break; + QOrganizerItem instance; + if (convertIncidenceToItem(ev, &instance) && + QOrganizerManagerEngine::isItemBetweenDates(instance, periodStart, periodEnd)) + persistentExceptions.append(QPair<QDateTime, QOrganizerItem>(ev->dtStart().dateTime(), instance)); + } + } else if (generatorIncidence->type() == KCalCore::Incidence::TypeTodo) { + KCalCore::Todo::List todos(d->m_calendarBackendPtr->todoInstances(generatorIncidence)); + foreach(const KCalCore::Todo::Ptr& t, todos) { + if (persistentExceptions.count() > maxCount) + break; + QOrganizerItem instance; + if (convertIncidenceToItem(t, &instance) && + QOrganizerManagerEngine::isItemBetweenDates(instance, periodStart, periodEnd)) + persistentExceptions.append(QPair<QDateTime, QOrganizerItem>(t->dtStart().dateTime(), instance)); + } + } + } + + KCalCore::Incidence::List generatorList; + generatorList.append(generatorIncidence); + mKCal::ExtendedCalendar::ExpandedIncidenceList incidenceList = d->m_calendarBackendPtr->expandRecurrences( + &generatorList, + KDateTime(periodStart), + KDateTime(periodEnd), + maxCount); + KCalCore::Recurrence *recurrence = generatorIncidence->recurrence(); + foreach (const mKCal::ExtendedCalendar::ExpandedIncidence& expandedIncidence, incidenceList) { + QDateTime incidenceDateTime = expandedIncidence.first; + KCalCore::Incidence::Ptr incidence = expandedIncidence.second; + QOrganizerItem instance; + if (!recurrence->exDates().containsSorted(incidenceDateTime.date()) && convertIncidenceToItem(incidence, &instance)) { + QDateTime startDT; + if (instance.type() == QOrganizerItemType::TypeEvent) { + QOrganizerEventOccurrence* event = static_cast<QOrganizerEventOccurrence*>(&instance); + event->setType(QOrganizerItemType::TypeEventOccurrence); + startDT = event->startDateTime(); + int duration = startDT.secsTo(event->endDateTime()); + startDT = QDateTime(incidenceDateTime.date(), startDT.time()); + event->setStartDateTime(startDT); + event->setEndDateTime(startDT.addSecs(duration)); + event->setOriginalDate(incidenceDateTime.date()); + event->setParentId(generatorId); + foreach (QOrganizerItemDetail recurrence, event->details<QOrganizerItemRecurrence>()) { + event->removeDetail(&recurrence); + } + } else if (instance.type() == QOrganizerItemType::TypeTodo) { + QOrganizerTodoOccurrence* todo = static_cast<QOrganizerTodoOccurrence*>(&instance); + todo->setType(QOrganizerItemType::TypeTodoOccurrence); + startDT = todo->startDateTime(); + int duration = todo->dueDateTime().isNull() ? -1 : startDT.secsTo(todo->dueDateTime()); + startDT = QDateTime(incidenceDateTime.date(), startDT.time()); + todo->setStartDateTime(startDT); + todo->setDueDateTime(duration >= 0 ? startDT.addSecs(duration) : QDateTime()); + todo->setOriginalDate(incidenceDateTime.date()); + todo->setParentId(generatorId); + foreach (QOrganizerItemDetail recurrence, todo->details<QOrganizerItemRecurrence>()) { + todo->removeDetail(&recurrence); + } + } + + // if the incidence is the generating event itself then clear its id + if (incidence == generatorIncidence) { + instance.setId(QOrganizerItemId()); + } + + //if the result needs to include the permanent exceptions then the instance need to be inserted + //into a temporal list to be able to merge sort in the end + if (includeInstances) + instances.append(QPair<QDateTime, QOrganizerItem>(startDT, instance)); + else + res.append(instance); + } + } + + if (includeInstances) { + res.reserve(qMin(maxCount, instances.count() + persistentExceptions.count())); + + //mergeSort the results based on the start time + QList<QPair<QDateTime, QOrganizerItem> >::const_iterator itExceptions = persistentExceptions.constBegin(); + QList<QPair<QDateTime, QOrganizerItem> >::const_iterator itInstances = instances.constBegin(); + while (itExceptions != persistentExceptions.constEnd() && itInstances != instances.constEnd() && res.count() < maxCount) { + if (itExceptions->first < itInstances->first) { + res.append(itExceptions->second); + ++itExceptions; + } else { + res.append(itInstances->second); + ++itInstances; + } + } + for (; itExceptions != persistentExceptions.constEnd() && res.count() < maxCount; ++itExceptions) { + res.append(itExceptions->second); + } + for (; itInstances != instances.constEnd() && res.count() < maxCount; ++itInstances) { + res.append(itInstances->second); + } + } + + *error = QOrganizerManager::NoError; + return res; +} + +QList<QOrganizerItemId> MKCalEngine::itemIds(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, QOrganizerManager::Error* error) const +{ + //small optimization for the default case when startDate, endDate, filter, sortOrders are the default values + if (startDate.isNull() && endDate.isNull() && filter == QOrganizerItemFilter() && sortOrders.count() == 0) { + QList<QOrganizerItemId> ids; + KCalCore::Incidence::List rawIncidences = d->m_calendarBackendPtr->rawIncidences(); + foreach(const KCalCore::Incidence::Ptr& i, rawIncidences) { + ids.append(QOrganizerItemId(new MKCalItemId(i->uid(), + i->hasRecurrenceId() ? i->recurrenceId() : KDateTime()))); + } + + return ids; + } + + QList<QOrganizerItem> ret = itemsForExport(startDate, endDate, filter, sortOrders, QOrganizerItemFetchHint(), error); + + return QOrganizerManager::extractIds(ret); +} + +QList<QOrganizerItem> MKCalEngine::itemsForExport(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const +{ + return internalItems(startDate, endDate, filter, sortOrders, fetchHint, error, false); +} + +QList<QOrganizerItem> MKCalEngine::items(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const +{ + return internalItems(startDate, endDate, filter, sortOrders, fetchHint, error, true); +} + +QList<QOrganizerItem> MKCalEngine::internalItems(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error, bool expand) const +{ + Q_UNUSED(fetchHint); + Q_UNUSED(error); + // TODO: optimise by using our own filters + + // Just naively get all the incidences between the given startDate and endDate + d->m_calendarBackendPtr->setFilter(0); + KCalCore::Incidence::List incidences = d->m_calendarBackendPtr->incidences(startDate.date(), endDate.date()); + QList<QOrganizerItem> partiallyFilteredItems; + QList<QOrganizerItem> ret; + + // Convert them all to QOrganizerItems + foreach (KCalCore::Incidence::Ptr incidence, incidences) { + QOrganizerItem item; + if (convertIncidenceToItem(incidence, &item)) { + if (incidence->recurs()) { + if (expand) { + partiallyFilteredItems << internalItemOccurrences(item, startDate, endDate, 100, fetchHint, error, false); + } else { + if (itemHasRecurringChild(incidence, startDate, endDate, filter)) + QOrganizerManagerEngine::addSorted(&ret, item, sortOrders); + } + } else { + partiallyFilteredItems << item; + } + } + } + + // Now filter them + foreach(const QOrganizerItem& item, partiallyFilteredItems) { + if (QOrganizerManagerEngine::testFilter(filter, item) && + QOrganizerManagerEngine::isItemBetweenDates(item, startDate, endDate)) { + QOrganizerManagerEngine::addSorted(&ret, item, sortOrders); + } + } + + return ret; +} + + +bool MKCalEngine::itemHasRecurringChild(KCalCore::Incidence::Ptr incidence, QDateTime startDate, QDateTime endDate, QOrganizerItemFilter filter) const +{ + //TODO: FIXME: check for filter + Q_UNUSED(filter); + + // if interval is null always return true + if (startDate.isNull() && endDate.isNull()) + return true; + + // if start interval is null, check if the incidence start date is before the interval end date + if (startDate.isNull()) + return incidence->dtStart().dateTime() <= endDate; + + int duration = 0; + if (incidence->type() == KCalCore::Incidence::TypeEvent) { + KCalCore::Event::Ptr ev = incidence.staticCast<KCalCore::Event>(); + if (!ev->dtEnd().isNull()) + duration = ev->dtStart().secsTo(ev->dtEnd()); + } else if (incidence->type() == KCalCore::Incidence::TypeTodo) { + //TODO: Is this correct? Todos have duration? + KCalCore::Todo::Ptr todo = incidence.staticCast<KCalCore::Todo>(); + if (!todo->dtDue().isNull()) + duration = todo->dtStart().secsTo(todo->dtDue()); + } + + QDateTime next(incidence->recurrence()->getNextDateTime(KDateTime(startDate)).dateTime()); + //fail if there are no recurrences after the start date + if (next.isNull()) + return false; + + //if end date is empty, check if the calculated recurrence + duration of the event is after the start date + if (endDate.isNull()) + return next.addSecs(duration) >= startDate; + + //in last case just check if a recurrence is before the interval end + return next <= endDate; +} + +QOrganizerItem MKCalEngine::item(const QOrganizerItemId& itemId, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const +{ + Q_UNUSED(fetchHint); + KCalCore::Incidence::Ptr theIncidence = incidence(itemId); + if (!theIncidence) { + *error = QOrganizerManager::DoesNotExistError; + return QOrganizerItem(); + } + QOrganizerItem item; + if (convertIncidenceToItem(theIncidence, &item)) { + return item; + } else { + *error = QOrganizerManager::DoesNotExistError; + return QOrganizerItem(); + } +} + +bool MKCalEngine::saveItems(QList<QOrganizerItem>* items, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error) +{ + if (!items) { + *error = QOrganizerManager::BadArgumentError; + return false; + } + + QOrganizerItemChangeSet ics; + QOrganizerManager::Error tempError = QOrganizerManager::NoError; + *error = QOrganizerManager::NoError; + for (int i = 0; i < items->size(); i++) { + QOrganizerItem item = items->at(i); + if (internalSaveItem(&ics, &item, &tempError)) { + items->replace(i, item); + } else { + *error = tempError; + errorMap->insert(i, tempError); + } + } + + d->m_storagePtr->save(); // commit all changes to the database. + ics.emitSignals(this); + return *error == QOrganizerManager::NoError; +} + +bool MKCalEngine::saveItem(QOrganizerItem* item, QOrganizerManager::Error* error) +{ + QOrganizerItemChangeSet ics; + bool retn = internalSaveItem(&ics, item, error); + if (retn) { + d->m_storagePtr->save(); // commit all changes to the database. + ics.emitSignals(this); + } + + return retn; +} + +bool MKCalEngine::internalSaveItem(QOrganizerItemChangeSet* ics, QOrganizerItem* item, QOrganizerManager::Error* error) +{ + // ensure that the organizeritem's details conform to their definitions + if (!validateItem(*item, error)) { + return false; + } + + KCalCore::Incidence::Ptr theIncidence = softSaveItem(ics, item, error); + return (!theIncidence.isNull()); +} + +bool MKCalEngine::removeItems(const QList<QOrganizerItemId>& itemIds, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error) +{ + QOrganizerItemChangeSet ics; + *error = QOrganizerManager::NoError; + for (int i = 0; i < itemIds.size(); i++) { + QOrganizerItemId id = itemIds[i]; + KCalCore::Incidence::Ptr theIncidence = incidence(id); + if (!theIncidence) { + *error = QOrganizerManager::DoesNotExistError; + errorMap->insert(i, QOrganizerManager::DoesNotExistError); + continue; + } + if (theIncidence->recurs()) { + if (theIncidence->type() == KCalCore::IncidenceBase::TypeEvent) { + if (!d->m_calendarBackendPtr->deleteEventInstances( + theIncidence.staticCast<KCalCore::Event>())) { + *error = QOrganizerManager::UnspecifiedError; + errorMap->insert(i, QOrganizerManager::UnspecifiedError); + } + } else if (theIncidence->type() == KCalCore::IncidenceBase::TypeTodo) { + if (!d->m_calendarBackendPtr->deleteTodoInstances( + theIncidence.staticCast<KCalCore::Todo>())) { + *error = QOrganizerManager::UnspecifiedError; + errorMap->insert(i, QOrganizerManager::UnspecifiedError); + } + } + } + if (!d->m_calendarBackendPtr->deleteIncidence(theIncidence)) { + *error = QOrganizerManager::UnspecifiedError; + errorMap->insert(i, QOrganizerManager::UnspecifiedError); + } else { + ics.insertRemovedItem(id); + } + } + + d->m_storagePtr->save(); + ics.emitSignals(this); + return *error == QOrganizerManager::NoError; +} + +QMap<QString, QOrganizerItemDetailDefinition> MKCalEngine::detailDefinitions(const QString& itemType, QOrganizerManager::Error* error) const +{ + *error = QOrganizerManager::NoError; + return schemaDefinitions().value(itemType); +} + +QOrganizerItemDetailDefinition MKCalEngine::detailDefinition(const QString& definitionId, const QString& itemType, QOrganizerManager::Error* error) const +{ + /* TODO - the default implementation just calls the base detailDefinitions function. If that's inefficent, implement this */ + return QOrganizerManagerEngine::detailDefinition(definitionId, itemType, error); +} + +bool MKCalEngine::saveDetailDefinition(const QOrganizerItemDetailDefinition& def, const QString& itemType, QOrganizerManager::Error* error) +{ + /* TODO - if you support adding custom fields, do that here. Otherwise call the base functionality. */ + return QOrganizerManagerEngine::saveDetailDefinition(def, itemType, error); +} + +bool MKCalEngine::removeDetailDefinition(const QString& definitionId, const QString& itemType, QOrganizerManager::Error* error) +{ + /* TODO - if you support removing custom fields, do that here. Otherwise call the base functionality. */ + return QOrganizerManagerEngine::removeDetailDefinition(definitionId, itemType, error); +} + +QOrganizerCollection MKCalEngine::defaultCollection(QOrganizerManager::Error* error) const +{ + *error = QOrganizerManager::NoError; + mKCal::Notebook::Ptr defaultNotebook = d->m_storagePtr->defaultNotebook(); + if (defaultNotebook) { + return convertNotebookToCollection(defaultNotebook); + } + + // no default collection; create one. + return convertNotebookToCollection(d->m_storagePtr->createDefaultNotebook("defaultNotebook")); +} + +QOrganizerCollection MKCalEngine::collection(const QOrganizerCollectionId& collectionId, QOrganizerManager::Error* error) const +{ + QString notebookUid = MKCalCollectionId::id_cast(collectionId)->uid(); + mKCal::Notebook::Ptr notebookPtr; + if (notebookUid.isEmpty() || !(notebookPtr = d->m_storagePtr->notebook(notebookUid))) { + *error = QOrganizerManager::DoesNotExistError; + return QOrganizerCollection(); + } + *error = QOrganizerManager::NoError; + return convertNotebookToCollection(notebookPtr); +} + +QList<QOrganizerCollection> MKCalEngine::collections(QOrganizerManager::Error* error) const +{ + QList<QOrganizerCollection> retn; + mKCal::Notebook::List allNotebooks(d->m_storagePtr->notebooks()); + foreach(mKCal::Notebook::Ptr currNotebook, allNotebooks) { + retn.append(convertNotebookToCollection(currNotebook)); + } + + *error = QOrganizerManager::NoError; + return retn; +} + +bool MKCalEngine::saveCollection(QOrganizerCollection* collection, QOrganizerManager::Error* error) +{ + *error = QOrganizerManager::NoError; + bool retn = false; + QOrganizerCollectionId colId = collection->id(); + + if (colId.isNull()) { + // new collection. + mKCal::Notebook::Ptr notebookPtr(new mKCal::Notebook); + convertCollectionToNotebook(*collection, notebookPtr); + retn = d->m_storagePtr->addNotebook(notebookPtr); + if (!retn) { + *error = QOrganizerManager::UnspecifiedError; + } else { + // update the collection with its id. + QOrganizerCollectionId newId(new MKCalCollectionId(notebookPtr->uid())); + collection->setId(newId); + } + + return retn; + } + + // retrieve the uid from the collection id + // to try and get a pre-existing notebook. + QString notebookUid = MKCalCollectionId::id_cast(colId)->uid(); + mKCal::Notebook::Ptr notebookPtr; + if (notebookUid.isEmpty() || !(notebookPtr = d->m_storagePtr->notebook(notebookUid))) { + // this notebook has been deleted (or never existed). + *error = QOrganizerManager::DoesNotExistError; + return false; + } + convertCollectionToNotebook(*collection, notebookPtr); + retn = d->m_storagePtr->updateNotebook(notebookPtr); + if (!retn) { + *error = QOrganizerManager::UnspecifiedError; + } + + return retn; +} + +bool MKCalEngine::removeCollection(const QOrganizerCollectionId& collectionId, QOrganizerManager::Error* error) +{ + // first, check to see if it's the default collection. + if (defaultCollection(error).id() == collectionId) { + *error = QOrganizerManager::PermissionsError; + return false; + } + + // otherwise, it's a potentially removable collection. + QString notebookUid = MKCalCollectionId::id_cast(collectionId)->uid(); + mKCal::Notebook::Ptr notebookPtr; + if (!notebookUid.isEmpty() && (notebookPtr = d->m_storagePtr->notebook(notebookUid))) { + if (!d->m_storagePtr->deleteNotebook(notebookPtr)) { + *error = QOrganizerManager::UnspecifiedError; + return false; + } else { + // success. + *error = QOrganizerManager::NoError; + d->m_storagePtr->save(); // commit all changes to the database. + return true; + } + } + + *error = QOrganizerManager::DoesNotExistError; + return false; +} + +bool MKCalEngine::startRequest(QOrganizerAbstractRequest* req) +{ + d->m_asynchProcess->addRequest(req); + return true; +} + +bool MKCalEngine::cancelRequest(QOrganizerAbstractRequest* req) +{ + return d->m_asynchProcess->cancelRequest(req); +} + +bool MKCalEngine::waitForRequestFinished(QOrganizerAbstractRequest* req, int msecs) +{ + return d->m_asynchProcess->waitForRequestFinished(req, msecs); +} + +void MKCalEngine::requestDestroyed(QOrganizerAbstractRequest* req) +{ + return d->m_asynchProcess->requestDestroyed(req); +} + +bool MKCalEngine::hasFeature(QOrganizerManager::ManagerFeature feature, const QString& itemType) const +{ + // TODO - the answer to the question may depend on the type + Q_UNUSED(itemType); + switch(feature) { + case QOrganizerManager::MutableDefinitions: + // TODO If you support save/remove detail definition, return true + return false; + + case QOrganizerManager::Anonymous: + // TODO if this engine is anonymous (e.g. no other engine can share the data) return true + // (mostly for an in memory engine) + return false; + case QOrganizerManager::ChangeLogs: + // TODO if this engine supports filtering by last modified/created/removed timestamps, return true + return false; + } + return false; +} + +bool MKCalEngine::isFilterSupported(const QOrganizerItemFilter& filter) const +{ + // TODO if you engine can natively support the filter, return true. Otherwise you should emulate support in the item{Ids} functions. + Q_UNUSED(filter); + return false; +} + +QList<int> MKCalEngine::supportedDataTypes() const +{ + QList<int> ret; + // TODO - tweak which data types this engine understands + ret << QVariant::String; + ret << QVariant::Date; + ret << QVariant::DateTime; + ret << QVariant::Time; + + return ret; +} + +QStringList MKCalEngine::supportedItemTypes() const +{ + // TODO - return which [predefined] types this engine supports + QStringList ret; + + ret << QOrganizerItemType::TypeEvent; + ret << QOrganizerItemType::TypeEventOccurrence; + ret << QOrganizerItemType::TypeJournal; + ret << QOrganizerItemType::TypeNote; + ret << QOrganizerItemType::TypeTodo; + ret << QOrganizerItemType::TypeTodoOccurrence; + + return ret; +} + +QMap<QString, QMap<QString, QOrganizerItemDetailDefinition> > MKCalEngine::schemaDefinitions() const { + // lazy initialisation of schema definitions. + if (d->m_definitions.isEmpty()) { + // Loop through default schema definitions + QMap<QString, QMap<QString, QOrganizerItemDetailDefinition> > schema + = QOrganizerManagerEngine::schemaDefinitions(); + foreach (const QString& itemType, schema.keys()) { + // Only add the item types that we support + if (itemType == QOrganizerItemType::TypeEvent || + itemType == QOrganizerItemType::TypeEventOccurrence || + itemType == QOrganizerItemType::TypeTodo || + itemType == QOrganizerItemType::TypeTodoOccurrence || + itemType == QOrganizerItemType::TypeJournal || + itemType == QOrganizerItemType::TypeNote) { + QMap<QString, QOrganizerItemDetailDefinition> definitions + = schema.value(itemType); + + QMap<QString, QOrganizerItemDetailDefinition> supportedDefinitions; + + QMapIterator<QString, QOrganizerItemDetailDefinition> it(definitions); + while (it.hasNext()) { + it.next(); + // Only add the definitions that we support + if (it.key() == QOrganizerItemType::DefinitionName || + it.key() == QOrganizerItemDescription::DefinitionName || + it.key() == QOrganizerItemDisplayLabel::DefinitionName || + it.key() == QOrganizerItemRecurrence::DefinitionName || + it.key() == QOrganizerEventTime::DefinitionName || + it.key() == QOrganizerItemGuid::DefinitionName || + it.key() == QOrganizerItemParent::DefinitionName || + it.key() == QOrganizerTodoTime::DefinitionName || + it.key() == QOrganizerItemLocation::DefinitionName) { + supportedDefinitions.insert(it.key(), it.value()); + } + } + d->m_definitions.insert(itemType, supportedDefinitions); + } + } + } + return d->m_definitions; +} + +// observer for changes from mKCal. +void MKCalEngine::storageModified(mKCal::ExtendedStorage* storage, const QString& info) +{ + Q_UNUSED(info); + if (storage != d->m_storagePtr) { + qWarning("MKCalEngine::storageModified() received change notification from unknown storage!"); + return; + } + + // XXX The mKCal docs says to not use this function because it's too slow! + // Can we optimise by only loading items from the date range required, perhaps lazily? + d->m_storagePtr->load(); + + emit dataChanged(); +} + +// observer for changes from mKCal. +void MKCalEngine::storageProgress(mKCal::ExtendedStorage* storage, const QString& info) +{ + Q_UNUSED(storage); + Q_UNUSED(info); + // do nothing. +} + +// observer for changes from mKCal. +void MKCalEngine::storageFinished(mKCal::ExtendedStorage* storage, bool error, const QString& info) +{ + Q_UNUSED(storage); + Q_UNUSED(error); + Q_UNUSED(info); + // do nothing. +} + +KCalCore::Incidence::Ptr MKCalEngine::incidence(const QOrganizerItemId& itemId) const +{ + const MKCalItemId *id = MKCalItemId::id_cast(itemId); + return id->id().isEmpty() ? KCalCore::Incidence::Ptr() : d->m_calendarBackendPtr->incidence(id->id(), id->rid()); +} + +KCalCore::Incidence::Ptr MKCalEngine::detachedIncidenceFromItem(const QOrganizerItem& item) const +{ + QOrganizerItemParent parentDetail(item.detail<QOrganizerItemParent>()); + QOrganizerItemId parentId(parentDetail.parentId()); + QDate originalDate(parentDetail.originalDate()); + QString guid(item.guid()); + + KCalCore::Incidence::Ptr parentIncidence; + if (!parentId.isNull()) { + QString parentUid(MKCalItemId::id_cast(parentId)->id()); + if (!guid.isEmpty() && guid != parentUid) + return KCalCore::Incidence::Ptr(); + parentIncidence = incidence(parentId); + } else if (!guid.isEmpty()) { + parentIncidence = d->m_calendarBackendPtr->incidence(guid); + } + if (parentIncidence.isNull() || !originalDate.isValid()) + return KCalCore::Incidence::Ptr(); + return d->m_calendarBackendPtr->dissociateSingleOccurrence( + parentIncidence, KDateTime(originalDate), KDateTime::LocalZone); +} + +/*! + * Saves \a item to the manager, but doesn't persist the change to disk. + * Sets \a error appropriately if if couldn't be saved. + */ +KCalCore::Incidence::Ptr MKCalEngine::softSaveItem(QOrganizerItemChangeSet* ics, QOrganizerItem* item, QOrganizerManager::Error* error) +{ + bool itemIsNew = item->id().isNull() || d->m_managerUri != item->id().managerUri(); + bool itemIsOccurrence = (item->type() == QOrganizerItemType::TypeEventOccurrence) || + (item->type() == QOrganizerItemType::TypeTodoOccurrence); + KCalCore::Incidence::Ptr newIncidence(0); + + // extract the collection id and ensure that we save it in the correct notebook + QOrganizerCollectionId destinationCollectionId = item->collectionId(); + QString destinationNotebookUid; + + if (destinationCollectionId.isNull()) { + // save to default collection. + destinationNotebookUid = d->m_storagePtr->defaultNotebook()->uid(); + // note that we readjust the destinationNotebookUid if the item is an occurrence, so that it is always saved in the parent's notebook. + } else { + // save to the specified collection (if possible) + destinationNotebookUid = MKCalCollectionId::id_cast(destinationCollectionId)->uid(); + } + + // mkCal backend does not support setting of notebooks for item occurrences, because of this the item collection id either should be null + // or equal with it's parent collection id + if (itemIsOccurrence && !destinationCollectionId.isNull()) { + // find parent id + QOrganizerItemParent parentDetail(item->detail<QOrganizerItemParent>()); + QOrganizerItemId parentId(parentDetail.parentId()); + QString parentUid = parentId.isNull() ? item->guid() : MKCalItemId::id_cast(parentId)->id(); + + // if item collection id and parent id are not null, then item collection id must be equal with parent collection id + if (!destinationNotebookUid.isEmpty() && !parentUid.isEmpty() && d->m_calendarBackendPtr->notebook(parentUid) != destinationNotebookUid) { + *error = QOrganizerManager::InvalidCollectionError; + return KCalCore::Incidence::Ptr(); + } + } else if (!(d->m_calendarBackendPtr->hasValidNotebook(destinationNotebookUid))) { + // fail if destination notebook does not exist in the storage + *error = QOrganizerManager::InvalidCollectionError; + return KCalCore::Incidence::Ptr(); + } + + + // First, either create the incidence or get the correct existing one + if (itemIsNew) { + if (itemIsOccurrence) { + newIncidence = detachedIncidenceFromItem(*item); + if (newIncidence.isNull()) { + *error = QOrganizerManager::InvalidOccurrenceError; + return KCalCore::Incidence::Ptr(0); + } + } else { + if (item->type() == QOrganizerItemType::TypeEvent) { + newIncidence = KCalCore::Event::Ptr(new KCalCore::Event); + } else if (item->type() == QOrganizerItemType::TypeTodo) { + newIncidence = KCalCore::Todo::Ptr(new KCalCore::Todo); + } else if (item->type() == QOrganizerItemType::TypeNote + || item->type() == QOrganizerItemType::TypeJournal) { + newIncidence = KCalCore::Journal::Ptr(new KCalCore::Journal); + } else { + *error = QOrganizerManager::InvalidItemTypeError; + return KCalCore::Incidence::Ptr(0); + } + } + } else { + newIncidence = incidence(item->id()); + if (!newIncidence) { + *error = QOrganizerManager::DoesNotExistError; + return KCalCore::Incidence::Ptr(0); + } + } + + Q_ASSERT(!newIncidence.isNull()); + + // second, populate the incidence with the item's details + if (item->type() == QOrganizerItemType::TypeEvent) { + convertQEventToKEvent(*item, newIncidence, true); + } else if (item->type() == QOrganizerItemType::TypeEventOccurrence) { + convertQEventToKEvent(*item, newIncidence, false); + } else if (item->type() == QOrganizerItemType::TypeTodo) { + convertQTodoToKTodo(*item, newIncidence, true); + } else if (item->type() == QOrganizerItemType::TypeTodoOccurrence) { + convertQTodoToKTodo(*item, newIncidence, false); + } else if (item->type() == QOrganizerItemType::TypeNote) { + convertQNoteToKNote(*item, newIncidence); + } else if (item->type() == QOrganizerItemType::TypeJournal) { + convertQJournalToKJournal(*item, newIncidence); + } + + // third, add it if it is new + if (itemIsNew) { + d->m_calendarBackendPtr->addIncidence(newIncidence); + } // if it is not new, nothing needs to be done (incidences are live objects) + + bool success = itemIsOccurrence || d->m_calendarBackendPtr->setNotebook(newIncidence, destinationNotebookUid); + if (!success) { + // unable to save to the correct notebook. + *error = QOrganizerManager::InvalidCollectionError; + // XXX TODO: roll back the save from memory. + } else { + *error = QOrganizerManager::NoError; + } + + // set the id of the item. + QString kId = newIncidence->uid(); + item->setId(QOrganizerItemId(new MKCalItemId( + kId, + newIncidence->hasRecurrenceId() ? newIncidence->recurrenceId() : KDateTime()))); + item->setGuid(kId); + + // modify the changeset as required. + if (itemIsNew) { + ics->insertAddedItem(item->id()); + } else { + ics->insertChangedItem(item->id()); + } + + return newIncidence; +} + +/*! + * Converts \a qEvent into an Incidence which is of subclass Event. The caller is responsible + * for deleting the object. + */ +void MKCalEngine::convertQEventToKEvent(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence, bool recurs) +{ + KCalCore::Event::Ptr kEvent(incidence.staticCast<KCalCore::Event>()); + convertCommonDetailsToIncidenceFields(item, kEvent); + QOrganizerEventTime eventTime(item.detail<QOrganizerEventTime>()); + kEvent->setDtStart(KDateTime(eventTime.startDateTime())); + kEvent->setDtEnd(KDateTime(eventTime.endDateTime())); + if (recurs) + convertQRecurrenceToKRecurrence(item.detail<QOrganizerItemRecurrence>(), + eventTime.startDateTime().date(), + kEvent->recurrence()); +} + +/*! + * Converts \a qTodo into an Incidence which is of subclass Todo. The caller is responsible + * for deleting the object. + */ +void MKCalEngine::convertQTodoToKTodo(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence, bool recurs) +{ + KCalCore::Todo::Ptr kTodo(incidence.staticCast<KCalCore::Todo>()); + convertCommonDetailsToIncidenceFields(item, kTodo); + QOrganizerTodoTime todoTime(item.detail<QOrganizerTodoTime>()); + kTodo->setDtStart(KDateTime(todoTime.startDateTime())); + kTodo->setDtDue(KDateTime(todoTime.dueDateTime())); + if (recurs) + convertQRecurrenceToKRecurrence(item.detail<QOrganizerItemRecurrence>(), + todoTime.startDateTime().date(), + kTodo->recurrence()); +} + +/*! + * Converts \a qJournal into an Incidence which is of subclass Journal. The caller is responsible + * for deleting the object. + */ +void MKCalEngine::convertQJournalToKJournal(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence) +{ + KCalCore::Journal::Ptr kJournal(incidence.staticCast<KCalCore::Journal>()); + convertCommonDetailsToIncidenceFields(item, kJournal); +} + +/*! + * Converts \a qNote into an Incidence which is of subclass Journal. The caller is responsible + * for deleting the object. + */ +void MKCalEngine::convertQNoteToKNote(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence) +{ + KCalCore::Journal::Ptr kJournal(incidence.staticCast<KCalCore::Journal>()); + convertCommonDetailsToIncidenceFields(item, kJournal); +} + +/*! + * Converts the item-common details of \a item to fields to set in \a incidence. + */ +void MKCalEngine::convertCommonDetailsToIncidenceFields( + const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence) +{ + if (item.id().isNull() && !item.guid().isEmpty()) + incidence->setUid(item.guid()); + incidence->setDescription(item.description()); + incidence->setSummary(item.displayLabel()); + QOrganizerItemLocation loc = static_cast<QOrganizerItemLocation>(item.detail(QOrganizerItemLocation::DefinitionName)); + incidence->setLocation(loc.label()); +} + +/*! Converts \a qRecurrence into the libkcal equivalent, stored in \a kRecurrence. kRecurrence must + * point to an initialized Recurrence. + */ +void MKCalEngine::convertQRecurrenceToKRecurrence( + const QOrganizerItemRecurrence& qRecurrence, const QDate& startDate, + KCalCore::Recurrence* kRecurrence) +{ + kRecurrence->clear(); + + foreach (const QOrganizerRecurrenceRule& rrule, qRecurrence.recurrenceRules()) { + if (rrule.frequency() != QOrganizerRecurrenceRule::Invalid) { + KCalCore::RecurrenceRule* krrule = createKRecurrenceRule(kRecurrence, startDate, rrule); + kRecurrence->addRRule(krrule); + } + } + + foreach (const QOrganizerRecurrenceRule& exrule, qRecurrence.exceptionRules()) { + if (exrule.frequency() != QOrganizerRecurrenceRule::Invalid) { + KCalCore::RecurrenceRule* kexrule = createKRecurrenceRule(kRecurrence, startDate, exrule); + kRecurrence->addExRule(kexrule); + } + } + + foreach (const QDate& rdate, qRecurrence.recurrenceDates()) + kRecurrence->addRDate(rdate); + + foreach (const QDate& exdate, qRecurrence.exceptionDates()) + kRecurrence->addExDate(exdate); +} + +KCalCore::RecurrenceRule* MKCalEngine::createKRecurrenceRule( + KCalCore::Recurrence* kRecurrence, + const QDate& startDate, + const QOrganizerRecurrenceRule& qRRule) +{ + Q_UNUSED(kRecurrence); + KCalCore::RecurrenceRule* kRRule = new KCalCore::RecurrenceRule(); + switch (qRRule.frequency()) { + case QOrganizerRecurrenceRule::Invalid: + break; + case QOrganizerRecurrenceRule::Daily: + kRRule->setRecurrenceType(KCalCore::RecurrenceRule::rDaily); + break; + case QOrganizerRecurrenceRule::Weekly: + kRRule->setRecurrenceType(KCalCore::RecurrenceRule::rWeekly); + break; + case QOrganizerRecurrenceRule::Monthly: + kRRule->setRecurrenceType(KCalCore::RecurrenceRule::rMonthly); + break; + case QOrganizerRecurrenceRule::Yearly: + kRRule->setRecurrenceType(KCalCore::RecurrenceRule::rYearly); + break; + } + kRRule->setFrequency(qRRule.interval()); + if (qRRule.limitCount() > 0) { + kRRule->setDuration(qRRule.limitCount()); + } + kRRule->setStartDt(KDateTime(startDate)); + QDate endDate = qRRule.limitDate(); + if (endDate.isValid()) { + kRRule->setEndDt(KDateTime(endDate)); + } + + //QOrganizerRecurrenceRule does not support position associated with dayOfWeek so we will always + //take the first position. When this will be implemented in the future please fix the code below. + int pos = qRRule.positions().size() ? *qRRule.positions().begin() : 0; + + QList<KCalCore::RecurrenceRule::WDayPos> daysOfWeek; + foreach (Qt::DayOfWeek dayOfWeek, qRRule.daysOfWeek()) { + daysOfWeek.append(KCalCore::RecurrenceRule::WDayPos(pos, (short)dayOfWeek)); + } + kRRule->setByDays(daysOfWeek); + + kRRule->setByMonthDays(qRRule.daysOfMonth().toList()); + + kRRule->setByYearDays(qRRule.daysOfYear().toList()); + + kRRule->setByWeekNumbers(qRRule.weeksOfYear().toList()); + + QList<int> months; + foreach (QOrganizerRecurrenceRule::Month month, qRRule.monthsOfYear()) { + months.append((int)month); + } + kRRule->setByMonths(months); + + kRRule->setWeekStart((short)qRRule.firstDayOfWeek()); + + return kRRule; +} + +/*! + * Converts a kcal \a incidence into a QOrganizer \a item. + * \a incidence and \a item must both not be null. + * \item must point to a default-constructed item. + */ +bool MKCalEngine::convertIncidenceToItem( + KCalCore::Incidence::Ptr incidence, QOrganizerItem* item) const +{ + convertCommonIncidenceFieldsToDetails(incidence, item); + if (incidence->type() == KCalCore::IncidenceBase::TypeEvent) { + convertKEventToQEvent(incidence.staticCast<KCalCore::Event>(), item); + } else if (incidence->type() == KCalCore::IncidenceBase::TypeTodo) { + convertKTodoToQTodo(incidence.staticCast<KCalCore::Todo>(), item); + } else if (incidence->type() == KCalCore::IncidenceBase::TypeJournal) { + convertKJournalToQJournal(incidence.staticCast<KCalCore::Journal>(), item); + } else { + return false; + } + + return true; +} + +/*! + * Converts a kcal Event. + */ +void MKCalEngine::convertKEventToQEvent(KCalCore::Event::Ptr e, QOrganizerItem* item) const +{ + QOrganizerEvent* event = static_cast<QOrganizerEvent*>(item); + if (!e->dtStart().isNull()) + event->setStartDateTime(e->dtStart().dateTime()); + if (!e->dtEnd().isNull()) + event->setEndDateTime(e->dtEnd().dateTime()); + + if (e->hasRecurrenceId()) { + item->setType(QOrganizerItemType::TypeEventOccurrence); + QOrganizerEventOccurrence* eventOccurrence = static_cast<QOrganizerEventOccurrence*>(item); + eventOccurrence->setOriginalDate(e->recurrenceId().date()); + eventOccurrence->setParentId(QOrganizerItemId(new MKCalItemId( + e->uid(), + KDateTime()))); + } else { + item->setType(QOrganizerItemType::TypeEvent); + convertKRecurrenceToQRecurrence(e->recurrence(), item); + } +} + +/*! + * Converts a kcal Todo. + */ +void MKCalEngine::convertKTodoToQTodo(KCalCore::Todo::Ptr t, QOrganizerItem* item) const +{ + QOrganizerTodo* todo = static_cast<QOrganizerTodo*>(item); + if (!t->dtStart().isNull()) + todo->setStartDateTime(t->dtStart().dateTime()); + if (!t->dtDue().isNull()) + todo->setDueDateTime(t->dtDue().dateTime()); + + if (t->hasRecurrenceId()) { + item->setType(QOrganizerItemType::TypeTodoOccurrence); + QOrganizerTodoOccurrence* todoOccurrence = static_cast<QOrganizerTodoOccurrence*>(item); + todoOccurrence->setOriginalDate(t->recurrenceId().date()); + todoOccurrence->setParentId(QOrganizerItemId(new MKCalItemId( + t->uid(), + KDateTime()))); + } else { + item->setType(QOrganizerItemType::TypeTodo); + convertKRecurrenceToQRecurrence(t->recurrence(), item); + } +} + +/*! + * Converts a kcal Journal. + */ +void MKCalEngine::convertKJournalToQJournal(KCalCore::Journal::Ptr j, QOrganizerItem* item) const +{ + if (j->dtStart().isValid()) { + item->setType(QOrganizerItemType::TypeJournal); + static_cast<QOrganizerJournal*>(item)->setDateTime(j->dtStart().dateTime()); + } else { + item->setType(QOrganizerItemType::TypeNote); + } +} + +/*! + * Adds details to \a item based on fields found in \a incidence. + */ +void MKCalEngine::convertCommonIncidenceFieldsToDetails( + KCalCore::Incidence::Ptr incidence, QOrganizerItem* item) const +{ + item->setId(QOrganizerItemId(new MKCalItemId( + incidence->uid(), + incidence->hasRecurrenceId() ? incidence->recurrenceId() : KDateTime()))); + item->setCollectionId(QOrganizerCollectionId(new MKCalCollectionId( + d->m_calendarBackendPtr->notebook(incidence)))); + + if (!incidence->summary().isEmpty()) + item->setDisplayLabel(incidence->summary()); + if (!incidence->description().isEmpty()) + item->setDescription(incidence->description()); + + if (!incidence->location().isEmpty()) { + QOrganizerItemLocation location; + location.setLabel(incidence->location()); + item->saveDetail(&location); + } + + item->setGuid(incidence->uid()); +} + +void MKCalEngine::convertKRecurrenceToQRecurrence(const KCalCore::Recurrence* kRecurrence, QOrganizerItem* item) const +{ + bool modified = false; + QOrganizerItemRecurrence recurrence; + foreach (const KCalCore::RecurrenceRule* kRRule, kRecurrence->rRules()) { + QOrganizerRecurrenceRule rrule(createQRecurrenceRule(kRRule)); + if (rrule.frequency() != QOrganizerRecurrenceRule::Invalid) { + recurrence.setRecurrenceRules(QSet<QOrganizerRecurrenceRule>() << rrule); + modified = true; + } + } + foreach (const KCalCore::RecurrenceRule* kExRule, kRecurrence->exRules()) { + QOrganizerRecurrenceRule exrule(createQRecurrenceRule(kExRule)); + if (exrule.frequency() != QOrganizerRecurrenceRule::Invalid) { + recurrence.setExceptionRules(QSet<QOrganizerRecurrenceRule>() << exrule); + modified = true; + } + } + if (!kRecurrence->rDates().isEmpty()) { + recurrence.setRecurrenceDates(kRecurrence->rDates().toSet()); + modified = true; + } + if (!kRecurrence->exDates().isEmpty()) { + recurrence.setExceptionDates(kRecurrence->exDates().toSet()); + modified = true; + } + if (modified) + item->saveDetail(&recurrence); +} + +QOrganizerRecurrenceRule MKCalEngine::createQRecurrenceRule(const KCalCore::RecurrenceRule* kRRule) const +{ + QOrganizerRecurrenceRule qRRule; + switch (kRRule->recurrenceType()) { + case KCalCore::RecurrenceRule::rDaily: + qRRule.setFrequency(QOrganizerRecurrenceRule::Daily); + break; + case KCalCore::RecurrenceRule::rWeekly: + qRRule.setFrequency(QOrganizerRecurrenceRule::Weekly); + break; + case KCalCore::RecurrenceRule::rMonthly: + qRRule.setFrequency(QOrganizerRecurrenceRule::Monthly); + break; + case KCalCore::RecurrenceRule::rYearly: + qRRule.setFrequency(QOrganizerRecurrenceRule::Yearly); + break; + default: + return qRRule; + } + + qRRule.setInterval(kRRule->frequency()); + + if (kRRule->duration() > 0) { + qRRule.setLimit(kRRule->duration()); + } else { + QDate limitDate(kRRule->endDt().dateTime().date()); + if (limitDate.isValid()) + qRRule.setLimit(limitDate); + } + + QSet<Qt::DayOfWeek> daysOfWeek; + foreach (KCalCore::RecurrenceRule::WDayPos wday, kRRule->byDays()) + daysOfWeek.insert(static_cast<Qt::DayOfWeek>(wday.day())); + qRRule.setDaysOfWeek(daysOfWeek); + + qRRule.setDaysOfMonth(kRRule->byMonthDays().toSet()); + + qRRule.setDaysOfYear(kRRule->byYearDays().toSet()); + + qRRule.setWeeksOfYear(kRRule->byWeekNumbers().toSet()); + + QSet<QOrganizerRecurrenceRule::Month> months; + foreach (int month, kRRule->byMonths()) { + months.insert(static_cast<QOrganizerRecurrenceRule::Month>(month)); + } + qRRule.setMonthsOfYear(months); + + qRRule.setFirstDayOfWeek(static_cast<Qt::DayOfWeek>(kRRule->weekStart())); + + return qRRule; +} + +/*! + * Convert mKCal notebook to QOrganizerCollection + */ +QOrganizerCollection MKCalEngine::convertNotebookToCollection(mKCal::Notebook::Ptr notebook) const +{ + QOrganizerCollection retn; + + QString string; + QStringList stringList; + QDateTime dateTime; + + if (!(string = notebook->name()).isEmpty()) + retn.setMetaData(QOrganizerCollection::KeyName, string); + if (!(string = notebook->color()).isEmpty()) + retn.setMetaData(QOrganizerCollection::KeyColor, string); + if (!(string = notebook->description()).isEmpty()) + retn.setMetaData(QOrganizerCollection::KeyDescription, string); + // XXX We lose the timezone information here!: + if ((dateTime = notebook->syncDate().dateTime()).isValid()) + retn.setMetaData(NotebookSyncDate, dateTime); + if ((dateTime = notebook->modifiedDate().dateTime()).isValid()) + retn.setMetaData(NotebookModifiedDate, dateTime); + if (!(string = notebook->pluginName()).isEmpty()) + retn.setMetaData(NotebookPluginName, string); + if (!(string = notebook->account()).isEmpty()) + retn.setMetaData(NotebookAccount, string); + if (!(stringList = notebook->sharedWith()).isEmpty()) + retn.setMetaData(NotebookSharedWith, stringList); + if (!(string = notebook->sharedWithStr()).isEmpty()) + retn.setMetaData(NotebookSharedWithStr, string); + if (!(string = notebook->syncProfile()).isEmpty()) + retn.setMetaData(NotebookSyncProfile, string); + int attachmentSize = notebook->attachmentSize(); + if (attachmentSize >= 0) + retn.setMetaData(NotebookAttachmentSize, attachmentSize); + + // Boolean flags that we always store + retn.setMetaData(NotebookIsDefault, notebook->isDefault()); + retn.setMetaData(NotebookIsShareable, notebook->isShareable()); + retn.setMetaData(NotebookIsShared, notebook->isShared()); + retn.setMetaData(NotebookIsMaster, notebook->isMaster()); + retn.setMetaData(NotebookIsOviSync, notebook->isOviSync()); + retn.setMetaData(NotebookIsReadOnly, notebook->isReadOnly()); + retn.setMetaData(NotebookIsVisible, notebook->isVisible()); + retn.setMetaData(NotebookIsRunTimeOnly, notebook->isRunTimeOnly()); + retn.setMetaData(NotebookEventsAllowed, notebook->eventsAllowed()); + retn.setMetaData(NotebookJournalsAllowed, notebook->journalsAllowed()); + retn.setMetaData(NotebookTodosAllowed, notebook->todosAllowed()); + + // now set the id of the collection. + QOrganizerCollectionId colId(new MKCalCollectionId(notebook->uid())); + retn.setId(colId); + + // done + return retn; +} + +/*! + * Convert QOrganizerCollection to mKCal notebook + */ +void MKCalEngine::convertCollectionToNotebook(const QOrganizerCollection& collection, mKCal::Notebook::Ptr notebook) const +{ + QVariant variant; + if (!(variant = collection.metaData(QOrganizerCollection::KeyName)).isNull()) + notebook->setName(variant.toString()); + + if (!(variant = collection.metaData(QOrganizerCollection::KeyColor)).isNull()) + notebook->setColor(variant.toString()); + if (!(variant = collection.metaData(QOrganizerCollection::KeyDescription)).isNull()) + notebook->setDescription(variant.toString()); + if (!(variant = collection.metaData(NotebookSyncDate)).isNull()) + notebook->setSyncDate(KDateTime(variant.toDateTime())); + if (!(variant = collection.metaData(NotebookModifiedDate)).isNull()) + notebook->setModifiedDate(KDateTime(variant.toDateTime())); + if (!(variant = collection.metaData(NotebookPluginName)).isNull()) + notebook->setPluginName(variant.toString()); + if (!(variant = collection.metaData(NotebookAccount)).isNull()) + notebook->setAccount(variant.toString()); + if (!(variant = collection.metaData(NotebookAttachmentSize)).isNull()) + notebook->setAttachmentSize(variant.toInt()); + if (!(variant = collection.metaData(NotebookSharedWith)).isNull()) + notebook->setSharedWith(variant.toStringList()); + if (!(variant = collection.metaData(NotebookSharedWithStr)).isNull()) + notebook->setSharedWithStr(variant.toString()); + if (!(variant = collection.metaData(NotebookSyncProfile)).isNull()) + notebook->setSyncProfile(variant.toString()); + + // Boolean flags + if (!(variant = collection.metaData(NotebookIsDefault)).isNull()) + notebook->setIsDefault(variant.toBool()); + if (!(variant = collection.metaData(NotebookIsShareable)).isNull()) + notebook->setIsShareable(variant.toBool()); + if (!(variant = collection.metaData(NotebookIsShared)).isNull()) + notebook->setIsShared(variant.toBool()); + if (!(variant = collection.metaData(NotebookIsMaster)).isNull()) + notebook->setIsMaster(variant.toBool()); + if (!(variant = collection.metaData(NotebookIsOviSync)).isNull()) + notebook->setIsOviSync(variant.toBool()); + if (!(variant = collection.metaData(NotebookIsReadOnly)).isNull()) + notebook->setIsReadOnly(variant.toBool()); + if (!(variant = collection.metaData(NotebookIsVisible)).isNull()) + notebook->setIsVisible(variant.toBool()); + if (!(variant = collection.metaData(NotebookIsRunTimeOnly)).isNull()) + notebook->setRunTimeOnly(variant.toBool()); + if (!(variant = collection.metaData(NotebookEventsAllowed)).isNull()) + notebook->setEventsAllowed(variant.toBool()); + if (!(variant = collection.metaData(NotebookJournalsAllowed)).isNull()) + notebook->setJournalsAllowed(variant.toBool()); + if (!(variant = collection.metaData(NotebookTodosAllowed)).isNull()) + notebook->setTodosAllowed(variant.toBool()); +} diff --git a/plugins/organizer/mkcal/mkcalengine.h b/plugins/organizer/mkcal/mkcalengine.h new file mode 100644 index 0000000000..10b9738fa9 --- /dev/null +++ b/plugins/organizer/mkcal/mkcalengine.h @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MKCALENGINE_H +#define MKCALENGINE_H + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QSharedData> +#include <QMap> +#include <QMultiMap> +#include <QList> +#include <QQueue> +#include <QPair> +#include <QSet> +#include <QDateTime> +#include <QString> +#include <QObject> + +#include <extendedcalendar.h> +#include <extendedstorage.h> +#include <notebook.h> + +#include "qorganizeritem.h" +#include "qorganizermanager.h" +#include "qorganizermanagerengine.h" +#include "qorganizermanagerenginefactory.h" +#include "qorganizeritemdetaildefinition.h" +#include "qorganizerabstractrequest.h" +#include "qorganizeritemchangeset.h" + +QTM_BEGIN_NAMESPACE +class QOrganizerEvent; +class QOrganizerTodo; +class QOrganizerNote; +class QOrganizerJournal; +class QOrganizerItemRecurrence; +class QOrganizerRecurrenceRule; +QTM_END_NAMESPACE + +QTM_USE_NAMESPACE + +class OrganizerAsynchManager; // helper class to process async requests + +class MKCalEngineFactory : public QObject, public QOrganizerManagerEngineFactory +{ + Q_OBJECT + Q_INTERFACES(QtMobility::QOrganizerManagerEngineFactory) + public: + QOrganizerManagerEngine* engine(const QMap<QString, QString>& parameters, QOrganizerManager::Error*); + QString managerName() const; + QOrganizerItemEngineId* createItemEngineId(const QMap<QString, QString>& parameters, const QString& engineIdString) const; + QOrganizerCollectionEngineId* createCollectionEngineId(const QMap<QString, QString>& parameters, const QString& engineIdString) const; +}; + +class MKCalEngineData : public QSharedData +{ +public: + MKCalEngineData() + : QSharedData(), + m_calendarBackendPtr(new mKCal::ExtendedCalendar(KDateTime::Spec::LocalZone())), + m_storagePtr(mKCal::ExtendedCalendar::defaultStorage(m_calendarBackendPtr)) + { + m_storagePtr->open(); + m_storagePtr->load(); + mKCal::Notebook::Ptr defaultNotebook = m_storagePtr->defaultNotebook(); + if (!defaultNotebook) + m_storagePtr->createDefaultNotebook("defaultNotebook"); + } + + ~MKCalEngineData() + { + } + + // map of organizeritem type to map of definition name to definitions: + mutable QMap<QString, QMap<QString, QOrganizerItemDetailDefinition> > m_definitions; + + mKCal::ExtendedCalendar::Ptr m_calendarBackendPtr; + mKCal::ExtendedStorage::Ptr m_storagePtr; + + // asynchronous request handler instance + OrganizerAsynchManager *m_asynchProcess; + + QString m_managerUri; + QOrganizerItem m_converted; +}; + +class MKCalEngine : public QOrganizerManagerEngine, public mKCal::ExtendedStorageObserver +{ + Q_OBJECT + +public: + ~MKCalEngine(); + + /* URI reporting */ + QString managerName() const; + QMap<QString, QString> managerParameters() const; + int managerVersion() const; + + QList<QOrganizerItem> itemOccurrences(const QOrganizerItem& parentItem, const QDateTime& periodStart, const QDateTime& periodEnd, int maxCount, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const; + QList<QOrganizerItemId> itemIds(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, QOrganizerManager::Error* error) const; + QList<QOrganizerItem> itemsForExport(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const; + QList<QOrganizerItem> items(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const; + QOrganizerItem item(const QOrganizerItemId& itemId, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error) const; + + bool saveItems(QList<QOrganizerItem>* items, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error); + bool saveItem(QOrganizerItem* item, QOrganizerManager::Error* error); + bool removeItems(const QList<QOrganizerItemId>& itemIds, QMap<int, QOrganizerManager::Error>* errorMap, QOrganizerManager::Error* error); + + /* Definitions - Accessors and Mutators */ + QMap<QString, QOrganizerItemDetailDefinition> detailDefinitions(const QString& itemType, QOrganizerManager::Error* error) const; + QOrganizerItemDetailDefinition detailDefinition(const QString& definitionId, const QString& itemType, QOrganizerManager::Error* error) const; + bool saveDetailDefinition(const QOrganizerItemDetailDefinition& def, const QString& itemType, QOrganizerManager::Error* error); + bool removeDetailDefinition(const QString& definitionId, const QString& itemType, QOrganizerManager::Error* error); + + /* Collections - every item belongs to exactly one collection */ + QOrganizerCollection defaultCollection(QOrganizerManager::Error* error) const; + QOrganizerCollection collection(const QOrganizerCollectionId& collectionId, QOrganizerManager::Error* error) const; + QList<QOrganizerCollection> collections(QOrganizerManager::Error* error) const; + bool saveCollection(QOrganizerCollection* collection, QOrganizerManager::Error* error); + bool removeCollection(const QOrganizerCollectionId& collectionId, QOrganizerManager::Error* error); + + /* Capabilities reporting */ + bool hasFeature(QOrganizerManager::ManagerFeature feature, const QString& itemType) const; + bool isFilterSupported(const QOrganizerItemFilter& filter) const; + QList<int> supportedDataTypes() const; + QStringList supportedItemTypes() const; + + /* Asynchronous Request Support */ + void requestDestroyed(QOrganizerAbstractRequest* req); + bool startRequest(QOrganizerAbstractRequest* req); + bool cancelRequest(QOrganizerAbstractRequest* req); + bool waitForRequestFinished(QOrganizerAbstractRequest* req, int msecs); + + /* mKCal Change Notification Support (ExtendedStorageObserver) */ + void storageModified(mKCal::ExtendedStorage* storage, const QString& info); + void storageProgress(mKCal::ExtendedStorage* storage, const QString& info); + void storageFinished(mKCal::ExtendedStorage* storage, bool error, const QString& info); + +private: + MKCalEngine(const QString& managerUri = QString()); + QMap<QString, QMap<QString, QOrganizerItemDetailDefinition> > schemaDefinitions() const; + KCalCore::Incidence::Ptr incidence(const QOrganizerItemId& itemId) const; + KCalCore::Incidence::Ptr detachedIncidenceFromItem(const QOrganizerItem& item) const; + bool internalSaveItem(QOrganizerItemChangeSet* ics, QOrganizerItem* item, QOrganizerManager::Error* error); + KCalCore::Incidence::Ptr softSaveItem(QOrganizerItemChangeSet* ics, QOrganizerItem* item, QOrganizerManager::Error* error); + void convertQEventToKEvent(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence, bool recurs); + void convertQTodoToKTodo(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence, bool recurs); + void convertQJournalToKJournal(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence); + void convertQNoteToKNote(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence); + void convertCommonDetailsToIncidenceFields(const QOrganizerItem& item, KCalCore::Incidence::Ptr incidence); + void convertQRecurrenceToKRecurrence( + const QOrganizerItemRecurrence& qRecurrence, + const QDate& startDate, + KCalCore::Recurrence* kRecurrence); + KCalCore::RecurrenceRule* createKRecurrenceRule( + KCalCore::Recurrence* kRecurrence, + const QDate& startDate, + const QOrganizerRecurrenceRule& qRRule); + + bool convertIncidenceToItem(KCalCore::Incidence::Ptr i, QOrganizerItem* item) const; + void convertKEventToQEvent(KCalCore::Event::Ptr e, QOrganizerItem* item) const; + void convertKTodoToQTodo(KCalCore::Todo::Ptr t, QOrganizerItem* item) const; + void convertKJournalToQJournal(KCalCore::Journal::Ptr j, QOrganizerItem* item) const; + void convertCommonIncidenceFieldsToDetails(KCalCore::Incidence::Ptr i, QOrganizerItem* item) const; + void convertKRecurrenceToQRecurrence(const KCalCore::Recurrence* kRecurrence, QOrganizerItem* item) const; + QOrganizerRecurrenceRule createQRecurrenceRule(const KCalCore::RecurrenceRule* kRRule) const; + + QOrganizerCollection convertNotebookToCollection(mKCal::Notebook::Ptr notebook) const; + void convertCollectionToNotebook(const QOrganizerCollection& collection, mKCal::Notebook::Ptr notebook) const; + + QList<QOrganizerItem> internalItemOccurrences(const QOrganizerItem& parentItem, const QDateTime& periodStart, const QDateTime& periodEnd, int maxCount, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error, bool includeInstances) const; + QList<QOrganizerItem> internalItems(const QDateTime& startDate, const QDateTime& endDate, const QOrganizerItemFilter& filter, const QList<QOrganizerItemSortOrder>& sortOrders, const QOrganizerItemFetchHint& fetchHint, QOrganizerManager::Error* error, bool expand) const; + bool itemHasRecurringChild(KCalCore::Incidence::Ptr incidence, QDateTime startDate, QDateTime endDate, QOrganizerItemFilter filter) const; + + MKCalEngineData* d; + + friend class MKCalEngineFactory; +}; + +#endif + diff --git a/plugins/organizer/mkcal/mkcalid.h b/plugins/organizer/mkcal/mkcalid.h new file mode 100644 index 0000000000..5987f9e4db --- /dev/null +++ b/plugins/organizer/mkcal/mkcalid.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MKCALID_H +#define MKCALID_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qorganizeritemengineid.h" + +QTM_USE_NAMESPACE + +class MKCalEngineFactory; // forward decl. for friend decl. + +class MKCalItemId : public QOrganizerItemEngineId +{ +public: + MKCalItemId() {} + MKCalItemId(const QString& id, const KDateTime& rid) : m_id(id), m_rid(rid) {} + bool isEqualTo(const QOrganizerItemEngineId* other) const + { + const MKCalItemId* otherid = static_cast<const MKCalItemId*>(other); + if (managerUri() != otherid->managerUri()) + return false; + if (m_id != otherid->m_id) + return false; + if (m_rid != otherid->m_rid) + return false; + return true; + } + bool isLessThan(const QOrganizerItemEngineId* other) const + { + const MKCalItemId* otherid = static_cast<const MKCalItemId*>(other); + if (m_id < otherid->m_id) + return true; + if (m_rid < otherid->m_rid) + return true; + return false; + } + QOrganizerItemEngineId* clone() const + { + return new MKCalItemId(m_id, m_rid); + } + QString managerUri() const + { + static QString uri(QLatin1String("qtorganizer:mkcal:")); + return uri; + } +#ifndef QT_NO_DEBUG_STREAM + QDebug& debugStreamOut(QDebug& dbg) const + { + return dbg << m_id << m_rid.dateTime() << managerUri(); + } +#endif + uint hash() const + { + return qHash(m_id); + } + + QString toString() const + { + QString retn; + retn += m_id; + if (m_rid.isNull()) { + retn += QLatin1String("::"); + } else { + retn += QString(":%1:").arg(m_rid.toTime_t()); + } + retn += managerUri(); + return retn; + } + QString id() const + { + return m_id; + } + KDateTime rid() const + { + return m_rid; + } + + static const MKCalItemId* id_cast(const QOrganizerItemId& itemId) + { + static MKCalItemId empty; + if (itemId.isNull() || itemId.managerUri() != empty.managerUri()) + return ∅ + return static_cast<const MKCalItemId *>(QOrganizerManagerEngine::engineItemId(itemId)); + } + +private: + QString m_id; + KDateTime m_rid; +}; + + +class MKCalCollectionId : public QOrganizerCollectionEngineId +{ +public: + MKCalCollectionId() {} + MKCalCollectionId(const QString& uid) : m_uid(uid) {} + bool isEqualTo(const QOrganizerCollectionEngineId* other) const + { + const MKCalCollectionId* otherid = static_cast<const MKCalCollectionId*>(other); + if (m_uid != otherid->m_uid) + return false; + return true; + } + bool isLessThan(const QOrganizerCollectionEngineId* other) const + { + const MKCalCollectionId* otherid = static_cast<const MKCalCollectionId*>(other); + if (managerUri() < otherid->managerUri()) + return true; + if (m_uid < otherid->m_uid) + return true; + return false; + } + QOrganizerCollectionEngineId* clone() const + { + return new MKCalCollectionId(m_uid); + } + QString managerUri() const + { + static QString uri(QLatin1String("qtorganizer:mkcal:")); + return uri; + } +#ifndef QT_NO_DEBUG_STREAM + QDebug& debugStreamOut(QDebug& dbg) const + { + return dbg << m_uid << ":" << managerUri(); + } +#endif + uint hash() const + { + return qHash(m_uid); + } + + QString toString() const + { + return m_uid + QLatin1String(":") + managerUri(); + } + + static const MKCalCollectionId* id_cast(const QOrganizerCollectionId& collId) + { + static MKCalCollectionId empty; + if (collId.isNull() || collId.managerUri() != empty.managerUri()) + return ∅ + return static_cast<const MKCalCollectionId *>(QOrganizerManagerEngine::engineCollectionId(collId)); + } + + QString uid() const + { + return m_uid; + } + +private: + QString m_uid; +}; + +#endif diff --git a/plugins/organizer/mkcal/qorganizerasynchmanager.cpp b/plugins/organizer/mkcal/qorganizerasynchmanager.cpp new file mode 100644 index 0000000000..aeadaa990b --- /dev/null +++ b/plugins/organizer/mkcal/qorganizerasynchmanager.cpp @@ -0,0 +1,489 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qorganizerasynchmanager.h" +#include "qtorganizer.h" +#include "mkcalengine.h" +#include "mkcalid.h" + +QTM_USE_NAMESPACE + +class AsyncWorker: public QThread +{ +public: + AsyncWorker(OrganizerAsynchManager* manager): m_manager(manager), m_req(0), m_kill(false) {}; + + void assignRequest(QOrganizerAbstractRequest* r); + void kill(); +protected: + void run(); +private: + OrganizerAsynchManager* m_manager; + QOrganizerAbstractRequest* m_req; + QMutex m_lock; + QWaitCondition m_wait; + bool m_kill; + + void processRequest(QOrganizerAbstractRequest *req); + + void handleItemFetchRequest(QOrganizerItemFetchRequest *req); + void handleItemFetchForExportRequest(QOrganizerItemFetchForExportRequest *req); + void handleItemOccurrenceFetchRequest(QOrganizerItemOccurrenceFetchRequest *req); + void handleIdFetchRequest(QOrganizerItemIdFetchRequest *req); + void handleItemRemoveRequest(QOrganizerItemRemoveRequest *req); + void handleSaveRequest(QOrganizerItemSaveRequest *req); + void handleDefinitionFetchRequest(QOrganizerItemDetailDefinitionFetchRequest *req); + void handleDefinitionRemoveRequest(QOrganizerItemDetailDefinitionRemoveRequest *req); + void handleDefinitionSaveRequest(QOrganizerItemDetailDefinitionSaveRequest *req); + void handleCollectionFetchRequest(QOrganizerCollectionFetchRequest *req); + void handleCollectionRemoveRequest(QOrganizerCollectionRemoveRequest *req); + void handleCollectionSaveRequest(QOrganizerCollectionSaveRequest *req); +}; + +void AsyncWorker::assignRequest(QOrganizerAbstractRequest* r) +{ + QMutexLocker lock(&m_lock); + Q_ASSERT_X(!m_req, "Worker", "Something is wrong with locking mechanism!"); + m_req = r; + + //either start the thread if is not running or wake up the sleeping thread + if (!isRunning()) + start(); + else + m_wait.wakeAll(); +} + +void AsyncWorker::kill() +{ + //mark the exit flag and wake up the thread if is sleeping + m_kill = true; + m_wait.wakeAll(); + + //wait the thread to finish + wait(); +} + +void AsyncWorker::run() +{ + QMutexLocker locker(&m_lock); + + //until manager destructor is called + while (!m_kill) { + //obtain a request + QOrganizerAbstractRequest *r = m_req; + m_req = 0; + + //if there are no requests sleep + if (!r) + m_wait.wait(&m_lock); + else { + locker.unlock(); + //process the request + processRequest(r); + //signal the manager + if (!m_kill) + m_manager->workerDone(this, r); + locker.relock(); + } + } +} + +void AsyncWorker::processRequest(QOrganizerAbstractRequest* req) +{ + //if the request state is not active no need to do anything + QOrganizerAbstractRequest::State state = req->state(); + + //if the request is in inactive state, start the request (this could happen due to thread scheduling) + if (state == QOrganizerAbstractRequest::InactiveState) { + QOrganizerManagerEngine::updateRequestState(req, QOrganizerAbstractRequest::ActiveState); + } else if (state != QOrganizerAbstractRequest::ActiveState) { + return; + } + + //process the request + switch(req->type()) { + case QOrganizerAbstractRequest::ItemFetchRequest: + handleItemFetchRequest(static_cast<QOrganizerItemFetchRequest *>(req)); + break; + case QOrganizerAbstractRequest::ItemFetchForExportRequest: + handleItemFetchForExportRequest(static_cast<QOrganizerItemFetchForExportRequest *>(req)); + break; + case QOrganizerAbstractRequest::ItemOccurrenceFetchRequest: + handleItemOccurrenceFetchRequest(static_cast<QOrganizerItemOccurrenceFetchRequest *>(req)); + break; + case QOrganizerAbstractRequest::ItemIdFetchRequest: + handleIdFetchRequest(static_cast<QOrganizerItemIdFetchRequest *>(req)); + break; + case QOrganizerAbstractRequest::ItemRemoveRequest: + handleItemRemoveRequest(static_cast<QOrganizerItemRemoveRequest *>(req)); + break; + case QOrganizerAbstractRequest::ItemSaveRequest: + handleSaveRequest(static_cast<QOrganizerItemSaveRequest *>(req)); + break; + case QOrganizerAbstractRequest::DetailDefinitionFetchRequest: + handleDefinitionFetchRequest(static_cast<QOrganizerItemDetailDefinitionFetchRequest *>(req)); + break; + case QOrganizerAbstractRequest::DetailDefinitionRemoveRequest: + handleDefinitionRemoveRequest(static_cast<QOrganizerItemDetailDefinitionRemoveRequest *>(req)); + break; + case QOrganizerAbstractRequest::DetailDefinitionSaveRequest: + handleDefinitionSaveRequest(static_cast<QOrganizerItemDetailDefinitionSaveRequest *>(req)); + break; + case QOrganizerAbstractRequest::CollectionFetchRequest: + handleCollectionFetchRequest(static_cast<QOrganizerCollectionFetchRequest *>(req)); + break; + case QOrganizerAbstractRequest::CollectionRemoveRequest: + handleCollectionRemoveRequest(static_cast<QOrganizerCollectionRemoveRequest *>(req)); + break; + case QOrganizerAbstractRequest::CollectionSaveRequest: + handleCollectionSaveRequest(static_cast<QOrganizerCollectionSaveRequest *>(req)); + break; + + default: + // invalid request + break; + } +} + +void AsyncWorker::handleItemFetchRequest(QOrganizerItemFetchRequest *req) +{ + QOrganizerManager::Error err = QOrganizerManager::NoError; + QList<QOrganizerItem> items = m_manager->m_engine->items(req->startDate(), req->endDate(), req->filter(), req->sorting(), req->fetchHint(), &err); + QOrganizerManagerEngine::updateItemFetchRequest(req, items, err, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleItemFetchForExportRequest(QOrganizerItemFetchForExportRequest *req) +{ + QOrganizerManager::Error err = QOrganizerManager::NoError; + QList<QOrganizerItem> items = m_manager->m_engine->itemsForExport(req->startDate(), req->endDate(), req->filter(), req->sorting(), req->fetchHint(), &err); + QOrganizerManagerEngine::updateItemFetchForExportRequest(req, items, err, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleItemOccurrenceFetchRequest(QOrganizerItemOccurrenceFetchRequest *req) +{ + QOrganizerManager::Error err = QOrganizerManager::NoError; + QList<QOrganizerItem> items = m_manager->m_engine->itemOccurrences(req->parentItem(), req->startDate(), req->endDate(), req->maxOccurrences(), req->fetchHint(), &err); + QOrganizerManagerEngine::updateItemOccurrenceFetchRequest(req, items, err, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleIdFetchRequest(QOrganizerItemIdFetchRequest *req) +{ + QOrganizerManager::Error err = QOrganizerManager::NoError; + QList<QOrganizerItemId> ids = m_manager->m_engine->itemIds(req->startDate(), req->endDate(), req->filter(), req->sorting(), &err); + QOrganizerManagerEngine::updateItemIdFetchRequest(req, ids, err, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleItemRemoveRequest(QOrganizerItemRemoveRequest *req) +{ + QOrganizerManager::Error err = QOrganizerManager::NoError; + QMap<int, QOrganizerManager::Error> errorMap; + m_manager->m_engine->removeItems(req->itemIds(), &errorMap, &err); + QOrganizerManagerEngine::updateItemRemoveRequest(req, err, errorMap, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleSaveRequest(QOrganizerItemSaveRequest *req) +{ + QOrganizerManager::Error err = QOrganizerManager::NoError; + QMap<int, QOrganizerManager::Error> errorMap; + QList<QOrganizerItem> items = req->items(); + m_manager->m_engine->saveItems(&items, &errorMap, &err); + QOrganizerManagerEngine::updateItemSaveRequest(req, items, err, errorMap, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleDefinitionFetchRequest(QOrganizerItemDetailDefinitionFetchRequest *req) +{ + QOrganizerManager::Error err = QOrganizerManager::NoError; + QMap<QString, QOrganizerItemDetailDefinition> definitions = m_manager->m_engine->detailDefinitions(req->itemType(), &err); + QMap<QString, QOrganizerItemDetailDefinition> retn; + QMap<int, QOrganizerManager::Error> errorMap; + QStringList keys = req->definitionNames(); + if (keys.isEmpty()) + keys = definitions.keys(); + int definitionsCount = keys.count(); + for (int i = 0; i < definitionsCount; ++i) { + if (definitions.contains(keys.at(i))) + retn.insert(keys.at(i), definitions[keys.at(i)]); + else + errorMap.insert(i, QOrganizerManager::DoesNotExistError); + } + QOrganizerManagerEngine::updateDefinitionFetchRequest(req, retn, err, errorMap, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleDefinitionRemoveRequest(QOrganizerItemDetailDefinitionRemoveRequest *req) +{ + QOrganizerManager::Error tempError = QOrganizerManager::NoError; + QOrganizerManager::Error operationError = QOrganizerManager::NoError; + QMap<int, QOrganizerManager::Error> errorMap; + QStringList definitionNames = req->definitionNames(); + int nameCount = definitionNames.count(); + for(int i = 0; i < nameCount; ++i) { + m_manager->m_engine->removeDetailDefinition(definitionNames.at(i), req->itemType(), &tempError); + if (tempError != QOrganizerManager::NoError) { + errorMap.insert(i, tempError); + operationError = tempError; + } + } + QOrganizerManagerEngine::updateDefinitionRemoveRequest(req, operationError, errorMap, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleDefinitionSaveRequest(QOrganizerItemDetailDefinitionSaveRequest *req) +{ + QOrganizerManager::Error tempError = QOrganizerManager::NoError; + QOrganizerManager::Error operationError = QOrganizerManager::NoError; + QMap<int, QOrganizerManager::Error> errorMap; + QList<QOrganizerItemDetailDefinition> definitions = req->definitions(); + int definitionCount = definitions.count(); + for (int i = 0; i < definitionCount; ++i) { + m_manager->m_engine->saveDetailDefinition(definitions.at(i), req->itemType(), &tempError); + if (tempError != QOrganizerManager::NoError) { + errorMap.insert(i, tempError); + operationError = tempError; + } + } + QOrganizerManagerEngine::updateDefinitionSaveRequest(req, definitions, operationError, errorMap, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleCollectionFetchRequest(QOrganizerCollectionFetchRequest *req) +{ + QOrganizerManager::Error err = QOrganizerManager::NoError; + QList<QOrganizerCollection> collections = m_manager->m_engine->collections(&err); + QOrganizerManagerEngine::updateCollectionFetchRequest(req, collections, err, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleCollectionRemoveRequest(QOrganizerCollectionRemoveRequest *req) +{ + QOrganizerManager::Error tempError = QOrganizerManager::NoError; + QOrganizerManager::Error operationError = QOrganizerManager::NoError; + QMap<int, QOrganizerManager::Error> errorMap; + QOrganizerCollectionId currentId; + QList<QOrganizerCollectionId> colsToRemove = req->collectionIds(); + int collectionsCount = colsToRemove.count(); + for (int i = 0; i < collectionsCount; ++i) { + currentId = colsToRemove.at(i); + m_manager->m_engine->removeCollection(currentId, &tempError); + if (tempError != QOrganizerManager::NoError) { + errorMap.insert(i, tempError); + operationError = tempError; + } + } + QOrganizerManagerEngine::updateCollectionRemoveRequest(req, operationError, errorMap, QOrganizerAbstractRequest::FinishedState); +} + +void AsyncWorker::handleCollectionSaveRequest(QOrganizerCollectionSaveRequest *req) +{ + QOrganizerManager::Error tempError = QOrganizerManager::NoError; + QOrganizerManager::Error operationError = QOrganizerManager::NoError; + QMap<int, QOrganizerManager::Error> errorMap; + QList<QOrganizerCollection> collections = req->collections(); + QList<QOrganizerCollection> retn; + int collectionsCount = collections.count(); + for (int i = 0; i < collectionsCount; ++i) { + QOrganizerCollection collection = collections.at(i); + m_manager->m_engine->saveCollection(&collection, &tempError); + retn << collection; + if (tempError != QOrganizerManager::NoError) { + errorMap.insert(i, tempError); + operationError = tempError; + } + } + QOrganizerManagerEngine::updateCollectionSaveRequest(req, retn, operationError, errorMap, QOrganizerAbstractRequest::FinishedState); +} + +OrganizerAsynchManager::OrganizerAsynchManager(QOrganizerManagerEngine* engine, int maxWorkers) + : m_engine(engine), m_maxWorkers(maxWorkers) +{ +} + +OrganizerAsynchManager::~OrganizerAsynchManager() +{ + QMutexLocker locker(&m_mutex); + + //kill all idle workers + foreach(AsyncWorker* worker, m_idleWorkers) + worker->kill(); + + //kill all active workers + foreach(AsyncWorker* worker, m_activeWorkers) + worker->kill(); + + //delete all workers + foreach(AsyncWorker* worker, m_idleWorkers) { + delete worker; + } +} + +void OrganizerAsynchManager::requestDestroyed(QOrganizerAbstractRequest *req) +{ + //request was deleted somewhere + m_mutex.lock(); + + //if is in the queued requests it means it has not been processed yet, so remove it + if (m_queuedRequests.contains(req)) { + m_queuedRequests.removeOne(req); + m_mutex.unlock(); + } else { + //wait until the request is processed + m_mutex.unlock(); + waitForRequestFinished(req); + } +} + +bool OrganizerAsynchManager::addRequest(QOrganizerAbstractRequest *req) +{ + QMutexLocker locker(&m_mutex); + + AsyncWorker *worker = 0; + + //first check if there is a lazy, idle worker around + if (m_idleWorkers.size() > 0) { + worker = m_idleWorkers.dequeue(); + } else + //if the active workers number is less then maxWorkers create an additional worker + if (m_activeWorkers.size() < m_maxWorkers) { + worker = new AsyncWorker(this); + } + + //if we found a worker assign him the request + if (worker) { + m_activeWorkers.enqueue(worker); + m_activeRequests.insert(req); + worker->assignRequest(req); + } else { + //no free worker is available, enqueue the request + m_queuedRequests.enqueue(req); + } + + locker.unlock(); + + //update the request state to active + QOrganizerManagerEngine::updateRequestState(req, QOrganizerAbstractRequest::ActiveState); + + return true; +} + +bool OrganizerAsynchManager::cancelRequest(QOrganizerAbstractRequest *req) +{ + QMutexLocker locker(&m_mutex); + + //if the request is still in the queue cancel and remove it + if (m_queuedRequests.contains(req)) { + m_queuedRequests.removeOne(req); + locker.unlock(); + QOrganizerManagerEngine::updateRequestState(req, QOrganizerAbstractRequest::CanceledState); + + return true; + } + + // cannot cancel request when processing has already begun + return false; +} + +bool OrganizerAsynchManager::waitForRequestFinished(QOrganizerAbstractRequest *req, int msecs) +{ + QOrganizerAbstractRequest::State state = req->state(); + + //check the request state, and if is not active return + if (state == QOrganizerAbstractRequest::FinishedState) { + return true; + } + else if (state == QOrganizerAbstractRequest::CanceledState + || state == QOrganizerAbstractRequest::InactiveState) { + return false; + } + + { + QMutexLocker locker(&m_mutex); + + //check if this request is known to the manager + if (!m_activeRequests.contains(req) && !m_queuedRequests.contains(req)) { + qWarning() << "Request was never queued: " << req; + return false; + } + } + + QMutexLocker locker(&m_mutexMap); + + //create or fetch an existing wait condition for this requests from + //the map to be able to block this thread until the condition is signaled + QSharedPointer<QWaitCondition> cond(m_waitMap.value(req)); + if (!cond) { + cond = QSharedPointer<QWaitCondition>(new QWaitCondition); + m_waitMap.insert(req, cond); + } + + //wait for the request to be finished or timeout + bool res = cond->wait(&m_mutexMap, msecs <= 0 ? ULONG_MAX : msecs); + + m_waitMap.remove(req); + + return res; +} + +void OrganizerAsynchManager::workerDone(AsyncWorker *worker, QOrganizerAbstractRequest *req) +{ + if (req) { + { + QMutexLocker locker(&m_mutexMap); + + //if somebody is waiting for this request wake him up + QSharedPointer<QWaitCondition> cond(m_waitMap.value(req)); + if (cond) { + cond->wakeAll(); + } + } + + QMutexLocker locker(&m_mutex); + + m_activeRequests.remove(req); + + //check if there is more job for this worker + if (m_queuedRequests.count() > 0) { + req = m_queuedRequests.dequeue(); + m_activeRequests.insert(req); + worker->assignRequest(req); + } else { + //remove from the active workers list and add it to the idle one + m_activeWorkers.removeOne(worker); + m_idleWorkers.enqueue(worker); + } + } + +} diff --git a/plugins/organizer/maemo6/maemo6itemlocalid.h b/plugins/organizer/mkcal/qorganizerasynchmanager.h index ee8525da02..f2e3fe98d2 100644 --- a/plugins/organizer/maemo6/maemo6itemlocalid.h +++ b/plugins/organizer/mkcal/qorganizerasynchmanager.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** @@ -39,8 +39,8 @@ ** ****************************************************************************/ -#ifndef MAEMO6ITEMLOCALID_H -#define MAEMO6ITEMLOCALID_H +#ifndef QORGANIZERASYNCHMANAGER_H +#define QORGANIZERASYNCHMANAGER_H // // W A R N I N G @@ -53,61 +53,48 @@ // We mean it. // -#include "qorganizeritemenginelocalid.h" +#include "qtorganizer.h" +#include <QtCore/qthread.h> +#include <QtCore/qqueue.h> +#include <QtCore/qmutex.h> +#include <QtCore/qmap.h> +#include <QSharedPointer> +#include <QWaitCondition> QTM_USE_NAMESPACE -class Maemo6ItemLocalId : public QOrganizerItemEngineLocalId +class QOrganizerItemMKCalEngine; +class AsyncWorker; + +class OrganizerAsynchManager { public: - Maemo6ItemLocalId() {} - Maemo6ItemLocalId(const QString& id) : m_id(id) {} - bool isEqualTo(const QOrganizerItemEngineLocalId* other) const - { - return m_id == static_cast<const Maemo6ItemLocalId*>(other)->m_id; - } - bool isLessThan(const QOrganizerItemEngineLocalId* other) const - { - return m_id < static_cast<const Maemo6ItemLocalId*>(other)->m_id; - } - uint engineLocalIdType() const - { - static uint t = qHash("maemo6"); - return t; - } - QOrganizerItemEngineLocalId* clone() const - { - return new Maemo6ItemLocalId(m_id); - } -#ifndef QT_NO_DEBUG_STREAM - QDebug debugStreamOut(QDebug dbg) - { - return dbg << m_id; - } -#endif -#ifndef QT_NO_DATASTREAM - QDataStream& dataStreamOut(QDataStream& out) - { - return out << m_id; - } - QDataStream& dataStreamIn(QDataStream& in) - { - in >> m_id; - return in; - } -#endif - uint hash() const - { - return qHash(m_id); - } + OrganizerAsynchManager(QOrganizerManagerEngine *engine, int maxWorkers = 1); + ~OrganizerAsynchManager(); - QString toString() const - { - return m_id; - } + void requestDestroyed(QOrganizerAbstractRequest *req); + bool addRequest(QOrganizerAbstractRequest *req); + bool cancelRequest(QOrganizerAbstractRequest *req); + bool waitForRequestFinished(QOrganizerAbstractRequest *req, int msecs = -1); private: - QString m_id; + QOrganizerManagerEngine* m_engine; + + int m_maxWorkers; + QQueue<AsyncWorker *> m_idleWorkers; + QQueue<AsyncWorker *> m_activeWorkers; + + QQueue<QOrganizerAbstractRequest *> m_queuedRequests; + QSet<QOrganizerAbstractRequest *> m_activeRequests; + + QMap<QOrganizerAbstractRequest*, QSharedPointer<QWaitCondition> > m_waitMap; + QMutex m_mutexMap; + + QMutex m_mutex; + + void workerDone(AsyncWorker *worker, QOrganizerAbstractRequest *req); + + friend class AsyncWorker; }; -#endif +#endif // QORGANIZERASYNCHMANAGER_H diff --git a/plugins/organizer/organizer.pro b/plugins/organizer/organizer.pro index 45a9457f9c..af52059bde 100644 --- a/plugins/organizer/organizer.pro +++ b/plugins/organizer/organizer.pro @@ -9,8 +9,7 @@ symbian { SUBDIRS += symbian contains(build_unit_tests, yes):SUBDIRS += symbian/tsrc } -# Disable this for now -# maemo6:SUBDIRS += maemo6 +maemo6:SUBDIRS += mkcal maemo5 { contains(maemo5-calendar_enabled, yes) { SUBDIRS += maemo5 diff --git a/src/organizer/items/qorganizerjournal.h b/src/organizer/items/qorganizerjournal.h index c397fbee20..01c5ebf5bb 100644 --- a/src/organizer/items/qorganizerjournal.h +++ b/src/organizer/items/qorganizerjournal.h @@ -53,7 +53,6 @@ public: Q_DECLARE_CUSTOM_ORGANIZER_ITEM(QOrganizerJournal, QOrganizerItemType::TypeJournal) #endif - // XXX TODO: research whether journal is a single point in time, or can cover a period of time... void setDateTime(const QDateTime& dateTime); QDateTime dateTime() const; }; diff --git a/src/organizer/organizer.pro b/src/organizer/organizer.pro index 5dc413f2bd..9d34e1a73c 100644 --- a/src/organizer/organizer.pro +++ b/src/organizer/organizer.pro @@ -84,7 +84,7 @@ maemo5 { } maemo6 { - isEmpty(ORGANIZER_DEFAULT_ENGINE): ORGANIZER_DEFAULT_ENGINE=maemo6 + isEmpty(ORGANIZER_DEFAULT_ENGINE): ORGANIZER_DEFAULT_ENGINE=mkcal } symbian { diff --git a/tests/auto/qorganizermanager/tst_qorganizermanager.cpp b/tests/auto/qorganizermanager/tst_qorganizermanager.cpp index 50c241b37e..a24b65cdee 100644 --- a/tests/auto/qorganizermanager/tst_qorganizermanager.cpp +++ b/tests/auto/qorganizermanager/tst_qorganizermanager.cpp @@ -154,12 +154,15 @@ private slots: void uriParsing(); void compatibleItem(); void recurrenceWithGenerator(); + void todoRecurrenceWithGenerator(); void dateRange(); /* Tests that are run on all managers */ void metadata(); void nullIdOperations(); void add(); + void saveRecurrence(); + void persistence(); void addExceptions(); void addExceptionsWithGuid(); void update(); @@ -172,6 +175,7 @@ private slots: void collections(); void dataSerialization(); void itemFetch(); + void todoItemFetch(); void spanOverDays(); void recurrence(); void idComparison(); @@ -191,11 +195,14 @@ private slots: void uriParsing_data(); void compatibleItem_data(); void recurrenceWithGenerator_data(); + void todoRecurrenceWithGenerator_data(); void dateRange_data(); /* Tests that are run on all managers */ void metadata_data() {addManagers();} void nullIdOperations_data() {addManagers();} void add_data() {addManagers();} + void saveRecurrence_data() {addManagers();} + void persistence_data() {addManagers();} void addExceptions_data() {addManagers();} void addExceptionsWithGuid_data() {addManagers();} void update_data() {addManagers();} @@ -208,6 +215,7 @@ private slots: void collections_data() {addManagers();} void dataSerialization_data() {addManagers();} void itemFetch_data() {addManagers();} + void todoItemFetch_data() {addManagers();} void spanOverDays_data() {addManagers();} void recurrence_data() {addManagers();} void idComparison_data() {addManagers();} @@ -743,7 +751,7 @@ void tst_QOrganizerManager::ctors() #if defined(Q_OS_SYMBIAN) QCOMPARE(defaultStore, QString("symbian")); #elif defined(Q_WS_MAEMO_6) - QCOMPARE(defaultStore, QString("tracker")); + QCOMPARE(defaultStore, QString("mkcal")); #elif defined(Q_WS_MAEMO_5) QCOMPARE(defaultStore, QString("maemo5")); #elif defined(Q_OS_WINCE) @@ -956,6 +964,95 @@ void tst_QOrganizerManager::add() // XXX TODO. } +void tst_QOrganizerManager::saveRecurrence() +{ + QFETCH(QString, uri); + QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); + + QOrganizerEvent event; + event.setDisplayLabel(QLatin1String("meeting")); + event.setStartDateTime(QDateTime(QDate(2010, 1, 1), QTime(11, 0, 0))); + event.setEndDateTime(QDateTime(QDate(2010, 1, 1), QTime(12, 0, 0))); + QOrganizerRecurrenceRule rrule; + rrule.setFrequency(QOrganizerRecurrenceRule::Weekly); + rrule.setLimit(QDate(2011, 1, 1)); + rrule.setInterval(2); + rrule.setDaysOfWeek(QSet<Qt::DayOfWeek>() << Qt::Monday << Qt::Tuesday); + rrule.setDaysOfMonth(QSet<int>() << 1 << 2); + rrule.setDaysOfYear(QSet<int>() << 1 << 2); + rrule.setMonthsOfYear(QSet<QOrganizerRecurrenceRule::Month>() + << QOrganizerRecurrenceRule::January + << QOrganizerRecurrenceRule::February); + rrule.setWeeksOfYear(QSet<int>() << 1 << 2); + rrule.setFirstDayOfWeek(Qt::Tuesday); + // this is disabled because mkcal doesn't support it: + //rrule.setPositions(QSet<int>() << 1 << 2); + event.setRecurrenceRule(rrule); + event.setExceptionRule(rrule); + QSet<QDate> rdates; + rdates << QDate(2010, 1, 4) << QDate(2010, 4, 1); + event.setRecurrenceDates(rdates); + event.setExceptionDates(rdates); + + QVERIFY(cm->saveItem(&event)); + QOrganizerEvent savedEvent(cm->item(event.id())); + QCOMPARE(event.recurrenceRule(), savedEvent.recurrenceRule()); + QCOMPARE(event.exceptionRule(), savedEvent.exceptionRule()); + QCOMPARE(event.recurrenceDates(), savedEvent.recurrenceDates()); + QCOMPARE(event.exceptionDates(), savedEvent.exceptionDates()); +} + +void tst_QOrganizerManager::persistence() +{ + // Test that changes in one manager are visible from another + QFETCH(QString, uri); + QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); + if (cm->managerName() == "memory") + return; // memory engine is not persistent + + cm->removeItems(cm->itemIds()); + QScopedPointer<QOrganizerManager> cm2(QOrganizerManager::fromUri(uri)); + QCOMPARE(cm->items().size(), 0); + + // Add an event + QOrganizerEvent event; + event.setDisplayLabel(QLatin1String("meeting")); + event.setStartDateTime(QDateTime(QDate(2010, 1, 1), QTime(11, 0, 0))); + event.setEndDateTime(QDateTime(QDate(2010, 1, 1), QTime(12, 0, 0))); + QVERIFY(cm->saveItem(&event)); + QTest::qWait(500); + QCOMPARE(cm->items().size(), 1); + QCOMPARE(cm2->items().size(), 1); + + // Remove the event + cm->removeItems(cm->itemIds()); + QTest::qWait(500); + QCOMPARE(cm->items().size(), 0); + qDebug() << cm2->items(); + QCOMPARE(cm2->items().size(), 0); + +#if 0 + // This is disabled because it'll fail on managers that don't support collections + + // Remove all non-default collections + QList<QOrganizerCollection> collections(cm->collections()); + QOrganizerCollectionId defaultCollectionId(cm->defaultCollection().id()); + foreach (const QOrganizerCollection &col, collections) { + QOrganizerCollectionId id(col.id()); + if (id != defaultCollectionId) + cm->removeCollection(id); + } + QTest::qWait(500); + QCOMPARE(cm2->collections().size(), cm->collections().size()); + + QOrganizerCollection collection; + collection.setMetaData(QOrganizerCollection::KeyName, QLatin1String("test collection")); + QVERIFY(cm->saveCollection(&collection)); + QTest::qWait(500); + QCOMPARE(cm2->collections().size(), cm->collections().size()); +#endif +} + void tst_QOrganizerManager::addExceptions() { QFETCH(QString, uri); @@ -1228,7 +1325,7 @@ void tst_QOrganizerManager::update() } } QVERIFY(foundException); - exception.setLocation("different location"); + exception.setDescription("different description"); QVERIFY(cm->saveItem(&exception)); persistentCount = cm->itemsForExport().size(); QCOMPARE(persistentCount, 2); // parent plus one exception @@ -1237,6 +1334,7 @@ void tst_QOrganizerManager::update() foreach (const QOrganizerEventOccurrence& curr, items) { if (curr.startDateTime() == QDateTime(QDate(2010, 10, 21), QTime(8, 0, 0))) { QVERIFY(!curr.id().isNull()); + QCOMPARE(curr.description(), QLatin1String("different description")); } else { QVERIFY(curr.id().isNull()); } @@ -1270,7 +1368,7 @@ void tst_QOrganizerManager::remove() { QFETCH(QString, uri); QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); - + // Use note & todo item depending on backend support QString type; if (cm->detailDefinitions(QOrganizerItemType::TypeNote).count()) @@ -1887,8 +1985,11 @@ void tst_QOrganizerManager::compatibleItem() void tst_QOrganizerManager::recurrenceWithGenerator_data() { QTest::addColumn<QString>("uri"); - QTest::addColumn<QDate>("eventDate"); + QTest::addColumn<QDate>("generatorDate"); QTest::addColumn<QOrganizerRecurrenceRule>("recurrenceRule"); + QTest::addColumn<QOrganizerRecurrenceRule>("exceptionRule"); + QTest::addColumn<QSet<QDate> >("recurrenceDates"); + QTest::addColumn<QSet<QDate> >("exceptionDates"); QTest::addColumn<QDate>("startDate"); QTest::addColumn<QDate>("endDate"); QTest::addColumn<QList<QDate> >("occurrenceDates"); @@ -1902,6 +2003,9 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() managers.removeAll("maliciousplugin"); managers.removeAll("skeleton"); + QOrganizerRecurrenceRule exrule; + QSet<QDate> rdates; + QSet<QDate> exdates; foreach(QString mgr, managers) { QString managerUri = QOrganizerManager::buildUri(mgr, QMap<QString, QString>()); @@ -1910,35 +2014,40 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setFrequency(QOrganizerRecurrenceRule::Weekly); rrule.setLimit(QDate(2010, 1, 22)); QTest::newRow(QString("mgr=%1, weekly recurrence").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2010, 1, 20) // stops at the 15th because the query end date is the 20th << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 8) << QDate(2010, 1, 15)); // change the end date of the query to 2010-02-01 QTest::newRow(QString("mgr=%1, weekly recurrence, end date is inclusive").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2010, 2, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 8) << QDate(2010, 1, 15) << QDate(2010, 1, 22)); // Now let's fiddle with the recurrence end date and see what happens rrule.setLimit(QDate(2010, 1, 23)); QTest::newRow(QString("mgr=%1, weekly recurrence, end date observed (+1)").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2010, 2, 1) // now stop on the 22nd << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 8) << QDate(2010, 1, 15) << QDate(2010, 1, 22)); rrule.setLimit(QDate(2010, 1, 21)); QTest::newRow(QString("mgr=%1, weekly recurrence, end date observed (-1)").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2010, 2, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 8) << QDate(2010, 1, 15)); rrule.setLimit(QDate()); rrule.setLimit(2); QTest::newRow(QString("mgr=%1, weekly recurrence, count").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2010, 2, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 8)); } @@ -1948,7 +2057,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setFrequency(QOrganizerRecurrenceRule::Daily); rrule.setLimit(QDate(2010, 1, 5)); QTest::newRow(QString("mgr=%1, daily").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 2) << QDate(2010, 1, 3) << QDate(2010, 1, 4) @@ -1956,7 +2066,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setInterval(3); QTest::newRow(QString("mgr=%1, daily, interval").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 4)); } @@ -1968,7 +2079,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setDaysOfWeek(QSet<Qt::DayOfWeek>() << Qt::Friday << Qt::Saturday << Qt::Sunday); rrule.setLimit(QDate(2010, 1, 27)); QTest::newRow(QString("mgr=%1, weekly, days of week").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 2) << QDate(2010, 1, 3) << QDate(2010, 1, 8) << QDate(2010, 1, 9) << QDate(2010, 1, 10) @@ -1977,7 +2089,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setInterval(3); QTest::newRow(QString("mgr=%1, weekly, days of week, interval").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 2) << QDate(2010, 1, 3) << QDate(2010, 1, 22) << QDate(2010, 1, 23) << QDate(2010, 1, 24)); @@ -1989,7 +2102,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setDaysOfMonth(QSet<int>() << 1 << 10); rrule.setLimit(QDate(2010, 4, 15)); QTest::newRow(QString("mgr=%1, monthly recurrence").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 10) << QDate(2010, 2, 1) << QDate(2010, 2, 10) @@ -1998,7 +2112,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setInterval(3); QTest::newRow(QString("mgr=%1, monthly recurrence, interval").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 1, 10) << QDate(2010, 4, 1) << QDate(2010, 4, 10)); @@ -2010,7 +2125,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setDaysOfYear(QSet<int>() << 1 << 32); rrule.setLimit(QDate(2012, 3, 15)); QTest::newRow(QString("mgr=%1, yearly recurrence").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 2, 1) << QDate(2011, 1, 1) << QDate(2011, 2, 1) @@ -2019,7 +2135,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setLimit(QDate(2013, 3, 15)); rrule.setInterval(3); QTest::newRow(QString("mgr=%1, yearly recurrence, interval").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 2, 1) << QDate(2013, 1, 1) << QDate(2013, 2, 1)); @@ -2033,7 +2150,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() << QOrganizerRecurrenceRule::March); rrule.setLimit(QDate(2011, 3, 15)); QTest::newRow(QString("mgr=%1, yearly recurrence, by month").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 1) << rrule + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 1) << QDate(2010, 3, 1) << QDate(2011, 1, 1) << QDate(2011, 3, 1)); @@ -2047,7 +2165,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setDaysOfWeek(QSet<Qt::DayOfWeek>() << Qt::Thursday); rrule.setLimit(QDate(2011, 3, 15)); QTest::newRow(QString("mgr=%1, yearly recurrence, by week").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 1, 7) << rrule // this is the first day of week 1 + << managerUri << QDate(2010, 1, 7) // this is the first day of week 1 + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 1, 7) << QDate(2010, 1, 28) << QDate(2011, 1, 6) << QDate(2011, 1, 27)); @@ -2061,7 +2180,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setDaysOfWeek(QSet<Qt::DayOfWeek>() << Qt::Sunday); rrule.setPositions(QSet<int>() << 1); QTest::newRow(QString("mgr=%1, yearly recurrence, first Sunday of April").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 4, 4) << rrule // this is the first Sunday of April 2010 + << managerUri << QDate(2010, 4, 4) // this is the first Sunday of April 2010 + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 4, 4) << QDate(2011, 4, 3) @@ -2078,7 +2198,8 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() rrule.setDaysOfWeek(QSet<Qt::DayOfWeek>() << Qt::Sunday); rrule.setPositions(QSet<int>() << -1); QTest::newRow(QString("mgr=%1, yearly recurrence, last Sunday of October").arg(mgr).toLatin1().constData()) - << managerUri << QDate(2010, 10, 31) << rrule // this is the last Sunday of October 2010 + << managerUri << QDate(2010, 10, 31) // this is the last Sunday of October 2010 + << rrule << exrule << rdates << exdates << QDate(2010, 1, 1) << QDate(2015, 1, 1) << (QList<QDate>() << QDate(2010, 10, 31) << QDate(2011, 10, 30) @@ -2086,24 +2207,73 @@ void tst_QOrganizerManager::recurrenceWithGenerator_data() << QDate(2013, 10, 27) << QDate(2014, 10, 26)); } + + { + QOrganizerRecurrenceRule rrule; // empty + QSet<QDate> rdates; + rdates << QDate(2010, 1, 5) << QDate(2010, 1, 8); + QTest::newRow(QString("mgr=%1, rdates").arg(mgr).toLatin1().constData()) + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates + << QDate(2010, 1, 1) << QDate(2015, 1, 1) + << (QList<QDate>() << QDate(2010, 1, 1) + << QDate(2010, 1, 5) + << QDate(2010, 1, 8)); + } + + { + QOrganizerRecurrenceRule rrule; + rrule.setFrequency(QOrganizerRecurrenceRule::Daily); + QSet<QDate> exdates; + exdates << QDate(2010, 1, 2) << QDate(2010, 1, 3); + QTest::newRow(QString("mgr=%1, exdates").arg(mgr).toLatin1().constData()) + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates + << QDate(2010, 1, 1) << QDate(2010, 1, 5) + << (QList<QDate>() << QDate(2010, 1, 1) + << QDate(2010, 1, 4) + << QDate(2010, 1, 5)); + } + + { + QOrganizerRecurrenceRule rrule; + rrule.setFrequency(QOrganizerRecurrenceRule::Daily); + QOrganizerRecurrenceRule exrule; + exrule.setFrequency(QOrganizerRecurrenceRule::Monthly); + exrule.setDaysOfMonth(QSet<int>() << 2 << 3); + QTest::newRow(QString("mgr=%1, exrule").arg(mgr).toLatin1().constData()) + << managerUri << QDate(2010, 1, 1) + << rrule << exrule << rdates << exdates + << QDate(2010, 1, 1) << QDate(2010, 1, 5) + << (QList<QDate>() << QDate(2010, 1, 1) + << QDate(2010, 1, 4) + << QDate(2010, 1, 5)); + } } } void tst_QOrganizerManager::recurrenceWithGenerator() { QFETCH(QString, uri); - QFETCH(QDate, eventDate); + QFETCH(QDate, generatorDate); QFETCH(QOrganizerRecurrenceRule, recurrenceRule); - QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); + QFETCH(QOrganizerRecurrenceRule, exceptionRule); + QFETCH(QSet<QDate>, recurrenceDates); + QFETCH(QSet<QDate>, exceptionDates); QFETCH(QDate, startDate); QFETCH(QDate, endDate); QFETCH(QList<QDate>, occurrenceDates); + QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); + QOrganizerEvent event; event.setDisplayLabel("event"); - event.setStartDateTime(QDateTime(eventDate, QTime(11, 0, 0))); - event.setEndDateTime(QDateTime(eventDate, QTime(11, 30, 0))); + event.setStartDateTime(QDateTime(generatorDate, QTime(11, 0, 0))); + event.setEndDateTime(QDateTime(generatorDate, QTime(11, 30, 0))); event.setRecurrenceRule(recurrenceRule); + event.setExceptionRule(exceptionRule); + event.setRecurrenceDates(recurrenceDates); + event.setExceptionDates(exceptionDates); if (cm->saveItem(&event)) { QList<QOrganizerItem> items = cm->itemOccurrences(event, @@ -2114,12 +2284,7 @@ void tst_QOrganizerManager::recurrenceWithGenerator() for (int i = 0; i < items.size(); i++) { QOrganizerItem item = items.at(i); QCOMPARE(item.type(), QString(QLatin1String(QOrganizerItemType::TypeEventOccurrence))); - QDate occurrenceDate; - if (item.type() == QOrganizerItemType::TypeEventOccurrence) { - occurrenceDate = item.detail<QOrganizerEventTime>().startDateTime().date(); - } else if (item.type() == QOrganizerItemType::TypeTodoOccurrence) { - occurrenceDate = item.detail<QOrganizerTodoTime>().startDateTime().date(); - } + QDate occurrenceDate = item.detail<QOrganizerEventTime>().startDateTime().date(); //QCOMPARE(occurrenceDate, occurrenceDates.at(i)); actualDates << occurrenceDate; } @@ -2136,6 +2301,61 @@ void tst_QOrganizerManager::recurrenceWithGenerator() } } +void tst_QOrganizerManager::todoRecurrenceWithGenerator_data() +{ + recurrenceWithGenerator_data(); +} + +// This is just a copy of recurrenceWithGenerator, but for todos, not events +void tst_QOrganizerManager::todoRecurrenceWithGenerator() +{ + QFETCH(QString, uri); + QFETCH(QDate, generatorDate); + QFETCH(QOrganizerRecurrenceRule, recurrenceRule); + QFETCH(QOrganizerRecurrenceRule, exceptionRule); + QFETCH(QSet<QDate>, recurrenceDates); + QFETCH(QSet<QDate>, exceptionDates); + QFETCH(QDate, startDate); + QFETCH(QDate, endDate); + QFETCH(QList<QDate>, occurrenceDates); + + QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); + + QOrganizerTodo todo; + todo.setDisplayLabel("todo"); + todo.setStartDateTime(QDateTime(generatorDate, QTime(11, 0, 0))); + todo.setDueDateTime(QDateTime(generatorDate, QTime(11, 30, 0))); + todo.setRecurrenceRule(recurrenceRule); + todo.setExceptionRule(exceptionRule); + todo.setRecurrenceDates(recurrenceDates); + todo.setExceptionDates(exceptionDates); + + if (cm->saveItem(&todo)) { + QList<QOrganizerItem> items = cm->itemOccurrences(todo, + QDateTime(startDate, QTime(0, 0, 0)), + QDateTime(endDate, QTime(23, 59, 59, 999))); + + QList<QDate> actualDates; + for (int i = 0; i < items.size(); i++) { + QOrganizerItem item = items.at(i); + QCOMPARE(item.type(), QString(QLatin1String(QOrganizerItemType::TypeTodoOccurrence))); + QDate occurrenceDate = item.detail<QOrganizerTodoTime>().startDateTime().date(); + //QCOMPARE(occurrenceDate, occurrenceDates.at(i)); + actualDates << occurrenceDate; + } + + if (actualDates != occurrenceDates) { + qDebug() << "Actual: " << actualDates; + qDebug() << "Expected: " << occurrenceDates; + QCOMPARE(actualDates, occurrenceDates); + } + } else { + // Allow backend specific limitations + QCOMPARE(cm->error(), QOrganizerManager::NotSupportedError); + qWarning() << "The todo not supported by the backend"; + } +} + void tst_QOrganizerManager::itemValidation() { /* Use the memory engine as a reference (validation is not engine specific) */ @@ -2242,9 +2462,10 @@ void tst_QOrganizerManager::signalEmission() qRegisterMetaType<QOrganizerItemId>("QOrganizerItemId"); qRegisterMetaType<QList<QOrganizerItemId> >("QList<QOrganizerItemId>"); - QSignalSpy spyCA(m1.data(), SIGNAL(itemsAdded(QList<QOrganizerItemId>))); - QSignalSpy spyCM(m1.data(), SIGNAL(itemsChanged(QList<QOrganizerItemId>))); - QSignalSpy spyCR(m1.data(), SIGNAL(itemsRemoved(QList<QOrganizerItemId>))); + QSignalSpy spyAdded(m1.data(), SIGNAL(itemsAdded(QList<QOrganizerItemId>))); + QSignalSpy spyModified(m1.data(), SIGNAL(itemsChanged(QList<QOrganizerItemId>))); + QSignalSpy spyRemoved(m1.data(), SIGNAL(itemsRemoved(QList<QOrganizerItemId>))); + QSignalSpy spyChanged(m1.data(), SIGNAL(dataChanged())); QList<QVariant> args; QList<QOrganizerItemId> arg; @@ -2263,8 +2484,8 @@ void tst_QOrganizerManager::signalEmission() QVERIFY(m1->saveItem(&todo)); QOrganizerItemId cid = todo.id(); addSigCount += 1; - QTRY_COMPARE(spyCA.count(), addSigCount); - args = spyCA.takeFirst(); + QTRY_COMPARE(spyAdded.count(), addSigCount); + args = spyAdded.takeFirst(); addSigCount -= 1; arg = args.first().value<QList<QOrganizerItemId> >(); QVERIFY(arg.count() == 1); @@ -2275,8 +2496,8 @@ void tst_QOrganizerManager::signalEmission() QVERIFY(todo.saveDetail(&nc)); QVERIFY(m1->saveItem(&todo)); modSigCount += 1; - QTRY_COMPARE(spyCM.count(), modSigCount); - args = spyCM.takeFirst(); + QTRY_COMPARE(spyModified.count(), modSigCount); + args = spyModified.takeFirst(); modSigCount -= 1; arg = args.first().value<QList<QOrganizerItemId> >(); QVERIFY(arg.count() == 1); @@ -2285,8 +2506,8 @@ void tst_QOrganizerManager::signalEmission() // verify remove emits signal removed QVERIFY(m1->removeItem(todo.id())); remSigCount += 1; - QTRY_COMPARE(spyCR.count(), remSigCount); - args = spyCR.takeFirst(); + QTRY_COMPARE(spyRemoved.count(), remSigCount); + args = spyRemoved.takeFirst(); remSigCount -= 1; arg = args.first().value<QList<QOrganizerItemId> >(); QVERIFY(arg.count() == 1); @@ -2303,8 +2524,8 @@ void tst_QOrganizerManager::signalEmission() addSigCount += 1; QVERIFY(m1->saveItem(&todo3)); addSigCount += 1; - QTRY_COMPARE(spyCM.count(), modSigCount); - QTRY_COMPARE(spyCA.count(), addSigCount); + QTRY_COMPARE(spyModified.count(), modSigCount); + QTRY_COMPARE(spyAdded.count(), addSigCount); // verify multiple modifies works as advertised nc2.setLabel("M."); @@ -2319,21 +2540,21 @@ void tst_QOrganizerManager::signalEmission() modSigCount += 1; QVERIFY(m1->saveItem(&todo3)); modSigCount += 1; - QTRY_COMPARE(spyCM.count(), modSigCount); + QTRY_COMPARE(spyModified.count(), modSigCount); // verify multiple removes works as advertised m1->removeItem(todo3.id()); remSigCount += 1; m1->removeItem(todo2.id()); remSigCount += 1; - QTRY_COMPARE(spyCR.count(), remSigCount); + QTRY_COMPARE(spyRemoved.count(), remSigCount); QVERIFY(!m1->removeItem(todo.id())); // not saved. /* Now test the batch equivalents */ - spyCA.clear(); - spyCM.clear(); - spyCR.clear(); + spyAdded.clear(); + spyModified.clear(); + spyRemoved.clear(); /* Batch adds - set ids to zero so add succeeds. */ todo.setId(QOrganizerItemId()); @@ -2352,9 +2573,9 @@ void tst_QOrganizerManager::signalEmission() /* We basically loop, processing events, until we've seen an Add signal for each item */ sigids.clear(); - QTRY_WAIT( while(spyCA.size() > 0) {sigids += spyCA.takeFirst().at(0).value<QList<QOrganizerItemId> >(); }, sigids.contains(todo.id()) && sigids.contains(todo2.id()) && sigids.contains(todo3.id())); - QTRY_COMPARE(spyCM.count(), 0); - QTRY_COMPARE(spyCR.count(), 0); + QTRY_WAIT( while(spyAdded.size() > 0) {sigids += spyAdded.takeFirst().at(0).value<QList<QOrganizerItemId> >(); }, sigids.contains(todo.id()) && sigids.contains(todo2.id()) && sigids.contains(todo3.id())); + QTRY_COMPARE(spyModified.count(), 0); + QTRY_COMPARE(spyRemoved.count(), 0); /* Batch modifies */ QOrganizerItemDisplayLabel modifiedName = todo.detail(QOrganizerItemDisplayLabel::DefinitionName); @@ -2370,7 +2591,7 @@ void tst_QOrganizerManager::signalEmission() errorMap = m1->errorMap(); sigids.clear(); - QTRY_WAIT( while(spyCM.size() > 0) {sigids += spyCM.takeFirst().at(0).value<QList<QOrganizerItemId> >(); }, sigids.contains(todo.id()) && sigids.contains(todo2.id()) && sigids.contains(todo3.id())); + QTRY_WAIT( while(spyModified.size() > 0) {sigids += spyModified.takeFirst().at(0).value<QList<QOrganizerItemId> >(); }, sigids.contains(todo.id()) && sigids.contains(todo2.id()) && sigids.contains(todo3.id())); /* Batch removes */ batchRemove << todo.id() << todo2.id() << todo3.id(); @@ -2378,20 +2599,24 @@ void tst_QOrganizerManager::signalEmission() errorMap = m1->errorMap(); sigids.clear(); - QTRY_WAIT( while(spyCR.size() > 0) {sigids += spyCR.takeFirst().at(0).value<QList<QOrganizerItemId> >(); }, sigids.contains(todo.id()) && sigids.contains(todo2.id()) && sigids.contains(todo3.id())); + QTRY_WAIT( while(spyRemoved.size() > 0) {sigids += spyRemoved.takeFirst().at(0).value<QList<QOrganizerItemId> >(); }, sigids.contains(todo.id()) && sigids.contains(todo2.id()) && sigids.contains(todo3.id())); - QTRY_COMPARE(spyCA.count(), 0); - QTRY_COMPARE(spyCM.count(), 0); + QTRY_COMPARE(spyAdded.count(), 0); + QTRY_COMPARE(spyModified.count(), 0); QScopedPointer<QOrganizerManager> m2(QOrganizerManager::fromUri(uri)); QVERIFY(m1->hasFeature(QOrganizerManager::Anonymous) == m2->hasFeature(QOrganizerManager::Anonymous)); /* Now some cross manager testing */ + spyAdded.clear(); + spyModified.clear(); + spyRemoved.clear(); + spyChanged.clear(); if (!m1->hasFeature(QOrganizerManager::Anonymous)) { // verify that signals are emitted for modifications made to other managers (same id). - QSignalSpy spyDC(m1.data(), SIGNAL(dataChanged())); - spyDC.clear(); + QSignalSpy spyDataChanged(m1.data(), SIGNAL(dataChanged())); + spyDataChanged.clear(); QOrganizerItemDisplayLabel ncs = todo.detail(QOrganizerItemDisplayLabel::DefinitionName); ncs.setLabel("Test"); QVERIFY(todo.saveDetail(&ncs)); @@ -2404,13 +2629,13 @@ void tst_QOrganizerManager::signalEmission() QVERIFY(m2->saveItem(&todo)); // we should have one addition and one modification (or at least a data changed signal). - QTRY_VERIFY(spyDC.count() || (spyCA.count() == 1)); // check that we received the update signals. - QTRY_VERIFY(spyDC.count() || (spyCM.count() == 1)); // check that we received the update signals. + QTRY_VERIFY(spyDataChanged.count() || (spyAdded.count() == 1)); // check that we received the update signals. + QTRY_VERIFY(spyDataChanged.count() || (spyModified.count() == 1)); // check that we received the update signals. todo = m2->item(todo.id()); // reload it. QVERIFY(m1->item(todo.id()) == todo); // ensure we can read it from m1. - spyDC.clear(); + spyDataChanged.clear(); m2->removeItem(todo.id()); - QTRY_VERIFY(spyDC.count() || (spyCR.count() == 1)); // check that we received the remove signal. + QTRY_VERIFY(spyDataChanged.count() || (spyRemoved.count() == 1)); // check that we received the remove signal. } } @@ -3069,6 +3294,149 @@ void tst_QOrganizerManager::itemFetch() QCOMPARE(eventOccurrenceCount, 3); } +// This is just a copy of itemFetch(), but for todos +void tst_QOrganizerManager::todoItemFetch() +{ + QFETCH(QString, uri); + QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); + + cm->removeItems(cm->itemIds()); // empty the calendar to prevent the previous test from interfering this one + + QOrganizerTodo todo; + todo.setDisplayLabel("todo"); + todo.setStartDateTime(QDateTime(QDate(2010, 9, 9), QTime(11, 0, 0))); + todo.setDueDateTime(QDateTime(QDate(2010, 9, 9), QTime(11, 30, 0))); + QVERIFY(cm->saveItem(&todo)); + + QOrganizerTodo recTodo; + recTodo.setDisplayLabel("daily todo"); + recTodo.setStartDateTime(QDateTime(QDate(2010, 9, 1), QTime(16, 0, 0))); + recTodo.setDueDateTime(QDateTime(QDate(2010, 9, 1), QTime(16, 30, 0))); + QOrganizerRecurrenceRule rrule; + rrule.setFrequency(QOrganizerRecurrenceRule::Daily); + rrule.setLimit(QDate(2010, 9, 10)); + recTodo.setRecurrenceRule(rrule); + QVERIFY(cm->saveItem(&recTodo)); + + //fetch all recurrences + QList<QOrganizerItem> items = cm->items(QDateTime(QDate(2010, 9, 8)), + QDateTime(QDate(2010, 9, 12))); + QCOMPARE(items.count(), 4); // should return todo + 3 x occurrencesOfRecTodo + + //fetch only the originating items + items = cm->itemsForExport(QDateTime(QDate(2010, 9, 8)), QDateTime(QDate(2010, 9, 12)), + QOrganizerItemFilter(), QList<QOrganizerItemSortOrder>(), QOrganizerItemFetchHint()); + QCOMPARE(items.count(), 2); + + // test semantics of items(): + // first - save todo with multiple occurrences; call items() -- should get back just occurrences. + cm->removeItems(cm->itemIds()); // empty the calendar to prevent the previous test from interfering this one + rrule.setLimit(QDate(2010, 9, 3)); + recTodo.setRecurrenceRule(rrule); + recTodo.setId(QOrganizerItemId()); + cm->saveItem(&recTodo); + items = cm->items(QDateTime(), QDateTime()); + QCOMPARE(items.count(), 3); + foreach (const QOrganizerItem& item, items) { + QVERIFY(item.type() == QOrganizerItemType::TypeTodoOccurrence); + } + + // second - the same situation, but giving a time span that only covers the first day - should get back a single occurrence. + items = cm->items(QDateTime(QDate(2010, 9, 1), QTime(15, 0, 0)), QDateTime(QDate(2010, 9, 1), QTime(18, 0, 0))); + QCOMPARE(items.count(), 1); + foreach (const QOrganizerItem& item, items) { + QVERIFY(item.type() == QOrganizerItemType::TypeTodoOccurrence); + } + + // third - save event with no recurrence; call items() -- should get back that parent, not an occurrence. + cm->removeItems(cm->itemIds()); // empty the calendar to prevent the previous test from interfering this one + recTodo.setRecurrenceRules(QSet<QOrganizerRecurrenceRule>()); // clear rrule. + recTodo.setId(QOrganizerItemId()); + cm->saveItem(&recTodo); + items = cm->items(QDateTime(), QDateTime()); + QCOMPARE(items.count(), 1); + foreach (const QOrganizerItem& item, items) { + QVERIFY(item.type() == QOrganizerItemType::TypeTodo); + } + + // fourth - the same situation, but giving a time span. should still get back the parent. + items = cm->items(QDateTime(QDate(2010, 9, 1), QTime(15, 0, 0)), QDateTime(QDate(2010, 9, 1), QTime(18, 0, 0))); + QCOMPARE(items.count(), 1); + foreach (const QOrganizerItem& item, items) { + QVERIFY(item.type() == QOrganizerItemType::TypeTodo); + } + + // test semantics of itemsForExport(): + // first - save event with multiple occurrences; call ife() -- get back that parent + cm->removeItems(cm->itemIds()); // empty the calendar to prevent the previous test from interfering this one + recTodo.setRecurrenceRule(rrule); + recTodo.setId(QOrganizerItemId()); + cm->saveItem(&recTodo); + items = cm->itemsForExport(); + QCOMPARE(items.count(), 1); + foreach (const QOrganizerItem& item, items) { + QVERIFY(item.type() == QOrganizerItemType::TypeTodo); + } + + // second - call items, resave only the first occurrence as an exception, + // call ife() -- get back parent + exception + items = cm->items(); + int todoCount = 0; + int todoOccurrenceCount = 0; + foreach (const QOrganizerItem& item, items) { + if (item.type() == QOrganizerItemType::TypeTodoOccurrence) { + if (todoOccurrenceCount == 0) { + QOrganizerTodoOccurrence exception(item); + exception.setDisplayLabel("exception"); + QVERIFY(cm->saveItem(&exception)); + } + todoOccurrenceCount++; + } else if (item.type() == QOrganizerItemType::TypeTodo) { + todoCount++; + } + } + QCOMPARE(todoOccurrenceCount, 3); + QCOMPARE(todoCount, 0); + items = cm->itemsForExport(); + QCOMPARE(items.count(), 2); + todoCount = 0; + todoOccurrenceCount = 0; + foreach (const QOrganizerItem& item, items) { + if (item.type() == QOrganizerItemType::TypeTodo) { + todoCount += 1; + } else if (item.type() == QOrganizerItemType::TypeTodoOccurrence) { + todoOccurrenceCount += 1; + } + QVERIFY(!item.id().isNull()); // should NEVER be null, since that would be a generated occurrence. + } + QCOMPARE(todoCount, 1); + QCOMPARE(todoOccurrenceCount, 1); + + // third, have all occurrences persisted + items = cm->items(); + foreach (const QOrganizerItem& item, items) { + if (item.type() == QOrganizerItemType::TypeTodoOccurrence) { + QOrganizerTodoOccurrence exception(item); + exception.setDisplayLabel("exception"); + QVERIFY(cm->saveItem(&exception)); + } + } + items = cm->itemsForExport(); + QCOMPARE(items.size(), 4); + todoCount = 0; + todoOccurrenceCount = 0; + foreach (const QOrganizerItem& item, items) { + if (item.type() == QOrganizerItemType::TypeTodo) { + todoCount += 1; + } else if (item.type() == QOrganizerItemType::TypeTodoOccurrence) { + todoOccurrenceCount += 1; + } + QVERIFY(!item.id().isNull()); // should NEVER be null, since that would be a generated occurrence. + } + QCOMPARE(todoCount, 1); + QCOMPARE(todoOccurrenceCount, 3); +} + void tst_QOrganizerManager::spanOverDays() { QFETCH(QString, uri); @@ -3265,7 +3633,7 @@ void tst_QOrganizerManager::idComparison() { QFETCH(QString, uri); QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); - + // Can we run this test? if (!cm->supportedItemTypes().contains(QOrganizerItemType::TypeJournal)) { QSKIP("Backend not compatible with this test", SkipSingle); @@ -3340,7 +3708,8 @@ void tst_QOrganizerManager::idComparison() QVERIFY(bcid1 < bcid2); QVERIFY(bcid3 < bcid2); - QVERIFY(e1id < e2id); // this test may be unstable, depending on the backend? + if (!uri.contains("mkcal")) //this verification makes no sense for mkcal + QVERIFY(e1id < e2id); // this test may be unstable, depending on the backend? // now we do some tests which might be unstable QVERIFY(bcid1 < c1id); // collectionIds: the first comparison should be manager uri, and bcid manager uri is early in the alphabet. @@ -3513,74 +3882,81 @@ void tst_QOrganizerManager::detailOrders() { QFETCH(QString, uri); QScopedPointer<QOrganizerManager> cm(QOrganizerManager::fromUri(uri)); - + if (cm->managerName() == "symbian") QSKIP("symbian manager does not support detail ordering", SkipSingle); if (cm->managerName() == "maemo5") QSKIP("maemo5 manager does not support detail ordering", SkipSingle); - + QOrganizerEvent a; - // comments - QOrganizerItemComment comment1, comment2, comment3; - - comment1.setComment("11111111"); - comment2.setComment("22222222"); - comment3.setComment("33333333"); + // comments are not supported in mkcal + if (cm->managerName() != "mkcal") { + // comments + QOrganizerItemComment comment1, comment2, comment3; - a.saveDetail(&comment1); - a.saveDetail(&comment2); - a.saveDetail(&comment3); + comment1.setComment("11111111"); + comment2.setComment("22222222"); + comment3.setComment("33333333"); - QVERIFY(cm->saveItem(&a)); - a = cm->item(a.id()); - - QList<QOrganizerItemDetail> details = a.details(QOrganizerItemComment::DefinitionName); - QVERIFY(details.count() == 3); - - QVERIFY(a.removeDetail(&comment2)); - QVERIFY(cm->saveItem(&a)); - a = cm->item(a.id()); - details = a.details(QOrganizerItemComment::DefinitionName); - QVERIFY(details.count() == 2); - - a.saveDetail(&comment2); - QVERIFY(cm->saveItem(&a)); - a = cm->item(a.id()); - - details = a.details(QOrganizerItemComment::DefinitionName); - QVERIFY(details.count() == 3); + a.saveDetail(&comment1); + a.saveDetail(&comment2); + a.saveDetail(&comment3); + + QVERIFY(cm->saveItem(&a)); + a = cm->item(a.id()); + + QList<QOrganizerItemDetail> details = a.details(QOrganizerItemComment::DefinitionName); + QVERIFY(details.count() == 3); + + QVERIFY(a.removeDetail(&comment2)); + QVERIFY(cm->saveItem(&a)); + a = cm->item(a.id()); + details = a.details(QOrganizerItemComment::DefinitionName); + QVERIFY(details.count() == 2); + + a.saveDetail(&comment2); + QVERIFY(cm->saveItem(&a)); + a = cm->item(a.id()); + + details = a.details(QOrganizerItemComment::DefinitionName); + QVERIFY(details.count() == 3); + } //addresses + { + QOrganizerItemLocation address1, address2, address3; - QOrganizerItemLocation address1, address2, address3; - - address1.setLabel("Brandl St"); - address3 = address2 = address1; + address1.setLabel("Brandl St"); + address3 = address2 = address1; - a.saveDetail(&address1); - a.saveDetail(&address2); - a.saveDetail(&address3); + a.saveDetail(&address1); + a.saveDetail(&address2); + a.saveDetail(&address3); - QVERIFY(cm->saveItem(&a)); - a = cm->item(a.id()); - - details = a.details(QOrganizerItemLocation::DefinitionName); - QVERIFY(details.count() == 1); // 1 location - they're unique - - QVERIFY(a.removeDetail(&address3)); // remove the most recent. - QVERIFY(cm->saveItem(&a)); - a = cm->item(a.id()); - details = a.details(QOrganizerItemLocation::DefinitionName); - QVERIFY(details.count() == 0); // unique, remove one means none left. - - a.saveDetail(&address2); - QVERIFY(cm->saveItem(&a)); - a = cm->item(a.id()); - - details = a.details(QOrganizerItemLocation::DefinitionName); - QVERIFY(details.count() == 1); // add one back. + QVERIFY(cm->saveItem(&a)); + a = cm->item(a.id()); + + QList<QOrganizerItemDetail> details = a.details(QOrganizerItemLocation::DefinitionName); + QVERIFY(details.count() == 1); // 1 location - they're unique + + // Detail keys for the moment are not persistent through an item save / fetch + address3 = details.at(0); + + QVERIFY(a.removeDetail(&address3)); // remove the most recent. + QVERIFY(cm->saveItem(&a)); + a = cm->item(a.id()); + details = a.details(QOrganizerItemLocation::DefinitionName); + QVERIFY(details.count() == 0); // unique, remove one means none left. + + a.saveDetail(&address2); + QVERIFY(cm->saveItem(&a)); + a = cm->item(a.id()); + + details = a.details(QOrganizerItemLocation::DefinitionName); + QVERIFY(details.count() == 1); // add one back. + } } @@ -3620,7 +3996,6 @@ void tst_QOrganizerManager::collections() QOrganizerEvent i1, i2, i3, i4, i5; i1.setDisplayLabel("one"); - i1.addComment("item one"); i2.setDisplayLabel("two"); i2.setDescription("this is the second item"); i3.setDisplayLabel("three"); @@ -3695,7 +4070,7 @@ void tst_QOrganizerManager::collections() i4 = saveList.at(2); i5 = saveList.at(3); QList<QOrganizerItem> fetchedItems = oim->items(); - QVERIFY(fetchedItems.count() == (originalItemCount + 4)); + QCOMPARE(fetchedItems.count(), originalItemCount + 4); QVERIFY(fetchedItems.contains(i2)); // these three should have been added QVERIFY(fetchedItems.contains(i3)); QVERIFY(fetchedItems.contains(i4)); @@ -3710,6 +4085,29 @@ void tst_QOrganizerManager::collections() QVERIFY(fetchedItems.contains(i4)); QVERIFY(fetchedItems.contains(i5)); + // exceptions must be saved in the same collection as their parent. + QOrganizerEvent recurringEvent; + recurringEvent.setDescription("A recurring test event parent."); + recurringEvent.setLocation("Some Location"); + recurringEvent.setStartDateTime(QDateTime(QDate(2010,10,5), QTime(10,30))); + recurringEvent.setEndDateTime(QDateTime(QDate(2010,10,5), QTime(11,30))); + QOrganizerRecurrenceRule rrule; + rrule.setFrequency(QOrganizerRecurrenceRule::Weekly); + rrule.setLimit(5); // count limited. + recurringEvent.setRecurrenceRule(rrule); + recurringEvent.setCollectionId(c2.id()); + QVERIFY(oim->saveItem(&recurringEvent)); + recurringEvent = oim->item(recurringEvent.id()); // reload it. + QVERIFY(recurringEvent.collectionId() == c2.id()); + QList<QOrganizerItem> occ(oim->itemOccurrences(recurringEvent, QDateTime(), QDateTime())); + QVERIFY(occ.size() == 5); + QOrganizerEventOccurrence someException = occ.at(2); // there should be five, so this shouldn't segfault. + someException.setLocation("Other Location"); + someException.setCollectionId(c3.id()); // different to parent. + QVERIFY(!oim->saveItem(&someException)); // shouldn't work. + someException.setCollectionId(c2.id()); // same as parent. + QVERIFY(oim->saveItem(&someException)); // should work. + // remove a collection, removes its items. QVERIFY(oim->removeCollection(c2.id())); fetchedItems = oim->items(); @@ -3717,6 +4115,8 @@ void tst_QOrganizerManager::collections() QVERIFY(!fetchedItems.contains(i2)); // these three should have been removed QVERIFY(!fetchedItems.contains(i3)); QVERIFY(!fetchedItems.contains(i4)); + QVERIFY(!fetchedItems.contains(recurringEvent)); // the parent + QVERIFY(!fetchedItems.contains(someException)); // and exceptions too. QVERIFY(fetchedItems.contains(i5)); // i5 should not have been removed. // attempt to save an item in a non-existent collection should fail. |