Renamed destination to direction for clarity.
[qwerkisync] / EventParsers / CSVSymbianEventLogParser.cpp
1 /*
2  * Copyright (C) 2011, Jamie Thompson
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License as published by the Free Software Foundation; either
7  * version 3 of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; If not, see
16  * <http://www.gnu.org/licenses/>.
17  */
18
19 #include "CSVSymbianEventLogParser.h"
20 #include "EventTypes/PhoneCall.h"
21 #include "Settings.h"
22
23 #include <QDebug>
24
25 #include <QFile>
26 #include <QString>
27 #include <QStringList>
28
29 #include <stdexcept>
30
31 using namespace EventParsers;
32
33 class SortByValueDesc
34 {
35 public:
36         inline bool operator()(const QPair<char, uint> &a, const QPair<char, uint> &b) const
37         {
38                 return b.second < a.second;
39         }
40 };
41
42 const QString ExtractString(const QString &originalString)
43 {
44         QRegExp content("^[\"\']?(\\w*)?[\"\']?$");
45         content.indexIn(originalString.trimmed());
46         return content.cap(1);
47 }
48
49 iEventParser *CSVSymbianEventLogParser::IsValid(const Settings &currentSettings, QFile &eventFile)
50 {
51         qDebug() << "Checking if a CSV call log file...";
52
53         QByteArray firstLineContent(eventFile.readLine());
54         eventFile.seek(0);
55         if(firstLineContent.length() > 0)
56         {
57                 // Count the non-alphanumeric characters used
58                 QHash<char, uint> counts;
59                 foreach(char c, firstLineContent)
60                         ++counts[c];
61
62                 QList<QPair<char, uint> > orderedCounts;
63                 orderedCounts.reserve(counts.size());
64                 foreach(char c, counts.keys())
65                         if(!QChar(c).isLetterOrNumber())
66                                 orderedCounts.append(QPair<char, uint>(c, counts.value(c)));
67
68                 qSort(orderedCounts.begin(), orderedCounts.end(), SortByValueDesc());
69
70                 // Work around Q_FOREACH macro limitation when dealing with
71                 // multi-typed templates (comma issue)
72                 typedef QPair<char, uint> bodge;
73                 foreach(bodge count, orderedCounts)
74                         qDebug() << count.first << " = " << count.second;
75
76                 char delim;
77                 // No-one would be mad enough to use quotation marks or apostrophes
78                 // as their delimiter,but just in case, check the second most
79                 // frequent character is present thr right number of times for
80                 // the qutation marks to be present on every column heading (two
81                 // per heading, less one as they're seperators)
82                 if((orderedCounts.value(0).first == '"' || orderedCounts.value(0).first == '\'')
83                         && ((orderedCounts.value(0).second / 2) - 1 == orderedCounts.value(1).second ))
84                 {
85                         // We're good.
86                         delim = orderedCounts.value(1).first;
87                 }
88                 else
89                         delim = orderedCounts.value(0).first;
90
91                 // Check we have the essential fields we need, and grab their
92                 // column ordering
93                 QStringList requiredHeadings;
94                 requiredHeadings << "etype" << "etime" << "remote" << "direction"
95                                                  << "duration" << "number" << "data";
96
97                 EventParsers::CSVSymbianEventLogParser::ColumnIndicesHash headingPositions;
98                 headingPositions.reserve(requiredHeadings.count());
99
100                 QStringList headings(QString(firstLineContent).split(delim, QString::KeepEmptyParts, Qt::CaseSensitive));
101                 int numColumnsPerRecord(headings.count());
102                 for(QStringList::size_type i(0); i < headings.count(); ++i)
103                 {
104                         QString heading(ExtractString(headings.value(i)));
105                         qDebug() << headings.value(i) << " : " << heading;
106
107                         // Check over the required headings
108                         foreach(QString requiredHeading, requiredHeadings)
109                         {
110                                 if(heading.toLower() == requiredHeading)
111                                 {
112                                         headingPositions[requiredHeading] = i;
113                                         requiredHeadings.removeOne(requiredHeading);
114                                 }
115                         }
116                 }
117
118                 // If we found all of the required headings, continue
119                 if(requiredHeadings.count() == 0)
120                         return new EventParsers::CSVSymbianEventLogParser(currentSettings, eventFile.fileName(), delim, numColumnsPerRecord, headingPositions);
121         }
122
123         return NULL;
124 }
125
126 CSVSymbianEventLogParser::CSVSymbianEventLogParser(const Settings &settings, const QString &filename, const char delimiter, const int numColumnsPerRecord, const ColumnIndicesHash &headingIndices)
127         : m_Settings(settings), m_Delimiter(delimiter), m_NumColumnsPerRecord(numColumnsPerRecord), m_HeadingIndices(headingIndices)
128 {
129 }
130
131 EventTypes::EventFromFileList CSVSymbianEventLogParser::ParseFile(QFile &eventFile, const QList<uint> &recordsToReturn)
132 {
133         qDebug() << "CSV Parsing NYI!";
134         EventTypes::EventFromFileList fileEvents;
135         //return fileEvents;
136
137         QSet<uint> recordsToReturnSet(QSet<uint>::fromList(recordsToReturn));
138
139         uint lineNumber(0);
140         uint recordNumber(0);
141         eventFile.seek(0);
142
143         // Read the first line
144         QByteArray firstLineContent(eventFile.readLine());
145         QStringList firstLineValues(QString(firstLineContent).split(m_Delimiter));
146         if(firstLineValues.count() != m_NumColumnsPerRecord)
147                 throw new std::runtime_error(QString("Unexpected number of columns (%1, expected %2) on line %3 of %4")
148                         .arg(firstLineValues.count())
149                          .arg(m_NumColumnsPerRecord)
150                          .arg(lineNumber)
151                          .arg(eventFile.fileName()).toStdString());
152         ++lineNumber;
153
154         // Read the main body of the file
155         while(!eventFile.atEnd())
156         {
157                 QStringList lineValues(QString(eventFile.readLine()).split(m_Delimiter));
158                 ++lineNumber;
159                 // Make sure we have enough columns (i.e. handle newlines in values)
160                 while(lineValues.count() < m_NumColumnsPerRecord)
161                 {
162                         lineValues.append(QString(eventFile.readLine()).split(m_Delimiter));
163                         ++lineNumber;
164                 }
165
166                 if(recordsToReturnSet.count() == 0 || recordsToReturnSet.contains(recordNumber))
167                 {
168                         bool bOK(false);
169                         int eType(lineValues.at(m_HeadingIndices.value("etype")).toUInt(&bOK));
170                         // We're only interested in phone calls
171                         if(bOK && eType == 0)
172                         {
173                                 qDebug() << "Parsing event from line #" << lineNumber << ". Values: " << lineValues;
174
175                                 QDateTime eTime(QDateTime::fromString(lineValues.at(m_HeadingIndices.value("etime")), "dd/MM/yyyy hh:mm:ss"));
176                                 int duration(lineValues.at(m_HeadingIndices.value("duration")).toInt(&bOK));
177                                 if(!bOK)
178                                 Settings::eDirection direction(lineValues.at(m_HeadingIndices.value("direction")) == "0"
179                                         ? Settings::INCOMING
180                                         : Settings::OUTGOING);
181                                 {
182                                         qDebug() << QString("Unable to parse '%1' as a duration. Skipping record.")
183                                                                         .arg(lineValues.at(m_HeadingIndices.value("duration")));
184                                         continue;
185                                 }
186                                 QString number(ExtractString(lineValues.at(m_HeadingIndices.value("number"))));
187                                 QString data(ExtractString(lineValues.at(m_HeadingIndices.value("data"))));
188
189                                 QSharedPointer<EventTypes::iEvent> newEvent(new EventTypes::PhoneCall(
190                                         CurrentSettings(),
191                                         direction,
192                                         eTime,
193                                         number,
194                                         duration));
195                                 fileEvents.append(EventTypes::EventFromFile(newEvent, recordNumber));
196                         }
197                 }
198
199                 ++recordNumber;
200         }
201
202         qDebug() << QString("File pos: %1, bAvail: %2, canReadLine: %3").arg(eventFile.pos()).arg(eventFile.bytesAvailable()).arg(eventFile.canReadLine());
203         qDebug() << fileEvents.count() << " events loaded from file";
204         return fileEvents;
205 }