1 /*
2 * Copyright (C) 2003, 2004 by Mark Bucciarelli <mark@hubcapconsutling.com>
3 * Copyright (C) 2019 Alexander Potashev <aspotashev@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the
17 * Free Software Foundation, Inc.
18 * 51 Franklin Street, Fifth Floor
19 * Boston, MA 02110-1301 USA.
20 *
21 */
22
23 #include "csvhistory.h"
24
25 #include "ktimetrackerutility.h"
26 #include "ktt_debug.h"
27 #include "model/projectmodel.h"
28 #include "model/task.h"
29 #include "model/tasksmodel.h"
30
todaySeconds(const QDate & date,const KCalendarCore::Event::Ptr & event)31 static int64_t todaySeconds(const QDate &date, const KCalendarCore::Event::Ptr &event)
32 {
33 if (!event) {
34 return 0;
35 }
36
37 qCDebug(KTT_LOG) << "found an event for task, event=" << event->uid();
38 QDateTime startTime = event->dtStart();
39 QDateTime endTime = event->dtEnd();
40 QDateTime NextMidNight = startTime;
41 NextMidNight.setTime(QTime(0, 0));
42 NextMidNight = NextMidNight.addDays(1);
43 // LastMidNight := mdate.setTime(0:00) as it would read in a decent programming language
44 QDateTime LastMidNight = QDateTime::currentDateTime();
45 LastMidNight.setDate(date);
46 LastMidNight.setTime(QTime(0, 0));
47 int64_t secsstartTillMidNight = startTime.secsTo(NextMidNight);
48 int64_t secondsToAdd = 0; // seconds that need to be added to the actual cell
49
50 if (startTime.date() == date && event->dtEnd().date() == date) {
51 // all the event occurred today
52 secondsToAdd = startTime.secsTo(endTime);
53 }
54
55 if (startTime.date() == date && endTime.date()>date) {
56 // the event started today, but ended later
57 secondsToAdd = secsstartTillMidNight;
58 }
59
60 if (startTime.date() < date && endTime.date() == date) {
61 // the event started before today and ended today
62 secondsToAdd = LastMidNight.secsTo(event->dtEnd());
63 }
64
65 if (startTime.date() < date && endTime.date() > date) {
66 // the event started before today and ended after
67 secondsToAdd = 86400;
68 }
69
70 return secondsToAdd;
71 }
72
exportCSVHistoryToString(ProjectModel * projectModel,const ReportCriteria & rc)73 QString exportCSVHistoryToString(ProjectModel *projectModel, const ReportCriteria &rc)
74 {
75 const QDate &from = rc.from;
76 const QDate &to = rc.to;
77 QString delim = rc.delimiter;
78 const QString cr = QStringLiteral("\n");
79 const int64_t intervalLength = from.daysTo(to) + 1;
80 QMap<QString, QVector<int64_t>> secsForUid;
81 QMap<QString, QString> uidForName;
82
83 QString retval;
84
85 // Step 1: Prepare two hashmaps:
86 // * "uid -> seconds each day": used while traversing events, as uid is their id
87 // "seconds each day" are stored in a vector
88 // * "name -> uid", ordered by name: used when creating the csv file at the end
89 auto tasks = projectModel->tasksModel()->getAllTasks();
90 qCDebug(KTT_LOG) << "Tasks count: " << tasks.size();
91 for (Task *task : tasks) {
92 qCDebug(KTT_LOG) << ", Task Name: " << task->name() << ", UID: " << task->uid();
93 // uid -> seconds each day
94 // * Init each element to zero
95 QVector<int64_t> vector(intervalLength, 0);
96 secsForUid[task->uid()] = vector;
97
98 // name -> uid
99 // * Create task fullname concatenating each parent's name
100 QString fullName;
101 Task* parentTask;
102 parentTask = task;
103 fullName += parentTask->name();
104 parentTask = parentTask->parentTask();
105 while (parentTask) {
106 fullName = parentTask->name() + "->" + fullName;
107 qCDebug(KTT_LOG) << "Fullname(inside): " << fullName;
108 parentTask = parentTask->parentTask();
109 qCDebug(KTT_LOG) << "Parent task: " << parentTask;
110 }
111
112 uidForName[fullName] = task->uid();
113
114 qCDebug(KTT_LOG) << "Fullname(end): " << fullName;
115 }
116
117 qCDebug(KTT_LOG) << "secsForUid" << secsForUid;
118 qCDebug(KTT_LOG) << "uidForName" << uidForName;
119
120 std::unique_ptr<FileCalendar> calendar = projectModel->asCalendar(QUrl());
121
122 // Step 2: For each date, get the events and calculate the seconds
123 // Store the seconds using secsForUid hashmap, so we don't need to translate uids
124 // We rely on rawEventsForDate to get the events
125 qCDebug(KTT_LOG) << "Let's iterate for each date: ";
126 int dayCount = 0;
127 for (QDate mdate = from; mdate.daysTo(to) >= 0; mdate = mdate.addDays(1)) {
128 if (dayCount++ > 365 * 100) {
129 return QStringLiteral("too many days to process");
130 }
131
132 qCDebug(KTT_LOG) << mdate.toString();
133 for (const auto &event : calendar->rawEventsForDate(mdate)) {
134 qCDebug(KTT_LOG) << "Summary: " << event->summary() << ", Related to uid: " << event->relatedTo();
135 qCDebug(KTT_LOG) << "Today's seconds: " << todaySeconds(mdate, event);
136 secsForUid[event->relatedTo()][from.daysTo(mdate)] += todaySeconds(mdate, event);
137 }
138 }
139
140
141 // Step 3: For each task, generate the matching row for the CSV file
142 // We use the two hashmaps to have direct access using the task name
143
144 // First CSV file line
145 // FIXME: localize strings and date formats
146 retval.append("\"Task name\"");
147 for (int i = 0; i < intervalLength; ++i) {
148 retval.append(delim);
149 retval.append(from.addDays(i).toString());
150 }
151 retval.append(cr);
152
153
154 // Rest of the CSV file
155 QMapIterator<QString, QString> nameUid(uidForName);
156 double time;
157 while (nameUid.hasNext()) {
158 nameUid.next();
159 retval.append(rc.quote + nameUid.key() + rc.quote);
160 qCDebug(KTT_LOG) << nameUid.key() << ": " << nameUid.value() << endl;
161
162 for (int day = 0; day < intervalLength; day++) {
163 qCDebug(KTT_LOG) << "Secs for day " << day << ":" << secsForUid[nameUid.value()][day];
164 retval.append(delim);
165 time = secsForUid[nameUid.value()][day] / 60.0;
166 retval.append(formatTime(time, rc.decimalMinutes));
167 }
168
169 retval.append(cr);
170 }
171
172 qDebug() << "Retval is \n" << retval;
173 return retval;
174 }
175