From: Jamie Thompson Date: Sun, 2 Oct 2011 23:23:27 +0000 (+0100) Subject: Added CSV parsing and export of Symbian-format Event logs that have had their tables... X-Git-Url: http://git.maemo.org/git/?p=qwerkisync;a=commitdiff_plain;h=43c287cf53b85a8a347ae12ce8d938b256357ce3 Added CSV parsing and export of Symbian-format Event logs that have had their tables exported to CSV files. The binary format of the DBU files is going to be a PITA to work with, so in the meantime, we have to make do with CSV representations of the tables. Header rows are mandatory, but any text encoding supported by Qt should be fine. Newlines are supported in column values. The import/export cycle is not identical. There is no way of: 1) Splitting the events into whatever criteria would be used by the original databses (they're capped so only the most recent 1000 or so events are stored). 2) Including data events. Symbian stores every use of any connection, including Wifi and GPRS. Maemo doesn't, because that would be stupid. 3) Continuing the arbitrary ID sequence to continue from wherever your phone currently is. --- diff --git a/CSV.cpp b/CSV.cpp new file mode 100644 index 0000000..0fe13ea --- /dev/null +++ b/CSV.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#include "CSV.h" + +#include + +#include +#include +#include +#include + +class SortByValueDesc +{ +public: + inline bool operator()(const QPair &a, const QPair &b) const + { + return b.second < a.second; + } +}; + +CSV::CSV() + : m_IsValid(false), m_File(NULL), m_Stream(NULL), m_LineNumber(0), m_RecordNumber(0) +{ +} + +CSV::CSV(QChar delimiter, int numColumnsPerRecord, const ColumnIndicesHash &headingIndices) + : m_IsValid(false), m_File(NULL), m_Stream(NULL), m_LineNumber(0), m_RecordNumber(0) +{ + Delimiter(delimiter); + NumColumnsPerRecord(numColumnsPerRecord); + + UpdateHeadings(headingIndices); + + IsValid(true); +} + +CSV::~CSV() +{ +} + +void CSV::Open(QFile &file) +{ + // Ready the file... + LineNumber(0); + RecordNumber(0); + File(&file); + File()->seek(0); + + // Read the first line + Stream(new QTextStream(&file)); + + // Set up the properties... + if(!IsValid()) + { + QString firstLineContent(Stream()->readLine()); + DetermineDelimiter(firstLineContent); + GetHeadings(firstLineContent); + } + // We accept we've already done the hard work, so advance to the first + // actual record (i.e. the 2nd row) + else + ReadRecord(); +} + +void CSV::Open(QFile &file, QChar delimiter, int numColumnsPerRecord, const ColumnIndicesHash &headingIndices) +{ + // Set up the properties... + Delimiter(delimiter); + NumColumnsPerRecord(numColumnsPerRecord); + UpdateHeadings(headingIndices); + IsValid(true); + + // Ready the file... + File(&file); + File()->seek(0); + + // Advance to the first actual record (i.e. the 2nd row) + ReadRecord(); +} + +void CSV::Close() +{ + IsValid(false); + File(NULL); +} + +bool CSV::AtEnd() const +{ + return Stream()->atEnd(); +} + +QHash CSV::ReadRecord() +{ + // If we have something more to read... + if(LineValues().count() < NumColumnsPerRecord() && !Stream()->atEnd()) + { + // ...read a line's worth but make sure we have enough columns (i.e. handle newlines in values) + while(LineValues().count() < NumColumnsPerRecord()) + { + QStringList nextValues(QString(Stream()->readLine()).split(Delimiter())); + if(LineValues().count() > 0) + { + // Merge the first value of the next line with the last of the previous... + LineValues().last().append('\n'); + nextValues.removeAt(0); + } + LineValues().append(nextValues); + ++LineNumber(); + } + } + + // The extract enough values to complete a record + QHash recordValues; + for(int i(NumColumnsPerRecord() - 1); i >= 0 && LineValues().count() >= 0; --i) + { + recordValues.insert(HeadingNames().value(i), LineValues().value(i)); + LineValues().removeAt(i); + } + return recordValues; +} + +void CSV::GetHeadings(const QString &firstLineContent) +{ + QStringList headingsRaw(QString(firstLineContent).split(Delimiter(), QString::KeepEmptyParts, Qt::CaseSensitive)); + + // We have this many fields per record + NumColumnsPerRecord(headingsRaw.count()); + + // Grab each column heading, and tidy it up. + ColumnIndicesHash indices; + indices.reserve(headingsRaw.count()); + for(QStringList::size_type i(0); i < headingsRaw.count(); ++i) + { + QString heading(ExtractString(headingsRaw.value(i))); + qDebug() << headingsRaw.value(i) << " : " << heading; + + indices[heading] = i; + } + + UpdateHeadings(indices); +} + +const QStringList CSV::HasRequiredHeadings(const QStringList &requiredHeadings) +{ + QStringList missingRequiredHeadings(requiredHeadings); + + // Check over the required headings + foreach(const QString requiredHeading, requiredHeadings) + { + if(HeadingIndices().contains(requiredHeading.toLower())) + missingRequiredHeadings.removeOne(requiredHeading); + } + + return missingRequiredHeadings; +} + +void CSV::DetermineDelimiter(const QString &firstLineContent) +{ + // Count the non-alphanumeric characters used + QHash counts; + foreach(const QChar c, firstLineContent) + ++counts[c]; + + QList > orderedCounts; + orderedCounts.reserve(counts.size()); + foreach(const QChar c, counts.keys()) + if(!QChar(c).isLetterOrNumber()) + orderedCounts.append(QPair(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 bodge; + foreach(bodge count, orderedCounts) + qDebug() << count.first << " = " << count.second; + + // 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 the right number of times for + // the quotation 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. + Delimiter(orderedCounts.value(1).first); + } + else + Delimiter(orderedCounts.value(0).first); +} + +const QString CSV::ExtractString(const QString &originalString) +{ + QRegExp content("^[\"\']?(.*)?[\"\']?$"); + content.indexIn(originalString.trimmed()); + return content.cap(1); +} + +void CSV::UpdateHeadings(const ColumnIndicesHash &headingIndices) +{ + HeadingIndices().clear(); + HeadingIndices().reserve(headingIndices.count()); + foreach(QString columnName, headingIndices.keys()) + HeadingIndices().insert(columnName.toLower(), headingIndices.value(columnName)); + + // ..and prepare the bidirectional hash (toLower not needed as above + // value reused) + HeadingNames().clear(); + HeadingNames().reserve(headingIndices.count()); + foreach(QString columnName, HeadingIndices().keys()) + HeadingNames().insert(HeadingIndices().value(columnName), columnName); +} diff --git a/CSV.h b/CSV.h new file mode 100644 index 0000000..98483b4 --- /dev/null +++ b/CSV.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#ifndef CSV_H +#define CSV_H + +#include +class QChar; +class QFile; +class QString; +#include +#include +class QTextStream; + +class CSV +{ +public: + typedef QHash ColumnIndicesHash; + typedef QHash ColumnNamesHash; + + CSV(); + CSV(QChar delimiter, int numColumnsPerRecord, const ColumnIndicesHash &headingIndices); + ~CSV(); + + void Open(QFile &file); + void Open(QFile &file, QChar delimiter, int numColumnsPerRecord, const ColumnIndicesHash &headingIndices); + bool AtEnd() const; + void Close(); + QHash ReadRecord(); + const QStringList HasRequiredHeadings(const QStringList &requiredHeadings); + +protected: + void GetHeadings(const QString &firstLine); + void DetermineDelimiter(const QString &firstLine); + +private: + const QString ExtractString(const QString &originalString); + void UpdateHeadings(const ColumnIndicesHash &headingIndices); + +public: + const unsigned int CurrentRecordNumber() const { return m_RecordNumber; } + unsigned int CurrentRecordNumber() { return m_RecordNumber; } + +protected: + const bool IsValid() { return m_IsValid; } + const QChar Delimiter() const { return m_Delimiter; } + const int NumColumnsPerRecord() const { return m_NumColumnsPerRecord; } + const ColumnIndicesHash & HeadingIndices() const { return m_HeadingIndices; } + const ColumnNamesHash & HeadingNames() const { return m_HeadingNames; } + QFile * const File() { return m_File; } + const QSharedPointer Stream() const { return m_Stream; } + const unsigned int &LineNumber() const { return m_LineNumber; } + unsigned int &LineNumber() { return m_LineNumber; } + const QStringList &LineValues() const { return m_LineValues; } + const unsigned int & RecordNumber() const { return m_RecordNumber; } + unsigned int & RecordNumber() { return m_RecordNumber; } + +protected: + ColumnIndicesHash & HeadingIndices() { return m_HeadingIndices; } + ColumnNamesHash & HeadingNames() { return m_HeadingNames; } + QStringList & LineValues() { return m_LineValues; } + +protected: + void IsValid(const bool isValid) { m_IsValid = isValid; } + void Delimiter(const QChar delimiter) { m_Delimiter = delimiter; } + void NumColumnsPerRecord(const int numColumnsPerRecord) { m_NumColumnsPerRecord = numColumnsPerRecord; } + void HeadingIndices(const ColumnIndicesHash headingIndices) { m_HeadingIndices = headingIndices; } + void HeadingNames(const ColumnNamesHash headingNames) { m_HeadingNames = headingNames; } + void File(QFile * const file) { m_File = file; } + void Stream(QTextStream * const stream) { m_Stream = QSharedPointer(stream); } + void LineNumber(unsigned int lineNumber) { m_LineNumber = lineNumber; } + void RecordNumber(unsigned int recordNumber) { m_RecordNumber = recordNumber; } + void LineValues(QStringList &lineValues) { m_LineValues = lineValues; } + +private: + bool m_IsValid; + QChar m_Delimiter; + int m_NumColumnsPerRecord; + ColumnIndicesHash m_HeadingIndices; + ColumnNamesHash m_HeadingNames; + QFile *m_File; + QSharedPointer m_Stream; + unsigned int m_LineNumber; + unsigned int m_RecordNumber; + QStringList m_LineValues; +}; + +#endif // CSV_H diff --git a/EventFormats/SymbianEventLog.cpp b/EventFormats/SymbianEventLog.cpp new file mode 100644 index 0000000..be7ac34 --- /dev/null +++ b/EventFormats/SymbianEventLog.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#include "EventFormats/SymbianEventLog.h" + +//SymbianEventLog::SymbianEventLog() +//{ +//} diff --git a/EventFormats/SymbianEventLog.h b/EventFormats/SymbianEventLog.h new file mode 100644 index 0000000..d7e55c0 --- /dev/null +++ b/EventFormats/SymbianEventLog.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#ifndef EVENTFORMATS_SYMBIANEVENTLOG_H +#define EVENTFORMATS_SYMBIANEVENTLOG_H + +template class QHash; +class QString; + +class SymbianEventLog +{ +public: + typedef QHash StringsLookup; +}; + +#endif // EVENTFORMATS_SYMBIANEVENTLOG_H diff --git a/EventParsers/CSVSymbianEventLogParser.cpp b/EventParsers/CSVSymbianEventLogParser.cpp index 43142f1..91855e1 100644 --- a/EventParsers/CSVSymbianEventLogParser.cpp +++ b/EventParsers/CSVSymbianEventLogParser.cpp @@ -16,13 +16,16 @@ * . */ +#include "CSV.h" #include "CSVSymbianEventLogParser.h" #include "EventTypes/PhoneCall.h" #include "Settings.h" #include +#include #include +#include #include #include @@ -51,170 +54,136 @@ iEventParser *CSVSymbianEventLogParser::IsValid(const Settings ¤tSettings, { 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 counts; - foreach(const QChar c, firstLineContent) - ++counts[c]; - - QList > orderedCounts; - orderedCounts.reserve(counts.size()); - foreach(const QChar c, counts.keys()) - if(!QChar(c).isLetterOrNumber()) - orderedCounts.append(QPair(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 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 &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 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 &recordsToReturn) -{ - qDebug() << "CSV Parsing NYI!"; - EventTypes::EventFromFileList fileEvents; - //return fileEvents; - QSet recordsToReturnSet(QSet::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 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 newEvent(new PhoneCall( + CurrentSettings(), + direction, + eTime, + number, + duration, + isMissedCall)); + fileEvents.append(EventTypes::EventFromFile(newEvent, eventsReader.CurrentRecordNumber())); } - - QSharedPointer 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 : "") + .toStdString()); + } +} diff --git a/EventParsers/CSVSymbianEventLogParser.h b/EventParsers/CSVSymbianEventLogParser.h index 102d210..b57b60a 100644 --- a/EventParsers/CSVSymbianEventLogParser.h +++ b/EventParsers/CSVSymbianEventLogParser.h @@ -19,35 +19,43 @@ #ifndef EVENTPARSERS_CSVSYMBIANEVENTLOGPARSER_H #define EVENTPARSERS_CSVSYMBIANEVENTLOGPARSER_H +#include "EventFormats/SymbianEventLog.h" #include "iEventParser.h" -class Settings; +#include "Settings.h" #include class QChar; class QFile; class QString; +class QStringList; namespace EventParsers { - class CSVSymbianEventLogParser : public iEventParser + class CSVSymbianEventLogParser : public iEventParser, public SymbianEventLog { public: typedef QHash ColumnIndicesHash; static iEventParser *IsValid(const Settings ¤tSettings, QFile &eventFile); - CSVSymbianEventLogParser(const Settings ¤tSettings, const QString &filename, const QChar delimiter, const int numColumnsPerRecord, const ColumnIndicesHash &columns); + CSVSymbianEventLogParser(const Settings ¤tSettings, const QString &filename); virtual EventTypes::EventFromFileList ParseFile(QFile &eventFile, const QList &recordsToReturn); + private: + Settings::eDirection ReadDirection(const uint value); + protected: const Settings &CurrentSettings() const { return m_Settings; } + const StringsLookup &Strings() const { return m_Strings; } + StringsLookup &Strings() { return m_Strings; } + + protected: + void Strings(const StringsLookup &strings) { m_Strings = strings; } private: const Settings &m_Settings; - const QChar m_Delimiter; - const int m_NumColumnsPerRecord; - const ColumnIndicesHash m_HeadingIndices; + StringsLookup m_Strings; }; } diff --git a/EventParsers/MMSParser.cpp b/EventParsers/MMSParser.cpp index 5339983..1549e60 100644 --- a/EventParsers/MMSParser.cpp +++ b/EventParsers/MMSParser.cpp @@ -18,6 +18,8 @@ #include "MMSParser.h" +#include "EventTypes/iEvent.h" + #include #include diff --git a/EventParsers/VMGParser.cpp b/EventParsers/VMGParser.cpp index 0cfda71..f8306a0 100644 --- a/EventParsers/VMGParser.cpp +++ b/EventParsers/VMGParser.cpp @@ -74,24 +74,29 @@ VMGParser::VMGParser(const Settings &settings, const QString &filename) : m_Sett EventTypes::EventFromFileList VMGParser::ParseFile(QFile &eventFile, const QList &recordsToReturn) { - // VMG files are stored in Little-Endian UTF16, with no BOM. - QTextStream eventStream(&eventFile); - eventStream.setCodec("UTF-16LE"); - - // Parse the event - EventTypes::SMS *event(new EventTypes::SMS(CurrentSettings())); - QString lineData = eventStream.readLine(); - EventParsers::VMGEntities::iReader* reader = EventParsers::VMGEntities::Factory::Instantiate(CurrentSettings(), lineData, NULL); - bool valid(NULL != reader && reader->Read(QString(""), eventStream, *event)); - delete reader; - if (!valid) - throw std::runtime_error(QString("Unsupported format. Unable to open: %1").arg(eventFile.fileName()).toStdString()); - - qDebug() << "\nParsed event:"; - qDebug() << event; - - // VMGs only support single events per file, so just create the list EventTypes::EventFromFileList retList; - retList.append(EventTypes::EventFromFile(QSharedPointer(event), 0)); + + // VMGs only support single events per file + if(recordsToReturn.count() == 0 || recordsToReturn.contains(0)) + { + // VMG files are stored in Little-Endian UTF16, with no BOM. + QTextStream eventStream(&eventFile); + eventStream.setCodec("UTF-16LE"); + + // Parse the event + EventTypes::SMS *event(new EventTypes::SMS(CurrentSettings())); + QString lineData = eventStream.readLine(); + EventParsers::VMGEntities::iReader* reader = EventParsers::VMGEntities::Factory::Instantiate(CurrentSettings(), lineData, NULL); + bool valid(NULL != reader && reader->Read(QString(""), eventStream, *event)); + delete reader; + if (!valid) + throw std::runtime_error(QString("Unsupported format. Unable to open: %1").arg(eventFile.fileName()).toStdString()); + + qDebug() << "\nParsed event:"; + qDebug() << event; + + retList.append(EventTypes::EventFromFile(QSharedPointer(event), 0)); + } + return retList; } diff --git a/EventProcessors/Writer.cpp b/EventProcessors/Writer.cpp index 90dc1aa..d1b0054 100644 --- a/EventProcessors/Writer.cpp +++ b/EventProcessors/Writer.cpp @@ -19,18 +19,33 @@ #include "Writer.h" #include "EventTypes/iEvent.h" +#include "EventWriters/iEventWriter.h" +#include "EventWriters/CSVSymbianEventLogWriter.h" +#include "EventWriters/VMGWriter.h" #include "Settings.h" +#include + +#include +#include + using namespace EventProcessors; Writer::Writer(Settings &settings, const NumberToNameLookup &numberToNamelookup) - : m_Settings(settings), m_NumberToNameLookup(numberToNameLookup) + : m_Settings(settings) +{ + m_Writers.append(QSharedPointer(new EventWriters::CSVSymbianEventLogWriter(CurrentSettings(), numberToNamelookup))); + m_Writers.append(QSharedPointer(new EventWriters::VMGWriter(CurrentSettings(), numberToNamelookup))); +} + +Writer::~Writer() { } void Writer::Process(EventTypes::iEvent &event) { - event.Export(CurrentSettings().Directory(), NumberToNameLookup()); + foreach (QSharedPointer writer, m_Writers) + writer->Write(event); } void Writer::EmitEventProcessed(int eventsProcessed, int totalEvents) diff --git a/EventProcessors/Writer.h b/EventProcessors/Writer.h index 56d5332..bb88f65 100644 --- a/EventProcessors/Writer.h +++ b/EventProcessors/Writer.h @@ -21,10 +21,13 @@ #include "iEventProcessor.h" +#include "EventWriters/iEventWriter.h" class NumberToNameLookup; class Settings; #include +template class QList; +#include namespace EventProcessors { @@ -34,16 +37,16 @@ namespace EventProcessors public: Writer(Settings &settings, const NumberToNameLookup &numberToNamelookup); + ~Writer(); - const Settings & CurrentSettings() const { return m_Settings; } - const NumberToNameLookup & NumberToNameLookup() const { return m_NumberToNameLookup; } + const Settings & CurrentSettings() { return m_Settings; } virtual void Process(EventTypes::iEvent &event); virtual void EmitEventProcessed(int eventsProcessed, int totalEvents); private: const Settings & m_Settings; - const NumberToNameLookup &numberToNameLookup; + QList > m_Writers; signals: void EventProcessed(int current, int total); diff --git a/EventTypes/PhoneCall.cpp b/EventTypes/PhoneCall.cpp index df8387a..54b52be 100644 --- a/EventTypes/PhoneCall.cpp +++ b/EventTypes/PhoneCall.cpp @@ -143,36 +143,86 @@ RTComElEvent * PhoneCall::toRTComEvent(const NumberToNameLookup &numberToNameLoo return event; } -void PhoneCall::Export(const QString &baseDirectory, const NumberToNameLookup &numberToNameLookup) const +void PhoneCall::WriteCSVSymbian(QTextStream &stream, const ColumnIndicesByIndexHash &headerIndices, const QChar delimiter, const NumberToNameLookup &numberToNameLookup, SymbianEventLogStrings &strings) const { -// // Build the path and ensure it exists... -// QString eventFilename(baseDirectory); -// eventFilename += Destination() == EventTypes::PhoneCall::OUTGOING ? "/Sent/" : "/Inbox/"; -// eventFilename += QString::number(Timestamp().toUTC().date().year()) + "/"; -// QDir().mkpath(eventFilename); - -// // ...then build the filename and open it. -// eventFilename += QString::number(Timestamp().toUTC().toTime_t()) + ".vmg"; -// QFile data(eventFilename); -// if (data.open(QFile::WriteOnly | QFile::Truncate)) -// { -// QTextStream stream(&data); - -// QTextCodec *oldCodec = stream.codec(); -// stream.setAutoDetectUnicode(false); -// stream.setCodec("UTF-16LE"); - -// EventParsers::VMGEntities::VMessage writer(NULL, 1.1); -// writer.Write(stream, *this); -////stream << "Test"; -// //stream.setCodec(oldCodec); -// stream.flush(); -// data.close(); - -// utimbuf fileTimes; -// fileTimes.modtime = Timestamp().toUTC().toTime_t(); -// utime(eventFilename.toAscii(), &fileTimes); -// } + // 0|05/09/2007 11:25:12 am|1|||||||3|8|0|0||||Unrecognized|| + for(uint columnIndex(0); columnIndex < (uint)headerIndices.count(); ++columnIndex) + { + const QString &heading(headerIndices.value(columnIndex)); + if("etype" == heading.toLower()) + { + stream << "0"; // Phone calls are type '0' + } + else if("etime" == heading.toLower()) + { + stream << Timestamp().toUTC().toString("dd/MM/yyyy h:mm:ss ap"); + } + else if("remote" == heading.toLower()) + { + stream << numberToNameLookup.ContactDetails().value(Tel()).second; + } + else if("direction" == heading.toLower()) + { + if(IsMissedCall()) + { + if(!strings.contains("Missed Call")) + strings.insert("Missed Call", strings.count()); + stream << strings.value("Missed Call"); + } + else if(Settings::OUTGOING == Destination()) + { + if(!strings.contains("Outgoing")) + strings.insert("Outgoing", strings.count()); + stream << strings.value("Outgoing"); + } + else if (Settings::INCOMING == Destination()) + { + if(!strings.contains("Incoming")) + strings.insert("Incoming", strings.count()); + stream << strings.value("Incoming"); + } + } + else if("duration" == heading.toLower()) + { + stream << DurationInSeconds(); + } + else if("dtype" == heading.toLower()) + { + stream << "1"; // 1 seems to be KLogDurationValid + } + else if("status" == heading.toLower()) + { + stream << "0"; // Always '0' for phone calls. + } + else if("subject" == heading.toLower()) + { + // Subject seems to be ignored - this makes sense, but I + // suspect that non-zero values are a bug that I can't duplicate... + stream << "0"; + } + else if("number" == heading.toLower()) + { + stream << Tel(); + } + else if("data" == heading.toLower()) + { + // Event-specfic data - but not supported by DBU-SCV tool + // Hex editing the DBU suggests there is SIP account info in here... + // ...along the lines of: + // "VOIP.URL=sip:@.MA=sip:@" + stream << "Unrecognised"; + } + else + { + // Don't print anything. Makes it obvious which fields we've + // generated. + } + + if(columnIndex < (uint)headerIndices.count() - 1) + stream << delimiter; + } + + stream << endl; } QDebug operator<<(QDebug dbg, PhoneCall& event) diff --git a/EventTypes/PhoneCall.h b/EventTypes/PhoneCall.h index 8fea880..415fb7f 100644 --- a/EventTypes/PhoneCall.h +++ b/EventTypes/PhoneCall.h @@ -26,6 +26,7 @@ #include #include +#include "EventTypes/iCSVSymbianEvent.h" #include "RtcomEvent.h" #include "AttachmentCollection.h" @@ -34,7 +35,7 @@ class Settings; namespace EventTypes { - class PhoneCall : public RtcomEvent + class PhoneCall : public RtcomEvent, public EventTypes::iCSVSymbianEvent { public: virtual const DBBackends::iDBBackend &DB() const; @@ -64,10 +65,12 @@ namespace EventTypes void IsMissedCall(const bool isMissedCall) { m_IsMissedCall = isMissedCall; } public: - virtual void Export(const QString &baseDirectory, const NumberToNameLookup &numberToNameLookup) const; PhoneCall(const Settings &settings, const RTComElEvent& event, const QList attachments = QList()); virtual RTComElEvent * toRTComEvent(const NumberToNameLookup &numberToNameLookup) const; + public: + virtual void WriteCSVSymbian(QTextStream &stream, const ColumnIndicesByIndexHash &headerIndices, const QChar delimiter, const NumberToNameLookup &numberToNameLookup, SymbianEventLogStrings &strings) const; + protected: const Settings &CurrentSettings() const { return m_Settings; } diff --git a/EventTypes/SMS.cpp b/EventTypes/SMS.cpp index e147c8d..c067fe6 100644 --- a/EventTypes/SMS.cpp +++ b/EventTypes/SMS.cpp @@ -24,16 +24,10 @@ #include "NumberToNameLookup.h" #include "Settings.h" -#include -#include -#include -#include #include #include #include -#include - #include #include @@ -127,36 +121,96 @@ RTComElEvent * SMS::toRTComEvent(const NumberToNameLookup &numberToNameLookup) c return event; } -void SMS::Export(const QString &baseDirectory, const NumberToNameLookup &numberToNameLookup) const +void SMS::WriteCSVSymbian(QTextStream &stream, const ColumnIndicesByIndexHash &headerIndices, const QChar delimiter, const NumberToNameLookup &numberToNameLookup, SymbianEventLogStrings &strings) const { - // Build the path and ensure it exists... - QString eventFilename(baseDirectory); - eventFilename += Destination() == EventTypes::SMS::SENT ? "/Sent/" : "/Inbox/"; - eventFilename += QString::number(Timestamp().toUTC().date().year()) + "/"; - QDir().mkpath(eventFilename); - - // ...then build the filename and open it. - eventFilename += QString::number(Timestamp().toUTC().toTime_t()) + ".vmg"; - QFile data(eventFilename); - if (data.open(QFile::WriteOnly | QFile::Truncate)) + // 0|05/09/2007 11:25:12 am|1|||||||3|8|0|0||||Unrecognized|| + for(uint columnIndex(0); columnIndex < headerIndices.count(); ++columnIndex) { - QTextStream stream(&data); - - QTextCodec *oldCodec = stream.codec(); - stream.setAutoDetectUnicode(false); - stream.setCodec("UTF-16LE"); - - EventParsers::VMGEntities::VMessage writer(CurrentSettings(), NULL, 1.1); - writer.Write(stream, *this, numberToNameLookup); -//stream << "Test"; - //stream.setCodec(oldCodec); - stream.flush(); - data.close(); - - utimbuf fileTimes; - fileTimes.modtime = Timestamp().toUTC().toTime_t(); - utime(eventFilename.toAscii(), &fileTimes); + const QString &heading(headerIndices.value(columnIndex)); + if("etype" == heading.toLower()) + { + stream << "3"; // SMSes are type '3' + } + else if("etime" == heading.toLower()) + { + stream << Timestamp().toUTC().toString("dd/MM/yyyy h:mm:ss ap"); + } + else if("remote" == heading.toLower()) + { + stream << numberToNameLookup.ContactDetails().value(Tel()).second; + } + else if("direction" == heading.toLower()) + { + if(Settings::OUTGOING == Destination()) + { + if(!strings.contains("Outgoing")) + strings.insert("Outgoing", strings.count()); + stream << strings.value("Outgoing"); + } + else if (Settings::INCOMING == Destination()) + { + if(!strings.contains("Incoming")) + strings.insert("Incoming", strings.count()); + stream << strings.value("Incoming"); + } + } + else if("duration" == heading.toLower()) + { + stream << "0"; // SMSes are always 0 + } + else if("dtype" == heading.toLower()) + { + stream << "-1"; // -1 seems to match KLogDurationNone + } + else if("status" == heading.toLower()) + { + stream << "0"; // Always '0' for phone calls. + } + else if("subject" == heading.toLower()) + { + // Subject seems to be ignored - this makes sense, but I + // suspect that non-zero values are a bug that I can't duplicate... + stream << "0"; + } + else if("number" == heading.toLower()) + { + stream << Tel(); + } + else if("data" == heading.toLower()) + { + // Event-specfic data - but not supported by DBU-SCV tool + // Hex editing the DBU suggests there is SIP account info in here... + // ...along the lines of: + // "VOIP.URL=sip:@.MA=sip:@" + stream << "Unrecognised"; + } + else + { + // Don't print anything. Makes it obvious which fields we've + // generated. + } + + if(columnIndex < headerIndices.count() - 1) + stream << delimiter; } + + stream << endl; +} + +const QString SMS::PathForVMG() const +{ + QString eventPath("/sms"); + eventPath += Destination() == EventTypes::SMS::SENT ? "/Sent/" : "/Inbox/"; + eventPath += QString::number(Timestamp().toUTC().date().year()) + "/"; + + return eventPath; +} + +void SMS::WriteVMG(QTextStream &stream, const NumberToNameLookup &numberToNameLookup) const +{ + EventParsers::VMGEntities::VMessage writer(CurrentSettings(), NULL, 1.1); + writer.Write(stream, *this, numberToNameLookup); + stream.flush(); } QDebug operator<<(QDebug dbg, SMS& event) diff --git a/EventTypes/SMS.h b/EventTypes/SMS.h index 403a820..41cb589 100644 --- a/EventTypes/SMS.h +++ b/EventTypes/SMS.h @@ -25,6 +25,8 @@ #include #include "RtcomEvent.h" +#include "EventTypes/iCSVSymbianEvent.h" +#include "EventTypes/iVMGEvent.h" #include "AttachmentCollection.h" @@ -32,7 +34,7 @@ class Settings; namespace EventTypes { - class SMS : public RtcomEvent + class SMS : public RtcomEvent, public EventTypes::iVMGEvent, public EventTypes::iCSVSymbianEvent { public: enum eDestination @@ -69,14 +71,19 @@ namespace EventTypes bool Pending() const { return m_Pending; } void Pending(const bool pending) { m_Pending = pending; } + virtual const AttachmentCollection & Attachments() const { return m_Attachments; } virtual AttachmentCollection & Attachments() { return m_Attachments; } public: - virtual void Export(const QString &baseDirectory, const NumberToNameLookup &numberToNameLookup) const; SMS(const Settings &settings, const RTComElEvent& event, const QList attachments = QList()); virtual RTComElEvent * toRTComEvent(const NumberToNameLookup &numberToNameLookup) const; + public: + virtual void WriteCSVSymbian(QTextStream &stream, const ColumnIndicesByIndexHash &headerIndices, const QChar delimiter, const NumberToNameLookup &numberToNameLookup, SymbianEventLogStrings &strings) const; + virtual const QString PathForVMG() const; + virtual void WriteVMG(QTextStream &stream, const NumberToNameLookup &numberToNameLookup) const; + protected: const Settings &CurrentSettings() const { return m_Settings; } diff --git a/EventTypes/iCSVSymbianEvent.h b/EventTypes/iCSVSymbianEvent.h new file mode 100644 index 0000000..e808a22 --- /dev/null +++ b/EventTypes/iCSVSymbianEvent.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#ifndef EVENTTYPES_ICSVSYMBIANEVENT_H +#define EVENTTYPES_ICSVSYMBIANEVENT_H + +class NumberToNameLookup; + +class QChar; +template class QHash; +class QString; +class QTextStream; +template class QVector; + +namespace EventTypes +{ + class iCSVSymbianEvent + { + public: + typedef QVector ColumnIndicesByIndexHash; + typedef QHash SymbianEventLogStrings; + virtual void WriteCSVSymbian(QTextStream &stream, const ColumnIndicesByIndexHash &headerIndices, const QChar delimiter, const NumberToNameLookup &numberToNameLookup, SymbianEventLogStrings &strings) const =0; + }; +} + +#endif // EVENTTYPES_ICSVSYMBIANEVENT_H diff --git a/EventTypes/iEvent.h b/EventTypes/iEvent.h index 36aebdb..d7ad8a3 100644 --- a/EventTypes/iEvent.h +++ b/EventTypes/iEvent.h @@ -24,7 +24,6 @@ namespace DBBackends { class iDBBackend; } class AttachmentCollection; -class NumberToNameLookup; class QDateTime; class QString; @@ -36,10 +35,6 @@ namespace EventTypes public: virtual ~iEvent() {} - virtual const DBBackends::iDBBackend &DB() const =0; - - virtual void Export(const QString &baseDirectory, const NumberToNameLookup &numberToNameLookup) const =0; - virtual const QDateTime Timestamp() const =0; virtual const AttachmentCollection & Attachments() const =0; virtual AttachmentCollection & Attachments() =0; diff --git a/EventTypes/iVMGEvent.h b/EventTypes/iVMGEvent.h new file mode 100644 index 0000000..96f9854 --- /dev/null +++ b/EventTypes/iVMGEvent.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#ifndef EVENTTYPES_IVMGEVENT_H +#define EVENTTYPES_IVMGEVENT_H + +class NumberToNameLookup; + +class QString; +class QTextStream; + +namespace EventTypes +{ + class iVMGEvent + { + public: + virtual void WriteVMG(QTextStream &stream, const NumberToNameLookup &numberToNameLookup) const =0; + virtual const QString PathForVMG() const =0; + }; +} + +#endif // EVENTTYPES_IVMGEVENT_H diff --git a/EventWriters/CSVSymbianEventLogWriter.cpp b/EventWriters/CSVSymbianEventLogWriter.cpp new file mode 100644 index 0000000..7f9b91d --- /dev/null +++ b/EventWriters/CSVSymbianEventLogWriter.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#include "CSVSymbianEventLogWriter.h" + +#include "EventTypes/iCSVSymbianEvent.h" +#include "EventTypes/iEvent.h" +#include "Settings.h" + +#include + +#include +#include +#include + +#include +#include + +using namespace EventWriters; + +CSVSymbianEventLogWriter::CSVSymbianEventLogWriter(const Settings &settings, const NumberToNameLookup &numberToNameLookup) + : m_Settings(settings), m_NumberToNameLookup(numberToNameLookup), m_Stream(NULL), m_Delimiter('|'), m_File(NULL) +{ + m_Headers << "EType" << "ETime" << "DType" << "Flag1" << "Flag2" << "Flag3" + << "Flag4" << "Id" << "Remote" << "Direction" << "Duration" + << "Status" << "Subject" << "Number" << "Contact" << "Link" + << "Data" << "Recent" << "Duplicate"; +} + +CSVSymbianEventLogWriter::~CSVSymbianEventLogWriter() +{ + if(Stream()) + { + Stream()->flush(); + delete Stream(); + } + + if(File()) + { + File()->close(); + delete File(); + } + + // Build the strings filepath + QString stringsFilename(CurrentSettings().Directory()); + stringsFilename += "/Call Logs/Strings.csv"; + + QFile stringsFile(stringsFilename); + if (stringsFile.open(QFile::WriteOnly | QFile::Truncate)) + { + QTextStream stream(&stringsFile); + stream.setAutoDetectUnicode(false); + stream.setCodec("UTF-16LE"); + + WriteStrings(stream); + } +} + +void CSVSymbianEventLogWriter::Write(EventTypes::iEvent &event) +{ + EventTypes::iCSVSymbianEvent *csvEvent(dynamic_cast(&event)); + if(csvEvent) + { + if(!Stream()) + OpenStream(); + + csvEvent->WriteCSVSymbian(*Stream(), Headers(), Delimiter(), NameLookup(), Strings()); + } +} + +void CSVSymbianEventLogWriter::OpenStream() +{ + // Build the path and ensure it exists... + QString eventFilename(CurrentSettings().Directory()); + eventFilename += "/Call Logs"; + QDir().mkpath(eventFilename); + + // ...then build the filename and open it. + eventFilename += "/Symbian Event Log.csv"; + File(new QFile(eventFilename)); + if (File()->open(QFile::WriteOnly | QFile::Truncate)) + { + Stream(new QTextStream(File())); + Stream()->setAutoDetectUnicode(false); + Stream()->setCodec("UTF-16LE"); + + WriteHeaders(); + } +} + +void CSVSymbianEventLogWriter::WriteHeaders() +{ + foreach(QString header, Headers()) + { + *Stream() << header; + if(Headers().back() != header) + *Stream() << "|"; + } + *Stream() << endl; +} + +void CSVSymbianEventLogWriter::WriteStrings(QTextStream &stringsStream) +{ + foreach(QString string, Strings().keys()) + stringsStream << "\"" << string << "\"|" << Strings().value(string) << endl; +} diff --git a/EventWriters/CSVSymbianEventLogWriter.h b/EventWriters/CSVSymbianEventLogWriter.h new file mode 100644 index 0000000..897419d --- /dev/null +++ b/EventWriters/CSVSymbianEventLogWriter.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#ifndef EVENTWRITERS_CSVSYMBIANEVENTLOGWRITER_H +#define EVENTWRITERS_CSVSYMBIANEVENTLOGWRITER_H + +#include "EventFormats/SymbianEventLog.h" +#include "iEventWriter.h" + +class NumberToNameLookup; +class Settings; + +#include +class QFile; +#include +class QString; +class QTextStream; +#include + +namespace EventWriters +{ + class CSVSymbianEventLogWriter : public iEventWriter, public SymbianEventLog + { + public: + typedef QVector ColumnIndicesByIndexHash; + + CSVSymbianEventLogWriter(const Settings &settings, const NumberToNameLookup &numberToNameLookup); + ~CSVSymbianEventLogWriter(); + + virtual void Write(EventTypes::iEvent &event); + + protected: + void OpenStream(); + + private: + void WriteHeaders(); + void WriteStrings(QTextStream &stringsStream); + + // Properties + protected: + const Settings &CurrentSettings() const { return m_Settings; } + const NumberToNameLookup &NameLookup() const { return m_NumberToNameLookup; } + QTextStream *const Stream() const { return m_Stream; } + const ColumnIndicesByIndexHash Headers() const { return m_Headers; } + const QChar Delimiter() const { return m_Delimiter; } + StringsLookup &Strings() { return m_Strings; } + + private: + const Settings &m_Settings; + const NumberToNameLookup &m_NumberToNameLookup; + + void Stream(QTextStream *const stream) { m_Stream = stream; } + QTextStream *m_Stream; + void Headers(ColumnIndicesByIndexHash &headers) { m_Headers = headers; } + ColumnIndicesByIndexHash m_Headers; + void Delimiter(QChar delimiter) { m_Delimiter = delimiter; } + QChar m_Delimiter; + void Strings(StringsLookup &strings) { m_Strings = strings; } + StringsLookup m_Strings; + + QFile *const File() const { return m_File; } + void File(QFile *const file) { m_File = file; } + QFile *m_File; + }; +} + +#endif // EVENTWRITERS_CSVSYMBIANEVENTLOGWRITER_H diff --git a/EventWriters/VMGWriter.cpp b/EventWriters/VMGWriter.cpp new file mode 100644 index 0000000..4626fa0 --- /dev/null +++ b/EventWriters/VMGWriter.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#include "EventWriters/VMGWriter.h" + +#include "EventTypes/iVMGEvent.h" +#include "EventTypes/iEvent.h" +#include "Settings.h" + +#include +#include +#include +#include + +#include +#include + +using namespace EventWriters; + +VMGWriter::VMGWriter(const Settings &settings, const NumberToNameLookup &numberToNameLookup) + : m_Settings(settings), m_NumberToNameLookup(numberToNameLookup) +{ +} + +void VMGWriter::Write(EventTypes::iEvent &event) +{ + EventTypes::iVMGEvent *vmgEvent(dynamic_cast(&event)); + if(vmgEvent) + { + // Build the path and ensure it exists... + QString eventFilename(CurrentSettings().Directory()); + eventFilename += vmgEvent->PathForVMG(); + QDir().mkpath(eventFilename); + + // ...then build the filename and open it. + eventFilename += QString::number(event.Timestamp().toUTC().toTime_t()) + ".vmg"; + QFile data(eventFilename); + if (data.open(QFile::WriteOnly | QFile::Truncate)) + { + QTextStream stream(&data); + + //QTextCodec *oldCodec = stream.codec(); + stream.setAutoDetectUnicode(false); + stream.setCodec("UTF-16LE"); + + vmgEvent->WriteVMG(stream, NameLookup()); + + data.close(); + } + + utimbuf fileTimes; + fileTimes.modtime = event.Timestamp().toUTC().toTime_t(); + utime(eventFilename.toAscii(), &fileTimes); + } +} diff --git a/EventWriters/VMGWriter.h b/EventWriters/VMGWriter.h new file mode 100644 index 0000000..e53d50e --- /dev/null +++ b/EventWriters/VMGWriter.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#ifndef EVENTWRITERS_VMGWRITER_H +#define EVENTWRITERS_VMGWRITER_H + +#include "iEventWriter.h" + +class NumberToNameLookup; +class Settings; + +namespace EventWriters +{ + class VMGWriter : public iEventWriter + { + public: + VMGWriter(const Settings &setting, const NumberToNameLookup &numberToNameLookup); + + virtual void Write(EventTypes::iEvent &event); + + protected: + const Settings &CurrentSettings() const { return m_Settings; } + const NumberToNameLookup &NameLookup() { return m_NumberToNameLookup; } + + private: + const Settings &m_Settings; + const NumberToNameLookup &m_NumberToNameLookup; + }; +} + +#endif // EVENTWRITERS_VMGWRITER_H diff --git a/EventWriters/iEventWriter.h b/EventWriters/iEventWriter.h new file mode 100644 index 0000000..f0512f4 --- /dev/null +++ b/EventWriters/iEventWriter.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011, Jamie Thompson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see + * . + */ + +#ifndef EVENTWRITERS_IEVENTWRITER_H +#define EVENTWRITERS_IEVENTWRITER_H + +namespace EventTypes { class iEvent; } + +namespace EventWriters +{ + class iEventWriter + { + public: + virtual ~iEventWriter() {} + virtual void Write(EventTypes::iEvent &event) =0; + }; +} + +#endif // EVENTWRITERS_IEVENTWRITER_H diff --git a/SyncerThread.cpp b/SyncerThread.cpp index 07858e8..e01cb92 100644 --- a/SyncerThread.cpp +++ b/SyncerThread.cpp @@ -96,6 +96,7 @@ void SyncerThread::run() EventProcessors::Writer eventWriter(CurrentSettings(), lookup); QObject::connect(&eventWriter, SIGNAL(EventProcessed(int,int)), this, SIGNAL(EventProcessed(int,int))); allBackends.Process(eventWriter); + QObject::disconnect(&eventWriter, SIGNAL(EventProcessed(int,int)), this, SIGNAL(EventProcessed(int,int))); } else { diff --git a/qwerkisync.pro b/qwerkisync.pro index f834b8f..75a6c67 100644 --- a/qwerkisync.pro +++ b/qwerkisync.pro @@ -60,7 +60,11 @@ SOURCES += main.cpp\ EventProcessors/Writer.cpp \ EventTypes/PhoneCall.cpp \ DBBackends/AllBackends.cpp \ - DBBackends/RtcomEventLoggerComponents/TriggerDisabler.cpp + DBBackends/RtcomEventLoggerComponents/TriggerDisabler.cpp \ + EventWriters/VMGWriter.cpp \ + EventWriters/CSVSymbianEventLogWriter.cpp \ + CSV.cpp \ + EventFormats/SymbianEventLog.cpp HEADERS += \ Windows/ModeWindow.h \ @@ -108,7 +112,14 @@ HEADERS += \ EventTypes/PhoneCall.h \ EventTypes/RtcomEvent.h \ EventTypes/eEventTypes.h \ - DBBackends/RtcomEventLoggerComponents/TriggerDisabler.h + DBBackends/RtcomEventLoggerComponents/TriggerDisabler.h \ + EventWriters/VMGWriter.h \ + EventWriters/iEventWriter.h \ + EventWriters/CSVSymbianEventLogWriter.h \ + EventTypes/iCSVSymbianEvent.h \ + EventTypes/iVMGEvent.h \ + CSV.h \ + EventFormats/SymbianEventLog.h FORMS += \ dialog.ui