1 /******************************************************************************
2  * konsolekalendar.cpp                                                        *
3  *                                                                            *
4  * KonsoleKalendar is a command line interface to KDE calendars               *
5  * SPDX-FileCopyrightText: 2002-2004 Tuukka Pasanen <illuusio@mailcity.com>   *
6  * SPDX-FileCopyrightText: 2003-2005 Allen Winter <winter@kde.org>            *
7  *                                                                            *
8  * SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 *
9  *                                                                            *
10  ******************************************************************************/
11 /**
12  * @file konsolekalendar.cpp
13  * Provides the KonsoleKalendar class definition.
14  * @author Tuukka Pasanen
15  * @author Allen Winter
16  */
17 #include "konsolekalendar.h"
18 #include "konsolekalendaradd.h"
19 #include "konsolekalendarchange.h"
20 #include "konsolekalendardelete.h"
21 #include "konsolekalendarexports.h"
22 
23 #include "konsolekalendar_debug.h"
24 #include <KLocalizedString>
25 
26 #include <Akonadi/AgentInstance>
27 #include <Akonadi/AgentInstanceCreateJob>
28 #include <Akonadi/AgentManager>
29 #include <Akonadi/Collection>
30 #include <Akonadi/CollectionFetchJob>
31 #include <Akonadi/CollectionFetchScope>
32 #include <KCalUtils/HtmlExport>
33 #include <kcalutils/htmlexportsettings.h>
34 
35 #include <QDBusInterface>
36 #include <QDBusReply>
37 #include <QEventLoop>
38 #include <QFile>
39 #include <QFileInfo>
40 #include <QTextStream>
41 
42 #include <iostream>
43 #include <stdio.h>
44 #include <stdlib.h>
45 
46 using namespace KCalendarCore;
47 using namespace std;
48 
KonsoleKalendar(KonsoleKalendarVariables * variables)49 KonsoleKalendar::KonsoleKalendar(KonsoleKalendarVariables *variables)
50 {
51     m_variables = variables;
52 }
53 
~KonsoleKalendar()54 KonsoleKalendar::~KonsoleKalendar()
55 {
56 }
57 
importCalendar()58 bool KonsoleKalendar::importCalendar()
59 {
60     KonsoleKalendarAdd add(m_variables);
61 
62     qCDebug(KONSOLEKALENDAR_LOG) << "konsolecalendar.cpp::importCalendar() | importing now!";
63     return add.addImportedCalendar();
64 }
65 
printCalendarList()66 bool KonsoleKalendar::printCalendarList()
67 {
68     auto job = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive);
69     const QStringList mimeTypes = QStringList() << QStringLiteral("text/calendar") << KCalendarCore::Event::eventMimeType()
70                                                 << KCalendarCore::Todo::todoMimeType() << KCalendarCore::Journal::journalMimeType();
71     job->fetchScope().setContentMimeTypes(mimeTypes);
72     QEventLoop loop;
73     QObject::connect(job, &Akonadi::CollectionFetchJob::result, &loop, &QEventLoop::quit);
74     job->start();
75     loop.exec();
76 
77     if (job->error() != 0) {
78         return false;
79     }
80 
81     const Akonadi::Collection::List collections = job->collections();
82 
83     if (collections.isEmpty()) {
84         cout << i18n("There are no calendars available.").toLocal8Bit().data() << endl;
85     } else {
86         cout << "--------------------------" << endl;
87         auto mimeTypeSet = QSet<QString>(mimeTypes.begin(), mimeTypes.end()); // set changes by run method intersect
88         for (const Akonadi::Collection &collection : collections) {
89             const QStringList contentMimeTypes = collection.contentMimeTypes();
90             auto collectionMimeTypeSet = QSet<QString>(contentMimeTypes.begin(), contentMimeTypes.end());
91 
92             if (mimeTypeSet.intersects(collectionMimeTypeSet)) {
93                 QString colId = QString::number(collection.id()).leftJustified(6, QLatin1Char(' '));
94                 colId += QLatin1String("- ");
95 
96                 bool readOnly = !(collection.rights() & Akonadi::Collection::CanCreateItem || collection.rights() & Akonadi::Collection::CanChangeItem
97                                   || collection.rights() & Akonadi::Collection::CanDeleteItem);
98 
99                 QString readOnlyString = readOnly ? i18n("(Read only)") + QLatin1Char(' ') : QString();
100 
101                 cout << colId.toLocal8Bit().data() << readOnlyString.toLocal8Bit().constData() << collection.displayName().toLocal8Bit().data() << endl;
102             }
103         }
104     }
105 
106     return true;
107 }
108 
createAkonadiResource(const QString & icalFileUri)109 bool KonsoleKalendar::createAkonadiResource(const QString &icalFileUri)
110 {
111     Akonadi::AgentType type = Akonadi::AgentManager::self()->type(QStringLiteral("akonadi_ical_resource"));
112     auto job = new Akonadi::AgentInstanceCreateJob(type);
113     QEventLoop loop;
114     QObject::connect(job, &Akonadi::CollectionFetchJob::result, &loop, &QEventLoop::quit);
115     job->start();
116     loop.exec();
117     if (job->error() != 0) {
118         return false;
119     }
120     auto inst = job->instance();
121     inst.setName(QFileInfo(icalFileUri).baseName());
122     QDBusInterface iface(QStringLiteral("org.freedesktop.Akonadi.Resource.") + inst.identifier(), QStringLiteral("/Settings"));
123     QDBusReply<void> reply = iface.call(QStringLiteral("setDisplayName"), QFileInfo(icalFileUri).baseName());
124     if (!reply.isValid()) {
125         qCWarning(KONSOLEKALENDAR_LOG) << "Could not set setting 'name': " << reply.error().message();
126         return false;
127     }
128     reply = iface.call(QStringLiteral("setPath"), icalFileUri);
129     if (!reply.isValid()) {
130         qCWarning(KONSOLEKALENDAR_LOG) << "Could not set setting 'path': " << reply.error().message();
131         return false;
132     }
133     reply = iface.call(QStringLiteral("save"));
134     if (!reply.isValid()) {
135         qCWarning(KONSOLEKALENDAR_LOG) << "Could not save settings: " << reply.error().message();
136         return false;
137     }
138     inst.reconfigure();
139     return true;
140 }
141 
createCalendar()142 bool KonsoleKalendar::createCalendar()
143 {
144     bool status = false;
145 
146     const QString filename = m_variables->getCalendarFile();
147 
148     if (m_variables->isDryRun()) {
149         cout << i18n("Create Calendar <Dry Run>: %1", filename).toLocal8Bit().data() << endl;
150     } else {
151         qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::createCalendar() |"
152                                      << "Creating calendar file: " << filename.toLocal8Bit().data();
153 
154         if (m_variables->isVerbose()) {
155             cout << i18n("Create Calendar <Verbose>: %1", filename).toLocal8Bit().data() << endl;
156         }
157 
158         status = createAkonadiResource(QStringLiteral("file://%1").arg(filename));
159     }
160 
161     return status;
162 }
163 
showInstance()164 bool KonsoleKalendar::showInstance()
165 {
166     bool status = true;
167     QFile f;
168     QString title;
169     Event::Ptr event;
170     const auto timeZone = m_variables->getCalendar()->timeZone();
171     Akonadi::CalendarBase::Ptr calendar = m_variables->getCalendar();
172 
173     if (m_variables->isDryRun()) {
174         cout << qPrintable(i18n("View Events <Dry Run>:")) << endl;
175         printSpecs();
176     } else {
177         qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
178                                      << "open export file";
179 
180         if (m_variables->isExportFile()) {
181             f.setFileName(m_variables->getExportFile());
182             if (!f.open(QIODevice::WriteOnly)) {
183                 status = false;
184                 qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
185                                              << "unable to open export file" << m_variables->getExportFile();
186             }
187         } else {
188             f.open(stdout, QIODevice::WriteOnly);
189         }
190 
191         if (status) {
192             qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
193                                          << "opened successful";
194 
195             if (m_variables->isVerbose()) {
196                 cout << i18n("View Event <Verbose>:").toLocal8Bit().data() << endl;
197                 printSpecs();
198             }
199 
200             QTextStream ts(&f);
201 
202             if (m_variables->getExportType() != ExportTypeHTML && m_variables->getExportType() != ExportTypeMonthHTML) {
203                 if (m_variables->getAll()) {
204                     qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
205                                                  << "view all events sorted list";
206 
207                     const Event::List sortedList = calendar->events(EventSortStartDate);
208                     qCDebug(KONSOLEKALENDAR_LOG) << "Found" << sortedList.count() << "events";
209                     if (!sortedList.isEmpty()) {
210                         // The code that was here before the akonadi port was really slow with 200 events
211                         // this is much faster:
212                         for (const KCalendarCore::Event::Ptr &event : sortedList) {
213                             status &= printEvent(&ts, event, event->dtStart().date());
214                         }
215                     }
216                 } else if (m_variables->isUID()) {
217                     qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
218                                                  << "view events by uid list";
219                     // TODO: support a list of UIDs
220                     event = calendar->event(m_variables->getUID());
221                     // If this UID represents a recurring Event,
222                     // only the first day of the Event will be printed
223                     status = printEvent(&ts, event, event->dtStart().date());
224                 } else if (m_variables->isNext()) {
225                     qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
226                                                  << "Show next activity in calendar";
227 
228                     QDateTime datetime = m_variables->getStartDateTime();
229                     datetime = datetime.addDays(720);
230 
231                     QDate dt;
232                     for (dt = m_variables->getStartDateTime().date(); dt <= datetime.date(); dt = dt.addDays(1)) {
233                         Event::List events = calendar->events(dt, timeZone, EventSortStartDate, SortDirectionAscending);
234                         qCDebug(KONSOLEKALENDAR_LOG) << "2-Found" << events.count() << "events on date" << dt;
235                         // finished here when we get the next event
236                         if (!events.isEmpty()) {
237                             qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
238                                                          << "Got the next event";
239                             printEvent(&ts, events.first(), dt);
240                             return true;
241                         }
242                     }
243                 } else {
244                     qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
245                                                  << "view raw events within date range list";
246 
247                     QDate dt;
248                     for (dt = m_variables->getStartDateTime().date(); dt <= m_variables->getEndDateTime().date() && status != false; dt = dt.addDays(1)) {
249                         Event::List events = calendar->events(dt, timeZone, EventSortStartDate, SortDirectionAscending);
250                         qCDebug(KONSOLEKALENDAR_LOG) << "3-Found" << events.count() << "events on date: " << dt;
251                         status = printEventList(&ts, &events, dt);
252                     }
253                 }
254             } else {
255                 QDate firstdate;
256                 QDate lastdate;
257                 if (m_variables->getAll()) {
258                     qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
259                                                  << "HTML view all events sorted list";
260                     // sort the events for this date by start date
261                     // in order to determine the date range.
262                     auto events = new Event::List(calendar->rawEvents(EventSortStartDate, SortDirectionAscending));
263                     firstdate = events->first()->dtStart().date();
264                     lastdate = events->last()->dtStart().date();
265                 } else if (m_variables->isUID()) {
266                     // TODO
267                     qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
268                                                  << "HTML view events by uid list";
269                     cout << i18n("Sorry, export to HTML by UID is not supported yet").toLocal8Bit().data() << endl;
270                     return false;
271                 } else {
272                     qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::showInstance() |"
273                                                  << "HTML view raw events within date range list";
274                     firstdate = m_variables->getStartDateTime().date();
275                     lastdate = m_variables->getEndDateTime().date();
276                 }
277 
278                 KCalUtils::HTMLExportSettings htmlSettings(QStringLiteral("Konsolekalendar"));
279 
280                 // TODO: get progname and url from the values set in main
281                 htmlSettings.setCreditName(QStringLiteral("KonsoleKalendar"));
282                 htmlSettings.setCreditURL(QStringLiteral("https://userbase.kde.org/KonsoleKalendar"));
283 
284                 htmlSettings.setExcludePrivate(true);
285                 htmlSettings.setExcludeConfidential(true);
286 
287                 htmlSettings.setEventView(false);
288                 htmlSettings.setMonthView(false);
289                 if (m_variables->getExportType() == ExportTypeMonthHTML) {
290                     title = i18n("Events:");
291                     htmlSettings.setMonthView(true);
292                 } else {
293                     if (firstdate == lastdate) {
294                         title = i18n("Events: %1", firstdate.toString(Qt::TextDate));
295                     } else {
296                         title = i18n("Events: %1 - %2", firstdate.toString(Qt::TextDate), lastdate.toString(Qt::TextDate));
297                     }
298                     htmlSettings.setEventView(true);
299                 }
300                 htmlSettings.setEventTitle(title);
301                 htmlSettings.setEventAttendees(true);
302                 // Not supporting Todos yet
303                 //         title = "To-Do List for " + firstdate.toString(Qt::TextDate);
304                 //         if ( firstdate != lastdate ) {
305                 //           title += " - " + lastdate.toString(Qt::TextDate);
306                 //         }
307                 htmlSettings.setTodoListTitle(title);
308                 htmlSettings.setTodoView(false);
309                 //         htmlSettings.setTaskCategories( false );
310                 //         htmlSettings.setTaskAttendees( false );
311                 //         htmlSettings.setTaskDueDate( true );
312 
313                 htmlSettings.setDateStart(QDateTime(firstdate.startOfDay()));
314                 htmlSettings.setDateEnd(QDateTime(lastdate.startOfDay()));
315 
316                 auto exp = new KCalUtils::HtmlExport(calendar.data(), &htmlSettings);
317                 status = exp->save(&ts);
318                 delete exp;
319             }
320             f.close();
321         }
322     }
323     return status;
324 }
325 
printEventList(QTextStream * ts,Event::List * eventList,QDate date)326 bool KonsoleKalendar::printEventList(QTextStream *ts, Event::List *eventList, QDate date)
327 {
328     bool status = true;
329 
330     qCDebug(KONSOLEKALENDAR_LOG) << eventList->count();
331     if (!eventList->isEmpty()) {
332         Event::Ptr singleEvent;
333         Event::List::ConstIterator it;
334 
335         for (it = eventList->constBegin(); it != eventList->constEnd() && status != false; ++it) {
336             singleEvent = *it;
337 
338             status = printEvent(ts, singleEvent, date);
339         }
340     }
341 
342     return status;
343 }
344 
printEvent(QTextStream * ts,const Event::Ptr & event,QDate dt)345 bool KonsoleKalendar::printEvent(QTextStream *ts, const Event::Ptr &event, QDate dt)
346 {
347     bool status = false;
348     KonsoleKalendarExports exports;
349 
350     if (event) {
351         switch (m_variables->getExportType()) {
352         case ExportTypeCSV:
353             qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::printEvent() |"
354                                          << "CSV export";
355             status = exports.exportAsCSV(ts, event, dt);
356             break;
357 
358         case ExportTypeTextShort: {
359             qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::printEvent() |"
360                                          << "TEXT-SHORT export";
361             bool sameDay = true;
362             if (dt.daysTo(m_saveDate)) {
363                 sameDay = false;
364                 m_saveDate = dt;
365             }
366             status = exports.exportAsTxtShort(ts, event, dt, sameDay);
367         } break;
368 
369         case ExportTypeHTML:
370             // this is handled separately for now
371             break;
372 
373         default: // Default export-type is ExportTypeText
374             qCDebug(KONSOLEKALENDAR_LOG) << "konsolekalendar.cpp::printEvent() |"
375                                          << "TEXT export";
376             status = exports.exportAsTxt(ts, event, dt);
377             break;
378         }
379     }
380     return status;
381 }
382 
addEvent()383 bool KonsoleKalendar::addEvent()
384 {
385     qCDebug(KONSOLEKALENDAR_LOG) << "konsolecalendar.cpp::addEvent() |"
386                                  << "Create Adding";
387     KonsoleKalendarAdd add(m_variables);
388     qCDebug(KONSOLEKALENDAR_LOG) << "konsolecalendar.cpp::addEvent() |"
389                                  << "Adding Event now!";
390     return add.addEvent();
391 }
392 
changeEvent()393 bool KonsoleKalendar::changeEvent()
394 {
395     qCDebug(KONSOLEKALENDAR_LOG) << "konsolecalendar.cpp::changeEvent() |"
396                                  << "Create Changing";
397     KonsoleKalendarChange change(m_variables);
398     qCDebug(KONSOLEKALENDAR_LOG) << "konsolecalendar.cpp::changeEvent() |"
399                                  << "Changing Event now!";
400     return change.changeEvent();
401 }
402 
deleteEvent()403 bool KonsoleKalendar::deleteEvent()
404 {
405     qCDebug(KONSOLEKALENDAR_LOG) << "konsolecalendar.cpp::deleteEvent() |"
406                                  << "Create Deleting";
407     KonsoleKalendarDelete del(m_variables);
408     qCDebug(KONSOLEKALENDAR_LOG) << "konsolecalendar.cpp::deleteEvent() |"
409                                  << "Deleting Event now!";
410     return del.deleteEvent();
411 }
412 
isEvent(const QDateTime & startdate,const QDateTime & enddate,const QString & summary)413 bool KonsoleKalendar::isEvent(const QDateTime &startdate, const QDateTime &enddate, const QString &summary)
414 {
415     // Search for an event with specified start and end datetime stamp and summary
416 
417     Event::Ptr event;
418     Event::List::ConstIterator it;
419 
420     bool found = false;
421 
422     const auto timeZone = m_variables->getCalendar()->timeZone();
423     Event::List eventList(m_variables->getCalendar()->rawEventsForDate(startdate.date(), timeZone, EventSortStartDate, SortDirectionAscending));
424     for (it = eventList.constBegin(); it != eventList.constEnd(); ++it) {
425         event = *it;
426         if (event->dtEnd().toTimeZone(timeZone) == enddate && event->summary() == summary) {
427             found = true;
428             break;
429         }
430     }
431     return found;
432 }
433 
printSpecs()434 void KonsoleKalendar::printSpecs()
435 {
436     cout << i18n("  What:  %1", m_variables->getSummary()).toLocal8Bit().data() << endl;
437 
438     cout << i18n("  Begin: %1", m_variables->getStartDateTime().toString(Qt::TextDate)).toLocal8Bit().data() << endl;
439 
440     cout << i18n("  End:   %1", m_variables->getEndDateTime().toString(Qt::TextDate)).toLocal8Bit().data() << endl;
441 
442     if (m_variables->getFloating() == true) {
443         cout << i18n("  No Time Associated with Event").toLocal8Bit().data() << endl;
444     }
445 
446     cout << i18n("  Desc:  %1", m_variables->getDescription()).toLocal8Bit().data() << endl;
447 
448     cout << i18n("  Location:  %1", m_variables->getLocation()).toLocal8Bit().data() << endl;
449 }
450