File indexing completed on 2024-05-12 05:10:45

0001 /**
0002   This file is part of the akonadi-calendar library.
0003 
0004   SPDX-FileCopyrightText: 2013 Sérgio Martins <iamsergio@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "icalimporter.h"
0010 #include "icalimporter_p.h"
0011 #include "utils_p.h"
0012 
0013 #include <Akonadi/AgentInstanceCreateJob>
0014 #include <Akonadi/AgentManager>
0015 #include <Akonadi/ServerManager>
0016 
0017 #include <KCalendarCore/FileStorage>
0018 
0019 #include <KIO/Job>
0020 #include <KIO/StoredTransferJob>
0021 
0022 #include <QDBusInterface>
0023 #include <QFileInfo>
0024 #include <QTemporaryFile>
0025 #include <QTimeZone>
0026 
0027 using namespace KCalendarCore;
0028 using namespace Akonadi;
0029 
0030 ICalImporterPrivate::ICalImporterPrivate(IncidenceChanger *changer, ICalImporter *qq)
0031     : QObject()
0032     , q(qq)
0033     , m_changer(changer)
0034 {
0035     if (!changer) {
0036         m_changer = new IncidenceChanger(q);
0037     }
0038     connect(m_changer, &IncidenceChanger::createFinished, this, &ICalImporterPrivate::onIncidenceCreated);
0039 }
0040 
0041 ICalImporterPrivate::~ICalImporterPrivate()
0042 {
0043     delete m_temporaryFile;
0044 }
0045 
0046 void ICalImporterPrivate::onIncidenceCreated(int changeId,
0047                                              const Akonadi::Item &item,
0048                                              Akonadi::IncidenceChanger::ResultCode resultCode,
0049                                              const QString &errorString)
0050 {
0051     Q_UNUSED(item)
0052 
0053     if (!m_pendingRequests.contains(changeId)) {
0054         return; // Not ours
0055     }
0056 
0057     m_pendingRequests.removeAll(changeId);
0058 
0059     if (resultCode != IncidenceChanger::ResultCodeSuccess) {
0060         m_working = false;
0061         setErrorMessage(errorString);
0062         m_pendingRequests.clear();
0063         Q_EMIT q->importIntoExistingFinished(false, m_numIncidences);
0064     } else if (m_pendingRequests.isEmpty()) {
0065         m_working = false;
0066         Q_EMIT q->importIntoExistingFinished(true, m_numIncidences);
0067     }
0068 }
0069 
0070 void ICalImporterPrivate::setErrorMessage(const QString &message)
0071 {
0072     m_lastErrorMessage = message;
0073     qCritical() << message;
0074 }
0075 
0076 void ICalImporterPrivate::resourceCreated(KJob *job)
0077 {
0078     auto createjob = qobject_cast<Akonadi::AgentInstanceCreateJob *>(job);
0079 
0080     Q_ASSERT(createjob);
0081     m_working = false;
0082     if (createjob->error()) {
0083         setErrorMessage(i18n("Error creating ical resource: %1", createjob->errorString()));
0084         Q_EMIT q->importIntoNewFinished(false);
0085         return;
0086     }
0087 
0088     Akonadi::AgentInstance instance = createjob->instance();
0089     const QString service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, instance.identifier());
0090 
0091     QDBusInterface iface(service, QStringLiteral("/Settings"));
0092     if (!iface.isValid()) {
0093         setErrorMessage(i18n("Failed to obtain D-Bus interface for remote configuration."));
0094         Q_EMIT q->importIntoNewFinished(false);
0095         return;
0096     }
0097 
0098     const QString path = createjob->property("path").toString();
0099     Q_ASSERT(!path.isEmpty());
0100 
0101     iface.call(QStringLiteral("setPath"), path);
0102     iface.call(QStringLiteral("save"));
0103     instance.reconfigure();
0104 
0105     Q_EMIT q->importIntoNewFinished(true);
0106 }
0107 
0108 void ICalImporterPrivate::remoteDownloadFinished(KIO::Job *job, const QByteArray &data)
0109 {
0110     const bool success = job->error() == 0;
0111     m_working = false;
0112     if (success) {
0113         delete m_temporaryFile;
0114         m_temporaryFile = new QTemporaryFile();
0115         m_temporaryFile->write(data.constData(), data.size());
0116         q->importIntoExistingResource(QUrl(m_temporaryFile->fileName()), m_collection);
0117     } else {
0118         setErrorMessage(i18n("Could not download remote file."));
0119         Q_EMIT q->importIntoExistingFinished(false, 0);
0120     }
0121 }
0122 
0123 ICalImporter::ICalImporter(Akonadi::IncidenceChanger *changer, QObject *parent)
0124     : QObject(parent)
0125     , d(new ICalImporterPrivate(changer, this))
0126 {
0127 }
0128 
0129 ICalImporter::~ICalImporter() = default;
0130 
0131 QString ICalImporter::errorMessage() const
0132 {
0133     return d->m_lastErrorMessage;
0134 }
0135 
0136 bool ICalImporter::importIntoNewResource(const QString &filename)
0137 {
0138     d->m_lastErrorMessage.clear();
0139 
0140     if (d->m_working) {
0141         d->setErrorMessage(i18n("An import task is already in progress."));
0142         return false;
0143     }
0144 
0145     d->m_working = true;
0146 
0147     Akonadi::AgentType type = Akonadi::AgentManager::self()->type(QStringLiteral("akonadi_ical_resource"));
0148 
0149     auto job = new Akonadi::AgentInstanceCreateJob(type, this);
0150     job->setProperty("path", filename);
0151     connect(job, &KJob::result, d.get(), &ICalImporterPrivate::resourceCreated);
0152     job->start();
0153 
0154     return true;
0155 }
0156 
0157 bool ICalImporter::importIntoExistingResource(const QUrl &url, Collection collection)
0158 {
0159     d->m_lastErrorMessage.clear();
0160 
0161     if (d->m_working) {
0162         d->setErrorMessage(i18n("An import task is already in progress."));
0163         return false;
0164     }
0165 
0166     if (url.isEmpty()) {
0167         d->setErrorMessage(i18n("Empty filename. Will not import ical file."));
0168         return false;
0169     }
0170 
0171     if (!url.isValid()) {
0172         d->setErrorMessage(i18n("Url to import is malformed."));
0173         return false;
0174     }
0175 
0176     if (url.isLocalFile()) {
0177         QFileInfo f{url.path()};
0178         if (!f.exists() || !f.isFile() || !f.isReadable()) {
0179             d->setErrorMessage(i18n("The selected file is not a readable file."));
0180             return false;
0181         }
0182         MemoryCalendar::Ptr temporaryCalendar(new MemoryCalendar(QTimeZone::systemTimeZone()));
0183         FileStorage storage(temporaryCalendar);
0184         storage.setFileName(url.path());
0185         bool success = storage.load();
0186         if (!success) {
0187             d->setErrorMessage(i18n("The selected file is not properly formatted, or in a format not supported by this software."));
0188             return false;
0189         }
0190 
0191         d->m_pendingRequests.clear();
0192         const Incidence::List incidences = temporaryCalendar->incidences();
0193 
0194         if (incidences.isEmpty()) {
0195             d->setErrorMessage(i18n("The selected file is empty."));
0196             return false;
0197         }
0198 
0199         if (!collection.isValid()) {
0200             int dialogCode;
0201             const QStringList mimeTypes = QStringList()
0202                 << KCalendarCore::Event::eventMimeType() << KCalendarCore::Todo::todoMimeType() << KCalendarCore::Journal::journalMimeType();
0203             collection = CalendarUtils::selectCollection(nullptr, dialogCode /*by-ref*/, mimeTypes);
0204         }
0205 
0206         if (!collection.isValid()) {
0207             // user canceled
0208             d->setErrorMessage(QString());
0209             return false;
0210         }
0211 
0212         const IncidenceChanger::DestinationPolicy policySaved = d->m_changer->destinationPolicy();
0213         d->m_changer->startAtomicOperation(i18n("Merge ical file into existing calendar."));
0214         d->m_changer->setDestinationPolicy(IncidenceChanger::DestinationPolicyNeverAsk);
0215         for (const Incidence::Ptr &incidence : std::as_const(incidences)) {
0216             Q_ASSERT(incidence);
0217             if (!incidence) {
0218                 continue;
0219             }
0220             const int requestId = d->m_changer->createIncidence(incidence, collection);
0221             Q_ASSERT(requestId != -1); // -1 only happens with invalid incidences
0222             if (requestId != -1) {
0223                 d->m_pendingRequests << requestId;
0224             }
0225         }
0226         d->m_changer->endAtomicOperation();
0227 
0228         d->m_changer->setDestinationPolicy(policySaved); // restore
0229         d->m_numIncidences = incidences.count();
0230     } else {
0231         d->m_collection = collection;
0232         KIO::StoredTransferJob *job = KIO::storedGet(url);
0233         connect(job, qOverload<KIO::Job *, const QByteArray &>(&KIO::TransferJob::data), d.get(), [this](KIO::Job *job, const QByteArray &ba) {
0234             d->remoteDownloadFinished(job, ba);
0235         });
0236     }
0237 
0238     d->m_working = true;
0239     return true;
0240 }
0241 
0242 #include "moc_icalimporter_p.cpp"
0243 
0244 #include "moc_icalimporter.cpp"