diff --git a/resources/kolab/kolabhelpers.cpp b/resources/kolab/kolabhelpers.cpp index 695105973..6898e0bb5 100644 --- a/resources/kolab/kolabhelpers.cpp +++ b/resources/kolab/kolabhelpers.cpp @@ -1,510 +1,513 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kolabhelpers.h" #include #include #include #include #include #include #include #include #include #include "tracer.h" bool KolabHelpers::checkForErrors(const Akonadi::Item &item) { if (!Kolab::ErrorHandler::instance().errorOccured()) { Kolab::ErrorHandler::instance().clear(); return false; } QString errorMsg; foreach (const Kolab::ErrorHandler::Err &error, Kolab::ErrorHandler::instance().getErrors()) { errorMsg.append(error.message); errorMsg.append(QLatin1String("\n")); } kWarning() << "Error on item with id: " << item.id() << " remote id: " << item.remoteId() << ":\n" << errorMsg; Kolab::ErrorHandler::instance().clear(); return true; } Akonadi::Item getErrorItem(Kolab::FolderType folderType, const QString &remoteId) { //TODO set title, text and icon Akonadi::Item item; item.setRemoteId(remoteId); switch (folderType) { case Kolab::EventType: { KCalCore::Event::Ptr event(new KCalCore::Event); //FIXME Use message creation date time event->setDtStart(KDateTime::currentUtcDateTime()); event->setSummary(i18n("Corrupt Event")); event->setDescription(i18n("Event could not be read. Delete this event to remove it from the server.")); item.setMimeType(KCalCore::Event::eventMimeType()); item.setPayload(event); } break; case Kolab::TaskType: { KCalCore::Todo::Ptr task(new KCalCore::Todo); //FIXME Use message creation date time task->setDtStart(KDateTime::currentUtcDateTime()); task->setSummary(i18n("Corrupt Task")); task->setDescription(i18n("Task could not be read. Delete this task to remove it from the server.")); item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(task); } break; case Kolab::JournalType: { KCalCore::Journal::Ptr journal(new KCalCore::Journal); //FIXME Use message creation date time journal->setDtStart(KDateTime::currentUtcDateTime()); journal->setSummary(i18n("Corrupt journal")); journal->setDescription(i18n("Journal could not be read. Delete this journal to remove it from the server.")); item.setMimeType(KCalCore::Journal::journalMimeType()); item.setPayload(journal); } break; case Kolab::ContactType: { KABC::Addressee addressee; addressee.setName(i18n("Corrupt Contact")); addressee.setNote(i18n("Contact could not be read. Delete this contact to remove it from the server.")); item.setMimeType(KABC::Addressee::mimeType()); item.setPayload(addressee); } break; case Kolab::NoteType: { Akonadi::NoteUtils::NoteMessageWrapper note; note.setTitle(i18n("Corrupt Note")); note.setText(i18n("Note could not be read. Delete this note to remove it from the server.")); item.setPayload(Akonadi::NoteUtils::noteMimeType()); item.setPayload(note.message()); } break; case Kolab::MailType: //We don't convert mails, so that should never fail. default: kDebug() << "unhandled folder type: " << folderType; } return item; } Akonadi::Item KolabHelpers::translateFromImap(Kolab::FolderType folderType, const Akonadi::Item &imapItem, bool &ok) { //Avoid trying to convert imap messages if (folderType == Kolab::MailType) { return imapItem; } //No payload, so it's a flag change. We ignore flag changes on groupware data. if (!imapItem.hasPayload()) { ok = false; return Akonadi::Item(); } if (!imapItem.hasPayload()) { kWarning() << "Payload is not a MessagePtr!"; Q_ASSERT(false); ok = false; return Akonadi::Item(); } const KMime::Message::Ptr payload = imapItem.payload(); const Kolab::KolabObjectReader reader(payload); if (checkForErrors(imapItem)) { ok = true; //We return an error object so the sync keeps working, and we can clean up the mess by simply deleting the object in the application. return getErrorItem(folderType, imapItem.remoteId()); } switch (reader.getType()) { case Kolab::EventObject: case Kolab::TodoObject: case Kolab::JournalObject: { const KCalCore::Incidence::Ptr incidencePtr = reader.getIncidence(); if (!incidencePtr) { kWarning() << "Failed to read incidence."; ok = false; return Akonadi::Item(); } Akonadi::Item newItem(incidencePtr->mimeType()); newItem.setPayload(incidencePtr); newItem.setRemoteId(imapItem.remoteId()); newItem.setGid(incidencePtr->instanceIdentifier()); return newItem; } break; case Kolab::NoteObject: { const KMime::Message::Ptr note = reader.getNote(); if (!note) { kWarning() << "Failed to read note."; ok = false; return Akonadi::Item(); } Akonadi::Item newItem(QLatin1String("text/x-vnd.akonadi.note")); newItem.setPayload(note); newItem.setRemoteId(imapItem.remoteId()); const Akonadi::NoteUtils::NoteMessageWrapper wrapper(note); newItem.setGid(wrapper.uid()); return newItem; } break; case Kolab::ContactObject: { Akonadi::Item newItem(KABC::Addressee::mimeType()); newItem.setPayload(reader.getContact()); newItem.setRemoteId(imapItem.remoteId()); newItem.setGid(reader.getContact().uid()); return newItem; } break; case Kolab::DistlistObject: { KABC::ContactGroup contactGroup = reader.getDistlist(); QList toAdd; for (uint index = 0; index < contactGroup.contactReferenceCount(); ++index) { const KABC::ContactGroup::ContactReference& reference = contactGroup.contactReference(index); KABC::ContactGroup::ContactReference ref; ref.setGid(reference.uid()); //libkolab set a gid with setUid() toAdd << ref; } contactGroup.removeAllContactReferences(); foreach (const KABC::ContactGroup::ContactReference &ref, toAdd) { contactGroup.append(ref); } Akonadi::Item newItem(KABC::ContactGroup::mimeType()); newItem.setPayload(contactGroup); newItem.setRemoteId(imapItem.remoteId()); newItem.setGid(contactGroup.id()); return newItem; } break; default: kWarning() << "Object type not handled"; ok = false; break; } return Akonadi::Item(); } Akonadi::Item::List KolabHelpers::translateToImap(const Akonadi::Item::List &items, bool &ok) { Akonadi::Item::List imapItems; Q_FOREACH(const Akonadi::Item &item, items) { bool translationOk = true; imapItems << translateToImap(item, translationOk); if (!translationOk) { ok = false; } } return imapItems; } static KABC::ContactGroup convertToGidOnly(const KABC::ContactGroup &contactGroup) { QList toAdd; for ( uint index = 0; index < contactGroup.contactReferenceCount(); ++index ) { const KABC::ContactGroup::ContactReference& reference = contactGroup.contactReference( index ); QString gid; if (!reference.gid().isEmpty()) { gid = reference.gid(); } else { // WARNING: this is an ugly hack for backwards compatiblity. Normally this codepath shouldn't be hit. // Replace all references with real data-sets // Hopefully all resources are available during saving, so we can look up // in the addressbook to get name+email from the UID. const Akonadi::Item item(reference.uid().toLongLong()); Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item); job->fetchScope().fetchFullPayload(); if (!job->exec()) { continue; } const Akonadi::Item::List items = job->items(); if (items.count() != 1) { continue; } const KABC::Addressee addressee = job->items().first().payload(); gid = addressee.uid(); } KABC::ContactGroup::ContactReference ref; ref.setUid(gid); //libkolab expects a gid for uid() toAdd << ref; } KABC::ContactGroup gidOnlyContactGroup = contactGroup; gidOnlyContactGroup.removeAllContactReferences(); foreach ( const KABC::ContactGroup::ContactReference &ref, toAdd ) { gidOnlyContactGroup.append( ref ); } return gidOnlyContactGroup; } Akonadi::Item KolabHelpers::translateToImap(const Akonadi::Item &item, bool &ok) { ok = true; //imap messages don't need to be translated if (item.mimeType() == KMime::Message::mimeType()) { Q_ASSERT(item.hasPayload()); return item; } const QLatin1String productId("Akonadi-Kolab-Resource"); //Everthing stays the same, except mime type and payload Akonadi::Item imapItem = item; imapItem.setMimeType( QLatin1String("message/rfc822") ); try { switch(getKolabTypeFromMimeType(item.mimeType())) { case Kolab::EventObject: case Kolab::TodoObject: case Kolab::JournalObject: { kDebug() << "converted event"; const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeIncidence( item.payload(), Kolab::KolabV3, productId, QLatin1String("UTC") ); imapItem.setPayload( message ); } break; case Kolab::NoteObject: { kDebug() << "converted note"; const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeNote( item.payload(), Kolab::KolabV3, productId); imapItem.setPayload( message ); } break; case Kolab::ContactObject: { kDebug() << "converted contact"; const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeContact( item.payload(), Kolab::KolabV3, productId); imapItem.setPayload( message ); } break; case Kolab::DistlistObject: { const KABC::ContactGroup contactGroup = convertToGidOnly(item.payload()); kDebug() << "converted distlist"; const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeDistlist( contactGroup, Kolab::KolabV3, productId); imapItem.setPayload( message ); } break; default: kWarning() << "object type not handled: " << item.id() << item.mimeType(); ok = false; return Akonadi::Item(); } } catch (Akonadi::PayloadException e) { kWarning() << "The item contains the wrong or no payload: " << item.id() << item.mimeType(); kWarning() << e.what(); return Akonadi::Item(); } if (checkForErrors(item)) { kWarning() << "an error occured while trying to translate the item to the kolab format: " << item.id(); ok = false; return Akonadi::Item(); } return imapItem; } QByteArray KolabHelpers::kolabTypeForMimeType( const QStringList &contentMimeTypes ) { if (contentMimeTypes.contains(KABC::Addressee::mimeType())) { return "contact"; } else if (contentMimeTypes.contains( KCalCore::Event::eventMimeType())) { return "event"; } else if (contentMimeTypes.contains( KCalCore::Todo::todoMimeType())) { return "task"; } else if (contentMimeTypes.contains( KCalCore::Journal::journalMimeType())) { return "journal"; } else if (contentMimeTypes.contains(QLatin1String("application/x-vnd.akonadi.note")) || contentMimeTypes.contains(QLatin1String("text/x-vnd.akonadi.note"))) { return "note"; } return QByteArray(); } Kolab::ObjectType KolabHelpers::getKolabTypeFromMimeType(const QString &type) { if (type == KCalCore::Event::eventMimeType()) { return Kolab::EventObject; } else if (type == KCalCore::Todo::todoMimeType()) { return Kolab::TodoObject; } else if (type == KCalCore::Journal::journalMimeType()) { return Kolab::JournalObject; } else if (type == KABC::Addressee::mimeType()) { return Kolab::ContactObject; } else if (type == KABC::ContactGroup::mimeType()) { return Kolab::DistlistObject; } else if (type == QLatin1String("text/x-vnd.akonadi.note") || type == QLatin1String("application/x-vnd.akonadi.note")) { return Kolab::NoteObject; } return Kolab::InvalidObject; } QString KolabHelpers::getMimeType(Kolab::FolderType type) { switch (type) { case Kolab::MailType: return KMime::Message::mimeType(); case Kolab::ConfigurationType: return QLatin1String(KOLAB_TYPE_RELATION); default: kDebug() << "unhandled folder type: " << type; } return QString(); } QStringList KolabHelpers::getContentMimeTypes(Kolab::FolderType type) { QStringList contentTypes; contentTypes << Akonadi::Collection::mimeType(); switch (type) { case Kolab::EventType: contentTypes << KCalCore::Event().mimeType(); break; case Kolab::TaskType: contentTypes << KCalCore::Todo().mimeType(); break; case Kolab::JournalType: contentTypes << KCalCore::Journal().mimeType(); break; case Kolab::ContactType: contentTypes << KABC::Addressee::mimeType() << KABC::ContactGroup::mimeType(); break; case Kolab::NoteType: contentTypes << QLatin1String("text/x-vnd.akonadi.note") << QLatin1String("application/x-vnd.akonadi.note"); break; case Kolab::MailType: contentTypes << KMime::Message::mimeType(); break; case Kolab::ConfigurationType: contentTypes << QLatin1String(KOLAB_TYPE_RELATION); break; default: break; } return contentTypes; } -Kolab::FolderType KolabHelpers::folderTypeFromString(const QByteArray& folderTypeName) +Kolab::FolderType KolabHelpers::folderTypeFromString(const QByteArray &folderTypeName) { - return Kolab::folderTypeFromString( std::string(folderTypeName.data(), folderTypeName.size()) ); + const QByteArray stripped = folderTypeName.split('.').first(); + return Kolab::folderTypeFromString(std::string(stripped.data(), stripped.size())); } QByteArray KolabHelpers::getFolderTypeAnnotation(const QMap< QByteArray, QByteArray > &annotations) { - if (annotations.contains("/shared" KOLAB_FOLDER_TYPE_ANNOTATION)) { - return annotations.value( "/shared" KOLAB_FOLDER_TYPE_ANNOTATION); + if (annotations.contains("/shared" KOLAB_FOLDER_TYPE_ANNOTATION) && !annotations.value("/shared" KOLAB_FOLDER_TYPE_ANNOTATION).isEmpty()) { + return annotations.value("/shared" KOLAB_FOLDER_TYPE_ANNOTATION); + }else if (annotations.contains("/private" KOLAB_FOLDER_TYPE_ANNOTATION) && !annotations.value("/private" KOLAB_FOLDER_TYPE_ANNOTATION).isEmpty()) { + return annotations.value("/private" KOLAB_FOLDER_TYPE_ANNOTATION); } return annotations.value(KOLAB_FOLDER_TYPE_ANNOTATION); } void KolabHelpers::setFolderTypeAnnotation(QMap< QByteArray, QByteArray >& annotations, const QByteArray& value) { annotations["/shared" KOLAB_FOLDER_TYPE_ANNOTATION] = value; } QString KolabHelpers::getIcon(Kolab::FolderType type) { switch (type) { case Kolab::EventType: case Kolab::TaskType: case Kolab::JournalType: return QLatin1String("view-calendar"); case Kolab::ContactType: return QLatin1String("view-pim-contacts"); case Kolab::NoteType: return QLatin1String("view-pim-notes"); case Kolab::MailType: case Kolab::ConfigurationType: case Kolab::FreebusyType: case Kolab::FileType: default: break; } return QString(); } bool KolabHelpers::isHandledType(Kolab::FolderType type) { switch (type) { case Kolab::EventType: case Kolab::TaskType: case Kolab::JournalType: case Kolab::ContactType: case Kolab::NoteType: case Kolab::MailType: return true; case Kolab::ConfigurationType: case Kolab::FreebusyType: case Kolab::FileType: default: break; } return false; } QList KolabHelpers::ancestorChain(const Akonadi::Collection &col) { Q_ASSERT(col.isValid()); if (col.parentCollection() == Akonadi::Collection::root() || col == Akonadi::Collection::root() || !col.isValid()) { return QList(); } QList ancestors = ancestorChain(col.parentCollection()); Q_ASSERT(!col.remoteId().isEmpty()); ancestors << col.remoteId().toLatin1().mid(1); //We strip the first character which is always the separator return ancestors; } QString KolabHelpers::createMemberUrl(const Akonadi::Item &item, const QString &user) { Trace() << item.id() << item.mimeType() << item.gid() << item.hasPayload(); Kolab::RelationMember member; if (item.mimeType() == KMime::Message::mimeType()) { if (!item.hasPayload()) { kWarning() << "Email without payload, failed to add to tag: " << item.id() << item.remoteId(); return QString(); } KMime::Message::Ptr msg = item.payload(); member.uid = item.remoteId().toLong(); member.user = user; member.subject = msg->subject()->asUnicodeString(); member.messageId = msg->messageID()->asUnicodeString(); member.date = msg->date()->asUnicodeString(); member.mailbox = ancestorChain(item.parentCollection()); } else { if (item.gid().isEmpty()) { kWarning() << "Groupware object without GID, failed to add to tag: " << item.id() << item.remoteId(); return QString(); } member.gid = item.gid(); } return Kolab::generateMemberUrl(member); } diff --git a/resources/kolab/kolabhelpers.h b/resources/kolab/kolabhelpers.h index dbd909aa3..19de177b1 100644 --- a/resources/kolab/kolabhelpers.h +++ b/resources/kolab/kolabhelpers.h @@ -1,47 +1,47 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOLABHELPERS_H #define KOLABHELPERS_H #include #include //libkolab #include //libkolab class KolabHelpers { public: static bool checkForErrors(const Akonadi::Item &affectedItem); static Akonadi::Item translateFromImap(Kolab::FolderType folderType, const Akonadi::Item &item, bool &ok); static Akonadi::Item::List translateToImap(const Akonadi::Item::List &items, bool &ok); static Akonadi::Item translateToImap(const Akonadi::Item &item, bool &ok); - static Kolab::FolderType folderTypeFromString( const QByteArray &folderTypeName ); + static Kolab::FolderType folderTypeFromString(const QByteArray &folderTypeName); static QByteArray getFolderTypeAnnotation( const QMap &annotations); static void setFolderTypeAnnotation( QMap &annotations, const QByteArray &value); static Kolab::ObjectType getKolabTypeFromMimeType(const QString &type); static QByteArray kolabTypeForMimeType( const QStringList &contentMimeTypes ); static QStringList getContentMimeTypes(Kolab::FolderType type); static QString getMimeType(Kolab::FolderType type); static QString getIcon(Kolab::FolderType type); //Returns true if the folder type shouldn't be ignored static bool isHandledType(Kolab::FolderType type); static QList ancestorChain(const Akonadi::Collection &col); static QString createMemberUrl(const Akonadi::Item &item, const QString &user); }; #endif diff --git a/resources/kolab/kolabretrievecollectionstask.cpp b/resources/kolab/kolabretrievecollectionstask.cpp index b8421b3e8..a4cabbe54 100644 --- a/resources/kolab/kolabretrievecollectionstask.cpp +++ b/resources/kolab/kolabretrievecollectionstask.cpp @@ -1,535 +1,535 @@ /* Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kolabretrievecollectionstask.h" #include "kolabhelpers.h" #include "tracer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool isNamespaceFolder(const QString &path, const QList &namespaces, bool matchCompletePath = false) { Q_FOREACH (const KIMAP::MailBoxDescriptor &desc, namespaces) { if (path.startsWith(desc.name.left(desc.name.size() - 1))) { //Namespace ends with path separator and pathPart doesn't if (!matchCompletePath || path.size() - desc.name.size() <= 1) { //We want to match only for the complete path return true; } } } return false; } RetrieveMetadataJob::RetrieveMetadataJob(KIMAP::Session *session, const QStringList &mailboxes, const QStringList &serverCapabilities, const QSet &requestedMetadata, const QString &separator, const QList &sharedNamespace, const QList &userNamespace, QObject *parent) : KJob(parent) , mJobs(0) , mRequestedMetadata(requestedMetadata) , mServerCapabilities(serverCapabilities) , mMailboxes(mailboxes) , mSession(session) , mSeparator(separator) , mSharedNamespace(sharedNamespace) , mUserNamespace(userNamespace) { } void RetrieveMetadataJob::start() { Trace(); //Fill the map with empty entires so we set the mimetype to mail if no metadata is retrieved Q_FOREACH (const QString &mailbox, mMailboxes) { mMetadata.insert(mailbox, QMap()); } if ( mServerCapabilities.contains( QLatin1String("METADATA") ) || mServerCapabilities.contains( QLatin1String("ANNOTATEMORE") ) ) { QSet toplevelMailboxes; Q_FOREACH (const QString &mailbox, mMailboxes) { const QStringList parts = mailbox.split(mSeparator); if (!parts.isEmpty()) { if (isNamespaceFolder(mailbox, mUserNamespace) && parts.length() >= 2) { // Other Users can be too big to request with a single command so we request Other Users//* toplevelMailboxes << parts.at(0) + mSeparator + parts.at(1) + mSeparator; } else if (!isNamespaceFolder(mailbox, mSharedNamespace)) { toplevelMailboxes << parts.first(); } } } Q_FOREACH (const KIMAP::MailBoxDescriptor &desc, mSharedNamespace) { toplevelMailboxes << desc.name; } //TODO perhaps exclude the shared and other users namespaces by listing only toplevel (with %), and then only getting metadata of the toplevel folders. Q_FOREACH (const QString &mailbox, toplevelMailboxes) { { KIMAP::GetMetaDataJob *meta = new KIMAP::GetMetaDataJob(mSession); meta->setMailBox(mailbox + QLatin1String("*")); if ( mServerCapabilities.contains( QLatin1String("METADATA") ) ) { meta->setServerCapability( KIMAP::MetaDataJobBase::Metadata ); } else { meta->setServerCapability( KIMAP::MetaDataJobBase::Annotatemore ); } meta->setDepth( KIMAP::GetMetaDataJob::AllLevels ); Q_FOREACH (const QByteArray &requestedEntry, mRequestedMetadata) { meta->addRequestedEntry(requestedEntry); } connect( meta, SIGNAL(result(KJob*)), SLOT(onGetMetaDataDone(KJob*)) ); mJobs++; meta->start(); } } } // Get the ACLs from the mailbox if it's supported if ( mServerCapabilities.contains( QLatin1String("ACL") ) ) { Q_FOREACH (const QString &mailbox, mMailboxes) { // "Shared Folders" is not a valid mailbox, so we have to skip the ACL request for this folder if (isNamespaceFolder(mailbox, mSharedNamespace, true)) { continue; } KIMAP::MyRightsJob *rights = new KIMAP::MyRightsJob( mSession ); rights->setMailBox(mailbox); connect( rights, SIGNAL(result(KJob*)), SLOT(onRightsReceived(KJob*)) ); mJobs++; rights->start(); } } checkDone(); } void RetrieveMetadataJob::onGetMetaDataDone( KJob *job ) { mJobs--; KIMAP::GetMetaDataJob *meta = static_cast( job ); if ( job->error() ) { kDebug() << "No metadata for for mailbox: " << meta->mailBox(); if (!isNamespaceFolder(meta->mailBox(), mSharedNamespace)) { kWarning() << "Get metadata failed: " << job->errorString(); //We ignore the error to avoid failing the complete sync. We can run into this when trying to retrieve rights for non-existing mailboxes. } checkDone(); return; } const QHash > metadata = meta->allMetaDataForMailboxes(); Q_FOREACH (const QString &folder, metadata.keys()) { mMetadata.insert(folder, metadata.value(folder)); } checkDone(); } void RetrieveMetadataJob::onRightsReceived( KJob *job ) { mJobs--; KIMAP::MyRightsJob *rights = static_cast(job); if ( job->error() ) { kDebug() << "No rights for mailbox: " << rights->mailBox(); if (!isNamespaceFolder(rights->mailBox(), mSharedNamespace)) { kWarning() << "MyRights failed: " << job->errorString(); //We ignore the error to avoid failing the complete sync. We can run into this when trying to retrieve rights for non-existing mailboxes. } checkDone(); return; } const KIMAP::Acl::Rights imapRights = rights->rights(); mRights.insert(rights->mailBox(), imapRights); checkDone(); } void RetrieveMetadataJob::checkDone() { if (!mJobs) { Trace() << "done"; kDebug() << "done"; emitResult(); } } KolabRetrieveCollectionsTask::KolabRetrieveCollectionsTask(ResourceStateInterface::Ptr resource, QObject* parent) : ResourceTask(CancelIfNoSession, resource, parent) , mJobs(0) , cContentMimeTypes("CONTENTMIMETYPES") , cAccessRights("AccessRights") , cImapAcl("imapacl") , cCollectionAnnotations("collectionannotations") , cDefaultKeepLocalChanges(QSet() << cContentMimeTypes << cAccessRights << cImapAcl << cCollectionAnnotations) , cDefaultMimeTypes(QStringList() << Akonadi::Collection::mimeType() << QLatin1String("application/x-kolab-objects")) , cCollectionOnlyContentMimeTypes(QStringList() << Akonadi::Collection::mimeType()) { mRequestedMetadata << "/shared/vendor/kolab/folder-type"; mRequestedMetadata << "/private/vendor/kolab/folder-type"; } KolabRetrieveCollectionsTask::~KolabRetrieveCollectionsTask() { } void KolabRetrieveCollectionsTask::doStart(KIMAP::Session *session) { Trace(); kDebug() << "Starting collection retrieval"; mTime.start(); mSession = session; Akonadi::Collection root; root.setName(resourceName()); root.setRemoteId(rootRemoteId()); root.setContentMimeTypes(QStringList(Akonadi::Collection::mimeType())); root.setParentCollection(Akonadi::Collection::root()); root.addAttribute(new NoSelectAttribute(true)); root.attribute(Akonadi::Collection::AddIfMissing)->setIconName(QLatin1String("kolab")); Akonadi::CachePolicy policy; policy.setInheritFromParent(false); policy.setSyncOnDemand(true); QStringList localParts; localParts << QLatin1String(Akonadi::MessagePart::Envelope) << QLatin1String(Akonadi::MessagePart::Header); int cacheTimeout = 60; if (isDisconnectedModeEnabled()) { // For disconnected mode we also cache the body // and we keep all data indifinitely localParts << QLatin1String(Akonadi::MessagePart::Body); cacheTimeout = -1; } policy.setLocalParts(localParts); policy.setCacheTimeout(cacheTimeout); policy.setIntervalCheckTime(intervalCheckTime()); root.setCachePolicy(policy); mMailCollections.insert(QString(), root); Trace() << "subscription enabled: " << isSubscriptionEnabled(); //jobs are serialized by the session if (isSubscriptionEnabled()) { KIMAP::ListJob *fullListJob = new KIMAP::ListJob(session); fullListJob->setOption(KIMAP::ListJob::NoOption); fullListJob->setQueriedNamespaces(serverNamespaces()); connect( fullListJob, SIGNAL(mailBoxesReceived(QList,QList >)), this, SLOT(onFullMailBoxesReceived(QList,QList >)) ); connect( fullListJob, SIGNAL(result(KJob*)), SLOT(onFullMailBoxesReceiveDone(KJob*))); mJobs++; fullListJob->start(); } KIMAP::ListJob *listJob = new KIMAP::ListJob(session); listJob->setOption(KIMAP::ListJob::IncludeUnsubscribed); listJob->setQueriedNamespaces(serverNamespaces()); connect(listJob, SIGNAL(mailBoxesReceived(QList,QList >)), this, SLOT(onMailBoxesReceived(QList,QList >))); connect(listJob, SIGNAL(result(KJob*)), SLOT(onMailBoxesReceiveDone(KJob*))); mJobs++; listJob->start(); } void KolabRetrieveCollectionsTask::onMailBoxesReceived(const QList< KIMAP::MailBoxDescriptor > &descriptors, const QList< QList > &flags) { for (int i=0; i(Akonadi::Collection::AddIfMissing); attr->setIdentifier(path.toLatin1()); // If the folder is a other users folder block all alarms from default if (isNamespaceFolder(path, resourceState()->userNamespaces())) { Akonadi::BlockAlarmsAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); attr->blockEverything(true); } // If the folder is a other users top-level folder mark it accordingly if (pathParts.size() == 1 && isNamespaceFolder(path, resourceState()->userNamespaces())) { Akonadi::EntityDisplayAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); attr->setDisplayName(i18n("Other Users")); attr->setIconName(QLatin1String("x-mail-distribution-list")); } //Mark user folders for searching if (pathParts.size() >= 2 && isNamespaceFolder(path, resourceState()->userNamespaces())) { CollectionIdentificationAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); if (pathParts.size() == 2) { attr->setCollectionNamespace("usertoplevel"); } else { attr->setCollectionNamespace("user"); } } // If the folder is a shared folders top-level folder mark it accordingly if (pathParts.size() == 1 && isNamespaceFolder(path, resourceState()->sharedNamespaces())) { Akonadi::EntityDisplayAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); attr->setDisplayName(i18n("Shared Folders")); attr->setIconName(QLatin1String("x-mail-distribution-list")); } //Mark shared folders for searching if (pathParts.size() >= 2 && isNamespaceFolder(path, resourceState()->sharedNamespaces())) { CollectionIdentificationAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); attr->setCollectionNamespace("shared"); } } void KolabRetrieveCollectionsTask::createCollection(const QString &mailbox, const QList ¤tFlags, bool isSubscribed) { const QString separator = separatorCharacter(); Q_ASSERT(separator.size() == 1); const QString boxName = mailbox.endsWith( separator ) ? mailbox.left( mailbox.size()-1 ) : mailbox; const QStringList pathParts = boxName.split( separator ); const QString pathPart = pathParts.last(); Akonadi::Collection c; //If we had a dummy collection we need to replace it if (mMailCollections.contains(mailbox)) { c = mMailCollections.value(mailbox); } c.setName( pathPart ); c.setRemoteId( separator + pathPart ); const QStringList parentPath = pathParts.mid(0, pathParts.size() - 1); const Akonadi::Collection parentCollection = getOrCreateParent(parentPath.join(separator)); c.setParentCollection(parentCollection); //TODO get from ResourceState, and add KMime::Message::mimeType() for the normal imap resource by default //We add a dummy mimetype, otherwise the itemsync doesn't even work (action is disabled and resourcebase aborts the operation) c.setContentMimeTypes(cDefaultMimeTypes); c.setKeepLocalChanges(cDefaultKeepLocalChanges); //assume LRS, until myrights is executed if (serverCapabilities().contains(QLatin1String("ACL"))) { c.setRights(Akonadi::Collection::ReadOnly); } else { c.setRights(Akonadi::Collection::AllRights); } setAttributes(c, pathParts, mailbox); // If the folder is the Inbox, make some special settings. if (pathParts.size() == 1 && pathPart.compare(QLatin1String("inbox") , Qt::CaseInsensitive) == 0) { Akonadi::EntityDisplayAttribute *attr = c.attribute(Akonadi::Collection::AddIfMissing); attr->setDisplayName(i18n("Inbox")); attr->setIconName(QLatin1String("mail-folder-inbox")); setIdleCollection(c); } // If this folder is a noselect folder, make some special settings. if (currentFlags.contains("\\noselect")) { c.addAttribute(new NoSelectAttribute(true)); c.setContentMimeTypes(cCollectionOnlyContentMimeTypes); c.setRights( Akonadi::Collection::ReadOnly ); } else { // remove the noselect attribute explicitly, in case we had set it before (eg. for non-subscribed non-leaf folders) c.removeAttribute(); } // If this folder is a noinferiors folder, it is not allowed to create subfolders inside. if (currentFlags.contains("\\noinferiors")) { //kDebug() << "Noinferiors: " << currentPath; c.addAttribute(new NoInferiorsAttribute(true)); c.setRights(c.rights() & ~Akonadi::Collection::CanCreateCollection); } c.setEnabled(isSubscribed); // kDebug() << "creating collection " << mailbox << " with parent " << parentPath; mMailCollections.insert(mailbox, c); } void KolabRetrieveCollectionsTask::onMailBoxesReceiveDone(KJob* job) { Trace(); kDebug() << "All mailboxes received: " << mTime.elapsed(); kDebug() << "in total: " << mMailCollections.size(); mJobs--; if (job->error()) { kWarning() << QLatin1String("Failed to retrieve mailboxes: ") + job->errorString(); cancelTask("Collection retrieval failed"); } else { QSet mailboxes; Q_FOREACH(const QString &mailbox, mMailCollections.keys()) { if (!mailbox.isEmpty() && !isNamespaceFolder(mailbox, resourceState()->userNamespaces() + resourceState()->sharedNamespaces())) { mailboxes << mailbox; } } //Only request metadata for subscribed Other Users Folders const QStringList metadataMailboxes = mailboxes.unite( mSubscribedMailboxes.toList().toSet()).toList(); RetrieveMetadataJob *metadata = new RetrieveMetadataJob(mSession, metadataMailboxes, serverCapabilities(), mRequestedMetadata, separatorCharacter(), resourceState()->sharedNamespaces(), resourceState()->userNamespaces(), this); connect(metadata, SIGNAL(result(KJob*)), this, SLOT(onMetadataRetrieved(KJob*))); mJobs++; metadata->start(); } } void KolabRetrieveCollectionsTask::applyRights(QHash rights) { // kDebug() << rights; Q_FOREACH(const QString &mailbox, rights.keys()) { if (mMailCollections.contains(mailbox)) { const KIMAP::Acl::Rights imapRights = rights.value(mailbox); QStringList parts = mailbox.split(separatorCharacter()); parts.removeLast(); QString parentMailbox = parts.join(separatorCharacter()); KIMAP::Acl::Rights parentImapRights; //If the parent folder is not existing we cant rename if (!parentMailbox.isEmpty() && rights.contains(parentMailbox)) { parentImapRights = rights.value(parentMailbox); } // kDebug() << mailbox << parentMailbox << imapRights << parentImapRights; Akonadi::Collection &collection = mMailCollections[mailbox]; CollectionMetadataHelper::applyRights(collection, imapRights, parentImapRights); // Store the mailbox ACLs Akonadi::ImapAclAttribute *aclAttribute = collection.attribute( Akonadi::Collection::AddIfMissing ); const KIMAP::Acl::Rights oldRights = aclAttribute->myRights(); if ( oldRights != imapRights ) { aclAttribute->setMyRights( imapRights ); } } else { kWarning() << "Can't find mailbox " << mailbox; } } } void KolabRetrieveCollectionsTask::applyMetadata(QHash > metadataMap) { // kDebug() << metadataMap; Q_FOREACH(const QString &mailbox, metadataMap.keys()) { const QMap metadata = metadataMap.value(mailbox); if (mMailCollections.contains(mailbox)) { Akonadi::Collection &collection = mMailCollections[mailbox]; - // kDebug() << "setting metadata: " << mailbox << metadata; collection.attribute(Akonadi::Collection::AddIfMissing)->setAnnotations(metadata); const QByteArray type = KolabHelpers::getFolderTypeAnnotation(metadata); const Kolab::FolderType folderType = KolabHelpers::folderTypeFromString(type); + // kDebug() << mailbox << metadata << type << folderType << KolabHelpers::getContentMimeTypes(folderType); collection.setContentMimeTypes(KolabHelpers::getContentMimeTypes(folderType)); QSet keepLocalChanges = collection.keepLocalChanges(); keepLocalChanges.remove(cContentMimeTypes); collection.setKeepLocalChanges(keepLocalChanges); } } } void KolabRetrieveCollectionsTask::onMetadataRetrieved(KJob *job) { Trace(); kDebug() << mTime.elapsed(); mJobs--; if (job->error()) { kWarning() << "Error while retrieving metadata, aborting collection retrieval: " << job->errorString(); cancelTask("Collection retrieval failed"); } else { RetrieveMetadataJob *metadata = static_cast(job); applyRights(metadata->mRights); applyMetadata(metadata->mMetadata); checkDone(); } } void KolabRetrieveCollectionsTask::checkDone() { if (!mJobs) { Trace() << "done " << mMailCollections.size(); collectionsRetrieved(mMailCollections.values()); kDebug() << "done " << mTime.elapsed(); } } void KolabRetrieveCollectionsTask::onFullMailBoxesReceived(const QList< KIMAP::MailBoxDescriptor >& descriptors, const QList< QList< QByteArray > >& flags) { Q_UNUSED(flags); foreach (const KIMAP::MailBoxDescriptor &descriptor, descriptors) { mSubscribedMailboxes.insert(descriptor.name); } } void KolabRetrieveCollectionsTask::onFullMailBoxesReceiveDone(KJob* job) { Trace(); kDebug() << "received subscribed collections " << mTime.elapsed(); mJobs--; if (job->error()) { kWarning() << QLatin1String("Failed to retrieve subscribed collections: ") + job->errorString(); cancelTask("Collection retrieval failed"); } else { checkDone(); } }