diff --git a/CMakeLists.txt b/CMakeLists.txt index fe46f733f..d93ff755e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,154 +1,154 @@ cmake_minimum_required(VERSION 2.8.12) project(kdepim-runtime) ############### KDEPIM-Runtime version ################ # KDEPIM_RUNTIME_VERSION # Version scheme: "x.y.z build". # # x is the version number. # y is the major release number. # z is the minor release number. # # "x.y.z" follow the kdelibs version kdepim is released with. # # If "z" is 0, it the version is "x.y" # # KDEPIM_RUNTIME_DEV_VERSION # is empty for final versions. For development versions "build" is # something like "pre", "alpha1", "alpha2", "beta1", "beta2", "rc1", "rc2". # # Examples in chronological order: # # 3.0 # 3.0.1 # 3.1 alpha1 # 3.1 beta1 # 3.1 beta2 # 3.1 rc1 # 3.1 # 3.1.1 # 3.2 pre # 3.2 alpha1 if(NOT DEFINED KDEPIM_RUNTIME_DEV_VERSION) set(KDEPIM_RUNTIME_DEV_VERSION "pre") endif() set(KDEPIM_RUNTIME_VERSION_NUMBER "5.2.40") set(KDEPIM_RUNTIME_VERSION "${KDEPIM_RUNTIME_VERSION_NUMBER}${KDEPIM_RUNTIME_DEV_VERSION}") configure_file(kdepim-runtime-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdepim-runtime-version.h @ONLY) find_package(ECM 5.19 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${kdepim-runtime_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(ECMPackageConfigHelpers) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) set(KF5_VERSION "5.19") set(QT_REQUIRED_VERSION "5.5.0") set(KDEPIMLIBS_LIB_VERSION "5.2.40") set(KDEPIMRUNTIME_LIB_VERSION "${KDEPIM_RUNTIME_VERSION_NUMBER}") set(KDEPIMRUNTIME_LIB_SOVERSION "5") -set(AKONADI_VERSION "5.2.40") +set(AKONADI_VERSION "5.2.41") set(KCONTACTS_LIB_VERSION "5.2.40") set(KCALENDARCORE_LIB_VERSION "5.2.40") set(IDENTITYMANAGEMENT_LIB_VERSION "5.2.40") set(KMAILTRANSPORT_LIB_VERSION "5.2.40") set(CALENDARUTILS_LIB_VERSION "5.2.40") set(KIMAP_LIB_VERSION "5.2.40") set(KMBOX_LIB_VERSION "5.2.40") set(AKONADICALENDAR_LIB_VERSION "5.2.40") set(SYNDICATION_LIB_VERSION "5.2.40") set(KONTACTINTERFACE_LIB_VERSION "5.2.40") set(AKONADIKALARM_LIB_VERSION "5.2.40") set(KMIME_LIB_VERSION "5.2.40") set(XMLRPCCLIENT_LIB_VERSION "5.2.40") set(KCONTACTS_LIB_VERSION "5.2.40") set(AKONADIMIME_LIB_VERSION "5.2.40") set(AKONADICONTACT_LIB_VERSION "5.2.40") set(AKONADINOTE_LIB_VERSION "5.2.40") set(AKONADISOCIALUTIL_LIB_VERSION "5.2.40") set(KPIMTEXTEDIT_LIB_VERSION "5.2.40") set( SHARED_MIME_INFO_MINIMUM_VERSION "0.40" ) find_package( SharedMimeInfo REQUIRED ) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Network Widgets Test XmlPatterns DBus) # QT5 package find_package(Qt5WebKitWidgets ${QT_REQUIRED_VERSION} REQUIRED NO_MODULE) find_package(Qt5 OPTIONAL_COMPONENTS TextToSpeech) if (NOT Qt5TextToSpeech_FOUND) message(STATUS "Qt5TextToSpeech not found, speech feature will be disabled") else() add_definitions(-DHAVE_SPEECH) endif() # KF5 package find_package(KF5KDELibs4Support ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5NotifyConfig ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Kross ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_VERSION} CONFIG REQUIRED) find_packagE(KF5TextWidgets ${KF5_VERSION} CONFIG REQUIRED) # for KPluralHandlingSpinBox # KdepimLibs package find_package(KF5Akonadi ${AKONADI_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiMime ${AKONADIMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5MailTransport ${KMAILTRANSPORT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IdentityManagement ${IDENTITYMANAGEMENT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiContact ${AKONADICONTACT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Contacts ${KCONTACTS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AlarmCalendar ${AKONADIKALARM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarCore ${KCALENDARCORE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5CalendarUtils ${CALENDARUTILS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mbox ${KMBOX_LIB_VERSION} CONFIG REQUIRED) find_package(KF5PimTextEdit ${KPIMTEXTEDIT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IMAP ${KIMAP_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Syndication ${SYNDICATION_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiNotes ${AKONADINOTE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiSocialUtils ${AKONADISOCIALUTIL_LIB_VERSION} CONFIG REQUIRED) option(KDEPIM_RUN_ISOLATED_TESTS "Run the isolated tests." FALSE) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_subdirectory(resources) add_subdirectory(agents) add_subdirectory(plugins) add_subdirectory(defaultsetup) add_subdirectory(kioslave) add_subdirectory(migration) ## install the MIME type spec file for KDEPIM specific MIME types install(FILES kdepim-mime.xml DESTINATION ${KDE_INSTALL_MIMEDIR}) update_xdg_mimetypes(${KDE_INSTALL_MIMEDIR}) install( FILES kdepim-runtime.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES ) diff --git a/resources/kolab/kolabhelpers.cpp b/resources/kolab/kolabhelpers.cpp index 97ce4dbd2..0a8552e83 100644 --- a/resources/kolab/kolabhelpers.cpp +++ b/resources/kolab/kolabhelpers.cpp @@ -1,506 +1,524 @@ /* 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 "kolabresource_debug.h" #include #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")); } qCWarning(KOLABRESOURCE_LOG) << "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: { KContacts::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(KContacts::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: qCWarning(KOLABRESOURCE_LOG) << "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, probably a flag change or alike, we just pass it through if (!imapItem.hasPayload()) { return imapItem; } if (!imapItem.hasPayload()) { qCWarning(KOLABRESOURCE_LOG) << "Payload is not a MessagePtr!"; Q_ASSERT(false); ok = false; return imapItem; } 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) { qCWarning(KOLABRESOURCE_LOG) << "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) { qCWarning(KOLABRESOURCE_LOG) << "Failed to read note."; ok = false; return Akonadi::Item(); } Akonadi::Item newItem(QStringLiteral("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(KContacts::Addressee::mimeType()); newItem.setPayload(reader.getContact()); newItem.setRemoteId(imapItem.remoteId()); newItem.setGid(reader.getContact().uid()); return newItem; } break; case Kolab::DistlistObject: { KContacts::ContactGroup contactGroup = reader.getDistlist(); QList toAdd; for (uint index = 0; index < contactGroup.contactReferenceCount(); ++index) { const KContacts::ContactGroup::ContactReference &reference = contactGroup.contactReference(index); KContacts::ContactGroup::ContactReference ref; ref.setGid(reference.uid()); //libkolab set a gid with setUid() toAdd << ref; } contactGroup.removeAllContactReferences(); foreach (const KContacts::ContactGroup::ContactReference &ref, toAdd) { contactGroup.append(ref); } Akonadi::Item newItem(KContacts::ContactGroup::mimeType()); newItem.setPayload(contactGroup); newItem.setRemoteId(imapItem.remoteId()); newItem.setGid(contactGroup.id()); return newItem; } break; default: qCWarning(KOLABRESOURCE_LOG) << "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 KContacts::ContactGroup convertToGidOnly(const KContacts::ContactGroup &contactGroup) { QList toAdd; for (uint index = 0; index < contactGroup.contactReferenceCount(); ++index) { const KContacts::ContactGroup::ContactReference &reference = contactGroup.contactReference(index); QString gid; if (!reference.gid().isEmpty()) { gid = reference.gid(); } else { // WARNING: this is an ugly hack for backwards compatibility. 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 KContacts::Addressee addressee = job->items().at(0).payload(); gid = addressee.uid(); } KContacts::ContactGroup::ContactReference ref; ref.setUid(gid); //libkolab expects a gid for uid() toAdd << ref; } KContacts::ContactGroup gidOnlyContactGroup = contactGroup; gidOnlyContactGroup.removeAllContactReferences(); foreach (const KContacts::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(QStringLiteral("message/rfc822")); try { switch (getKolabTypeFromMimeType(item.mimeType())) { case Kolab::EventObject: case Kolab::TodoObject: case Kolab::JournalObject: { qCDebug(KOLABRESOURCE_LOG) << "converted event"; const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeIncidence( item.payload(), Kolab::KolabV3, productId, QStringLiteral("UTC")); imapItem.setPayload(message); } break; case Kolab::NoteObject: { qCDebug(KOLABRESOURCE_LOG) << "converted note"; const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeNote( item.payload(), Kolab::KolabV3, productId); imapItem.setPayload(message); } break; case Kolab::ContactObject: { qCDebug(KOLABRESOURCE_LOG) << "converted contact"; const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeContact( item.payload(), Kolab::KolabV3, productId); imapItem.setPayload(message); } break; case Kolab::DistlistObject: { const KContacts::ContactGroup contactGroup = convertToGidOnly(item.payload()); qCDebug(KOLABRESOURCE_LOG) << "converted distlist"; const KMime::Message::Ptr message = Kolab::KolabObjectWriter::writeDistlist( contactGroup, Kolab::KolabV3, productId); imapItem.setPayload(message); } break; default: qCWarning(KOLABRESOURCE_LOG) << "object type not handled: " << item.id() << item.mimeType(); ok = false; return Akonadi::Item(); } } catch (const Akonadi::PayloadException &e) { qCWarning(KOLABRESOURCE_LOG) << "The item contains the wrong or no payload: " << item.id() << item.mimeType(); qCWarning(KOLABRESOURCE_LOG) << e.what(); return Akonadi::Item(); } if (checkForErrors(item)) { qCWarning(KOLABRESOURCE_LOG) << "an error occurred 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(KContacts::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(QStringLiteral("application/x-vnd.akonadi.note")) || contentMimeTypes.contains(QStringLiteral("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 == KContacts::Addressee::mimeType()) { return Kolab::ContactObject; } else if (type == KContacts::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 QStringLiteral(KOLAB_TYPE_RELATION); default: qCDebug(KOLABRESOURCE_LOG) << "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 << KContacts::Addressee::mimeType() << KContacts::ContactGroup::mimeType(); break; case Kolab::NoteType: contentTypes << QStringLiteral("text/x-vnd.akonadi.note") << QStringLiteral("application/x-vnd.akonadi.note"); break; case Kolab::MailType: contentTypes << KMime::Message::mimeType(); break; case Kolab::ConfigurationType: contentTypes << QStringLiteral(KOLAB_TYPE_RELATION); break; default: break; } return contentTypes; } Kolab::FolderType KolabHelpers::folderTypeFromString(const QByteArray &folderTypeName) { 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) && !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; } +QColor KolabHelpers::getFolderColor(const QMap &annotations) +{ + // kolab saves the color without a "#", so we need to add it to the rgb string to have a propper QColor + if (annotations.contains("/shared" KOLAB_COLOR_ANNOTATION) && !annotations.value("/shared" KOLAB_COLOR_ANNOTATION).isEmpty()) { + return QColor(QStringLiteral("#").append(QString::fromUtf8(annotations.value("/shared" KOLAB_COLOR_ANNOTATION)))); + } else if (annotations.contains("/private" KOLAB_COLOR_ANNOTATION) && !annotations.value("/private" KOLAB_COLOR_ANNOTATION).isEmpty()) { + return QColor(QStringLiteral("#").append(QString::fromUtf8(annotations.value("/private" KOLAB_COLOR_ANNOTATION)))); + } + return QColor(); +} + +void KolabHelpers::setFolderColor(QMap &annotations, const QColor &color) +{ + // kolab saves the color without a "#", so we need to delete the prefix "#" if we save it to the annotations + annotations["/shared" KOLAB_COLOR_ANNOTATION] = color.name().toAscii().remove(0,1); +} + QString KolabHelpers::getIcon(Kolab::FolderType type) { switch (type) { case Kolab::EventType: case Kolab::TaskType: case Kolab::JournalType: return QStringLiteral("view-calendar"); case Kolab::ContactType: return QStringLiteral("view-pim-contacts"); case Kolab::NoteType: return QStringLiteral("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()) { qCWarning(KOLABRESOURCE_LOG) << "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()) { qCWarning(KOLABRESOURCE_LOG) << "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 21805cf39..185002dde 100644 --- a/resources/kolab/kolabhelpers.h +++ b/resources/kolab/kolabhelpers.h @@ -1,48 +1,54 @@ /* 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 +#define KOLAB_COLOR_ANNOTATION "/vendor/kolab/color" + +class QColor; + 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 QByteArray getFolderTypeAnnotation(const QMap &annotations); static void setFolderTypeAnnotation(QMap &annotations, const QByteArray &value); + static QColor getFolderColor(const QMap &annotations); + static void setFolderColor(QMap &annotations, const QColor &color); static Kolab::ObjectType getKolabTypeFromMimeType(const QString &type); static QByteArray kolabTypeForMimeType(const QStringList &contentMimeTypes); static QStringList getContentMimeTypes(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 QString getMimeType(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/kolabresource.cpp b/resources/kolab/kolabresource.cpp index ad5f30dda..207ab13c1 100644 --- a/resources/kolab/kolabresource.cpp +++ b/resources/kolab/kolabresource.cpp @@ -1,234 +1,257 @@ /* 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 "kolabresource.h" #include "kolabresource_debug.h" #include "setupserver.h" #include #include #include #include #include +#include +#include #include #include #include #include #include "kolabretrievetagstask.h" #include "kolabresourcestate.h" #include "kolabhelpers.h" #include "kolabsettings.h" #include "kolabaddtagtask.h" #include "kolabchangeitemstagstask.h" #include "kolabchangeitemsrelationstask.h" #include "kolabchangetagtask.h" #include "kolabremovetagtask.h" #include "kolabretrievecollectionstask.h" #include "kolabretrievetagstask.h" #include "tracer.h" KolabResource::KolabResource(const QString &id) : ImapResource(id) { + Akonadi::AttributeFactory::registerAttribute(); //Ensure we have up-to date metadata before attempting to sync folder setScheduleAttributeSyncBeforeItemSync(true); setKeepLocalCollectionChanges(QSet() << "ENTITYDISPLAY" << Akonadi::BlockAlarmsAttribute().type()); } KolabResource::~KolabResource() { } Settings *KolabResource::settings() const { if (m_settings == 0) { m_settings = new KolabSettings; } return m_settings; } void KolabResource::delayedInit() { ImapResource::delayedInit(); settings()->setRetrieveMetadataOnFolderListing(false); Q_ASSERT(!settings()->retrieveMetadataOnFolderListing()); } QString KolabResource::defaultName() const { return i18n("Kolab Resource"); } QDialog *KolabResource::createConfigureDialog(WId windowId) { SetupServer *dlg = new SetupServer(this, windowId); KWindowSystem::setMainWindow(dlg, windowId); dlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("kolab"))); connect(dlg, SIGNAL(finished(int)), this, SLOT(onConfigurationDone(int)));; return dlg; } ResourceStateInterface::Ptr KolabResource::createResourceState(const TaskArguments &args) { return ResourceStateInterface::Ptr(new KolabResourceState(this, args)); } void KolabResource::retrieveCollections() { Trace(); emit status(AgentBase::Running, i18nc("@info:status", "Retrieving folders")); startTask(new KolabRetrieveCollectionsTask(createResourceState(TaskArguments()), this)); synchronizeTags(); synchronizeRelations(); } void KolabResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { Trace() << item.id() << collection.id(); bool ok = true; const Akonadi::Item imapItem = KolabHelpers::translateToImap(item, ok); if (!ok) { qCWarning(KOLABRESOURCE_LOG) << "Failed to convert item"; cancelTask(); return; } ImapResource::itemAdded(imapItem, collection); } void KolabResource::itemChanged(const Akonadi::Item &item, const QSet< QByteArray > &parts) { Trace() << item.id() << parts; bool ok = true; const Akonadi::Item imapItem = KolabHelpers::translateToImap(item, ok); if (!ok) { qCWarning(KOLABRESOURCE_LOG) << "Failed to convert item"; cancelTask(); return; } ImapResource::itemChanged(imapItem, parts); } void KolabResource::itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination) { Trace() << items.size() << source.id() << destination.id(); bool ok = true; const Akonadi::Item::List imapItems = KolabHelpers::translateToImap(items, ok); if (!ok) { qCWarning(KOLABRESOURCE_LOG) << "Failed to convert item"; cancelTask(); return; } ImapResource::itemsMoved(imapItems, source, destination); } static Akonadi::Collection updateAnnotations(const Akonadi::Collection &collection) { Trace() << collection.id(); //Set the annotations on new folders const QByteArray kolabType = KolabHelpers::kolabTypeForMimeType(collection.contentMimeTypes()); + Akonadi::Collection col = collection; + Akonadi::CollectionAnnotationsAttribute *attr = col.attribute(Akonadi::Collection::AddIfMissing); + QMap annotations = attr->annotations(); + + bool changed = false; + Akonadi::CollectionColorAttribute *colorAttribute = col.attribute(); + if (colorAttribute) { + const QColor color = colorAttribute->color(); + if (color.isValid()) { + KolabHelpers::setFolderColor(annotations, color); + changed = true; + } + } + if (!kolabType.isEmpty()) { - Akonadi::Collection col = collection; - Akonadi::CollectionAnnotationsAttribute *attr = col.attribute(Akonadi::Collection::AddIfMissing); - QMap annotations = attr->annotations(); KolabHelpers::setFolderTypeAnnotation(annotations, kolabType); + changed = true; + } + + if (changed) { attr->setAnnotations(annotations); return col; } return collection; } void KolabResource::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { Trace() << collection.id() << parent.id(); //Set the annotations on new folders const Akonadi::Collection col = updateAnnotations(collection); //TODO we need to save the collections as well if the annotations have changed //or we simply don't have the annotations locally, which perhaps is also not required? ImapResource::collectionAdded(col, parent); } void KolabResource::collectionChanged(const Akonadi::Collection &collection, const QSet< QByteArray > &parts) { Trace() << collection.id() << parts; + QSet p = parts; //Update annotations if necessary const Akonadi::Collection col = updateAnnotations(collection); + if(parts.contains(Akonadi::CollectionColorAttribute().type())) { + p << Akonadi::CollectionAnnotationsAttribute().type(); + } + //TODO we need to save the collections as well if the annotations have changed emit status(AgentBase::Running, i18nc("@info:status", "Updating folder '%1'", collection.name())); - ChangeCollectionTask *task = new ChangeCollectionTask(createResourceState(TaskArguments(collection, parts)), this); + ChangeCollectionTask *task = new ChangeCollectionTask(createResourceState(TaskArguments(collection, p)), this); task->syncEnabledState(true); startTask(task); } void KolabResource::tagAdded(const Akonadi::Tag &tag) { Trace() << tag.id(); KolabAddTagTask *task = new KolabAddTagTask(createResourceState(TaskArguments(tag)), this); startTask(task); } void KolabResource::tagChanged(const Akonadi::Tag &tag) { Trace() << tag.id(); KolabChangeTagTask *task = new KolabChangeTagTask(createResourceState(TaskArguments(tag)), QSharedPointer(new TagConverter), this); startTask(task); } void KolabResource::tagRemoved(const Akonadi::Tag &tag) { Trace() << tag.id(); KolabRemoveTagTask *task = new KolabRemoveTagTask(createResourceState(TaskArguments(tag)), this); startTask(task); } void KolabResource::itemsTagsChanged(const Akonadi::Item::List &items, const QSet &addedTags, const QSet &removedTags) { Trace() << items.size() << addedTags.size() << removedTags.size(); KolabChangeItemsTagsTask *task = new KolabChangeItemsTagsTask(createResourceState(TaskArguments(items, addedTags, removedTags)), QSharedPointer(new TagConverter), this); startTask(task); } void KolabResource::retrieveTags() { Trace(); KolabRetrieveTagTask *task = new KolabRetrieveTagTask(createResourceState(TaskArguments()), KolabRetrieveTagTask::RetrieveTags, this); startTask(task); } void KolabResource::retrieveRelations() { Trace(); KolabRetrieveTagTask *task = new KolabRetrieveTagTask(createResourceState(TaskArguments()), KolabRetrieveTagTask::RetrieveRelations, this); startTask(task); } void KolabResource::itemsRelationsChanged(const Akonadi::Item::List &items, const Akonadi::Relation::List &addedRelations, const Akonadi::Relation::List &removedRelations) { Trace() << items.size() << addedRelations.size() << removedRelations.size(); KolabChangeItemsRelationsTask *task = new KolabChangeItemsRelationsTask(createResourceState(TaskArguments(items, addedRelations, removedRelations))); startTask(task); } AKONADI_RESOURCE_MAIN(KolabResource) diff --git a/resources/kolab/kolabretrievecollectionstask.cpp b/resources/kolab/kolabretrievecollectionstask.cpp index dbe9a6cad..276fd4504 100644 --- a/resources/kolab/kolabretrievecollectionstask.cpp +++ b/resources/kolab/kolabretrievecollectionstask.cpp @@ -1,534 +1,543 @@ /* 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 "kolabresource_debug.h" #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include +#include + static 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(QStringLiteral("METADATA")) || mServerCapabilities.contains(QStringLiteral("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(QStringLiteral("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, &KJob::result, this, &RetrieveMetadataJob::onGetMetaDataDone); mJobs++; meta->start(); } } } // Get the ACLs from the mailbox if it's supported if (mServerCapabilities.contains(QStringLiteral("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, &KJob::result, this, &RetrieveMetadataJob::onRightsReceived); mJobs++; rights->start(); } } checkDone(); } void RetrieveMetadataJob::onGetMetaDataDone(KJob *job) { mJobs--; KIMAP::GetMetaDataJob *meta = static_cast(job); if (job->error()) { qCDebug(KOLABRESOURCE_LOG) << "No metadata for for mailbox: " << meta->mailBox(); if (!isNamespaceFolder(meta->mailBox(), mSharedNamespace)) { qCWarning(KOLABRESOURCE_LOG) << "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()) { qCDebug(KOLABRESOURCE_LOG) << "No rights for mailbox: " << rights->mailBox(); if (!isNamespaceFolder(rights->mailBox(), mSharedNamespace)) { qCWarning(KOLABRESOURCE_LOG) << "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"; qCDebug(KOLABRESOURCE_LOG) << "done"; emitResult(); } } KolabRetrieveCollectionsTask::KolabRetrieveCollectionsTask(ResourceStateInterface::Ptr resource, QObject *parent) : ResourceTask(CancelIfNoSession, resource, parent) , mJobs(0) , mSession(Q_NULLPTR) , cContentMimeTypes("CONTENTMIMETYPES") , cAccessRights("AccessRights") , cImapAcl("imapacl") , cCollectionAnnotations("collectionannotations") , cDefaultKeepLocalChanges(QSet() << cContentMimeTypes << cAccessRights << cImapAcl << cCollectionAnnotations) , cDefaultMimeTypes(QStringList() << Akonadi::Collection::mimeType() << QStringLiteral("application/x-kolab-objects")) , cCollectionOnlyContentMimeTypes(QStringList() << Akonadi::Collection::mimeType()) { mRequestedMetadata << "/shared/vendor/kolab/folder-type"; mRequestedMetadata << "/private/vendor/kolab/folder-type"; + mRequestedMetadata << "/shared" KOLAB_COLOR_ANNOTATION + << "/private" KOLAB_COLOR_ANNOTATION; } KolabRetrieveCollectionsTask::~KolabRetrieveCollectionsTask() { } void KolabRetrieveCollectionsTask::doStart(KIMAP::Session *session) { Trace(); qCDebug(KOLABRESOURCE_LOG) << "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(QStringLiteral("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, &KIMAP::ListJob::mailBoxesReceived, this, &KolabRetrieveCollectionsTask::onFullMailBoxesReceived); connect(fullListJob, &KJob::result, this, &KolabRetrieveCollectionsTask::onFullMailBoxesReceiveDone); mJobs++; fullListJob->start(); } KIMAP::ListJob *listJob = new KIMAP::ListJob(session); listJob->setOption(KIMAP::ListJob::IncludeUnsubscribed); listJob->setQueriedNamespaces(serverNamespaces()); connect(listJob, &KIMAP::ListJob::mailBoxesReceived, this, &KolabRetrieveCollectionsTask::onMailBoxesReceived); connect(listJob, &KJob::result, this, &KolabRetrieveCollectionsTask::onMailBoxesReceiveDone); mJobs++; listJob->start(); } void KolabRetrieveCollectionsTask::onMailBoxesReceived(const QList< KIMAP::MailBoxDescriptor > &descriptors, const QList< QList > &flags) { for (int i = 0; i < descriptors.size(); ++i) { const KIMAP::MailBoxDescriptor descriptor = descriptors[i]; createCollection(descriptor.name, flags.at(i), !isSubscriptionEnabled() || mSubscribedMailboxes.contains(descriptor.name)); } checkDone(); } Akonadi::Collection KolabRetrieveCollectionsTask::getOrCreateParent(const QString &path) { if (mMailCollections.contains(path)) { return mMailCollections.value(path); } //create a dummy collection const QString separator = separatorCharacter(); const QStringList pathParts = path.split(separator); const QString pathPart = pathParts.last(); Akonadi::Collection c; 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); c.addAttribute(new NoSelectAttribute(true)); c.setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType()); c.setRights(Akonadi::Collection::ReadOnly); c.setEnabled(false); setAttributes(c, pathParts, path); mMailCollections.insert(path, c); return c; } void KolabRetrieveCollectionsTask::setAttributes(Akonadi::Collection &c, const QStringList &pathParts, const QString &path) { Akonadi::CollectionIdentificationAttribute *attr = c.attribute(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(QStringLiteral("x-mail-distribution-list")); } //Mark user folders for searching if (pathParts.size() >= 2 && isNamespaceFolder(path, resourceState()->userNamespaces())) { Akonadi::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(QStringLiteral("x-mail-distribution-list")); } //Mark shared folders for searching if (pathParts.size() >= 2 && isNamespaceFolder(path, resourceState()->sharedNamespaces())) { Akonadi::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(QStringLiteral("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(QStringLiteral("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")) { //qCDebug(KOLABRESOURCE_LOG) << "Noinferiors: " << currentPath; c.addAttribute(new NoInferiorsAttribute(true)); c.setRights(c.rights() & ~Akonadi::Collection::CanCreateCollection); } c.setEnabled(isSubscribed); // qCDebug(KOLABRESOURCE_LOG) << "creating collection " << mailbox << " with parent " << parentPath; mMailCollections.insert(mailbox, c); } void KolabRetrieveCollectionsTask::onMailBoxesReceiveDone(KJob *job) { Trace(); qCDebug(KOLABRESOURCE_LOG) << "All mailboxes received: " << mTime.elapsed(); qCDebug(KOLABRESOURCE_LOG) << "in total: " << mMailCollections.size(); mJobs--; if (job->error()) { qCWarning(KOLABRESOURCE_LOG) << QStringLiteral("Failed to retrieve mailboxes: ") + job->errorString(); cancelTask(QStringLiteral("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, &KJob::result, this, &KolabRetrieveCollectionsTask::onMetadataRetrieved); mJobs++; metadata->start(); } } void KolabRetrieveCollectionsTask::applyRights(QHash rights) { // qCDebug(KOLABRESOURCE_LOG) << 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); } // qCDebug(KOLABRESOURCE_LOG) << 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 { qCWarning(KOLABRESOURCE_LOG) << "Can't find mailbox " << mailbox; } } } void KolabRetrieveCollectionsTask::applyMetadata(QHash > metadataMap) { // qCDebug(KOLABRESOURCE_LOG) << metadataMap; Q_FOREACH (const QString &mailbox, metadataMap.keys()) { const QMap metadata = metadataMap.value(mailbox); if (mMailCollections.contains(mailbox)) { Akonadi::Collection &collection = mMailCollections[mailbox]; // qCDebug(KOLABRESOURCE_LOG) << mailbox << metadata << type << folderType << KolabHelpers::getContentMimeTypes(folderType); collection.attribute(Akonadi::Collection::AddIfMissing)->setAnnotations(metadata); const QByteArray type = KolabHelpers::getFolderTypeAnnotation(metadata); const Kolab::FolderType folderType = KolabHelpers::folderTypeFromString(type); collection.setContentMimeTypes(KolabHelpers::getContentMimeTypes(folderType)); + const QColor color = KolabHelpers::getFolderColor(metadata); + if (color.isValid()) { + collection.attribute(Akonadi::Collection::AddIfMissing)->setColor(color); + } QSet keepLocalChanges = collection.keepLocalChanges(); keepLocalChanges.remove(cContentMimeTypes); collection.setKeepLocalChanges(keepLocalChanges); } } } void KolabRetrieveCollectionsTask::onMetadataRetrieved(KJob *job) { Trace(); qCDebug(KOLABRESOURCE_LOG) << mTime.elapsed(); mJobs--; if (job->error()) { qCWarning(KOLABRESOURCE_LOG) << "Error while retrieving metadata, aborting collection retrieval: " << job->errorString(); cancelTask(QStringLiteral("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(Akonadi::valuesToVector(mMailCollections)); qCDebug(KOLABRESOURCE_LOG) << "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(); qCDebug(KOLABRESOURCE_LOG) << "received subscribed collections " << mTime.elapsed(); mJobs--; if (job->error()) { qCWarning(KOLABRESOURCE_LOG) << QStringLiteral("Failed to retrieve subscribed collections: ") + job->errorString(); cancelTask(QStringLiteral("Collection retrieval failed")); } else { checkDone(); } }