alarmcalendar.cpp
00001 /* 00002 * alarmcalendar.cpp - KAlarm calendar file access 00003 * Program: kalarm 00004 * Copyright © 2001-2006,2009 by David Jarvie <djarvie@kde.org> 00005 * 00006 * This program is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License along 00017 * with this program; if not, write to the Free Software Foundation, Inc., 00018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kalarm.h" 00022 #include <unistd.h> 00023 #include <time.h> 00024 00025 #include <tqfile.h> 00026 #include <tqtextstream.h> 00027 #include <tqregexp.h> 00028 #include <tqtimer.h> 00029 00030 #include <tdelocale.h> 00031 #include <tdemessagebox.h> 00032 #include <kstandarddirs.h> 00033 #include <kstaticdeleter.h> 00034 #include <tdeconfig.h> 00035 #include <tdeaboutdata.h> 00036 #include <tdeio/netaccess.h> 00037 #include <tdefileitem.h> 00038 #include <tdetempfile.h> 00039 #include <tdefiledialog.h> 00040 #include <dcopclient.h> 00041 #include <kdebug.h> 00042 00043 extern "C" { 00044 #include <libical/ical.h> 00045 } 00046 00047 #include <libkcal/vcaldrag.h> 00048 #include <libkcal/vcalformat.h> 00049 #include <libkcal/icalformat.h> 00050 00051 #include "calendarcompat.h" 00052 #include "daemon.h" 00053 #include "functions.h" 00054 #include "kalarmapp.h" 00055 #include "mainwindow.h" 00056 #include "preferences.h" 00057 #include "startdaytimer.h" 00058 #include "alarmcalendar.moc" 00059 00060 using namespace KCal; 00061 00062 TQString AlarmCalendar::icalProductId() 00063 { 00064 return TQString::fromLatin1("-//K Desktop Environment//NONSGML " KALARM_NAME " %1//EN").arg(KAlarm::currentCalendarVersionString()); 00065 } 00066 00067 static const KAEvent::Status eventTypes[AlarmCalendar::NCALS] = { 00068 KAEvent::ACTIVE, KAEvent::EXPIRED, KAEvent::DISPLAYING, KAEvent::TEMPLATE 00069 }; 00070 static const TQString calendarNames[AlarmCalendar::NCALS] = { 00071 TQString::fromLatin1("calendar.ics"), 00072 TQString::fromLatin1("expired.ics"), 00073 TQString::fromLatin1("displaying.ics"), 00074 TQString::fromLatin1("template.ics") 00075 }; 00076 static KStaticDeleter<AlarmCalendar> calendarDeleter[AlarmCalendar::NCALS]; // ensure that the calendar destructors are called 00077 00078 AlarmCalendar* AlarmCalendar::mCalendars[NCALS] = { 0, 0, 0, 0 }; 00079 00080 00081 /****************************************************************************** 00082 * Initialise the alarm calendars, and ensure that their file names are different. 00083 * There are 4 calendars: 00084 * 1) A user-independent one containing the active alarms; 00085 * 2) A historical one containing expired alarms; 00086 * 3) A user-specific one which contains details of alarms which are currently 00087 * being displayed to that user and which have not yet been acknowledged; 00088 * 4) One containing alarm templates. 00089 * Reply = true if success, false if calendar name error. 00090 */ 00091 bool AlarmCalendar::initialiseCalendars() 00092 { 00093 TDEConfig* config = kapp->config(); 00094 config->setGroup(TQString::fromLatin1("General")); 00095 TQString activeKey = TQString::fromLatin1("Calendar"); 00096 TQString expiredKey = TQString::fromLatin1("ExpiredCalendar"); 00097 TQString templateKey = TQString::fromLatin1("TemplateCalendar"); 00098 TQString displayCal, activeCal, expiredCal, templateCal; 00099 calendarDeleter[ACTIVE].setObject(mCalendars[ACTIVE], createCalendar(ACTIVE, config, activeCal, activeKey)); 00100 calendarDeleter[EXPIRED].setObject(mCalendars[EXPIRED], createCalendar(EXPIRED, config, expiredCal, expiredKey)); 00101 calendarDeleter[DISPLAY].setObject(mCalendars[DISPLAY], createCalendar(DISPLAY, config, displayCal)); 00102 calendarDeleter[TEMPLATE].setObject(mCalendars[TEMPLATE], createCalendar(TEMPLATE, config, templateCal, templateKey)); 00103 00104 TQString errorKey1, errorKey2; 00105 if (activeCal == displayCal) 00106 errorKey1 = activeKey; 00107 else if (expiredCal == displayCal) 00108 errorKey1 = expiredKey; 00109 else if (templateCal == displayCal) 00110 errorKey1 = templateKey; 00111 if (!errorKey1.isNull()) 00112 { 00113 kdError(5950) << "AlarmCalendar::initialiseCalendars(): '" << errorKey1 << "' calendar name = display calendar name\n"; 00114 TQString file = config->readPathEntry(errorKey1); 00115 KAlarmApp::displayFatalError(i18n("%1: file name not permitted: %2").arg(errorKey1).arg(file)); 00116 return false; 00117 } 00118 if (activeCal == expiredCal) 00119 { 00120 errorKey1 = activeKey; 00121 errorKey2 = expiredKey; 00122 } 00123 else if (activeCal == templateCal) 00124 { 00125 errorKey1 = activeKey; 00126 errorKey2 = templateKey; 00127 } 00128 else if (expiredCal == templateCal) 00129 { 00130 errorKey1 = expiredKey; 00131 errorKey2 = templateKey; 00132 } 00133 if (!errorKey1.isNull()) 00134 { 00135 kdError(5950) << "AlarmCalendar::initialiseCalendars(): calendar names clash: " << errorKey1 << ", " << errorKey2 << endl; 00136 KAlarmApp::displayFatalError(i18n("%1, %2: file names must be different").arg(errorKey1).arg(errorKey2)); 00137 return false; 00138 } 00139 if (!mCalendars[ACTIVE]->valid()) 00140 { 00141 TQString path = mCalendars[ACTIVE]->path(); 00142 kdError(5950) << "AlarmCalendar::initialiseCalendars(): invalid name: " << path << endl; 00143 KAlarmApp::displayFatalError(i18n("Invalid calendar file name: %1").arg(path)); 00144 return false; 00145 } 00146 return true; 00147 } 00148 00149 /****************************************************************************** 00150 * Create an alarm calendar instance. 00151 * If 'configKey' is non-null, the calendar will be converted to ICal format. 00152 */ 00153 AlarmCalendar* AlarmCalendar::createCalendar(CalID type, TDEConfig* config, TQString& writePath, const TQString& configKey) 00154 { 00155 static TQRegExp vcsRegExp(TQString::fromLatin1("\\.vcs$")); 00156 static TQString ical = TQString::fromLatin1(".ics"); 00157 00158 if (configKey.isNull()) 00159 { 00160 writePath = locateLocal("appdata", calendarNames[type]); 00161 return new AlarmCalendar(writePath, type); 00162 } 00163 else 00164 { 00165 TQString readPath = config->readPathEntry(configKey, locateLocal("appdata", calendarNames[type])); 00166 writePath = readPath; 00167 writePath.replace(vcsRegExp, ical); 00168 return new AlarmCalendar(readPath, type, writePath, configKey); 00169 } 00170 } 00171 00172 /****************************************************************************** 00173 * Terminate access to all calendars. 00174 */ 00175 void AlarmCalendar::terminateCalendars() 00176 { 00177 for (int i = 0; i < NCALS; ++i) 00178 { 00179 calendarDeleter[i].destructObject(); 00180 mCalendars[i] = 0; 00181 } 00182 } 00183 00184 /****************************************************************************** 00185 * Return a calendar, opening it first if not already open. 00186 * Reply = calendar instance 00187 * = 0 if calendar could not be opened. 00188 */ 00189 AlarmCalendar* AlarmCalendar::calendarOpen(CalID id) 00190 { 00191 AlarmCalendar* cal = mCalendars[id]; 00192 if (!cal->mPurgeDays) 00193 return 0; // all events are automatically purged from the calendar 00194 if (cal->open()) 00195 return cal; 00196 kdError(5950) << "AlarmCalendar::calendarOpen(" << calendarNames[id] << "): open error\n"; 00197 return 0; 00198 } 00199 00200 /****************************************************************************** 00201 * Find and return the event with the specified ID. 00202 * The calendar searched is determined by the calendar identifier in the ID. 00203 */ 00204 const KCal::Event* AlarmCalendar::getEvent(const TQString& uniqueID) 00205 { 00206 if (uniqueID.isEmpty()) 00207 return 0; 00208 CalID calID; 00209 switch (KAEvent::uidStatus(uniqueID)) 00210 { 00211 case KAEvent::ACTIVE: calID = ACTIVE; break; 00212 case KAEvent::TEMPLATE: calID = TEMPLATE; break; 00213 case KAEvent::EXPIRED: calID = EXPIRED; break; 00214 case KAEvent::DISPLAYING: calID = DISPLAY; break; 00215 default: 00216 return 0; 00217 } 00218 AlarmCalendar* cal = calendarOpen(calID); 00219 if (!cal) 00220 return 0; 00221 return cal->event(uniqueID); 00222 } 00223 00224 00225 /****************************************************************************** 00226 * Constructor. 00227 * If 'icalPath' is non-null, the file will be always be saved in ICal format. 00228 * If 'configKey' is also non-null, that config file entry will be updated when 00229 * the file is saved in ICal format. 00230 */ 00231 AlarmCalendar::AlarmCalendar(const TQString& path, CalID type, const TQString& icalPath, 00232 const TQString& configKey) 00233 : mCalendar(0), 00234 mConfigKey(icalPath.isNull() ? TQString() : configKey), 00235 mType(eventTypes[type]), 00236 mPurgeDays(-1), // default to not purging 00237 mOpen(false), 00238 mPurgeDaysQueued(-1), 00239 mUpdateCount(0), 00240 mUpdateSave(false) 00241 { 00242 mUrl.setPath(path); // N.B. constructor mUrl(path) doesn't work with UNIX paths 00243 mICalUrl.setPath(icalPath.isNull() ? path : icalPath); 00244 mVCal = (icalPath.isNull() || path != icalPath); // is the calendar in ICal or VCal format? 00245 } 00246 00247 AlarmCalendar::~AlarmCalendar() 00248 { 00249 close(); 00250 } 00251 00252 /****************************************************************************** 00253 * Open the calendar file if not already open, and load it into memory. 00254 */ 00255 bool AlarmCalendar::open() 00256 { 00257 if (mOpen) 00258 return true; 00259 if (!mUrl.isValid()) 00260 return false; 00261 00262 kdDebug(5950) << "AlarmCalendar::open(" << mUrl.prettyURL() << ")\n"; 00263 if (!mCalendar) 00264 mCalendar = new CalendarLocal(TQString::fromLatin1("UTC")); 00265 mCalendar->setLocalTime(); // write out using local time (i.e. no time zone) 00266 00267 // Check for file's existence, assuming that it does exist when uncertain, 00268 // to avoid overwriting it. 00269 if (!TDEIO::NetAccess::exists(mUrl, true, MainWindow::mainMainWindow())) 00270 { 00271 // The calendar file doesn't yet exist, so create it 00272 if (create()) 00273 load(); 00274 } 00275 else 00276 { 00277 // Load the existing calendar file 00278 if (load() == 0) 00279 { 00280 if (create()) // zero-length file - create a new one 00281 load(); 00282 } 00283 } 00284 if (!mOpen) 00285 { 00286 delete mCalendar; 00287 mCalendar = 0; 00288 } 00289 return mOpen; 00290 } 00291 00292 /****************************************************************************** 00293 * Private method to create a new calendar file. 00294 * It is always created in iCalendar format. 00295 */ 00296 bool AlarmCalendar::create() 00297 { 00298 if (mICalUrl.isLocalFile()) 00299 return saveCal(mICalUrl.path()); 00300 else 00301 { 00302 KTempFile tmpFile; 00303 return saveCal(tmpFile.name()); 00304 } 00305 } 00306 00307 /****************************************************************************** 00308 * Load the calendar file into memory. 00309 * Reply = 1 if success 00310 * = 0 if zero-length file exists. 00311 * = -1 if failure to load calendar file 00312 * = -2 if instance uninitialised. 00313 */ 00314 int AlarmCalendar::load() 00315 { 00316 if (!mCalendar) 00317 return -2; 00318 00319 kdDebug(5950) << "AlarmCalendar::load(): " << mUrl.prettyURL() << endl; 00320 TQString tmpFile; 00321 if (!TDEIO::NetAccess::download(mUrl, tmpFile, MainWindow::mainMainWindow())) 00322 { 00323 kdError(5950) << "AlarmCalendar::load(): Load failure" << endl; 00324 KMessageBox::error(0, i18n("Cannot open calendar:\n%1").arg(mUrl.prettyURL())); 00325 return -1; 00326 } 00327 kdDebug(5950) << "AlarmCalendar::load(): --- Downloaded to " << tmpFile << endl; 00328 mCalendar->setTimeZoneId(TQString()); // default to the local time zone for reading 00329 bool loaded = mCalendar->load(tmpFile); 00330 mCalendar->setLocalTime(); // write using local time (i.e. no time zone) 00331 if (!loaded) 00332 { 00333 // Check if the file is zero length 00334 TDEIO::NetAccess::removeTempFile(tmpFile); 00335 TDEIO::UDSEntry uds; 00336 TDEIO::NetAccess::stat(mUrl, uds, MainWindow::mainMainWindow()); 00337 KFileItem fi(uds, mUrl); 00338 if (!fi.size()) 00339 return 0; // file is zero length 00340 kdError(5950) << "AlarmCalendar::load(): Error loading calendar file '" << tmpFile << "'" << endl; 00341 KMessageBox::error(0, i18n("Error loading calendar:\n%1\n\nPlease fix or delete the file.").arg(mUrl.prettyURL())); 00342 // load() could have partially populated the calendar, so clear it out 00343 mCalendar->close(); 00344 delete mCalendar; 00345 mCalendar = 0; 00346 return -1; 00347 } 00348 if (!mLocalFile.isEmpty()) 00349 TDEIO::NetAccess::removeTempFile(mLocalFile); // removes it only if it IS a temporary file 00350 mLocalFile = tmpFile; 00351 00352 CalendarCompat::fix(*mCalendar, mLocalFile); // convert events to current KAlarm format for when calendar is saved 00353 mOpen = true; 00354 return 1; 00355 } 00356 00357 /****************************************************************************** 00358 * Reload the calendar file into memory. 00359 */ 00360 bool AlarmCalendar::reload() 00361 { 00362 if (!mCalendar) 00363 return false; 00364 kdDebug(5950) << "AlarmCalendar::reload(): " << mUrl.prettyURL() << endl; 00365 close(); 00366 bool result = open(); 00367 return result; 00368 } 00369 00370 /****************************************************************************** 00371 * Save the calendar from memory to file. 00372 * If a filename is specified, create a new calendar file. 00373 */ 00374 bool AlarmCalendar::saveCal(const TQString& newFile) 00375 { 00376 if (!mCalendar || (!mOpen && newFile.isNull())) 00377 return false; 00378 00379 kdDebug(5950) << "AlarmCalendar::saveCal(\"" << newFile << "\", " << mType << ")\n"; 00380 TQString saveFilename = newFile.isNull() ? mLocalFile : newFile; 00381 if (mVCal && newFile.isNull() && mUrl.isLocalFile()) 00382 saveFilename = mICalUrl.path(); 00383 if (!mCalendar->save(saveFilename, new ICalFormat)) 00384 { 00385 kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): failed.\n"; 00386 KMessageBox::error(0, i18n("Failed to save calendar to\n'%1'").arg(mICalUrl.prettyURL())); 00387 return false; 00388 } 00389 00390 if (!mICalUrl.isLocalFile()) 00391 { 00392 if (!TDEIO::NetAccess::upload(saveFilename, mICalUrl, MainWindow::mainMainWindow())) 00393 { 00394 kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): upload failed.\n"; 00395 KMessageBox::error(0, i18n("Cannot upload calendar to\n'%1'").arg(mICalUrl.prettyURL())); 00396 return false; 00397 } 00398 } 00399 00400 if (mVCal) 00401 { 00402 // The file was in vCalendar format, but has now been saved in iCalendar format. 00403 // Save the change in the config file. 00404 if (!mConfigKey.isNull()) 00405 { 00406 TDEConfig* config = kapp->config(); 00407 config->setGroup(TQString::fromLatin1("General")); 00408 config->writePathEntry(mConfigKey, mICalUrl.path()); 00409 config->sync(); 00410 } 00411 mUrl = mICalUrl; 00412 mVCal = false; 00413 } 00414 00415 mUpdateSave = false; 00416 emit calendarSaved(this); 00417 return true; 00418 } 00419 00420 /****************************************************************************** 00421 * Delete any temporary file at program exit. 00422 */ 00423 void AlarmCalendar::close() 00424 { 00425 if (!mLocalFile.isEmpty()) 00426 { 00427 TDEIO::NetAccess::removeTempFile(mLocalFile); // removes it only if it IS a temporary file 00428 mLocalFile = ""; 00429 } 00430 if (mCalendar) 00431 { 00432 mCalendar->close(); 00433 delete mCalendar; 00434 mCalendar = 0; 00435 } 00436 mOpen = false; 00437 } 00438 00439 /****************************************************************************** 00440 * Import alarms from an external calendar and merge them into KAlarm's calendar. 00441 * The alarms are given new unique event IDs. 00442 * Parameters: parent = parent widget for error message boxes 00443 * Reply = true if all alarms in the calendar were successfully imported 00444 * = false if any alarms failed to be imported. 00445 */ 00446 bool AlarmCalendar::importAlarms(TQWidget* parent) 00447 { 00448 KURL url = KFileDialog::getOpenURL(TQString::fromLatin1(":importalarms"), 00449 TQString::fromLatin1("*.vcs *.ics|%1").arg(i18n("Calendar Files")), parent); 00450 if (url.isEmpty()) 00451 { 00452 kdError(5950) << "AlarmCalendar::importAlarms(): Empty URL" << endl; 00453 return false; 00454 } 00455 if (!url.isValid()) 00456 { 00457 kdDebug(5950) << "AlarmCalendar::importAlarms(): Invalid URL" << endl; 00458 return false; 00459 } 00460 kdDebug(5950) << "AlarmCalendar::importAlarms(" << url.prettyURL() << ")" << endl; 00461 00462 bool success = true; 00463 TQString filename; 00464 bool local = url.isLocalFile(); 00465 if (local) 00466 { 00467 filename = url.path(); 00468 if (!TDEStandardDirs::exists(filename)) 00469 { 00470 kdDebug(5950) << "AlarmCalendar::importAlarms(): File '" << url.prettyURL() << "' not found" << endl; 00471 KMessageBox::error(parent, i18n("Could not load calendar '%1'.").arg(url.prettyURL())); 00472 return false; 00473 } 00474 } 00475 else 00476 { 00477 if (!TDEIO::NetAccess::download(url, filename, MainWindow::mainMainWindow())) 00478 { 00479 kdError(5950) << "AlarmCalendar::importAlarms(): Download failure" << endl; 00480 KMessageBox::error(parent, i18n("Cannot download calendar:\n%1").arg(url.prettyURL())); 00481 return false; 00482 } 00483 kdDebug(5950) << "--- Downloaded to " << filename << endl; 00484 } 00485 00486 // Read the calendar and add its alarms to the current calendars 00487 CalendarLocal cal(TQString::fromLatin1("UTC")); 00488 cal.setLocalTime(); // write out using local time (i.e. no time zone) 00489 success = cal.load(filename); 00490 if (!success) 00491 { 00492 kdDebug(5950) << "AlarmCalendar::importAlarms(): error loading calendar '" << filename << "'" << endl; 00493 KMessageBox::error(parent, i18n("Could not load calendar '%1'.").arg(url.prettyURL())); 00494 } 00495 else 00496 { 00497 CalendarCompat::fix(cal, filename); 00498 bool saveActive = false; 00499 bool saveExpired = false; 00500 bool saveTemplate = false; 00501 AlarmCalendar* active = activeCalendar(); 00502 AlarmCalendar* expired = expiredCalendar(); 00503 AlarmCalendar* templat = 0; 00504 AlarmCalendar* acal; 00505 Event::List events = cal.rawEvents(); 00506 for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) 00507 { 00508 const Event* event = *it; 00509 if (event->alarms().isEmpty() || !KAEvent(*event).valid()) 00510 continue; // ignore events without alarms, or usable alarms 00511 KAEvent::Status type = KAEvent::uidStatus(event->uid()); 00512 switch (type) 00513 { 00514 case KAEvent::ACTIVE: 00515 acal = active; 00516 saveActive = true; 00517 break; 00518 case KAEvent::EXPIRED: 00519 acal = expired; 00520 saveExpired = true; 00521 break; 00522 case KAEvent::TEMPLATE: 00523 if (!templat) 00524 templat = templateCalendarOpen(); 00525 acal = templat; 00526 saveTemplate = true; 00527 break; 00528 default: 00529 continue; 00530 } 00531 if (!acal) 00532 continue; 00533 00534 Event* newev = new Event(*event); 00535 00536 // If there is a display alarm without display text, use the event 00537 // summary text instead. 00538 if (type == KAEvent::ACTIVE && !newev->summary().isEmpty()) 00539 { 00540 const Alarm::List& alarms = newev->alarms(); 00541 for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait) 00542 { 00543 Alarm* alarm = *ait; 00544 if (alarm->type() == Alarm::Display && alarm->text().isEmpty()) 00545 alarm->setText(newev->summary()); 00546 } 00547 newev->setSummary(TQString()); // KAlarm only uses summary for template names 00548 } 00549 // Give the event a new ID and add it to the calendar 00550 newev->setUid(KAEvent::uid(CalFormat::createUniqueId(), type)); 00551 if (!acal->mCalendar->addEvent(newev)) 00552 success = false; 00553 } 00554 00555 // Save any calendars which have been modified 00556 if (saveActive) 00557 active->saveCal(); 00558 if (saveExpired) 00559 expired->saveCal(); 00560 if (saveTemplate) 00561 templat->saveCal(); 00562 } 00563 if (!local) 00564 TDEIO::NetAccess::removeTempFile(filename); 00565 return success; 00566 } 00567 00568 /****************************************************************************** 00569 * Flag the start of a group of calendar update calls. 00570 * The purpose is to avoid multiple calendar saves during a group of operations. 00571 */ 00572 void AlarmCalendar::startUpdate() 00573 { 00574 ++mUpdateCount; 00575 } 00576 00577 /****************************************************************************** 00578 * Flag the end of a group of calendar update calls. 00579 * The calendar is saved if appropriate. 00580 */ 00581 bool AlarmCalendar::endUpdate() 00582 { 00583 if (mUpdateCount > 0) 00584 --mUpdateCount; 00585 if (!mUpdateCount) 00586 { 00587 if (mUpdateSave) 00588 return saveCal(); 00589 } 00590 return true; 00591 } 00592 00593 /****************************************************************************** 00594 * Save the calendar, or flag it for saving if in a group of calendar update calls. 00595 */ 00596 bool AlarmCalendar::save() 00597 { 00598 if (mUpdateCount) 00599 { 00600 mUpdateSave = true; 00601 return true; 00602 } 00603 else 00604 return saveCal(); 00605 } 00606 00607 #if 0 00608 /****************************************************************************** 00609 * If it is VCal format, convert the calendar URL to ICal and save the new URL 00610 * in the config file. 00611 */ 00612 void AlarmCalendar::convertToICal() 00613 { 00614 if (mVCal) 00615 { 00616 if (!mConfigKey.isNull()) 00617 { 00618 TDEConfig* config = kapp->config(); 00619 config->setGroup(TQString::fromLatin1("General")); 00620 config->writePathEntry(mConfigKey, mICalUrl.path()); 00621 config->sync(); 00622 } 00623 mUrl = mICalUrl; 00624 mVCal = false; 00625 } 00626 } 00627 #endif 00628 00629 /****************************************************************************** 00630 * Set the number of days to keep alarms. 00631 * Alarms which are older are purged immediately, and at the start of each day. 00632 */ 00633 void AlarmCalendar::setPurgeDays(int days) 00634 { 00635 if (days != mPurgeDays) 00636 { 00637 int oldDays = mPurgeDays; 00638 mPurgeDays = days; 00639 if (mPurgeDays <= 0) 00640 StartOfDayTimer::disconnect(this); 00641 if (oldDays < 0 || (days >= 0 && days < oldDays)) 00642 { 00643 // Alarms are now being kept for less long, so purge them 00644 if (open()) 00645 slotPurge(); 00646 } 00647 else if (mPurgeDays > 0) 00648 startPurgeTimer(); 00649 } 00650 } 00651 00652 /****************************************************************************** 00653 * Called at the start of each day by the purge timer. 00654 * Purge all events from the calendar whose end time is longer ago than 'mPurgeDays'. 00655 */ 00656 void AlarmCalendar::slotPurge() 00657 { 00658 purge(mPurgeDays); 00659 startPurgeTimer(); 00660 } 00661 00662 /****************************************************************************** 00663 * Purge all events from the calendar whose end time is longer ago than 00664 * 'daysToKeep'. All events are deleted if 'daysToKeep' is zero. 00665 */ 00666 void AlarmCalendar::purge(int daysToKeep) 00667 { 00668 if (mPurgeDaysQueued < 0 || daysToKeep < mPurgeDaysQueued) 00669 mPurgeDaysQueued = daysToKeep; 00670 00671 // Do the purge once any other current operations are completed 00672 theApp()->processQueue(); 00673 } 00674 00675 /****************************************************************************** 00676 * This method must only be called from the main KAlarm queue processing loop, 00677 * to prevent asynchronous calendar operations interfering with one another. 00678 * 00679 * Purge all events from the calendar whose end time is longer ago than 'daysToKeep'. 00680 * All events are deleted if 'daysToKeep' is zero. 00681 * The calendar must already be open. 00682 */ 00683 void AlarmCalendar::purgeIfQueued() 00684 { 00685 if (mPurgeDaysQueued >= 0) 00686 { 00687 if (open()) 00688 { 00689 kdDebug(5950) << "AlarmCalendar::purgeIfQueued(" << mPurgeDaysQueued << ")\n"; 00690 bool changed = false; 00691 TQDate cutoff = TQDate::currentDate().addDays(-mPurgeDaysQueued); 00692 Event::List events = mCalendar->rawEvents(); 00693 for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) 00694 { 00695 Event* kcalEvent = *it; 00696 if (!mPurgeDaysQueued || kcalEvent->created().date() < cutoff) 00697 { 00698 mCalendar->deleteEvent(kcalEvent); 00699 changed = true; 00700 } 00701 } 00702 if (changed) 00703 { 00704 saveCal(); 00705 emit purged(); 00706 } 00707 mPurgeDaysQueued = -1; 00708 } 00709 } 00710 } 00711 00712 00713 /****************************************************************************** 00714 * Start the purge timer to expire at the start of the next day (using the user- 00715 * defined start-of-day time). 00716 */ 00717 void AlarmCalendar::startPurgeTimer() 00718 { 00719 if (mPurgeDays > 0) 00720 StartOfDayTimer::connect(this, TQT_SLOT(slotPurge())); 00721 } 00722 00723 /****************************************************************************** 00724 * Add the specified event to the calendar. 00725 * If it is the active calendar and 'useEventID' is false, a new event ID is 00726 * created. In all other cases, the event ID is taken from 'event'. 00727 * 'event' is updated with the actual event ID. 00728 * Reply = the KCal::Event as written to the calendar. 00729 */ 00730 Event* AlarmCalendar::addEvent(KAEvent& event, bool useEventID) 00731 { 00732 if (!mOpen) 00733 return 0; 00734 TQString id = event.id(); 00735 Event* kcalEvent = new Event; 00736 if (mType == KAEvent::ACTIVE) 00737 { 00738 if (id.isEmpty()) 00739 useEventID = false; 00740 if (!useEventID) 00741 event.setEventID(kcalEvent->uid()); 00742 } 00743 else 00744 { 00745 if (id.isEmpty()) 00746 id = kcalEvent->uid(); 00747 useEventID = true; 00748 } 00749 if (useEventID) 00750 { 00751 id = KAEvent::uid(id, mType); 00752 event.setEventID(id); 00753 kcalEvent->setUid(id); 00754 } 00755 event.updateKCalEvent(*kcalEvent, false, (mType == KAEvent::EXPIRED), true); 00756 mCalendar->addEvent(kcalEvent); 00757 event.clearUpdated(); 00758 return kcalEvent; 00759 } 00760 00761 /****************************************************************************** 00762 * Update the specified event in the calendar with its new contents. 00763 * The event retains the same ID. 00764 */ 00765 void AlarmCalendar::updateEvent(const KAEvent& evnt) 00766 { 00767 if (mOpen) 00768 { 00769 Event* kcalEvent = event(evnt.id()); 00770 if (kcalEvent) 00771 { 00772 evnt.updateKCalEvent(*kcalEvent); 00773 evnt.clearUpdated(); 00774 if (mType == KAEvent::ACTIVE) 00775 Daemon::savingEvent(evnt.id()); 00776 return; 00777 } 00778 } 00779 if (mType == KAEvent::ACTIVE) 00780 Daemon::eventHandled(evnt.id(), false); 00781 } 00782 00783 /****************************************************************************** 00784 * Delete the specified event from the calendar, if it exists. 00785 * The calendar is then optionally saved. 00786 */ 00787 bool AlarmCalendar::deleteEvent(const TQString& eventID, bool saveit) 00788 { 00789 if (mOpen) 00790 { 00791 Event* kcalEvent = event(eventID); 00792 if (kcalEvent) 00793 { 00794 mCalendar->deleteEvent(kcalEvent); 00795 if (mType == KAEvent::ACTIVE) 00796 Daemon::savingEvent(eventID); 00797 if (saveit) 00798 return save(); 00799 return true; 00800 } 00801 } 00802 if (mType == KAEvent::ACTIVE) 00803 Daemon::eventHandled(eventID, false); 00804 return false; 00805 } 00806 00807 /****************************************************************************** 00808 * Emit a signal to indicate whether the calendar is empty. 00809 */ 00810 void AlarmCalendar::emitEmptyStatus() 00811 { 00812 emit emptyStatus(events().isEmpty()); 00813 } 00814 00815 /****************************************************************************** 00816 * Return the event with the specified ID. 00817 */ 00818 KCal::Event* AlarmCalendar::event(const TQString& uniqueID) 00819 { 00820 return mCalendar ? mCalendar->event(uniqueID) : 0; 00821 } 00822 00823 /****************************************************************************** 00824 * Return all events in the calendar which contain usable alarms. 00825 */ 00826 KCal::Event::List AlarmCalendar::events() 00827 { 00828 if (!mCalendar) 00829 return KCal::Event::List(); 00830 KCal::Event::List list = mCalendar->rawEvents(); 00831 KCal::Event::List::Iterator it = list.begin(); 00832 while (it != list.end()) 00833 { 00834 KCal::Event* event = *it; 00835 if (event->alarms().isEmpty() || !KAEvent(*event).valid()) 00836 it = list.remove(it); 00837 else 00838 ++it; 00839 } 00840 return list; 00841 } 00842 00843 /****************************************************************************** 00844 * Return all events which have alarms falling within the specified time range. 00845 */ 00846 Event::List AlarmCalendar::eventsWithAlarms(const TQDateTime& from, const TQDateTime& to) 00847 { 00848 kdDebug(5950) << "AlarmCalendar::eventsWithAlarms(" << from.toString() << " - " << to.toString() << ")\n"; 00849 Event::List evnts; 00850 TQDateTime dt; 00851 Event::List allEvents = events(); // ignore events without usable alarms 00852 for (Event::List::ConstIterator it = allEvents.begin(); it != allEvents.end(); ++it) 00853 { 00854 Event* e = *it; 00855 bool recurs = e->doesRecur(); 00856 int endOffset = 0; 00857 bool endOffsetValid = false; 00858 const Alarm::List& alarms = e->alarms(); 00859 for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait) 00860 { 00861 Alarm* alarm = *ait; 00862 if (alarm->enabled()) 00863 { 00864 if (recurs) 00865 { 00866 if (alarm->hasTime()) 00867 dt = alarm->time(); 00868 else 00869 { 00870 // The alarm time is defined by an offset from the event start or end time. 00871 // Find the offset from the event start time, which is also used as the 00872 // offset from the recurrence time. 00873 int offset = 0; 00874 if (alarm->hasStartOffset()) 00875 offset = alarm->startOffset().asSeconds(); 00876 else if (alarm->hasEndOffset()) 00877 { 00878 if (!endOffsetValid) 00879 { 00880 endOffset = e->hasDuration() ? e->duration() : e->hasEndDate() ? e->dtStart().secsTo(e->dtEnd()) : 0; 00881 endOffsetValid = true; 00882 } 00883 offset = alarm->endOffset().asSeconds() + endOffset; 00884 } 00885 // Adjust the 'from' date/time and find the next recurrence at or after it 00886 TQDateTime pre = from.addSecs(-offset - 1); 00887 if (e->doesFloat() && pre.time() < Preferences::startOfDay()) 00888 pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come 00889 dt = e->recurrence()->getNextDateTime(pre); 00890 if (!dt.isValid()) 00891 continue; 00892 dt = dt.addSecs(offset); 00893 } 00894 } 00895 else 00896 dt = alarm->time(); 00897 if (dt >= from && dt <= to) 00898 { 00899 kdDebug(5950) << "AlarmCalendar::events() '" << e->summary() 00900 << "': " << dt.toString() << endl; 00901 evnts.append(e); 00902 break; 00903 } 00904 } 00905 } 00906 } 00907 return evnts; 00908 }