kalarm

alarmcalendar.cpp
1/*
2 * alarmcalendar.cpp - KAlarm calendar file access
3 * Program: kalarm
4 * Copyright © 2001-2006,2009 by David Jarvie <djarvie@kde.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21#include "kalarm.h"
22#include <unistd.h>
23#include <time.h>
24
25#include <tqfile.h>
26#include <tqtextstream.h>
27#include <tqregexp.h>
28#include <tqtimer.h>
29
30#include <tdelocale.h>
31#include <tdemessagebox.h>
32#include <tdestandarddirs.h>
33#include <kstaticdeleter.h>
34#include <tdeconfig.h>
35#include <tdeaboutdata.h>
36#include <tdeio/netaccess.h>
37#include <tdefileitem.h>
38#include <tdetempfile.h>
39#include <tdefiledialog.h>
40#include <dcopclient.h>
41#include <kdebug.h>
42
43extern "C" {
44#include <libical/ical.h>
45}
46
47#include <libkcal/vcaldrag.h>
48#include <libkcal/vcalformat.h>
49#include <libkcal/icalformat.h>
50
51#include "calendarcompat.h"
52#include "daemon.h"
53#include "functions.h"
54#include "kalarmapp.h"
55#include "mainwindow.h"
56#include "preferences.h"
57#include "startdaytimer.h"
58#include "alarmcalendar.moc"
59
60using namespace KCal;
61
62TQString AlarmCalendar::icalProductId()
63{
64 return TQString::fromLatin1("-//K Desktop Environment//NONSGML " KALARM_NAME " %1//EN").arg(KAlarm::currentCalendarVersionString());
65}
66
67static const KAEvent::Status eventTypes[AlarmCalendar::NCALS] = {
68 KAEvent::ACTIVE, KAEvent::EXPIRED, KAEvent::DISPLAYING, KAEvent::TEMPLATE
69};
70static const TQString calendarNames[AlarmCalendar::NCALS] = {
71 TQString::fromLatin1("calendar.ics"),
72 TQString::fromLatin1("expired.ics"),
73 TQString::fromLatin1("displaying.ics"),
74 TQString::fromLatin1("template.ics")
75};
76static KStaticDeleter<AlarmCalendar> calendarDeleter[AlarmCalendar::NCALS]; // ensure that the calendar destructors are called
77
78AlarmCalendar* AlarmCalendar::mCalendars[NCALS] = { 0, 0, 0, 0 };
79
80
81/******************************************************************************
82* Initialise the alarm calendars, and ensure that their file names are different.
83* There are 4 calendars:
84* 1) A user-independent one containing the active alarms;
85* 2) A historical one containing expired alarms;
86* 3) A user-specific one which contains details of alarms which are currently
87* being displayed to that user and which have not yet been acknowledged;
88* 4) One containing alarm templates.
89* Reply = true if success, false if calendar name error.
90*/
91bool AlarmCalendar::initialiseCalendars()
92{
93 TDEConfig* config = tdeApp->config();
94 config->setGroup(TQString::fromLatin1("General"));
95 TQString activeKey = TQString::fromLatin1("Calendar");
96 TQString expiredKey = TQString::fromLatin1("ExpiredCalendar");
97 TQString templateKey = TQString::fromLatin1("TemplateCalendar");
98 TQString displayCal, activeCal, expiredCal, templateCal;
99 calendarDeleter[ACTIVE].setObject(mCalendars[ACTIVE], createCalendar(ACTIVE, config, activeCal, activeKey));
100 calendarDeleter[EXPIRED].setObject(mCalendars[EXPIRED], createCalendar(EXPIRED, config, expiredCal, expiredKey));
101 calendarDeleter[DISPLAY].setObject(mCalendars[DISPLAY], createCalendar(DISPLAY, config, displayCal));
102 calendarDeleter[TEMPLATE].setObject(mCalendars[TEMPLATE], createCalendar(TEMPLATE, config, templateCal, templateKey));
103
104 TQString errorKey1, errorKey2;
105 if (activeCal == displayCal)
106 errorKey1 = activeKey;
107 else if (expiredCal == displayCal)
108 errorKey1 = expiredKey;
109 else if (templateCal == displayCal)
110 errorKey1 = templateKey;
111 if (!errorKey1.isNull())
112 {
113 kdError(5950) << "AlarmCalendar::initialiseCalendars(): '" << errorKey1 << "' calendar name = display calendar name\n";
114 TQString file = config->readPathEntry(errorKey1);
115 KAlarmApp::displayFatalError(i18n("%1: file name not permitted: %2").arg(errorKey1).arg(file));
116 return false;
117 }
118 if (activeCal == expiredCal)
119 {
120 errorKey1 = activeKey;
121 errorKey2 = expiredKey;
122 }
123 else if (activeCal == templateCal)
124 {
125 errorKey1 = activeKey;
126 errorKey2 = templateKey;
127 }
128 else if (expiredCal == templateCal)
129 {
130 errorKey1 = expiredKey;
131 errorKey2 = templateKey;
132 }
133 if (!errorKey1.isNull())
134 {
135 kdError(5950) << "AlarmCalendar::initialiseCalendars(): calendar names clash: " << errorKey1 << ", " << errorKey2 << endl;
136 KAlarmApp::displayFatalError(i18n("%1, %2: file names must be different").arg(errorKey1).arg(errorKey2));
137 return false;
138 }
139 if (!mCalendars[ACTIVE]->valid())
140 {
141 TQString path = mCalendars[ACTIVE]->path();
142 kdError(5950) << "AlarmCalendar::initialiseCalendars(): invalid name: " << path << endl;
143 KAlarmApp::displayFatalError(i18n("Invalid calendar file name: %1").arg(path));
144 return false;
145 }
146 return true;
147}
148
149/******************************************************************************
150* Create an alarm calendar instance.
151* If 'configKey' is non-null, the calendar will be converted to ICal format.
152*/
153AlarmCalendar* AlarmCalendar::createCalendar(CalID type, TDEConfig* config, TQString& writePath, const TQString& configKey)
154{
155 static TQRegExp vcsRegExp(TQString::fromLatin1("\\.vcs$"));
156 static TQString ical = TQString::fromLatin1(".ics");
157
158 if (configKey.isNull())
159 {
160 writePath = locateLocal("appdata", calendarNames[type]);
161 return new AlarmCalendar(writePath, type);
162 }
163 else
164 {
165 TQString readPath = config->readPathEntry(configKey, locateLocal("appdata", calendarNames[type]));
166 writePath = readPath;
167 writePath.replace(vcsRegExp, ical);
168 return new AlarmCalendar(readPath, type, writePath, configKey);
169 }
170}
171
172/******************************************************************************
173* Terminate access to all calendars.
174*/
175void AlarmCalendar::terminateCalendars()
176{
177 for (int i = 0; i < NCALS; ++i)
178 {
179 calendarDeleter[i].destructObject();
180 mCalendars[i] = 0;
181 }
182}
183
184/******************************************************************************
185* Return a calendar, opening it first if not already open.
186* Reply = calendar instance
187* = 0 if calendar could not be opened.
188*/
189AlarmCalendar* AlarmCalendar::calendarOpen(CalID id)
190{
191 AlarmCalendar* cal = mCalendars[id];
192 if (!cal->mPurgeDays)
193 return 0; // all events are automatically purged from the calendar
194 if (cal->open())
195 return cal;
196 kdError(5950) << "AlarmCalendar::calendarOpen(" << calendarNames[id] << "): open error\n";
197 return 0;
198}
199
200/******************************************************************************
201* Find and return the event with the specified ID.
202* The calendar searched is determined by the calendar identifier in the ID.
203*/
204const KCal::Event* AlarmCalendar::getEvent(const TQString& uniqueID)
205{
206 if (uniqueID.isEmpty())
207 return 0;
208 CalID calID;
209 switch (KAEvent::uidStatus(uniqueID))
210 {
211 case KAEvent::ACTIVE: calID = ACTIVE; break;
212 case KAEvent::TEMPLATE: calID = TEMPLATE; break;
213 case KAEvent::EXPIRED: calID = EXPIRED; break;
214 case KAEvent::DISPLAYING: calID = DISPLAY; break;
215 default:
216 return 0;
217 }
218 AlarmCalendar* cal = calendarOpen(calID);
219 if (!cal)
220 return 0;
221 return cal->event(uniqueID);
222}
223
224
225/******************************************************************************
226* Constructor.
227* If 'icalPath' is non-null, the file will be always be saved in ICal format.
228* If 'configKey' is also non-null, that config file entry will be updated when
229* the file is saved in ICal format.
230*/
231AlarmCalendar::AlarmCalendar(const TQString& path, CalID type, const TQString& icalPath,
232 const TQString& configKey)
233 : mCalendar(0),
234 mConfigKey(icalPath.isNull() ? TQString() : configKey),
235 mType(eventTypes[type]),
236 mPurgeDays(-1), // default to not purging
237 mOpen(false),
238 mPurgeDaysQueued(-1),
239 mUpdateCount(0),
240 mUpdateSave(false)
241{
242 mUrl.setPath(path); // N.B. constructor mUrl(path) doesn't work with UNIX paths
243 mICalUrl.setPath(icalPath.isNull() ? path : icalPath);
244 mVCal = (icalPath.isNull() || path != icalPath); // is the calendar in ICal or VCal format?
245}
246
247AlarmCalendar::~AlarmCalendar()
248{
249 close();
250}
251
252/******************************************************************************
253* Open the calendar file if not already open, and load it into memory.
254*/
255bool AlarmCalendar::open()
256{
257 if (mOpen)
258 return true;
259 if (!mUrl.isValid())
260 return false;
261
262 kdDebug(5950) << "AlarmCalendar::open(" << mUrl.prettyURL() << ")\n";
263 if (!mCalendar)
264 mCalendar = new CalendarLocal(TQString::fromLatin1("UTC"));
265 mCalendar->setLocalTime(); // write out using local time (i.e. no time zone)
266
267 // Check for file's existence, assuming that it does exist when uncertain,
268 // to avoid overwriting it.
269 if (!TDEIO::NetAccess::exists(mUrl, true, MainWindow::mainMainWindow()))
270 {
271 // The calendar file doesn't yet exist, so create it
272 if (create())
273 load();
274 }
275 else
276 {
277 // Load the existing calendar file
278 if (load() == 0)
279 {
280 if (create()) // zero-length file - create a new one
281 load();
282 }
283 }
284 if (!mOpen)
285 {
286 delete mCalendar;
287 mCalendar = 0;
288 }
289 return mOpen;
290}
291
292/******************************************************************************
293* Private method to create a new calendar file.
294* It is always created in iCalendar format.
295*/
296bool AlarmCalendar::create()
297{
298 if (mICalUrl.isLocalFile())
299 return saveCal(mICalUrl.path());
300 else
301 {
302 KTempFile tmpFile;
303 return saveCal(tmpFile.name());
304 }
305}
306
307/******************************************************************************
308* Load the calendar file into memory.
309* Reply = 1 if success
310* = 0 if zero-length file exists.
311* = -1 if failure to load calendar file
312* = -2 if instance uninitialised.
313*/
314int AlarmCalendar::load()
315{
316 if (!mCalendar)
317 return -2;
318
319 kdDebug(5950) << "AlarmCalendar::load(): " << mUrl.prettyURL() << endl;
320 TQString tmpFile;
321 if (!TDEIO::NetAccess::download(mUrl, tmpFile, MainWindow::mainMainWindow()))
322 {
323 kdError(5950) << "AlarmCalendar::load(): Load failure" << endl;
324 KMessageBox::error(0, i18n("Cannot open calendar:\n%1").arg(mUrl.prettyURL()));
325 return -1;
326 }
327 kdDebug(5950) << "AlarmCalendar::load(): --- Downloaded to " << tmpFile << endl;
328 mCalendar->setTimeZoneId(TQString()); // default to the local time zone for reading
329 bool loaded = mCalendar->load(tmpFile);
330 mCalendar->setLocalTime(); // write using local time (i.e. no time zone)
331 if (!loaded)
332 {
333 // Check if the file is zero length
334 TDEIO::NetAccess::removeTempFile(tmpFile);
335 TDEIO::UDSEntry uds;
336 TDEIO::NetAccess::stat(mUrl, uds, MainWindow::mainMainWindow());
337 KFileItem fi(uds, mUrl);
338 if (!fi.size())
339 return 0; // file is zero length
340 kdError(5950) << "AlarmCalendar::load(): Error loading calendar file '" << tmpFile << "'" << endl;
341 KMessageBox::error(0, i18n("Error loading calendar:\n%1\n\nPlease fix or delete the file.").arg(mUrl.prettyURL()));
342 // load() could have partially populated the calendar, so clear it out
343 mCalendar->close();
344 delete mCalendar;
345 mCalendar = 0;
346 return -1;
347 }
348 if (!mLocalFile.isEmpty())
349 TDEIO::NetAccess::removeTempFile(mLocalFile); // removes it only if it IS a temporary file
350 mLocalFile = tmpFile;
351
352 CalendarCompat::fix(*mCalendar, mLocalFile); // convert events to current KAlarm format for when calendar is saved
353 mOpen = true;
354 return 1;
355}
356
357/******************************************************************************
358* Reload the calendar file into memory.
359*/
360bool AlarmCalendar::reload()
361{
362 if (!mCalendar)
363 return false;
364 kdDebug(5950) << "AlarmCalendar::reload(): " << mUrl.prettyURL() << endl;
365 close();
366 bool result = open();
367 return result;
368}
369
370/******************************************************************************
371* Save the calendar from memory to file.
372* If a filename is specified, create a new calendar file.
373*/
374bool AlarmCalendar::saveCal(const TQString& newFile)
375{
376 if (!mCalendar || (!mOpen && newFile.isNull()))
377 return false;
378
379 kdDebug(5950) << "AlarmCalendar::saveCal(\"" << newFile << "\", " << mType << ")\n";
380 TQString saveFilename = newFile.isNull() ? mLocalFile : newFile;
381 if (mVCal && newFile.isNull() && mUrl.isLocalFile())
382 saveFilename = mICalUrl.path();
383 if (!mCalendar->save(saveFilename, new ICalFormat))
384 {
385 kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): failed.\n";
386 KMessageBox::error(0, i18n("Failed to save calendar to\n'%1'").arg(mICalUrl.prettyURL()));
387 return false;
388 }
389
390 if (!mICalUrl.isLocalFile())
391 {
392 if (!TDEIO::NetAccess::upload(saveFilename, mICalUrl, MainWindow::mainMainWindow()))
393 {
394 kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): upload failed.\n";
395 KMessageBox::error(0, i18n("Cannot upload calendar to\n'%1'").arg(mICalUrl.prettyURL()));
396 return false;
397 }
398 }
399
400 if (mVCal)
401 {
402 // The file was in vCalendar format, but has now been saved in iCalendar format.
403 // Save the change in the config file.
404 if (!mConfigKey.isNull())
405 {
406 TDEConfig* config = tdeApp->config();
407 config->setGroup(TQString::fromLatin1("General"));
408 config->writePathEntry(mConfigKey, mICalUrl.path());
409 config->sync();
410 }
411 mUrl = mICalUrl;
412 mVCal = false;
413 }
414
415 mUpdateSave = false;
416 emit calendarSaved(this);
417 return true;
418}
419
420/******************************************************************************
421* Delete any temporary file at program exit.
422*/
423void AlarmCalendar::close()
424{
425 if (!mLocalFile.isEmpty())
426 {
427 TDEIO::NetAccess::removeTempFile(mLocalFile); // removes it only if it IS a temporary file
428 mLocalFile = "";
429 }
430 if (mCalendar)
431 {
432 mCalendar->close();
433 delete mCalendar;
434 mCalendar = 0;
435 }
436 mOpen = false;
437}
438
439/******************************************************************************
440* Import alarms from an external calendar and merge them into KAlarm's calendar.
441* The alarms are given new unique event IDs.
442* Parameters: parent = parent widget for error message boxes
443* Reply = true if all alarms in the calendar were successfully imported
444* = false if any alarms failed to be imported.
445*/
446bool AlarmCalendar::importAlarms(TQWidget* parent)
447{
448 KURL url = KFileDialog::getOpenURL(TQString::fromLatin1(":importalarms"),
449 TQString::fromLatin1("*.vcs *.ics|%1").arg(i18n("Calendar Files")), parent);
450 if (url.isEmpty())
451 {
452 kdError(5950) << "AlarmCalendar::importAlarms(): Empty URL" << endl;
453 return false;
454 }
455 if (!url.isValid())
456 {
457 kdDebug(5950) << "AlarmCalendar::importAlarms(): Invalid URL" << endl;
458 return false;
459 }
460 kdDebug(5950) << "AlarmCalendar::importAlarms(" << url.prettyURL() << ")" << endl;
461
462 bool success = true;
463 TQString filename;
464 bool local = url.isLocalFile();
465 if (local)
466 {
467 filename = url.path();
468 if (!TDEStandardDirs::exists(filename))
469 {
470 kdDebug(5950) << "AlarmCalendar::importAlarms(): File '" << url.prettyURL() << "' not found" << endl;
471 KMessageBox::error(parent, i18n("Could not load calendar '%1'.").arg(url.prettyURL()));
472 return false;
473 }
474 }
475 else
476 {
477 if (!TDEIO::NetAccess::download(url, filename, MainWindow::mainMainWindow()))
478 {
479 kdError(5950) << "AlarmCalendar::importAlarms(): Download failure" << endl;
480 KMessageBox::error(parent, i18n("Cannot download calendar:\n%1").arg(url.prettyURL()));
481 return false;
482 }
483 kdDebug(5950) << "--- Downloaded to " << filename << endl;
484 }
485
486 // Read the calendar and add its alarms to the current calendars
487 CalendarLocal cal(TQString::fromLatin1("UTC"));
488 cal.setLocalTime(); // write out using local time (i.e. no time zone)
489 success = cal.load(filename);
490 if (!success)
491 {
492 kdDebug(5950) << "AlarmCalendar::importAlarms(): error loading calendar '" << filename << "'" << endl;
493 KMessageBox::error(parent, i18n("Could not load calendar '%1'.").arg(url.prettyURL()));
494 }
495 else
496 {
497 CalendarCompat::fix(cal, filename);
498 bool saveActive = false;
499 bool saveExpired = false;
500 bool saveTemplate = false;
501 AlarmCalendar* active = activeCalendar();
502 AlarmCalendar* expired = expiredCalendar();
503 AlarmCalendar* templat = 0;
504 AlarmCalendar* acal;
505 Event::List events = cal.rawEvents();
506 for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
507 {
508 const Event* event = *it;
509 if (event->alarms().isEmpty() || !KAEvent(*event).valid())
510 continue; // ignore events without alarms, or usable alarms
511 KAEvent::Status type = KAEvent::uidStatus(event->uid());
512 switch (type)
513 {
514 case KAEvent::ACTIVE:
515 acal = active;
516 saveActive = true;
517 break;
518 case KAEvent::EXPIRED:
519 acal = expired;
520 saveExpired = true;
521 break;
522 case KAEvent::TEMPLATE:
523 if (!templat)
524 templat = templateCalendarOpen();
525 acal = templat;
526 saveTemplate = true;
527 break;
528 default:
529 continue;
530 }
531 if (!acal)
532 continue;
533
534 Event* newev = new Event(*event);
535
536 // If there is a display alarm without display text, use the event
537 // summary text instead.
538 if (type == KAEvent::ACTIVE && !newev->summary().isEmpty())
539 {
540 const Alarm::List& alarms = newev->alarms();
541 for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait)
542 {
543 Alarm* alarm = *ait;
544 if (alarm->type() == Alarm::Display && alarm->text().isEmpty())
545 alarm->setText(newev->summary());
546 }
547 newev->setSummary(TQString()); // KAlarm only uses summary for template names
548 }
549 // Give the event a new ID and add it to the calendar
550 newev->setUid(KAEvent::uid(CalFormat::createUniqueId(), type));
551 if (!acal->mCalendar->addEvent(newev))
552 success = false;
553 }
554
555 // Save any calendars which have been modified
556 if (saveActive)
557 active->saveCal();
558 if (saveExpired)
559 expired->saveCal();
560 if (saveTemplate)
561 templat->saveCal();
562 }
563 if (!local)
564 TDEIO::NetAccess::removeTempFile(filename);
565 return success;
566}
567
568/******************************************************************************
569* Flag the start of a group of calendar update calls.
570* The purpose is to avoid multiple calendar saves during a group of operations.
571*/
572void AlarmCalendar::startUpdate()
573{
574 ++mUpdateCount;
575}
576
577/******************************************************************************
578* Flag the end of a group of calendar update calls.
579* The calendar is saved if appropriate.
580*/
581bool AlarmCalendar::endUpdate()
582{
583 if (mUpdateCount > 0)
584 --mUpdateCount;
585 if (!mUpdateCount)
586 {
587 if (mUpdateSave)
588 return saveCal();
589 }
590 return true;
591}
592
593/******************************************************************************
594* Save the calendar, or flag it for saving if in a group of calendar update calls.
595*/
596bool AlarmCalendar::save()
597{
598 if (mUpdateCount)
599 {
600 mUpdateSave = true;
601 return true;
602 }
603 else
604 return saveCal();
605}
606
607#if 0
608/******************************************************************************
609* If it is VCal format, convert the calendar URL to ICal and save the new URL
610* in the config file.
611*/
612void AlarmCalendar::convertToICal()
613{
614 if (mVCal)
615 {
616 if (!mConfigKey.isNull())
617 {
618 TDEConfig* config = tdeApp->config();
619 config->setGroup(TQString::fromLatin1("General"));
620 config->writePathEntry(mConfigKey, mICalUrl.path());
621 config->sync();
622 }
623 mUrl = mICalUrl;
624 mVCal = false;
625 }
626}
627#endif
628
629/******************************************************************************
630* Set the number of days to keep alarms.
631* Alarms which are older are purged immediately, and at the start of each day.
632*/
633void AlarmCalendar::setPurgeDays(int days)
634{
635 if (days != mPurgeDays)
636 {
637 int oldDays = mPurgeDays;
638 mPurgeDays = days;
639 if (mPurgeDays <= 0)
641 if (oldDays < 0 || (days >= 0 && days < oldDays))
642 {
643 // Alarms are now being kept for less long, so purge them
644 if (open())
645 slotPurge();
646 }
647 else if (mPurgeDays > 0)
648 startPurgeTimer();
649 }
650}
651
652/******************************************************************************
653* Called at the start of each day by the purge timer.
654* Purge all events from the calendar whose end time is longer ago than 'mPurgeDays'.
655*/
656void AlarmCalendar::slotPurge()
657{
658 purge(mPurgeDays);
659 startPurgeTimer();
660}
661
662/******************************************************************************
663* Purge all events from the calendar whose end time is longer ago than
664* 'daysToKeep'. All events are deleted if 'daysToKeep' is zero.
665*/
666void AlarmCalendar::purge(int daysToKeep)
667{
668 if (mPurgeDaysQueued < 0 || daysToKeep < mPurgeDaysQueued)
669 mPurgeDaysQueued = daysToKeep;
670
671 // Do the purge once any other current operations are completed
672 theApp()->processQueue();
673}
674
675/******************************************************************************
676* This method must only be called from the main KAlarm queue processing loop,
677* to prevent asynchronous calendar operations interfering with one another.
678*
679* Purge all events from the calendar whose end time is longer ago than 'daysToKeep'.
680* All events are deleted if 'daysToKeep' is zero.
681* The calendar must already be open.
682*/
683void AlarmCalendar::purgeIfQueued()
684{
685 if (mPurgeDaysQueued >= 0)
686 {
687 if (open())
688 {
689 kdDebug(5950) << "AlarmCalendar::purgeIfQueued(" << mPurgeDaysQueued << ")\n";
690 bool changed = false;
691 TQDate cutoff = TQDate::currentDate().addDays(-mPurgeDaysQueued);
692 Event::List events = mCalendar->rawEvents();
693 for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
694 {
695 Event* kcalEvent = *it;
696 if (!mPurgeDaysQueued || kcalEvent->created().date() < cutoff)
697 {
698 mCalendar->deleteEvent(kcalEvent);
699 changed = true;
700 }
701 }
702 if (changed)
703 {
704 saveCal();
705 emit purged();
706 }
707 mPurgeDaysQueued = -1;
708 }
709 }
710}
711
712
713/******************************************************************************
714* Start the purge timer to expire at the start of the next day (using the user-
715* defined start-of-day time).
716*/
717void AlarmCalendar::startPurgeTimer()
718{
719 if (mPurgeDays > 0)
720 StartOfDayTimer::connect(this, TQ_SLOT(slotPurge()));
721}
722
723/******************************************************************************
724* Add the specified event to the calendar.
725* If it is the active calendar and 'useEventID' is false, a new event ID is
726* created. In all other cases, the event ID is taken from 'event'.
727* 'event' is updated with the actual event ID.
728* Reply = the KCal::Event as written to the calendar.
729*/
730Event* AlarmCalendar::addEvent(KAEvent& event, bool useEventID)
731{
732 if (!mOpen)
733 return 0;
734 TQString id = event.id();
735 Event* kcalEvent = new Event;
736 if (mType == KAEvent::ACTIVE)
737 {
738 if (id.isEmpty())
739 useEventID = false;
740 if (!useEventID)
741 event.setEventID(kcalEvent->uid());
742 }
743 else
744 {
745 if (id.isEmpty())
746 id = kcalEvent->uid();
747 useEventID = true;
748 }
749 if (useEventID)
750 {
751 id = KAEvent::uid(id, mType);
752 event.setEventID(id);
753 kcalEvent->setUid(id);
754 }
755 event.updateKCalEvent(*kcalEvent, false, (mType == KAEvent::EXPIRED), true);
756 mCalendar->addEvent(kcalEvent);
757 event.clearUpdated();
758 return kcalEvent;
759}
760
761/******************************************************************************
762* Update the specified event in the calendar with its new contents.
763* The event retains the same ID.
764*/
765void AlarmCalendar::updateEvent(const KAEvent& evnt)
766{
767 if (mOpen)
768 {
769 Event* kcalEvent = event(evnt.id());
770 if (kcalEvent)
771 {
772 evnt.updateKCalEvent(*kcalEvent);
773 evnt.clearUpdated();
774 if (mType == KAEvent::ACTIVE)
775 Daemon::savingEvent(evnt.id());
776 return;
777 }
778 }
779 if (mType == KAEvent::ACTIVE)
780 Daemon::eventHandled(evnt.id(), false);
781}
782
783/******************************************************************************
784* Delete the specified event from the calendar, if it exists.
785* The calendar is then optionally saved.
786*/
787bool AlarmCalendar::deleteEvent(const TQString& eventID, bool saveit)
788{
789 if (mOpen)
790 {
791 Event* kcalEvent = event(eventID);
792 if (kcalEvent)
793 {
794 mCalendar->deleteEvent(kcalEvent);
795 if (mType == KAEvent::ACTIVE)
796 Daemon::savingEvent(eventID);
797 if (saveit)
798 return save();
799 return true;
800 }
801 }
802 if (mType == KAEvent::ACTIVE)
803 Daemon::eventHandled(eventID, false);
804 return false;
805}
806
807/******************************************************************************
808* Emit a signal to indicate whether the calendar is empty.
809*/
810void AlarmCalendar::emitEmptyStatus()
811{
812 emit emptyStatus(events().isEmpty());
813}
814
815/******************************************************************************
816* Return the event with the specified ID.
817*/
818KCal::Event* AlarmCalendar::event(const TQString& uniqueID)
819{
820 return mCalendar ? mCalendar->event(uniqueID) : 0;
821}
822
823/******************************************************************************
824* Return all events in the calendar which contain usable alarms.
825*/
826KCal::Event::List AlarmCalendar::events()
827{
828 if (!mCalendar)
829 return KCal::Event::List();
830 KCal::Event::List list = mCalendar->rawEvents();
831 KCal::Event::List::Iterator it = list.begin();
832 while (it != list.end())
833 {
834 KCal::Event* event = *it;
835 if (event->alarms().isEmpty() || !KAEvent(*event).valid())
836 it = list.remove(it);
837 else
838 ++it;
839 }
840 return list;
841}
842
843/******************************************************************************
844* Return all events which have alarms falling within the specified time range.
845*/
846Event::List AlarmCalendar::eventsWithAlarms(const TQDateTime& from, const TQDateTime& to)
847{
848 kdDebug(5950) << "AlarmCalendar::eventsWithAlarms(" << from.toString() << " - " << to.toString() << ")\n";
849 Event::List evnts;
850 TQDateTime dt;
851 Event::List allEvents = events(); // ignore events without usable alarms
852 for (Event::List::ConstIterator it = allEvents.begin(); it != allEvents.end(); ++it)
853 {
854 Event* e = *it;
855 bool recurs = e->doesRecur();
856 int endOffset = 0;
857 bool endOffsetValid = false;
858 const Alarm::List& alarms = e->alarms();
859 for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait)
860 {
861 Alarm* alarm = *ait;
862 if (alarm->enabled())
863 {
864 if (recurs)
865 {
866 if (alarm->hasTime())
867 dt = alarm->time();
868 else
869 {
870 // The alarm time is defined by an offset from the event start or end time.
871 // Find the offset from the event start time, which is also used as the
872 // offset from the recurrence time.
873 int offset = 0;
874 if (alarm->hasStartOffset())
875 offset = alarm->startOffset().asSeconds();
876 else if (alarm->hasEndOffset())
877 {
878 if (!endOffsetValid)
879 {
880 endOffset = e->hasDuration() ? e->duration() : e->hasEndDate() ? e->dtStart().secsTo(e->dtEnd()) : 0;
881 endOffsetValid = true;
882 }
883 offset = alarm->endOffset().asSeconds() + endOffset;
884 }
885 // Adjust the 'from' date/time and find the next recurrence at or after it
886 TQDateTime pre = from.addSecs(-offset - 1);
887 if (e->doesFloat() && pre.time() < Preferences::startOfDay())
888 pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
889 dt = e->recurrence()->getNextDateTime(pre);
890 if (!dt.isValid())
891 continue;
892 dt = dt.addSecs(offset);
893 }
894 }
895 else
896 dt = alarm->time();
897 if (dt >= from && dt <= to)
898 {
899 kdDebug(5950) << "AlarmCalendar::events() '" << e->summary()
900 << "': " << dt.toString() << endl;
901 evnts.append(e);
902 break;
903 }
904 }
905 }
906 }
907 return evnts;
908}
Provides read and write access to calendar files.
Definition: alarmcalendar.h:37
KAEvent corresponds to a KCal::Event instance.
Definition: alarmevent.h:232
Status
The category of an event, indicated by the middle part of its UID.
Definition: alarmevent.h:270
bool hasStartOffset() const
TQDateTime time() const
TQString text() const
Duration endOffset() const
bool hasEndOffset() const
Duration startOffset() const
void setText(const TQString &text)
bool enabled() const
bool hasTime() const
Type type() const
Event::List rawEvents(EventSortField sortField=EventSortUnsorted, SortDirection sortDirection=SortDirectionAscending)
bool deleteEvent(Event *event)
bool addEvent(Event *event)
Event * event(const TQString &uid)
bool load(const TQString &fileName, CalFormat *format=0)
bool save(const TQString &fileName, CalFormat *format=0)
void setLocalTime()
void setTimeZoneId(const TQString &timeZoneId)
int asSeconds() const
virtual TQDateTime dtEnd() const
bool hasEndDate() const
bool doesFloat() const
TQString uid() const
void setUid(const TQString &)
virtual TQDateTime dtStart() const
const Alarm::List & alarms() const
void setSummary(const TQString &summary)
TQDateTime created() const
bool doesRecur() const
TQString summary() const
Recurrence * recurrence() const
TQDateTime getNextDateTime(const TQDateTime &preDateTime) const
static void disconnect(TQObject *receiver, const char *member=0)
Disconnect from the timer signal.
Definition: startdaytimer.h:52
static void connect(TQObject *receiver, const char *member)
Connect to the timer signal.
Definition: startdaytimer.h:45
miscellaneous functions
the KAlarm application object
main application window