mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 18:32:51 +00:00
1076 lines
34 KiB
C++
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;
|
|
}
|