File indexing completed on 2024-12-22 04:57:55

0001 /*
0002     SPDX-FileCopyrightText: 2016 Stefan Stäglich <sstaeglich@kdemail.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "tomboynotesresource.h"
0008 #include "debug.h"
0009 #include "settings.h"
0010 #include "settingsadaptor.h"
0011 #include "tomboycollectionsdownloadjob.h"
0012 #include "tomboyitemdownloadjob.h"
0013 #include "tomboyitemsdownloadjob.h"
0014 #include "tomboyitemuploadjob.h"
0015 #include "tomboyserverauthenticatejob.h"
0016 #include <Akonadi/ChangeRecorder>
0017 #include <Akonadi/ItemFetchScope>
0018 #include <KLocalizedString>
0019 #include <QDBusConnection>
0020 #include <QSslCipher>
0021 
0022 using namespace Akonadi;
0023 
0024 TomboyNotesResource::TomboyNotesResource(const QString &id)
0025     : ResourceBase(id)
0026 {
0027     Settings::instance(KSharedConfig::openConfig());
0028     new SettingsAdaptor(Settings::self());
0029     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), Settings::self(), QDBusConnection::ExportAdaptors);
0030 
0031     // Akonadi:Item should always provide the payload
0032     changeRecorder()->itemFetchScope().fetchFullPayload(true);
0033 
0034     // Status message stuff
0035     mStatusMessageTimer = new QTimer(this);
0036     mStatusMessageTimer->setSingleShot(true);
0037     connect(mStatusMessageTimer, &QTimer::timeout, [this]() {
0038         Q_EMIT status(Akonadi::AgentBase::Idle, QString());
0039     });
0040     connect(this, &AgentBase::error, this, &TomboyNotesResource::showError);
0041 
0042     mUploadJobProcessRunning = false;
0043 
0044     mManager = new QNetworkAccessManager(this);
0045     mManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
0046     mManager->setStrictTransportSecurityEnabled(true);
0047     mManager->enableStrictTransportSecurityStore(true);
0048     connect(mManager, &QNetworkAccessManager::sslErrors, this, &TomboyNotesResource::onSslError);
0049 
0050     qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Resource started";
0051     connect(this, &TomboyNotesResource::reloadConfiguration, this, &TomboyNotesResource::slotReloadConfig);
0052 }
0053 
0054 TomboyNotesResource::~TomboyNotesResource() = default;
0055 
0056 void TomboyNotesResource::retrieveCollections()
0057 {
0058     qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Retrieving collections started";
0059 
0060     if (configurationNotValid()) {
0061         cancelTask(i18n("Resource configuration is not valid"));
0062         return;
0063     }
0064 
0065     int refreshInterval = Settings::self()->refreshInterval();
0066     if (refreshInterval == 0) {
0067         refreshInterval = -1;
0068     }
0069 
0070     auto job = new TomboyCollectionsDownloadJob(Settings::collectionName(), mManager, refreshInterval, this);
0071     job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret());
0072     job->setServerURL(Settings::serverURL(), Settings::userURL());
0073     // connect to its result() signal
0074     connect(job, &KJob::result, this, &TomboyNotesResource::onCollectionsRetrieved);
0075     job->start();
0076     qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Retrieving collections job started";
0077 }
0078 
0079 void TomboyNotesResource::retrieveItems(const Akonadi::Collection &collection)
0080 {
0081     if (configurationNotValid()) {
0082         cancelTask(i18n("Resource configuration is not valid"));
0083         return;
0084     }
0085 
0086     // create the job
0087     auto job = new TomboyItemsDownloadJob(collection.id(), mManager, this);
0088     job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret());
0089     job->setServerURL(Settings::serverURL(), Settings::contentURL());
0090     // connect to its result() signal
0091     connect(job, &KJob::result, this, &TomboyNotesResource::onItemsRetrieved);
0092     job->start();
0093     qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Retrieving items job started";
0094 }
0095 
0096 bool TomboyNotesResource::retrieveItem(const Akonadi::Item &item, const QSet<QByteArray> &parts)
0097 {
0098     Q_UNUSED(parts)
0099 
0100     if (configurationNotValid()) {
0101         cancelTask(i18n("Resource configuration is not valid"));
0102         return false;
0103     }
0104 
0105     // this method is called when Akonadi wants more data for a given item.
0106     auto job = new TomboyItemDownloadJob(item, mManager, this);
0107     job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret());
0108     job->setServerURL(Settings::serverURL(), Settings::contentURL());
0109     // connect to its result() signal
0110     connect(job, &KJob::result, this, &TomboyNotesResource::onItemRetrieved);
0111     job->start();
0112     qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Retrieving item data job started";
0113 
0114     return true;
0115 }
0116 
0117 void TomboyNotesResource::onAuthorizationFinished(KJob *kjob)
0118 {
0119     // Saves the received client authentication data in the settings
0120     qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Authorization job finished";
0121     auto job = qobject_cast<TomboyServerAuthenticateJob *>(kjob);
0122     if (job->error() == TomboyJobError::NoError) {
0123         Settings::setRequestToken(job->getRequestToken());
0124         Settings::setRequestTokenSecret(job->getRequestTokenSecret());
0125         Settings::setContentURL(job->getContentUrl());
0126         Settings::setUserURL(job->getUserURL());
0127         Settings::self()->save();
0128         synchronizeCollectionTree();
0129         synchronize();
0130     } else {
0131         showError(job->errorText());
0132     }
0133 }
0134 
0135 void TomboyNotesResource::onCollectionsRetrieved(KJob *kjob)
0136 {
0137     auto job = qobject_cast<TomboyCollectionsDownloadJob *>(kjob);
0138     if (job->error() != TomboyJobError::NoError) {
0139         cancelTask();
0140         showError(job->errorText());
0141         return;
0142     }
0143 
0144     collectionsRetrieved(job->collections());
0145 }
0146 
0147 void TomboyNotesResource::onItemChangeCommitted(KJob *kjob)
0148 {
0149     auto job = qobject_cast<TomboyItemUploadJob *>(kjob);
0150     mUploadJobProcessRunning = false;
0151     switch (job->error()) {
0152     case TomboyJobError::PermanentError:
0153         cancelTask();
0154         showError(job->errorText());
0155         return;
0156     case TomboyJobError::TemporaryError:
0157         retryAfterFailure(job->errorString());
0158         return;
0159     case TomboyJobError::NoError:
0160         changeCommitted(job->item());
0161         // The data should be actualized for the next UploadJob
0162         synchronize();
0163         return;
0164     }
0165 }
0166 
0167 void TomboyNotesResource::onItemRetrieved(KJob *kjob)
0168 {
0169     auto job = qobject_cast<TomboyItemDownloadJob *>(kjob);
0170 
0171     if (job->error() != TomboyJobError::NoError) {
0172         cancelTask();
0173         showError(job->errorText());
0174         return;
0175     }
0176 
0177     itemRetrieved(job->item());
0178     qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Retrieving item data job with remoteId " << job->item().remoteId() << " finished";
0179 }
0180 
0181 void TomboyNotesResource::onItemsRetrieved(KJob *kjob)
0182 {
0183     auto job = qobject_cast<TomboyItemsDownloadJob *>(kjob);
0184     if (job->error() != TomboyJobError::NoError) {
0185         cancelTask();
0186         showError(job->errorText());
0187         return;
0188     }
0189 
0190     itemsRetrieved(job->items());
0191     qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Retrieving items job finished";
0192 }
0193 
0194 void TomboyNotesResource::onSslError(QNetworkReply *reply, const QList<QSslError> &errors)
0195 {
0196     Q_UNUSED(errors)
0197     if (Settings::ignoreSslErrors()) {
0198         reply->ignoreSslErrors();
0199     }
0200 }
0201 
0202 void TomboyNotesResource::aboutToQuit()
0203 {
0204     // TODO: any cleanup you need to do while there is still an active
0205     // event loop. The resource will terminate after this method returns
0206 }
0207 
0208 void TomboyNotesResource::slotReloadConfig()
0209 {
0210     setAgentName(Settings::collectionName());
0211 
0212     if (configurationNotValid()) {
0213         auto job = new TomboyServerAuthenticateJob(mManager, this);
0214         job->setServerURL(Settings::serverURL(), QString());
0215         connect(job, &KJob::result, this, &TomboyNotesResource::onAuthorizationFinished);
0216         job->start();
0217         qCDebug(TOMBOYNOTESRESOURCE_LOG) << "Authorization job started";
0218     } else {
0219         synchronize();
0220     }
0221 }
0222 
0223 void TomboyNotesResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection)
0224 {
0225     Q_UNUSED(collection)
0226     if (Settings::readOnly() || configurationNotValid()) {
0227         cancelTask(i18n("Resource is read-only"));
0228         return;
0229     }
0230 
0231     if (mUploadJobProcessRunning) {
0232         retryAfterFailure(QString());
0233         return;
0234     }
0235 
0236     auto job = new TomboyItemUploadJob(item, JobType::AddItem, mManager, this);
0237     job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret());
0238     job->setServerURL(Settings::serverURL(), Settings::contentURL());
0239     connect(job, &KJob::result, this, &TomboyNotesResource::onItemChangeCommitted);
0240     mUploadJobProcessRunning = true;
0241     job->start();
0242 }
0243 
0244 void TomboyNotesResource::itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &parts)
0245 {
0246     Q_UNUSED(parts)
0247     if (Settings::readOnly() || configurationNotValid()) {
0248         cancelTask(i18n("Resource is read-only"));
0249         return;
0250     }
0251 
0252     if (mUploadJobProcessRunning) {
0253         retryAfterFailure(QString());
0254         return;
0255     }
0256 
0257     auto job = new TomboyItemUploadJob(item, JobType::ModifyItem, mManager, this);
0258     job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret());
0259     job->setServerURL(Settings::serverURL(), Settings::contentURL());
0260     connect(job, &KJob::result, this, &TomboyNotesResource::onItemChangeCommitted);
0261     mUploadJobProcessRunning = true;
0262     job->start();
0263 }
0264 
0265 void TomboyNotesResource::itemRemoved(const Akonadi::Item &item)
0266 {
0267     if (Settings::readOnly() || configurationNotValid()) {
0268         cancelTask(i18n("Resource is read-only"));
0269         return;
0270     }
0271 
0272     if (mUploadJobProcessRunning) {
0273         retryAfterFailure(QString());
0274         return;
0275     }
0276 
0277     auto job = new TomboyItemUploadJob(item, JobType::DeleteItem, mManager, this);
0278     job->setAuthentication(Settings::requestToken(), Settings::requestTokenSecret());
0279     job->setServerURL(Settings::serverURL(), Settings::contentURL());
0280     connect(job, &KJob::result, this, &TomboyNotesResource::onItemChangeCommitted);
0281     mUploadJobProcessRunning = true;
0282     job->start();
0283 }
0284 
0285 bool TomboyNotesResource::configurationNotValid() const
0286 {
0287     return Settings::requestToken().isEmpty() || Settings::contentURL().isEmpty();
0288 }
0289 
0290 void TomboyNotesResource::retryAfterFailure(const QString &errorMessage)
0291 {
0292     Q_EMIT status(Broken, errorMessage);
0293     deferTask();
0294     setTemporaryOffline(Settings::self()->refreshInterval() <= 0 ? 300 : Settings::self()->refreshInterval() * 60);
0295 }
0296 
0297 void TomboyNotesResource::showError(const QString &errorText)
0298 {
0299     Q_EMIT status(Akonadi::AgentBase::Idle, errorText);
0300     mStatusMessageTimer->start(1000 * 10);
0301 }
0302 
0303 AKONADI_RESOURCE_MAIN(TomboyNotesResource)
0304 
0305 #include "moc_tomboynotesresource.cpp"