From 6a658b3be01bb4025bad6c904dd22713676e114f Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sat, 18 Jul 2020 21:44:05 -0400 Subject: [PATCH 1/6] Enable serial device recording in the main application. This is transparent to the user and is recorded into a log directory within the OSCAR_Data directory. Also add log rotation so that these logs don't grow forever. --- oscar/SleepLib/deviceconnection.cpp | 2 +- oscar/logger.cpp | 86 +++++++++++++++++++++++++++++ oscar/logger.h | 4 ++ oscar/main.cpp | 22 +++++++- 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/oscar/SleepLib/deviceconnection.cpp b/oscar/SleepLib/deviceconnection.cpp index 06da68bc..ab5cd39c 100644 --- a/oscar/SleepLib/deviceconnection.cpp +++ b/oscar/SleepLib/deviceconnection.cpp @@ -92,7 +92,7 @@ void DeviceConnectionManager::replay(QFile* file) // Return singleton instance of DeviceConnectionManager, creating it if necessary. -inline DeviceConnectionManager & DeviceConnectionManager::getInstance() +DeviceConnectionManager & DeviceConnectionManager::getInstance() { static DeviceConnectionManager instance; return instance; diff --git a/oscar/logger.cpp b/oscar/logger.cpp index 986b5c40..1ded710a 100644 --- a/oscar/logger.cpp +++ b/oscar/logger.cpp @@ -8,6 +8,9 @@ * for more details. */ #include "logger.h" +#include "SleepLib/preferences.h" +#include "version.h" +#include #define ASSERTS_SUCK @@ -147,3 +150,86 @@ void LogThread::run() } +QString GetLogDir() +{ + static const QString LOG_DIR_NAME = "logs"; + + QDir oscarData(GetAppData()); + Q_ASSERT(oscarData.exists()); + if (!oscarData.exists(LOG_DIR_NAME)) { + oscarData.mkdir(LOG_DIR_NAME); + } + QDir logDir(oscarData.canonicalPath() + "/" + LOG_DIR_NAME); + if (!logDir.exists()) { + qWarning() << "Unable to create" << logDir.absolutePath() << "reverting to" << oscarData.canonicalPath(); + logDir = oscarData; + } + Q_ASSERT(logDir.exists()); + + return logDir.canonicalPath(); +} + +void rotateLogs(const QString & filePath, int maxPrevious) +{ + if (maxPrevious < 0) { + if (getVersion().IsReleaseVersion()) { + maxPrevious = 1; + } else { + // keep more in testing builds + maxPrevious = 4; + } + } + + // Build the list of rotated logs for this filePath. + QFileInfo info(filePath); + QString path = QDir(info.absolutePath()).canonicalPath(); + QString base = info.baseName(); + QString ext = info.completeSuffix(); + if (!ext.isEmpty()) { + ext = "." + ext; + } + if (path.isEmpty()) { + qWarning() << "Skipping log rotation, directory does not exist:" << info.absoluteFilePath(); + return; + } + + QStringList logs; + logs.append(filePath); + for (int i = 0; i < maxPrevious; i++) { + logs.append(QString("%1/%2.%3%4").arg(path).arg(base).arg(i).arg(ext)); + } + + // Remove the expired log. + QFileInfo expired(logs[maxPrevious]); + if (expired.exists()) { + if (expired.isDir()) { + QDir dir(expired.canonicalFilePath()); + //qDebug() << "Removing expired log directory" << dir.canonicalPath(); + if (!dir.removeRecursively()) { + qWarning() << "Unable to delete expired log directory" << dir.canonicalPath(); + } + } else { + QFile file(expired.canonicalFilePath()); + //qDebug() << "Removing expired log file" << file.fileName(); + if (!file.remove()) { + qWarning() << "Unable to delete expired log file" << file.fileName(); + } + } + } + + // Rotate the remaining logs. + for (int i = maxPrevious; i > 0; i--) { + QFileInfo from(logs[i-1]); + QFileInfo to(logs[i]); + if (from.exists()) { + if (to.exists()) { + qWarning() << "Unable to rotate log:" << to.absoluteFilePath() << "exists"; + continue; + } + //qDebug() << "Renaming" << from.absoluteFilePath() << "to" << to.absoluteFilePath(); + if (!QFile::rename(from.absoluteFilePath(), to.absoluteFilePath())) { + qWarning() << "Unable to rename" << from.absoluteFilePath() << "to" << to.absoluteFilePath(); + } + } + } +} diff --git a/oscar/logger.h b/oscar/logger.h index 6868c25c..65ddf948 100644 --- a/oscar/logger.h +++ b/oscar/logger.h @@ -10,6 +10,10 @@ void initializeLogger(); void shutdownLogger(); +QString GetLogDir(); +void rotateLogs(const QString & filePath, int maxPrevious=-1); + + void MyOutputHandler(QtMsgType type, const QMessageLogContext &context, const QString &msgtxt); class LogThread:public QObject, public QRunnable diff --git a/oscar/main.cpp b/oscar/main.cpp index 23b96666..dcccb77a 100644 --- a/oscar/main.cpp +++ b/oscar/main.cpp @@ -28,6 +28,7 @@ #include "SleepLib/profiles.h" #include "translation.h" #include "SleepLib/common.h" +#include "SleepLib/deviceconnection.h" #include #include @@ -636,6 +637,21 @@ int main(int argc, char *argv[]) { MD300W1Loader::Register(); ViatomLoader::Register(); + // Begin logging device connection activity. + QString connectionsLogDir = GetLogDir() + "/connections"; + rotateLogs(connectionsLogDir); // keep a limited set of previous logs + if (!QDir(connectionsLogDir).mkpath(".")) { + qWarning().noquote() << "Unable to create directory" << connectionsLogDir; + } + + QFile deviceLog(connectionsLogDir + "/devices.xml"); + if (deviceLog.open(QFile::ReadWrite)) { + qDebug().noquote() << "Logging device connections to" << deviceLog.fileName(); + DeviceConnectionManager::getInstance().record(&deviceLog); + } else { + qWarning().noquote() << "Unable to start device connection logging to" << deviceLog.fileName(); + } + schema::setOrders(); // could be called in init... // Scan for user profiles @@ -653,7 +669,11 @@ int main(int argc, char *argv[]) { mainwin->SetupGUI(); mainwin->show(); - return a.exec(); + int result = a.exec(); + + DeviceConnectionManager::getInstance().record(nullptr); + + return result; } #endif // !UNITTEST_MODE From 20dfb666b0c06728c313826ed45f4eab7f627b8d Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 19 Jul 2020 16:36:22 -0400 Subject: [PATCH 2/6] Write the debug log to a file within the OSCAR data folder. Also immediately emit debug messages to console even before the UI is initialized. And present a useful error if the OSCAR data folder can't be created. --- oscar/logger.cpp | 48 +++++++++++++++++++++++++++++++++++++----------- oscar/logger.h | 5 ++++- oscar/main.cpp | 10 ++++++++++ oscar/oscar.pro | 1 + 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/oscar/logger.cpp b/oscar/logger.cpp index 1ded710a..39955e37 100644 --- a/oscar/logger.cpp +++ b/oscar/logger.cpp @@ -12,8 +12,6 @@ #include "version.h" #include -#define ASSERTS_SUCK - QThreadPool * otherThreadPool = NULL; void MyOutputHandler(QtMsgType type, const QMessageLogContext &context, const QString &msgtxt) @@ -51,11 +49,7 @@ void MyOutputHandler(QtMsgType type, const QMessageLogContext &context, const QS if (logger && logger->isRunning()) { logger->append(msg); } -#ifdef ASSERTS_SUCK -// else { -// fprintf(stderr, "%s\n", msg.toLocal8Bit().data()); -// } -#endif + fprintf(stderr, "%s\n", msg.toLocal8Bit().constData()); if (type == QtFatalMsg) { abort(); @@ -91,6 +85,25 @@ void LogThread::connectionReady() qDebug() << "Logging UI initialized"; } +void LogThread::logToFile() +{ + QString debugLog = GetLogDir() + "/debug.txt"; + rotateLogs(debugLog); // keep a limited set of previous logs + + strlock.lock(); + m_logFile = new QFile(debugLog); + Q_ASSERT(m_logFile); + if (m_logFile->open(QFile::ReadWrite | QFile::Text)) { + m_logStream = new QTextStream(m_logFile); + } + strlock.unlock(); + if (m_logStream) { + qDebug().noquote() << "Logging to" << debugLog; + } else { + qWarning().noquote() << "Unable to open" << debugLog; + } +} + void shutdownLogger() { if (logger) { @@ -126,7 +139,16 @@ void LogThread::quit() { strlock.lock(); while (!buffer.isEmpty()) { QString msg = buffer.takeFirst(); - fprintf(stderr, "%s\n", msg.toLocal8Bit().constData()); + if (m_logStream) { + *m_logStream << msg << endl; + } + } + if (m_logStream) { + delete m_logStream; + m_logStream = nullptr; + Q_ASSERT(m_logFile); + delete m_logFile; + m_logFile = nullptr; } strlock.unlock(); } @@ -139,10 +161,13 @@ void LogThread::run() do { strlock.lock(); //int r = receivers(SIGNAL(outputLog(QString()))); - while (connected && !buffer.isEmpty()) { + // Wait to flush the buffer until the UI is connected and the log file has been opened. + while (connected && m_logFile && !buffer.isEmpty()) { QString msg = buffer.takeFirst(); - fprintf(stderr, "%s\n", msg.toLocal8Bit().data()); - emit outputLog(msg); + if (m_logStream) { + *m_logStream << msg << endl; + } + emit outputLog(msg); } strlock.unlock(); QThread::msleep(1000); @@ -154,6 +179,7 @@ QString GetLogDir() { static const QString LOG_DIR_NAME = "logs"; + Q_ASSERT(!GetAppData().isEmpty()); // If GetLogDir gets called before GetAppData() is valid, this would point at root. QDir oscarData(GetAppData()); Q_ASSERT(oscarData.exists()); if (!oscarData.exists(LOG_DIR_NAME)) { diff --git a/oscar/logger.h b/oscar/logger.h index 65ddf948..df1d0045 100644 --- a/oscar/logger.h +++ b/oscar/logger.h @@ -20,7 +20,7 @@ class LogThread:public QObject, public QRunnable { Q_OBJECT public: - explicit LogThread() : QRunnable() { running = false; logtime.start(); connected = false; } + explicit LogThread() : QRunnable() { running = false; logtime.start(); connected = false; m_logFile = nullptr; m_logStream = nullptr; } virtual ~LogThread() {} void run(); @@ -28,6 +28,7 @@ public: void appendClean(QString msg); bool isRunning() { return running; } void connectionReady(); + void logToFile(); void quit(); @@ -40,6 +41,8 @@ protected: volatile bool running; QTime logtime; bool connected; + class QFile* m_logFile; + class QTextStream* m_logStream; }; extern LogThread * logger; diff --git a/oscar/main.cpp b/oscar/main.cpp index dcccb77a..cc19464d 100644 --- a/oscar/main.cpp +++ b/oscar/main.cpp @@ -545,6 +545,16 @@ int main(int argc, char *argv[]) { } } + // Make sure the data directory exists. + if (!newDir.mkpath(".")) { + QMessageBox::warning(nullptr, QObject::tr("Exiting"), + QObject::tr("Unable to create the OSCAR data folder at")+"\n"+ + GetAppData()); + return 0; + } + + // Begin logging to file now that there's a data folder. + logger->logToFile(); /////////////////////////////////////////////////////////////////////////////////////////// // Initialize preferences system (Don't use p_pref before this point!) diff --git a/oscar/oscar.pro b/oscar/oscar.pro index 37aa096f..5becfa8d 100644 --- a/oscar/oscar.pro +++ b/oscar/oscar.pro @@ -500,6 +500,7 @@ lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { # Create a debug GUI build by adding "CONFIG+=memdebug" to your qmake command memdebug { + CONFIG += debug !win32 { # add memory checking on Linux and macOS debug builds QMAKE_CFLAGS += -g -Werror -fsanitize=address -fno-omit-frame-pointer -fno-common -fsanitize-address-use-after-scope lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,9) { From 162e5695b181ed6c85889a787d7070abf500dcfe Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 19 Jul 2020 21:34:08 -0400 Subject: [PATCH 3/6] Update the OSCAR data zip to use the live debug log instead of creating one. Also fix the logger so that messages are logged immediately rather than once per second. --- oscar/logger.cpp | 29 +++++++++++++++++++++-------- oscar/logger.h | 3 +++ oscar/mainwindow.cpp | 30 +++++++++++------------------- oscar/zip.cpp | 13 +++++++++++-- oscar/zip.h | 2 +- 5 files changed, 47 insertions(+), 30 deletions(-) diff --git a/oscar/logger.cpp b/oscar/logger.cpp index 39955e37..79b069d1 100644 --- a/oscar/logger.cpp +++ b/oscar/logger.cpp @@ -48,8 +48,9 @@ void MyOutputHandler(QtMsgType type, const QMessageLogContext &context, const QS if (logger && logger->isRunning()) { logger->append(msg); + } else { + fprintf(stderr, "%s\n", msg.toLocal8Bit().constData()); } - fprintf(stderr, "%s\n", msg.toLocal8Bit().constData()); if (type == QtFatalMsg) { abort(); @@ -81,12 +82,18 @@ void LogThread::connectionReady() { strlock.lock(); connected = true; + logTrigger.wakeAll(); strlock.unlock(); qDebug() << "Logging UI initialized"; } void LogThread::logToFile() { + if (m_logStream) { + qWarning().noquote() << "Already logging to" << m_logFile->fileName(); + return; + } + QString debugLog = GetLogDir() + "/debug.txt"; rotateLogs(debugLog); // keep a limited set of previous logs @@ -96,6 +103,7 @@ void LogThread::logToFile() if (m_logFile->open(QFile::ReadWrite | QFile::Text)) { m_logStream = new QTextStream(m_logFile); } + logTrigger.wakeAll(); strlock.unlock(); if (m_logStream) { qDebug().noquote() << "Logging to" << debugLog; @@ -104,6 +112,14 @@ void LogThread::logToFile() } } +QString LogThread::logFileName() +{ + if (!m_logFile) { + return ""; + } + return m_logFile->fileName(); +} + void shutdownLogger() { if (logger) { @@ -119,16 +135,15 @@ LogThread * logger = NULL; void LogThread::append(QString msg) { QString tmp = QString("%1: %2").arg(logtime.elapsed(), 5, 10, QChar('0')).arg(msg); - //QStringList appears not to be threadsafe - strlock.lock(); - buffer.append(tmp); - strlock.unlock(); + appendClean(tmp); } void LogThread::appendClean(QString msg) { + fprintf(stderr, "%s\n", msg.toLocal8Bit().constData()); strlock.lock(); buffer.append(msg); + logTrigger.wakeAll(); strlock.unlock(); } @@ -160,8 +175,7 @@ void LogThread::run() s_LoggerRunning.unlock(); // unlock as soon as the thread begins to run do { strlock.lock(); - //int r = receivers(SIGNAL(outputLog(QString()))); - // Wait to flush the buffer until the UI is connected and the log file has been opened. + logTrigger.wait(&strlock); while (connected && m_logFile && !buffer.isEmpty()) { QString msg = buffer.takeFirst(); if (m_logStream) { @@ -170,7 +184,6 @@ void LogThread::run() emit outputLog(msg); } strlock.unlock(); - QThread::msleep(1000); } while (running); } diff --git a/oscar/logger.h b/oscar/logger.h index df1d0045..e7ca3584 100644 --- a/oscar/logger.h +++ b/oscar/logger.h @@ -5,6 +5,7 @@ #include #include #include +#include #include void initializeLogger(); @@ -29,6 +30,7 @@ public: bool isRunning() { return running; } void connectionReady(); void logToFile(); + QString logFileName(); void quit(); @@ -43,6 +45,7 @@ protected: bool connected; class QFile* m_logFile; class QTextStream* m_logStream; + QWaitCondition logTrigger; }; extern LogThread * logger; diff --git a/oscar/mainwindow.cpp b/oscar/mainwindow.cpp index e7890dfe..b33b291b 100644 --- a/oscar/mainwindow.cpp +++ b/oscar/mainwindow.cpp @@ -2682,7 +2682,7 @@ void MainWindow::on_actionCreate_Card_zip_triggered() infostr = datacard.loader->loaderName(); } prefix = infostr; - folder += QDir::separator() + infostr + ".zip"; + folder += "/" + infostr + ".zip"; filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); @@ -2734,7 +2734,7 @@ void MainWindow::on_actionCreate_OSCAR_Data_zip_triggered() // Note: macOS ignores this and points to OSCAR's most recently used directory for saving. folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - folder += QDir::separator() + STR_AppData + ".zip"; + folder += "/" + STR_AppData + ".zip"; QString filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); @@ -2749,7 +2749,6 @@ void MainWindow::on_actionCreate_OSCAR_Data_zip_triggered() qDebug() << "Create zip of OSCAR data folder:" << filename; QDir oscarData(GetAppData()); - QFile debugLog(oscarData.canonicalPath() + QDir::separator() + "debuglog.txt"); ZipFile z; bool ok = z.Open(filename); @@ -2759,29 +2758,22 @@ void MainWindow::on_actionCreate_OSCAR_Data_zip_triggered() prog->setWindowModality(Qt::ApplicationModal); prog->open(); - // Build the list of files and exclude any existing debug log. + // Build the list of files. FileQueue files; files.AddDirectory(oscarData.canonicalPath(), oscarData.dirName()); - files.Remove(debugLog.fileName()); + + // Defer the current debug log to the end. + QString debugLog = logger->logFileName(); + QString debugLogZipName; + int exists = files.Remove(debugLog, &debugLogZipName); + if (exists) { + files.AddFile(debugLog, debugLogZipName); + } prog->setMessage(tr("Creating zip...")); // Create the zip. ok = z.AddFiles(files, prog); - if (ok && z.aborted() == false) { - // Update the debug log and add it last. - ok = debugLog.open(QIODevice::WriteOnly); - if (ok) { - debugLog.write(ui->logText->toPlainText().toLocal8Bit().data()); - debugLog.close(); - QString debugLogName = oscarData.dirName() + "/" + QFileInfo(debugLog).fileName(); - ok = z.AddFile(debugLog.fileName(), debugLogName); - if (!ok) { - qWarning() << "Unable to add debug log to zip!"; - } - } - } - z.Close(); } else { qWarning() << "Unable to open" << filename; diff --git a/oscar/zip.cpp b/oscar/zip.cpp index 60a439c8..48c51fa8 100644 --- a/oscar/zip.cpp +++ b/oscar/zip.cpp @@ -225,7 +225,7 @@ bool FileQueue::AddFile(const QString & path, const QString & prefix) return true; } -int FileQueue::Remove(const QString & path) +int FileQueue::Remove(const QString & path, QString* outName) { QFileInfo fi(path); QString canonicalPath = fi.canonicalFilePath(); @@ -235,6 +235,13 @@ int FileQueue::Remove(const QString & path) while (i.hasNext()) { Entry & entry = i.next(); if (entry.path == canonicalPath) { + if (outName) { + // If the caller cares about the name, it will most likely be re-added later rather than skipped. + *outName = entry.name; + } else { + qDebug().noquote() << "skipping file:" << path; + } + if (fi.isDir()) { m_dir_count--; } else { @@ -243,10 +250,12 @@ int FileQueue::Remove(const QString & path) } i.remove(); removed++; - qDebug().noquote() << "skipping file:" << path; } } + if (removed > 1) { + qWarning().noquote() << removed << "copies found in zip queue:" << path; + } return removed; } diff --git a/oscar/zip.h b/oscar/zip.h index d5d2fd8e..c7488f53 100644 --- a/oscar/zip.h +++ b/oscar/zip.h @@ -63,7 +63,7 @@ public: ~FileQueue() = default; //!brief Remove a file from the queue, return the number of instances removed. - int Remove(const QString & path); + int Remove(const QString & path, QString* outName=nullptr); //!brief Recursively add a directory and its contents to the queue along with the prefix to be used in an archive. bool AddDirectory(const QString & path, const QString & prefix=""); From 5e9d391ccc8ac9ac9c681ec3cad400c1e60f0232 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Mon, 20 Jul 2020 19:12:46 -0400 Subject: [PATCH 4/6] Fix a rare race condition when shutting down the logger. This resulted in an occasional use-after-free crash every once in a while. --- oscar/logger.cpp | 46 +++++++++++++++++++++++++++------------------- oscar/logger.h | 2 +- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/oscar/logger.cpp b/oscar/logger.cpp index 79b069d1..8c1ade84 100644 --- a/oscar/logger.cpp +++ b/oscar/logger.cpp @@ -105,6 +105,7 @@ void LogThread::logToFile() } logTrigger.wakeAll(); strlock.unlock(); + if (m_logStream) { qDebug().noquote() << "Logging to" << debugLog; } else { @@ -112,6 +113,20 @@ void LogThread::logToFile() } } +LogThread::~LogThread() +{ + QMutexLocker lock(&strlock); + + Q_ASSERT(running == false); + if (m_logStream) { + delete m_logStream; + m_logStream = nullptr; + Q_ASSERT(m_logFile); + delete m_logFile; + m_logFile = nullptr; + } +} + QString LogThread::logFileName() { if (!m_logFile) { @@ -124,7 +139,8 @@ void shutdownLogger() { if (logger) { logger->quit(); - otherThreadPool->waitForDone(-1); + // The thread is automatically destroyed when its run() method exits. + otherThreadPool->waitForDone(-1); // wait until that happens logger = NULL; } delete otherThreadPool; @@ -150,32 +166,23 @@ void LogThread::appendClean(QString msg) void LogThread::quit() { qDebug() << "Shutting down logging thread"; - running = false; + qInstallMessageHandler(0); // Remove our logger. + strlock.lock(); - while (!buffer.isEmpty()) { - QString msg = buffer.takeFirst(); - if (m_logStream) { - *m_logStream << msg << endl; - } - } - if (m_logStream) { - delete m_logStream; - m_logStream = nullptr; - Q_ASSERT(m_logFile); - delete m_logFile; - m_logFile = nullptr; - } - strlock.unlock(); + running = false; // Force the thread to exit after its next iteration. + logTrigger.wakeAll(); // Trigger the final flush. + strlock.unlock(); // Release the lock so that the thread can complete. } void LogThread::run() { + QMutexLocker lock(&strlock); + running = true; s_LoggerRunning.unlock(); // unlock as soon as the thread begins to run do { - strlock.lock(); - logTrigger.wait(&strlock); + logTrigger.wait(&strlock); // releases strlock while it waits while (connected && m_logFile && !buffer.isEmpty()) { QString msg = buffer.takeFirst(); if (m_logStream) { @@ -183,8 +190,9 @@ void LogThread::run() } emit outputLog(msg); } - strlock.unlock(); } while (running); + + // strlock will be released when lock goes out of scope } diff --git a/oscar/logger.h b/oscar/logger.h index e7ca3584..2f4a1803 100644 --- a/oscar/logger.h +++ b/oscar/logger.h @@ -22,7 +22,7 @@ class LogThread:public QObject, public QRunnable Q_OBJECT public: explicit LogThread() : QRunnable() { running = false; logtime.start(); connected = false; m_logFile = nullptr; m_logStream = nullptr; } - virtual ~LogThread() {} + virtual ~LogThread(); void run(); void append(QString msg); From 7316ac676c693af6c5653ee021abee04e1487457 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Tue, 21 Jul 2020 13:21:29 -0400 Subject: [PATCH 5/6] Add a menu item to create a zip of all diagnostic logs. Also fix a buffering issue in XmlRecorder that left devices.xml empty in the zip. --- oscar/SleepLib/xmlreplay.cpp | 9 +++++++ oscar/SleepLib/xmlreplay.h | 1 + oscar/mainwindow.cpp | 50 ++++++++++++++++++++++++++++++++++++ oscar/mainwindow.h | 2 ++ oscar/mainwindow.ui | 6 +++++ 5 files changed, 68 insertions(+) diff --git a/oscar/SleepLib/xmlreplay.cpp b/oscar/SleepLib/xmlreplay.cpp index 6782d018..cd121dcd 100644 --- a/oscar/SleepLib/xmlreplay.cpp +++ b/oscar/SleepLib/xmlreplay.cpp @@ -116,6 +116,14 @@ void XmlRecorder::epilogue() m_xml->writeEndElement(); // close enclosing tag } +void XmlRecorder::flush() +{ + if (m_file) { + if (!m_file->flush()) { + qWarning().noquote() << "Unable to flush XML to" << m_file->fileName(); + } + } +} XmlReplay::XmlReplay(QFile* file, const QString & tag) : m_tag(tag), m_file(file), m_pendingSignal(nullptr), m_parent(nullptr) @@ -416,6 +424,7 @@ void XmlReplayEvent::record(XmlRecorder* writer) const if (writer != nullptr) { writer->lock(); writer->xml() << *this; + writer->flush(); writer->unlock(); } } diff --git a/oscar/SleepLib/xmlreplay.h b/oscar/SleepLib/xmlreplay.h index 81fd12a9..5c784cd9 100644 --- a/oscar/SleepLib/xmlreplay.h +++ b/oscar/SleepLib/xmlreplay.h @@ -54,6 +54,7 @@ public: inline QXmlStreamWriter & xml() { return *m_xml; } inline void lock() { m_mutex.lock(); } inline void unlock() { m_mutex.unlock(); } + void flush(); protected: XmlRecorder(XmlRecorder* parent, const QString & id, const QString & tag); // constructor used by substreams diff --git a/oscar/mainwindow.cpp b/oscar/mainwindow.cpp index b33b291b..c330304d 100644 --- a/oscar/mainwindow.cpp +++ b/oscar/mainwindow.cpp @@ -2727,6 +2727,56 @@ void MainWindow::on_actionCreate_Card_zip_triggered() } } + +void MainWindow::on_actionCreate_Log_zip_triggered() +{ + QString folder; + + // Note: macOS ignores this and points to OSCAR's most recently used directory for saving. + folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + folder += "/OSCAR-logs.zip"; + QString filename = QFileDialog::getSaveFileName(this, tr("Choose where to save zip"), folder, tr("ZIP files (*.zip)")); + if (filename.isEmpty()) { + return; // aborted + } + if (!filename.toLower().endsWith(".zip")) { + filename += ".zip"; + } + + qDebug() << "Create zip of OSCAR diagnostic logs:" << filename; + + ZipFile z; + bool ok = z.Open(filename); + if (ok) { + ProgressDialog * prog = new ProgressDialog(this); + prog->setMessage(tr("Creating zip...")); + + // Build the list of files. + FileQueue files; + files.AddDirectory(GetLogDir(), "logs"); + + // Defer the current debug log to the end. + QString debugLog = logger->logFileName(); + QString debugLogZipName; + int exists = files.Remove(debugLog, &debugLogZipName); + if (exists) { + files.AddFile(debugLog, debugLogZipName); + } + + // Create the zip. + ok = z.AddFiles(files, prog); + z.Close(); + } else { + qWarning() << "Unable to open" << filename; + } + if (!ok) { + QMessageBox::warning(nullptr, STR_MessageBox_Error, + QObject::tr("Unable to create zip!"), + QMessageBox::Ok); + } +} + + void MainWindow::on_actionCreate_OSCAR_Data_zip_triggered() { QString folder; diff --git a/oscar/mainwindow.h b/oscar/mainwindow.h index 0f389833..93834d6b 100644 --- a/oscar/mainwindow.h +++ b/oscar/mainwindow.h @@ -344,6 +344,8 @@ class MainWindow : public QMainWindow void on_actionCreate_Card_zip_triggered(); + void on_actionCreate_Log_zip_triggered(); + void on_actionCreate_OSCAR_Data_zip_triggered(); void on_actionReport_a_Bug_triggered(); diff --git a/oscar/mainwindow.ui b/oscar/mainwindow.ui index df56f053..9b797bae 100644 --- a/oscar/mainwindow.ui +++ b/oscar/mainwindow.ui @@ -2866,6 +2866,7 @@ p, li { white-space: pre-wrap; } + @@ -3232,6 +3233,11 @@ p, li { white-space: pre-wrap; } Create zip of CPAP data card + + + Create zip of OSCAR diagnostic logs + + Create zip of all OSCAR data From cfabdbe74289dfce928a253ce7ad08d435f96b6c Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Tue, 21 Jul 2020 13:51:20 -0400 Subject: [PATCH 6/6] Improve gcc fix from d2fc5ac. Evidently gcc doesn't recognize inline forward declaration, but clang does. I wonder who is right? This was previously addressed by adding an #include. Instead, this patch adds a standalone forward declaration of the QXmlStreamReader/Writer classes. This has been tested and verified to compile with gcc and clang. --- oscar/SleepLib/deviceconnection.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oscar/SleepLib/deviceconnection.h b/oscar/SleepLib/deviceconnection.h index f56bc031..352aab29 100644 --- a/oscar/SleepLib/deviceconnection.h +++ b/oscar/SleepLib/deviceconnection.h @@ -13,7 +13,6 @@ // connections to devices. For now it just supports serial ports. #include -#include #include #include @@ -263,6 +262,9 @@ public: * supports Bluetooth and BLE as well as serial. Such a class might then be * used instead of port "name" between DeviceConnectionManager and clients. */ +class QXmlStreamWriter; +class QXmlStreamReader; + class SerialPortInfo { public: