Early out parsing if we're only processing incoming OR outgoing.
[qwerkisync] / EventParsers / CSVSymbianEventLogParser.cpp
index 8d38ffe..43142f1 100644 (file)
@@ -17,6 +17,8 @@
  */
 
 #include "CSVSymbianEventLogParser.h"
+#include "EventTypes/PhoneCall.h"
+#include "Settings.h"
 
 #include <QDebug>
 
 #include <QString>
 #include <QStringList>
 
+#include <stdexcept>
+
 using namespace EventParsers;
+using EventTypes::PhoneCall;
 
 class SortByValueDesc
 {
 public:
-       inline bool operator()(const QPair<char, uint> &a, const QPair<char, uint> &b) const
+       inline bool operator()(const QPair<QChar, uint> &a, const QPair<QChar, uint> &b) const
        {
                return b.second < a.second;
        }
 };
 
-iEventParser *CSVSymbianEventLogParser::IsValid(QFile &eventFile)
+const QString ExtractString(const QString &originalString)
+{
+       QRegExp content("^[\"\']?(.*)?[\"\']?$");
+       content.indexIn(originalString.trimmed());
+       return content.cap(1);
+}
+
+iEventParser *CSVSymbianEventLogParser::IsValid(const Settings &currentSettings, QFile &eventFile)
 {
        qDebug() << "Checking if a CSV call log file...";
 
-       QByteArray firstLineContent(eventFile.readLine());
+       QTextStream stream(&eventFile);
+
+       QString firstLineContent(stream.readLine());
        eventFile.seek(0);
        if(firstLineContent.length() > 0)
        {
                // Count the non-alphanumeric characters used
-               QHash<char, uint> counts;
-               foreach(char c, firstLineContent)
+               QHash<QChar, uint> counts;
+               foreach(const QChar c, firstLineContent)
                        ++counts[c];
 
-               QList<QPair<char, uint> > orderedCounts;
+               QList<QPair<QChar, uint> > orderedCounts;
                orderedCounts.reserve(counts.size());
-               foreach(char c, counts.keys())
+               foreach(const QChar c, counts.keys())
                        if(!QChar(c).isLetterOrNumber())
-                               orderedCounts.append(QPair<char, uint>(c, counts.value(c)));
+                               orderedCounts.append(QPair<QChar, uint>(c, counts.value(c)));
 
                qSort(orderedCounts.begin(), orderedCounts.end(), SortByValueDesc());
 
                // Work around Q_FOREACH macro limitation when dealing with
                // multi-typed templates (comma issue)
-               typedef QPair<char, uint> bodge;
+               typedef QPair<QChar, uint> bodge;
                foreach(bodge count, orderedCounts)
                        qDebug() << count.first << " = " << count.second;
 
-               char delim;
+               QChar delim;
                // No-one would be mad enough to use quotation marks or apostrophes
                // as their delimiter,but just in case, check the second most
                // frequent character is present thr right number of times for
@@ -80,18 +94,17 @@ iEventParser *CSVSymbianEventLogParser::IsValid(QFile &eventFile)
                // Check we have the essential fields we need, and grab their
                // column ordering
                QStringList requiredHeadings;
-               requiredHeadings << "etype" << "etime" << "remote"
-                                                << "direction" << "duration" << "number";
+               requiredHeadings << "etype" << "etime" << "remote" << "direction"
+                                                << "duration" << "number" << "data";
 
                EventParsers::CSVSymbianEventLogParser::ColumnIndicesHash headingPositions;
                headingPositions.reserve(requiredHeadings.count());
 
-               QStringList headings(QString(firstLineContent).split(delim));
+               QStringList headings(QString(firstLineContent).split(delim, QString::KeepEmptyParts, Qt::CaseSensitive));
+               int numColumnsPerRecord(headings.count());
                for(QStringList::size_type i(0); i < headings.count(); ++i)
                {
-                       QRegExp content("^[\"\']?(\\w*)?[\"\']?$");
-                       content.indexIn(headings.value(i).trimmed());
-                       QString heading(content.cap(1));
+                       QString heading(ExtractString(headings.value(i)));
                        qDebug() << headings.value(i) << " : " << heading;
 
                        // Check over the required headings
@@ -107,20 +120,101 @@ iEventParser *CSVSymbianEventLogParser::IsValid(QFile &eventFile)
 
                // If we found all of the required headings, continue
                if(requiredHeadings.count() == 0)
-               {
-                       return new EventParsers::CSVSymbianEventLogParser(eventFile.fileName(), headingPositions);
-               }
+                       return new EventParsers::CSVSymbianEventLogParser(currentSettings, eventFile.fileName(), delim, numColumnsPerRecord, headingPositions);
        }
 
        return NULL;
 }
 
-CSVSymbianEventLogParser::CSVSymbianEventLogParser(const QString &filename, const ColumnIndicesHash &columns)
+CSVSymbianEventLogParser::CSVSymbianEventLogParser(const Settings &settings, const QString &filename, const QChar delimiter, const int numColumnsPerRecord, const ColumnIndicesHash &headingIndices)
+       : m_Settings(settings), m_Delimiter(delimiter), m_NumColumnsPerRecord(numColumnsPerRecord), m_HeadingIndices(headingIndices)
 {
 }
 
 EventTypes::EventFromFileList CSVSymbianEventLogParser::ParseFile(QFile &eventFile, const QList<uint> &recordsToReturn)
 {
        qDebug() << "CSV Parsing NYI!";
-       return EventTypes::EventFromFileList();
+       EventTypes::EventFromFileList fileEvents;
+       //return fileEvents;
+
+       QSet<uint> recordsToReturnSet(QSet<uint>::fromList(recordsToReturn));
+
+       uint lineNumber(0);
+       uint recordNumber(0);
+       eventFile.seek(0);
+
+       // Read the first line
+       QTextStream stream(&eventFile);
+       QString firstLineContent(stream.readLine());
+       QStringList firstLineValues(QString(firstLineContent).split(m_Delimiter));
+       if(firstLineValues.count() != m_NumColumnsPerRecord)
+               throw new std::runtime_error(QString("Unexpected number of columns (%1, expected %2) on line %3 of %4")
+                       .arg(firstLineValues.count())
+                       .arg(m_NumColumnsPerRecord)
+                       .arg(lineNumber)
+                       .arg(eventFile.fileName()).toStdString());
+       ++lineNumber;
+
+       // Read the main body of the file
+       while(!stream.atEnd())
+       {
+               QStringList lineValues(QString(stream.readLine()).split(m_Delimiter));
+               ++lineNumber;
+               // Make sure we have enough columns (i.e. handle newlines in values)
+               while(lineValues.count() < m_NumColumnsPerRecord)
+               {
+                       lineValues.append(QString(stream.readLine()).split(m_Delimiter));
+                       ++lineNumber;
+               }
+
+               if(recordsToReturnSet.count() == 0 || recordsToReturnSet.contains(recordNumber))
+               {
+                       bool bOK(false);
+                       int eType(lineValues.at(m_HeadingIndices.value("etype")).toUInt(&bOK));
+                       // We're only interested in phone calls
+                       if(bOK && eType == 0)
+                       {
+                               qDebug() << "Parsing event from line #" << lineNumber << ". Values: " << lineValues;
+
+                               QDateTime eTime(QDateTime::fromString(lineValues.at(m_HeadingIndices.value("etime")), "dd/MM/yyyy h:mm:ss ap"));
+                               Settings::eDirection direction(lineValues.at(m_HeadingIndices.value("direction")) == "0"
+                                       ? Settings::INCOMING
+                                       : Settings::OUTGOING);
+
+                               // We only care about the requested directions...
+                               if(CurrentSettings().ShouldProcess(direction, EventTypes::EVENT_TYPE_CALL))
+                               {
+                                       int duration(lineValues.at(m_HeadingIndices.value("duration")).toInt(&bOK));
+                                       if(!bOK)
+                                       {
+                                               qDebug() << QString("Unable to parse '%1' as a duration. Skipping record.")
+                                                                               .arg(lineValues.at(m_HeadingIndices.value("duration")));
+                                               continue;
+                                       }
+                                       QString number(ExtractString(lineValues.at(m_HeadingIndices.value("number"))));
+                                       // TODO: Not currently used...but almost certainly contains SIP call data
+                                       QString data(ExtractString(lineValues.at(m_HeadingIndices.value("data"))));
+
+                                       if(number.trimmed().length() == 0)
+                                       {
+                                               qDebug() << "Empty tel!";
+                                       }
+
+                                       QSharedPointer<EventTypes::iEvent> newEvent(new PhoneCall(
+                                               CurrentSettings(),
+                                               direction,
+                                               eTime,
+                                               number,
+                                               duration));
+                                       fileEvents.append(EventTypes::EventFromFile(newEvent, recordNumber));
+                               }
+                       }
+               }
+
+               ++recordNumber;
+       }
+
+       qDebug() << QString("File pos: %1, bAvail: %2, canReadLine: %3").arg(eventFile.pos()).arg(eventFile.bytesAvailable()).arg(eventFile.canReadLine());
+       qDebug() << fileEvents.count() << " events loaded from file";
+       return fileEvents;
 }