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 <kstandarddirs.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 
43 extern "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 
60 using namespace KCal;
61 
62 TQString AlarmCalendar::icalProductId()
63 {
64  return TQString::fromLatin1("-//K Desktop Environment//NONSGML " KALARM_NAME " %1//EN").arg(KAlarm::currentCalendarVersionString());
65 }
66 
67 static const KAEvent::Status eventTypes[AlarmCalendar::NCALS] = {
68  KAEvent::ACTIVE, KAEvent::EXPIRED, KAEvent::DISPLAYING, KAEvent::TEMPLATE
69 };
70 static 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 };
76 static KStaticDeleter<AlarmCalendar> calendarDeleter[AlarmCalendar::NCALS]; // ensure that the calendar destructors are called
77 
78 AlarmCalendar* 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 */
91 bool AlarmCalendar::initialiseCalendars()
92 {
93  TDEConfig* config = kapp->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 */
153 AlarmCalendar* 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 */
175 void 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 */
189 AlarmCalendar* 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 */
204 const 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 */
231 AlarmCalendar::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 
247 AlarmCalendar::~AlarmCalendar()
248 {
249  close();
250 }
251 
252 /******************************************************************************
253 * Open the calendar file if not already open, and load it into memory.
254 */
255 bool 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 */
296 bool 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 */
314 int 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 */
360 bool 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 */
374 bool 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 = kapp->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 */
423 void 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 */
446 bool 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 */
572 void 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 */
581 bool 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 */
596 bool 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 */
612 void AlarmCalendar::convertToICal()
613 {
614  if (mVCal)
615  {
616  if (!mConfigKey.isNull())
617  {
618  TDEConfig* config = kapp->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 */
633 void 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 */
656 void 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 */
666 void 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 */
683 void 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 */
717 void AlarmCalendar::startPurgeTimer()
718 {
719  if (mPurgeDays > 0)
720  StartOfDayTimer::connect(this, TQT_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 */
730 Event* 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 */
765 void 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 */
787 bool 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 */
810 void AlarmCalendar::emitEmptyStatus()
811 {
812  emit emptyStatus(events().isEmpty());
813 }
814 
815 /******************************************************************************
816 * Return the event with the specified ID.
817 */
818 KCal::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 */
826 KCal::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 */
846 Event::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 }
bool save(const TQString &fileName, CalFormat *format=0)
void setUid(const TQString &)
TQDateTime getNextDateTime(const TQDateTime &preDateTime) const
bool doesRecur() const
bool load(const TQString &fileName, CalFormat *format=0)
bool doesFloat() const
Status
The category of an event, indicated by the middle part of its UID.
Definition: alarmevent.h:269
Event::List rawEvents(EventSortField sortField=EventSortUnsorted, SortDirection sortDirection=SortDirectionAscending)
static void disconnect(TQObject *receiver, const char *member=0)
Disconnect from the timer signal.
Definition: startdaytimer.h:52
KAEvent corresponds to a KCal::Event instance.
Definition: alarmevent.h:231
void setSummary(const TQString &summary)
TQDateTime created() const
static void connect(TQObject *receiver, const char *member)
Connect to the timer signal.
Definition: startdaytimer.h:45
virtual TQDateTime dtEnd() const
int asSeconds() const
virtual TQDateTime dtStart() const
the KAlarm application object
bool addEvent(Event *event)
void setLocalTime()
bool hasTime() const
TQString summary() const
TQDateTime time() const
bool hasEndDate() const
main application window
TQString text() const
bool hasStartOffset() const
TQString uid() const
bool hasEndOffset() const
bool deleteEvent(Event *event)
Duration endOffset() const
Duration startOffset() const
const Alarm::List & alarms() const
miscellaneous functions
bool enabled() const
Type type() const
Recurrence * recurrence() const
void setTimeZoneId(const TQString &timeZoneId)
Provides read and write access to calendar files.
Definition: alarmcalendar.h:36
Event * event(const TQString &uid)
void setText(const TQString &text)