File: Task.cpp

package info (click to toggle)
charmtimetracker 1.12.0-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 3,340 kB
  • sloc: cpp: 19,176; xml: 284; python: 120; makefile: 14
file content (379 lines) | stat: -rw-r--r-- 11,416 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/*
  Task.cpp

  This file is part of Charm, a task-based time tracking application.

  Copyright (C) 2007-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com

  Author: Mirko Boehm <mirko.boehm@kdab.com>
  Author: Frank Osterfeld <frank.osterfeld@kdab.com>
  Author: Mike McQuaid <mike.mcquaid@kdab.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, see <http://www.gnu.org/licenses/>.
*/

#include "Task.h"
#include "CharmConstants.h"
#include "CharmExceptions.h"

#include <QtDebug>

#include <set>
#include <algorithm>

Task::Task()
{
}

Task::Task(TaskId id, const QString &name, TaskId parent, bool subscribed)
    : m_id(id)
    , m_parent(parent)
    , m_name(name)
    , m_subscribed(subscribed)
{
}

bool Task::isValid() const
{
    return id() != 0;
}

QString Task::tagName()
{
    static const QString tag(QStringLiteral("task"));
    return tag;
}

QString Task::taskListTagName()
{
    static const QString tag(QStringLiteral("tasks"));
    return tag;
}

bool Task::operator ==(const Task &other) const
{
    return other.id() == id()
           && other.parent() == parent()
           && other.name() == name()
           && other.subscribed() == subscribed()
           && other.m_trackable == m_trackable
           && other.validFrom() == validFrom()
           && other.validUntil() == validUntil();
}

TaskId Task::id() const
{
    return m_id;
}

void Task::setId(TaskId id)
{
    m_id = id;
}

QString Task::name() const
{
    return m_name;
}

void Task::setName(const QString &name)
{
    m_name = name;
}

int Task::parent() const
{
    return m_parent;
}

void Task::setParent(int parent)
{
    m_parent = parent;
}

bool Task::subscribed() const
{
    return m_subscribed;
}

void Task::setSubscribed(bool value)
{
    m_subscribed = value;
}

bool Task::trackable() const
{
    return m_trackable;
}

void Task::setTrackable(bool trackable)
{
    m_trackable = trackable;
}

QDateTime Task::validFrom() const
{
    return m_validFrom;
}

void Task::setValidFrom(const QDateTime &stamp)
{
    m_validFrom = stamp;
    QTime time(m_validFrom.time());
    time.setHMS(time.hour(), time.minute(), time.second());
    m_validFrom.setTime(time);
}

QDateTime Task::validUntil() const
{
    return m_validUntil;
}

void Task::setValidUntil(const QDateTime &stamp)
{
    m_validUntil = stamp;
    QTime time(m_validUntil.time());
    time.setHMS(time.hour(), time.minute(), time.second());
    m_validUntil.setTime(time);
}

bool Task::isCurrentlyValid() const
{
    return isValid()
           && (!validFrom().isValid() || validFrom() < QDateTime::currentDateTime())
           && (!validUntil().isValid() || validUntil() > QDateTime::currentDateTime());
}

void Task::dump() const
{
    qDebug() << "[Task " << this << "] task id:" << id() << "- name:" << name()
             << " - parent:" << parent() << " - subscribed:" << subscribed()
             << " - valid from:" << validFrom() << " - valid until:"
             << validUntil() << " - trackable:" << trackable();
}

void dumpTaskList(const TaskList &tasks)
{
    qDebug() << "dumpTaskList: task list of" << tasks.size() << "elements";
    for (int i = 0; i < tasks.size(); ++i)
        tasks[i].dump();
}

// FIXME make XmlSerializable interface, with tagName/toXml/fromXml:
const QString TaskIdElement(QStringLiteral("taskid"));
const QString TaskParentId(QStringLiteral("parentid"));
const QString TaskSubscribed(QStringLiteral("subscribed"));
const QString TaskTrackable(QStringLiteral("trackable"));
const QString TaskComment(QStringLiteral("comment"));
const QString TaskValidFrom(QStringLiteral("validfrom"));
const QString TaskValidUntil(QStringLiteral("validuntil"));

QDomElement Task::toXml(QDomDocument document) const
{
    QDomElement element = document.createElement(tagName());
    element.setAttribute(TaskIdElement, id());
    element.setAttribute(TaskParentId, parent());
    element.setAttribute(TaskSubscribed, (subscribed() ? 1 : 0));
    element.setAttribute(TaskTrackable, (trackable() ? 1 : 0));
    if (!name().isEmpty()) {
        QDomText taskName = document.createTextNode(name());
        element.appendChild(taskName);
    }
    if (validFrom().isValid())
        element.setAttribute(TaskValidFrom, validFrom().toString(Qt::ISODate));
    if (validUntil().isValid())
        element.setAttribute(TaskValidUntil, validUntil().toString(Qt::ISODate));
    return element;
}

