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, 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 */
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 }
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