File indexing completed on 2024-11-10 04:40:07

0001 /*
0002     SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org>
0003     SPDX-FileCopyrightText: 2009 Volker Krause <vkrause@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "knutresource.h"
0009 #include "knutresource_debug.h"
0010 #include "settingsadaptor.h"
0011 #include "xmlreader.h"
0012 #include "xmlwriter.h"
0013 
0014 #include "agentfactory.h"
0015 #include "changerecorder.h"
0016 #include "itemfetchscope.h"
0017 #include "tagcreatejob.h"
0018 #include <QDBusConnection>
0019 
0020 #include <KLocalizedString>
0021 #include <QFileDialog>
0022 
0023 #include <QDir>
0024 #include <QFile>
0025 #include <QFileSystemWatcher>
0026 #include <QStandardPaths>
0027 #include <QUuid>
0028 
0029 using namespace Akonadi;
0030 
0031 KnutResource::KnutResource(const QString &id)
0032     : ResourceBase(id)
0033     , mWatcher(new QFileSystemWatcher(this))
0034     , mSettings(new KnutSettings())
0035 {
0036     changeRecorder()->itemFetchScope().fetchFullPayload();
0037     changeRecorder()->fetchCollection(true);
0038 
0039     new SettingsAdaptor(mSettings);
0040     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), mSettings, QDBusConnection::ExportAdaptors);
0041     connect(this, &KnutResource::reloadConfiguration, this, &KnutResource::load);
0042     connect(mWatcher, &QFileSystemWatcher::fileChanged, this, &KnutResource::load);
0043     load();
0044 }
0045 
0046 KnutResource::~KnutResource()
0047 {
0048     delete mSettings;
0049 }
0050 
0051 void KnutResource::load()
0052 {
0053     if (!mWatcher->files().isEmpty()) {
0054         mWatcher->removePaths(mWatcher->files());
0055     }
0056 
0057     // file loading
0058     QString fileName = mSettings->dataFile();
0059     if (fileName.isEmpty()) {
0060         Q_EMIT status(Broken, i18n("No data file selected."));
0061         return;
0062     }
0063 
0064     if (!QFile::exists(fileName)) {
0065         fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf6/akonadi_knut_resource/knut-template.xml"));
0066     }
0067 
0068     if (!mDocument.loadFile(fileName)) {
0069         Q_EMIT status(Broken, mDocument.lastError());
0070         return;
0071     }
0072 
0073     if (mSettings->fileWatchingEnabled()) {
0074         mWatcher->addPath(fileName);
0075     }
0076 
0077     Q_EMIT status(Idle, i18n("File '%1' loaded successfully.", fileName));
0078     synchronize();
0079 }
0080 
0081 void KnutResource::save()
0082 {
0083     if (mSettings->readOnly()) {
0084         return;
0085     }
0086     const QString fileName = mSettings->dataFile();
0087     if (!mDocument.writeToFile(fileName)) {
0088         Q_EMIT error(mDocument.lastError());
0089         return;
0090     }
0091 }
0092 
0093 void KnutResource::configure(WId windowId)
0094 {
0095     QString oldFile = mSettings->dataFile();
0096     if (oldFile.isEmpty()) {
0097         oldFile = QDir::homePath();
0098     }
0099 
0100     // TODO: Use windowId
0101     Q_UNUSED(windowId)
0102     const QString newFile =
0103         QFileDialog::getSaveFileName(nullptr,
0104                                      i18n("Select Data File"),
0105                                      QString(),
0106                                      QStringLiteral("*.xml |") + i18nc("Filedialog filter for Akonadi data file", "Akonadi Knut Data File"));
0107 
0108     if (newFile.isEmpty() || oldFile == newFile) {
0109         return;
0110     }
0111 
0112     mSettings->setDataFile(newFile);
0113     mSettings->save();
0114     load();
0115 
0116     Q_EMIT configurationDialogAccepted();
0117 }
0118 
0119 void KnutResource::retrieveCollections()
0120 {
0121     const Collection::List collections = mDocument.collections();
0122     collectionsRetrieved(collections);
0123     const Tag::List tags = mDocument.tags();
0124     for (const Tag &tag : tags) {
0125         auto createjob = new TagCreateJob(tag);
0126         createjob->setMergeIfExisting(true);
0127     }
0128 }
0129 
0130 void KnutResource::retrieveItems(const Akonadi::Collection &collection)
0131 {
0132     Item::List items = mDocument.items(collection, false);
0133     if (!mDocument.lastError().isEmpty()) {
0134         cancelTask(mDocument.lastError());
0135         return;
0136     }
0137 
0138     itemsRetrieved(items);
0139 }
0140 
0141 #ifdef DO_IT_THE_OLD_WAY
0142 bool KnutResource::retrieveItem(const Item &item, const QSet<QByteArray> &parts)
0143 {
0144     Q_UNUSED(parts)
0145 
0146     const QDomElement itemElem = mDocument.itemElementByRemoteId(item.remoteId());
0147     if (itemElem.isNull()) {
0148         cancelTask(i18n("No item found for remoteid %1", item.remoteId()));
0149         return false;
0150     }
0151 
0152     Item i = XmlReader::elementToItem(itemElem, true);
0153     i.setId(item.id());
0154     itemRetrieved(i);
0155     return true;
0156 }
0157 #endif
0158 
0159 bool KnutResource::retrieveItems(const Item::List &items, const QSet<QByteArray> &parts)
0160 {
0161     Q_UNUSED(parts)
0162 
0163     Item::List results;
0164     results.reserve(items.size());
0165     for (const auto &item : items) {
0166         const QDomElement itemElem = mDocument.itemElementByRemoteId(item.remoteId());
0167         if (itemElem.isNull()) {
0168             cancelTask(i18n("No item found for remoteid %1", item.remoteId()));
0169             return false;
0170         }
0171 
0172         Item i = XmlReader::elementToItem(itemElem, true);
0173         i.setParentCollection(item.parentCollection());
0174         i.setId(item.id());
0175         results.push_back(i);
0176     }
0177 
0178     itemsRetrieved(results);
0179     return true;
0180 }
0181 
0182 void KnutResource::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent)
0183 {
0184     QDomElement parentElem = mDocument.collectionElementByRemoteId(parent.remoteId());
0185     if (parentElem.isNull()) {
0186         Q_EMIT error(i18n("Parent collection not found in DOM tree."));
0187         changeProcessed();
0188         return;
0189     }
0190 
0191     Collection c(collection);
0192     c.setRemoteId(QUuid::createUuid().toString());
0193     if (XmlWriter::writeCollection(c, parentElem).isNull()) {
0194         Q_EMIT error(i18n("Unable to write collection."));
0195         changeProcessed();
0196     } else {
0197         save();
0198         changeCommitted(c);
0199     }
0200 }
0201 
0202 void KnutResource::collectionChanged(const Akonadi::Collection &collection)
0203 {
0204     QDomElement oldElem = mDocument.collectionElementByRemoteId(collection.remoteId());
0205     if (oldElem.isNull()) {
0206         Q_EMIT error(i18n("Modified collection not found in DOM tree."));
0207         changeProcessed();
0208         return;
0209     }
0210 
0211     QDomElement newElem;
0212     newElem = XmlWriter::collectionToElement(collection, mDocument.document());
0213     // move all items/collections over to the new node
0214     const QDomNodeList children = oldElem.childNodes();
0215     const int numberOfChildren = children.count();
0216     for (int i = 0; i < numberOfChildren; ++i) {
0217         const QDomElement child = children.at(i).toElement();
0218         qCDebug(KNUTRESOURCE_LOG) << "reparenting " << child.tagName() << child.attribute(QStringLiteral("rid"));
0219         if (child.isNull()) {
0220             continue;
0221         }
0222         if (child.tagName() == QLatin1StringView("item") || child.tagName() == QStringLiteral("collection")) {
0223             newElem.appendChild(child); // reparents
0224             --i; // children, despite being const is modified by the reparenting
0225         }
0226     }
0227     oldElem.parentNode().replaceChild(newElem, oldElem);
0228     save();
0229     changeCommitted(collection);
0230 }
0231 
0232 void KnutResource::collectionRemoved(const Akonadi::Collection &collection)
0233 {
0234     const QDomElement colElem = mDocument.collectionElementByRemoteId(collection.remoteId());
0235     if (colElem.isNull()) {
0236         Q_EMIT error(i18n("Deleted collection not found in DOM tree."));
0237         changeProcessed();
0238         return;
0239     }
0240 
0241     colElem.parentNode().removeChild(colElem);
0242     save();
0243     changeProcessed();
0244 }
0245 
0246 void KnutResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection)
0247 {
0248     QDomElement parentElem = mDocument.collectionElementByRemoteId(collection.remoteId());
0249     if (parentElem.isNull()) {
0250         Q_EMIT error(i18n("Parent collection '%1' not found in DOM tree.", collection.remoteId()));
0251         changeProcessed();
0252         return;
0253     }
0254 
0255     Item i(item);
0256     i.setRemoteId(QUuid::createUuid().toString());
0257     if (XmlWriter::writeItem(i, parentElem).isNull()) {
0258         Q_EMIT error(i18n("Unable to write item."));
0259         changeProcessed();
0260     } else {
0261         save();
0262         changeCommitted(i);
0263     }
0264 }
0265 
0266 void KnutResource::itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &parts)
0267 {
0268     Q_UNUSED(parts)
0269 
0270     const QDomElement oldElem = mDocument.itemElementByRemoteId(item.remoteId());
0271     if (oldElem.isNull()) {
0272         Q_EMIT error(i18n("Modified item not found in DOM tree."));
0273         changeProcessed();
0274         return;
0275     }
0276 
0277     const QDomElement newElem = XmlWriter::itemToElement(item, mDocument.document());
0278     oldElem.parentNode().replaceChild(newElem, oldElem);
0279     save();
0280     changeCommitted(item);
0281 }
0282 
0283 void KnutResource::itemRemoved(const Akonadi::Item &item)
0284 {
0285     const QDomElement itemElem = mDocument.itemElementByRemoteId(item.remoteId());
0286     if (itemElem.isNull()) {
0287         Q_EMIT error(i18n("Deleted item not found in DOM tree."));
0288         changeProcessed();
0289         return;
0290     }
0291 
0292     itemElem.parentNode().removeChild(itemElem);
0293     save();
0294     changeProcessed();
0295 }
0296 
0297 void KnutResource::itemMoved(const Item &item, const Collection &collectionSource, const Collection &collectionDestination)
0298 {
0299     const QDomElement oldElem = mDocument.itemElementByRemoteId(item.remoteId());
0300     if (oldElem.isNull()) {
0301         qCWarning(KNUTRESOURCE_LOG) << "Moved item not found in DOM tree";
0302         changeProcessed();
0303         return;
0304     }
0305 
0306     QDomElement sourceParentElem = mDocument.collectionElementByRemoteId(collectionSource.remoteId());
0307     if (sourceParentElem.isNull()) {
0308         Q_EMIT error(i18n("Parent collection '%1' not found in DOM tree.", collectionSource.remoteId()));
0309         changeProcessed();
0310         return;
0311     }
0312 
0313     QDomElement destParentElem = mDocument.collectionElementByRemoteId(collectionDestination.remoteId());
0314     if (destParentElem.isNull()) {
0315         Q_EMIT error(i18n("Parent collection '%1' not found in DOM tree.", collectionDestination.remoteId()));
0316         changeProcessed();
0317         return;
0318     }
0319     QDomElement itemElem = mDocument.itemElementByRemoteId(item.remoteId());
0320     if (itemElem.isNull()) {
0321         Q_EMIT error(i18n("No item found for remoteid %1", item.remoteId()));
0322     }
0323 
0324     sourceParentElem.removeChild(itemElem);
0325     destParentElem.appendChild(itemElem);
0326 
0327     if (XmlWriter::writeItem(item, destParentElem).isNull()) {
0328         Q_EMIT error(i18n("Unable to write item."));
0329     } else {
0330         save();
0331     }
0332     changeProcessed();
0333 }
0334 
0335 QSet<qint64> KnutResource::parseQuery(const QString &queryString)
0336 {
0337     QSet<qint64> resultSet;
0338     Akonadi::SearchQuery query = Akonadi::SearchQuery::fromJSON(queryString.toLatin1());
0339     const QList<SearchTerm> subTerms = query.term().subTerms();
0340     for (const Akonadi::SearchTerm &term : subTerms) {
0341         if (term.key() == QLatin1StringView("resource")) {
0342             resultSet << term.value().toInt();
0343         }
0344     }
0345     return resultSet;
0346 }
0347 
0348 void KnutResource::search(const QString &query, const Collection &collection)
0349 {
0350     Q_UNUSED(collection)
0351     const QList<qint64> result = parseQuery(query).values().toVector();
0352     qCDebug(KNUTRESOURCE_LOG) << "KNUT QUERY:" << query;
0353     qCDebug(KNUTRESOURCE_LOG) << "KNUT RESOURCE:" << result;
0354     searchFinished(result, Akonadi::AgentSearchInterface::Uid);
0355 }
0356 
0357 void KnutResource::addSearch(const QString &query, const QString &queryLanguage, const Collection &resultCollection)
0358 {
0359     Q_UNUSED(query)
0360     Q_UNUSED(queryLanguage)
0361     Q_UNUSED(resultCollection)
0362     qCDebug(KNUTRESOURCE_LOG) << "addSearch: query=" << query << ", queryLanguage=" << queryLanguage << ", resultCollection=" << resultCollection.id();
0363 }
0364 
0365 void KnutResource::removeSearch(const Collection &resultCollection)
0366 {
0367     Q_UNUSED(resultCollection)
0368     qCDebug(KNUTRESOURCE_LOG) << "removeSearch:" << resultCollection.id();
0369 }
0370 
0371 AKONADI_RESOURCE_MAIN(KnutResource)
0372 
0373 #include "moc_knutresource.cpp"