CalendarSupport

eventarchiver.cpp
1/*
2 SPDX-FileCopyrightText: 2000, 2001 Cornelius Schumacher <schumacher@kde.org>
3 SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
5
6 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
7*/
8
9#include "eventarchiver.h"
10
11#include "kcalprefs.h"
12
13#include <Akonadi/CalendarUtils>
14#include <Akonadi/IncidenceChanger>
15
16#include <KCalendarCore/FileStorage>
17#include <KCalendarCore/ICalFormat>
18#include <KCalendarCore/MemoryCalendar>
19
20#include <KCalUtils/Stringify>
21
22#include "calendarsupport_debug.h"
23#include <KIO/FileCopyJob>
24#include <KIO/StatJob>
25#include <KJobWidgets>
26#include <KLocalizedString>
27#include <KMessageBox>
28
29#include <QLocale>
30#include <QTemporaryFile>
31#include <QTimeZone>
32
33using namespace KCalendarCore;
34using namespace KCalUtils;
35using namespace CalendarSupport;
36
37class GroupwareScoppedDisabler
38{
39public:
40 GroupwareScoppedDisabler(Akonadi::IncidenceChanger *changer)
41 : m_changer(changer)
42 {
43 m_wasEnabled = m_changer->groupwareCommunication();
44 m_changer->setGroupwareCommunication(false);
45 }
46
47 ~GroupwareScoppedDisabler()
48 {
49 m_changer->setGroupwareCommunication(m_wasEnabled);
50 }
51
52 bool m_wasEnabled = false;
53 Akonadi::IncidenceChanger *const m_changer;
54};
55
56EventArchiver::EventArchiver(QObject *parent)
57 : QObject(parent)
58{
59}
60
61EventArchiver::~EventArchiver() = default;
62
63void EventArchiver::runOnce(const Akonadi::ETMCalendar::Ptr &calendar, Akonadi::IncidenceChanger *changer, QDate limitDate, QWidget *widget)
64{
65 run(calendar, changer, limitDate, widget, true, true);
66}
67
68void EventArchiver::runAuto(const Akonadi::ETMCalendar::Ptr &calendar, Akonadi::IncidenceChanger *changer, QWidget *widget, bool withGUI)
69{
70 QDate limitDate(QDate::currentDate());
71 const int expiryTime = KCalPrefs::instance()->mExpiryTime;
72 switch (KCalPrefs::instance()->mExpiryUnit) {
73 case KCalPrefs::UnitDays: // Days
74 limitDate = limitDate.addDays(-expiryTime);
75 break;
76 case KCalPrefs::UnitWeeks: // Weeks
77 limitDate = limitDate.addDays(-expiryTime * 7);
78 break;
79 case KCalPrefs::UnitMonths: // Months
80 limitDate = limitDate.addMonths(-expiryTime);
81 break;
82 default:
83 return;
84 }
85 run(calendar, changer, limitDate, widget, withGUI, false);
86}
87
88void EventArchiver::run(const Akonadi::ETMCalendar::Ptr &calendar,
89 Akonadi::IncidenceChanger *changer,
90 QDate limitDate,
91 QWidget *widget,
92 bool withGUI,
93 bool errorIfNone)
94{
95 GroupwareScoppedDisabler disabler(changer); // Disables groupware communication temporarily
96
97 // We need to use rawEvents, otherwise events hidden by filters will not be archived.
101
102 if (KCalPrefs::instance()->mArchiveEvents) {
103 events = calendar->rawEvents(QDate(1769, 12, 1),
104 // #29555, also advertised by the "limitDate not included" in the class docu
105 limitDate.addDays(-1),
107 true);
108 }
109 if (KCalPrefs::instance()->mArchiveTodos) {
110 const KCalendarCore::Todo::List rawTodos = calendar->rawTodos();
111
112 for (const KCalendarCore::Todo::Ptr &todo : rawTodos) {
113 Q_ASSERT(todo);
114 if (isSubTreeComplete(calendar, todo, limitDate)) {
115 todos.append(todo);
116 }
117 }
118 }
119
120 const KCalendarCore::Incidence::List incidences = calendar->mergeIncidenceList(events, todos, journals);
121
122 qCDebug(CALENDARSUPPORT_LOG) << "archiving incidences before" << limitDate << " ->" << incidences.count() << " incidences found.";
123 if (incidences.isEmpty()) {
124 if (withGUI && errorIfNone) {
126 i18n("There are no items before %1", QLocale::system().toString(limitDate, QLocale::ShortFormat)),
127 i18nc("@title:window", "Archive"),
128 QStringLiteral("ArchiverNoIncidences"));
129 }
130 return;
131 }
132
133 switch (KCalPrefs::instance()->mArchiveAction) {
134 case KCalPrefs::actionDelete:
135 deleteIncidences(changer, limitDate, widget, calendar->itemList(incidences), withGUI);
136 break;
137 case KCalPrefs::actionArchive:
138 archiveIncidences(calendar, changer, limitDate, widget, incidences, withGUI);
139 break;
140 }
141}
142
143void EventArchiver::deleteIncidences(Akonadi::IncidenceChanger *changer, QDate limitDate, QWidget *widget, const Akonadi::Item::List &items, bool withGUI)
144{
145 QStringList incidenceStrs;
148 incidenceStrs.reserve(items.count());
149 for (it = items.constBegin(); it != end; ++it) {
150 incidenceStrs.append(Akonadi::CalendarUtils::incidence(*it)->summary());
151 }
152
153 if (withGUI) {
154 const int result = KMessageBox::warningContinueCancelList(widget,
155 i18n("Delete all items before %1 without saving?\n"
156 "The following items will be deleted:",
158 incidenceStrs,
159 i18nc("@title:window", "Delete Old Items"),
161 if (result != KMessageBox::Continue) {
162 return;
163 }
164 }
165
166 changer->deleteIncidences(items, /**parent=*/widget);
167
168 // TODO: Q_EMIT only after hearing back from incidence changer
169 Q_EMIT eventsDeleted();
170}
171
172void EventArchiver::archiveIncidences(const Akonadi::ETMCalendar::Ptr &calendar,
173 Akonadi::IncidenceChanger *changer,
174 QDate limitDate,
175 QWidget *widget,
176 const KCalendarCore::Incidence::List &incidences,
177 bool withGUI)
178{
179 Q_UNUSED(limitDate)
180 Q_UNUSED(withGUI)
181
182 FileStorage storage(calendar);
183
184 QString tmpFileName;
185 // KSaveFile cannot be called with an open File Handle on Windows.
186 // So we use QTemporaryFile only to generate a unique filename
187 // and then close/delete the file again. This file must be deleted
188 // here.
189 {
190 QTemporaryFile tmpFile;
191 tmpFile.open();
192 tmpFileName = tmpFile.fileName();
193 }
194 // Save current calendar to disk
195 storage.setFileName(tmpFileName);
196 if (!storage.save()) {
197 qCDebug(CALENDARSUPPORT_LOG) << "Can't save calendar to temp file";
198 return;
199 }
200
201 // Duplicate current calendar by loading in new calendar object
203
204 FileStorage archiveStore(archiveCalendar);
205 archiveStore.setFileName(tmpFileName);
206 auto format = new ICalFormat();
207 archiveStore.setSaveFormat(format);
208 if (!archiveStore.load()) {
209 qCDebug(CALENDARSUPPORT_LOG) << "Can't load calendar from temp file";
210 QFile::remove(tmpFileName);
211 return;
212 }
213
214 // Strip active events from calendar so that only events to be archived
215 // remain. This is not really efficient, but there is no other easy way.
216 QStringList uids;
217 Incidence::List allIncidences = archiveCalendar->rawIncidences();
218 uids.reserve(incidences.count());
219 for (const KCalendarCore::Incidence::Ptr &incidence : std::as_const(incidences)) {
220 uids.append(incidence->uid());
221 }
222 for (const KCalendarCore::Incidence::Ptr &incidence : std::as_const(allIncidences)) {
223 if (!uids.contains(incidence->uid())) {
224 archiveCalendar->deleteIncidence(incidence);
225 }
226 }
227
228 // Get or create the archive file
229 QUrl archiveURL(KCalPrefs::instance()->mArchiveFile);
230 QString archiveFile;
231 QTemporaryFile downloadTempFile;
232
233 bool fileExists = false;
234 if (archiveURL.isLocalFile()) {
235 fileExists = QFile::exists(archiveURL.toLocalFile());
236 } else {
237 auto job = KIO::stat(archiveURL, KIO::StatJob::SourceSide, KIO::StatBasic);
238
239 KJobWidgets::setWindow(job, widget);
240 fileExists = job->exec();
241 }
242
243 if (fileExists) {
244 archiveFile = downloadTempFile.fileName();
245 auto job = KIO::file_copy(archiveURL, QUrl::fromLocalFile(archiveFile));
246 KJobWidgets::setWindow(job, widget);
247 if (!job->exec()) {
248 qCDebug(CALENDARSUPPORT_LOG) << "Can't download archive file";
249 QFile::remove(tmpFileName);
250 return;
251 }
252 // Merge with events to be archived.
253 archiveStore.setFileName(archiveFile);
254 if (!archiveStore.load()) {
255 qCDebug(CALENDARSUPPORT_LOG) << "Can't merge with archive file";
256 QFile::remove(tmpFileName);
257 return;
258 }
259 } else {
260 archiveFile = tmpFileName;
261 }
262
263 // Save archive calendar
264 if (!archiveStore.save()) {
265 QString errmess;
266 if (format->exception()) {
267 errmess = Stringify::errorMessage(*format->exception());
268 } else {
269 errmess = i18nc("save failure cause unknown", "Reason unknown");
270 }
271 KMessageBox::error(widget, i18n("Cannot write archive file %1. %2", archiveStore.fileName(), errmess));
272 QFile::remove(tmpFileName);
273 return;
274 }
275
276 // Upload if necessary
277 QUrl srcUrl = QUrl::fromLocalFile(archiveFile);
278 if (srcUrl != archiveURL) {
279 auto job = KIO::file_copy(QUrl::fromLocalFile(archiveFile), archiveURL);
280 KJobWidgets::setWindow(job, widget);
281 if (!job->exec()) {
282 KMessageBox::error(widget, i18n("Cannot write archive. %1", job->errorString()));
283 QFile::remove(tmpFileName);
284 return;
285 }
286 }
287
288 QFile::remove(tmpFileName);
289
290 // We don't want it to ask to send invitations for each incidence.
291 changer->startAtomicOperation(i18n("Archiving events"));
292
293 // Delete archived events from calendar
294 const Akonadi::Item::List items = calendar->itemList(incidences);
295 for (const Akonadi::Item &item : items) {
296 changer->deleteIncidence(item, widget);
297 } // TODO: Q_EMIT only after hearing back from incidence changer
298 changer->endAtomicOperation();
299
300 Q_EMIT eventsDeleted();
301}
302
303bool EventArchiver::isSubTreeComplete(const Akonadi::ETMCalendar::Ptr &calendar, const Todo::Ptr &todo, QDate limitDate, QStringList checkedUids) const
304{
305 if (!todo->isCompleted() || todo->completed().date() >= limitDate) {
306 return false;
307 }
308
309 // This QList is only to prevent infinite recursion
310 if (checkedUids.contains(todo->uid())) {
311 // Probably will never happen, calendar.cpp checks for this
312 qCWarning(CALENDARSUPPORT_LOG) << "To-do hierarchy loop detected!";
313 return false;
314 }
315
316 checkedUids.append(todo->uid());
317 const KCalendarCore::Incidence::List children = calendar->childIncidences(todo->uid());
318 for (const KCalendarCore::Incidence::Ptr &incidence : children) {
320 if (t && !isSubTreeComplete(calendar, t, limitDate, checkedUids)) {
321 return false;
322 }
323 }
324
325 return true;
326}
327
328#include "moc_eventarchiver.cpp"
void runOnce(const Akonadi::ETMCalendar::Ptr &calendar, Akonadi::IncidenceChanger *changer, QDate limitDate, QWidget *widget)
Delete or archive events once.
void runAuto(const Akonadi::ETMCalendar::Ptr &calendar, Akonadi::IncidenceChanger *changer, QWidget *widget, bool withGUI)
Delete or archive events.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Todo::Ptr todo(const Akonadi::Item &item)
char * toString(const EngineQuery &query)
bool fileExists(const QUrl &path)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
KIOCORE_EXPORT FileCopyJob * file_copy(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
void setWindow(QObject *job, QWidget *widget)
ButtonCode warningContinueCancelList(QWidget *parent, const QString &text, const QStringList &strlist, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KGuiItem del()
const QList< QKeySequence > & end()
QDate addDays(qint64 ndays) const const
QDate addMonths(int nmonths) const const
QDate currentDate()
bool exists() const const
bool remove()
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
bool isEmpty() const const
void reserve(qsizetype size)
QLocale system()
Q_EMITQ_EMIT
const QObjectList & children() const const
QSharedPointer< X > dynamicCast() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
virtual QString fileName() const const override
QTimeZone systemTimeZone()
QUrl fromLocalFile(const QString &localFile)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:13:02 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.