- recover from backup files
[Sak.git] / task.cpp
blob7b6e4b7c6c8796c432c5953fc8ed677925e44c60
1 #include "task.h"
2 #include <math.h>
5 QXmlStreamWriter & operator<< ( QXmlStreamWriter & out, const Task & task )
7 out.writeStartElement("Task");
9 out.writeAttribute("title", task.title);
10 out.writeAttribute("tags", task.tags.join(","));
11 out.writeAttribute("active", QString("%1").arg(task.active));
12 if (task.dueDate.isValid())
13 out.writeAttribute("due", task.dueDate.toString("dd/MM/yyyy"));
14 out.writeAttribute("estimated", QString("%1").arg(task.estimatedHours));
15 out.writeAttribute("bgcolor", task.bgColor.name());
16 out.writeAttribute("fgcolor", task.fgColor.name());
17 out.writeAttribute("url", QUrl::toPercentEncoding(task.url));
19 out.writeStartElement("Description");
20 out.writeCharacters(task.description);
21 out.writeEndElement();
23 out.writeStartElement("Icon");
24 QByteArray bytes;
25 QBuffer buffer(&bytes);
26 buffer.open(QIODevice::WriteOnly);
27 task.icon.save(&buffer, "PNG"); // writes pixmap into bytes in PNG format
28 out.writeCharacters(bytes.toBase64());
29 out.writeEndElement();
31 out.writeStartElement("Subtasks");
32 QHash< QString, QList< Task::Hit > >::const_iterator itr = task.hits.begin(), end = task.hits.end();
33 while(itr != end) {
34 out.writeStartElement("Subtask");
35 out.writeAttribute("title", itr.key());
36 QHash< QString, Task::SubTask >::const_iterator sitr = task.subTasks.find(itr.key());
37 if (sitr != task.subTasks.end()) {
38 const Task::SubTask& st(sitr.value());
39 if (st.bgColor.isValid())
40 out.writeAttribute("bgcolor", st.bgColor.name());
41 if (st.fgColor.isValid())
42 out.writeAttribute("fgcolor", st.fgColor.name());
43 out.writeAttribute("active", QString("%1").arg(st.active));
44 out.writeStartElement("Description");
45 out.writeCharacters(st.description);
46 out.writeEndElement();
48 out.writeStartElement("Hits");
49 const QList<Task::Hit>& h(itr.value());
50 for(int i=h.count()-1; i>=0; i--) {
51 if (h[i].timestamp.isValid()) {
52 out.writeEmptyElement("h");
53 out.writeAttribute("t", h[i].timestamp.toString(DATETIMEFORMAT));
54 out.writeAttribute("d", QString("%1").arg(h[i].duration));
57 out.writeEndElement();
58 out.writeEndElement();
59 itr++;
61 out.writeEndElement();
63 out.writeEndElement();
64 return out;
69 void parseHits(QXmlStreamReader & in, Task & task, const QString subtask)
71 QMultiMap<int, Task::Hit> sortedHits;
72 while(!in.atEnd()) {
73 QXmlStreamReader::TokenType token = in.readNext();
74 if (token == QXmlStreamReader::StartElement) {
75 QString elementName = in.name().toString();
76 if (0) {
77 } else if (elementName.compare("h", Qt::CaseInsensitive) == 0){
78 Task::Hit hit;
79 QXmlStreamAttributes attrs = in.attributes();
80 hit.timestamp = QDateTime::fromString(attrs.value("t").toString(), DATETIMEFORMAT);
81 hit.duration = attrs.value("d").toString().toUInt();
82 sortedHits.insertMulti(hit.timestamp.toTime_t(), hit);
84 } else if (token == QXmlStreamReader::EndElement && in.name().toString().compare("hits", Qt::CaseInsensitive) == 0) {
85 break;
89 // remove duplicates
90 QMultiMap<int, Task::Hit>::iterator itr = sortedHits.begin();
91 Task::Hit prev;
92 while(itr != sortedHits.end()) {
93 if (itr.value() == prev)
94 itr = sortedHits.erase(itr);
95 else {
96 prev = itr.value();
97 itr++;
100 task.hits[subtask] = sortedHits.values();
105 void parseSubtask(QXmlStreamReader & in, Task & task)
107 QXmlStreamAttributes attrs = in.attributes();
108 QString title = attrs.value("title").toString();
109 QStringList tags = attrs.value("tags").toString().split(QRegExp("[,:]"));
110 Task::SubTask& st(task.subTasks[title]);
111 QStringRef bgColor = attrs.value("bgcolor");
112 QStringRef fgColor = attrs.value("fgcolor");
113 QStringRef active = attrs.value("active");
114 st.title = title;
115 if (!bgColor.isEmpty())
116 st.bgColor = QColor(bgColor.toString());
117 if (!fgColor.isEmpty())
118 st.fgColor = QColor(fgColor.toString());
119 if (!active.isEmpty())
120 st.active = active.toString().toUInt();
122 // read subtask
123 while(!in.atEnd()) {
124 QXmlStreamReader::TokenType token = in.readNext();
125 if (token == QXmlStreamReader::StartElement) {
126 QString elementName = in.name().toString();
127 if (0) {
128 } else if (elementName.compare("description", Qt::CaseInsensitive) == 0){
129 st.description = in.readElementText();
130 } else if (elementName.compare("hits", Qt::CaseInsensitive) == 0) {
131 parseHits(in, task, st.title);
133 } else if (token == QXmlStreamReader::EndElement && in.name().toString().compare("subtask", Qt::CaseInsensitive) == 0) {
134 return;
139 void parseSubtasks(QXmlStreamReader & in, Task & task)
141 while(!in.atEnd()) {
142 QXmlStreamReader::TokenType token = in.readNext();
143 if (token == QXmlStreamReader::StartElement ) {
144 QString elementName = in.name().toString();
145 if (0) {
146 } else if (elementName.compare("subtask", Qt::CaseInsensitive) == 0) {
147 parseSubtask(in, task);
149 } else {
150 //qDebug() << " > token type " << token;
156 QXmlStreamReader & operator>> ( QXmlStreamReader & in, Task & task )
158 QXmlStreamReader::TokenType token = in.readNext();
159 if (!in.atEnd() && token != QXmlStreamReader::StartElement) {
160 in.raiseError("Expected start of Task element, got " + QString("%1").arg(token));
162 if (!in.atEnd() && in.name().toString().compare("Task",Qt::CaseInsensitive)) {
163 in.raiseError("Expected token name \"Task\", got " + in.name().toString());
165 if (!in.atEnd()) {
166 QXmlStreamAttributes attrs = in.attributes();
167 task.title = attrs.value("title").toString();
168 task.tags = attrs.value("tags").toString().split(QRegExp("[:,]"));
169 task.url = QUrl::fromPercentEncoding(attrs.value("url").toString().toUtf8());
170 QStringRef bgColor = attrs.value("bgcolor");
171 QStringRef fgColor = attrs.value("fgcolor");
172 QStringRef active = attrs.value("active");
173 QStringRef due = attrs.value("due");
174 QStringRef estimated = attrs.value("estimated");
175 if (!bgColor.isEmpty())
176 task.bgColor = QColor(bgColor.toString());
177 if (!fgColor.isEmpty())
178 task.fgColor = QColor(fgColor.toString());
179 if (!active.isEmpty())
180 task.active = active.toString().toUInt();
181 if (!estimated.isEmpty())
182 task.estimatedHours = estimated.toString().toUInt();
183 if (!due.isEmpty())
184 task.dueDate = QDate::fromString(due.toString(), "dd/MM/yyyy");
186 QXmlStreamReader::TokenType token;
187 while(!in.atEnd()) {
188 token = in.readNext();
189 if (token == QXmlStreamReader::StartElement) {
190 QString elementName = in.name().toString();
191 if (0) {
192 } else if (elementName.compare("description", Qt::CaseInsensitive) == 0) {
193 task.description = in.readElementText();
194 } else if (elementName.compare("icon", Qt::CaseInsensitive) == 0) {
195 task.icon.loadFromData(QByteArray::fromBase64(in.readElementText().toAscii()), "PNG");
196 } else if (elementName.compare("subtasks", Qt::CaseInsensitive) == 0) {
197 parseSubtasks(in, task);
199 } else {
200 //qDebug() << "token type " << token;
204 return in;
210 double Task::workedHours(const QDateTime& from, const QDateTime& to) const
212 double worked = 0;
213 QHash<QString, QList<Task::Hit > >::const_iterator hitr = hits.begin(), hend = hits.end();
214 while(hitr != hend) {
215 const QList<Task::Hit>& l(hitr.value());
216 for(int i=l.count()-1; i>=0; i--) {
217 const Task::Hit& hit ( l[i] );
218 if ( hit.timestamp < from) return worked;
219 if ( hit.timestamp > to ) continue;
220 else {
221 worked += hours(hit.duration); // multiple of quarter of hour
224 hitr++;
226 return worked;
230 void Task::updateSubTasks()
232 const QList<QString>& subtasks( hits.keys() );
233 for(int i=0; i<subtasks.count(); i++) {
234 if (!subTasks.contains(subtasks[i])) {
235 subTasks[subtasks[i]].title = subtasks[i];
240 bool Task::checkConsistency()
242 QHash<QString, QList<Task::Hit > >::const_iterator hitr = hits.begin(), hend = hits.end();
243 totHours = 0;
244 totOverestimation = 0;
245 // ensure tasks are in list
246 updateSubTasks();
247 while(hitr != hend) {
248 const QList<Task::Hit>& l(hitr.value());
249 QList<HitElement> hitlist;
250 for(int i=0; i<l.count(); i++) {
251 hitlist << HitElement(this, hitr.key(), l[i].timestamp, l[i].duration);
253 double subtaskHours, subtaskOverestimation;
254 QVector<double> o;
255 subtaskHours = HitElement::overestimations(hitlist, o, subtaskOverestimation);
256 subTasks[hitr.key()].totHours = subtaskHours;
257 totHours += subtaskHours;
258 totOverestimation +=subtaskOverestimation;
259 hitr++;
261 return totHours >= 0;
264 // QPair<QDateTime, quint8>* Task::findHit(QDateTime t, quint8 d)
265 // {
266 // QList< QPair<QDateTime, quint8> >::const_iterator itr = hits.begin(), end = hits.end();
267 // for (int i=0; i<hits.count(); i++) {
268 // if (hits[i].first == t && hits[i].second == d)
269 // return i;
270 // }
271 // return -1;
272 // }
275 // returns tot time
276 double HitElement::overestimations(const QList<HitElement>& hits, QVector<double>& overestimation, double& totOverestimation)
278 QList<HitElement> sortedList(hits);
279 double totHours = 0;
280 qSort(sortedList);
282 QDateTime t = QDateTime::currentDateTime();
283 overestimation.resize(sortedList.count());
284 totOverestimation=0;
285 int count = sortedList.count()-1;
286 for (int i=count; i>=0; i--) {
287 if (sortedList[i].timestamp > t) {
288 overestimation[i] = ( sortedList[i].timestamp.toTime_t() - t.toTime_t() ) / 60;
289 //qWarning() << "SAK: inconsistency in task " << sortedList[i].task->title << ": " << sortedList[i].timestamp << " > " << t;
290 totHours = -1;
291 return totHours;
292 } else {
293 totHours += Task::hours(sortedList[i].duration);
294 qint64 expected = (t.toTime_t() - sortedList[i].timestamp.toTime_t());
295 qint64 got = Task::hours(sortedList[i].duration)*3600; //seconds
296 qint64 diff = expected - got;
297 if (diff < 0) {
298 overestimation[i] = 0;
299 totOverestimation -= diff;
300 } if (diff < -59) {
301 if (i < count) {
302 overestimation[i+1] = qCeil(-diff/120);
303 overestimation[i] = qCeil(-diff/120);
305 overestimation[i] = qCeil(-diff/120);
306 //qDebug() << "SAK: task " << sortedList[i].task->title << ": overestimated time from hit " << sortedList[i].timestamp << " and hit " << t << ". Expected " << expected/60 << " minutes, got " << got/60 << " (diff = " << diff/60 << ")";
308 t=sortedList[i].timestamp;
311 if (totOverestimation > 60) {
312 totOverestimation /= 3600.0;
314 return totHours;