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