karm Library API Documentation

karmstorage.cpp

00001 /*
00002  *   This file only:
00003  *     Copyright (C) 2003  Mark Bucciarelli <mark@hubcapconsutling.com>
00004  *
00005  *   This program is free software; you can redistribute it and/or modify
00006  *   it under the terms of the GNU General Public License as published by
00007  *   the Free Software Foundation; either version 2 of the License, or
00008  *   (at your option) any later version.
00009  *
00010  *   This program is distributed in the hope that it will be useful,
00011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  *   GNU General Public License for more details.
00014  *
00015  *   You should have received a copy of the GNU General Public License along
00016  *   with this program; if not, write to the
00017  *      Free Software Foundation, Inc.
00018  *      59 Temple Place - Suite 330
00019  *      Boston, MA  02111-1307  USA.
00020  *
00021  */
00022 
00023 #include <sys/types.h>
00024 #include <sys/stat.h>
00025 #include <fcntl.h>
00026 #include <unistd.h>
00027 
00028 #include <cassert>
00029 
00030 #include <qfile.h>
00031 #include <qdict.h>
00032 #include <qdatetime.h>
00033 #include <qstringlist.h>
00034 
00035 #include "kapplication.h"       // kapp
00036 #include <kdebug.h>
00037 #include <kemailsettings.h>
00038 #include <klocale.h>            // i18n
00039 #include <taskview.h>
00040 
00041 #include "incidence.h"
00042 
00043 //#include <calendarlocal.h>
00044 //#include <journal.h>
00045 //#include <event.h>
00046 //#include <todo.h>
00047 
00048 #include "karmstorage.h"
00049 #include "preferences.h"
00050 #include "task.h"
00051 
00052 
00053 KarmStorage *KarmStorage::_instance = 0;
00054 
00055 KarmStorage *KarmStorage::instance()
00056 {
00057   if (_instance == 0) {
00058     _instance = new KarmStorage();
00059   }
00060   return _instance;
00061 }
00062 
00063 KarmStorage::KarmStorage() { }
00064 
00065 QString KarmStorage::load(TaskView* view, const Preferences* preferences)
00066 {
00067   // When I tried raising an exception from this method, the compiler
00068   // complained that exceptions are not allowed.  Not sure how apps
00069   // typically handle error conditions in KDE, but I'll return the error
00070   // as a string (empty is no error).  -- Mark, Aug 8, 2003
00071 
00072   // Use KDE_CXXFLAGS=$(USE_EXCEPTIONS) in Makefile.am if you want to use
00073   // exceptions (David Faure)
00074   //
00075   // Ok, put on the TODO list.  :)  (Mark B)
00076 
00077   QString err;
00078   KEMailSettings settings;
00079   int handle;
00080 
00081   // if same file, don't reload
00082   if (preferences->iCalFile() == _icalfile)
00083     return err;
00084 
00085   // If file doesn't exist, create a blank one.  This avoids an error dialog
00086   // that libkcal presents when asked to load a non-existent file.  We make it
00087   // user and group read/write, others read.  This is masked by the users
00088   // umask.  (See man creat)
00089   handle = open(QFile::encodeName(preferences->iCalFile()),
00090       O_CREAT|O_EXCL|O_WRONLY,
00091       S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
00092 
00093   if (handle != -1)
00094   {
00095     close(handle);
00096   }
00097 
00098   // Clear view and calendar from memory.
00099   view->clear();
00100   _calendar.close();
00101 
00102   // Load new file
00103   _icalfile = preferences->iCalFile();
00104   kdDebug() << "KarmStorage::load - loading " << _icalfile << endl;
00105   _calendar.setEmail( settings.getSetting( KEMailSettings::EmailAddress ) );
00106   _calendar.setOwner( settings.getSetting( KEMailSettings::RealName ) );
00107   if (!_calendar.load(_icalfile))
00108     err = i18n("Error loading file \"%1\"")
00109       .arg(_icalfile);
00110 
00111   // Build task view from iCal data
00112   if (!err)
00113   {
00114     KCal::Todo::List todoList;
00115     KCal::Todo::List::ConstIterator todo;
00116     QDict< Task > map;
00117 
00118     // Build dictionary to look up Task object from Todo uid.  Each task is a
00119     // QListViewItem, and is initially added with the view as the parent.
00120     todoList = _calendar.rawTodos();
00121     kdDebug() << "KarmStorage::load "
00122       << "rawTodo count (includes completed todos) ="
00123       << todoList.count() << endl;
00124     for( todo = todoList.begin(); todo != todoList.end(); ++todo )
00125     {
00126       // Initially, if a task was complete, it was removed from the view.
00127       // However, this increased the complexity of reporting on task history.
00128       //
00129       // For example, if a task is complete yet has time logged to it during
00130       // the date range specified on the history report, we have to figure out
00131       // how that task fits into the task hierarchy.  Currently, this
00132       // structure is held in memory by the structure in the list view.
00133       //
00134       // I considered creating a second tree that held the full structure of
00135       // all complete and incomplete tasks.  But this seemed to much of a
00136       // change with an impending beta release and a full todo list.
00137       //
00138       // Hence this "solution".  Include completed tasks, but mark them as
00139       // inactive in the view.
00140       //
00141       //if ((*todo)->isCompleted()) continue;
00142 
00143       Task* task = new Task(*todo, view);
00144       map.insert( (*todo)->uid(), task );
00145       view->setRootIsDecorated(true);
00146       if ((*todo)->isCompleted())
00147       {
00148         task->setEnabled(false);
00149         task->setOpen(false);
00150       }
00151       else
00152         task->setOpen(true);
00153 
00154     }
00155 
00156     // Load each task under it's parent task.
00157     for( todo = todoList.begin(); todo != todoList.end(); ++todo )
00158     {
00159       Task* task = map.find( (*todo)->uid() );
00160 
00161       // No relatedTo incident just means this is a top-level task.
00162       if ( (*todo)->relatedTo() )
00163       {
00164         Task* newParent = map.find( (*todo)->relatedToUid() );
00165 
00166         // Complete the loading but return a message
00167         if ( !newParent )
00168           err = i18n("Error loading \"%1\": could not find parent (uid=%2)")
00169             .arg(task->name())
00170             .arg((*todo)->relatedToUid());
00171 
00172         if (!err)
00173           task->move( newParent);
00174       }
00175     }
00176 
00177     kdDebug() << "KarmStorage::load - loaded " << view->count()
00178       << " tasks from " << _icalfile << endl;
00179   }
00180 
00181   return err;
00182 }
00183 
00184 void KarmStorage::save(TaskView* taskview)
00185 {
00186   QPtrStack< KCal::Todo > parents;
00187 
00188   for (Task* task=taskview->first_child(); task; task = task->nextSibling())
00189   {
00190     writeTaskAsTodo(task, 1, parents );
00191   }
00192 
00193   _calendar.save(_icalfile);
00194 
00195   kdDebug()
00196     << "KarmStorage::save : wrote "
00197     << taskview->count() << " tasks to " << _icalfile << endl;
00198 }
00199 
00200 void KarmStorage::writeTaskAsTodo(Task* task, const int level,
00201     QPtrStack< KCal::Todo >& parents )
00202 {
00203 
00204   KCal::Todo* todo;
00205 
00206   todo = _calendar.todo(task->uid());
00207   task->asTodo(todo);
00208 
00209   if ( !parents.isEmpty() )
00210     todo->setRelatedTo( parents.top() );
00211 
00212   parents.push( todo );
00213 
00214   for (Task* nextTask = task->firstChild(); nextTask;
00215       nextTask = nextTask->nextSibling() )
00216   {
00217     writeTaskAsTodo(nextTask, level+1, parents );
00218   }
00219 
00220   parents.pop();
00221 }
00222 
00223 bool KarmStorage::isEmpty()
00224 {
00225   KCal::Todo::List todoList;
00226 
00227   todoList = _calendar.rawTodos();
00228   return todoList.empty();
00229 }
00230 
00231 bool KarmStorage::isNewStorage(const Preferences* preferences) const
00232 {
00233   if (!_icalfile.isNull())
00234     return preferences->iCalFile() != _icalfile;
00235   else
00236     return false;
00237 }
00238 
00239 //----------------------------------------------------------------------------
00240 // Routines that handle legacy flat file format.
00241 // These only stored total and session times.
00242 //
00243 
00244 QString KarmStorage::loadFromFlatFile(TaskView* taskview,
00245     const QString& filename)
00246 {
00247   QString err;
00248 
00249   kdDebug()
00250     << "KarmStorage::loadFromFlatFile: " << filename << endl;
00251 
00252   QFile f(filename);
00253   if( !f.exists() )
00254     err = i18n("File \"%1\" not found.").arg(filename);
00255 
00256   if (!err)
00257   {
00258     if( !f.open( IO_ReadOnly ) )
00259       err = i18n("Could not open \"%1\".").arg(filename);
00260   }
00261 
00262   if (!err)
00263   {
00264 
00265     QString line;
00266 
00267     QPtrStack<Task> stack;
00268     Task *task;
00269 
00270     QTextStream stream(&f);
00271 
00272     while( !stream.atEnd() ) {
00273       // lukas: this breaks for non-latin1 chars!!!
00274       // if ( file.readLine( line, T_LINESIZE ) == 0 )
00275       //   break;
00276 
00277       line = stream.readLine();
00278       kdDebug() << "DEBUG: line: " << line << "\n";
00279 
00280       if (line.isNull())
00281         break;
00282 
00283       long minutes;
00284       int level;
00285       QString name;
00286       DesktopList desktopList;
00287       if (!parseLine(line, &minutes, &name, &level, &desktopList))
00288         continue;
00289 
00290       unsigned int stackLevel = stack.count();
00291       for (unsigned int i = level; i<=stackLevel ; i++) {
00292         stack.pop();
00293       }
00294 
00295       if (level == 1) {
00296         kdDebug() << "KarmStorage::loadFromFlatFile - toplevel task: "
00297           << name << " min: " << minutes << "\n";
00298         task = new Task(name, minutes, 0, desktopList, taskview);
00299         task->setUid(addTask(task, 0));
00300       }
00301       else {
00302         Task *parent = stack.top();
00303         kdDebug() << "KarmStorage::loadFromFlatFile - task: " << name
00304             << " min: " << minutes << " parent" << parent->name() << "\n";
00305         task = new Task(name, minutes, 0, desktopList, parent);
00306 
00307         task->setUid(addTask(task, parent));
00308 
00309         // Legacy File Format (!):
00310         parent->changeTimes(0, -minutes, false);
00311         taskview->setRootIsDecorated(true);
00312         parent->setOpen(true);
00313       }
00314       if (!task->uid().isNull())
00315         stack.push(task);
00316       else
00317         delete task;
00318     }
00319 
00320     f.close();
00321 
00322   }
00323 
00324   return err;
00325 }
00326 
00327 QString KarmStorage::loadFromFlatFileCumulative(TaskView* taskview,
00328     const QString& filename)
00329 {
00330   QString err = loadFromFlatFile(taskview, filename);
00331   if (!err)
00332   {
00333     for (Task* task = taskview->first_child(); task;
00334         task = task->nextSibling())
00335     {
00336       adjustFromLegacyFileFormat(task);
00337     }
00338   }
00339   return err;
00340 }
00341 
00342 bool KarmStorage::parseLine(QString line, long *time, QString *name,
00343     int *level, DesktopList* desktopList)
00344 {
00345   if (line.find('#') == 0) {
00346     // A comment line
00347     return false;
00348   }
00349 
00350   int index = line.find('\t');
00351   if (index == -1) {
00352     // This doesn't seem like a valid record
00353     return false;
00354   }
00355 
00356   QString levelStr = line.left(index);
00357   QString rest = line.remove(0,index+1);
00358 
00359   index = rest.find('\t');
00360   if (index == -1) {
00361     // This doesn't seem like a valid record
00362     return false;
00363   }
00364 
00365   QString timeStr = rest.left(index);
00366   rest = rest.remove(0,index+1);
00367 
00368   bool ok;
00369 
00370   index = rest.find('\t'); // check for optional desktops string
00371   if (index >= 0) {
00372     *name = rest.left(index);
00373     QString deskLine = rest.remove(0,index+1);
00374 
00375     // now transform the ds string (e.g. "3", or "1,4,5") into
00376     // an DesktopList
00377     QString ds;
00378     int d;
00379     int commaIdx = deskLine.find(',');
00380     while (commaIdx >= 0) {
00381       ds = deskLine.left(commaIdx);
00382       d = ds.toInt(&ok);
00383       if (!ok)
00384         return false;
00385 
00386       desktopList->push_back(d);
00387       deskLine.remove(0,commaIdx+1);
00388       commaIdx = deskLine.find(',');
00389     }
00390 
00391     d = deskLine.toInt(&ok);
00392 
00393     if (!ok)
00394       return false;
00395 
00396     desktopList->push_back(d);
00397   }
00398   else {
00399     *name = rest.remove(0,index+1);
00400   }
00401 
00402   *time = timeStr.toLong(&ok);
00403 
00404   if (!ok) {
00405     // the time field was not a number
00406     return false;
00407   }
00408   *level = levelStr.toInt(&ok);
00409   if (!ok) {
00410     // the time field was not a number
00411     return false;
00412   }
00413 
00414   return true;
00415 }
00416 
00417 void KarmStorage::adjustFromLegacyFileFormat(Task* task)
00418 {
00419   // unless the parent is the listView
00420   if ( task->parent() )
00421     task->parent()->changeTimes(-task->sessionTime(), -task->time(), false);
00422 
00423   // traverse depth first -
00424   // as soon as we're in a leaf, we'll substract it's time from the parent
00425   // then, while descending back we'll do the same for each node untill
00426   // we reach the root
00427   for ( Task* subtask = task->firstChild(); subtask;
00428       subtask = subtask->nextSibling() )
00429     adjustFromLegacyFileFormat(subtask);
00430 }
00431 
00432 //----------------------------------------------------------------------------
00433 // Routines that handle logging KArm history
00434 //
00435 
00436 //
00437 // public routines:
00438 //
00439 
00440 QString KarmStorage::addTask(const Task* task, const Task* parent)
00441 {
00442   KCal::Todo* todo;
00443   QString uid;
00444 
00445   todo = new KCal::Todo();
00446   if (_calendar.addTodo(todo))
00447   {
00448     task->asTodo(todo);
00449     if (parent)
00450       todo->setRelatedTo(_calendar.todo(parent->uid()));
00451     uid = todo->uid();
00452   }
00453 
00454   return uid;
00455 }
00456 
00457 bool KarmStorage::removeTask(Task* task)
00458 {
00459 
00460   // delete history
00461   KCal::Event::List eventList = _calendar.rawEvents();
00462   for(KCal::Event::List::iterator i = eventList.begin();
00463       i != eventList.end();
00464       ++i)
00465   {
00466     //kdDebug() << "KarmStorage::removeTask: "
00467     //  << (*i)->uid() << " - relatedToUid() "
00468     //  << (*i)->relatedToUid()
00469     //  << ", relatedTo() = " << (*i)->relatedTo() <<endl;
00470     if ( (*i)->relatedToUid() == task->uid()
00471         || ( (*i)->relatedTo()
00472             && (*i)->relatedTo()->uid() == task->uid()))
00473     {
00474       _calendar.deleteEvent(*i);
00475     }
00476   }
00477 
00478   // delete todo
00479   KCal::Todo *todo = _calendar.todo(task->uid());
00480   _calendar.deleteTodo(todo);
00481 
00482   // save entire file
00483   _calendar.save(_icalfile);
00484 
00485   return true;
00486 }
00487 
00488 void KarmStorage::addComment(const Task* task, const QString& comment)
00489 {
00490   KCal::Todo* todo;
00491 
00492   todo = _calendar.todo(task->uid());
00493 
00494   // Do this to avoid compiler warnings about comment not being used.  once we
00495   // transition to using the addComment method, we need this second param.
00496   QString s = comment;
00497 
00498   // Need to wait until my libkcal-comment patch is applied for this ...
00499   //todo->addComment(comment);
00500 
00501 
00502   // temporary
00503   todo->setDescription(task->comment());
00504 
00505   _calendar.save(_icalfile);
00506 }
00507 
00508 void KarmStorage::stopTimer(const Task* task)
00509 {
00510   long delta = task->startTime().secsTo(QDateTime::currentDateTime());
00511   changeTime(task, delta);
00512 }
00513 
00514 void KarmStorage::changeTime(const Task* task, const long deltaSeconds)
00515 {
00516 
00517   KCal::Event* e;
00518   QDateTime end;
00519 
00520   e = baseEvent(task);
00521 
00522   // Don't use duration, as ICalFormatImpl::writeIncidence never writes a
00523   // duration, even though it looks like it's used in event.cpp.
00524   end = task->startTime();
00525   if (deltaSeconds > 0)
00526     end = task->startTime().addSecs(deltaSeconds);
00527   e->setDtEnd(end);
00528 
00529   // Use a custom property to keep a record of negative durations
00530   e->setCustomProperty( kapp->instanceName(),
00531       QCString("duration"),
00532       QString::number(deltaSeconds));
00533 
00534   _calendar.addEvent(e);
00535 
00536   // This saves the entire iCal file each time, which isn't efficient but
00537   // ensures no data loss.  A faster implementation would be to append events
00538   // to a file, and then when KArm closes, append the data in this file to the
00539   // iCal file.
00540   //_calendar.save(_icalfile);
00541   // Meanwhile, we simply use a timer to delay the full-saving until the GUI
00542   // has updated, for better user feedback. Feel free to get rid of this if/when
00543   // implementing the faster saving (DF).
00544   task->taskView()->scheduleSave();
00545 }
00546 
00547 
00548 KCal::Event* KarmStorage::baseEvent(const Task * task)
00549 {
00550   KCal::Event* e;
00551   QStringList categories;
00552 
00553   e = new KCal::Event;
00554   e->setSummary(task->name());
00555 
00556   // Can't use setRelatedToUid()--no error, but no RelatedTo written to disk
00557   e->setRelatedTo(_calendar.todo(task->uid()));
00558 
00559   // Debugging: some events where not getting a related-to field written.
00560   assert(e->relatedTo()->uid() == task->uid());
00561 
00562   // Have to turn this off to get datetimes in date fields.
00563   e->setFloats(false);
00564   e->setDtStart(task->startTime());
00565 
00566   // So someone can filter this mess out of their calendar display
00567   categories.append(i18n("KArm"));
00568   e->setCategories(categories);
00569 
00570   return e;
00571 }
00572 
00573 HistoryEvent::HistoryEvent(QString uid, QString name, long duration,
00574         QDateTime start, QDateTime stop, QString todoUid)
00575 {
00576   _uid = uid;
00577   _name = name;
00578   _duration = duration;
00579   _start = start;
00580   _stop = stop;
00581   _todoUid = todoUid;
00582 }
00583 
00584 
00585 QValueList<HistoryEvent> KarmStorage::getHistory(const QDate& from,
00586     const QDate& to)
00587 {
00588   QValueList<HistoryEvent> retval;
00589   QStringList processed;
00590   KCal::Event::List events;
00591   KCal::Event::List::iterator event;
00592   QString duration;
00593 
00594   for(QDate d = from; d <= to; d = d.addDays(1))
00595   {
00596     events = _calendar.rawEventsForDate(d);
00597     for (event = events.begin(); event != events.end(); ++event)
00598     {
00599 
00600       // KArm events have the custom property X-KDE-Karm-duration
00601       if (! processed.contains( (*event)->uid()))
00602       {
00603         // If an event spans multiple days, CalendarLocal::rawEventsForDate
00604         // will return the same event on both days.  To avoid double-counting
00605         // such events, we (arbitrarily) attribute the hours from both days on
00606         // the first day.  This mis-reports the actual time spent, but it is
00607         // an easy fix for a (hopefully) rare situation.
00608         processed.append( (*event)->uid());
00609 
00610         duration = (*event)->customProperty(kapp->instanceName(),
00611             QCString("duration"));
00612         if ( ! duration.isNull() )
00613         {
00614           if ( (*event)->relatedTo()
00615               &&  ! (*event)->relatedTo()->uid().isEmpty() )
00616           {
00617             retval.append(HistoryEvent(
00618                 (*event)->uid(),
00619                 (*event)->summary(),
00620                 duration.toLong(),
00621                 (*event)->dtStart(),
00622                 (*event)->dtEnd(),
00623                 (*event)->relatedTo()->uid()
00624                 ));
00625           }
00626           else
00627             // Something is screwy with the ics file, as this KArm history event
00628             // does not have a todo related to it.  Could have been deleted
00629             // manually?  We'll continue with report on with report ...
00630             kdDebug() << "KarmStorage::getHistory(): "
00631               << "The event " << (*event)->uid()
00632               << " is not related to a todo.  Dropped." << endl;
00633         }
00634       }
00635     }
00636   }
00637 
00638   return retval;
00639 }
00640 
00641 /*
00642  * Obsolete methods for writing to flat file format.
00643  * Aug 8, 2003, Mark
00644  *
00645 void KarmStorage::saveToFileFormat()
00646 {
00647   //QFile f(_preferences->saveFile());
00648   QFile f(_preferences->flatFile());
00649 
00650   if ( !f.open( IO_WriteOnly | IO_Truncate ) ) {
00651     QString msg = i18n( "There was an error trying to save your data file.\n"
00652                        "Time accumulated during this session will not be saved!\n");
00653     KMessageBox::error(0, msg );
00654     return;
00655   }
00656   const char * comment = "# TaskView save data\n";
00657 
00658   f.writeBlock(comment, strlen(comment));  //comment
00659   f.flush();
00660 
00661   QTextStream stream(&f);
00662   for (Task* child = firstChild();
00663              child;
00664              child = child->nextSibling())
00665     writeTaskToFile(&stream, child, 1);
00666 
00667   f.close();
00668   kdDebug() << "Saved data to file " << f.name() << endl;
00669 }
00670 void KarmStorage::writeTaskToFile( QTextStream *strm, Task *task,
00671                                 int level)
00672 {
00673   //lukas: correct version for non-latin1 users
00674   QString _line = QString::fromLatin1("%1\t%2\t%3").arg(level).
00675           arg(task->time()).arg(task->name());
00676 
00677   DesktopList d = task->getDesktops();
00678   int dsize = d.size();
00679   if (dsize>0) {
00680     _line += '\t';
00681     for (int i=0; i<dsize-1; i++) {
00682       _line += QString::number(d[i]);
00683       _line += ',';
00684     }
00685     _line += QString::number(d[dsize-1]);
00686   }
00687   *strm << _line << "\n";
00688 
00689   for ( Task* child= task->firstChild();
00690               child;
00691               child=child->nextSibling()) {
00692     writeTaskToFile(strm, child, level+1);
00693   }
00694 }
00695 
00696 */
KDE Logo
This file is part of the documentation for karm Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat May 1 11:37:52 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003