Task Task::fromXml(const QDomElement &element, int databaseSchemaVersion)
{   // in case any task object creates trouble with
    // serialization/deserialization, add an object of it to
    // void XmlSerializationTests::testTaskSerialization()
    if (element.tagName() != tagName())
        throw XmlSerializationException(QObject::tr(
                                            "Task::fromXml: judging from the tag name, this is not a task tag"));

    Task task;
    bool ok;
    task.setName(element.text());
    task.setId(element.attribute(TaskIdElement).toInt(&ok));
    if (!ok)
        throw XmlSerializationException(QObject::tr("Task::fromXml: invalid task id"));
    task.setParent(element.attribute(TaskParentId).toInt(&ok));
    if (!ok)
        throw XmlSerializationException(QObject::tr("Task::fromXml: invalid parent task id"));
    task.setSubscribed(element.attribute(TaskSubscribed).toInt(&ok) == 1);
    if (!ok)
        throw XmlSerializationException(QObject::tr("Task::fromXml: invalid subscription setting"));

    if (databaseSchemaVersion > CHARM_DATABASE_VERSION_BEFORE_TASK_EXPIRY) {
        if (element.hasAttribute(TaskValidFrom)) {
            QDateTime start = QDateTime::fromString(element.attribute(TaskValidFrom), Qt::ISODate);
            if (!start.isValid()) throw XmlSerializationException(QObject::tr(
                                                                      "Task::fromXml: invalid valid-from date"));

            task.setValidFrom(start);
        }
        if (element.hasAttribute(TaskValidUntil)) {
            QDateTime end = QDateTime::fromString(element.attribute(TaskValidUntil), Qt::ISODate);
            if (!end.isValid()) throw XmlSerializationException(QObject::tr(
                                                                    "Task::fromXml: invalid valid-until date"));

            task.setValidUntil(end);
        }
    }
    if (databaseSchemaVersion > CHARM_DATABASE_VERSION_BEFORE_TRACKABLE) {
        task.setTrackable(element.attribute(TaskTrackable, QStringLiteral("1")).toInt(&ok) == 1);
        if (!ok)
            throw XmlSerializationException(QObject::tr("Task::fromXml: invalid trackable settings"));

    }
    if (element.hasAttribute(TaskComment))
        task.setComment(element.attribute(TaskComment));
    return task;
}

TaskList Task::readTasksElement(const QDomElement &element, int databaseSchemaVersion)
{
    if (element.tagName() == taskListTagName()) {
        TaskList tasks;
        for (QDomElement child = element.firstChildElement(); !child.isNull();
             child = child.nextSiblingElement(tagName())) {
            if (child.tagName() != tagName())
                throw XmlSerializationException(QObject::tr(
                                                    "Task::readTasksElement: parent-child mismatch"));

            Task task = fromXml(child, databaseSchemaVersion);
            tasks << task;
        }
        return tasks;
    } else {
        throw XmlSerializationException(QObject::tr(
                                            "Task::readTasksElement: judging by the tag name, this is not a tasks element"));
    }
}

QDomElement Task::makeTasksElement(QDomDocument document, const TaskList &tasks)
{
    QDomElement element = document.createElement(taskListTagName());
    Q_FOREACH (const Task &task, tasks)
        element.appendChild(task.toXml(document));
    return element;
}

bool Task::lowerTaskId(const Task &left, const Task &right)
{
    return left.id() < right.id();
}

QString Task::comment() const
{
    return m_comment;
}

void Task::setComment(const QString &comment)
{
    m_comment = comment;
}

bool Task::checkForUniqueTaskIds(const TaskList &tasks)
{
    std::set<TaskId> ids;

    for (TaskList::const_iterator it = tasks.begin(); it != tasks.end(); ++it)
        ids.insert((*it).id());

    return static_cast<int>(ids.size()) == tasks.size();
}

/** collectTaskIds visits the task and all subtasks recursively, and
 * adds all visited task ids to visitedIds.
 * @returns false if any visited task id is already in visitedIds
 * @param id the parent task to traverse
 * @param visitedIds reference to a TaskId set that contains already
 * visited task ids
 * @param tasks the tasklist to process
 */
bool collectTaskIds(std::set<TaskId> &visitedIds, TaskId id, const TaskList &tasks)
{
    bool foundSelf = false;
    TaskIdList children;

    // find children and the task itself (the parameter tasks is not sorted)
    for (TaskList::const_iterator it = tasks.begin(); it != tasks.end(); ++it) {
        if ((*it).parent() == id)
            children << (*it).id();
        if ((*it).id() == id) {
            // just checking that it really exists...
            if (std::find(visitedIds.begin(), visitedIds.end(), id) != visitedIds.end()) {
                return false;
            } else {
                if ((*it).isValid()) {
                    visitedIds.insert(id);
                    foundSelf = true;
                } else {
                    return false;
                }
            }
        }
    }

    if (!foundSelf)
        return false;

    Q_FOREACH (const TaskId i, children)
        collectTaskIds(visitedIds, i, tasks);

    return true;
}

/** checkForTreeness checks a task list against cycles in the
 * parent-child relationship, and for orphans (tasks where the parent
 * task does not exist). If the task list contains invalid tasks,
 * false is returned as well.
 *
 * @return false, if cycles in the task tree or orphans have been found
 * @param tasks the tasklist to verify
 */
bool Task::checkForTreeness(const TaskList &tasks)
{
    std::set<TaskId> ids;

    for (TaskList::const_iterator it = tasks.begin(); it != tasks.end(); ++it) {
        if (!(*it).isValid())
            return false;
        if ((*it).parent() == 0) {
            if (!collectTaskIds(ids, (*it).id(), tasks))
                return false;
        }
    }

    // the count of ids now must be equal to the count of tasks,
    // otherwise tasks contains elements that are not in the subtrees
    // of toplevel elements
    if (ids.size() != static_cast<unsigned>(tasks.size())) {
#ifndef NDEBUG
        Q_FOREACH (const Task &task, tasks) {
            if (find(ids.begin(), ids.end(), task.id()) == ids.end()) {
                qDebug() << "Orphan task:";
                task.dump();
            }
        }
#endif
        return false;
    }

    return true;
}