Early out parsing if we're only processing incoming OR outgoing.
[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 using EventTypes::PhoneCall;
33
34 class SortByValueDesc
35 {
36 public:
37         inline bool operator()(const QPair<QChar, uint> &a, const QPair<QChar, uint> &b) const
38         {
39                 return b.second < a.second;
40         }
41 };
42
43 const QString ExtractString(const QString &originalString)
44 {
45         QRegExp content("^[\"\']?(.*)?[\"\']?$");
46         content.indexIn(originalString.trimmed());
47         return content.cap(1);
48 }
49
50 iEventParser *CSVSymbianEventLogParser::IsValid(const Settings &currentSettings, QFile &eventFile)
51 {
52         qDebug() << "Checking if a CSV call log file...";
53
54         QTextStream stream(&eventFile);
55
56         QString firstLineContent(stream.readLine());
57         eventFile.seek(0);
58         if(firstLineContent.length() > 0)
59         {
60                 // Count the non-alphanumeric characters used
61                 QHash<QChar, uint> counts;
62                 foreach(const QChar c, firstLineContent)
63                         ++counts[c];
64
65                 QList<QPair<QChar, uint> > orderedCounts;
66                 orderedCounts.reserve(counts.size());
67                 foreach(const QChar c, counts.keys())
68                         if(!QChar(c).isLetterOrNumber())
69                                 orderedCounts.append(QPair<QChar, uint>(c, counts.value(c)));
70
71                 qSort(orderedCounts.begin(), orderedCounts.end(), SortByValueDesc());
72
73                 // Work around Q_FOREACH macro limitation when dealing with
74                 // multi-typed templates (comma issue)
75                 typedef QPair<QChar, uint> bodge;
76                 foreach(bodge count, orderedCounts)
77                         qDebug() << count.first << " = " << count.second;
78
79                 QChar delim;
80                 // No-one would be mad enough to use quotation marks or apostrophes
81                 // as their delimiter,but just in case, check the second most
82                 // frequent character is present thr right number of times for
83                 // the qutation marks to be present on every column heading (two
84                 // per heading, less one as they're seperators)
85                 if((orderedCounts.value(0).first == '"' || orderedCounts.value(0).first == '\'')
86                         && ((orderedCounts.value(0).second / 2) - 1 == orderedCounts.value(1).second ))
87                 {
88                         // We're good.
89                         delim = orderedCounts.value(1).first;
90                 }
91                 else
92                         delim = orderedCounts.value(0).first;
93
94                 // Check we have the essential fields we need, and grab their
95                 // column ordering
96                 QStringList requiredHeadings;
97                 requiredHeadings << "etype" << "etime" << "remote" << "direction"
98                                                  << "duration" << "number" << "data";
99
100                 EventParsers::CSVSymbianEventLogParser::ColumnIndicesHash headingPositions;
101                 headingPositions.reserve(requiredHeadings.count());
102
103                 QStringList headings(QString(firstLineContent).split(delim, QString::KeepEmptyParts, Qt::CaseSensitive));
104                 int numColumnsPerRecord(headings.count());
105                 for(QStringList::size_type i(0); i < headings.count(); ++i)
106                 {
107                         QString heading(ExtractString(headings.value(i)));
108                         qDebug() << headings.value(i) << " : " << heading;
109
110                         // Check over the required headings
111                         foreach(QString requiredHeading, requiredHeadings)
112                         {
113                                 if(heading.toLower() == requiredHeading)
114                                 {
115                                         headingPositions[requiredHeading] = i;
116                                         requiredHeadings.removeOne(requiredHeading);
117                                 }
118                         }
119                 }
120
121                 // If we found all of the required headings, continue
122                 if(requiredHeadings.count() == 0)
123                         return new EventParsers::CSVSymbianEventLogParser(currentSettings, eventFile.fileName(), delim, numColumnsPerRecord, headingPositions);
124         }
125
126         return NULL;
127 }
128
129 CSVSymbianEventLogParser::CSVSymbianEventLogParser(const Settings &settings, const QString &filename, const QChar delimiter, const int numColumnsPerRecord, const ColumnIndicesHash &headingIndices)
130         : m_Settings(settings), m_Delimiter(delimiter), m_NumColumnsPerRecord(numColumnsPerRecord), m_HeadingIndices(headingIndices)
131 {
132 }
133
134 EventTypes::EventFromFileList CSVSymbianEventLogParser::ParseFile(QFile &eventFile, const QList<uint> &recordsToReturn)
135 {
136         qDebug() << "CSV Parsing NYI!";
137         EventTypes::EventFromFileList fileEvents;
138         //return fileEvents;
139
140         QSet<uint> recordsToReturnSet(QSet<uint>::fromList(recordsToReturn));
141
142         uint lineNumber(0);
143         uint recordNumber(0);
144         eventFile.seek(0);
145
146         // Read the first line
147         QTextStream stream(&eventFile);
148         QString firstLineContent(stream.readLine());
149         QStringList firstLineValues(QString(firstLineContent).split(m_Delimiter));
150         if(firstLineValues.count() != m_NumColumnsPerRecord)
151                 throw new std::runtime_error(QString("Unexpected number of columns (%1, expected %2) on line %3 of %4")
152                         .arg(firstLineValues.count())
153                         .arg(m_NumColumnsPerRecord)
154                         .arg(lineNumber)
155                         .arg(eventFile.fileName()).toStdString());
156         ++lineNumber;
157
158         // Read the main body of the file
159         while(!stream.atEnd())
160         {
161                 QStringList lineValues(QString(stream.readLine()).split(m_Delimiter));
162                 ++lineNumber;
163                 // Make sure we have enough columns (i.e. handle newlines in values)
164                 while(lineValues.count() < m_NumColumnsPerRecord)
165                 {
166                         lineValues.append(QString(stream.readLine()).split(m_Delimiter));
167                         ++lineNumber;
168                 }
169
170                 if(recordsToReturnSet.count() == 0 || recordsToReturnSet.contains(recordNumber))
171                 {
172                         bool bOK(false);
173                         int eType(lineValues.at(m_HeadingIndices.value("etype")).toUInt(&bOK));
174                         // We're only interested in phone calls
175                         if(bOK && eType == 0)
176                         {
177                                 qDebug() << "Parsing event from line #" << lineNumber << ". Values: " << lineValues;
178
179                                 QDateTime eTime(QDateTime::fromString(lineValues.at(m_HeadingIndices.value("etime")), "dd/MM/yyyy h:mm:ss ap"));
180                                 Settings::eDirection direction(lineValues.at(m_HeadingIndices.value("direction")) == "0"
181                                         ? Settings::INCOMING
182                                         : Settings::OUTGOING);
183
184                                 // We only care about the requested directions...
185                                 if(CurrentSettings().ShouldProcess(direction, EventTypes::EVENT_TYPE_CALL))
186                                 {
187                                         int duration(lineValues.at(m_HeadingIndices.value("duration")).toInt(&bOK));
188                                         if(!bOK)
189                                         {
190                                                 qDebug() << QString("Unable to parse '%1' as a duration. Skipping record.")
191                                                                                 .arg(lineValues.at(m_HeadingIndices.value("duration")));
192                                                 continue;
193                                         }
194                                         QString number(ExtractString(lineValues.at(m_HeadingIndices.value("number"))));
195                                         // TODO: Not currently used...but almost certainly contains SIP call data
196                                         QString data(ExtractString(lineValues.at(m_HeadingIndices.value("data"))));
197
198                                         if(number.trimmed().length() == 0)
199                                         {
200                                                 qDebug() << "Empty tel!";
201                                         }
202
203                                         QSharedPointer<EventTypes::iEvent> newEvent(new PhoneCall(
204                                                 CurrentSettings(),
205                                                 direction,
206                                                 eTime,
207                                                 number,
208                                                 duration));
209                                         fileEvents.append(EventTypes::EventFromFile(newEvent, recordNumber));
210                                 }
211                         }
212                 }
213
214                 ++recordNumber;
215         }
216
217         qDebug() << QString("File pos: %1, bAvail: %2, canReadLine: %3").arg(eventFile.pos()).arg(eventFile.bytesAvailable()).arg(eventFile.canReadLine());
218         qDebug() << fileEvents.count() << " events loaded from file";
219         return fileEvents;
220 }