karm Library API Documentation

karmstorage.cpp

00001 /*
00002  *   This file only:
00003  *     Copyright (C) 2003, 2004  Mark Bucciarelli <mark@hubcapconsulting.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 <qsize.h>
00032 #include <qdict.h>
00033 #include <qdatetime.h>
00034 #include <qstringlist.h>
00035 
00036 #include "incidence.h"
00037 #include "kapplication.h"       // kapp
00038 #include <kdebug.h>
00039 #include <kemailsettings.h>
00040 #include <klocale.h>            // i18n
00041 #include <kmessagebox.h>
00042 #include <kprogress.h>
00043 #include <resourcecalendar.h>
00044 #include <resourcelocal.h>
00045 #include <kpimprefs.h>
00046 #include <taskview.h>
00047 #include <timekard.h>
00048 #include <karmutility.h>
00049 
00050 //#include <calendarlocal.h>
00051 //#include <journal.h>
00052 //#include <event.h>
00053 //#include <todo.h>
00054 
00055 #include "karmstorage.h"
00056 #include "preferences.h"
00057 #include "task.h"
00058 #include "reportcriteria.h"
00059 
00060 
00061 KarmStorage *KarmStorage::_instance = 0;
00062 
00063 KarmStorage *KarmStorage::instance()
00064 {
00065   if (_instance == 0) _instance = new KarmStorage();
00066   return _instance;
00067 }
00068 
00069 KarmStorage::KarmStorage()
00070 {
00071   _calendar = 0;
00072   _lock = 0;
00073 }
00074 
00075 QString KarmStorage::load (TaskView* view, const Preferences* preferences)
00076 {
00077   // When I tried raising an exception from this method, the compiler
00078   // complained that exceptions are not allowed.  Not sure how apps
00079   // typically handle error conditions in KDE, but I'll return the error
00080   // as a string (empty is no error).  -- Mark, Aug 8, 2003
00081 
00082   // Use KDE_CXXFLAGS=$(USE_EXCEPTIONS) in Makefile.am if you want to use
00083   // exceptions (David Faure)
00084 
00085   QString err;
00086   KEMailSettings settings;
00087 
00088   // If same file, don't reload
00089   if ( preferences->iCalFile() == _icalfile ) return err;
00090 
00091 
00092   // If file doesn't exist, create a blank one to avoid ResourceLocal load
00093   // error.  We make it user and group read/write, others read.  This is
00094   // masked by the users umask.  (See man creat)
00095   int handle;
00096   handle = open (
00097       QFile::encodeName( preferences->iCalFile() ),
00098       O_CREAT|O_EXCL|O_WRONLY,
00099       S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH
00100       );
00101   if (handle != -1) close(handle);
00102 
00103   if ( _calendar) closeStorage(view);
00104   else _calendar = new KCal::CalendarResources();
00105 
00106   // Create local file resource and add to resources
00107   _icalfile = preferences->iCalFile();
00108   KCal::ResourceCalendar *l = new KCal::ResourceLocal( _icalfile );
00109   l->setTimeZoneId( KPimPrefs::timezone() );
00110   l->setResourceName( QString::fromLatin1("KArm") );
00111   l->open();
00112   l->load();
00113 
00114   KCal::CalendarResourceManager *m = _calendar->resourceManager();
00115   m->add(l);
00116   m->setStandardResource(l);
00117 
00118   // Claim ownership of iCalendar file if no one else has.
00119   QString email = _calendar->getEmail();
00120   QString owner = _calendar->getOwner();
00121   if ( email.isEmpty() && owner.isEmpty() )
00122   {
00123     _calendar->setEmail( settings.getSetting( KEMailSettings::EmailAddress ) );
00124     _calendar->setOwner( settings.getSetting( KEMailSettings::RealName ) );
00125   }
00126 
00127   // Get lock.  If no lock, allow read-only access to data.
00128   //
00129   // Note:  An improved implementation would be to behave like KOrganizer, and
00130   //        allow updates, and only request lock when trying to save data.
00131   _lock = _calendar->requestSaveTicket(m->standardResource());
00132   if ( !_lock )
00133   {
00134     KMessageBox::information(0,
00135         i18n("Another program is currently using this file. "
00136           "Access will be read-only."));
00137   }
00138 
00139   // Build task view from iCal data
00140   if (!err)
00141   {
00142     KCal::Todo::List todoList;
00143     KCal::Todo::List::ConstIterator todo;
00144     QDict< Task > map;
00145 
00146     // Build dictionary to look up Task object from Todo uid.  Each task is a
00147     // QListViewItem, and is initially added with the view as the parent.
00148     todoList = _calendar->rawTodos();
00149     kdDebug(5970) << "KarmStorage::load "
00150       << "rawTodo count (includes completed todos) ="
00151       << todoList.count() << endl;
00152     for( todo = todoList.begin(); todo != todoList.end(); ++todo )
00153     {
00154       // Initially, if a task was complete, it was removed from the view.
00155       // However, this increased the complexity of reporting on task history.
00156       //
00157       // For example, if a task is complete yet has time logged to it during
00158       // the date range specified on the history report, we have to figure out
00159       // how that task fits into the task hierarchy.  Currently, this
00160       // structure is held in memory by the structure in the list view.
00161       //
00162       // I considered creating a second tree that held the full structure of
00163       // all complete and incomplete tasks.  But this seemed to much of a
00164       // change with an impending beta release and a full todo list.
00165       //
00166       // Hence this "solution".  Include completed tasks, but mark them as
00167       // inactive in the view.
00168       //
00169       //if ((*todo)->isCompleted()) continue;
00170 
00171       Task* task = new Task(*todo, view);
00172       map.insert( (*todo)->uid(), task );
00173       view->setRootIsDecorated(true);
00174       if ((*todo)->isCompleted())
00175       {
00176         task->setEnabled(false);
00177         task->setOpen(false);
00178       }
00179       else
00180         task->setOpen(true);
00181     }
00182 
00183     // Load each task under it's parent task.
00184     for( todo = todoList.begin(); todo != todoList.end(); ++todo )
00185     {
00186       Task* task = map.find( (*todo)->uid() );
00187 
00188       // No relatedTo incident just means this is a top-level task.
00189       if ( (*todo)->relatedTo() )
00190       {
00191         Task* newParent = map.find( (*todo)->relatedToUid() );
00192 
00193         // Complete the loading but return a message
00194         if ( !newParent )
00195           err = i18n("Error loading \"%1\": could not find parent (uid=%2)")
00196             .arg(task->name())
00197             .arg((*todo)->relatedToUid());
00198 
00199         if (!err) task->move( newParent);
00200       }
00201     }
00202 
00203     kdDebug(5970) << "KarmStorage::load - loaded " << view->count()
00204       << " tasks from " << _icalfile << endl;
00205   }
00206 
00207   return err;
00208 }
00209 
00210 void KarmStorage::closeStorage(TaskView* view)
00211 {
00212   if ( _calendar )
00213   {
00214     if ( _lock ) _calendar->releaseSaveTicket( _lock );
00215 
00216     _calendar->close();
00217 
00218     KCal::CalendarResourceManager *m = _calendar->resourceManager();
00219     m->remove( m->standardResource() );
00220 
00221     view->clear();
00222   }
00223 }
00224 
00225 void KarmStorage::save(TaskView* taskview)
00226 {
00227   if ( !_lock ) return;
00228 
00229   QPtrStack< KCal::Todo > parents;
00230 
00231   for (Task* task=taskview->first_child(); task; task = task->nextSibling())
00232   {
00233     writeTaskAsTodo(task, 1, parents );
00234   }
00235 
00236   _calendar->save(_lock);
00237   _lock = _calendar->requestSaveTicket
00238     ( _calendar->resourceManager()->standardResource() );
00239 
00240   kdDebug(5970)
00241     << "KarmStorage::save : wrote "
00242     << taskview->count() << " tasks to " << _icalfile << endl;
00243 }
00244 
00245 void KarmStorage::writeTaskAsTodo(Task* task, const int level,
00246     QPtrStack< KCal::Todo >& parents )
00247 {
00248   KCal::Todo* todo;
00249 
00250   todo = _calendar->todo(task->uid());
00251   task->asTodo(todo);
00252   if ( !parents.isEmpty() ) todo->setRelatedTo( parents.top() );
00253   parents.push( todo );
00254 
00255   for (Task* nextTask = task->firstChild(); nextTask;
00256       nextTask = nextTask->nextSibling() )
00257   {
00258     writeTaskAsTodo(nextTask, level+1, parents );
00259   }
00260 
00261   parents.pop();
00262 }
00263 
00264 bool KarmStorage::isEmpty()
00265 {
00266   KCal::Todo::List todoList;
00267 
00268   todoList = _calendar->rawTodos();
00269   return todoList.empty();
00270 }
00271 
00272 bool KarmStorage::isNewStorage(const Preferences* preferences) const
00273 {
00274   if ( !_icalfile.isNull() ) return preferences->iCalFile() != _icalfile;
00275   else return false;
00276 }
00277 
00278 //----------------------------------------------------------------------------
00279 // Routines that handle legacy flat file format.
00280 // These only stored total and session times.
00281 //
00282 
00283 QString KarmStorage::loadFromFlatFile(TaskView* taskview,
00284     const QString& filename)
00285 {
00286   QString err;
00287 
00288   kdDebug(5970)
00289     << "KarmStorage::loadFromFlatFile: " << filename << endl;
00290 
00291   QFile f(filename);
00292   if( !f.exists() )
00293     err = i18n("File \"%1\" not found.").arg(filename);
00294 
00295   if (!err)
00296   {
00297     if( !f.open( IO_ReadOnly ) )
00298       err = i18n("Could not open \"%1\".").arg(filename);
00299   }
00300 
00301   if (!err)
00302   {
00303 
00304     QString line;
00305 
00306     QPtrStack<Task> stack;
00307     Task *task;
00308 
00309     QTextStream stream(&f);
00310 
00311     while( !stream.atEnd() ) {
00312       // lukas: this breaks for non-latin1 chars!!!
00313       // if ( file.readLine( line, T_LINESIZE ) == 0 )
00314       //   break;
00315 
00316       line = stream.readLine();
00317       kdDebug(5970) << "DEBUG: line: " << line << "\n";
00318 
00319       if (line.isNull())
00320         break;
00321 
00322       long minutes;
00323       int level;
00324       QString name;
00325       DesktopList desktopList;
00326       if (!parseLine(line, &minutes, &name, &level, &desktopList))
00327         continue;
00328 
00329       unsigned int stackLevel = stack.count();
00330       for (unsigned int i = level; i<=stackLevel ; i++) {
00331         stack.pop();
00332       }
00333 
00334       if (level == 1) {
00335         kdDebug(5970) << "KarmStorage::loadFromFlatFile - toplevel task: "
00336           << name << " min: " << minutes << "\n";
00337         task = new Task(name, minutes, 0, desktopList, taskview);
00338         task->setUid(addTask(task, 0));
00339       }
00340       else {
00341         Task *parent = stack.top();
00342         kdDebug(5970) << "KarmStorage::loadFromFlatFile - task: " << name
00343             << " min: " << minutes << " parent" << parent->name() << "\n";
00344         task = new Task(name, minutes, 0, desktopList, parent);
00345 
00346         task->setUid(addTask(task, parent));
00347 
00348         // Legacy File Format (!):
00349         parent->changeTimes(0, -minutes);
00350         taskview->setRootIsDecorated(true);
00351         parent->setOpen(true);
00352       }
00353       if (!task->uid().isNull())
00354         stack.push(task);
00355       else
00356         delete task;
00357     }
00358 
00359     f.close();
00360 
00361   }
00362 
00363   return err;
00364 }
00365 
00366 QString KarmStorage::loadFromFlatFileCumulative(TaskView* taskview,
00367     const QString& filename)
00368 {
00369   QString err = loadFromFlatFile(taskview, filename);
00370   if (!err)
00371   {
00372     for (Task* task = taskview->first_child(); task;
00373         task = task->nextSibling())
00374     {
00375       adjustFromLegacyFileFormat(task);
00376     }
00377   }
00378   return err;
00379 }
00380 
00381 bool KarmStorage::parseLine(QString line, long *time, QString *name,
00382     int *level, DesktopList* desktopList)
00383 {
00384   if (line.find('#') == 0) {
00385     // A comment line
00386     return false;
00387   }
00388 
00389   int index = line.find('\t');
00390   if (index == -1) {
00391     // This doesn't seem like a valid record
00392     return false;
00393   }
00394 
00395   QString levelStr = line.left(index);
00396   QString rest = line.remove(0,index+1);
00397 
00398   index = rest.find('\t');
00399   if (index == -1) {
00400     // This doesn't seem like a valid record
00401     return false;
00402   }
00403 
00404   QString timeStr = rest.left(index);
00405   rest = rest.remove(0,index+1);
00406 
00407   bool ok;
00408 
00409   index = rest.find('\t'); // check for optional desktops string
00410   if (index >= 0) {
00411     *name = rest.left(index);
00412     QString deskLine = rest.remove(0,index+1);
00413 
00414     // now transform the ds string (e.g. "3", or "1,4,5") into
00415     // an DesktopList
00416     QString ds;
00417     int d;
00418     int commaIdx = deskLine.find(',');
00419     while (commaIdx >= 0) {
00420       ds = deskLine.left(commaIdx);
00421       d = ds.toInt(&ok);
00422       if (!ok)
00423         return false;
00424 
00425       desktopList->push_back(d);
00426       deskLine.remove(0,commaIdx+1);
00427       commaIdx = deskLine.find(',');
00428     }
00429 
00430     d = deskLine.toInt(&ok);
00431 
00432     if (!ok)
00433       return false;
00434 
00435     desktopList->push_back(d);
00436   }
00437   else {
00438     *name = rest.remove(0,index+1);
00439   }
00440 
00441   *time = timeStr.toLong(&ok);
00442 
00443   if (!ok) {
00444     // the time field was not a number
00445     return false;
00446   }
00447   *level = levelStr.toInt(&ok);
00448   if (!ok) {
00449     // the time field was not a number
00450     return false;
00451   }
00452 
00453   return true;
00454 }
00455 
00456 void KarmStorage::adjustFromLegacyFileFormat(Task* task)
00457 {
00458   // unless the parent is the listView
00459   if ( task->parent() )
00460     task->parent()->changeTimes(-task->sessionTime(), -task->time());
00461 
00462   // traverse depth first -
00463   // as soon as we're in a leaf, we'll substract it's time from the parent
00464   // then, while descending back we'll do the same for each node untill
00465   // we reach the root
00466   for ( Task* subtask = task->firstChild(); subtask;
00467       subtask = subtask->nextSibling() )
00468     adjustFromLegacyFileFormat(subtask);
00469 }
00470 
00471 //----------------------------------------------------------------------------
00472 // Routines that handle Comma-Separated Values export file format.
00473 //
00474 QString KarmStorage::exportcsvFile( TaskView *taskview, 
00475                                     const ReportCriteria &rc )
00476 {
00477   QString delim = rc.delimiter;
00478   QString dquote = rc.quote;
00479   QString double_dquote = dquote + dquote;
00480   bool to_quote = true;
00481   
00482   QString err;
00483   Task* task;
00484   int maxdepth=0; 
00485 
00486   kdDebug(5970)
00487     << "KarmStorage::exportcsvFile: " << rc.url << endl;
00488 
00489   QFile f( rc.url );
00490   if( !f.open( IO_WriteOnly ) ) {
00491       err = i18n("Could not open \"%1\".").arg( rc.url );
00492   }
00493 
00494   if (!err)
00495   {
00496     QString title = i18n("Export Progress");
00497     KProgressDialog dialog( taskview, 0, title );
00498     dialog.setAutoClose( true );
00499     dialog.setAllowCancel( true );
00500     dialog.progressBar()->setTotalSteps( 2 * taskview->count() );
00501 
00502     // The default dialog was not displaying all the text in the title bar.
00503     int width = taskview->fontMetrics().width(title) * 3;
00504     QSize dialogsize;
00505     dialogsize.setWidth(width);
00506     dialog.setInitialSize( dialogsize, true );
00507     
00508     if ( taskview->count() > 1 ) dialog.show();
00509 
00510     QTextStream stream(&f);
00511 
00512     // Find max task depth
00513     int tasknr = 0;
00514     while ( tasknr < taskview->count() && !dialog.wasCancelled() )
00515     { 
00516       dialog.progressBar()->advance( 1 );
00517       if ( tasknr % 15 == 0 ) kapp->processEvents(); // repainting is slow 
00518       if ( taskview->item_at_index(tasknr)->depth() > maxdepth ) 
00519         maxdepth = taskview->item_at_index(tasknr)->depth(); 
00520       tasknr++;
00521     } 
00522 
00523     // Export to file
00524     tasknr = 0;
00525     while ( tasknr < taskview->count() && !dialog.wasCancelled() )
00526     {
00527       task = taskview->item_at_index( tasknr );
00528 
00529       dialog.progressBar()->advance( 1 );
00530       if ( tasknr % 15 == 0 ) kapp->processEvents();
00531 
00532       // indent the task in the csv-file:
00533       for ( int i=0; i < task->depth(); ++i ) stream << delim;
00534       
00535       /*
00536       // CSV compliance
00537       // Surround the field with quotes if the field contains 
00538       // a comma (delim) or a double quote
00539       if (task->name().contains(delim) || task->name().contains(dquote))
00540         to_quote = TRUE;
00541       else
00542         to_quote = FALSE;
00543       */
00544       to_quote = true;
00545 
00546       if (to_quote)
00547         stream << dquote;
00548         
00549       // Double quotes replaced by a pair of consecutive double quotes 
00550       stream << task->name().replace( dquote, double_dquote );
00551 
00552       if (to_quote)
00553         stream << dquote;
00554       
00555       // maybe other tasks are more indented, so to align the columns:
00556       for ( int i = 0; i < maxdepth - task->depth(); ++i ) stream << delim;
00557 
00558       stream << delim << formatTime( task->sessionTime(),
00559                                      rc.decimalMinutes )
00560              << delim << formatTime( task->time(),
00561                                      rc.decimalMinutes )
00562              << delim << formatTime( task->totalSessionTime(),
00563                                      rc.decimalMinutes )
00564              << delim << formatTime( task->totalTime(),
00565                                      rc.decimalMinutes )
00566              << endl;
00567       tasknr++;
00568     }
00569     f.close();
00570 
00571   }
00572   return err;
00573 }
00574 
00575 //----------------------------------------------------------------------------
00576 // Routines that handle logging KArm history
00577 //
00578 
00579 //
00580 // public routines:
00581 //
00582 
00583 QString KarmStorage::addTask(const Task* task, const Task* parent)
00584 {
00585   KCal::Todo* todo;
00586   QString uid;
00587 
00588   todo = new KCal::Todo();
00589   if (_calendar->addTodo(todo))
00590   {
00591     task->asTodo(todo);
00592     if (parent)
00593       todo->setRelatedTo(_calendar->todo(parent->uid()));
00594     uid = todo->uid();
00595   }
00596 
00597   return uid;
00598 }
00599 
00600 bool KarmStorage::removeTask(Task* task)
00601 {
00602   if ( !_lock ) return false;
00603 
00604   // delete history
00605   KCal::Event::List eventList = _calendar->rawEvents();
00606   for(KCal::Event::List::iterator i = eventList.begin();
00607       i != eventList.end();
00608       ++i)
00609   {
00610     //kdDebug(5970) << "KarmStorage::removeTask: "
00611     //  << (*i)->uid() << " - relatedToUid() "
00612     //  << (*i)->relatedToUid()
00613     //  << ", relatedTo() = " << (*i)->relatedTo() <<endl;
00614     if ( (*i)->relatedToUid() == task->uid()
00615         || ( (*i)->relatedTo()
00616             && (*i)->relatedTo()->uid() == task->uid()))
00617     {
00618       _calendar->deleteEvent(*i);
00619     }
00620   }
00621 
00622   // delete todo
00623   KCal::Todo *todo = _calendar->todo(task->uid());
00624   _calendar->deleteTodo(todo);
00625 
00626   // Save entire file
00627   _calendar->save(_lock);
00628   _lock = _calendar->requestSaveTicket
00629     (_calendar->resourceManager()->standardResource());
00630 
00631   return true;
00632 }
00633 
00634 void KarmStorage::addComment(const Task* task, const QString& comment)
00635 {
00636   if ( !_lock) return;
00637 
00638   KCal::Todo* todo;
00639 
00640   todo = _calendar->todo(task->uid());
00641 
00642   // Do this to avoid compiler warnings about comment not being used.  once we
00643   // transition to using the addComment method, we need this second param.
00644   QString s = comment;
00645 
00646   // TODO: Use libkcal comments 
00647   // todo->addComment(comment);
00648   // temporary
00649   todo->setDescription(task->comment());
00650 
00651   _calendar->save(_lock);
00652   _lock = _calendar->requestSaveTicket
00653     ( _calendar->resourceManager()->standardResource() );
00654 }
00655 
00656 void KarmStorage::printTaskHistory (
00657         const Task               *task, 
00658         const QMap<QString,long> &taskdaytotals, 
00659         QMap<QString,long>       &daytotals, 
00660         const QDate              &from,
00661         const QDate              &to, 
00662         const int                level, 
00663         QString                  &s,
00664         const ReportCriteria     &rc)
00665 // to>=from is precondition
00666 {
00667   QString delim = rc.delimiter;
00668   QString dquote = rc.quote;
00669   QString double_dquote = dquote + dquote;
00670   bool to_quote = true;
00671 
00672   const QString cr = QString::fromLatin1("\n");
00673   QString buf;
00674   QString daytaskkey, daykey;
00675   QDate day;
00676   long sum;
00677 
00678   if ( !task ) return;
00679 
00680   day = from;
00681   sum = 0;
00682   while (day <= to)
00683   {
00684     // write the time in seconds for the given task for the given day to s
00685     daykey = day.toString(QString::fromLatin1("yyyyMMdd"));
00686     daytaskkey = QString::fromLatin1("%1_%2")
00687       .arg(daykey)
00688       .arg(task->uid());
00689 
00690     if (taskdaytotals.contains(daytaskkey))
00691     {
00692       s += QString::fromLatin1("%1")
00693         .arg(formatTime(taskdaytotals[daytaskkey]/60, rc.decimalMinutes));
00694       sum += taskdaytotals[daytaskkey];  // in seconds
00695 
00696       if (daytotals.contains(daykey))
00697         daytotals.replace(daykey, daytotals[daykey]+taskdaytotals[daytaskkey]);
00698       else
00699         daytotals.insert(daykey, taskdaytotals[daytaskkey]);
00700     }
00701     s += delim;
00702 
00703     day = day.addDays(1);
00704   }
00705 
00706   // Total for task this week
00707   s += QString::fromLatin1("%1").arg(formatTime(sum/60, rc.decimalMinutes));
00708 
00709   // Task name
00710   for ( int i = level + 1; i > 0; i-- ) s += delim;
00711 
00712   /*
00713   // CSV compliance
00714   // Surround the field with quotes if the field contains 
00715   // a comma (delim) or a double quote
00716   to_quote = task->name().contains(delim) || task->name().contains(dquote);
00717   */
00718   to_quote = true; 
00719   if ( to_quote) s += dquote;
00720 
00721 
00722   // Double quotes replaced by a pair of consecutive double quotes 
00723   s += task->name().replace( dquote, double_dquote );
00724 
00725   if ( to_quote) s += dquote;
00726 
00727   s += cr;
00728 
00729   for (Task* subTask = task->firstChild();
00730       subTask;
00731       subTask = subTask->nextSibling())
00732   {
00733     printTaskHistory( subTask, taskdaytotals, daytotals, from, to , level+1, s,
00734                       rc );
00735   }
00736 }
00737 
00738 QString KarmStorage::report( TaskView *taskview, const ReportCriteria &rc )
00739 {
00740   QString err;
00741   if ( rc.reportType == ReportCriteria::CSVHistoryExport )
00742       err = exportActivityReport( taskview, rc.from, rc.to, rc );
00743   else if ( rc.reportType == ReportCriteria::CSVTotalsExport )
00744       err = exportcsvFile( taskview, rc );
00745   else
00746       // hmmmm ... assert(0)?
00747       ;
00748   return err;
00749 }
00750 
00751 // export history report as csv, all tasks X all dates in one block
00752 QString KarmStorage::exportActivityReport ( TaskView      *taskview, 
00753                                             const QDate   &from, 
00754                                             const QDate   &to,
00755                                             const ReportCriteria &rc)
00756 {
00757   QString delim = rc.delimiter;
00758   const QString cr = QString::fromLatin1("\n");
00759   QString err;
00760   
00761   // below taken from timekard.cpp
00762   QString retval;
00763   QString taskhdr, totalhdr;
00764   QString line, buf;
00765   long sum;
00766   
00767   QValueList<Week>::iterator week;
00768   QValueList<HistoryEvent> events;
00769   QValueList<HistoryEvent>::iterator event;
00770   QMap<QString, long> taskdaytotals;
00771   QMap<QString, long> daytotals;
00772   QString daytaskkey, daykey;
00773   QDate day;
00774   QDate dayheading;
00775 
00776   // parameter-plausi
00777   if ( from > to ) 
00778   {
00779     err = QString::fromLatin1 (
00780             "'to' has to be a date later than or equal to 'from'.");
00781   }
00782  
00783   // header
00784   retval += i18n("Task History\n");
00785   retval += i18n("From %1 to %2")
00786     .arg(KGlobal::locale()->formatDate(from))
00787     .arg(KGlobal::locale()->formatDate(to));
00788   retval += cr;
00789   retval += i18n("Printed on: %1")
00790     .arg(KGlobal::locale()->formatDateTime(QDateTime::currentDateTime()));
00791   retval += cr;
00792 
00793   // output one time card table for each week in the date range
00794   QValueList<Week> weeks = Week::weeksFromDateRange(from, to);
00795   day=from;
00796 
00797   events = taskview->getHistory(from, to);
00798   taskdaytotals.clear();
00799   daytotals.clear();
00800  
00801   // Build lookup dictionary used to output data in table cells.  keys are
00802   // in this format: YYYYMMDD_NNNNNN, where Y = year, M = month, d = day and
00803   // NNNNN = the VTODO uid.  The value is the total seconds logged against
00804   // that task on that day.  Note the UID is the todo id, not the event id,
00805   // so times are accumulated for each task.
00806   for (event = events.begin(); event != events.end(); ++event)
00807   {
00808     daykey = (*event).start().date().toString(QString::fromLatin1("yyyyMMdd"));
00809     daytaskkey = QString(QString::fromLatin1("%1_%2"))
00810         .arg(daykey)
00811         .arg((*event).todoUid());
00812         
00813     if (taskdaytotals.contains(daytaskkey))
00814         taskdaytotals.replace(daytaskkey, 
00815                 taskdaytotals[daytaskkey] + (*event).duration());
00816     else
00817         taskdaytotals.insert(daytaskkey, (*event).duration());
00818   }
00819         
00820   // day headings
00821   dayheading = from;
00822   while ( dayheading <= to )
00823   {
00824     // Use ISO 8601 format for date.
00825     retval += dayheading.toString(QString::fromLatin1("yyyy-MM-dd"));
00826     retval += delim;
00827     dayheading=dayheading.addDays(1);
00828   }
00829   retval += cr;
00830   retval += line;
00831         
00832   // the tasks
00833   if (events.empty())
00834   {
00835     retval += i18n("  No hours logged.");
00836   }
00837   else
00838   {
00839     if ( rc.allTasks ) 
00840     {
00841       for ( Task* task= taskview->item_at_index(0);
00842             task; task= task->nextSibling() )
00843       {
00844         printTaskHistory( task, taskdaytotals, daytotals, from, to, 0, 
00845                           retval, rc );
00846       }
00847     }
00848     else
00849     {
00850       printTaskHistory( taskview->current_item(), taskdaytotals, daytotals, 
00851                         from, to, 0, retval, rc );
00852     }
00853     retval += line;
00854         
00855     // totals
00856     sum = 0;
00857     day = from;
00858     while (day<=to)
00859     {
00860       daykey = day.toString(QString::fromLatin1("yyyyMMdd"));
00861         
00862       if (daytotals.contains(daykey))
00863       {
00864         retval += QString::fromLatin1("%1")
00865             .arg(formatTime(daytotals[daykey]/60, rc.decimalMinutes));
00866         sum += daytotals[daykey];  // in seconds
00867       }
00868       retval += delim;
00869       day = day.addDays(1);
00870     }
00871         
00872     retval += QString::fromLatin1("%1%2%3")
00873         .arg( formatTime( sum/60, rc.decimalMinutes ) )
00874         .arg( delim )
00875         .arg( i18n( "Total" ) );
00876   }
00877 
00878   // above taken from timekard.cpp
00879       
00880   QFile f( rc.url );
00881   if( !f.open( IO_WriteOnly ) ) {
00882       err = i18n( "Could not open \"%1\"." ).arg( rc.url );
00883   }
00884 
00885   if (!err)
00886   {
00887 
00888     QTextStream stream(&f);
00889     // Export to file
00890     stream << retval;
00891     f.close();
00892 
00893   }
00894   return err;
00895 }
00896 
00897 void KarmStorage::stopTimer(const Task* task)
00898 {
00899   long delta = task->startTime().secsTo(QDateTime::currentDateTime());
00900   changeTime(task, delta);
00901 }
00902 
00903 void KarmStorage::changeTime(const Task* task, const long deltaSeconds)
00904 {
00905   KCal::Event* e;
00906   QDateTime end;
00907 
00908   // Don't write events (with timer start/stop duration) if user has turned
00909   // this off in the settings dialog.
00910   if ( ! task->taskView()->preferences()->logging() ) return;
00911 
00912   e = baseEvent(task);
00913 
00914   // Don't use duration, as ICalFormatImpl::writeIncidence never writes a
00915   // duration, even though it looks like it's used in event.cpp.
00916   end = task->startTime();
00917   if ( deltaSeconds > 0 ) end = task->startTime().addSecs(deltaSeconds);
00918   e->setDtEnd(end);
00919 
00920   // Use a custom property to keep a record of negative durations
00921   e->setCustomProperty( kapp->instanceName(),
00922       QCString("duration"),
00923       QString::number(deltaSeconds));
00924 
00925   _calendar->addEvent(e);
00926 
00927   // This saves the entire iCal file each time, which isn't efficient but
00928   // ensures no data loss.  A faster implementation would be to append events
00929   // to a file, and then when KArm closes, append the data in this file to the
00930   // iCal file.
00931   //
00932   // Meanwhile, we simply use a timer to delay the full-saving until the GUI
00933   // has updated, for better user feedback. Feel free to get rid of this
00934   // if/when implementing the faster saving (DF).
00935   task->taskView()->scheduleSave();
00936 }
00937 
00938 
00939 KCal::Event* KarmStorage::baseEvent(const Task * task)
00940 {
00941   KCal::Event* e;
00942   QStringList categories;
00943 
00944   e = new KCal::Event;
00945   e->setSummary(task->name());
00946 
00947   // Can't use setRelatedToUid()--no error, but no RelatedTo written to disk
00948   e->setRelatedTo(_calendar->todo(task->uid()));
00949 
00950   // Debugging: some events where not getting a related-to field written.
00951   assert(e->relatedTo()->uid() == task->uid());
00952 
00953   // Have to turn this off to get datetimes in date fields.
00954   e->setFloats(false);
00955   e->setDtStart(task->startTime());
00956 
00957   // So someone can filter this mess out of their calendar display
00958   categories.append(i18n("KArm"));
00959   e->setCategories(categories);
00960 
00961   return e;
00962 }
00963 
00964 HistoryEvent::HistoryEvent(QString uid, QString name, long duration,
00965         QDateTime start, QDateTime stop, QString todoUid)
00966 {
00967   _uid = uid;
00968   _name = name;
00969   _duration = duration;
00970   _start = start;
00971   _stop = stop;
00972   _todoUid = todoUid;
00973 }
00974 
00975 
00976 QValueList<HistoryEvent> KarmStorage::getHistory(const QDate& from,
00977     const QDate& to)
00978 {
00979   QValueList<HistoryEvent> retval;
00980   QStringList processed;
00981   KCal::Event::List events;
00982   KCal::Event::List::iterator event;
00983   QString duration;
00984 
00985   for(QDate d = from; d <= to; d = d.addDays(1))
00986   {
00987     events = _calendar->events(d);
00988     for (event = events.begin(); event != events.end(); ++event)
00989     {
00990 
00991       // KArm events have the custom property X-KDE-Karm-duration
00992       if (! processed.contains( (*event)->uid()))
00993       {
00994         // If an event spans multiple days, CalendarLocal::rawEventsForDate
00995         // will return the same event on both days.  To avoid double-counting
00996         // such events, we (arbitrarily) attribute the hours from both days on
00997         // the first day.  This mis-reports the actual time spent, but it is
00998         // an easy fix for a (hopefully) rare situation.
00999         processed.append( (*event)->uid());
01000 
01001         duration = (*event)->customProperty(kapp->instanceName(),
01002             QCString("duration"));
01003         if ( ! duration.isNull() )
01004         {
01005           if ( (*event)->relatedTo()
01006               &&  ! (*event)->relatedTo()->uid().isEmpty() )
01007           {
01008             retval.append(HistoryEvent(
01009                 (*event)->uid(),
01010                 (*event)->summary(),
01011                 duration.toLong(),
01012                 (*event)->dtStart(),
01013                 (*event)->dtEnd(),
01014                 (*event)->relatedTo()->uid()
01015                 ));
01016           }
01017           else
01018             // Something is screwy with the ics file, as this KArm history event
01019             // does not have a todo related to it.  Could have been deleted
01020             // manually?  We'll continue with report on with report ...
01021             kdDebug(5970) << "KarmStorage::getHistory(): "
01022               << "The event " << (*event)->uid()
01023               << " is not related to a todo.  Dropped." << endl;
01024         }
01025       }
01026     }
01027   }
01028 
01029   return retval;
01030 }
01031 
01032 /*
01033  * Obsolete methods for writing to flat file format.
01034  * Aug 8, 2003, Mark
01035  *
01036 void KarmStorage::saveToFileFormat()
01037 {
01038   //QFile f(_preferences->saveFile());
01039   QFile f(_preferences->flatFile());
01040 
01041   if ( !f.open( IO_WriteOnly | IO_Truncate ) ) {
01042     QString msg = i18n( "There was an error trying to save your data file.\n"
01043                        "Time accumulated during this session will not be saved!\n");
01044     KMessageBox::error(0, msg );
01045     return;
01046   }
01047   const char * comment = "# TaskView save data\n";
01048 
01049   f.writeBlock(comment, strlen(comment));  //comment
01050   f.flush();
01051 
01052   QTextStream stream(&f);
01053   for (Task* child = firstChild();
01054              child;
01055              child = child->nextSibling())
01056     writeTaskToFile(&stream, child, 1);
01057 
01058   f.close();
01059   kdDebug(5970) << "Saved data to file " << f.name() << endl;
01060 }
01061 void KarmStorage::writeTaskToFile( QTextStream *strm, Task *task,
01062                                 int level)
01063 {
01064   //lukas: correct version for non-latin1 users
01065   QString _line = QString::fromLatin1("%1\t%2\t%3").arg(level).
01066           arg(task->time()).arg(task->name());
01067 
01068   DesktopList d = task->getDesktops();
01069   int dsize = d.size();
01070   if (dsize>0) {
01071     _line += '\t';
01072     for (int i=0; i<dsize-1; i++) {
01073       _line += QString::number(d[i]);
01074       _line += ',';
01075     }
01076     _line += QString::number(d[dsize-1]);
01077   }
01078   *strm << _line << "\n";
01079 
01080   for ( Task* child= task->firstChild();
01081               child;
01082               child=child->nextSibling()) {
01083     writeTaskToFile(strm, child, level+1);
01084   }
01085 }
01086 
01087 */
KDE Logo
This file is part of the documentation for karm Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Jul 25 11:19:15 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003