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 QString AlarmCalendar::icalProductId()
00063 {
00064     return QString::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 QString calendarNames[AlarmCalendar::NCALS] = {
00071     QString::fromLatin1("calendar.ics"),
00072     QString::fromLatin1("expired.ics"),
00073     QString::fromLatin1("displaying.ics"),
00074     QString::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     KConfig* config = kapp->config();
00094     config->setGroup(QString::fromLatin1("General"));
00095     QString activeKey   = QString::fromLatin1("Calendar");
00096     QString expiredKey  = QString::fromLatin1("ExpiredCalendar");
00097     QString templateKey = QString::fromLatin1("TemplateCalendar");
00098     QString 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     QString 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         QString 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         QString 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, KConfig* config, QString& writePath, const QString& configKey)
00154 {
00155     static QRegExp vcsRegExp(QString::fromLatin1("\\.vcs$"));
00156     static QString ical = QString::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         QString 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 QString& 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 QString& path, CalID type, const QString& icalPath,
00232                              const QString& configKey)
00233     : mCalendar(0),
00234       mConfigKey(icalPath.isNull() ? QString::null : 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(QString::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 (!KIO::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     QString tmpFile;
00321     if (!KIO::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(QString::null);   // 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         KIO::NetAccess::removeTempFile(tmpFile);
00335         KIO::UDSEntry uds;
00336         KIO::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         KIO::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 QString& newFile)
00375 {
00376     if (!mCalendar  ||  !mOpen && newFile.isNull())
00377         return false;
00378 
00379     kdDebug(5950) << "AlarmCalendar::saveCal(\"" << newFile << "\", " << mType << ")\n";
00380     QString 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 (!KIO::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             KConfig* config = kapp->config();
00407             config->setGroup(QString::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         KIO::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(QWidget* parent)
00447 {
00448     KURL url = KFileDialog::getOpenURL(QString::fromLatin1(":importalarms"),
00449                                        QString::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     QString filename;
00464     bool local = url.isLocalFile();
00465     if (local)
00466     {
00467         filename = url.path();
00468         if (!KStandardDirs::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 (!KIO::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(QString::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())
00510                 continue;    // ignore events without 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(QString::null);   // 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         KIO::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             KConfig* config = kapp->config();
00619             config->setGroup(QString::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             QDate cutoff = QDate::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, 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     QString 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 QString& 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 QString& uniqueID)
00819 {
00820     return mCalendar ?  mCalendar->event(uniqueID) : 0;
00821 }
00822 
00823 /******************************************************************************
00824 * Return all events in the calendar which contain 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         if ((*it)->alarms().isEmpty())
00835             it = list.remove(it);
00836         else
00837             ++it;
00838     }
00839     return list;
00840 }
00841 
00842 /******************************************************************************
00843 * Return all events which have alarms falling within the specified time range.
00844 */
00845 Event::List AlarmCalendar::eventsWithAlarms(const QDateTime& from, const QDateTime& to)
00846 {
00847     kdDebug(5950) << "AlarmCalendar::eventsWithAlarms(" << from.toString() << " - " << to.toString() << ")\n";
00848     Event::List evnts;
00849     if (!mCalendar)
00850         return evnts;
00851     QDateTime dt;
00852     Event::List allEvents = mCalendar->rawEvents();
00853     for (Event::List::ConstIterator it = allEvents.begin();  it != allEvents.end();  ++it)
00854     {
00855         Event* e = *it;
00856         bool recurs = e->doesRecur();
00857         int  endOffset = 0;
00858         bool endOffsetValid = false;
00859         const Alarm::List& alarms = e->alarms();
00860         for (Alarm::List::ConstIterator ait = alarms.begin();  ait != alarms.end();  ++ait)
00861         {
00862             Alarm* alarm = *ait;
00863             if (alarm->enabled())
00864             {
00865                 if (recurs)
00866                 {
00867                     if (alarm->hasTime())
00868                         dt = alarm->time();
00869                     else
00870                     {
00871                         // The alarm time is defined by an offset from the event start or end time.
00872                         // Find the offset from the event start time, which is also used as the
00873                         // offset from the recurrence time.
00874                         int offset = 0;
00875                         if (alarm->hasStartOffset())
00876                             offset = alarm->startOffset().asSeconds();
00877                         else if (alarm->hasEndOffset())
00878                         {
00879                             if (!endOffsetValid)
00880                             {
00881                                 endOffset = e->hasDuration() ? e->duration() : e->hasEndDate() ? e->dtStart().secsTo(e->dtEnd()) : 0;
00882                                 endOffsetValid = true;
00883                             }
00884                             offset = alarm->endOffset().asSeconds() + endOffset;
00885                         }
00886                         // Adjust the 'from' date/time and find the next recurrence at or after it
00887                         QDateTime pre = from.addSecs(-offset - 1);
00888                         if (e->doesFloat()  &&  pre.time() < Preferences::startOfDay())
00889                             pre = pre.addDays(-1);    // today's recurrence (if today recurs) is still to come
00890                         dt = e->recurrence()->getNextDateTime(pre);
00891                         if (!dt.isValid())
00892                             continue;
00893                         dt = dt.addSecs(offset);
00894                     }
00895                 }
00896                 else
00897                     dt = alarm->time();
00898                 if (dt >= from  &&  dt <= to)
00899                 {
00900                     kdDebug(5950) << "AlarmCalendar::events() '" << e->summary()
00901                                   << "': " << dt.toString() << endl;
00902                     evnts.append(e);
00903                     break;
00904                 }
00905             }
00906         }
00907     }
00908     return evnts;
00909 }
KDE Home | KDE Accessibility Home | Description of Access Keys