* <http://www.gnu.org/licenses/>.
*/
+#include "CSV.h"
#include "CSVSymbianEventLogParser.h"
#include "EventTypes/PhoneCall.h"
#include "Settings.h"
#include <QDebug>
+#include <QDir>
#include <QFile>
+#include <QFileInfo>
#include <QString>
#include <QStringList>
{
qDebug() << "Checking if a CSV call log file...";
- QTextStream stream(&eventFile);
+ CSV eventsReader;
+ eventsReader.Open(eventFile);
- QString firstLineContent(stream.readLine());
- eventFile.seek(0);
- if(firstLineContent.length() > 0)
- {
- // Count the non-alphanumeric characters used
- QHash<QChar, uint> counts;
- foreach(const QChar c, firstLineContent)
- ++counts[c];
-
- QList<QPair<QChar, uint> > orderedCounts;
- orderedCounts.reserve(counts.size());
- foreach(const QChar c, counts.keys())
- if(!QChar(c).isLetterOrNumber())
- 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<QChar, uint> bodge;
- foreach(bodge count, orderedCounts)
- qDebug() << count.first << " = " << count.second;
-
- 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
- // the qutation marks to be present on every column heading (two
- // per heading, less one as they're seperators)
- if((orderedCounts.value(0).first == '"' || orderedCounts.value(0).first == '\'')
- && ((orderedCounts.value(0).second / 2) - 1 == orderedCounts.value(1).second ))
- {
- // We're good.
- delim = orderedCounts.value(1).first;
- }
- else
- delim = orderedCounts.value(0).first;
+ // If we found all of the required headings, continue
+ if(eventsReader.HasRequiredHeadings(QStringList() << "EType" << "ETime"<< "Remote" << "Direction" << "Status" << "Duration" << "Number" << "Data").count() == 0)
+ return new EventParsers::CSVSymbianEventLogParser(currentSettings, eventFile.fileName()/*, delim, numColumnsPerRecord, headingPositions*/);
+ else
+ return NULL;
+}
- // Check we have the essential fields we need, and grab their
- // column ordering
- QStringList requiredHeadings;
- requiredHeadings << "etype" << "etime" << "remote" << "direction"
- << "duration" << "number" << "data";
+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)*/
+{
+}
- EventParsers::CSVSymbianEventLogParser::ColumnIndicesHash headingPositions;
- headingPositions.reserve(requiredHeadings.count());
+EventTypes::EventFromFileList CSVSymbianEventLogParser::ParseFile(QFile &eventFile, const QList<uint> &recordsToReturn)
+{
+ EventTypes::EventFromFileList fileEvents;
- QStringList headings(QString(firstLineContent).split(delim, QString::KeepEmptyParts, Qt::CaseSensitive));
- int numColumnsPerRecord(headings.count());
- for(QStringList::size_type i(0); i < headings.count(); ++i)
- {
- QString heading(ExtractString(headings.value(i)));
- qDebug() << headings.value(i) << " : " << heading;
+ // Parse secondary table for string values
+ {
+ // Determine filename
+ QFileInfo eventFileInfo(eventFile);
+ QString filePath(eventFileInfo.absolutePath() + "/string.csv");
- // Check over the required headings
- foreach(QString requiredHeading, requiredHeadings)
+ QFile stringsFile(filePath);
+ if(stringsFile.open(QFile::ReadOnly))
+ {
+ CSV stringsReader;
+ stringsReader.Open(stringsFile);
+ if(stringsReader.HasRequiredHeadings(QStringList() << "TEXT" << "Id").count() == 0)
{
- if(heading.toLower() == requiredHeading)
+ while(!stringsReader.AtEnd())
{
- headingPositions[requiredHeading] = i;
- requiredHeadings.removeOne(requiredHeading);
+ QHash<QString, QString> values(stringsReader.ReadRecord());
+ Strings().insert(values.value("text").toLower(), values.value("id").toUInt());
}
}
}
-
- // If we found all of the required headings, continue
- if(requiredHeadings.count() == 0)
- return new EventParsers::CSVSymbianEventLogParser(currentSettings, eventFile.fileName(), delim, numColumnsPerRecord, headingPositions);
+ else
+ throw std::runtime_error(QString("Unable to open: '%1'. The error was: %2").arg(filePath).arg(stringsFile.error()).toStdString());
}
- return NULL;
-}
-
-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!";
- 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())
+ CSV eventsReader;
+ eventsReader.Open(eventFile);
+ if(eventsReader.HasRequiredHeadings(QStringList() << "EType" << "ETime"
+ << "Remote" << "Direction" << "Status"
+ << "Duration" << "Number" << "Data").count() == 0)
{
- 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)
+ while(!eventsReader.AtEnd())
{
- lineValues.append(QString(stream.readLine()).split(m_Delimiter));
- ++lineNumber;
- }
+ QHash<QString, QString> recordValues(eventsReader.ReadRecord());
- 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)
+ if(recordsToReturnSet.count() == 0 || recordsToReturnSet.contains(eventsReader.CurrentRecordNumber()))
{
- 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))
+ bool bOK(false);
+ int eType(recordValues.value("etype").toUInt(&bOK));
+ // We're only interested in phone calls
+ if(bOK && eType == 0)
{
- 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"))));
+ qDebug() << "Parsing event from record #" << eventsReader.CurrentRecordNumber() << ". Values: " << recordValues;
+
+ QDateTime eTime(QDateTime::fromString(recordValues.value("etime"), "dd/MM/yyyy h:mm:ss ap"));
+ Settings::eDirection direction(ReadDirection(recordValues.value("direction").toUInt()));
- if(number.trimmed().length() == 0)
+ // We only care about the requested directions...
+ if(CurrentSettings().ShouldProcess(direction, EventTypes::EVENT_TYPE_CALL))
{
- qDebug() << "Empty tel!";
+ int duration(recordValues.value("duration").toInt(&bOK));
+ if(!bOK)
+ {
+ qDebug() << QString("Unable to parse '%1' as a duration. Skipping record.")
+ .arg(recordValues.value("duration"));
+ continue;
+ }
+
+ QString number(ExtractString(recordValues.value("number")));
+
+ bool isMissedCall(ExtractString(recordValues.value("direction")).toUInt() == Strings().value("missed call"));
+
+ uint test = ExtractString(recordValues.value("direction")).toUInt();
+ if(test != Strings().value("incoming") && test != Strings().value("outgoing"))
+ qDebug() << eventFile.fileName() << ", line #" << eventsReader.CurrentRecordNumber() << "Direction? : " << test;
+
+ // TODO: Not currently used...but almost certainly contains SIP call data
+ QString data(ExtractString(recordValues.value("data")));
+
+ if(number.trimmed().length() == 0)
+ qDebug() << "Empty tel!";
+
+ QSharedPointer<EventTypes::iEvent> newEvent(new PhoneCall(
+ CurrentSettings(),
+ direction,
+ eTime,
+ number,
+ duration,
+ isMissedCall));
+ fileEvents.append(EventTypes::EventFromFile(newEvent, eventsReader.CurrentRecordNumber()));
}
-
- 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;
}
+
+Settings::eDirection CSVSymbianEventLogParser::ReadDirection(const uint value)
+{
+ // Missed calls are
+ if(value == Strings().value("incoming") || value == Strings().value("missed call"))
+ return Settings::INCOMING;
+ else if(value == Strings().value("outgoing"))
+ return Settings::OUTGOING;
+ else
+ {
+ // Determine which string this value corresponds to...
+ QString unexpectedString("");
+ foreach(QString string, Strings().keys())
+ if(Strings().value(string) == value)
+ unexpectedString = string;
+
+ throw std::runtime_error(
+ QString("Unexpected value found: \"%1\" (maps to \"%2\")")
+ .arg(value)
+ .arg(unexpectedString != "" ? unexpectedString : "<no known string>")
+ .toStdString());
+ }
+}