diff --git a/agents/migration/migrationagent.cpp b/agents/migration/migrationagent.cpp index 3c756ab05..f8a46d3c6 100644 --- a/agents/migration/migrationagent.cpp +++ b/agents/migration/migrationagent.cpp @@ -1,66 +1,67 @@ /* * Copyright 2013 Christian Mollekopf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "migrationagent.h" #include "migrationstatuswidget.h" #include #include #include #include #include #include #include #include namespace Akonadi { MigrationAgent::MigrationAgent(const QString &id) : AgentBase(id) , mScheduler(new KUiServerJobTracker) { KLocalizedString::setApplicationDomain("akonadi_migration_agent"); mScheduler.addMigrator(QSharedPointer(new GidMigrator(KContacts::Addressee::mimeType()))); } void MigrationAgent::configure(WId windowId) { QDialog *dlg = new QDialog(); QVBoxLayout *topLayout = new QVBoxLayout(dlg); MigrationStatusWidget *widget = new MigrationStatusWidget(mScheduler, dlg); topLayout->addWidget(widget); dlg->setAttribute(Qt::WA_DeleteOnClose); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dlg); connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); topLayout->addWidget(buttonBox); dlg->setWindowTitle(i18nc("Title of the window that shows the status of the migration agent and offers controls to start/stop individual migration jobs.", "Migration Status")); dlg->resize(600, 300); if (windowId) { + dlg->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(dlg->windowHandle(), windowId); } dlg->show(); } } AKONADI_AGENT_MAIN(Akonadi::MigrationAgent) diff --git a/resources/dav/resource/davgroupwareresource.cpp b/resources/dav/resource/davgroupwareresource.cpp index 73cc108fa..3d9d43597 100644 --- a/resources/dav/resource/davgroupwareresource.cpp +++ b/resources/dav/resource/davgroupwareresource.cpp @@ -1,1362 +1,1364 @@ /* Copyright (c) 2009 Grégory Oestreicher This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "davgroupwareresource.h" #include "akonadietagcache.h" #include "configdialog.h" #include "ctagattribute.h" #include "davfreebusyhandler.h" #include "davprotocolattribute.h" #include "utils.h" #include "settings.h" #include "settingsadaptor.h" #include "setupwizard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "davresource_debug.h" #include using namespace Akonadi; typedef QSharedPointer IncidencePtr; DavGroupwareResource::DavGroupwareResource(const QString &id) : ResourceBase(id) , FreeBusyProviderBase() , mSyncErrorNotified(false) { AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); setNeedsNetwork(true); mDavCollectionRoot.setParentCollection(Collection::root()); mDavCollectionRoot.setName(identifier()); mDavCollectionRoot.setRemoteId(identifier()); mDavCollectionRoot.setContentMimeTypes(QStringList() << Collection::mimeType()); mDavCollectionRoot.setRights(Collection::CanCreateCollection | Collection::CanDeleteCollection | Collection::CanChangeCollection); EntityDisplayAttribute *attribute = mDavCollectionRoot.attribute(Collection::AddIfMissing); attribute->setIconName(QStringLiteral("folder-remote")); int refreshInterval = Settings::self()->refreshInterval(); if (refreshInterval == 0) { refreshInterval = -1; } Akonadi::CachePolicy cachePolicy; cachePolicy.setInheritFromParent(false); cachePolicy.setSyncOnDemand(false); cachePolicy.setCacheTimeout(-1); cachePolicy.setIntervalCheckTime(refreshInterval); cachePolicy.setLocalParts(QStringList() << QStringLiteral("ALL")); mDavCollectionRoot.setCachePolicy(cachePolicy); changeRecorder()->fetchCollection(true); changeRecorder()->collectionFetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); changeRecorder()->itemFetchScope().fetchFullPayload(true); changeRecorder()->itemFetchScope().setAncestorRetrieval(ItemFetchScope::All); Settings::self()->setWinId(winIdForDialogs()); Settings::self()->setResourceIdentifier(identifier()); mFreeBusyHandler = new DavFreeBusyHandler(this); connect(mFreeBusyHandler, &DavFreeBusyHandler::handlesFreeBusy, this, &DavGroupwareResource::onHandlesFreeBusy); connect(mFreeBusyHandler, &DavFreeBusyHandler::freeBusyRetrieved, this, &DavGroupwareResource::onFreeBusyRetrieved); connect(this, &DavGroupwareResource::reloadConfiguration, this, &DavGroupwareResource::onReloadConfig); scheduleCustomTask(this, "initialRetrieveCollections", QVariant(), ResourceBase::Prepend); scheduleCustomTask(this, "createInitialCache", QVariant(), ResourceBase::Prepend); } DavGroupwareResource::~DavGroupwareResource() { delete mFreeBusyHandler; } void DavGroupwareResource::collectionRemoved(const Akonadi::Collection &collection) { qCDebug(DAVRESOURCE_LOG) << "Removing collection " << collection.remoteId(); if (!configurationIsValid()) { return; } const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(collection.remoteId()); KDAV::DavCollectionDeleteJob *job = new KDAV::DavCollectionDeleteJob(davUrl); job->setProperty("collection", QVariant::fromValue(collection)); connect(job, &KDAV::DavCollectionDeleteJob::result, this, &DavGroupwareResource::onCollectionRemovedFinished); job->start(); } void DavGroupwareResource::cleanup() { Settings::self()->cleanup(); ResourceBase::cleanup(); } QDateTime DavGroupwareResource::lastCacheUpdate() const { return QDateTime::currentDateTime(); } void DavGroupwareResource::canHandleFreeBusy(const QString &email) const { if (!isOnline()) { handlesFreeBusy(email, false); } else { mFreeBusyHandler->canHandleFreeBusy(email); } } void DavGroupwareResource::onHandlesFreeBusy(const QString &email, bool handles) { handlesFreeBusy(email, handles); } void DavGroupwareResource::retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) { if (!isOnline()) { freeBusyRetrieved(email, QString(), false, i18n("Unable to retrieve free-busy info while offline")); } else { mFreeBusyHandler->retrieveFreeBusy(email, start, end); } } void DavGroupwareResource::onFreeBusyRetrieved(const QString &email, const QString &freeBusy, bool success, const QString &errorText) { freeBusyRetrieved(email, freeBusy, success, errorText); } void DavGroupwareResource::configure(WId windowId) { Settings::self()->setWinId(windowId); // On the initial configuration we start the setup wizard if (Settings::self()->configuredDavUrls().isEmpty()) { SetupWizard wizard; if (windowId) { + wizard.setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(wizard.windowHandle(), windowId); } const int result = wizard.exec(); if (result == QDialog::Accepted) { const SetupWizard::Url::List urls = wizard.urls(); for (const SetupWizard::Url &url : urls) { Settings::UrlConfiguration *urlConfig = new Settings::UrlConfiguration(); urlConfig->mUrl = url.url; urlConfig->mProtocol = url.protocol; urlConfig->mUser = url.userName; urlConfig->mPassword = wizard.field(QStringLiteral("credentialsPassword")).toString(); Settings::self()->newUrlConfiguration(urlConfig); } if (!urls.isEmpty()) { Settings::self()->setDisplayName(wizard.displayName()); } QString defaultUser = wizard.field(QStringLiteral("credentialsUserName")).toString(); if (!defaultUser.isEmpty()) { Settings::self()->setDefaultUsername(defaultUser); Settings::self()->setDefaultPassword(wizard.field(QStringLiteral("credentialsPassword")).toString()); } } } // continue with the normal config dialog ConfigDialog dialog; if (windowId) { + dialog.setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(dialog.windowHandle(), windowId); } if (!Settings::self()->defaultUsername().isEmpty()) { dialog.setPassword(Settings::self()->defaultPassword()); } const int result = dialog.exec(); if (result == QDialog::Accepted) { Settings::self()->setSettingsVersion(3); Settings::self()->save(); synchronize(); Q_EMIT configurationDialogAccepted(); } else { Q_EMIT configurationDialogRejected(); } } KJob *DavGroupwareResource::createRetrieveCollectionsJob() { qCDebug(DAVRESOURCE_LOG) << "Retrieving collections list"; mSyncErrorNotified = false; if (!configurationIsValid()) { return nullptr; } Q_EMIT status(Running, i18n("Fetching collections")); KDAV::DavCollectionsMultiFetchJob *job = new KDAV::DavCollectionsMultiFetchJob(Settings::self()->configuredDavUrls()); connect(job, &KDAV::DavCollectionsMultiFetchJob::result, this, &DavGroupwareResource::onRetrieveCollectionsFinished); connect(job, &KDAV::DavCollectionsMultiFetchJob::collectionDiscovered, this, &DavGroupwareResource::onCollectionDiscovered); return job; } void DavGroupwareResource::initialRetrieveCollections() { auto job = createRetrieveCollectionsJob(); if (!job) { return; } job->setProperty("initialCacheSync", QVariant::fromValue(true)); job->start(); } void DavGroupwareResource::retrieveCollections() { auto job = createRetrieveCollectionsJob(); if (!job) { return; } job->setProperty("initialCacheSync", QVariant::fromValue(false)); job->start(); } void DavGroupwareResource::retrieveItems(const Akonadi::Collection &collection) { if (!collection.isValid()) { itemsRetrievalDone(); return; } qCDebug(DAVRESOURCE_LOG) << "Retrieving items for collection " << collection.remoteId(); if (!configurationIsValid()) { return; } // As the resource root collection contains mime types for items we must // work around the fact that Akonadi will rightfully try to retrieve items // from it. So just return an empty list if (collection.remoteId() == identifier()) { itemsRetrievalDone(); return; } if (!mEtagCaches.contains(collection.remoteId())) { qCDebug(DAVRESOURCE_LOG) << "Asked to retrieve items for a collection we don't have in the cache"; itemsRetrievalDone(); return; } // Only continue if the collection has changed or if // it's the first time we see it const CTagAttribute *CTagAttr = collection.attribute(); if (CTagAttr && mCTagCache.contains(collection.remoteId()) && mCTagCache.value(collection.remoteId()) == CTagAttr->CTag()) { qCDebug(DAVRESOURCE_LOG) << "CTag for collection" << collection.remoteId() << "didn't change: " << CTagAttr->CTag(); itemsRetrievalDone(); return; } const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(collection.remoteId()); if (!davUrl.url().isValid()) { qCCritical(DAVRESOURCE_LOG) << "Can't find a configured URL, collection.remoteId() is " << collection.remoteId(); cancelTask(i18n("Asked to retrieve items for an unknown collection: %1", collection.remoteId())); //Q_ASSERT_X( false, "DavGroupwareResource::retrieveItems", "Url is invalid" ); return; } KDAV::DavItemsListJob *job = new KDAV::DavItemsListJob(davUrl, mEtagCaches.value(collection.remoteId())); if (Settings::self()->limitSyncRange()) { QDateTime start = Settings::self()->getSyncRangeStart(); qCDebug(DAVRESOURCE_LOG) << "Start time for list job:" << start; if (start.isValid()) { job->setTimeRange(start.toString(QStringLiteral("yyyyMMddTHHMMssZ")), QString()); } } job->setProperty("collection", QVariant::fromValue(collection)); job->setContentMimeTypes(collection.contentMimeTypes()); connect(job, &KDAV::DavItemsListJob::result, this, &DavGroupwareResource::onRetrieveItemsFinished); job->start(); } bool DavGroupwareResource::retrieveItem(const Akonadi::Item &item, const QSet &) { qCDebug(DAVRESOURCE_LOG) << "Retrieving single item. Remote id = " << item.remoteId(); if (!configurationIsValid()) { return false; } const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(item.parentCollection().remoteId(), item.remoteId()); if (!davUrl.url().isValid()) { qCDebug(DAVRESOURCE_LOG) << "Failed to get a valid DavUrl. Parent collection remote ID is" << item.parentCollection().remoteId(); cancelTask(); return false; } KDAV::DavItem davItem; davItem.setContentType(QStringLiteral("text/calendar")); davItem.setEtag(item.remoteRevision()); KDAV::DavItemFetchJob *job = new KDAV::DavItemFetchJob(davItem); job->setProperty("item", QVariant::fromValue(item)); connect(job, &KDAV::DavItemFetchJob::result, this, &DavGroupwareResource::onRetrieveItemFinished); job->start(); return true; } void DavGroupwareResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { qCDebug(DAVRESOURCE_LOG) << "Received notification for added item. Local id = " << item.id() << ". Remote id = " << item.remoteId() << ". Collection remote id = " << collection.remoteId(); if (!configurationIsValid()) { return; } if (collection.remoteId().isEmpty()) { qCCritical(DAVRESOURCE_LOG) << "Invalid remote id for collection " << collection.id() << " = " << collection.remoteId(); cancelTask(i18n("Invalid collection for item %1.", item.id())); return; } KDAV::DavItem davItem = Utils::createDavItem(item, collection); if (davItem.data().isEmpty()) { qCCritical(DAVRESOURCE_LOG) << "Item " << item.id() << " doesn't has a valid payload"; cancelTask(); return; } const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(collection.remoteId(), davItem.url().toDisplayString()); qCDebug(DAVRESOURCE_LOG) << "Item " << item.id() << " will be put to " << davItem.url().toDisplayString(); davItem.setUrl(davUrl); KDAV::DavItemCreateJob *job = new KDAV::DavItemCreateJob(davItem); job->setProperty("collection", QVariant::fromValue(collection)); job->setProperty("item", QVariant::fromValue(item)); connect(job, &KDAV::DavItemCreateJob::result, this, &DavGroupwareResource::onItemAddedFinished); job->start(); } void DavGroupwareResource::itemChanged(const Akonadi::Item &item, const QSet &) { qCDebug(DAVRESOURCE_LOG) << "Received notification for changed item. Local id = " << item.id() << ". Remote id = " << item.remoteId(); if (!configurationIsValid()) { return; } const Akonadi::Collection collection = item.parentCollection(); if (!mEtagCaches.contains(collection.remoteId())) { qCDebug(DAVRESOURCE_LOG) << "Changed item is in a collection we don't have in the cache"; // TODO: display an error cancelTask(); return; } QString ridBase = item.remoteId(); if (ridBase.contains(QLatin1Char('#'))) { ridBase.truncate(ridBase.indexOf(QLatin1Char('#'))); } auto cache = mEtagCaches.value(collection.remoteId()); Akonadi::Item::List extraItems; const QStringList lstUrls = cache->urls(); for (const QString &rid : lstUrls) { if (rid.startsWith(ridBase) && rid != item.remoteId()) { Akonadi::Item extraItem; extraItem.setRemoteId(rid); extraItems << extraItem; } } if (extraItems.isEmpty()) { doItemChange(item); } else { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(extraItems); job->setCollection(item.parentCollection()); job->fetchScope().fetchFullPayload(); job->setProperty("item", QVariant::fromValue(item)); connect(job, &Akonadi::ItemFetchJob::result, this, &DavGroupwareResource::onItemChangePrepared); } } void DavGroupwareResource::onItemChangePrepared(KJob *job) { Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); Akonadi::Item item = job->property("item").value(); doItemChange(item, fetchJob->items()); } void DavGroupwareResource::doItemChange(const Akonadi::Item &item, const Akonadi::Item::List &dependentItems) { KDAV::DavItem davItem = Utils::createDavItem(item, item.parentCollection(), dependentItems); if (davItem.data().isEmpty()) { qCCritical(DAVRESOURCE_LOG) << "Item " << item.id() << " doesn't has a valid payload"; cancelTask(); return; } QString url = item.remoteId(); if (url.contains(QLatin1Char('#'))) { url.truncate(url.indexOf(QLatin1Char('#'))); } const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(item.parentCollection().remoteId(), url); // We have to re-set the URL as it's not necessarily valid after createDavItem() davItem.setUrl(davUrl); davItem.setEtag(item.remoteRevision()); KDAV::DavItemModifyJob *modJob = new KDAV::DavItemModifyJob(davItem); modJob->setProperty("collection", QVariant::fromValue(item.parentCollection())); modJob->setProperty("item", QVariant::fromValue(item)); modJob->setProperty("dependentItems", QVariant::fromValue(dependentItems)); connect(modJob, &KDAV::DavItemModifyJob::result, this, &DavGroupwareResource::onItemChangedFinished); modJob->start(); } void DavGroupwareResource::itemRemoved(const Akonadi::Item &item) { qCDebug(DAVRESOURCE_LOG) << "Received notification for removed item. Remote id = " << item.remoteId(); if (!configurationIsValid()) { return; } const Akonadi::Collection collection = item.parentCollection(); if (!mEtagCaches.contains(collection.remoteId())) { qCDebug(DAVRESOURCE_LOG) << "Removed item is in a collection we don't have in the cache"; // TODO: display an error cancelTask(); return; } QString ridBase = item.remoteId(); if (ridBase.contains(QLatin1Char('#'))) { // A bit tricky: we must remove an incidence contained in a resource // containing multiple ones. ridBase.truncate(ridBase.indexOf(QLatin1Char('#'))); auto cache = mEtagCaches.value(collection.remoteId()); Akonadi::Item::List extraItems; const QStringList lstUrl = cache->urls(); for (const QString &rid : lstUrl) { if (rid.startsWith(ridBase) && rid != item.remoteId()) { Akonadi::Item extraItem; extraItem.setRemoteId(rid); extraItems << extraItem; } } if (extraItems.isEmpty()) { // Urrrr? // Well, just delete the item. doItemRemoval(item); } else { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(extraItems); job->setCollection(item.parentCollection()); job->fetchScope().fetchFullPayload(); job->setProperty("item", QVariant::fromValue(item)); connect(job, &Akonadi::ItemFetchJob::result, this, &DavGroupwareResource::onItemRemovalPrepared); } } else { // easy as pie: just remove everything at the URL. doItemRemoval(item); } } void DavGroupwareResource::onItemRemovalPrepared(KJob *job) { Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); Akonadi::Item item = job->property("item").value(); const Akonadi::Item::List keptItems = fetchJob->items(); if (keptItems.isEmpty()) { // Urrrr? Not again! doItemRemoval(item); } else { Akonadi::Item mainItem; Akonadi::Item::List extraItems; QString ridBase = item.remoteId(); ridBase.truncate(ridBase.indexOf(QLatin1Char('#'))); for (const Akonadi::Item &kept : keptItems) { if (kept.remoteId() == ridBase && extraItems.isEmpty()) { mainItem = kept; } else { extraItems << kept; } } if (!mainItem.hasPayload()) { mainItem = extraItems.takeFirst(); } const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(item.parentCollection().remoteId(), ridBase); KDAV::DavItem davItem = Utils::createDavItem(mainItem, mainItem.parentCollection(), extraItems); davItem.setUrl(davUrl); davItem.setEtag(item.remoteRevision()); KDAV::DavItemModifyJob *modJob = new KDAV::DavItemModifyJob(davItem); modJob->setProperty("collection", QVariant::fromValue(mainItem.parentCollection())); modJob->setProperty("item", QVariant::fromValue(mainItem)); modJob->setProperty("dependentItems", QVariant::fromValue(extraItems)); modJob->setProperty("isRemoval", QVariant::fromValue(true)); modJob->setProperty("removedItem", QVariant::fromValue(item)); connect(modJob, &KDAV::DavItemModifyJob::result, this, &DavGroupwareResource::onItemChangedFinished); modJob->start(); } } void DavGroupwareResource::doItemRemoval(const Akonadi::Item &item) { const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(item.parentCollection().remoteId(), item.remoteId()); KDAV::DavItem davItem; davItem.setUrl(davUrl); davItem.setEtag(item.remoteRevision()); KDAV::DavItemDeleteJob *job = new KDAV::DavItemDeleteJob(davItem); job->setProperty("item", QVariant::fromValue(item)); job->setProperty("collection", QVariant::fromValue(item.parentCollection())); connect(job, &KDAV::DavItemDeleteJob::result, this, &DavGroupwareResource::onItemRemovedFinished); job->start(); } void DavGroupwareResource::doSetOnline(bool online) { qCDebug(DAVRESOURCE_LOG) << "Resource changed online status to" << online; if (online) { synchronize(); } ResourceBase::doSetOnline(online); } void DavGroupwareResource::createInitialCache() { // Get all the items fetched by this resource Akonadi::RecursiveItemFetchJob *job = new Akonadi::RecursiveItemFetchJob(mDavCollectionRoot, QStringList()); job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(job, &Akonadi::RecursiveItemFetchJob::result, this, &DavGroupwareResource::onCreateInitialCacheReady); job->start(); } void DavGroupwareResource::onCreateInitialCacheReady(KJob *job) { Akonadi::RecursiveItemFetchJob *fetchJob = qobject_cast(job); const Akonadi::Item::List itemsLst = fetchJob->items(); for (const Akonadi::Item &item : itemsLst) { const QString rid = item.remoteId(); if (rid.isEmpty()) { qCDebug(DAVRESOURCE_LOG) << "DavGroupwareResource::onCreateInitialCacheReady: Found an item without remote ID. " << item.id(); continue; } const Akonadi::Collection collection = item.parentCollection(); if (collection.remoteId().isEmpty()) { qCDebug(DAVRESOURCE_LOG) << "DavGroupwareResource::onCreateInitialCacheReady: Found an item in a collection without remote ID. " << item.remoteId(); continue; } const QString etag = item.remoteRevision(); if (etag.isEmpty()) { qCDebug(DAVRESOURCE_LOG) << "DavGroupwareResource::onCreateInitialCacheReady: Found an item without ETag. " << item.remoteId(); continue; } if (!mEtagCaches.contains(collection.remoteId())) { auto cache = std::shared_ptr(new AkonadiEtagCache(collection)); mEtagCaches.insert(collection.remoteId(), cache); } mEtagCaches[collection.remoteId()]->setEtag(rid, etag); } taskDone(); } void DavGroupwareResource::onReloadConfig() { Settings::self()->reloadConfig(); synchronize(); } void DavGroupwareResource::onCollectionRemovedFinished(KJob *job) { if (job->error()) { cancelTask(i18n("Unable to remove collection: %1", job->errorText())); return; } Akonadi::Collection collection = job->property("collection").value(); if (mEtagCaches.contains(collection.remoteId())) { mEtagCaches[collection.remoteId()]->deleteLater(); mEtagCaches.remove(collection.remoteId()); } changeProcessed(); } void DavGroupwareResource::onRetrieveCollectionsFinished(KJob *job) { const KDAV::DavCollectionsMultiFetchJob *fetchJob = qobject_cast< KDAV::DavCollectionsMultiFetchJob *>(job); if (job->error()) { qCWarning(DAVRESOURCE_LOG) << "Unable to fetch collections" << job->error() << job->errorText(); cancelTask(i18n("Unable to retrieve collections: %1", job->errorText())); mSyncErrorNotified = true; return; } bool initialCacheSync = job->property("initialCacheSync").toBool(); Akonadi::Collection::List collections; collections << mDavCollectionRoot; QSet seenCollectionsUrls; const KDAV::DavCollection::List davCollections = fetchJob->collections(); for (const KDAV::DavCollection &davCollection : davCollections) { if (seenCollectionsUrls.contains(davCollection.url().toDisplayString())) { qCDebug(DAVRESOURCE_LOG) << "DavGroupwareResource::onRetrieveCollectionsFinished: Duplicate collection reported. " << davCollection.url().toDisplayString(); continue; } else { seenCollectionsUrls.insert(davCollection.url().toDisplayString()); } Akonadi::Collection collection; collection.setParentCollection(mDavCollectionRoot); collection.setRemoteId(davCollection.url().toDisplayString()); collection.setName(collection.remoteId()); if (davCollection.color().isValid()) { CollectionColorAttribute *colorAttr = collection.attribute(Akonadi::Collection::AddIfMissing); colorAttr->setColor(davCollection.color()); } if (!davCollection.displayName().isEmpty()) { EntityDisplayAttribute *attr = collection.attribute(Collection::AddIfMissing); attr->setDisplayName(davCollection.displayName()); } QStringList mimeTypes; mimeTypes << Collection::mimeType(); const KDAV::DavCollection::ContentTypes contentTypes = davCollection.contentTypes(); if (contentTypes & KDAV::DavCollection::Calendar) { mimeTypes << QStringLiteral("text/calendar"); } if (contentTypes & KDAV::DavCollection::Events) { mimeTypes << KCalendarCore::Event::eventMimeType(); } if (contentTypes & KDAV::DavCollection::Todos) { mimeTypes << KCalendarCore::Todo::todoMimeType(); } if (contentTypes & KDAV::DavCollection::Contacts) { mimeTypes << KContacts::Addressee::mimeType(); } if (contentTypes & KDAV::DavCollection::FreeBusy) { mimeTypes << KCalendarCore::FreeBusy::freeBusyMimeType(); } if (contentTypes & KDAV::DavCollection::Journal) { mimeTypes << KCalendarCore::Journal::journalMimeType(); } collection.setContentMimeTypes(mimeTypes); setCollectionIcon(collection /*by-ref*/); DavProtocolAttribute *protoAttr = collection.attribute(Collection::AddIfMissing); protoAttr->setDavProtocol(davCollection.url().protocol()); /* * We unfortunately have to update the CTag now in the cache * as this information will not be available when retrieveItems() * is called. We leave it untouched in the collection attribute * and will only update it there after successful sync. */ if (!davCollection.CTag().isEmpty()) { mCTagCache.insert(davCollection.url().toDisplayString(), davCollection.CTag()); } KDAV::Privileges privileges = davCollection.privileges(); Akonadi::Collection::Rights rights; if (privileges & KDAV::All || privileges & KDAV::Write) { rights |= Akonadi::Collection::AllRights; } if (privileges & KDAV::WriteContent) { rights |= Akonadi::Collection::CanChangeItem; } if (privileges & KDAV::Bind) { rights |= Akonadi::Collection::CanCreateItem; } if (privileges & KDAV::Unbind) { rights |= Akonadi::Collection::CanDeleteItem; } if (privileges == KDAV::Read) { rights |= Akonadi::Collection::ReadOnly; } collection.setRights(rights); collections << collection; if (!mEtagCaches.contains(collection.remoteId())) { auto cache = std::shared_ptr(new AkonadiEtagCache(collection)); mEtagCaches.insert(collection.remoteId(), cache); } } foreach (const QString &rid, mEtagCaches.keys()) { if (!seenCollectionsUrls.contains(rid)) { qCDebug(DAVRESOURCE_LOG) << "DavGroupwareResource::onRetrieveCollectionsFinished: Collection disappeared. " << rid; mEtagCaches[rid]->deleteLater(); mEtagCaches.remove(rid); } } if (!initialCacheSync) { collectionsRetrieved(collections); } else { taskDone(); } } void DavGroupwareResource::onRetrieveItemsFinished(KJob *job) { if (job->error()) { if (mSyncErrorNotified) { cancelTask(); } else { cancelTask(i18n("Unable to retrieve items: %1", job->errorText())); mSyncErrorNotified = true; } return; } Collection collection = job->property("collection").value(); const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(collection.remoteId()); const bool protocolSupportsMultiget = KDAV::ProtocolInfo::useMultiget(davUrl.protocol()); const KDAV::DavItemsListJob *listJob = qobject_cast< KDAV::DavItemsListJob *>(job); auto cache = mEtagCaches.value(collection.remoteId()); if (!cache) { qCDebug(DAVRESOURCE_LOG) << "Collection has disappeared during item fetch!"; cancelTask(); return; } Akonadi::Item::List changedItems; QSet seenRids; QStringList changedRids; changedItems.reserve(listJob->changedItems().count()); foreach (const KDAV::DavItem &davItem, listJob->changedItems()) { seenRids.insert(davItem.url().toDisplayString()); Akonadi::Item item; item.setParentCollection(collection); item.setRemoteId(davItem.url().toDisplayString()); item.setMimeType(davItem.contentType()); item.setRemoteRevision(davItem.etag()); cache->markAsChanged(item.remoteId()); changedRids << item.remoteId(); changedItems << item; // Only clear the payload (and therefor trigger a refetch from the backend) if we // do not use multiget, because in this case we fetch the complete payload // some lines below already. if (!protocolSupportsMultiget) { qCDebug(DAVRESOURCE_LOG) << "Outdated item " << item.remoteId() << " (etag = " << davItem.etag() << ")"; item.clearPayload(); } } foreach (const QString &rmd, listJob->deletedItems()) { // We don't want to delete dependent items if the main item was seen if (rmd.contains(QLatin1Char('#'))) { const QString base = rmd.left(rmd.indexOf(QLatin1Char('#'))); if (seenRids.contains(base)) { continue; } } qCDebug(DAVRESOURCE_LOG) << "DavGroupwareResource::onRetrieveItemsFinished: Item disappeared. " << rmd; Akonadi::Item item; item.setParentCollection(collection); item.setRemoteId(rmd); cache->removeEtag(rmd); // Use a job to delete items as itemsRetrievedIncremental seem to choke // when many items are given with just their RID. Akonadi::ItemDeleteJob *deleteJob = new Akonadi::ItemDeleteJob(item); deleteJob->start(); } // If the protocol supports multiget then deviate from the expected behavior // and fetch all items with payload now instead of waiting for Akonadi to // request it item by item in retrieveItem(). // This allows the resource to use the multiget query and let it be nice // to the remote server : only one request for n items instead of n requests. if (protocolSupportsMultiget && !changedRids.isEmpty()) { KDAV::DavItemsFetchJob *fetchJob = new KDAV::DavItemsFetchJob(davUrl, changedRids); connect(fetchJob, &KDAV::DavItemsFetchJob::result, this, &DavGroupwareResource::onMultigetFinished); fetchJob->setProperty("collection", QVariant::fromValue(collection)); fetchJob->setProperty("items", QVariant::fromValue(changedItems)); fetchJob->start(); // delay the call of itemsRetrieved() to onMultigetFinished() } else { // Update the collection CTag attribute now as sync is done. if (mCTagCache.contains(collection.remoteId())) { CTagAttribute *CTagAttr = collection.attribute(Collection::AddIfMissing); qCDebug(DAVRESOURCE_LOG) << "Updating collection CTag from" << CTagAttr->CTag() << "to" << mCTagCache.value(collection.remoteId()); CTagAttr->setCTag(mCTagCache.value(collection.remoteId())); Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob(collection); job->start(); } itemsRetrievedIncremental(changedItems, Akonadi::Item::List()); } } void DavGroupwareResource::onMultigetFinished(KJob *job) { if (job->error()) { if (mSyncErrorNotified) { cancelTask(); } else { cancelTask(i18n("Unable to retrieve items: %1", job->errorText())); mSyncErrorNotified = true; } return; } Akonadi::Collection collection = job->property("collection").value(); auto cache = mEtagCaches.value(collection.remoteId()); if (!cache) { qCDebug(DAVRESOURCE_LOG) << "Collection has disappeared during item fetch!"; cancelTask(); return; } const Akonadi::Item::List origItems = job->property("items").value(); const KDAV::DavItemsFetchJob *davJob = qobject_cast< KDAV::DavItemsFetchJob *>(job); Akonadi::Item::List items; for (Akonadi::Item item : qAsConst(origItems)) { //krazy:exclude=foreach non-const is intended here const KDAV::DavItem davItem = davJob->item(item.remoteId()); // No data was retrieved for this item, maybe because it is not out of date if (davItem.data().isEmpty()) { qCDebug(DAVRESOURCE_LOG) << "DavGroupwareResource::onMultigetFinished: Empty item returned. " << item.remoteId(); if (!cache->isOutOfDate(item.remoteId())) { qCDebug(DAVRESOURCE_LOG) << "DavGroupwareResource::onMultigetFinished: Item is not changed, including it. " << item.remoteId(); items << item; } continue; } Akonadi::Item::List extraItems; if (!Utils::parseDavData(davItem, item, extraItems)) { qCWarning(DAVRESOURCE_LOG) << "DavGroupwareResource::onMultigetFinished: Failed to parse item data. " << item.remoteId(); continue; } // update etag item.setRemoteRevision(davItem.etag()); cache->setEtag(item.remoteId(), davItem.etag()); items << item; for (const Akonadi::Item &extraItem : qAsConst(extraItems)) { cache->setEtag(extraItem.remoteId(), davItem.etag()); items << extraItem; } } // Update the collection CTag attribute now as sync is done. if (mCTagCache.contains(collection.remoteId())) { CTagAttribute *CTagAttr = collection.attribute(Collection::AddIfMissing); qCDebug(DAVRESOURCE_LOG) << "Updating collection CTag from" << CTagAttr->CTag() << "to" << mCTagCache.value(collection.remoteId()); CTagAttr->setCTag(mCTagCache.value(collection.remoteId())); Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob(collection); job->start(); } itemsRetrievedIncremental(items, Akonadi::Item::List()); } void DavGroupwareResource::onRetrieveItemFinished(KJob *job) { onItemFetched(job, ItemUpdateAdd); } void DavGroupwareResource::onItemRefreshed(KJob *job) { ItemFetchUpdateType update = ItemUpdateChange; if (job->property("isRemoval").isValid() && job->property("isRemoval").toBool()) { update = ItemUpdateNone; } onItemFetched(job, update); } void DavGroupwareResource::onItemFetched(KJob *job, ItemFetchUpdateType updateType) { if (job->error()) { if (mSyncErrorNotified) { cancelTask(); } else { cancelTask(i18n("Unable to retrieve item: %1", job->errorText())); mSyncErrorNotified = true; } return; } const KDAV::DavItemFetchJob *fetchJob = qobject_cast< KDAV::DavItemFetchJob *>(job); const KDAV::DavItem davItem = fetchJob->item(); Akonadi::Item item = fetchJob->property("item").value(); Akonadi::Collection collection = fetchJob->property("collection").value(); Akonadi::Item::List extraItems; if (!Utils::parseDavData(davItem, item, extraItems)) { qCWarning(DAVRESOURCE_LOG) << "DavGroupwareResource::onItemFetched: Failed to parse item data. " << item.remoteId(); return; } // update etag item.setRemoteRevision(davItem.etag()); auto etag = mEtagCaches[collection.remoteId()]; etag->setEtag(item.remoteId(), davItem.etag()); if (!extraItems.isEmpty()) { for (int i = 0, total = extraItems.size(); i < total; ++i) { etag->setEtag(extraItems.at(i).remoteId(), davItem.etag()); } Akonadi::ItemModifyJob *j = new Akonadi::ItemModifyJob(extraItems); j->setIgnorePayload(true); } if (updateType == ItemUpdateChange) { changeCommitted(item); } else if (updateType == ItemUpdateAdd) { itemRetrieved(item); } } void DavGroupwareResource::onItemAddedFinished(KJob *job) { const KDAV::DavItemCreateJob *createJob = qobject_cast< KDAV::DavItemCreateJob *>(job); KDAV::DavItem davItem = createJob->item(); Akonadi::Item item = createJob->property("item").value(); item.setRemoteId(davItem.url().toDisplayString()); if (createJob->error()) { qCCritical(DAVRESOURCE_LOG) << "Error when uploading item:" << createJob->error() << createJob->errorString(); if (createJob->canRetryLater()) { retryAfterFailure(createJob->errorString()); } else { cancelTask(i18n("Unable to add item: %1", createJob->errorString())); } return; } Akonadi::Collection collection = createJob->property("collection").value(); if (davItem.etag().isEmpty()) { const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(collection.remoteId(), item.remoteId()); davItem.setUrl(davUrl); KDAV::DavItemFetchJob *fetchJob = new KDAV::DavItemFetchJob(davItem); fetchJob->setProperty("item", QVariant::fromValue(item)); fetchJob->setProperty("collection", QVariant::fromValue(collection)); connect(fetchJob, &KDAV::DavItemFetchJob::result, this, &DavGroupwareResource::onItemRefreshed); fetchJob->start(); } else { item.setRemoteRevision(davItem.etag()); mEtagCaches[collection.remoteId()]->setEtag(davItem.url().toDisplayString(), davItem.etag()); changeCommitted(item); } } void DavGroupwareResource::onItemChangedFinished(KJob *job) { const KDAV::DavItemModifyJob *modifyJob = qobject_cast< KDAV::DavItemModifyJob *>(job); KDAV::DavItem davItem = modifyJob->item(); Akonadi::Collection collection = modifyJob->property("collection").value(); Akonadi::Item item = modifyJob->property("item").value(); Akonadi::Item::List dependentItems = modifyJob->property("dependentItems").value(); bool isRemoval = modifyJob->property("isRemoval").isValid() && modifyJob->property("isRemoval").toBool(); auto cache = mEtagCaches.value(collection.remoteId()); if (!cache) { qCDebug(DAVRESOURCE_LOG) << "Collection has disappeared during item fetch!"; cancelTask(); return; } if (modifyJob->error()) { qCCritical(DAVRESOURCE_LOG) << "Error when uploading item:" << modifyJob->error() << modifyJob->errorString(); if (modifyJob->hasConflict()) { handleConflict(item, dependentItems, modifyJob->freshItem(), isRemoval, modifyJob->freshResponseCode()); } else if (modifyJob->canRetryLater()) { retryAfterFailure(modifyJob->errorString()); } else { cancelTask(i18n("Unable to change item: %1", modifyJob->errorString())); } return; } if (isRemoval) { Akonadi::Item removedItem = job->property("removedItem").value(); if (removedItem.isValid()) { cache->removeEtag(removedItem.remoteId()); changeProcessed(); } } if (davItem.etag().isEmpty()) { const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(item.parentCollection().remoteId(), item.remoteId()); davItem.setUrl(davUrl); KDAV::DavItemFetchJob *fetchJob = new KDAV::DavItemFetchJob(davItem); fetchJob->setProperty("item", QVariant::fromValue(item)); fetchJob->setProperty("collection", QVariant::fromValue(collection)); fetchJob->setProperty("dependentItems", QVariant::fromValue(dependentItems)); fetchJob->setProperty("isRemoval", QVariant::fromValue(isRemoval)); connect(fetchJob, &KDAV::DavItemsFetchJob::result, this, &DavGroupwareResource::onItemRefreshed); fetchJob->start(); } else { if (!isRemoval) { item.setRemoteRevision(davItem.etag()); cache->setEtag(davItem.url().toDisplayString(), davItem.etag()); changeCommitted(item); } if (!dependentItems.isEmpty()) { for (int i = 0, total = dependentItems.size(); i < total; ++i) { dependentItems[i].setRemoteRevision(davItem.etag()); cache->setEtag(dependentItems.at(i).remoteId(), davItem.etag()); } Akonadi::ItemModifyJob *j = new Akonadi::ItemModifyJob(dependentItems); j->setIgnorePayload(true); } } } void DavGroupwareResource::onDeletedItemRecreated(KJob *job) { const KDAV::DavItemCreateJob *createJob = qobject_cast< KDAV::DavItemCreateJob *>(job); KDAV::DavItem davItem = createJob->item(); Akonadi::Item item = createJob->property("item").value(); Akonadi::Collection collection = item.parentCollection(); Akonadi::Item::List dependentItems = createJob->property("dependentItems").value(); if (davItem.etag().isEmpty()) { const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(item.parentCollection().remoteId(), item.remoteId()); davItem.setUrl(davUrl); KDAV::DavItemFetchJob *fetchJob = new KDAV::DavItemFetchJob(davItem); fetchJob->setProperty("item", QVariant::fromValue(item)); fetchJob->setProperty("dependentItems", QVariant::fromValue(dependentItems)); connect(fetchJob, &KDAV::DavItemFetchJob::result, this, &DavGroupwareResource::onItemRefreshed); fetchJob->start(); } else { item.setRemoteRevision(davItem.etag()); auto etag = mEtagCaches[collection.remoteId()]; etag->setEtag(davItem.url().toDisplayString(), davItem.etag()); changeCommitted(item); if (!dependentItems.isEmpty()) { for (int i = 0, total = dependentItems.size(); i < total; ++i) { dependentItems[i].setRemoteRevision(davItem.etag()); etag->setEtag(dependentItems.at(i).remoteId(), davItem.etag()); } Akonadi::ItemModifyJob *j = new Akonadi::ItemModifyJob(dependentItems); j->setIgnorePayload(true); } } } void DavGroupwareResource::onItemRemovedFinished(KJob *job) { if (job->error()) { const KDAV::DavItemDeleteJob *deleteJob = qobject_cast< KDAV::DavItemDeleteJob *>(job); if (deleteJob->hasConflict()) { // Use a shortcut here as we don't show a conflict dialog to the user. handleConflict(Akonadi::Item(), Akonadi::Item::List(), deleteJob->freshItem(), true, 0); } else if (deleteJob->canRetryLater()) { retryAfterFailure(job->errorString()); } else { cancelTask(i18n("Unable to remove item: %1", job->errorString())); } } else { Akonadi::Item item = job->property("item").value(); Akonadi::Collection collection = job->property("collection").value(); mEtagCaches[collection.remoteId()]->removeEtag(item.remoteId()); changeProcessed(); } } void DavGroupwareResource::onCollectionDiscovered(int protocol, const QString &collection, const QString &config) { Settings::self()->addCollectionUrlMapping(KDAV::Protocol(protocol), collection, config); } void DavGroupwareResource::handleConflict(const Item &lI, const Item::List &localDependentItems, const KDAV::DavItem &rI, bool isLocalRemoval, int responseCode) { Akonadi::Item localItem(lI); Akonadi::Item remoteItem, tmpRemoteItem; // The tmp* vars are here to store the result of the parseDavData() call Akonadi::Item::List remoteDependentItems, tmpRemoteDependentItems; // as we have no idea which item triggered the conflict. qCDebug(DAVRESOURCE_LOG) << "Fresh response code is" << responseCode; bool isRemoteRemoval = (responseCode == 404 || responseCode == 410); if (!isRemoteRemoval) { if (!Utils::parseDavData(rI, tmpRemoteItem, tmpRemoteDependentItems)) { // TODO: set a more correct error message here cancelTask(i18n("Unable to change item: %1", QStringLiteral("conflict resolution failed"))); return; // TODO: we can end up here if the remote item was deleted } // Now try to find the item that really triggered the conflict const Akonadi::Item::List allRemoteItems = Akonadi::Item::List() << tmpRemoteItem << tmpRemoteDependentItems; for (const Akonadi::Item &tmpItem : allRemoteItems) { if (tmpItem.payloadData() != localItem.payloadData()) { if (remoteItem.isValid()) { // Oops, we can only manage one changed item at this stage, sorry... // TODO: make this translatable cancelTask(i18n("Unable to change item: %1", QStringLiteral("more than one item was changed in the backend"))); return; } remoteItem = tmpItem; } else { remoteDependentItems << tmpItem; } } } if (isLocalRemoval) { // TODO: implement with the configurable strategy /* * Here by default we don't delete an event that was modified in the backend, and * instead we just abort the current task. * Also, trigger an immediate sync to refresh the item. */ qCDebug(DAVRESOURCE_LOG) << "Local removal conflict"; // TODO: make this translatable cancelTask(i18n("Unable to remove item: %1", QStringLiteral("it was changed in the backend in the meantime"))); synchronize(); } else if (isRemoteRemoval) { // TODO: implement with the configurable strategy /* * Here also it is a bit tricky to clear the item in the local cache as the resource * will not get notified if the user chooses to delete the item and abandon the local * modification. For the time being let's just re-upload the changed item. */ qCDebug(DAVRESOURCE_LOG) << "Remote removal conflict"; Akonadi::Collection collection = localItem.parentCollection(); KDAV::DavItem davItem = Utils::createDavItem(localItem, collection, localDependentItems); QString urlStr = localItem.remoteId(); if (urlStr.contains(QLatin1Char('#'))) { urlStr.truncate(urlStr.indexOf(QLatin1Char('#'))); } const KDAV::DavUrl davUrl = Settings::self()->davUrlFromCollectionUrl(collection.remoteId(), urlStr); davItem.setUrl(davUrl); KDAV::DavItemCreateJob *job = new KDAV::DavItemCreateJob(davItem); job->setProperty("item", QVariant::fromValue(localItem)); job->setProperty("dependentItems", QVariant::fromValue(localDependentItems)); connect(job, &KJob::result, this, &DavGroupwareResource::onDeletedItemRecreated); job->start(); } else { const QString remoteEtag = rI.etag(); Akonadi::Collection collection = localItem.parentCollection(); localItem.setRemoteRevision(remoteEtag); changeCommitted(localItem); // Update the ETag cache in all cases as the new ETag will have to be used // later for any update or deletion mEtagCaches[collection.remoteId()]->setEtag(rI.url().toDisplayString(), remoteEtag); // The first step is to fire a first modify job that will replace the item currently // in the local cache with the one that was found in the backend. Akonadi::Item updatedItem(localItem); updatedItem.setPayloadFromData(remoteItem.payloadData()); updatedItem.setRemoteRevision(remoteEtag); Akonadi::ItemModifyJob *j = new Akonadi::ItemModifyJob(updatedItem); j->setIgnorePayload(false); j->start(); // So now we have in the cache what's in the backend but the user is not aware // that behind the scenes something terrible is happening. Well, nearly... // To notify him of this, and due to the way the conflict handler works, we have // to re-attempt a modification to revert the modify job that was just fired. // So yes, we are effectively re-submitting the client-provided content, but // with a revision that will trigger the conflict dialog. // The only problem is that the user will see that we update the item before // the conflict dialog has time to display (if it's not behind the application // window). localItem.setRevision(0); j = new Akonadi::ItemModifyJob(localItem); j->setIgnorePayload(false); connect(j, &KJob::result, this, &DavGroupwareResource::onConflictModifyJobFinished); j->start(); // Hopefully for the dependent items everything will be fine. Right? // Not so sure in fact. if (!remoteDependentItems.isEmpty()) { auto etag = mEtagCaches[collection.remoteId()]; for (int i = 0; i < remoteDependentItems.size(); ++i) { remoteDependentItems[i].setRemoteRevision(remoteEtag); etag->setEtag(remoteDependentItems.at(i).remoteId(), remoteEtag); } Akonadi::ItemModifyJob *j = new Akonadi::ItemModifyJob(remoteDependentItems); j->setIgnorePayload(true); } } } void DavGroupwareResource::onConflictModifyJobFinished(KJob *job) { Akonadi::ItemModifyJob *j = qobject_cast(job); if (j->error()) { qCCritical(DAVRESOURCE_LOG) << "Conflict update failed: " << job->errorText(); // TODO: what do we do now? We just committed an item that's in a weird state... } } bool DavGroupwareResource::configurationIsValid() { if (Settings::self()->configuredDavUrls().empty()) { Q_EMIT status(NotConfigured, i18n("The resource is not configured yet")); cancelTask(i18n("The resource is not configured yet")); return false; } int newICT = Settings::self()->refreshInterval(); if (newICT == 0) { newICT = -1; } if (newICT != mDavCollectionRoot.cachePolicy().intervalCheckTime()) { Akonadi::CachePolicy cachePolicy = mDavCollectionRoot.cachePolicy(); cachePolicy.setIntervalCheckTime(newICT); mDavCollectionRoot.setCachePolicy(cachePolicy); } if (!Settings::self()->displayName().isEmpty()) { EntityDisplayAttribute *attribute = mDavCollectionRoot.attribute(Collection::AddIfMissing); attribute->setDisplayName(Settings::self()->displayName()); setName(Settings::self()->displayName()); } return true; } void DavGroupwareResource::retryAfterFailure(const QString &errorMessage) { Q_EMIT status(Broken, errorMessage); deferTask(); setTemporaryOffline(Settings::self()->refreshInterval() <= 0 ? 300 : Settings::self()->refreshInterval() * 60); } /*static*/ void DavGroupwareResource::setCollectionIcon(Akonadi::Collection &collection) { const QStringList mimeTypes = collection.contentMimeTypes(); if (mimeTypes.count() == 1) { QHash mapping; mapping.insert(KCalendarCore::Event::eventMimeType(), QStringLiteral("view-calendar")); mapping.insert(KCalendarCore::Todo::todoMimeType(), QStringLiteral("view-calendar-tasks")); mapping.insert(KCalendarCore::Journal::journalMimeType(), QStringLiteral("view-pim-journal")); mapping.insert(KContacts::Addressee::mimeType(), QStringLiteral("view-pim-contacts")); const QString mimetypeFirst = mimeTypes.first(); if (!mimetypeFirst.isEmpty()) { EntityDisplayAttribute *attribute = collection.attribute(Collection::AddIfMissing); attribute->setIconName(mimetypeFirst); } } } AKONADI_RESOURCE_MAIN(DavGroupwareResource) diff --git a/resources/ews/ewsconfigdialog.cpp b/resources/ews/ewsconfigdialog.cpp index bfc88ae40..b2860a974 100644 --- a/resources/ews/ewsconfigdialog.cpp +++ b/resources/ews/ewsconfigdialog.cpp @@ -1,494 +1,495 @@ /* Copyright (C) 2015-2018 Krzysztof Nowicki 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 "ewsconfigdialog.h" #include #include #include #include #include #include #include "ewsautodiscoveryjob.h" #include "ewsgetfolderrequest.h" #include "ewsresource.h" #include "ewssettings.h" #include "ewssubscriptionwidget.h" #include "ewsprogressdialog.h" #include "auth/ewspasswordauth.h" #include "auth/ewsoauth.h" #include "ui_ewsconfigdialog.h" typedef QPair StringPair; static const QVector userAgents = { {QStringLiteral("Microsoft Outlook 2016"), QStringLiteral("Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.6326; Pro)")}, {QStringLiteral("Microsoft Outlook 2013"), QStringLiteral("Microsoft Office/15.0 (Windows NT 6.1; Microsoft Outlook 15.0.4420; Pro)")}, {QStringLiteral("Microsoft Outlook 2010"), QStringLiteral("Microsoft Office/14.0 (Windows NT 6.1; Microsoft Outlook 14.0.5128; Pro)")}, {QStringLiteral("Microsoft Outlook 2011 for Mac"), QStringLiteral("MacOutlook/14.2.0.101115 (Intel Mac OS X 10.6.7)")}, {QStringLiteral("Mozilla Thunderbird 38 for Windows (with ExQuilla)"), QStringLiteral("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Thunderbird/38.2.0")}, {QStringLiteral("Mozilla Thunderbird 38 for Linux (with ExQuilla)"), QStringLiteral("Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.2.0")}, {QStringLiteral("Mozilla Thunderbird 38 for Mac (with ExQuilla)"), QStringLiteral("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:38.0) Gecko/20100101 Thunderbird/38.2.0")} }; static const QString pkeyPasswordMapKey = QStringLiteral("pkey-password"); static bool execJob(KJob *job) { QEventLoop loop; QObject::connect(job, &KJob::finished, &loop, [&](KJob *j) { loop.exit(j->error()); }); job->start(); return loop.exec() == 0; } EwsConfigDialog::EwsConfigDialog(EwsResource *parentResource, EwsClient &client, WId wId, EwsSettings *settings) : QDialog() , mParentResource(parentResource) , mSettings(settings) { if (wId) { + setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(windowHandle(), wId); } QVBoxLayout *mainLayout = new QVBoxLayout(this); QWidget *mainWidget = new QWidget(this); mainLayout->addWidget(mainWidget); mButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(mButtonBox, &QDialogButtonBox::accepted, this, &EwsConfigDialog::dialogAccepted); connect(mButtonBox, &QDialogButtonBox::rejected, this, &EwsConfigDialog::reject); mainLayout->addWidget(mButtonBox); setWindowTitle(i18n("Microsoft Exchange Configuration")); mUi = new Ui::SetupServerView; mUi->setupUi(mainWidget); mUi->accountName->setText(parentResource->name()); mSubWidget = new EwsSubscriptionWidget(client, mSettings.data(), this); mUi->subscriptionTabLayout->addWidget(mSubWidget); mConfigManager = new KConfigDialogManager(this, mSettings.data()); mConfigManager->updateWidgets(); switch (mSettings->retrievalMethod()) { case 0: mUi->pollRadioButton->setChecked(true); break; case 1: mUi->streamingRadioButton->setChecked(true); break; default: break; } const EwsServerVersion &serverVer = client.serverVersion(); if (serverVer.isValid()) { mUi->serverStatusText->setText(i18nc("Server status", "OK")); mUi->serverVersionText->setText(serverVer.toString()); } bool baseUrlEmpty = mUi->kcfg_BaseUrl->text().isEmpty(); mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(!baseUrlEmpty); mUi->tryConnectButton->setEnabled(!baseUrlEmpty); mTryConnectNeeded = baseUrlEmpty; connect(mSettings.data(), &EwsSettings::passwordRequestFinished, mUi->passwordEdit, &KPasswordLineEdit::setPassword); mSettings->requestPassword(false); mUi->authOAuth2RadioButton->setEnabled(true); const auto authMode = mSettings->authMode(); if (authMode == QLatin1String("username-password")) { mUi->authUsernameRadioButton->setChecked(true); } else if (authMode == QLatin1String("oauth2")) { mUi->authOAuth2RadioButton->setChecked(true); mUi->pkeyAuthGroupBox->setEnabled(true); } #ifdef HAVE_QCA mUi->pkeyAuthCert->setText(mSettings->pKeyCert()); mUi->pkeyAuthKey->setText(mSettings->pKeyKey()); connect(mSettings.data(), &EwsSettings::mapRequestFinished, this, [&](const QMap &map) { if (map.contains(pkeyPasswordMapKey)) { mUi->pkeyAuthPassword->setPassword(map[pkeyPasswordMapKey]); } }); mSettings->requestMap(); #endif int selectedIndex = -1; int i = 0; for (const StringPair &item : qAsConst(userAgents)) { mUi->userAgentCombo->addItem(item.first, item.second); if (mSettings->userAgent() == item.second) { selectedIndex = i; } i++; } mUi->userAgentCombo->addItem(i18nc("User Agent", "Custom")); if (!mSettings->userAgent().isEmpty()) { mUi->userAgentGroupBox->setChecked(true); mUi->userAgentCombo->setCurrentIndex(selectedIndex >= 0 ? selectedIndex : mUi->userAgentCombo->count() - 1); mUi->userAgentEdit->setText(mSettings->userAgent()); } else { mUi->userAgentCombo->setCurrentIndex(mUi->userAgentCombo->count()); } QIcon ewsIcon = QIcon::fromTheme(QStringLiteral("akonadi-ews")); mUi->aboutIconLabel->setPixmap(ewsIcon.pixmap(96, 96, QIcon::Normal, QIcon::On)); mUi->aboutTextLabel->setText(i18nc("@info", "Akonadi Resource for Microsoft Exchange Web Services (EWS)")); mUi->aboutCopyrightLabel->setText(i18nc("@info", "Copyright (c) Krzysztof Nowicki 2015-2017")); mUi->aboutVersionLabel->setText(i18nc("@info", "Version %1", QStringLiteral(AKONADI_EWS_VERSION))); mUi->aboutLicenseLabel->setText(i18nc("@info", "Distributed under the GNU Library General Public License version 2.0 or later.")); mUi->aboutUrlLabel->setText(QStringLiteral("https://github.com/KrissN/akonadi-ews")); mUi->pkeyAuthCert->setMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly); mUi->pkeyAuthKey->setMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly); connect(okButton, &QPushButton::clicked, this, &EwsConfigDialog::save); connect(mUi->autodiscoverButton, &QPushButton::clicked, this, &EwsConfigDialog::performAutoDiscovery); connect(mUi->kcfg_Username, &KLineEdit::textChanged, this, &EwsConfigDialog::setAutoDiscoveryNeeded); connect(mUi->passwordEdit, &KPasswordLineEdit::passwordChanged, this, &EwsConfigDialog::setAutoDiscoveryNeeded); connect(mUi->kcfg_Domain, &KLineEdit::textChanged, this, &EwsConfigDialog::setAutoDiscoveryNeeded); connect(mUi->kcfg_HasDomain, &QCheckBox::toggled, this, &EwsConfigDialog::setAutoDiscoveryNeeded); connect(mUi->kcfg_Email, &KLineEdit::textChanged, this, &EwsConfigDialog::setAutoDiscoveryNeeded); connect(mUi->authUsernameRadioButton, &QRadioButton::toggled, this, &EwsConfigDialog::setAutoDiscoveryNeeded); connect(mUi->authOAuth2RadioButton, &QRadioButton::toggled, this, &EwsConfigDialog::setAutoDiscoveryNeeded); connect(mUi->kcfg_BaseUrl, &KLineEdit::textChanged, this, &EwsConfigDialog::enableTryConnect); connect(mUi->tryConnectButton, &QPushButton::clicked, this, &EwsConfigDialog::tryConnect); connect(mUi->userAgentCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &EwsConfigDialog::userAgentChanged); connect(mUi->clearFolderTreeSyncStateButton, &QPushButton::clicked, mParentResource, &EwsResource::clearFolderTreeSyncState); connect(mUi->clearFolderItemSyncStateButton, &QPushButton::clicked, mParentResource, QOverload<>::of(&EwsResource::clearFolderSyncState)); } EwsConfigDialog::~EwsConfigDialog() { delete mUi; } void EwsConfigDialog::save() { mParentResource->setName(mUi->accountName->text()); mConfigManager->updateSettings(); if (mUi->pollRadioButton->isChecked()) { mSettings->setRetrievalMethod(0); } else { mSettings->setRetrievalMethod(1); } /* Erase the subscription id in case subscription is disabled or its parameters changed. This * fill force creation of a new subscription. */ if (!mSubWidget->subscriptionEnabled() || (mSubWidget->subscribedList() != mSettings->serverSubscriptionList())) { mSettings->setEventSubscriptionId(QString()); mSettings->setEventSubscriptionWatermark(QString()); } mSettings->setServerSubscription(mSubWidget->subscriptionEnabled()); if (mSubWidget->subscribedListValid()) { mSettings->setServerSubscriptionList(mSubWidget->subscribedList()); } if (mUi->userAgentGroupBox->isChecked()) { mSettings->setUserAgent(mUi->userAgentEdit->text()); } else { mSettings->setUserAgent(QString()); } if (mUi->authUsernameRadioButton->isChecked()) { mSettings->setAuthMode(QStringLiteral("username-password")); } if (mUi->authOAuth2RadioButton->isChecked()) { mSettings->setAuthMode(QStringLiteral("oauth2")); } #ifdef HAVE_QCA if (mUi->pkeyAuthGroupBox->isEnabled() && !mUi->pkeyAuthCert->text().isEmpty() && !mUi->pkeyAuthKey->text().isEmpty()) { mSettings->setPKeyCert(mUi->pkeyAuthCert->text()); mSettings->setPKeyKey(mUi->pkeyAuthKey->text()); const QMap map = {{pkeyPasswordMapKey, mUi->pkeyAuthPassword->password()}}; mSettings->setMap(map); } #endif if (!mAuthMap.isEmpty()) { mSettings->setMap(mAuthMap); } mSettings->save(); } void EwsConfigDialog::performAutoDiscovery() { mAutoDiscoveryJob = new EwsAutodiscoveryJob(mUi->kcfg_Email->text(), fullUsername(), mUi->passwordEdit->password(), mUi->userAgentGroupBox->isEnabled() ? mUi->userAgentEdit->text() : QString(), mUi->kcfg_EnableNTLMv2->isChecked(), this); connect(mAutoDiscoveryJob, &EwsAutodiscoveryJob::result, this, &EwsConfigDialog::autoDiscoveryFinished); mProgressDialog = new EwsProgressDialog(this, EwsProgressDialog::AutoDiscovery); connect(mProgressDialog, &QDialog::rejected, this, &EwsConfigDialog::autoDiscoveryCancelled); mAutoDiscoveryJob->start(); mProgressDialog->show(); } void EwsConfigDialog::autoDiscoveryFinished(KJob *job) { if (job->error() || job != mAutoDiscoveryJob) { KMessageBox::error(this, job->errorText(), i18nc("Exchange server autodiscovery", "Autodiscovery failed")); mProgressDialog->reject(); } else { mProgressDialog->accept(); mUi->kcfg_BaseUrl->setText(mAutoDiscoveryJob->ewsUrl()); } mAutoDiscoveryJob->deleteLater(); mAutoDiscoveryJob = nullptr; mProgressDialog->deleteLater(); mProgressDialog = nullptr; } void EwsConfigDialog::tryConnectFinished(KJob *job) { if (job->error() || job != mTryConnectJob) { KMessageBox::error(this, job->errorText(), i18nc("Exchange server connection", "Connection failed")); mUi->serverStatusText->setText(i18nc("Exchange server status", "Failed")); mUi->serverVersionText->setText(i18nc("Exchange server version", "Unknown")); mProgressDialog->reject(); } else { mUi->serverStatusText->setText(i18nc("Exchange server status", "OK")); mUi->serverVersionText->setText(mTryConnectJob->serverVersion().toString()); mProgressDialog->accept(); } //mTryConnectJob->deleteLater(); mTryConnectJob = nullptr; //mProgressDialog->deleteLater(); mProgressDialog = nullptr; } void EwsConfigDialog::autoDiscoveryCancelled() { if (mAutoDiscoveryJob) { mAutoDiscoveryJob->kill(); } mProgressDialog->deleteLater(); mProgressDialog = nullptr; } void EwsConfigDialog::tryConnectCancelled() { if (mTryConnectJob) { mTryConnectJob->kill(); } mTryConnectJobCancelled = true; } void EwsConfigDialog::setAutoDiscoveryNeeded() { mAutoDiscoveryNeeded = true; mTryConnectNeeded = true; mAuthMap.clear(); /* Enable the OK button when at least the e-mail and username fields are set. Additionally if * autodiscovery is disabled, enable the OK button only if the base URL is set. */ bool okEnabled = !mUi->kcfg_Username->text().isEmpty() && !mUi->kcfg_Email->text().isEmpty(); if (!mUi->kcfg_AutoDiscovery->isChecked() && mUi->kcfg_BaseUrl->text().isEmpty()) { okEnabled = false; } mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(okEnabled); mUi->pkeyAuthGroupBox->setEnabled(mUi->authOAuth2RadioButton->isChecked()); } QString EwsConfigDialog::fullUsername() const { QString username = mUi->kcfg_Username->text(); if (mUi->kcfg_HasDomain->isChecked()) { username.prepend(mUi->kcfg_Domain->text() + QStringLiteral("\\")); } return username; } void EwsConfigDialog::dialogAccepted() { if (mUi->kcfg_AutoDiscovery->isChecked() && mAutoDiscoveryNeeded) { mAutoDiscoveryJob = new EwsAutodiscoveryJob(mUi->kcfg_Email->text(), fullUsername(), mUi->passwordEdit->password(), mUi->userAgentGroupBox->isEnabled() ? mUi->userAgentEdit->text() : QString(), mUi->kcfg_EnableNTLMv2->isChecked(), this); connect(mAutoDiscoveryJob, &EwsAutodiscoveryJob::result, this, &EwsConfigDialog::autoDiscoveryFinished); mProgressDialog = new EwsProgressDialog(this, EwsProgressDialog::AutoDiscovery); connect(mProgressDialog, &QDialog::rejected, this, &EwsConfigDialog::autoDiscoveryCancelled); mAutoDiscoveryJob->start(); if (!mProgressDialog->exec()) { if (KMessageBox::questionYesNo(this, i18n("Autodiscovery failed. This can be caused by incorrect parameters. Do you still want to save your settings?"), i18n("Exchange server autodiscovery")) == KMessageBox::Yes) { accept(); } return; } } if (mTryConnectNeeded) { EwsClient cli; cli.setUrl(mUi->kcfg_BaseUrl->text()); const auto auth = prepareAuth(); cli.setAuth(auth); if (mUi->userAgentGroupBox->isChecked()) { cli.setUserAgent(mUi->userAgentEdit->text()); } cli.setEnableNTLMv2(mUi->kcfg_EnableNTLMv2->isChecked()); mTryConnectJob = new EwsGetFolderRequest(cli, this); mTryConnectJob->setFolderShape(EwsFolderShape(EwsShapeIdOnly)); mTryConnectJob->setFolderIds(EwsId::List() << EwsId(EwsDIdInbox)); connect(mTryConnectJob, &EwsGetFolderRequest::result, this, &EwsConfigDialog::tryConnectFinished); mProgressDialog = new EwsProgressDialog(this, EwsProgressDialog::TryConnect); connect(mProgressDialog, &QDialog::rejected, this, &EwsConfigDialog::tryConnectCancelled); mTryConnectJob->start(); if (!execJob(mTryConnectJob)) { if (!mTryConnectJobCancelled) { if (KMessageBox::questionYesNo(this, i18n("Connecting to Exchange failed. This can be caused by incorrect parameters. Do you still want to save your settings?"), i18n("Exchange server connection")) == KMessageBox::Yes) { accept(); } } return; } else { accept(); } } if (!mTryConnectNeeded && !mAutoDiscoveryNeeded) { accept(); } } void EwsConfigDialog::enableTryConnect() { mTryConnectNeeded = true; bool baseUrlEmpty = mUi->kcfg_BaseUrl->text().isEmpty(); /* Enable the OK button when at least the e-mail and username fields are set. Additionally if * autodiscovery is disabled, enable the OK button only if the base URL is set. */ bool okEnabled = !mUi->kcfg_Username->text().isEmpty() && !mUi->kcfg_Email->text().isEmpty(); if (!mUi->kcfg_AutoDiscovery->isChecked() && baseUrlEmpty) { okEnabled = false; } mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(okEnabled); mUi->tryConnectButton->setEnabled(!baseUrlEmpty); } void EwsConfigDialog::tryConnect() { EwsClient cli; cli.setUrl(mUi->kcfg_BaseUrl->text()); const auto auth = prepareAuth(); cli.setAuth(auth); if (mUi->userAgentGroupBox->isChecked()) { cli.setUserAgent(mUi->userAgentEdit->text()); } cli.setEnableNTLMv2(mUi->kcfg_EnableNTLMv2->isChecked()); mTryConnectJob = new EwsGetFolderRequest(cli, this); mTryConnectJob->setFolderShape(EwsFolderShape(EwsShapeIdOnly)); mTryConnectJob->setFolderIds(EwsId::List() << EwsId(EwsDIdInbox)); mTryConnectJobCancelled = false; mProgressDialog = new EwsProgressDialog(this, EwsProgressDialog::TryConnect); connect(mProgressDialog, &QDialog::rejected, this, &EwsConfigDialog::tryConnectCancelled); mProgressDialog->show(); if (!execJob(mTryConnectJob)) { if (!mTryConnectJobCancelled) { mUi->serverStatusText->setText(i18nc("Exchange server status", "Failed")); mUi->serverVersionText->setText(i18nc("Exchange server version", "Unknown")); KMessageBox::error(this, mTryConnectJob->errorText(), i18n("Connection failed")); } } else { mUi->serverStatusText->setText(i18nc("Exchange server status", "OK")); mUi->serverVersionText->setText(mTryConnectJob->serverVersion().toString()); } mProgressDialog->hide(); mTryConnectJob = nullptr; } void EwsConfigDialog::userAgentChanged(int) { QString data = mUi->userAgentCombo->currentData().toString(); mUi->userAgentEdit->setEnabled(data.isEmpty()); if (!data.isEmpty()) { mUi->userAgentEdit->setText(data); } } EwsAbstractAuth *EwsConfigDialog::prepareAuth() { EwsAbstractAuth *auth = nullptr; if (mUi->authOAuth2RadioButton->isChecked()) { auth = new EwsOAuth(this, mUi->kcfg_Email->text(), mSettings->oAuth2AppId(), mSettings->oAuth2ReturnUri()); } else if (mUi->authUsernameRadioButton->isChecked()) { auth = new EwsPasswordAuth(fullUsername(), this); } auth->setAuthParentWidget(this); #ifdef HAVE_QCA if (mUi->pkeyAuthGroupBox->isEnabled() && !mUi->pkeyAuthCert->text().isEmpty() && !mUi->pkeyAuthKey->text().isEmpty()) { auth->setPKeyAuthCertificateFiles(mUi->pkeyAuthCert->text(), mUi->pkeyAuthKey->text()); mAuthMap[pkeyPasswordMapKey] = mUi->pkeyAuthPassword->password(); } #endif connect(auth, &EwsAbstractAuth::requestWalletPassword, this, [&](bool) { auth->walletPasswordRequestFinished(mUi->passwordEdit->password()); }); connect(auth, &EwsAbstractAuth::requestWalletMap, this, [&]() { auth->walletMapRequestFinished(mAuthMap); }); connect(auth, &EwsAbstractAuth::setWalletMap, this, [&](const QMap &map) { mAuthMap = map; }); auth->init(); QEventLoop loop; bool authFinished = false; connect(auth, &EwsAbstractAuth::authSucceeded, this, [&]() { authFinished = true; loop.exit(0); }); connect(auth, &EwsAbstractAuth::authFailed, this, [&](const QString &) { authFinished = true; loop.exit(0); }); if (auth->authenticate(true)) { if (!authFinished) { loop.exec(); } } return auth; } diff --git a/resources/ews/ewsmtaconfigdialog.cpp b/resources/ews/ewsmtaconfigdialog.cpp index 34f72763e..4c7fe4a1f 100644 --- a/resources/ews/ewsmtaconfigdialog.cpp +++ b/resources/ews/ewsmtaconfigdialog.cpp @@ -1,92 +1,93 @@ /* Copyright (C) 2015-2016 Krzysztof Nowicki 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 "ewsmtaconfigdialog.h" #include #include #include #include #include #include #include #include #include "ewsmtaresource.h" #include "ewsres_mta_debug.h" #include "ewsmtasettings.h" #include "ui_ewsmtaconfigdialog.h" EwsMtaConfigDialog::EwsMtaConfigDialog(EwsMtaResource *parentResource, WId wId) : QDialog() , mParentResource(parentResource) { if (wId) { + setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(windowHandle(), wId); } QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(this); mButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mainLayout->addWidget(mainWidget); QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(mButtonBox, &QDialogButtonBox::accepted, this, &EwsMtaConfigDialog::accept); connect(mButtonBox, &QDialogButtonBox::rejected, this, &EwsMtaConfigDialog::reject); mainLayout->addWidget(mButtonBox); setWindowTitle(i18n("Microsoft Exchange Mail Transport Configuration")); mUi = new Ui::SetupServerView; mUi->setupUi(mainWidget); mUi->accountName->setText(parentResource->name()); Akonadi::AgentFilterProxyModel *model = mUi->resourceWidget->agentFilterProxyModel(); model->addCapabilityFilter(QStringLiteral("X-EwsMailTransport")); mUi->resourceWidget->view()->setSelectionMode(QAbstractItemView::SingleSelection); for (int i = 0, total = model->rowCount(); i < total; ++i) { QModelIndex index = model->index(i, 0); QVariant v = model->data(index, Akonadi::AgentInstanceModel::InstanceIdentifierRole); if (v.toString() == EwsMtaSettings::ewsResource()) { mUi->resourceWidget->view()->setCurrentIndex(index); } } connect(okButton, &QPushButton::clicked, this, &EwsMtaConfigDialog::save); } EwsMtaConfigDialog::~EwsMtaConfigDialog() { delete mUi; } void EwsMtaConfigDialog::save() { if (!mUi->resourceWidget->selectedAgentInstances().isEmpty()) { EwsMtaSettings::setEwsResource(mUi->resourceWidget->selectedAgentInstances().first().identifier()); mParentResource->setName(mUi->accountName->text()); EwsMtaSettings::self()->save(); } else { qCWarning(EWSRES_MTA_LOG) << "Any agent instance selected"; } } diff --git a/resources/google/common/googlesettingsdialog.cpp b/resources/google/common/googlesettingsdialog.cpp index 1b523620d..6df605310 100644 --- a/resources/google/common/googlesettingsdialog.cpp +++ b/resources/google/common/googlesettingsdialog.cpp @@ -1,265 +1,266 @@ /* Copyright (C) 2013 Daniel Vrátil This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "googlesettingsdialog.h" #include "googleaccountmanager.h" #include "googlesettings.h" #include "googleresource.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KGAPI2::Job *) using namespace KGAPI2; GoogleSettingsDialog::GoogleSettingsDialog(GoogleAccountManager *accountManager, WId wId, GoogleResource *parent) : QDialog() , m_parentResource(parent) , m_accountManager(accountManager) { + setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(windowHandle(), wId); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QVBoxLayout *topLayout = new QVBoxLayout(this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &GoogleSettingsDialog::slotSaveSettings); connect(buttonBox, &QDialogButtonBox::rejected, this, &GoogleSettingsDialog::reject); QWidget *widget = new QWidget(this); topLayout->addWidget(widget); topLayout->addWidget(buttonBox); QVBoxLayout *mainLayout = new QVBoxLayout(widget); mainLayout->setContentsMargins(0, 0, 0, 0); m_mainLayout = mainLayout; m_accGroupBox = new QGroupBox(i18n("Accounts"), this); mainLayout->addWidget(m_accGroupBox); QHBoxLayout *accLayout = new QHBoxLayout(m_accGroupBox); m_accComboBox = new KComboBox(m_accGroupBox); accLayout->addWidget(m_accComboBox, 1); connect(m_accComboBox, QOverload::of(&KComboBox::currentTextChanged), this, &GoogleSettingsDialog::currentAccountChanged); m_addAccButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add-user")), i18n("&Add"), m_accGroupBox); accLayout->addWidget(m_addAccButton); connect(m_addAccButton, &QPushButton::clicked, this, &GoogleSettingsDialog::slotAddAccountClicked); m_removeAccButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove-user")), i18n("&Remove"), m_accGroupBox); accLayout->addWidget(m_removeAccButton); connect(m_removeAccButton, &QPushButton::clicked, this, &GoogleSettingsDialog::slotRemoveAccountClicked); QGroupBox *refreshBox = new QGroupBox(i18n("Refresh"), this); mainLayout->addWidget(refreshBox); QGridLayout *refreshLayout = new QGridLayout(refreshBox); m_enableRefresh = new QCheckBox(i18n("Enable interval refresh"), refreshBox); m_enableRefresh->setChecked(Settings::self()->enableIntervalCheck()); refreshLayout->addWidget(m_enableRefresh, 0, 0, 1, 2); QLabel *label = new QLabel(i18n("Refresh interval:")); refreshLayout->addWidget(label, 1, 0); m_refreshSpinBox = new KPluralHandlingSpinBox(this); m_refreshSpinBox->setMaximum(720); m_refreshSpinBox->setMinimum(10); m_refreshSpinBox->setSingleStep(1); m_refreshSpinBox->setValue(30); m_refreshSpinBox->setDisplayIntegerBase(10); m_refreshSpinBox->setSuffix(ki18np(" minute", " minutes")); m_refreshSpinBox->setEnabled(Settings::self()->enableIntervalCheck()); refreshLayout->addWidget(m_refreshSpinBox, 1, 1); connect(m_enableRefresh, &QCheckBox::toggled, m_refreshSpinBox, &KPluralHandlingSpinBox::setEnabled); if (m_enableRefresh->isEnabled()) { m_refreshSpinBox->setValue(Settings::self()->intervalCheckTime()); } QMetaObject::invokeMethod(this, &GoogleSettingsDialog::reloadAccounts, Qt::QueuedConnection); } GoogleSettingsDialog::~GoogleSettingsDialog() { } QVBoxLayout *GoogleSettingsDialog::mainLayout() const { return m_mainLayout; } GoogleAccountManager *GoogleSettingsDialog::accountManager() const { return m_accountManager; } KGAPI2::AccountPtr GoogleSettingsDialog::currentAccount() const { return m_accountManager->findAccount(m_accComboBox->currentText()); } void GoogleSettingsDialog::reloadAccounts() { disconnect(m_accComboBox, QOverload::of(&KComboBox::currentTextChanged), this, &GoogleSettingsDialog::currentAccountChanged); m_accComboBox->clear(); const AccountsList accounts = m_accountManager->listAccounts(); for (const AccountPtr &account : accounts) { m_accComboBox->addItem(account->accountName()); } int index = m_accComboBox->findText(m_parentResource->settings()->account(), Qt::MatchExactly); if (index > -1) { m_accComboBox->setCurrentIndex(index); } connect(m_accComboBox, QOverload::of(&KComboBox::currentTextChanged), this, &GoogleSettingsDialog::currentAccountChanged); m_removeAccButton->setEnabled(m_accComboBox->count() > 0); Q_EMIT currentAccountChanged(m_accComboBox->currentText()); } void GoogleSettingsDialog::slotAddAccountClicked() { AccountPtr account(new Account()); // FIXME: We need a proper API for this account->addScope(Account::contactsScopeUrl()); account->addScope(Account::calendarScopeUrl()); account->addScope(Account::tasksScopeUrl()); account->addScope(Account::accountInfoEmailScopeUrl()); account->addScope(Account::accountInfoScopeUrl()); AuthJob *authJob = new AuthJob(account, m_parentResource->settings()->clientId(), m_parentResource->settings()->clientSecret()); connect(authJob, &AuthJob::finished, this, &GoogleSettingsDialog::slotAccountAuthenticated); } void GoogleSettingsDialog::slotRemoveAccountClicked() { const AccountPtr account = currentAccount(); if (!account) { return; } if (KMessageBox::warningYesNo( this, i18n("Do you really want to revoke access to account %1?" "

This will revoke access to all resources using this account!

", account->accountName()), i18n("Revoke Access?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QString(), KMessageBox::Dangerous) != KMessageBox::Yes) { return; } m_accountManager->removeAccount(account->accountName()); reloadAccounts(); } void GoogleSettingsDialog::slotAccountAuthenticated(Job *job) { AuthJob *authJob = qobject_cast(job); const AccountPtr account = authJob->account(); if (authJob->error() != KGAPI2::NoError) { KMessageBox::sorry(this, authJob->errorString()); return; } if (!m_accountManager->storeAccount(account)) { qWarning() << "Failed to add account to KWallet"; } reloadAccounts(); } bool GoogleSettingsDialog::handleError(Job *job) { if ((job->error() == KGAPI2::NoError) || (job->error() == KGAPI2::OK)) { return true; } if (job->error() == KGAPI2::Unauthorized) { qDebug() << job << job->errorString(); const AccountPtr account = currentAccount(); const QList resourceScopes = m_parentResource->scopes(); for (const QUrl &scope : resourceScopes) { if (!account->scopes().contains(scope)) { account->addScope(scope); } } AuthJob *authJob = new AuthJob(account, m_parentResource->settings()->clientId(), m_parentResource->settings()->clientSecret(), this); authJob->setProperty(JOB_PROPERTY, QVariant::fromValue(job)); connect(authJob, &AuthJob::finished, this, &GoogleSettingsDialog::slotAuthJobFinished); return false; } KMessageBox::sorry(this, job->errorString()); job->deleteLater(); return false; } void GoogleSettingsDialog::slotAuthJobFinished(Job *job) { qDebug(); if (job->error() != KGAPI2::NoError) { KMessageBox::sorry(this, job->errorString()); return; } AuthJob *authJob = qobject_cast(job); const AccountPtr account = authJob->account(); if (!m_accountManager->storeAccount(account)) { qWarning() << "Failed to store account in KWallet"; } KGAPI2::Job *otherJob = job->property(JOB_PROPERTY).value(); otherJob->setAccount(account); otherJob->restart(); job->deleteLater(); } void GoogleSettingsDialog::slotSaveSettings() { Settings::self()->setEnableIntervalCheck(m_enableRefresh->isChecked()); Settings::self()->setIntervalCheckTime(m_refreshSpinBox->value()); saveSettings(); accept(); } diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp index 63d7d846a..4fb9cc8d4 100644 --- a/resources/imap/imapresource.cpp +++ b/resources/imap/imapresource.cpp @@ -1,92 +1,93 @@ /* Copyright (c) 2007 Till Adam Copyright (C) 2008 Omat Holding B.V. Copyright (C) 2009 Kevin Ottens Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens 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 "imapresource.h" #include "setupserver.h" #include "settings.h" #include "sessionpool.h" #include "sessionuiproxy.h" #include "utils.h" #include #ifdef WITH_GMAIL_XOAUTH2 #include "passwordrequester.h" #else #include "settingspasswordrequester.h" #endif #include #include #include ImapResource::ImapResource(const QString &id) : ImapResourceBase(id) { #ifdef WITH_GMAIL_XOAUTH2 m_pool->setPasswordRequester(new PasswordRequester(this, m_pool)); #else m_pool->setPasswordRequester(new SettingsPasswordRequester(this, m_pool)); #endif m_pool->setSessionUiProxy(SessionUiProxy::Ptr(new SessionUiProxy)); m_pool->setClientId(clientId()); } ImapResource::~ImapResource() { } QString ImapResource::defaultName() const { return i18n("IMAP Account"); } QByteArray ImapResource::clientId() const { return "Kontact IMAP Resource"; } QDialog *ImapResource::createConfigureDialog(WId windowId) { SetupServer *dlg = new SetupServer(this, windowId); + dlg->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(dlg->windowHandle(), windowId); dlg->setWindowTitle(i18n("IMAP Account Settings")); dlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("network-server"))); connect(dlg, &SetupServer::finished, this, &ImapResource::onConfigurationDone); return dlg; } void ImapResource::onConfigurationDone(int result) { SetupServer *dlg = qobject_cast(sender()); if (result) { if (dlg->shouldClearCache()) { clearCache(); } settings()->save(); } dlg->deleteLater(); } diff --git a/resources/imap/imapresourcebase.cpp b/resources/imap/imapresourcebase.cpp index 24e776053..1071b14ed 100644 --- a/resources/imap/imapresourcebase.cpp +++ b/resources/imap/imapresourcebase.cpp @@ -1,750 +1,751 @@ /* Copyright (c) 2007 Till Adam Copyright (C) 2008 Omat Holding B.V. Copyright (C) 2009 Kevin Ottens Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens 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 "imapresourcebase.h" #include #include #include #include "imapresource_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collectionannotationsattribute.h" #include "collectionflagsattribute.h" #include "imapaclattribute.h" #include "imapquotaattribute.h" #include "noselectattribute.h" #include "uidvalidityattribute.h" #include "uidnextattribute.h" #include "highestmodseqattribute.h" #include "settings.h" #include "imapaccount.h" #include "imapidlemanager.h" #include "resourcestate.h" #include "subscriptiondialog.h" #include "addcollectiontask.h" #include "additemtask.h" #include "changecollectiontask.h" #include "changeitemsflagstask.h" #include "changeitemtask.h" #include "expungecollectiontask.h" #include "movecollectiontask.h" #include "moveitemstask.h" #include "removecollectionrecursivetask.h" #include "retrievecollectionmetadatatask.h" #include "retrievecollectionstask.h" #include "retrieveitemtask.h" #include "retrieveitemstask.h" #include "searchtask.h" #include "settingspasswordrequester.h" #include "sessionpool.h" #include "sessionuiproxy.h" #include "imapflags.h" #include "resourceadaptor.h" #ifdef MAIL_SERIALIZER_PLUGIN_STATIC Q_IMPORT_PLUGIN(akonadi_serializer_mail) #endif Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QWeakPointer) using namespace Akonadi; ImapResourceBase::ImapResourceBase(const QString &id) : ResourceBase(id) , m_pool(new SessionPool(2, this)) , m_settings(nullptr) , mSubscriptions(nullptr) , m_idle(nullptr) { QTimer::singleShot(0, this, &ImapResourceBase::updateResourceName); connect(m_pool, &SessionPool::connectDone, this, &ImapResourceBase::onConnectDone); connect(m_pool, &SessionPool::connectionLost, this, &ImapResourceBase::onConnectionLost); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); // For QMetaObject::invokeMethod() qRegisterMetaType >(); changeRecorder()->fetchCollection(true); changeRecorder()->collectionFetchScope().setAncestorRetrieval(CollectionFetchScope::All); changeRecorder()->collectionFetchScope().setIncludeStatistics(true); changeRecorder()->collectionFetchScope().fetchAttribute(); changeRecorder()->itemFetchScope().fetchFullPayload(true); changeRecorder()->itemFetchScope().setAncestorRetrieval(ItemFetchScope::All); changeRecorder()->itemFetchScope().setFetchModificationTime(false); //(Andras) disable now, as tokoe reported problems with it and the mail filter: changeRecorder()->fetchChangedOnly( true ); setHierarchicalRemoteIdentifiersEnabled(true); setItemTransactionMode(ItemSync::MultipleTransactions); // we can recover from incomplete syncs, so we can use a faster mode ItemFetchScope scope(changeRecorder()->itemFetchScope()); scope.fetchFullPayload(false); scope.setAncestorRetrieval(ItemFetchScope::None); setItemSynchronizationFetchScope(scope); setDisableAutomaticItemDeliveryDone(true); setItemSyncBatchSize(100); connect(this, &AgentBase::reloadConfiguration, this, &ImapResourceBase::reconnect); m_statusMessageTimer = new QTimer(this); m_statusMessageTimer->setSingleShot(true); connect(m_statusMessageTimer, &QTimer::timeout, this, &ImapResourceBase::clearStatusMessage); connect(this, &AgentBase::error, this, &ImapResourceBase::showError); QMetaObject::invokeMethod(this, &ImapResourceBase::delayedInit, Qt::QueuedConnection); } void ImapResourceBase::delayedInit() { settings(); // make sure the D-Bus settings interface is up new ImapResourceBaseAdaptor(this); setNeedsNetwork(needsNetwork()); // Migration issue: trash folder had ID in config, but didn't have SpecialCollections attribute, fix that. if (!settings()->trashCollectionMigrated()) { const Akonadi::Collection::Id trashCollection = settings()->trashCollection(); if (trashCollection != -1) { Collection attributeCollection(trashCollection); SpecialCollections::setSpecialCollectionType("trash", attributeCollection); } settings()->setTrashCollectionMigrated(true); } } ImapResourceBase::~ImapResourceBase() { //Destroy everything that could cause callbacks immediately, otherwise the callbacks can result in a crash. delete m_idle; m_idle = nullptr; for (ResourceTask *task : qAsConst(m_taskList)) { delete task; } m_taskList.clear(); delete m_pool; delete m_settings; } void ImapResourceBase::aboutToQuit() { //TODO the resource would ideally have to signal when it's done with logging out etc, before the destructor gets called if (m_idle) { m_idle->stop(); } for (ResourceTask *task : qAsConst(m_taskList)) { task->kill(); } m_pool->disconnect(); } void ImapResourceBase::updateResourceName() { if (name() == identifier()) { const QString agentType = AgentManager::self()->instance(identifier()).type().identifier(); const QString agentsrcFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QLatin1String("akonadi/agentsrc"); const QSettings agentsrc(agentsrcFile, QSettings::IniFormat); const int instanceCounter = agentsrc.value( QStringLiteral("InstanceCounters/%1/InstanceCounter").arg(agentType), -1).toInt(); if (instanceCounter > 0) { setName(QStringLiteral("%1 %2").arg(defaultName()).arg(instanceCounter)); } else { setName(defaultName()); } } } // ----------------------------------------------------------------------------- void ImapResourceBase::configure(WId windowId) { if (createConfigureDialog(windowId)->exec() == QDialog::Accepted) { Q_EMIT configurationDialogAccepted(); reconnect(); } else { Q_EMIT configurationDialogRejected(); } } // ---------------------------------------------------------------------------------- void ImapResourceBase::startConnect(const QVariant &) { if (settings()->imapServer().isEmpty()) { setOnline(false); Q_EMIT status(NotConfigured, i18n("No server configured yet.")); taskDone(); return; } m_pool->disconnect(); // reset all state, delete any old account ImapAccount *account = new ImapAccount; settings()->loadAccount(account); const bool result = m_pool->connect(account); Q_ASSERT(result); Q_UNUSED(result); } int ImapResourceBase::configureSubscription(qlonglong windowId) { if (mSubscriptions) { return 0; } if (!m_pool->account()) { return -2; } const QString password = settings()->password(); if (password.isEmpty()) { return -1; } mSubscriptions = new SubscriptionDialog(nullptr, SubscriptionDialog::AllowToEnableSubscription); if (windowId) { + mSubscriptions->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(mSubscriptions->windowHandle(), windowId); } mSubscriptions->setWindowTitle(i18nc("@title:window", "Serverside Subscription")); mSubscriptions->setWindowIcon(QIcon::fromTheme(QStringLiteral("network-server"))); mSubscriptions->connectAccount(*m_pool->account(), password); mSubscriptions->setSubscriptionEnabled(settings()->subscriptionEnabled()); if (mSubscriptions->exec()) { settings()->setSubscriptionEnabled(mSubscriptions->subscriptionEnabled()); settings()->save(); Q_EMIT configurationDialogAccepted(); reconnect(); } delete mSubscriptions; return 0; } void ImapResourceBase::onConnectDone(int errorCode, const QString &errorString) { switch (errorCode) { case SessionPool::NoError: setOnline(true); taskDone(); Q_EMIT status(Idle, i18n("Connection established.")); synchronizeCollectionTree(); break; case SessionPool::PasswordRequestError: case SessionPool::EncryptionError: case SessionPool::LoginFailError: case SessionPool::CapabilitiesTestError: case SessionPool::IncompatibleServerError: setOnline(false); Q_EMIT status(Broken, errorString); cancelTask(errorString); return; case SessionPool::CouldNotConnectError: case SessionPool::CancelledError: // e.g. we got disconnected during login Q_EMIT status(Idle, i18n("Server is not available.")); deferTask(); setTemporaryOffline((m_pool->account() && m_pool->account()->timeout() > 0) ? m_pool->account()->timeout() : 300); return; case SessionPool::ReconnectNeededError: reconnect(); return; case SessionPool::NoAvailableSessionError: qFatal("Shouldn't happen"); return; } } void ImapResourceBase::onConnectionLost(KIMAP::Session */*session*/) { if (!m_pool->isConnected()) { reconnect(); } } ResourceStateInterface::Ptr ImapResourceBase::createResourceState(const TaskArguments &args) { return ResourceStateInterface::Ptr(new ResourceState(this, args)); } Settings *ImapResourceBase::settings() const { if (m_settings == nullptr) { m_settings = new Settings; } return m_settings; } // ---------------------------------------------------------------------------------- bool ImapResourceBase::retrieveItem(const Akonadi::Item &item, const QSet &parts) { // The collection name is empty here... //Q_EMIT status( AgentBase::Running, i18nc( "@info:status", "Retrieving item in '%1'", item.parentCollection().name() ) ); RetrieveItemTask *task = new RetrieveItemTask(createResourceState(TaskArguments(item, parts)), this); task->start(m_pool); queueTask(task); return true; } void ImapResourceBase::itemAdded(const Item &item, const Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Adding item in '%1'", collection.name())); startTask(new AddItemTask(createResourceState(TaskArguments(item, collection)), this)); } void ImapResourceBase::itemChanged(const Item &item, const QSet &parts) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating item in '%1'", item.parentCollection().name())); startTask(new ChangeItemTask(createResourceState(TaskArguments(item, parts)), this)); } void ImapResourceBase::itemsFlagsChanged(const Item::List &items, const QSet< QByteArray > &addedFlags, const QSet< QByteArray > &removedFlags) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating items")); startTask(new ChangeItemsFlagsTask(createResourceState(TaskArguments(items, addedFlags, removedFlags)), this)); } void ImapResourceBase::itemsRemoved(const Akonadi::Item::List &items) { const QString mailBox = ResourceStateInterface::mailBoxForCollection(items.first().parentCollection(), false); if (mailBox.isEmpty()) { // this item will be removed soon by its parent collection changeProcessed(); return; } Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing items")); startTask(new ChangeItemsFlagsTask(createResourceState(TaskArguments(items, QSet() << ImapFlags::Deleted, QSet())), this)); } void ImapResourceBase::itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination) { if (items.first().parentCollection() != destination) { // should have been set by the server qCWarning(IMAPRESOURCE_LOG) << "Collections don't match: destination=" << destination.id() << "; items parent=" << items.first().parentCollection().id() << "; source collection=" << source.id(); //Q_ASSERT( false ); //TODO: Find out why this happens cancelTask(); return; } Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Moving items from '%1' to '%2'", source.name(), destination.name())); startTask(new MoveItemsTask(createResourceState(TaskArguments(items, source, destination)), this)); } // ---------------------------------------------------------------------------------- void ImapResourceBase::retrieveCollections() { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving folders")); startTask(new RetrieveCollectionsTask(createResourceState(TaskArguments()), this)); } void ImapResourceBase::retrieveCollectionAttributes(const Akonadi::Collection &col) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving extra folder information for '%1'", col.name())); startTask(new RetrieveCollectionMetadataTask(createResourceState(TaskArguments(col)), this)); } void ImapResourceBase::retrieveItems(const Collection &col) { synchronizeCollectionAttributes(col.id()); setItemStreamingEnabled(true); RetrieveItemsTask *task = new RetrieveItemsTask(createResourceState(TaskArguments(col)), this); connect(task, SIGNAL(status(int,QString)), SIGNAL(status(int,QString))); connect(this, &ResourceBase::retrieveNextItemSyncBatch, task, &RetrieveItemsTask::onReadyForNextBatch); startTask(task); } void ImapResourceBase::collectionAdded(const Collection &collection, const Collection &parent) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Creating folder '%1'", collection.name())); startTask(new AddCollectionTask(createResourceState(TaskArguments(collection, parent)), this)); } void ImapResourceBase::collectionChanged(const Collection &collection, const QSet &parts) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating folder '%1'", collection.name())); startTask(new ChangeCollectionTask(createResourceState(TaskArguments(collection, parts)), this)); } void ImapResourceBase::collectionRemoved(const Collection &collection) { //TODO Move this to the task const QString mailBox = ResourceStateInterface::mailBoxForCollection(collection, false); if (mailBox.isEmpty()) { // this collection will be removed soon by its parent collection changeProcessed(); return; } Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing folder '%1'", collection.name())); startTask(new RemoveCollectionRecursiveTask(createResourceState(TaskArguments(collection)), this)); } void ImapResourceBase::collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Moving folder '%1' from '%2' to '%3'", collection.name(), source.name(), destination.name())); startTask(new MoveCollectionTask(createResourceState(TaskArguments(collection, source, destination)), this)); } void ImapResourceBase::addSearch(const QString &query, const QString &queryLanguage, const Collection &resultCollection) { Q_UNUSED(query); Q_UNUSED(queryLanguage); Q_UNUSED(resultCollection); } void ImapResourceBase::removeSearch(const Collection &resultCollection) { Q_UNUSED(resultCollection); } void ImapResourceBase::search(const QString &query, const Collection &collection) { QVariantMap arg; arg[QStringLiteral("query")] = query; arg[QStringLiteral("collection")] = QVariant::fromValue(collection); scheduleCustomTask(this, "doSearch", arg); } void ImapResourceBase::doSearch(const QVariant &arg) { const QVariantMap map = arg.toMap(); const QString query = map[QStringLiteral("query")].toString(); const Collection collection = map[QStringLiteral("collection")].value(); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Searching...")); startTask(new SearchTask(createResourceState(TaskArguments(collection)), query, this)); } // ---------------------------------------------------------------------------------- void ImapResourceBase::scheduleConnectionAttempt() { // block all other tasks, until we are connected scheduleCustomTask(this, "startConnect", QVariant(), ResourceBase::Prepend); } void ImapResourceBase::doSetOnline(bool online) { qCDebug(IMAPRESOURCE_LOG) << "online=" << online; if (!online) { for (ResourceTask *task : qAsConst(m_taskList)) { task->kill(); delete task; } m_taskList.clear(); m_pool->cancelPasswordRequests(); if (m_pool->isConnected()) { m_pool->disconnect(); } if (m_idle) { m_idle->stop(); delete m_idle; m_idle = nullptr; } settings()->clearCachedPassword(); } else if (online && !m_pool->isConnected()) { scheduleConnectionAttempt(); } ResourceBase::doSetOnline(online); } QChar ImapResourceBase::separatorCharacter() const { return m_separatorCharacter; } void ImapResourceBase::setSeparatorCharacter(QChar separator) { m_separatorCharacter = separator; } bool ImapResourceBase::needsNetwork() const { const QString hostName = settings()->imapServer().section(QLatin1Char(':'), 0, 0); // ### is there a better way to do this? if (hostName == QLatin1String("127.0.0.1") || hostName == QLatin1String("localhost") || hostName == QHostInfo::localHostName()) { return false; } return true; } void ImapResourceBase::reconnect() { setNeedsNetwork(needsNetwork()); setOnline(false); // we are not connected initially setOnline(true); } // ---------------------------------------------------------------------------------- void ImapResourceBase::startIdleIfNeeded() { if (!m_idle) { startIdle(); } } void ImapResourceBase::startIdle() { delete m_idle; m_idle = nullptr; if (!m_pool->serverCapabilities().contains(QLatin1String("IDLE"))) { return; } //Without password we don't even have to try if (m_pool->account()->authenticationMode() != KIMAP::LoginJob::GSSAPI && settings()->password().isEmpty()) { return; } const QStringList ridPath = settings()->idleRidPath(); if (ridPath.size() < 2) { return; } Collection c, p; p.setParentCollection(Collection::root()); for (int i = ridPath.size() - 1; i > 0; --i) { p.setRemoteId(ridPath.at(i)); c.setParentCollection(p); p = c; } c.setRemoteId(ridPath.first()); Akonadi::CollectionFetchScope scope; scope.setResource(identifier()); scope.setAncestorRetrieval(Akonadi::CollectionFetchScope::All); Akonadi::CollectionFetchJob *fetch = new Akonadi::CollectionFetchJob(c, Akonadi::CollectionFetchJob::Base, this); fetch->setFetchScope(scope); connect(fetch, &KJob::result, this, &ImapResourceBase::onIdleCollectionFetchDone); } void ImapResourceBase::onIdleCollectionFetchDone(KJob *job) { if (job->error()) { qCWarning(IMAPRESOURCE_LOG) << "CollectionFetch for idling failed." << "error=" << job->error() << ", errorString=" << job->errorString(); return; } Akonadi::CollectionFetchJob *fetch = static_cast(job); //Can be empty if collection is not subscribed locally if (!fetch->collections().isEmpty()) { delete m_idle; m_idle = new ImapIdleManager(createResourceState(TaskArguments(fetch->collections().at(0))), m_pool, this); } else { qCWarning(IMAPRESOURCE_LOG) << "Failed to retrieve IDLE collection: no such collection"; } } // ---------------------------------------------------------------------------------- void ImapResourceBase::requestManualExpunge(qint64 collectionId) { if (!settings()->automaticExpungeEnabled()) { Collection collection(collectionId); Akonadi::CollectionFetchScope scope; scope.setResource(identifier()); scope.setAncestorRetrieval(Akonadi::CollectionFetchScope::All); scope.setListFilter(CollectionFetchScope::NoFilter); Akonadi::CollectionFetchJob *fetch = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Base, this); fetch->setFetchScope(scope); connect(fetch, &KJob::result, this, &ImapResourceBase::onExpungeCollectionFetchDone); } } void ImapResourceBase::onExpungeCollectionFetchDone(KJob *job) { if (job->error() == 0) { Akonadi::CollectionFetchJob *fetch = static_cast(job); Akonadi::Collection collection = fetch->collections().at(0); scheduleCustomTask(this, "triggerCollectionExpunge", QVariant::fromValue(collection)); } else { qCWarning(IMAPRESOURCE_LOG) << "CollectionFetch for expunge failed." << "error=" << job->error() << ", errorString=" << job->errorString(); } } void ImapResourceBase::triggerCollectionExpunge(const QVariant &collectionVariant) { const Collection collection = collectionVariant.value(); ExpungeCollectionTask *task = new ExpungeCollectionTask(createResourceState(TaskArguments(collection)), this); task->start(m_pool); queueTask(task); } // ---------------------------------------------------------------------------------- void ImapResourceBase::abortActivity() { if (!m_taskList.isEmpty()) { m_pool->disconnect(SessionPool::CloseSession); scheduleConnectionAttempt(); } } void ImapResourceBase::queueTask(ResourceTask *task) { connect(task, &QObject::destroyed, this, &ImapResourceBase::taskDestroyed); m_taskList << task; } void ImapResourceBase::startTask(ResourceTask *task) { task->start(m_pool); queueTask(task); } void ImapResourceBase::taskDestroyed(QObject *task) { m_taskList.removeAll(static_cast(task)); } QStringList ImapResourceBase::serverCapabilities() const { return m_pool->serverCapabilities(); } void ImapResourceBase::cleanup() { settings()->cleanup(); ResourceBase::cleanup(); } QString ImapResourceBase::dumpResourceToString() const { QString ret; for (ResourceTask *task : qAsConst(m_taskList)) { if (!ret.isEmpty()) { ret += QLatin1String(", "); } ret += QLatin1String(task->metaObject()->className()); } return QLatin1String("IMAP tasks: ") + ret; } void ImapResourceBase::showError(const QString &message) { Q_EMIT status(Akonadi::AgentBase::Idle, message); m_statusMessageTimer->start(1000 * 10); } void ImapResourceBase::clearStatusMessage() { Q_EMIT status(Akonadi::AgentBase::Idle, QString()); } void ImapResourceBase::modifyCollection(const Collection &col) { Akonadi::CollectionModifyJob *modJob = new Akonadi::CollectionModifyJob(col, this); connect(modJob, &KJob::result, this, &ImapResourceBase::onCollectionModifyDone); } void ImapResourceBase::onCollectionModifyDone(KJob *job) { if (job->error()) { qCWarning(IMAPRESOURCE_LOG) << "Failed to modify collection: " << job->errorString(); } } diff --git a/resources/imap/settingspasswordrequester.cpp b/resources/imap/settingspasswordrequester.cpp index 00d311a38..e0cffc422 100644 --- a/resources/imap/settingspasswordrequester.cpp +++ b/resources/imap/settingspasswordrequester.cpp @@ -1,182 +1,183 @@ /* Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens 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 "settingspasswordrequester.h" #include #include #include #include #include #include "imapresource_debug.h" #include #include #include #include "imapresourcebase.h" #include "settings.h" SettingsPasswordRequester::SettingsPasswordRequester(ImapResourceBase *resource, QObject *parent) : PasswordRequesterInterface(parent) , m_resource(resource) { } SettingsPasswordRequester::~SettingsPasswordRequester() { cancelPasswordRequests(); } void SettingsPasswordRequester::requestPassword(RequestType request, const QString &serverError) { if (request == WrongPasswordRequest) { QMetaObject::invokeMethod(this, "askUserInput", Qt::QueuedConnection, Q_ARG(QString, serverError)); } else { connect(m_resource->settings(), &Settings::passwordRequestCompleted, this, &SettingsPasswordRequester::onPasswordRequestCompleted); m_resource->settings()->requestPassword(); } } void SettingsPasswordRequester::askUserInput(const QString &serverError) { // the credentials were not ok, allow to retry or change password if (m_requestDialog) { qCDebug(IMAPRESOURCE_LOG) << "Password request dialog is already open"; return; } QWidget *parent = QWidget::find(m_resource->winIdForDialogs()); QString text = i18n("The server for account \"%2\" refused the supplied username and password. " "Do you want to go to the settings, have another attempt " "at logging in, or do nothing?\n\n" "%1", serverError, m_resource->name()); QDialog *dialog = new QDialog(parent, Qt::Dialog); dialog->setWindowTitle(i18n("Could Not Authenticate")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::No | QDialogButtonBox::Yes, dialog); buttonBox->button(QDialogButtonBox::Yes)->setDefault(true); buttonBox->button(QDialogButtonBox::Yes)->setText(i18n("Account Settings")); buttonBox->button(QDialogButtonBox::No)->setText(i18nc("Input username/password manually and not store them", "Try Again")); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(buttonBox->button(QDialogButtonBox::Yes), &QPushButton::clicked, this, &SettingsPasswordRequester::slotYesClicked); connect(buttonBox->button(QDialogButtonBox::No), &QPushButton::clicked, this, &SettingsPasswordRequester::slotNoClicked); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &SettingsPasswordRequester::slotCancelClicked); connect(dialog, &QDialog::destroyed, this, &SettingsPasswordRequester::onDialogDestroyed); m_requestDialog = dialog; + dialog->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(dialog->windowHandle(), m_resource->winIdForDialogs()); bool checkboxResult = false; KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Information, text, QStringList(), QString(), &checkboxResult, KMessageBox::NoExec); dialog->show(); } void SettingsPasswordRequester::onDialogDestroyed() { m_requestDialog = nullptr; } void SettingsPasswordRequester::slotNoClicked() { connect(m_resource->settings(), &Settings::passwordRequestCompleted, this, &SettingsPasswordRequester::onPasswordRequestCompleted); requestManualAuth(nullptr); m_requestDialog = nullptr; } void SettingsPasswordRequester::slotYesClicked() { if (!m_settingsDialog) { QDialog *dialog = m_resource->createConfigureDialog(m_resource->winIdForDialogs()); connect(dialog, &QDialog::finished, this, &SettingsPasswordRequester::onSettingsDialogFinished); m_settingsDialog = dialog; dialog->show(); } m_requestDialog = nullptr; } void SettingsPasswordRequester::slotCancelClicked() { Q_EMIT done(UserRejected); m_requestDialog = nullptr; } void SettingsPasswordRequester::onSettingsDialogFinished(int result) { m_settingsDialog = nullptr; if (result == QDialog::Accepted) { Q_EMIT done(ReconnectNeeded); } else { Q_EMIT done(UserRejected); } } void SettingsPasswordRequester::cancelPasswordRequests() { if (m_requestDialog) { if (m_requestDialog->close()) { m_requestDialog = nullptr; } } } void SettingsPasswordRequester::onPasswordRequestCompleted(const QString &password, bool userRejected) { disconnect(m_resource->settings(), &Settings::passwordRequestCompleted, this, &SettingsPasswordRequester::onPasswordRequestCompleted); QString pwd = password; if (userRejected || pwd.isEmpty()) { pwd = requestManualAuth(&userRejected); } if (userRejected) { Q_EMIT done(UserRejected); } else if (password.isEmpty() && (m_resource->settings()->authentication() != MailTransport::Transport::EnumAuthenticationType::GSSAPI)) { Q_EMIT done(EmptyPasswordEntered); } else { Q_EMIT done(PasswordRetrieved, password); } } QString SettingsPasswordRequester::requestManualAuth(bool *userRejected) { QScopedPointer dlg(new KPasswordDialog(nullptr)); dlg->setModal(true); dlg->setPrompt(i18n("Please enter password for user '%1' on IMAP server '%2'.", m_resource->settings()->userName(), m_resource->settings()->imapServer())); dlg->setPassword(m_resource->settings()->password()); if (dlg->exec()) { if (userRejected) { *userRejected = false; } m_resource->settings()->setPassword(dlg->password()); return dlg->password(); } else { if (userRejected) { *userRejected = true; } return QString(); } } diff --git a/resources/kalarm/kalarmdir/settingsdialog.cpp b/resources/kalarm/kalarmdir/settingsdialog.cpp index 61b7c694d..da93ff9c1 100644 --- a/resources/kalarm/kalarmdir/settingsdialog.cpp +++ b/resources/kalarm/kalarmdir/settingsdialog.cpp @@ -1,138 +1,139 @@ /* * settingsdialog.cpp - Akonadi KAlarm directory resource configuration dialog * Program: kalarm * Copyright © 2011 by David Jarvie * * 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 "settingsdialog.h" #include "settings.h" #include "alarmtypewidget.h" #include #include #include #include #include #include #include namespace Akonadi_KAlarm_Dir_Resource { SettingsDialog::SettingsDialog(WId windowId, Settings *settings) : QDialog() , mSettings(settings) , mReadOnlySelected(false) { QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); ui.setupUi(mainWidget); mTypeSelector = new AlarmTypeWidget(ui.tab, ui.tabLayout); ui.kcfg_Path->setMode(KFile::LocalOnly | KFile::Directory); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setDefault(true); mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &SettingsDialog::reject); mainLayout->addWidget(buttonBox); setWindowTitle(i18nc("@title", "Configure Calendar")); if (windowId) { + setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(windowHandle(), windowId); } // Make directory path read-only if the resource already exists QUrl path = QUrl::fromLocalFile(mSettings->path()); ui.kcfg_Path->setUrl(path); if (!path.isEmpty()) { ui.kcfg_Path->setEnabled(false); } mTypeSelector->setAlarmTypes(CalEvent::types(mSettings->alarmTypes())); mManager = new KConfigDialogManager(this, mSettings); mManager->updateWidgets(); connect(mOkButton, &QPushButton::clicked, this, &SettingsDialog::save); connect(ui.kcfg_Path, &KUrlRequester::textChanged, this, &SettingsDialog::textChanged); connect(ui.kcfg_ReadOnly, &QCheckBox::clicked, this, &SettingsDialog::readOnlyClicked); connect(mTypeSelector, &AlarmTypeWidget::changed, this, &SettingsDialog::validate); QTimer::singleShot(0, this, &SettingsDialog::validate); } void SettingsDialog::save() { mManager->updateSettings(); mSettings->setPath(ui.kcfg_Path->url().toLocalFile()); mSettings->setAlarmTypes(CalEvent::mimeTypes(mTypeSelector->alarmTypes())); mSettings->save(); } void SettingsDialog::readOnlyClicked(bool set) { mReadOnlySelected = set; } void SettingsDialog::textChanged() { bool oldReadOnly = ui.kcfg_ReadOnly->isEnabled(); validate(); if (ui.kcfg_ReadOnly->isEnabled() && !oldReadOnly) { // If read-only was only set earlier by validating the input path, // and the path is now ok to be read-write, clear the read-only status. ui.kcfg_ReadOnly->setChecked(mReadOnlySelected); } } void SettingsDialog::validate() { bool enableOk = false; // At least one alarm type must be selected if (mTypeSelector->alarmTypes() != CalEvent::EMPTY) { // The entered URL must be valid and local const QUrl currentUrl = ui.kcfg_Path->url(); if (currentUrl.isEmpty()) { ui.kcfg_ReadOnly->setEnabled(true); } else if (currentUrl.isLocalFile()) { QFileInfo file(currentUrl.toLocalFile()); // It must either be an existing directory, or in a writable // directory, and it must not be an existing file. if (file.exists()) { if (file.isDir()) { enableOk = true; // it's an existing directory } } else { // Specified directory doesn't already exist. // Find the first level of parent directory which exists, // and check that it is writable. for (;;) { file.setFile(file.dir().absolutePath()); // get parent dir's file info if (file.exists()) { if (file.isDir() && file.isWritable()) { enableOk = true; // it's possible to create the directory } break; } } } } } mOkButton->setEnabled(enableOk); } } diff --git a/resources/kolab/kolabresource.cpp b/resources/kolab/kolabresource.cpp index 9a2e96a8d..e89dade7f 100644 --- a/resources/kolab/kolabresource.cpp +++ b/resources/kolab/kolabresource.cpp @@ -1,279 +1,280 @@ /* 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 "kolabresource_trace.h" #include "setupserver.h" #include "sessionpool.h" #include "sessionuiproxy.h" #include "settingspasswordrequester.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" KolabResource::KolabResource(const QString &id) : ImapResourceBase(id) { m_pool->setPasswordRequester(new SettingsPasswordRequester(this, m_pool)); m_pool->setSessionUiProxy(SessionUiProxy::Ptr(new SessionUiProxy)); m_pool->setClientId(clientId()); 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) { m_settings = new KolabSettings; } return m_settings; } void KolabResource::delayedInit() { ImapResourceBase::delayedInit(); settings()->setRetrieveMetadataOnFolderListing(false); Q_ASSERT(!settings()->retrieveMetadataOnFolderListing()); } QString KolabResource::defaultName() const { return i18n("Kolab Resource"); } QByteArray KolabResource::clientId() const { return QByteArrayLiteral("Kontact Kolab Resource 5/KOLAB"); } QDialog *KolabResource::createConfigureDialog(WId windowId) { SetupServer *dlg = new SetupServer(this, windowId); + dlg->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(dlg->windowHandle(), windowId); dlg->setWindowTitle(i18n("Kolab Account Settings")); dlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("kolab"))); connect(dlg, &QDialog::finished, this, &KolabResource::onConfigurationDone); return dlg; } void KolabResource::onConfigurationDone(int result) { SetupServer *dlg = qobject_cast(sender()); if (result) { if (dlg->shouldClearCache()) { clearCache(); } settings()->save(); } dlg->deleteLater(); } ResourceStateInterface::Ptr KolabResource::createResourceState(const TaskArguments &args) { return ResourceStateInterface::Ptr(new KolabResourceState(this, args)); } void KolabResource::retrieveCollections() { qCDebug(KOLABRESOURCE_TRACE); Q_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) { qCDebug(KOLABRESOURCE_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; } ImapResourceBase::itemAdded(imapItem, collection); } void KolabResource::itemChanged(const Akonadi::Item &item, const QSet< QByteArray > &parts) { qCDebug(KOLABRESOURCE_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; } ImapResourceBase::itemChanged(imapItem, parts); } void KolabResource::itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination) { qCDebug(KOLABRESOURCE_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; } ImapResourceBase::itemsMoved(imapItems, source, destination); } static Akonadi::Collection updateAnnotations(const Akonadi::Collection &collection) { qCDebug(KOLABRESOURCE_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()) { 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) { qCDebug(KOLABRESOURCE_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? ImapResourceBase::collectionAdded(col, parent); } void KolabResource::collectionChanged(const Akonadi::Collection &collection, const QSet< QByteArray > &parts) { qCDebug(KOLABRESOURCE_TRACE) << collection.id() << parts; QSet p = parts; //Update annotations if necessary //FIXME col ????? 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 Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating folder '%1'", collection.name())); ChangeCollectionTask *task = new ChangeCollectionTask(createResourceState(TaskArguments(collection, p)), this); task->syncEnabledState(true); startTask(task); } void KolabResource::tagAdded(const Akonadi::Tag &tag) { qCDebug(KOLABRESOURCE_TRACE) << tag.id(); KolabAddTagTask *task = new KolabAddTagTask(createResourceState(TaskArguments(tag)), this); startTask(task); } void KolabResource::tagChanged(const Akonadi::Tag &tag) { qCDebug(KOLABRESOURCE_TRACE) << tag.id(); KolabChangeTagTask *task = new KolabChangeTagTask(createResourceState(TaskArguments(tag)), QSharedPointer(new TagConverter), this); startTask(task); } void KolabResource::tagRemoved(const Akonadi::Tag &tag) { qCDebug(KOLABRESOURCE_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) { qCDebug(KOLABRESOURCE_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() { qCDebug(KOLABRESOURCE_TRACE); KolabRetrieveTagTask *task = new KolabRetrieveTagTask(createResourceState(TaskArguments()), KolabRetrieveTagTask::RetrieveTags, this); startTask(task); } void KolabResource::retrieveRelations() { qCDebug(KOLABRESOURCE_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) { qCDebug(KOLABRESOURCE_TRACE) << items.size() << addedRelations.size() << removedRelations.size(); KolabChangeItemsRelationsTask *task = new KolabChangeItemsRelationsTask(createResourceState(TaskArguments(items, addedRelations, removedRelations))); startTask(task); } AKONADI_RESOURCE_MAIN(KolabResource)