Akonadi Calendar

icalimporter.cpp
1/**
2 This file is part of the akonadi-calendar library.
3
4 SPDX-FileCopyrightText: 2013 Sérgio Martins <iamsergio@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "icalimporter.h"
10#include "icalimporter_p.h"
11#include "utils_p.h"
12
13#include <Akonadi/AgentInstanceCreateJob>
14#include <Akonadi/AgentManager>
15#include <Akonadi/ServerManager>
16
17#include <KCalendarCore/FileStorage>
18
19#include <KIO/Job>
20#include <KIO/StoredTransferJob>
21
22#include <QDBusInterface>
23#include <QFileInfo>
24#include <QTemporaryFile>
25#include <QTimeZone>
26
27using namespace KCalendarCore;
28using namespace Akonadi;
29
30ICalImporterPrivate::ICalImporterPrivate(IncidenceChanger *changer, ICalImporter *qq)
31 : QObject()
32 , q(qq)
33 , m_changer(changer)
34{
35 if (!changer) {
36 m_changer = new IncidenceChanger(q);
37 }
38 connect(m_changer, &IncidenceChanger::createFinished, this, &ICalImporterPrivate::onIncidenceCreated);
39}
40
41ICalImporterPrivate::~ICalImporterPrivate()
42{
43 delete m_temporaryFile;
44}
45
46void ICalImporterPrivate::onIncidenceCreated(int changeId,
47 const Akonadi::Item &item,
48 Akonadi::IncidenceChanger::ResultCode resultCode,
49 const QString &errorString)
50{
51 Q_UNUSED(item)
52
53 if (!m_pendingRequests.contains(changeId)) {
54 return; // Not ours
55 }
56
57 m_pendingRequests.removeAll(changeId);
58
59 if (resultCode != IncidenceChanger::ResultCodeSuccess) {
60 m_working = false;
61 setErrorMessage(errorString);
62 m_pendingRequests.clear();
63 Q_EMIT q->importIntoExistingFinished(false, m_numIncidences);
64 } else if (m_pendingRequests.isEmpty()) {
65 m_working = false;
66 Q_EMIT q->importIntoExistingFinished(true, m_numIncidences);
67 }
68}
69
70void ICalImporterPrivate::setErrorMessage(const QString &message)
71{
72 m_lastErrorMessage = message;
73 qCritical() << message;
74}
75
76void ICalImporterPrivate::resourceCreated(KJob *job)
77{
78 auto createjob = qobject_cast<Akonadi::AgentInstanceCreateJob *>(job);
79
80 Q_ASSERT(createjob);
81 m_working = false;
82 if (createjob->error()) {
83 setErrorMessage(i18n("Error creating ical resource: %1", createjob->errorString()));
84 Q_EMIT q->importIntoNewFinished(false);
85 return;
86 }
87
88 Akonadi::AgentInstance instance = createjob->instance();
89 const QString service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, instance.identifier());
90
91 QDBusInterface iface(service, QStringLiteral("/Settings"));
92 if (!iface.isValid()) {
93 setErrorMessage(i18n("Failed to obtain D-Bus interface for remote configuration."));
94 Q_EMIT q->importIntoNewFinished(false);
95 return;
96 }
97
98 const QString path = createjob->property("path").toString();
99 Q_ASSERT(!path.isEmpty());
100
101 iface.call(QStringLiteral("setPath"), path);
102 iface.call(QStringLiteral("save"));
103 instance.reconfigure();
104
105 Q_EMIT q->importIntoNewFinished(true);
106}
107
108void ICalImporterPrivate::remoteDownloadFinished(KIO::Job *job, const QByteArray &data)
109{
110 const bool success = job->error() == 0;
111 m_working = false;
112 if (success) {
113 delete m_temporaryFile;
114 m_temporaryFile = new QTemporaryFile();
115 m_temporaryFile->write(data.constData(), data.size());
116 q->importIntoExistingResource(QUrl(m_temporaryFile->fileName()), m_collection);
117 } else {
118 setErrorMessage(i18n("Could not download remote file."));
119 Q_EMIT q->importIntoExistingFinished(false, 0);
120 }
121}
122
123ICalImporter::ICalImporter(Akonadi::IncidenceChanger *changer, QObject *parent)
124 : QObject(parent)
125 , d(new ICalImporterPrivate(changer, this))
126{
127}
128
129ICalImporter::~ICalImporter() = default;
130
131QString ICalImporter::errorMessage() const
132{
133 return d->m_lastErrorMessage;
134}
135
136bool ICalImporter::importIntoNewResource(const QString &filename)
137{
138 d->m_lastErrorMessage.clear();
139
140 if (d->m_working) {
141 d->setErrorMessage(i18n("An import task is already in progress."));
142 return false;
143 }
144
145 d->m_working = true;
146
147 Akonadi::AgentType type = Akonadi::AgentManager::self()->type(QStringLiteral("akonadi_ical_resource"));
148
149 auto job = new Akonadi::AgentInstanceCreateJob(type, this);
150 job->setProperty("path", filename);
151 connect(job, &KJob::result, d.get(), &ICalImporterPrivate::resourceCreated);
152 job->start();
153
154 return true;
155}
156
157bool ICalImporter::importIntoExistingResource(const QUrl &url, Collection collection)
158{
159 d->m_lastErrorMessage.clear();
160
161 if (d->m_working) {
162 d->setErrorMessage(i18n("An import task is already in progress."));
163 return false;
164 }
165
166 if (url.isEmpty()) {
167 d->setErrorMessage(i18n("Empty filename. Will not import ical file."));
168 return false;
169 }
170
171 if (!url.isValid()) {
172 d->setErrorMessage(i18n("Url to import is malformed."));
173 return false;
174 }
175
176 if (url.isLocalFile()) {
177 QFileInfo f{url.path()};
178 if (!f.exists() || !f.isFile() || !f.isReadable()) {
179 d->setErrorMessage(i18n("The selected file is not a readable file."));
180 return false;
181 }
183 FileStorage storage(temporaryCalendar);
184 storage.setFileName(url.path());
185 bool success = storage.load();
186 if (!success) {
187 d->setErrorMessage(i18n("The selected file is not properly formatted, or in a format not supported by this software."));
188 return false;
189 }
190
191 d->m_pendingRequests.clear();
192 const Incidence::List incidences = temporaryCalendar->incidences();
193
194 if (incidences.isEmpty()) {
195 d->setErrorMessage(i18n("The selected file is empty."));
196 return false;
197 }
198
199 if (!collection.isValid()) {
200 int dialogCode;
201 const QStringList mimeTypes = QStringList()
203 collection = CalendarUtils::selectCollection(nullptr, dialogCode /*by-ref*/, mimeTypes);
204 }
205
206 if (!collection.isValid()) {
207 // user canceled
208 d->setErrorMessage(QString());
209 return false;
210 }
211
212 const IncidenceChanger::DestinationPolicy policySaved = d->m_changer->destinationPolicy();
213 d->m_changer->startAtomicOperation(i18n("Merge ical file into existing calendar."));
214 d->m_changer->setDestinationPolicy(IncidenceChanger::DestinationPolicyNeverAsk);
215 for (const Incidence::Ptr &incidence : std::as_const(incidences)) {
216 Q_ASSERT(incidence);
217 if (!incidence) {
218 continue;
219 }
220 const int requestId = d->m_changer->createIncidence(incidence, collection);
221 Q_ASSERT(requestId != -1); // -1 only happens with invalid incidences
222 if (requestId != -1) {
223 d->m_pendingRequests << requestId;
224 }
225 }
226 d->m_changer->endAtomicOperation();
227
228 d->m_changer->setDestinationPolicy(policySaved); // restore
229 d->m_numIncidences = incidences.count();
230 } else {
231 d->m_collection = collection;
233 connect(job, qOverload<KIO::Job *, const QByteArray &>(&KIO::TransferJob::data), d.get(), [this](KIO::Job *job, const QByteArray &ba) {
234 d->remoteDownloadFinished(job, ba);
235 });
236 }
237
238 d->m_working = true;
239 return true;
240}
241
242#include "moc_icalimporter_p.cpp"
243
244#include "moc_icalimporter.cpp"
QString identifier() const
void reconfigure() const
AgentType type(const QString &identifier) const
static AgentManager * self()
bool isValid() const
static QString agentServiceName(ServiceAgentType agentType, const QString &identifier)
static QLatin1String eventMimeType()
static QLatin1String journalMimeType()
static QLatin1String todoMimeType()
void data(KIO::Job *job, const QByteArray &data)
int error() const
void result(KJob *job)
virtual Q_SCRIPTABLE void start()=0
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
FreeBusyManager::Singleton.
KCRASH_EXPORT void setErrorMessage(const QString &message)
KIOCORE_EXPORT StoredTransferJob * storedGet(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
QString path(const QString &relativePath)
const char * constData() const const
qsizetype size() const const
qsizetype count() const const
bool isEmpty() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool setProperty(const char *name, QVariant &&value)
bool isEmpty() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTimeZone systemTimeZone()
bool isEmpty() const const
bool isLocalFile() const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:14:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.