00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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 <dcopclient.h>
00040 #include <kdebug.h>
00041
00042 extern "C" {
00043 #include <libical/ical.h>
00044 }
00045
00046 #include <libkcal/vcaldrag.h>
00047 #include <libkcal/vcalformat.h>
00048 #include <libkcal/icalformat.h>
00049
00050 #include "calendarcompat.h"
00051 #include "kalarmapp.h"
00052 #include "mainwindow.h"
00053 #include "preferences.h"
00054 #include "startdaytimer.h"
00055 #include "alarmcalendar.moc"
00056
00057 using namespace KCal;
00058
00059 static const KAEvent::Status eventTypes[AlarmCalendar::NCALS] = {
00060 KAEvent::ACTIVE, KAEvent::EXPIRED, KAEvent::DISPLAYING, KAEvent::TEMPLATE
00061 };
00062 static const QString calendarNames[AlarmCalendar::NCALS] = {
00063 QString::fromLatin1("calendar.ics"),
00064 QString::fromLatin1("expired.ics"),
00065 QString::fromLatin1("displaying.ics"),
00066 QString::fromLatin1("template.ics")
00067 };
00068 static KStaticDeleter<AlarmCalendar> calendarDeleter[AlarmCalendar::NCALS];
00069
00070 AlarmCalendar* AlarmCalendar::mCalendars[NCALS] = { 0, 0, 0, 0 };
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083 bool AlarmCalendar::initialiseCalendars()
00084 {
00085 KConfig* config = kapp->config();
00086 config->setGroup(QString::fromLatin1("General"));
00087 QString activeKey = QString::fromLatin1("Calendar");
00088 QString expiredKey = QString::fromLatin1("ExpiredCalendar");
00089 QString templateKey = QString::fromLatin1("TemplateCalendar");
00090 QString displayCal, activeCal, expiredCal, templateCal;
00091 calendarDeleter[ACTIVE].setObject(mCalendars[ACTIVE], createCalendar(ACTIVE, config, activeCal, activeKey));
00092 calendarDeleter[EXPIRED].setObject(mCalendars[EXPIRED], createCalendar(EXPIRED, config, expiredCal, expiredKey));
00093 calendarDeleter[DISPLAY].setObject(mCalendars[DISPLAY], createCalendar(DISPLAY, config, displayCal));
00094 calendarDeleter[TEMPLATE].setObject(mCalendars[TEMPLATE], createCalendar(TEMPLATE, config, templateCal, templateKey));
00095
00096 QString errorKey1, errorKey2;
00097 if (activeCal == displayCal)
00098 errorKey1 = activeKey;
00099 else if (expiredCal == displayCal)
00100 errorKey1 = expiredKey;
00101 else if (templateCal == displayCal)
00102 errorKey1 = templateKey;
00103 if (!errorKey1.isNull())
00104 {
00105 kdError(5950) << "AlarmCalendar::initialiseCalendars(): '" << errorKey1 << "' calendar name = display calendar name\n";
00106 QString file = config->readPathEntry(errorKey1);
00107 KAlarmApp::displayFatalError(i18n("%1: file name not permitted: %2").arg(errorKey1).arg(file));
00108 return false;
00109 }
00110 if (activeCal == expiredCal)
00111 {
00112 errorKey1 = activeKey;
00113 errorKey2 = expiredKey;
00114 }
00115 else if (activeCal == templateCal)
00116 {
00117 errorKey1 = activeKey;
00118 errorKey2 = templateKey;
00119 }
00120 else if (expiredCal == templateCal)
00121 {
00122 errorKey1 = expiredKey;
00123 errorKey2 = templateKey;
00124 }
00125 if (!errorKey1.isNull())
00126 {
00127 kdError(5950) << "AlarmCalendar::initialiseCalendars(): calendar names clash: " << errorKey1 << ", " << errorKey2 << endl;
00128 KAlarmApp::displayFatalError(i18n("%1, %2: file names must be different").arg(errorKey1).arg(errorKey2));
00129 return false;
00130 }
00131 if (!mCalendars[ACTIVE]->valid())
00132 {
00133 QString path = mCalendars[ACTIVE]->path();
00134 kdError(5950) << "AlarmCalendar::initialiseCalendars(): invalid name: " << path << endl;
00135 KAlarmApp::displayFatalError(i18n("Invalid calendar file name: %1").arg(path));
00136 return false;
00137 }
00138 return true;
00139 }
00140
00141
00142
00143
00144
00145 AlarmCalendar* AlarmCalendar::createCalendar(CalID type, KConfig* config, QString& writePath, const QString& configKey)
00146 {
00147 static QRegExp vcsRegExp(QString::fromLatin1("\\.vcs$"));
00148 static QString ical = QString::fromLatin1(".ics");
00149
00150 if (configKey.isNull())
00151 {
00152 writePath = locateLocal("appdata", calendarNames[type]);
00153 return new AlarmCalendar(writePath, type);
00154 }
00155 else
00156 {
00157 QString readPath = config->readPathEntry(configKey, locateLocal("appdata", calendarNames[type]));
00158 writePath = readPath;
00159 writePath.replace(vcsRegExp, ical);
00160 return new AlarmCalendar(readPath, type, writePath, configKey);
00161 }
00162 }
00163
00164
00165
00166
00167 void AlarmCalendar::terminateCalendars()
00168 {
00169 for (int i = 0; i < NCALS; ++i)
00170 {
00171 calendarDeleter[i].destructObject();
00172 mCalendars[i] = 0;
00173 }
00174 }
00175
00176
00177
00178
00179
00180
00181 AlarmCalendar* AlarmCalendar::calendarOpen(CalID id)
00182 {
00183 AlarmCalendar* cal = mCalendars[id];
00184 if (!cal->mPurgeDays)
00185 return 0;
00186 if (cal->open())
00187 return cal;
00188 kdError(5950) << "AlarmCalendar::calendarOpen(" << calendarNames[id] << "): open error\n";
00189 return 0;
00190 }
00191
00192
00193
00194
00195
00196 const KCal::Event* AlarmCalendar::getEvent(const QString& uniqueID)
00197 {
00198 if (uniqueID.isEmpty())
00199 return 0;
00200 CalID calID;
00201 switch (KAEvent::uidStatus(uniqueID))
00202 {
00203 case KAEvent::ACTIVE: calID = ACTIVE; break;
00204 case KAEvent::TEMPLATE: calID = TEMPLATE; break;
00205 case KAEvent::EXPIRED: calID = EXPIRED; break;
00206 case KAEvent::DISPLAYING: calID = DISPLAY; break;
00207 default:
00208 return 0;
00209 }
00210 AlarmCalendar* cal = calendarOpen(calID);
00211 if (!cal)
00212 return 0;
00213 return cal->event(uniqueID);
00214 }
00215
00216
00217
00218
00219
00220
00221
00222
00223 AlarmCalendar::AlarmCalendar(const QString& path, CalID type, const QString& icalPath,
00224 const QString& configKey)
00225 : mCalendar(0),
00226 mConfigKey(icalPath.isNull() ? QString::null : configKey),
00227 mType(eventTypes[type]),
00228 mPurgeDays(-1),
00229 mOpen(false),
00230 mPurgeDaysQueued(-1),
00231 mUpdateCount(0),
00232 mUpdateSave(false)
00233 {
00234 mUrl.setPath(path);
00235 mICalUrl.setPath(icalPath.isNull() ? path : icalPath);
00236 mVCal = (icalPath.isNull() || path != icalPath);
00237 }
00238
00239 AlarmCalendar::~AlarmCalendar()
00240 {
00241 close();
00242 }
00243
00244
00245
00246
00247 bool AlarmCalendar::open()
00248 {
00249 if (mOpen)
00250 return true;
00251 if (!mUrl.isValid())
00252 return false;
00253
00254 kdDebug(5950) << "AlarmCalendar::open(" << mUrl.prettyURL() << ")\n";
00255 if (!mCalendar)
00256 mCalendar = new CalendarLocal(QString::fromLatin1("UTC"));
00257 mCalendar->setLocalTime();
00258
00259
00260
00261 if (!KIO::NetAccess::exists(mUrl, true, MainWindow::mainMainWindow()))
00262 {
00263
00264 if (create())
00265 load();
00266 }
00267 else
00268 {
00269
00270 if (load() == 0)
00271 {
00272 if (create())
00273 load();
00274 }
00275 }
00276 if (!mOpen)
00277 {
00278 delete mCalendar;
00279 mCalendar = 0;
00280 }
00281 return mOpen;
00282 }
00283
00284
00285
00286
00287
00288 bool AlarmCalendar::create()
00289 {
00290 if (mICalUrl.isLocalFile())
00291 return saveCal(mICalUrl.path());
00292 else
00293 {
00294 KTempFile tmpFile;
00295 return saveCal(tmpFile.name());
00296 }
00297 }
00298
00299
00300
00301
00302
00303
00304
00305
00306 int AlarmCalendar::load()
00307 {
00308 if (!mCalendar)
00309 return -2;
00310
00311 kdDebug(5950) << "AlarmCalendar::load(): " << mUrl.prettyURL() << endl;
00312 QString tmpFile;
00313 if (!KIO::NetAccess::download(mUrl, tmpFile, MainWindow::mainMainWindow()))
00314 {
00315 kdError(5950) << "AlarmCalendar::load(): Load failure" << endl;
00316 KMessageBox::error(0, i18n("Cannot open calendar:\n%1").arg(mUrl.prettyURL()));
00317 return -1;
00318 }
00319 kdDebug(5950) << "AlarmCalendar::load(): --- Downloaded to " << tmpFile << endl;
00320 mCalendar->setTimeZoneId(QString::null);
00321 bool loaded = mCalendar->load(tmpFile);
00322 mCalendar->setLocalTime();
00323 if (!loaded)
00324 {
00325
00326 KIO::NetAccess::removeTempFile(tmpFile);
00327 KIO::UDSEntry uds;
00328 KIO::NetAccess::stat(mUrl, uds, MainWindow::mainMainWindow());
00329 KFileItem fi(uds, mUrl);
00330 if (!fi.size())
00331 return 0;
00332 kdError(5950) << "AlarmCalendar::load(): Error loading calendar file '" << tmpFile << "'" << endl;
00333 KMessageBox::error(0, i18n("Error loading calendar:\n%1\n\nPlease fix or delete the file.").arg(mUrl.prettyURL()));
00334
00335 mCalendar->close();
00336 delete mCalendar;
00337 mCalendar = 0;
00338 return -1;
00339 }
00340 if (!mLocalFile.isEmpty())
00341 KIO::NetAccess::removeTempFile(mLocalFile);
00342 mLocalFile = tmpFile;
00343
00344 CalendarCompat::fix(*mCalendar, mLocalFile);
00345 mOpen = true;
00346 return 1;
00347 }
00348
00349
00350
00351
00352 bool AlarmCalendar::reload()
00353 {
00354 if (!mCalendar)
00355 return false;
00356 kdDebug(5950) << "AlarmCalendar::reload(): " << mUrl.prettyURL() << endl;
00357 close();
00358 bool result = open();
00359 return result;
00360 }
00361
00362
00363
00364
00365
00366 bool AlarmCalendar::saveCal(const QString& newFile)
00367 {
00368 if (!mCalendar || !mOpen && newFile.isNull())
00369 return false;
00370
00371 kdDebug(5950) << "AlarmCalendar::saveCal(\"" << newFile << "\", " << mType << ")\n";
00372 QString saveFilename = newFile.isNull() ? mLocalFile : newFile;
00373 if (mVCal && newFile.isNull() && mUrl.isLocalFile())
00374 saveFilename = mICalUrl.path();
00375 if (!mCalendar->save(saveFilename, new ICalFormat))
00376 {
00377 kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): failed.\n";
00378 KMessageBox::error(0, i18n("Failed to save calendar to\n'%1'").arg(mICalUrl.prettyURL()));
00379 return false;
00380 }
00381
00382 if (!mICalUrl.isLocalFile())
00383 {
00384 if (!KIO::NetAccess::upload(saveFilename, mICalUrl, MainWindow::mainMainWindow()))
00385 {
00386 kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): upload failed.\n";
00387 KMessageBox::error(0, i18n("Cannot upload calendar to\n'%1'").arg(mICalUrl.prettyURL()));
00388 return false;
00389 }
00390 }
00391
00392 if (mVCal)
00393 {
00394
00395
00396 if (!mConfigKey.isNull())
00397 {
00398 KConfig* config = kapp->config();
00399 config->setGroup(QString::fromLatin1("General"));
00400 config->writePathEntry(mConfigKey, mICalUrl.path());
00401 config->sync();
00402 }
00403 mUrl = mICalUrl;
00404 mVCal = false;
00405 }
00406
00407 mUpdateSave = false;
00408 emit calendarSaved(this);
00409 return true;
00410 }
00411
00412
00413
00414
00415 void AlarmCalendar::close()
00416 {
00417 if (!mLocalFile.isEmpty())
00418 {
00419 KIO::NetAccess::removeTempFile(mLocalFile);
00420 mLocalFile = "";
00421 }
00422 if (mCalendar)
00423 {
00424 mCalendar->close();
00425 delete mCalendar;
00426 mCalendar = 0;
00427 }
00428 mOpen = false;
00429 }
00430
00431
00432
00433
00434
00435 void AlarmCalendar::startUpdate()
00436 {
00437 ++mUpdateCount;
00438 }
00439
00440
00441
00442
00443
00444 void AlarmCalendar::endUpdate()
00445 {
00446 if (mUpdateCount > 0)
00447 --mUpdateCount;
00448 if (!mUpdateCount)
00449 {
00450 if (mUpdateSave)
00451 saveCal();
00452 }
00453 }
00454
00455
00456
00457
00458 void AlarmCalendar::save()
00459 {
00460 if (mUpdateCount)
00461 mUpdateSave = true;
00462 else
00463 saveCal();
00464 }
00465
00466 #if 0
00467
00468
00469
00470
00471 void AlarmCalendar::convertToICal()
00472 {
00473 if (mVCal)
00474 {
00475 if (!mConfigKey.isNull())
00476 {
00477 KConfig* config = kapp->config();
00478 config->setGroup(QString::fromLatin1("General"));
00479 config->writePathEntry(mConfigKey, mICalUrl.path());
00480 config->sync();
00481 }
00482 mUrl = mICalUrl;
00483 mVCal = false;
00484 }
00485 }
00486 #endif
00487
00488
00489
00490
00491
00492 void AlarmCalendar::setPurgeDays(int days)
00493 {
00494 if (days != mPurgeDays)
00495 {
00496 int oldDays = mPurgeDays;
00497 mPurgeDays = days;
00498 if (mPurgeDays <= 0)
00499 StartOfDayTimer::disconnect(this);
00500 if (oldDays < 0 || days >= 0 && days < oldDays)
00501 {
00502
00503 if (open())
00504 slotPurge();
00505 }
00506 else if (mPurgeDays > 0)
00507 startPurgeTimer();
00508 }
00509 }
00510
00511
00512
00513
00514
00515 void AlarmCalendar::slotPurge()
00516 {
00517 purge(mPurgeDays);
00518 startPurgeTimer();
00519 }
00520
00521
00522
00523
00524
00525 void AlarmCalendar::purge(int daysToKeep)
00526 {
00527 if (mPurgeDaysQueued < 0 || daysToKeep < mPurgeDaysQueued)
00528 mPurgeDaysQueued = daysToKeep;
00529
00530
00531 theApp()->processQueue();
00532 }
00533
00534
00535
00536
00537
00538
00539
00540
00541
00542 void AlarmCalendar::purgeIfQueued()
00543 {
00544 if (mPurgeDaysQueued >= 0)
00545 {
00546 if (open())
00547 {
00548 kdDebug(5950) << "AlarmCalendar::purgeIfQueued(" << mPurgeDaysQueued << ")\n";
00549 bool changed = false;
00550 QDate cutoff = QDate::currentDate().addDays(-mPurgeDaysQueued);
00551 Event::List events = mCalendar->rawEvents();
00552 for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
00553 {
00554 Event* kcalEvent = *it;
00555 if (!mPurgeDaysQueued || kcalEvent->created().date() < cutoff)
00556 {
00557 mCalendar->deleteEvent(kcalEvent);
00558 changed = true;
00559 }
00560 }
00561 if (changed)
00562 {
00563 saveCal();
00564 emit purged();
00565 }
00566 mPurgeDaysQueued = -1;
00567 }
00568 }
00569 }
00570
00571
00572
00573
00574
00575
00576 void AlarmCalendar::startPurgeTimer()
00577 {
00578 if (mPurgeDays > 0)
00579 StartOfDayTimer::connect(this, SLOT(slotPurge()));
00580 }
00581
00582
00583
00584
00585
00586
00587
00588
00589 Event* AlarmCalendar::addEvent(KAEvent& event, bool useEventID)
00590 {
00591 if (!mOpen)
00592 return 0;
00593 QString id = event.id();
00594 Event* kcalEvent = new Event;
00595 if (mType == KAEvent::ACTIVE)
00596 {
00597 if (id.isEmpty())
00598 useEventID = false;
00599 if (!useEventID)
00600 event.setEventID(kcalEvent->uid());
00601 }
00602 else
00603 {
00604 if (id.isEmpty())
00605 id = kcalEvent->uid();
00606 useEventID = true;
00607 }
00608 if (useEventID)
00609 {
00610 id = KAEvent::uid(id, mType);
00611 event.setEventID(id);
00612 kcalEvent->setUid(id);
00613 }
00614 event.updateKCalEvent(*kcalEvent, false, (mType == KAEvent::EXPIRED), true);
00615 mCalendar->addEvent(kcalEvent);
00616 event.clearUpdated();
00617 return kcalEvent;
00618 }
00619
00620
00621
00622
00623
00624 void AlarmCalendar::updateEvent(const KAEvent& evnt)
00625 {
00626 if (mOpen)
00627 {
00628 Event* kcalEvent = event(evnt.id());
00629 if (kcalEvent)
00630 {
00631 evnt.updateKCalEvent(*kcalEvent);
00632 evnt.clearUpdated();
00633 }
00634 }
00635 }
00636
00637
00638
00639
00640
00641 void AlarmCalendar::deleteEvent(const QString& eventID, bool saveit)
00642 {
00643 if (mOpen)
00644 {
00645 Event* kcalEvent = event(eventID);
00646 if (kcalEvent)
00647 {
00648 mCalendar->deleteEvent(kcalEvent);
00649 if (saveit)
00650 save();
00651 }
00652 }
00653 }
00654
00655
00656
00657
00658 void AlarmCalendar::emitEmptyStatus()
00659 {
00660 emit emptyStatus(events().isEmpty());
00661 }
00662
00663
00664
00665
00666 KCal::Event* AlarmCalendar::event(const QString& uniqueID)
00667 {
00668 return mCalendar ? mCalendar->event(uniqueID) : 0;
00669 }
00670
00671
00672
00673
00674 KCal::Event::List AlarmCalendar::events()
00675 {
00676 if (!mCalendar)
00677 return KCal::Event::List();
00678 KCal::Event::List list = mCalendar->rawEvents();
00679 KCal::Event::List::Iterator it = list.begin();
00680 while (it != list.end())
00681 {
00682 if ((*it)->alarms().isEmpty())
00683 it = list.remove(it);
00684 else
00685 ++it;
00686 }
00687 return list;
00688 }
00689
00690
00691
00692
00693 Event::List AlarmCalendar::eventsWithAlarms(const QDateTime& from, const QDateTime& to)
00694 {
00695 kdDebug(5950) << "AlarmCalendar::eventsWithAlarms(" << from.toString() << " - " << to.toString() << ")\n";
00696 Event::List evnts;
00697 if (!mCalendar)
00698 return evnts;
00699 QDateTime dt;
00700 Event::List allEvents = mCalendar->rawEvents();
00701 for (Event::List::ConstIterator it = allEvents.begin(); it != allEvents.end(); ++it)
00702 {
00703 Event* e = *it;
00704 bool recurs = e->doesRecur();
00705 int endOffset = 0;
00706 bool endOffsetValid = false;
00707 const Alarm::List& alarms = e->alarms();
00708 for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait)
00709 {
00710 Alarm* alarm = *ait;
00711 if (alarm->enabled())
00712 {
00713 if (recurs)
00714 {
00715 if (alarm->hasTime())
00716 dt = alarm->time();
00717 else
00718 {
00719
00720
00721
00722 int offset = 0;
00723 if (alarm->hasStartOffset())
00724 offset = alarm->startOffset().asSeconds();
00725 else if (alarm->hasEndOffset())
00726 {
00727 if (!endOffsetValid)
00728 {
00729 endOffset = e->hasDuration() ? e->duration() : e->hasEndDate() ? e->dtStart().secsTo(e->dtEnd()) : 0;
00730 endOffsetValid = true;
00731 }
00732 offset = alarm->endOffset().asSeconds() + endOffset;
00733 }
00734
00735 QDateTime pre = from.addSecs(-offset - 1);
00736 if (e->doesFloat() && pre.time() < Preferences::startOfDay())
00737 pre = pre.addDays(-1);
00738 dt = e->recurrence()->getNextDateTime(pre);
00739 if (!dt.isValid())
00740 continue;
00741 dt = dt.addSecs(offset);
00742 }
00743 }
00744 else
00745 dt = alarm->time();
00746 if (dt >= from && dt <= to)
00747 {
00748 kdDebug(5950) << "AlarmCalendar::events() '" << e->summary()
00749 << "': " << dt.toString() << endl;
00750 evnts.append(e);
00751 break;
00752 }
00753 }
00754 }
00755 }
00756 return evnts;
00757 }