kde-playground/kdepim/ktimetracker/timetrackerstorage.cpp
2015-04-14 21:49:29 +00:00

1076 lines
34 KiB
C++

/*
* Copyright (C) 2003, 2004 by Mark Bucciarelli <mark@hubcapconsutling.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA.
*
*/
/** timetrackerstorage
* This class cares for the storage of ktimetracker's data.
* ktimetracker's data is
* - tasks like "programming for customer foo"
* - events like "from 2009-09-07, 8pm till 10pm programming for customer foo"
* tasks are like the items on your todo list, events are dates when you worked on them.
* ktimetracker's data is stored in a ResourceCalendar object hold by mCalendar.
*/
#include "timetrackerstorage.h"
#include "ktimetrackerutility.h"
#include "ktimetracker.h"
#include "storageadaptor.h"
#include "task.h"
#include "taskview.h"
#include "timekard.h"
#include <kemailsettings.h>
#include <kio/netaccess.h>
#include <KCalCore/Person>
#include <KDirWatch>
#include <KLockFile>
#include <KApplication> // kapp
#include <KDebug>
#include <KLocale> // i18n
#include <KMessageBox>
#include <KProgressDialog>
#include <KSystemTimeZones>
#include <KTemporaryFile>
#include <KUrl>
#include <QByteArray>
#include <QDateTime>
#include <QFile>
#include <QList>
#include <QMultiHash>
#include <QSize>
#include <QString>
#include <QStringList>
#include <QTableWidget>
#include <QTextStream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <QMap>
using namespace KTimeTracker;
//@cond PRIVATE
class timetrackerstorage::Private
{
public:
Private()
{
m_fileLock = new KLockFile( QLatin1String( "ktimetrackerics.lock" ) );
}
~Private()
{
delete m_fileLock;
}
KTimeTracker::KTTCalendar::Ptr mCalendar;
QString mICalFile;
KLockFile *m_fileLock;
};
//@endcond
timetrackerstorage::timetrackerstorage() : d( new Private() )
{
}
timetrackerstorage::~timetrackerstorage()
{
delete d;
}
QString timetrackerstorage::load(TaskView* view, const QString &fileName)
// loads data from filename into view. If no filename is given, filename from preferences is used.
// filename might be of use if this program is run as embedded konqueror plugin.
{
// loading might create the file
bool removedFromDirWatch = false;
if ( KDirWatch::self()->contains( d->mICalFile ) ) {
KDirWatch::self()->removeFile( d->mICalFile );
removedFromDirWatch = true;
}
kDebug(5970) << "Entering function";
QString err;
KEMailSettings settings;
QString lFileName = fileName;
Q_ASSERT( !( lFileName.isEmpty() ) );
// If same file, don't reload
if ( lFileName == d->mICalFile ) {
if ( removedFromDirWatch ) {
KDirWatch::self()->addFile( d->mICalFile );
}
return err;
}
// If file doesn't exist, create a blank one to avoid ResourceLocal load
// error. We make it user and group read/write, others read. This is
// masked by the users umask. (See man creat)
const bool fileIsLocal = !isRemoteFile( lFileName );
if ( fileIsLocal )
{
int handle;
handle = open ( QFile::encodeName( lFileName ), O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
if ( handle != -1 )
{
close( handle );
}
}
if ( d->mCalendar )
closeStorage();
// Create local file resource and add to resources
d->mICalFile = lFileName;
d->mCalendar = KTTCalendar::createInstance( d->mICalFile, /*monitorFile=*/ fileIsLocal );
QObject::connect( d->mCalendar.data(), SIGNAL(calendarChanged()),
view, SLOT(iCalFileModified()) );
d->mCalendar->setTimeSpec( KSystemTimeZones::local() );
d->mCalendar->reload();
// Claim ownership of iCalendar file if no one else has.
KCalCore::Person::Ptr owner = d->mCalendar->owner();
if ( owner && owner->isEmpty() )
{
d->mCalendar->setOwner( KCalCore::Person::Ptr(
new KCalCore::Person( settings.getSetting( KEMailSettings::RealName ),
settings.getSetting( KEMailSettings::EmailAddress ) ) ) );
}
// Build task view from iCal data
if (!err.isEmpty())
{
KCalCore::Todo::List todoList;
KCalCore::Todo::List::ConstIterator todo;
QMultiHash< QString, Task* > map;
// Build dictionary to look up Task object from Todo uid. Each task is a
// QListViewItem, and is initially added with the view as the parent.
todoList = d->mCalendar->rawTodos();
kDebug(5970) << "timetrackerstorage::load"
<< "rawTodo count (includes completed todos) ="
<< todoList.count();
for (todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo)
{
Task* task = new Task(*todo, view);
map.insert( (*todo)->uid(), task );
view->setRootIsDecorated(true);
task->setPixmapProgress();
}
// Load each task under its parent task.
for (todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo)
{
Task* task = map.value( (*todo)->uid() );
// No relatedTo incident just means this is a top-level task.
if ( !(*todo)->relatedTo().isEmpty() )
{
Task *newParent = map.value( (*todo)->relatedTo() );
// Complete the loading but return a message
if ( !newParent )
err = i18n("Error loading \"%1\": could not find parent (uid=%2)",
task->name(), (*todo)->relatedTo() );
if (!err.isEmpty()) task->move( newParent );
}
}
kDebug(5970) << "timetrackerstorage::load - loaded" << view->count()
<< "tasks from" << d->mICalFile;
}
if ( view ) buildTaskView(d->mCalendar->weakPointer(), view);
if ( removedFromDirWatch ) {
KDirWatch::self()->addFile( d->mICalFile );
}
return err;
}
Task* timetrackerstorage::task(const QString& uid, TaskView* view)
// return the tasks with the uid uid out of view.
// If !view, return the todo with the uid uid.
{
kDebug(5970) << "Entering function";
KCalCore::Todo::List todoList;
KCalCore::Todo::List::ConstIterator todo;
todoList = d->mCalendar->rawTodos();
todo = todoList.constBegin();
Task* result=0;
bool konsolemode=false;
if ( view == 0 ) konsolemode=true;
while ( todo != todoList.constEnd() && ( (*todo)->uid() != uid ) ) ++todo;
if ( todo != todoList.constEnd() ) result = new Task((*todo), view, konsolemode);
kDebug(5970) << "Leaving function, returning " << result;
return result;
}
QString timetrackerstorage::icalfile()
{
kDebug(5970) << "Entering function";
return d->mICalFile;
}
QString timetrackerstorage::buildTaskView( const KTimeTracker::KTTCalendar::Ptr &calendar,
TaskView *view )
// makes *view contain the tasks out of *rc.
{
kDebug(5970) << "Entering function";
QString err;
KCalCore::Todo::List todoList;
KCalCore::Todo::List::ConstIterator todo;
QMultiHash< QString, Task* > map;
QVector<QString> runningTasks;
QVector<QDateTime> startTimes;
// remember tasks that are running and their start times
QTreeWidgetItemIterator it( view );
while ( *it )
{
Task *task = static_cast< Task* >( *it );
if ( task->isRunning() )
{
runningTasks.append( task->uid() );
startTimes.append( task->startTime() );
}
++it;
}
view->clear();
todoList = calendar->rawTodos();
for ( todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo )
{
Task* task = new Task(*todo, view);
task->setWhatsThis(0,i18n("The task name is what you call the task, it can be chosen freely."));
task->setWhatsThis(1,i18n("The session time is the time since you last chose \"start new session.\""));
map.insert( (*todo)->uid(), task );
view->setRootIsDecorated(true);
task->setPixmapProgress();
}
// 1.1. Load each task under its parent task.
for( todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo )
{
Task* task = map.value( (*todo)->uid() );
// No relatedTo incident just means this is a top-level task.
if ( !(*todo)->relatedTo().isEmpty() )
{
Task* newParent = map.value( (*todo)->relatedTo() );
// Complete the loading but return a message
if ( !newParent )
err = i18n("Error loading \"%1\": could not find parent (uid=%2)",
task->name(),
(*todo)->relatedTo());
else task->move( newParent );
}
}
view->clearActiveTasks();
// restart tasks that have been running with their start times
for ( int i=0; i<view->count(); ++i)
{
for ( int n = 0; n < runningTasks.count(); ++n )
{
if ( runningTasks[n] == view->itemAt(i)->uid() )
{
view->startTimerFor( view->itemAt(i), startTimes[n] );
}
}
}
view->refresh();
return err;
}
QString timetrackerstorage::buildTaskView(TaskView *view)
// makes *view contain the tasks out of mCalendar
{
return buildTaskView(d->mCalendar, view);
}
void timetrackerstorage::closeStorage()
{
kDebug(5970) << "Entering function";
if ( d->mCalendar )
{
d->mCalendar->close();
d->mCalendar = KTTCalendar::Ptr();
}
kDebug(5970) << "Leaving function";
}
KCalCore::Event::List timetrackerstorage::rawevents()
{
kDebug(5970) << "Entering function";
return d->mCalendar->rawEvents();
}
KCalCore::Todo::List timetrackerstorage::rawtodos()
{
kDebug(5970) << "Entering function";
return d->mCalendar->rawTodos();
}
bool timetrackerstorage::allEventsHaveEndTiMe(Task* task)
{
kDebug(5970) << "Entering function";
KCalCore::Event::List eventList = d->mCalendar->rawEvents();
for(KCalCore::Event::List::iterator i = eventList.begin();
i != eventList.end(); ++i)
{
if ( (*i)->relatedTo() == task->uid() && !(*i)->hasEndDate() ) {
return false;
}
}
return true;
}
bool timetrackerstorage::allEventsHaveEndTiMe()
{
kDebug(5970) << "Entering function";
KCalCore::Event::List eventList = d->mCalendar->rawEvents();
for(KCalCore::Event::List::iterator i = eventList.begin();
i != eventList.end(); ++i)
{
if ( !(*i)->hasEndDate() ) return false;
}
return true;
}
QString timetrackerstorage::deleteAllEvents()
{
kDebug(5970) << "Entering function";
QString err;
KCalCore::Event::List eventList = d->mCalendar->rawEvents();
d->mCalendar->deleteAllEvents();
return err;
}
QString timetrackerstorage::save(TaskView* taskview)
{
kDebug(5970) << "Entering function";
QString errorString;
QStack<KCalCore::Todo::Ptr> parents;
if ( taskview ) // we may also be in konsole mode
{
for (int i = 0; i < taskview->topLevelItemCount(); ++i )
{
Task *task = static_cast< Task* >( taskview->topLevelItem( i ) );
kDebug( 5970 ) << "write task" << task->name();
errorString = writeTaskAsTodo( task, parents );
}
}
errorString = saveCalendar();
if ( errorString.isEmpty() ) {
kDebug(5970) << "timetrackerstorage::save : wrote tasks to" << d->mICalFile;
} else {
kWarning(5970) << "timetrackerstorage::save :" << errorString;
}
return errorString;
}
QString timetrackerstorage::setTaskParent(Task* task, Task* parent)
{
kDebug(5970) << "Entering function";
QString err;
KCalCore::Todo::Ptr todo = d->mCalendar->todo( task->uid() );
if ( !parent ) {
todo->setRelatedTo( QString() );
} else {
todo->setRelatedTo( parent->uid() );
}
kDebug(5970) << "Leaving function";
return err;
}
QString timetrackerstorage::writeTaskAsTodo(Task* task, QStack<KCalCore::Todo::Ptr>& parents)
{
kDebug(5970) << "Entering function";
QString err;
KCalCore::Todo::Ptr todo = d->mCalendar->todo(task->uid());
if ( !todo )
{
kDebug(5970) << "Could not get todo from calendar";
return "Could not get todo from calendar";
}
task->asTodo(todo);
if ( !parents.isEmpty() ) todo->setRelatedTo( parents.top() ? parents.top()->uid() : QString() );
parents.push( todo );
for ( int i = 0; i < task->childCount(); ++i )
{
Task *nextTask = static_cast< Task* >( task->child( i ) );
err = writeTaskAsTodo( nextTask, parents );
}
parents.pop();
return err;
}
bool timetrackerstorage::isEmpty()
{
kDebug(5970) << "Entering function";
return d->mCalendar->rawTodos().isEmpty();
}
//----------------------------------------------------------------------------
// Routines that handle logging ktimetracker history
QString timetrackerstorage::addTask(const Task* task, const Task* parent)
{
kDebug(5970) << "Entering function";
KCalCore::Todo::Ptr todo;
QString uid;
if ( !d->mCalendar )
{
kDebug(5970) << "mCalendar is not set";
return uid;
}
todo = KCalCore::Todo::Ptr( new KCalCore::Todo() );
if ( d->mCalendar->addTodo( todo ) )
{
task->asTodo( todo );
if (parent)
todo->setRelatedTo( parent->uid() );
uid = todo->uid();
}
else
{
// Most likely a lock could not be pulled, although there are other
// possiblities (like a really confused resource manager).
uid.clear();;
}
return uid;
}
QStringList timetrackerstorage::taskidsfromname(QString taskname)
{
kDebug(5970) << "Entering function";
QStringList result;
KCalCore::Todo::List todoList = d->mCalendar->rawTodos();
for(KCalCore::Todo::List::iterator i = todoList.begin();
i != todoList.end(); ++i)
{
kDebug(5970) << (*i)->uid();
if ( (*i)->summary() == taskname ) result << (*i)->uid();
}
return result;
}
QStringList timetrackerstorage::taskNames() const
{
kDebug(5970) << "Entering function";
QStringList result;
KCalCore::Todo::List todoList = d->mCalendar->rawTodos();
for(KCalCore::Todo::List::iterator i = todoList.begin();
i != todoList.end(); ++i) result << (*i)->summary();
return result;
}
QString timetrackerstorage::removeEvent(QString uid)
{
kDebug(5970) << "Entering function";
QString err=QString();
KCalCore::Event::List eventList = d->mCalendar->rawEvents();
for(KCalCore::Event::List::iterator i = eventList.begin();
i != eventList.end(); ++i)
{
if ( (*i)->uid() == uid )
{
d->mCalendar->deleteEvent(*i);
}
}
return err;
}
bool timetrackerstorage::removeTask(Task* task)
{
kDebug(5970) << "Entering function";
// delete history
KCalCore::Event::List eventList = d->mCalendar->rawEvents();
for(KCalCore::Event::List::iterator i = eventList.begin();
i != eventList.end(); ++i)
{
if ( (*i)->relatedTo() == task->uid() )
{
d->mCalendar->deleteEvent(*i);
}
}
// delete todo
KCalCore::Todo::Ptr todo = d->mCalendar->todo(task->uid());
d->mCalendar->deleteTodo(todo);
// Save entire file
saveCalendar();
return true;
}
bool timetrackerstorage::removeTask(QString taskid)
{
kDebug(5970) << "Entering function";
// delete history
KCalCore::Event::List eventList = d->mCalendar->rawEvents();
for(KCalCore::Event::List::iterator i = eventList.begin();
i != eventList.end();
++i)
{
if ( (*i)->relatedTo() == taskid )
{
d->mCalendar->deleteEvent(*i);
}
}
// delete todo
KCalCore::Todo::Ptr todo = d->mCalendar->todo(taskid);
d->mCalendar->deleteTodo(todo);
// Save entire file
saveCalendar();
return true;
}
void timetrackerstorage::addComment(const Task* task, const QString& comment)
{
kDebug(5970) << "Entering function";
KCalCore::Todo::Ptr todo = d->mCalendar->todo(task->uid());
// Do this to avoid compiler warnings about comment not being used. once we
// transition to using the addComment method, we need this second param.
QString s = comment;
// TODO: Use libkcalcore comments
// todo->addComment(comment);
// temporary
todo->setDescription(task->comment());
saveCalendar();
}
QString timetrackerstorage::report(TaskView *taskview, const ReportCriteria &rc)
{
kDebug(5970) << "Entering function";
QString err;
if ( rc.reportType == ReportCriteria::CSVHistoryExport )
{
err = exportcsvHistory( taskview, rc.from, rc.to, rc );
}
else // if ( rc.reportType == ReportCriteria::CSVTotalsExport )
{
if ( !rc.bExPortToClipBoard )
err = exportcsvFile( taskview, rc );
else
err = taskview->clipTotals( rc );
}
return err;
}
//----------------------------------------------------------------------------
// Routines that handle Comma-Separated Values export file format.
//
QString timetrackerstorage::exportcsvFile(TaskView *taskview, const ReportCriteria &rc)
{
kDebug(5970) << "Entering function";
QString delim = rc.delimiter;
QString dquote = rc.quote;
QString double_dquote = dquote + dquote;
bool to_quote = true;
QString err;
Task* task;
int maxdepth=0;
QString title = i18n("Export Progress");
KProgressDialog dialog( taskview, 0, title );
dialog.setAutoClose( true );
dialog.setAllowCancel( true );
dialog.progressBar()->setMaximum( 2 * taskview->count() );
// The default dialog was not displaying all the text in the title bar.
int width = taskview->fontMetrics().width(title) * 3;
QSize dialogsize;
dialogsize.setWidth(width);
dialog.setInitialSize( dialogsize );
if ( taskview->count() > 1 ) dialog.show();
QString retval;
// Find max task depth
int tasknr = 0;
while ( tasknr < taskview->count() && !dialog.wasCancelled() )
{
dialog.progressBar()->setValue( dialog.progressBar()->value() + 1 );
if ( tasknr % 15 == 0 ) kapp->processEvents(); // repainting is slow
if ( taskview->itemAt(tasknr)->depth() > maxdepth )
maxdepth = taskview->itemAt(tasknr)->depth();
tasknr++;
}
// Export to file
tasknr = 0;
while ( tasknr < taskview->count() && !dialog.wasCancelled() )
{
task = taskview->itemAt( tasknr );
dialog.progressBar()->setValue( dialog.progressBar()->value() + 1 );
if ( tasknr % 15 == 0 ) kapp->processEvents();
// indent the task in the csv-file:
for ( int i=0; i < task->depth(); ++i ) retval += delim;
/*
// CSV compliance
// Surround the field with quotes if the field contains
// a comma (delim) or a double quote
if (task->name().contains(delim) || task->name().contains(dquote))
to_quote = true;
else
to_quote = false;
*/
to_quote = true;
if (to_quote)
retval += dquote;
// Double quotes replaced by a pair of consecutive double quotes
retval += task->name().replace( dquote, double_dquote );
if (to_quote)
retval += dquote;
// maybe other tasks are more indented, so to align the columns:
for ( int i = 0; i < maxdepth - task->depth(); ++i ) retval += delim;
retval += delim + formatTime( task->sessionTime(),
rc.decimalMinutes )
+ delim + formatTime( task->time(),
rc.decimalMinutes )
+ delim + formatTime( task->totalSessionTime(),
rc.decimalMinutes )
+ delim + formatTime( task->totalTime(),
rc.decimalMinutes )
+ '\n';
tasknr++;
}
// save, either locally or remote
if ((rc.url.isLocalFile()) || (!rc.url.url().contains("/")))
{
QString filename=rc.url.toLocalFile();
if (filename.isEmpty()) filename=rc.url.url();
QFile f( filename );
if( !f.open( QIODevice::WriteOnly ) )
{
err = i18n( "Could not open \"%1\".", filename );
}
if (err.length()==0)
{
QTextStream stream(&f);
// Export to file
stream << retval;
f.close();
}
}
else // use remote file
{
KTemporaryFile tmpFile;
if ( !tmpFile.open() ) err = QString::fromLatin1( "Unable to get temporary file" );
else
{
QTextStream stream ( &tmpFile );
stream << retval;
stream.flush();
if (!KIO::NetAccess::upload( tmpFile.fileName(), rc.url, 0 )) err=QString::fromLatin1("Could not upload");
}
}
return err;
}
int todaySeconds (const QDate &date, const KCalCore::Event::Ptr &event)
{
if ( !event )
return 0;
kDebug(5970) << "found an event for task, event=" << event->uid();
KDateTime startTime=event->dtStart();
KDateTime endTime=event->dtEnd();
KDateTime NextMidNight=startTime;
NextMidNight.setTime(QTime ( 0,0 ));
NextMidNight=NextMidNight.addDays(1);
// LastMidNight := mdate.setTime(0:00) as it would read in a decent programming language
KDateTime LastMidNight=KDateTime::currentLocalDateTime();
LastMidNight.setDate(date);
LastMidNight.setTime(QTime(0,0));
int secsstartTillMidNight=startTime.secsTo(NextMidNight);
int secondsToAdd=0; // seconds that need to be added to the actual cell
if ( (startTime.date()==date) && (event->dtEnd().date()==date) ) // all the event occurred today
secondsToAdd=startTime.secsTo(endTime);
if ( (startTime.date()==date) && (endTime.date()>date) ) // the event started today, but ended later
secondsToAdd=secsstartTillMidNight;
if ( (startTime.date()<date) && (endTime.date()==date) ) // the event started before today and ended today
secondsToAdd=LastMidNight.secsTo(event->dtEnd());
if ( (startTime.date()<date) && (endTime.date()>date) ) // the event started before today and ended after
secondsToAdd=86400;
return secondsToAdd;
}
// export history report as csv, all tasks X all dates in one block
QString timetrackerstorage::exportcsvHistory (TaskView *taskview,
const QDate &from,
const QDate &to,
const ReportCriteria &rc)
{
kDebug(5970) << "Entering function";
QString delim = rc.delimiter;
const QString cr = QString::fromLatin1("\n");
QString err=QString::null;
QString retval;
Task* task;
const int intervalLength = from.daysTo(to)+1;
QMap< QString, QVector<int> > secsForUid;
QMap< QString, QString > uidForName;
// Step 1: Prepare two hashmaps:
// * "uid -> seconds each day": used while traversing events, as uid is their id
// "seconds each day" are stored in a vector
// * "name -> uid", ordered by name: used when creating the csv file at the end
kDebug(5970) << "Taskview Count: " << taskview->count();
for ( int n=0; n<taskview->count(); n++ )
{
task=taskview->itemAt(n);
kDebug(5970) << "n: " << n << ", Task Name: " << task->name() << ", UID: " << task->uid();
// uid -> seconds each day
// * Init each element to zero
QVector<int> vector(intervalLength, 0);
secsForUid[task->uid()] = vector;
// name -> uid
// * Create task fullname concatenating each parent's name
QString fullName;
Task* parentTask;
parentTask = task;
fullName += parentTask->name();
parentTask = parentTask->parent();
while (parentTask)
{
fullName = parentTask->name() + "->" + fullName;
kDebug(5970) << "Fullname(inside): " << fullName;
parentTask = parentTask->parent();
kDebug(5970) << "Parent task: " << parentTask;
}
uidForName[fullName] = task->uid();
kDebug(5970) << "Fullname(end): " << fullName;
}
kDebug(5970) << "secsForUid" << secsForUid;
kDebug(5970) << "uidForName" << uidForName;
// Step 2: For each date, get the events and calculate the seconds
// Store the seconds using secsForUid hashmap, so we don't need to translate uids
// We rely on rawEventsForDate to get the events
kDebug(5970) << "Let's iterate for each date: ";
for ( QDate mdate=from; mdate.daysTo(to)>=0; mdate=mdate.addDays(1) )
{
kDebug(5970) << mdate.toString();
KCalCore::Event::List dateEvents = d->mCalendar->rawEventsForDate(mdate);
for(KCalCore::Event::List::iterator i = dateEvents.begin();i != dateEvents.end(); ++i)
{
kDebug(5970) << "Summary: " << (*i)->summary() << ", Related to uid: " << (*i)->relatedTo();
kDebug(5970) << "Today's seconds: " << todaySeconds(mdate, *i);
secsForUid[(*i)->relatedTo()][from.daysTo(mdate)] += todaySeconds(mdate, *i);
}
}
// Step 3: For each task, generate the matching row for the CSV file
// We use the two hashmaps to have direct access using the task name
// First CSV file line
// FIXME: localize strings and date formats
retval.append("\"Task name\"");
for (int i=0; i<intervalLength; ++i)
{
retval.append(delim);
retval.append(from.addDays(i).toString());
}
retval.append(cr);
// Rest of the CSV file
QMapIterator<QString, QString> nameUid(uidForName);
double time;
while (nameUid.hasNext())
{
nameUid.next();
retval.append("\"" + nameUid.key() + "\"");
kDebug(5970) << nameUid.key() << ": " << nameUid.value() << endl;
for (int day=0; day<intervalLength; day++)
{
kDebug(5970) << "Secs for day " << day << ":" << secsForUid[nameUid.value()][day];
retval.append(delim);
time = secsForUid[nameUid.value()][day]/60.0;
retval.append(formatTime(time, rc.decimalMinutes));
}
retval.append(cr);
}
kDebug() << "Retval is \n" << retval;
if (rc.bExPortToClipBoard)
taskview->setClipBoardText(retval);
else
{
// store the file locally or remote
if ((rc.url.isLocalFile()) || (!rc.url.url().contains("/")))
{
kDebug(5970) << "storing a local file";
QString filename=rc.url.toLocalFile();
if (filename.isEmpty()) filename=rc.url.url();
QFile f( filename );
if( !f.open( QIODevice::WriteOnly ) )
{
err = i18n( "Could not open \"%1\".", filename );
kDebug(5970) << "Could not open file";
}
kDebug() << "Err is " << err;
if (err.length()==0)
{
QTextStream stream(&f);
kDebug(5970) << "Writing to file: " << retval;
// Export to file
stream << retval;
f.close();
}
}
else // use remote file
{
KTemporaryFile tmpFile;
if ( !tmpFile.open() )
{
err = QString::fromLatin1( "Unable to get temporary file" );
}
else
{
QTextStream stream ( &tmpFile );
stream << retval;
stream.flush();
if (!KIO::NetAccess::upload( tmpFile.fileName(), rc.url, 0 )) err=QString::fromLatin1("Could not upload");
}
}
}
return err;
}
void timetrackerstorage::startTimer(const Task* task, const KDateTime &when)
{
kDebug(5970) << "Entering function; when=" << when;
KCalCore::Event::Ptr e;
e = baseEvent(task);
e->setDtStart(when);
d->mCalendar->addEvent(e);
task->taskView()->scheduleSave();
}
void timetrackerstorage::startTimer(QString taskID)
{
kDebug(5970) << "Entering function";
KCalCore::Todo::List todoList;
KCalCore::Todo::List::ConstIterator todo;
todoList = d->mCalendar->rawTodos();
for( todo = todoList.constBegin(); todo != todoList.constEnd(); ++todo )
{
kDebug(5970) << (*todo)->uid();
kDebug(5970) << taskID;
if ( (*todo)->uid() == taskID )
{
kDebug(5970) << "adding event";
KCalCore::Event::Ptr e;
e = baseEvent((*todo));
e->setDtStart(KDateTime::currentLocalDateTime());
d->mCalendar->addEvent(e);
}
}
saveCalendar();
}
void timetrackerstorage::stopTimer(const Task* task, const QDateTime &when)
{
kDebug(5970) << "Entering function; when=" << when;
KCalCore::Event::List eventList = d->mCalendar->rawEvents();
for(KCalCore::Event::List::iterator i = eventList.begin();
i != eventList.end();
++i)
{
if ( (*i)->relatedTo() == task->uid() )
{
kDebug(5970) << "found an event for task, event=" << (*i)->uid();
if (!(*i)->hasEndDate())
{
kDebug(5970) << "this event has no enddate";
(*i)->setDtEnd(KDateTime(when, KDateTime::Spec::LocalZone()));
}
else
{
kDebug(5970) << "this event has an enddate";
kDebug(5970) << "end date is " << (*i)->dtEnd();
}
};
}
saveCalendar();
}
void timetrackerstorage::changeTime(const Task* task, const long deltaSeconds)
{
kDebug(5970) << "Entering function; deltaSeconds=" << deltaSeconds;
KCalCore::Event::Ptr e;
QDateTime end;
e = baseEvent(task);
// Don't use duration, as ICalFormatImpl::writeIncidence never writes a
// duration, even though it looks like it's used in event.cpp.
end = task->startTime();
if ( deltaSeconds > 0 ) end = task->startTime().addSecs(deltaSeconds);
e->setDtEnd(KDateTime(end, KDateTime::Spec::LocalZone()));
// Use a custom property to keep a record of negative durations
e->setCustomProperty( KGlobal::mainComponent().componentName().toUtf8(),
QByteArray("duration"),
QString::number(deltaSeconds));
d->mCalendar->addEvent(e);
task->taskView()->scheduleSave();
}
KCalCore::Event::Ptr timetrackerstorage::baseEvent(const Task *task)
{
kDebug(5970) << "Entering function";
KCalCore::Event::Ptr e( new KCalCore::Event() );
QStringList categories;
e->setSummary(task->name());
// Can't use setRelatedToUid()--no error, but no RelatedTo written to disk
e->setRelatedTo( task->uid() );
// Debugging: some events where not getting a related-to field written.
Q_ASSERT(e->relatedTo() == task->uid());
// Have to turn this off to get datetimes in date fields.
e->setAllDay(false);
e->setDtStart(KDateTime(task->startTime(), KDateTime::Spec::LocalZone()));
// So someone can filter this mess out of their calendar display
categories.append(i18n("KTimeTracker"));
e->setCategories(categories);
return e;
}
KCalCore::Event::Ptr timetrackerstorage::baseEvent(const KCalCore::Todo::Ptr &todo)
{
kDebug(5970) << "Entering function";
KCalCore::Event::Ptr e( new KCalCore::Event() );
QStringList categories;
e->setSummary(todo->summary());
// Can't use setRelatedToUid()--no error, but no RelatedTo written to disk
e->setRelatedTo( todo->uid() );
// Have to turn this off to get datetimes in date fields.
e->setAllDay(false);
e->setDtStart(KDateTime(todo->dtStart()));
// So someone can filter this mess out of their calendar display
categories.append(i18n("KTimeTracker"));
e->setCategories(categories);
return e;
}
HistoryEvent::HistoryEvent(const QString &uid, const QString &name,
long duration, const KDateTime &start,
const KDateTime &stop, const QString &todoUid)
{
_uid = uid;
_name = name;
_duration = duration;
_start = start;
_stop = stop;
_todoUid = todoUid;
}
bool timetrackerstorage::isRemoteFile( const QString &file ) const
{
kDebug(5970) << "Entering function";
QString f = file.toLower();
bool rval = f.startsWith( QLatin1String("http://") ) || f.startsWith( QLatin1String("ftp://") );
kDebug(5970) << "timetrackerstorage::isRemoteFile(" << file <<" ) returns" << rval;
return rval;
}
QString timetrackerstorage::saveCalendar()
{
kDebug(5970) << "Entering function";
bool removedFromDirWatch = false;
if ( KDirWatch::self()->contains( d->mICalFile ) ) {
KDirWatch::self()->removeFile( d->mICalFile );
removedFromDirWatch = true;
}
QString errorMessage;
if ( d->mCalendar ) {
d->m_fileLock->lock();
} else {
kDebug() << "mCalendar not set";
return errorMessage;
}
if ( !d->mCalendar->save() ) {
errorMessage = QString("Could not save. Could lock file.");
}
d->m_fileLock->unlock();
if ( removedFromDirWatch ) {
KDirWatch::self()->addFile( d->mICalFile );
}
return errorMessage;
}
KTTCalendar::Ptr timetrackerstorage::calendar() const
{
return _calendar;
}