2 * Copyright (C) 2011, Jamie Thompson
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.
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.
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/>.
19 #include "CSVSymbianEventLogParser.h"
20 #include "EventTypes/PhoneCall.h"
27 #include <QStringList>
31 using namespace EventParsers;
32 using EventTypes::PhoneCall;
37 inline bool operator()(const QPair<QChar, uint> &a, const QPair<QChar, uint> &b) const
39 return b.second < a.second;
43 const QString ExtractString(const QString &originalString)
45 QRegExp content("^[\"\']?(.*)?[\"\']?$");
46 content.indexIn(originalString.trimmed());
47 return content.cap(1);
50 iEventParser *CSVSymbianEventLogParser::IsValid(const Settings ¤tSettings, QFile &eventFile)
52 qDebug() << "Checking if a CSV call log file...";
54 QTextStream stream(&eventFile);
56 QString firstLineContent(stream.readLine());
58 if(firstLineContent.length() > 0)
60 // Count the non-alphanumeric characters used
61 QHash<QChar, uint> counts;
62 foreach(const QChar c, firstLineContent)
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)));
71 qSort(orderedCounts.begin(), orderedCounts.end(), SortByValueDesc());
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;
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 ))
89 delim = orderedCounts.value(1).first;
92 delim = orderedCounts.value(0).first;
94 // Check we have the essential fields we need, and grab their
96 QStringList requiredHeadings;
97 requiredHeadings << "etype" << "etime" << "remote" << "direction"
98 << "duration" << "number" << "data";
100 EventParsers::CSVSymbianEventLogParser::ColumnIndicesHash headingPositions;
101 headingPositions.reserve(requiredHeadings.count());
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)
107 QString heading(ExtractString(headings.value(i)));
108 qDebug() << headings.value(i) << " : " << heading;
110 // Check over the required headings
111 foreach(QString requiredHeading, requiredHeadings)
113 if(heading.toLower() == requiredHeading)
115 headingPositions[requiredHeading] = i;
116 requiredHeadings.removeOne(requiredHeading);
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);
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)
134 EventTypes::EventFromFileList CSVSymbianEventLogParser::ParseFile(QFile &eventFile, const QList<uint> &recordsToReturn)
136 qDebug() << "CSV Parsing NYI!";
137 EventTypes::EventFromFileList fileEvents;
140 QSet<uint> recordsToReturnSet(QSet<uint>::fromList(recordsToReturn));
143 uint recordNumber(0);
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)
155 .arg(eventFile.fileName()).toStdString());
158 // Read the main body of the file
159 while(!stream.atEnd())
161 QStringList lineValues(QString(stream.readLine()).split(m_Delimiter));
163 // Make sure we have enough columns (i.e. handle newlines in values)
164 while(lineValues.count() < m_NumColumnsPerRecord)
166 lineValues.append(QString(stream.readLine()).split(m_Delimiter));
170 if(recordsToReturnSet.count() == 0 || recordsToReturnSet.contains(recordNumber))
173 int eType(lineValues.at(m_HeadingIndices.value("etype")).toUInt(&bOK));
174 // We're only interested in phone calls
175 if(bOK && eType == 0)
177 qDebug() << "Parsing event from line #" << lineNumber << ". Values: " << lineValues;
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"
182 : Settings::OUTGOING);
184 // We only care about the requested directions...
185 if(CurrentSettings().ShouldProcess(direction, EventTypes::EVENT_TYPE_CALL))
187 int duration(lineValues.at(m_HeadingIndices.value("duration")).toInt(&bOK));
190 qDebug() << QString("Unable to parse '%1' as a duration. Skipping record.")
191 .arg(lineValues.at(m_HeadingIndices.value("duration")));
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"))));
198 if(number.trimmed().length() == 0)
200 qDebug() << "Empty tel!";
203 QSharedPointer<EventTypes::iEvent> newEvent(new PhoneCall(
209 fileEvents.append(EventTypes::EventFromFile(newEvent, recordNumber));
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";