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