kalarm

alarmcalendar.cpp

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