dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
logfile.cpp
Go to the documentation of this file.
1 
12 /*
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 3 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful, but
19  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  * for more details.
22  *
23  * You should have received a copy of the GNU General Public License along
24  * with this program; if not, see <http://www.gnu.org/licenses/>
25  */
26 
27 #include "logfile.h"
28 #include <QDebug>
29 #include <QtGlobal>
30 #include <QTextStream>
31 #include <QMainWindow>
32 #include <QMessageBox>
33 
34 #include <coreplugin/icore.h>
36 
37 LogFile::LogFile(QObject *parent)
38  : QIODevice(parent)
39  , timestampBufferIdx(0)
40 {
41  connect(&timer, SIGNAL(timeout()), this, SLOT(timerFired()));
42 }
43 
49 bool LogFile::open(OpenMode mode)
50 {
51 
52  // start a timer for playback
53  myTime.restart();
54  if (file.isOpen()) {
55  // We end up here when doing a replay, because the connection
56  // manager will also try to open the QIODevice, even though we just
57  // opened it after selecting the file, which happens before the
58  // connection manager call...
59  return true;
60  }
61 
62  // Open file as either WriteOnly, or ReadOnly, depending on `mode` parameter
63  if (file.open(mode) == false) {
64  qDebug() << "Unable to open " << file.fileName() << " for logging";
65  return false;
66  }
67 
68  // TODO: Write a header at the beginng describing objects so that in future
69  // they can be read back if ID's change
70 
71  /*This addresses part of the TODO. It writes the git hash to the beginning of the file. This
72  * will
73  * not protect against data losses due to UAVO that have changed when there is no commit to
74  * public
75  * git, or to commits that are based off of branches that have since been pruned. As such, this
76  * can only be seen as a temporary fix.
77  */
78  if (mode == QIODevice::WriteOnly) {
79  QString gitHash = QString::fromLatin1(Core::Constants::GCS_REVISION_STR);
80  // UAVOSHA1_STR looks something like: "{
81  // 0xbd,0xfc,0x47,0x16,0x59,0xb9,0x08,0x18,0x1c,0x82,0x5e,0x3f,0xe1,0x1a,0x77,0x7f,0x4e,0x06,0xea,0x7c
82  // }"
83  // This string needs to be reduced to just the hex letters, so in the example we need:
84  // bdfc471659b908181c825e3fe11a777f4e06ea7c
85  QString uavoHash = QString::fromLatin1(Core::Constants::UAVOSHA1_STR)
86  .replace("\"{ ", "")
87  .replace(" }\"", "")
88  .replace(",", "")
89  .replace("0x", "");
90  QTextStream out(&file);
91 
92  out << "dRonin git hash:\n" << gitHash << "\n" << uavoHash << "\n##\n";
93  } else if (mode == QIODevice::ReadOnly) {
94  file.readLine(); // Read first line of log file. This assumes that the logfile is of the new
95  // format.
96  QString logGitHashString = file.readLine().trimmed(); // Read second line of log file. This
97  // assumes that the logfile is of the
98  // new format.
99  QString logUAVOHashString = file.readLine().trimmed(); // Read third line of log file. This
100  // assumes that the logfile is of the
101  // new format.
102  QString gitHash = QString::fromLatin1(Core::Constants::GCS_REVISION_STR);
103  QString uavoHash =
104  QString::fromLatin1(Core::Constants::UAVOSHA1_STR)
105  .replace("\"{ ", "")
106  .replace(" }\"", "")
107  .replace(",", "")
108  .replace("0x", ""); // See comment above for necessity for string replacements
109 
110  if (logUAVOHashString != uavoHash) {
111  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
112  msgBox.setText("Likely log file incompatibility.");
113  msgBox.setInformativeText(QString("The log file was made with branch %1, UAVO hash %2. "
114  "GCS will attempt to play the file.")
115  .arg(logGitHashString)
116  .arg(logUAVOHashString));
117  msgBox.exec();
118  } else if (logGitHashString != gitHash) {
119  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
120  msgBox.setText("Possible log file incompatibility.");
121  msgBox.setInformativeText(
122  QString("The log file was made with branch %1. GCS will attempt to play the file.")
123  .arg(logGitHashString));
124  msgBox.exec();
125  }
126 
127  QString tmpLine = file.readLine(); // Look for the header/body separation string.
128  int cnt = 0;
129  while (tmpLine != "##\n" && cnt < 10 && !file.atEnd()) {
130  tmpLine = file.readLine().trimmed();
131  cnt++;
132  }
133 
134  // Check if we reached the end of the file before finding the separation string
135  if (cnt >= 10 || file.atEnd()) {
136  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
137  msgBox.setText("Corrupted file.");
138  msgBox.setInformativeText("GCS cannot find the separation byte. GCS will attempt to "
139  "play the file."); //<--TODO: add hyperlink to webpage with
140  // better description.
141  msgBox.exec();
142 
143  // Since we could not find the file separator, we need to return to the beginning of the
144  // file
145  file.seek(0);
146  }
147 
148  } else {
149  qDebug() << "Logging read/write mode incorrectly set.";
150  }
151 
152  // Must call parent function for QIODevice to pass calls to writeData
153  // We always open ReadWrite, because otherwise we will get tons of warnings
154  // during a logfile replay. Read nature is checked upon write ops below.
155  QIODevice::open(QIODevice::ReadWrite);
156 
157  return true;
158 }
159 
161 {
162  emit aboutToClose();
163 
164  if (timer.isActive())
165  timer.stop();
166  file.close();
167  QIODevice::close();
168 }
169 
170 qint64 LogFile::writeData(const char *data, qint64 dataSize)
171 {
172  if (!file.isWritable())
173  return dataSize;
174 
175  quint32 timeStamp = myTime.elapsed();
176 
177  file.write(reinterpret_cast<char *>(&timeStamp), sizeof(timeStamp));
178  file.write(reinterpret_cast<char *>(&dataSize), sizeof(dataSize));
179 
180  qint64 written = file.write(data, dataSize);
181  if (written != -1)
182  emit bytesWritten(written);
183 
184  return dataSize;
185 }
186 
187 qint64 LogFile::readData(char *data, qint64 maxSize)
188 {
189  QMutexLocker locker(&mutex);
190  qint64 toRead = qMin(maxSize, (qint64)dataBuffer.size());
191  memcpy(data, dataBuffer.data(), toRead);
192  dataBuffer.remove(0, toRead);
193  return toRead;
194 }
195 
197 {
198  return dataBuffer.size();
199 }
200 
202 {
203  qint64 dataSize;
204 
205  if (file.bytesAvailable() > 4) {
206 
207  int time;
208  time = myTime.elapsed();
209 
210  // Read packets
211  while ((lastPlayTime + ((time - lastPlayTimeOffset) * playbackSpeed)
212  > (lastTimeStamp - firstTimestamp))) {
213  lastPlayTime += ((time - lastPlayTimeOffset) * playbackSpeed);
214  if (file.bytesAvailable() < 4) {
215  stopReplay();
216  return;
217  }
218 
219  file.seek(lastTimeStampPos + sizeof(lastTimeStamp));
220 
221  file.read(reinterpret_cast<char *>(&dataSize), sizeof(dataSize));
222 
223  if (dataSize < 1 || dataSize > (1024 * 1024)) {
224  qDebug() << "Error: Logfile corrupted! Unlikely packet size: " << dataSize << "\n";
225  stopReplay();
226  return;
227  }
228  if (file.bytesAvailable() < dataSize) {
229  stopReplay();
230  return;
231  }
232 
233  mutex.lock();
234  dataBuffer.append(file.read(dataSize));
235  mutex.unlock();
236  emit readyRead();
237 
238  if (file.bytesAvailable() < 4) {
239  stopReplay();
240  return;
241  }
242 
243  lastTimeStampPos = timestampPos[timestampBufferIdx];
244  lastTimeStamp = timestampBuffer[timestampBufferIdx];
245  timestampBufferIdx++;
246 
247  lastPlayTimeOffset = time;
248  time = myTime.elapsed();
249  }
250  } else {
251  stopReplay();
252  }
253 }
254 
256 {
257  dataBuffer.clear();
258  myTime.restart();
259  lastPlayTimeOffset = 0;
260  lastPlayTime = 0;
261  playbackSpeed = 1;
262 
263  // Read all log timestamps into array
264  timestampBuffer.clear(); // Save beginning of log for later use
265  timestampPos.clear();
266  quint64 logFileStartIdx = file.pos();
267  timestampBufferIdx = 0;
268  lastTimeStamp = 0;
269 
270  while (!file.atEnd()) {
271  qint64 dataSize;
272 
273  // Get time stamp position
274  timestampPos.append(file.pos());
275 
276  // Read timestamp and logfile packet size
277  file.read(reinterpret_cast<char *>(&lastTimeStamp), sizeof(lastTimeStamp));
278  file.read(reinterpret_cast<char *>(&dataSize), sizeof(dataSize));
279 
280  // Check if dataSize sync bytes are correct.
281  // TODO: LIKELY AS NOT, THIS WILL FAIL TO RESYNC BECAUSE THERE IS TOO LITTLE INFORMATION IN
282  // THE STRING OF SIX 0x00
283  if ((dataSize & 0xFFFFFFFFFFFF0000) != 0) {
284  qDebug() << "Wrong sync byte. At file location 0x"
285  << QString("%1").arg(file.pos(), 0, 16) << "Got 0x"
286  << QString("%1").arg(dataSize & 0xFFFFFFFFFFFF0000, 0, 16)
287  << ", but expected 0x"
288  "00"
289  ".";
290  file.seek(timestampPos.last() + 1);
291  timestampPos.pop_back();
292  continue;
293  }
294 
295  // Check if timestamps are sequential.
296  if (!timestampBuffer.isEmpty() && lastTimeStamp < timestampBuffer.last()) {
297  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
298  msgBox.setText("Corrupted file.");
299  msgBox.setInformativeText("Timestamps are not sequential. Playback may have unexpected "
300  "behavior"); //<--TODO: add hyperlink to webpage with better
301  // description.
302  msgBox.exec();
303 
304  qDebug() << "Timestamp: " << timestampBuffer.last() << " " << lastTimeStamp;
305  }
306 
307  timestampBuffer.append(lastTimeStamp);
308 
309  file.seek(timestampPos.last() + sizeof(lastTimeStamp) + sizeof(dataSize) + dataSize);
310  }
311 
312  // Check if any timestamps were successfully read
313  if (timestampBuffer.size() == 0) {
314  QMessageBox msgBox(dynamic_cast<QWidget *>(Core::ICore::instance()->mainWindow()));
315  msgBox.setText("Empty logfile.");
316  msgBox.setInformativeText("No log data can be found.");
317  msgBox.exec();
318 
319  stopReplay();
320  return false;
321  }
322 
323  // Reset to log beginning.
324  file.seek(logFileStartIdx + sizeof(lastTimeStamp));
325  lastTimeStampPos = timestampPos[0];
326  lastTimeStamp = timestampBuffer[0];
327  firstTimestamp = timestampBuffer[0];
328  timestampBufferIdx = 1;
329 
330  timer.setInterval(10);
331  timer.start();
332  emit replayStarted();
333  return true;
334 }
335 
337 {
338  close();
339  emit replayFinished();
340  return true;
341 }
342 
344 {
345  timer.stop();
346 }
347 
349 {
350  lastPlayTimeOffset = myTime.elapsed();
351  timer.start();
352 }
353 
358 void LogFile::setReplayTime(double val)
359 {
360  quint32 tmpIdx = 0;
361  while (timestampBuffer[tmpIdx++] <= val * 1000 && tmpIdx <= timestampBufferIdx) {
362  }
363 
364  lastTimeStampPos = timestampPos[tmpIdx];
365  lastTimeStamp = timestampBuffer[tmpIdx];
366  timestampBufferIdx = tmpIdx;
367 
368  lastPlayTimeOffset = myTime.elapsed();
370 
371  qDebug() << "Replaying at: " << lastTimeStamp << ", but requestion at" << val * 1000;
372 }
qint64 writeData(const char *data, qint64 dataSize)
Definition: logfile.cpp:170
QTimer timer
Definition: logfile.h:75
void setReplayTime(double val)
LogFile::setReplayTime, sets the playback time.
Definition: logfile.cpp:358
const char *const UAVOSHA1_STR
Definition: coreconstants.h:46
qint64 bytesAvailable() const
Definition: logfile.cpp:196
QTime myTime
Definition: logfile.h:76
QFile file
Definition: logfile.h:77
qint64 readData(char *data, qint64 maxlen)
Definition: logfile.cpp:187
bool open(OpenMode mode)
Definition: logfile.cpp:49
void replayStarted()
bool startReplay()
Definition: logfile.cpp:255
Plugin for generating a logfile.
DataFields data
QByteArray dataBuffer
Definition: logfile.h:74
end function out
void timerFired()
Definition: logfile.cpp:201
static ICore * instance()
Definition: coreimpl.cpp:46
bool stopReplay()
Definition: logfile.cpp:336
int lastPlayTimeOffset
Definition: logfile.h:82
double playbackSpeed
Definition: logfile.h:83
quint32 lastPlayTime
Definition: logfile.h:79
const char *const GCS_REVISION_STR
Definition: coreconstants.h:45
void close()
Definition: logfile.cpp:160
QMutex mutex
Definition: logfile.h:80
void resumeReplay()
Definition: logfile.cpp:348
void pauseReplay()
Definition: logfile.cpp:343
quint32 lastTimeStamp
Definition: logfile.h:78
LogFile(QObject *parent=nullptr)
Definition: logfile.cpp:37
void replayFinished()