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 Wed Jul 28 23:58:05 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003