/*=================================================================== QGroundControl Open Source Ground Control Station (c) 2009, 2010 QGROUNDCONTROL PROJECT This file is part of the QGROUNDCONTROL project QGROUNDCONTROL 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. QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see . ======================================================================*/ /** * @file * @brief Implementation of class LogCompressor. This class reads in a file containing messages and translates it into a tab-delimited CSV file. * @author Lorenz Meier * */ #include #include #include #include #include #include #include "LogCompressor.h" #include /** * Initializes all the variables necessary for a compression run. This won't actually happen * until startCompression(...) is called. */ LogCompressor::LogCompressor(QString logFileName, QString outFileName, QString delimiter) : logFileName(logFileName), outFileName(outFileName), running(true), currentDataLine(0), delimiter(delimiter), holeFillingEnabled(true) { } void LogCompressor::run() { // Verify that the input file is useable QFile infile(logFileName); if (!infile.exists() || !infile.open(QIODevice::ReadOnly | QIODevice::Text)) { emit logProcessingStatusChanged(tr("Log Compressor: Cannot start/compress log file, since input file %1 is not readable").arg(QFileInfo(infile.fileName()).absoluteFilePath())); return; } // Verify that the output file is useable QFile outTmpFile("processed_" + outFileName); if (!outTmpFile.open(QIODevice::WriteOnly | QIODevice::Text)) { emit logProcessingStatusChanged(tr("Log Compressor: Cannot start/compress log file, since output file %1 is not writable").arg(QFileInfo(outTmpFile.fileName()).absoluteFilePath())); return; } // First we search the input file through keySearchLimit number of lines // looking for variables. This is neccessary before CSV files require // the same number of fields for every line. const unsigned int keySearchLimit = 15000; unsigned int keyCounter = 0; QTextStream in(&infile); QMap messageMap; while (!in.atEnd() && keyCounter < keySearchLimit) { QString messageName = in.readLine().split(delimiter).at(2); messageMap.insert(messageName, 0); ++keyCounter; } // Now update each key with its index in the output string. These are // all offset by one to account for the first field: timestamp_ms. QMap::iterator i = messageMap.begin(); int j; for (i = messageMap.begin(), j = 1; i != messageMap.end(); ++i, ++j) { i.value() = j; } // Open the output file and write the header line to it QStringList headerList(messageMap.keys()); QString headerLine = "timestamp_ms" + delimiter + headerList.join(delimiter) + "\n"; outTmpFile.write(headerLine.toLocal8Bit()); emit logProcessingStatusChanged(tr("Log compressor: Dataset contains dimension: ") + headerLine); // Reset our position in the input file before we start the main processing loop. infile.reset(); in.reset(); in.resetStatus(); // Template list stores a list for populating with data as it's parsed from messages. QStringList templateList; for (int i = 0; i < headerList.size() + 1; ++i) { templateList << (holeFillingEnabled?"NaN":""); } QStringList filledList(templateList); QStringList currentLine = in.readLine().split(delimiter); currentDataLine = 1; while (!in.atEnd()) { // We only overwrite data from the last time set if we aren't doing a zero-order hold if (!holeFillingEnabled) { filledList = templateList; } // Populate this time set with the data from this first message filledList.replace(0, currentLine.at(0)); filledList.replace(messageMap.value(currentLine.at(2)), currentLine.at(3)); // Continue searching for messages in the same time set and adding that data // to the current time set if appropriate. while (!in.atEnd()) { QStringList newLine = in.readLine().split(delimiter); ++currentDataLine; if (newLine.at(0) == currentLine.at(0)) { QString currentDataName = newLine.at(2); QString currentDataValue = newLine.at(3); filledList.replace(messageMap.value(currentDataName), currentDataValue); } else { currentLine = newLine; break; } } // Write this current time set out to the file QString output = filledList.join(delimiter) + "\n"; outTmpFile.write(output.toLocal8Bit()); } // We're now done with the source file infile.close(); // Make sure we remove the source file before replacing it. // QFile::remove(outFileName); // outTmpFile.copy(outFileName); // outTmpFile.close(); emit logProcessingStatusChanged(tr("Log Compressor: Writing output to file %1").arg(QFileInfo(outFileName).absoluteFilePath())); // Clean up and update the status before we return. currentDataLine = 0; emit logProcessingStatusChanged(tr("Log compressor: Finished processing file: %1").arg(outFileName)); emit finishedFile(outFileName); qDebug() << "Done with logfile processing"; running = false; } /** * @param holeFilling If hole filling is enabled, the compressor tries to fill empty data fields with previous * values from the same variable (or NaN, if no previous value existed) */ void LogCompressor::startCompression(bool holeFilling) { holeFillingEnabled = holeFilling; start(); } bool LogCompressor::isFinished() { return !running; } int LogCompressor::getCurrentLine() { return currentDataLine; }