From f36b36f8dacafd8dd9f41da340a9ff053bcb392c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 13 Mar 2021 14:31:43 +0100 Subject: [PATCH] Move AppLogController into libnymea-app --- libnymea-app/applogcontroller.cpp | 380 ++++++++++++++++++ .../applogcontroller.h | 114 ++++-- libnymea-app/connection/awsclient.cpp | 149 +++---- .../connection/discovery/upnpdiscovery.cpp | 34 +- .../discovery/zeroconfdiscovery.cpp | 35 +- libnymea-app/connection/nymeaconnection.cpp | 109 ++--- libnymea-app/libnymea-app-core.h | 4 + libnymea-app/libnymea-app.pri | 8 +- libnymea-app/logging.cpp | 6 + libnymea-app/logging.h | 20 + libnymea-app/thingmanager.cpp | 11 +- nymea-app/applogcontroller.cpp | 219 ---------- nymea-app/main.cpp | 18 +- nymea-app/nymea-app.pro | 2 - nymea-app/platformhelper.cpp | 10 + nymea-app/platformhelper.h | 2 + .../io/guh/nymeaapp/NymeaAppActivity.java | 18 + .../android/platformhelperandroid.cpp | 9 +- .../android/platformhelperandroid.h | 2 + .../generic/platformhelpergeneric.cpp | 4 + .../generic/screenhelper.cpp | 27 +- .../ios/platformhelperios.h | 2 + nymea-app/resources.qrc | 1 + nymea-app/ui/MainMenu.qml | 2 +- nymea-app/ui/Nymea.qml | 6 + nymea-app/ui/RootItem.qml | 10 +- nymea-app/ui/StyleBase.qml | 29 ++ nymea-app/ui/appsettings/AppLogPage.qml | 32 +- .../ui/appsettings/DeveloperOptionsPage.qml | 18 +- .../ui/appsettings/LoggingCategories.qml | 130 ++++++ nymea-app/ui/components/NymeaItemDelegate.qml | 1 - nymea-app/ui/delegates/InterfaceTile.qml | 4 + nymea-app/ui/mainviews/EnergyView.qml | 6 +- nymea-app/ui/utils/NymeaUtils.qml | 1 - packaging/android/AndroidManifest.xml | 10 + packaging/android/res/xml/file_paths.xml | 3 + packaging/ios/platformhelperios.mm | 8 + 37 files changed, 989 insertions(+), 455 deletions(-) create mode 100644 libnymea-app/applogcontroller.cpp rename {nymea-app => libnymea-app}/applogcontroller.h (50%) create mode 100644 libnymea-app/logging.cpp create mode 100644 libnymea-app/logging.h delete mode 100644 nymea-app/applogcontroller.cpp create mode 100644 nymea-app/ui/appsettings/LoggingCategories.qml create mode 100644 packaging/android/res/xml/file_paths.xml diff --git a/libnymea-app/applogcontroller.cpp b/libnymea-app/applogcontroller.cpp new file mode 100644 index 00000000..b5e605a7 --- /dev/null +++ b/libnymea-app/applogcontroller.cpp @@ -0,0 +1,380 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "applogcontroller.h" + +#include +#include +#include +#include +#include +#include + +#include "logging.h" + +QtMessageHandler AppLogController::s_oldLogMessageHandler = nullptr; + + +AppLogController::LogLevel AppLogController::qtMsgTypeToLogLevel(QtMsgType msgType) +{ + switch (msgType) { + case QtDebugMsg: + return LogLevelDebug; + case QtInfoMsg: + return LogLevelInfo; + case QtWarningMsg: + return LogLevelWarning; + default: + return LogLevelCritical; + } +} + +QtMsgType AppLogController::logLevelToQtMsgType(AppLogController::LogLevel logLevel) +{ + switch (logLevel) { + case LogLevelDebug: + return QtDebugMsg; + case LogLevelInfo: + return QtInfoMsg; + case LogLevelWarning: + return QtWarningMsg; + default: + return QtCriticalMsg; + } +} + +QObject *AppLogController::appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + return instance(); +} + +AppLogController *AppLogController::instance() +{ + static AppLogController* thiz = nullptr; + if (!thiz) { + thiz = new AppLogController(); + } + return thiz; +} + +AppLogController::AppLogController(QObject *parent) : QObject(parent) +{ + m_loggingCategories = new LoggingCategories(this); + + QSettings settings; + settings.beginGroup("LoggingLevels"); + foreach (const QString &category, nymeaLoggingCategories()) { + m_logLevels[category] = static_cast(settings.value(category, LogLevelInfo).toInt()); + } + settings.endGroup(); + updateFilters(); + + // Finally, install the logMessageHandler + s_oldLogMessageHandler = qInstallMessageHandler(&logMessageHandler); + + if (enabled()) { + openLogFile(); + } +} + +bool AppLogController::enabled() const +{ + QSettings settings; + return settings.value("AppLoggingEnabled", false).toBool(); +} + +void AppLogController::setEnabled(bool enabled) +{ + if (enabled == this->enabled()) { + return; + } + + if (enabled) { + openLogFile(); + } else { + m_logFile.close(); + } + QSettings settings; + settings.setValue("AppLoggingEnabled", enabled); + emit enabledChanged(); +} + +LoggingCategories *AppLogController::loggingCategories() const +{ + return m_loggingCategories; +} + +AppLogController::LogLevel AppLogController::logLevel(const QString &category) const +{ + return m_logLevels.value(category); +} + +void AppLogController::setLogLevel(const QString &category, AppLogController::LogLevel logLevel) +{ + m_logLevels[category] = logLevel; + + QSettings settings; + settings.beginGroup("LoggingLevels"); + settings.setValue(category, logLevel); + settings.endGroup(); + + emit categoryChanged(category, logLevel); +} + +QString AppLogController::logPath() const +{ + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/logs/"; +} + +QString AppLogController::currentLogFile() const +{ + return logPath() + "/" + QGuiApplication::applicationName() + ".log"; +} + +QStringList AppLogController::logFiles() const +{ + QDir dir(logPath()); + QStringList files; + foreach (const QString &file, dir.entryList({QGuiApplication::applicationName() + ".log*"})) { + files.append(logPath() + "/" + file); + } + return files; +} + +QString AppLogController::exportLogs() +{ + QFile f(logPath() + "/" + QGuiApplication::applicationName() + "-logs.txt"); + if (!f.open(QFile::WriteOnly)) { + return QString(); + } + foreach (const QString &logFile, logFiles()) { + QFile l(logFile); + if (!l.open(QFile::ReadOnly)) { + continue; + } + f.write("\n******** App start ********\n"); + f.write(logFile.toUtf8() + "\n"); + f.write(l.readAll()); + } + f.close(); + return f.fileName(); +} + +void AppLogController::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + s_oldLogMessageHandler(type, context, message); + QMetaObject::invokeMethod(instance(), "append", Q_ARG(QString, context.category), Q_ARG(QString, message), Q_ARG(AppLogController::LogLevel, qtMsgTypeToLogLevel(type))); +} + +void AppLogController::append(const QString &category, const QString &message, LogLevel level) +{ + if (m_logLevels.value(category) < level) { + return; + } + + if (m_logFile.isOpen()) { + QHash t = { + {LogLevelDebug, "D"}, + {LogLevelInfo, "I"}, + {LogLevelWarning, "W"}, + {LogLevelCritical, "C"} + }; + QString line = QString("%0: %1: %2\n").arg(t.value(level), category, message); + m_logFile.write(line.toUtf8()); + m_logFile.flush(); + } + + emit messageAdded(category, message, level); +} + +void AppLogController::updateFilters() +{ + QStringList loggingRules = {"*.warn=false", "*.info=false", "*.debug=false"}; + + // Load the rules from nymead.conf file and append them to the rules + foreach (const QString &category, nymeaLoggingCategories()) { + LogLevel level = m_logLevels.value(category, LogLevelWarning); + loggingRules << QString("%1.debug=%2").arg(category).arg(level >= LogLevelDebug ? "true" : "false"); + loggingRules << QString("%1.info=%2").arg(category).arg(level >= LogLevelInfo ? "true" : "false"); + loggingRules << QString("%1.warn=%2").arg(category).arg(level >= LogLevelWarning ? "true" : "false"); + } + QLoggingCategory::setFilterRules(loggingRules.join('\n')); +} + +void AppLogController::openLogFile() +{ + // Make sure log dir exists + if (!QDir().mkpath(logPath())) { + qWarning() << "Cannot create cache location. Logging will not work."; + } + + // Rotate old log files, keeping the last 5 + for (int i = 4; i > 0; i--) { + if (QFile::exists(currentLogFile() + "." + QString::number(i))) { + if (QFile::exists(currentLogFile() + "." + QString::number(i + 1))) { + QFile::remove(currentLogFile() + "." + QString::number(i + 1)); + } + QFile::rename(currentLogFile() + "." + QString::number(i), currentLogFile() + "." + QString::number(i + 1)); + } + } + if (QFile::exists(currentLogFile())) { + QFile::rename(currentLogFile(), currentLogFile() + ".1"); + } + + m_logFile.setFileName(currentLogFile()); + if (!m_logFile.open(QFile::ReadWrite | QFile::Truncate)) { + qWarning() << "Cannot open logfile for writing."; + } else { + qDebug() << "App log opened at" << m_logFile.fileName(); + } +} + +LogMessages::LogMessages(QObject *parent): + QAbstractListModel(parent) +{ + QFile f(AppLogController::instance()->currentLogFile()); + if (!f.open(QFile::ReadOnly)) { + return; + } + QHash map = { + {"C", AppLogController::LogLevelCritical}, + {"W", AppLogController::LogLevelWarning}, + {"I", AppLogController::LogLevelInfo}, + {"D", AppLogController::LogLevelDebug} + }; + while (!f.atEnd()) { + QByteArray line = f.readLine().trimmed(); + QList parts = line.split(':'); + if (parts.length() < 2) { + continue; + } + LogMessage message; + message.level = map.value(parts.takeFirst()); + message.category = parts.takeFirst(); + message.message = parts.join(":"); + m_messages.append(message); + } + connect(AppLogController::instance(), &AppLogController::messageAdded, this, [=](const QString &category, const QString &message, AppLogController::LogLevel level){ + beginInsertRows(QModelIndex(), m_messages.count(), m_messages.count()); + LogMessage msg; + msg.category = category; + msg.message = message; + msg.level = level; + m_messages.append(msg); + endInsertRows(); + }); +} + +int LogMessages::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_messages.count(); +} + +QVariant LogMessages::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleCategory: + return m_messages.at(index.row()).category; + case RoleMessage: + return m_messages.at(index.row()).message; + case RoleLevel: + return m_messages.at(index.row()).level; + case RoleText: + return m_messages.at(index.row()).category + ": " + m_messages.at(index.row()).message; + } + return QVariant(); +} + +QHash LogMessages::roleNames() const +{ + QHash roles; + roles.insert(RoleCategory, "category"); + roles.insert(RoleMessage, "message"); + roles.insert(RoleLevel, "level"); + roles.insert(RoleText, "text"); + return roles; +} + +void LogMessages::append(const QString &category, const QString &message, AppLogController::LogLevel level) +{ + beginInsertRows(QModelIndex(), m_messages.count(), m_messages.count()); + LogMessage msg; + msg.category = category; + msg.message = message; + msg.level = level; + m_messages.append(msg); + endInsertRows(); + + int maxEntries = 1024; + if (m_messages.size() > maxEntries) { + beginRemoveRows(QModelIndex(), 0, 0); + m_messages.removeFirst(); + endRemoveRows(); + } +} + +LoggingCategories::LoggingCategories(AppLogController *parent): + QAbstractListModel(parent), + m_controller(parent) +{ + connect(m_controller, &AppLogController::categoryChanged, this, [=](const QString &category) { + QModelIndex idx = index(nymeaLoggingCategories().indexOf(category)); + emit dataChanged(idx, idx, {RoleLevel}); + }); +} + +int LoggingCategories::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return nymeaLoggingCategories().count(); +} + +QVariant LoggingCategories::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleName: + return nymeaLoggingCategories().at(index.row()); + case RoleLevel: + return m_controller->logLevel(nymeaLoggingCategories().at(index.row())); + } + return QVariant(); +} + +QHash LoggingCategories::roleNames() const +{ + QHash roles; + roles.insert(RoleName, "name"); + roles.insert(RoleLevel, "logLevel"); + return roles; +} + diff --git a/nymea-app/applogcontroller.h b/libnymea-app/applogcontroller.h similarity index 50% rename from nymea-app/applogcontroller.h rename to libnymea-app/applogcontroller.h index a9f4c02b..3a46e1fc 100644 --- a/nymea-app/applogcontroller.h +++ b/libnymea-app/applogcontroller.h @@ -37,60 +37,120 @@ #include #include -class AppLogController : public QAbstractListModel +class LogMessages; +class LoggingCategories; + +class AppLogController : public QObject { Q_OBJECT - Q_PROPERTY(bool canWriteLogs READ canWriteLogs CONSTANT) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) - Q_PROPERTY(QString logFile READ logFile CONSTANT) + + Q_PROPERTY(LoggingCategories* loggingCategories READ loggingCategories CONSTANT) public: - enum Type { - TypeInfo, - TypeWarning + // Note: QtMsgType is sorted in a way that we can't compare for >= etc + enum LogLevel { + LogLevelCritical = 0, + LogLevelWarning = 1, + LogLevelInfo = 2, + LogLevelDebug = 3 }; - Q_ENUM(Type) - - enum Roles { - RoleText, - RoleType - }; - Q_ENUM(Roles) + Q_ENUM(LogLevel) + static LogLevel qtMsgTypeToLogLevel(QtMsgType msgType); + static QtMsgType logLevelToQtMsgType(LogLevel logLevel); static QObject* appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static AppLogController* instance(); - bool canWriteLogs() const; - bool enabled() const; void setEnabled(bool enabled); - int rowCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &parent, int role) const override; - QHash roleNames() const override; + LoggingCategories* loggingCategories() const; + LogLevel logLevel(const QString &category) const; + Q_INVOKABLE void setLogLevel(const QString &category, LogLevel logLevel); + Q_INVOKABLE QString logPath() const; + Q_INVOKABLE QString currentLogFile() const; + Q_INVOKABLE QStringList logFiles() const; - Q_INVOKABLE void toClipboard(); - - QString logFile() const; + Q_INVOKABLE QString exportLogs(); signals: void enabledChanged(); + void logToModelChanged(); + + void categoryChanged(const QString &category, LogLevel level); + void messageAdded(const QString &category, const QString &message, LogLevel level); private: explicit AppLogController(QObject *parent = nullptr); + static QtMessageHandler s_oldLogMessageHandler; static void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); - void append(const QString &message, Type type = TypeInfo); + Q_INVOKABLE void append(const QString &category, const QString &message, AppLogController::LogLevel level); - void activate(); - void deactivate(); + void updateFilters(); + void openLogFile(); + + QHash m_logLevels; - QFile m_logFile; - QStringList m_buffer; - QList m_types; QMutex m_mutex; + QFile m_logFile; + LoggingCategories *m_loggingCategories = nullptr; +}; +Q_DECLARE_METATYPE(AppLogController::LogLevel) + +class LogMessages: public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + RoleCategory, + RoleMessage, + RoleLevel, + RoleText + }; + Q_ENUM(Roles) + + struct LogMessage { + QString category; + QString message; + AppLogController::LogLevel level; + }; + + LogMessages(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + void append(const QString &category, const QString &message, AppLogController::LogLevel level); + +private: + QList m_messages; +}; + +class LoggingCategories: public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + RoleName, + RoleLevel + }; + Q_ENUM(Roles) + + LoggingCategories(AppLogController *parent); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + +private: + AppLogController *m_controller = nullptr; }; #endif // APPLOGCONTROLLER_H diff --git a/libnymea-app/connection/awsclient.cpp b/libnymea-app/connection/awsclient.cpp index a1120691..7085bda3 100644 --- a/libnymea-app/connection/awsclient.cpp +++ b/libnymea-app/connection/awsclient.cpp @@ -41,9 +41,12 @@ #include #include "sigv4utils.h" +#include "logging.h" AWSClient* AWSClient::s_instance = nullptr; +NYMEA_LOGGING_CATEGORY(dcCloud, "Cloud") + // This is Symantec's root CA certificate and most platforms should // have this in their certificate storage already, but as we can't // be certain about the core's setup, let's deploy it ourselves. @@ -187,11 +190,11 @@ bool AWSClient::confirmationPending() const void AWSClient::login(const QString &username, const QString &password) { if (m_usedConfig.isEmpty()) { - qDebug() << "AWS config not set. Not logging in."; + qCInfo(dcCloud()) << "AWS config not set. Not logging in."; return; } if (m_loginInProgress) { - qWarning() << "Login already pending..."; + qCDebug(dcCloud()) << "Login already pending..."; return; } m_loginInProgress = true; @@ -229,7 +232,7 @@ void AWSClient::login(const QString &username, const QString &password) QJsonDocument jsonDoc = QJsonDocument::fromVariant(params); QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact); - qDebug() << "Logging in to AWS as user:" << username << "with config" << m_usedConfig; + qCInfo(dcCloud()) << "Logging in to AWS as user:" << username << "with config" << m_usedConfig; QNetworkReply *reply = m_nam->post(request, payload); connect(reply, &QNetworkReply::finished, this, [this, reply, username, password]() { @@ -237,18 +240,18 @@ void AWSClient::login(const QString &username, const QString &password) m_loginInProgress = false; if (reply->error() != QNetworkReply::NoError) { if (reply->error() == QNetworkReply::HostNotFoundError) { - qDebug() << "Error logging in to aws due to network connection."; + qCWarning(dcCloud()) << "Error logging in to aws due to network connection."; emit loginResult(LoginErrorNetworkError); return; } if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) { - qWarning() << "Looks like a wrong password."; + qCWarning(dcCloud()) << "Looks like a wrong password."; m_username.clear(); m_password.clear(); emit loginResult(LoginErrorInvalidUserOrPass); return; } - qWarning() << "Error logging in to aws. Error:" << reply->error() << reply->errorString(); + qCWarning(dcCloud()) << "Error logging in to aws. Error:" << reply->error() << reply->errorString(); emit loginResult(LoginErrorUnknownError); return; } @@ -256,7 +259,7 @@ void AWSClient::login(const QString &username, const QString &password) QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qWarning() << "Failed to parse AWS login response" << error.errorString(); + qCWarning(dcCloud()) << "Failed to parse AWS login response" << error.errorString(); m_username.clear(); m_password.clear(); emit loginResult(LoginErrorUnknownError); @@ -272,7 +275,7 @@ void AWSClient::login(const QString &username, const QString &password) // qDebug() << "AWS ID token" << m_idToken; QList jwtParts = m_idToken.split('.'); if (jwtParts.count() != 3) { - qWarning() << "Error: JWT token doesn't have 3 parts. Cannot retrieve AWS Cognito ID."; + qCWarning(dcCloud()) << "Error: JWT token doesn't have 3 parts. Cannot retrieve AWS Cognito ID."; return; } // qDebug() << "decoded header:" << QByteArray::fromBase64(jwtParts.at(0)); @@ -331,13 +334,13 @@ void AWSClient::signup(const QString &username, const QString &password) QJsonDocument jsonDoc = QJsonDocument::fromVariant(params); QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact); - qDebug() << "Signing up to AWS as user:" << username << payload; + qCInfo(dcCloud()) << "Signing up to AWS as user:" << username << payload; QNetworkReply *reply = m_nam->post(request, payload); connect(reply, &QNetworkReply::finished, this, [this, reply]() { QByteArray data = reply->readAll(); reply->deleteLater(); - qDebug() << "AWS signup reply:" << data; + qCDebug(dcCloud()) << "AWS signup reply:" << data; if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) { emit signupResult(LoginErrorInvalidUserOrPass); @@ -345,7 +348,7 @@ void AWSClient::signup(const QString &username, const QString &password) } if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error signing up to aws:" << reply->error() << reply->errorString(); + qCWarning(dcCloud()) << "Error signing up to aws:" << reply->error() << reply->errorString(); m_username.clear(); m_password.clear(); emit signupResult(LoginErrorUnknownError); @@ -382,13 +385,13 @@ void AWSClient::confirmRegistration(const QString &code) QJsonDocument jsonDoc = QJsonDocument::fromVariant(params); QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact); - qDebug() << "Confirming registration for user:" << m_username; + qCInfo(dcCloud()) << "Confirming registration for user:" << m_username; QNetworkReply *reply = m_nam->post(request, payload); connect(reply, &QNetworkReply::finished, this, [this, reply]() { QByteArray data = reply->readAll(); reply->deleteLater(); - qDebug() << "AWS signup reply:" << data; + qCDebug(dcCloud()) << "AWS signup reply:" << data; if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) { QJsonParseError error; @@ -405,7 +408,7 @@ void AWSClient::confirmRegistration(const QString &code) } if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error confirming registration:" << reply->error() << reply->errorString(); + qCWarning(dcCloud()) << "Error confirming registration:" << reply->error() << reply->errorString(); emit confirmationResult(LoginErrorUnknownError); return; } @@ -440,7 +443,7 @@ void AWSClient::forgotPassword(const QString &username) QJsonDocument jsonDoc = QJsonDocument::fromVariant(params); QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact); - qDebug() << "Forgot password for user:" << username << payload; + qCInfo(dcCloud()) << "Forgot password for user:" << username << payload; QNetworkReply *reply = m_nam->post(request, payload); connect(reply, &QNetworkReply::finished, this, [this, reply]() { @@ -456,12 +459,12 @@ void AWSClient::forgotPassword(const QString &username) } if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error calling ForgotPassword:" << reply->error() << reply->errorString() << data; + qCWarning(dcCloud()) << "Error calling ForgotPassword:" << reply->error() << reply->errorString() << data; emit forgotPasswordResult(LoginErrorUnknownError); return; } - qDebug() << "AWS forgotPassword success:" << data; + qCInfo(dcCloud()) << "AWS forgotPassword success:" << data; emit forgotPasswordResult(LoginErrorNoError); }); @@ -491,7 +494,8 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co QJsonDocument jsonDoc = QJsonDocument::fromVariant(params); QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact); - qDebug() << "ConfirmForgotPassword for user:" << username << payload; + qCInfo(dcCloud()) << "Resetting password for user:" << username; + qCDebug(dcCloud()) << "Reset password payload:" << payload; QNetworkReply *reply = m_nam->post(request, payload); connect(reply, &QNetworkReply::finished, this, [this, reply]() { @@ -499,12 +503,12 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error calling ConfirmForgotPassword:" << reply->error() << reply->errorString() << data; + qCWarning(dcCloud()) << "Error calling ConfirmForgotPassword:" << reply->error() << reply->errorString() << data; emit confirmForgotPasswordResult(LoginErrorUnknownError); return; } - qDebug() << "AWS ConfirmForgotPassword success:" << data; + qCInfo(dcCloud()) << "Password reset successfully."; emit confirmForgotPasswordResult(LoginErrorNoError); }); @@ -513,26 +517,26 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co void AWSClient::deleteAccount() { if (!isLoggedIn()) { - qWarning() << "Not logged in at AWS. Can't delete account"; + qCWarning(dcCloud()) << "Not logged in at AWS. Can't delete account"; return; } if (tokensExpired()) { - qDebug() << "Cannot unpair device. Need to refresh our tokens"; + qCInfo(dcCloud()) << "Cannot unpair device. Need to refresh our tokens"; refreshAccessToken(); QueuedCall::enqueue(m_callQueue, QueuedCall("deleteAccount")); return; } - qDebug() << "Deleting account"; + qCInfo(dcCloud()) << "Deleting account"; QUrl url(QString("https://%1/users/profiles/%2").arg(m_configs.value(m_usedConfig).apiEndpoint).arg(m_userId)); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader("x-api-idToken", m_idToken); - qDebug() << "DELETE" << url.toString(); - qDebug() << "HEADERS:"; + qCDebug(dcCloud()) << "DELETE" << url.toString(); + qCDebug(dcCloud()) << "HEADERS:"; foreach (const QByteArray &hdr, request.rawHeaderList()) { - qDebug() << hdr << ":" << request.rawHeader(hdr); + qCDebug(dcCloud()) << hdr << ":" << request.rawHeader(hdr); } QNetworkReply *reply = m_nam->deleteResource(request); @@ -540,36 +544,36 @@ void AWSClient::deleteAccount() reply->deleteLater(); QByteArray data = reply->readAll(); if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error deleting cloud user account:" << reply->error() << reply->errorString() << qUtf8Printable(data); + qCWarning(dcCloud()) << "Error deleting cloud user account:" << reply->error() << reply->errorString() << qUtf8Printable(data); emit deleteAccountResult(LoginErrorUnknownError); return; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data); + qCWarning(dcCloud()) << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data); emit deleteAccountResult(LoginErrorUnknownError); return; } emit deleteAccountResult(LoginErrorNoError); logout(); - qDebug() << "Account deleted" << data; + qCInfo(dcCloud()) << "Account deleted" << data; }); } void AWSClient::unpairDevice(const QString &coreId) { if (!isLoggedIn()) { - qWarning() << "Not logged in at AWS. Can't unpair device"; + qCWarning(dcCloud()) << "Not logged in at AWS. Can't unpair device"; return; } if (tokensExpired()) { - qDebug() << "Cannot unpair device. Need to refresh our tokens"; + qCInfo(dcCloud()) << "Cannot unpair device. Need to refresh our tokens"; refreshAccessToken(); QueuedCall::enqueue(m_callQueue, QueuedCall("unpairDevice", coreId)); return; } - qDebug() << "unpairing device"; + qCInfo(dcCloud()) << "Unpairing device" << coreId << "from user" << m_username; QUrl url(QString("https://%1/users/devices/%2").arg(m_configs.value(m_usedConfig).apiEndpoint).arg(coreId)); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -591,7 +595,7 @@ void AWSClient::unpairDevice(const QString &coreId) qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data); return; } - qDebug() << "Device unpaired" << data; + qCInfo(dcCloud()) << "Device" << coreId << "unpaired from user" << m_username; m_devices->remove(coreId); }); @@ -628,19 +632,19 @@ void AWSClient::getId() connect(reply, &QNetworkReply::finished, this, [this, reply]() { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error calling GetId" << reply->error() << reply->errorString(); + qCWarning(dcCloud()) << "Error calling GetId" << reply->error() << reply->errorString(); return; } QByteArray data = reply->readAll(); QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qWarning() << "Error parsing json reply for GetId" << error.errorString(); + qCWarning(dcCloud()) << "Error parsing json reply for GetId" << error.errorString(); return; } m_identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray(); -// qDebug() << "Received cognito identity id" << m_identityId;// << qUtf8Printable(data); + qCDebug(dcCloud()) << "Received cognito identity id" << m_identityId;// << qUtf8Printable(data); getCredentialsForIdentity(m_identityId); }); @@ -649,11 +653,11 @@ void AWSClient::getId() void AWSClient::registerPushNotificationEndpoint(const QString ®istrationId, const QString &deviceDisplayName, const QString mobileDeviceId, const QString &mobileDeviceManufacturer, const QString &mobileDeviceModel) { if (!isLoggedIn()) { - qWarning() << "Not logged in at AWS. Can't register push endpoint"; + qCWarning(dcCloud()) << "Not logged in at AWS. Can't register push endpoint"; return; } if (tokensExpired()) { - qDebug() << "Cannot register push endpoint. Need to refresh our tokens"; + qCInfo(dcCloud()) << "Cannot register push endpoint. Need to refresh our tokens"; QueuedCall::enqueue(m_callQueue, QueuedCall("registerPushNotificationEndpoint", registrationId, deviceDisplayName, mobileDeviceId, mobileDeviceManufacturer, mobileDeviceModel)); refreshAccessToken(); return; @@ -681,7 +685,7 @@ void AWSClient::registerPushNotificationEndpoint(const QString ®istrationId, QJsonDocument jsonDoc = QJsonDocument::fromVariant(payload); - qDebug() << "Registering push notification endpoint" << qUtf8Printable(QJsonDocument::fromVariant(payload).toJson()); + qCInfo(dcCloud()) << "Registering push notification endpoint" << qUtf8Printable(QJsonDocument::fromVariant(payload).toJson()); // qDebug() << "POST" << url.toString(); // qDebug() << "HEADERS:"; // foreach (const QByteArray &hdr, request.rawHeaderList()) { @@ -694,10 +698,10 @@ void AWSClient::registerPushNotificationEndpoint(const QString ®istrationId, reply->deleteLater(); QByteArray data = reply->readAll(); if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error registering push notification endpoint:" << reply->error() << reply->errorString() << qUtf8Printable(data); + qCWarning(dcCloud()) << "Error registering push notification endpoint:" << reply->error() << reply->errorString() << qUtf8Printable(data); return; } - qDebug() << "Push notification endpoint registered" << data; + qCInfo(dcCloud()) << "Push notification endpoint registered" << data; }); } @@ -722,27 +726,27 @@ void AWSClient::fetchCertificate(const QString &uuid, std::functionget(request); - qDebug() << "Fetching certificate for vendor:" << m_configs.value(m_usedConfig).certificateVendorId << "device id:" << fixedUuid; + qCInfo(dcCloud()) << "Fetching certificate for vendor:" << m_configs.value(m_usedConfig).certificateVendorId << "device id:" << fixedUuid; connect(reply, &QNetworkReply::finished, this, [this, reply, callback]() { reply->deleteLater(); QByteArray data = reply->readAll(); if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error deploying certificate" << data; + qCWarning(dcCloud()) << "Error deploying certificate" << data; return; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qWarning() << "Error parsing certificate json" << data; + qCWarning(dcCloud()) << "Error parsing certificate json" << data; return; } QByteArray certificate = jsonDoc.toVariant().toMap().value("certificatePem").toByteArray(); QByteArray publicKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PublicKey").toByteArray(); QByteArray privateKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PrivateKey").toByteArray(); - qDebug() << "Certificate received" << certificate; - qDebug() << "Public key" << publicKey; - qDebug() << "Private key" << privateKey; + qCDebug(dcCloud()) << "Certificate received" << certificate; + qCDebug(dcCloud()) << "Public key" << publicKey; + qCDebug(dcCloud()) << "Private key" << privateKey; callback(rootCA, certificate, publicKey, privateKey, m_configs.value(m_usedConfig).mqttEndpoint); }); @@ -769,10 +773,10 @@ void AWSClient::setConfig(const QString &config) if (m_usedConfig != fixedConfig) { if (!m_configs.contains(fixedConfig)) { - qWarning() << "AWS: Config" << fixedConfig << "not known. Not switching AWS config"; + qCWarning(dcCloud()) << "AWS: Config" << fixedConfig << "not known. Not switching AWS config"; return; } - qDebug() << "Setting AWS configuration to" << fixedConfig; + qCInfo(dcCloud()) << "Setting AWS configuration to" << fixedConfig; m_usedConfig = fixedConfig; emit configChanged(); } @@ -814,7 +818,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId) connect(reply, &QNetworkReply::finished, this, [this, reply]() { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error calling GetCredentialsForIdentity" << reply->errorString(); + qCWarning(dcCloud()) << "Error calling GetCredentialsForIdentity" << reply->errorString(); emit loginResult(LoginErrorUnknownError); return; } @@ -822,7 +826,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId) QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qWarning() << "Error parsing JSON reply from GetCredentialsForIdentity" << error.errorString(); + qCWarning(dcCloud()) << "Error parsing JSON reply from GetCredentialsForIdentity" << error.errorString(); emit loginResult(LoginErrorUnknownError); return; } @@ -832,14 +836,11 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId) m_secretKey = credentialsMap.value("SecretKey").toByteArray(); m_sessionToken = credentialsMap.value("SessionToken").toByteArray(); m_sessionTokenExpiry = QDateTime::fromSecsSinceEpoch(credentialsMap.value("Expiration").toLongLong()); - qDebug() << "AWS Credentials for Identity received.";// << data; - qDebug() << "AWS login successful. Userid:" << m_userId; + qCInfo(dcCloud()) << "AWS login successful. Userid:" << m_userId; QSettings settings; - qDebug() << "settings has:" << settings.childGroups(); - bool newLogin = !settings.childGroups().contains("cloud"); settings.remove("cloud"); @@ -860,7 +861,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId) emit loginResult(LoginErrorNoError); if (newLogin) { - qDebug() << "new login!"; + qCInfo(dcCloud()) << "New login!"; emit isLoggedInChanged(); } @@ -890,11 +891,11 @@ bool AWSClient::tokensExpired() const bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject* sender, std::function callback) { if (!isLoggedIn()) { - qWarning() << "Cannot post to MQTT. Not logged in to AWS"; + qCWarning(dcCloud()) << "Cannot post to MQTT. Not logged in to AWS"; return false; } if (tokensExpired()) { - qDebug() << "Cannot post to MQTT. Need to refresh the tokens first"; + qCDebug(dcCloud()) << "Cannot post to MQTT. Need to refresh the tokens first"; refreshAccessToken(); QueuedCall::enqueue(m_callQueue, QueuedCall("postToMQTT", coreId, nonce, sender, callback)); return true; // So far it looks we're doing ok... let's return true @@ -929,7 +930,7 @@ bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject* // Workaround MQTT broker url weirdness as described above request.setUrl("https://" + m_configs.value(m_usedConfig).mqttEndpoint + path1); - qDebug() << "Posting to MQTT:" << request.url().toString(); + qCDebug(dcCloud()) << "Posting to MQTT:" << request.url().toString(); // qDebug() << "HEADERS:"; // foreach (const QByteArray &headerName, request.rawHeaderList()) { // qDebug() << headerName << ":" << request.rawHeader(headerName); @@ -948,23 +949,23 @@ bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject* QByteArray data = reply->readAll(); // qDebug() << "MQTT post reply" << data; if (senderWatcher.isNull()) { - qDebug() << "Request object disappeared. Discarding MQTT reply..."; + qCDebug(dcCloud()) << "Request object disappeared. Discarding MQTT reply..."; return; } if (reply->error() != QNetworkReply::NoError) { - qWarning() << "MQTT Network reply error" << reply->error() << reply->errorString(); + qCWarning(dcCloud()) << "MQTT Network reply error" << reply->error() << reply->errorString(); callback(false); return; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qWarning() << "Failed to parse MQTT reply" << error.error << error.errorString() << data; + qCWarning(dcCloud()) << "Failed to parse MQTT reply" << error.error << error.errorString() << data; callback(false); return; } if (jsonDoc.toVariant().toMap().value("message").toString() != "OK") { - qWarning() << "Something went wrong posting to MQTT:" << jsonDoc.toVariant().toMap().value("message").toString(); + qCWarning(dcCloud()) << "Something went wrong posting to MQTT:" << jsonDoc.toVariant().toMap().value("message").toString(); callback(false); return; } @@ -976,18 +977,22 @@ bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject* void AWSClient::fetchDevices() { + if (m_usedConfig.isEmpty()) { + qCWarning(dcCloud()) << "Cloud environment not set. Not fetching cloud devices"; + return; + } if (!isLoggedIn()) { - qWarning() << "Not logged in at AWS. Can't fetch paired devices"; + qCWarning(dcCloud()) << "Not logged in at AWS. Can't fetch paired devices"; return; } if (tokensExpired()) { - qDebug() << "Cannot fetch devices. Need to refresh our tokens"; + qCDebug(dcCloud()) << "Cannot fetch devices. Need to refresh our tokens"; refreshAccessToken(); QueuedCall::enqueue(m_callQueue, QueuedCall("fetchDevices")); return; } -// qDebug() << "Fetching cloud devices"; QUrl url(QString("https://%1/users/devices").arg(m_configs.value(m_usedConfig).apiEndpoint)); + qCDebug(dcCloud()) << "Fetching cloud devices" << url.toString(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader("x-api-idToken", m_idToken); @@ -999,13 +1004,13 @@ void AWSClient::fetchDevices() m_devices->setBusy(false); QByteArray data = reply->readAll(); if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Error fetching cloud devices:" << reply->error() << reply->errorString() << qUtf8Printable(data); + qCWarning(dcCloud()) << "Error fetching cloud devices:" << reply->error() << reply->errorString() << qUtf8Printable(data); return; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data); + qCWarning(dcCloud()) << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data); return; } QList actualDevices; @@ -1013,7 +1018,7 @@ void AWSClient::fetchDevices() QString deviceId = entry.toMap().value("deviceId").toString(); QString name = entry.toMap().value("name").toString(); bool online = entry.toMap().value("online").toBool(); - qDebug() << "Have cloud device:" << deviceId << name << "online:" << online; + qCDebug(dcCloud()) << "Have cloud device:" << deviceId << name << "online:" << online; AWSDevice *d = m_devices->getDevice(deviceId); if (!d) { @@ -1044,7 +1049,7 @@ void AWSClient::fetchDevices() void AWSClient::refreshAccessToken() { if (!isLoggedIn()) { - qDebug() << "Cannot refresh tokens. Not logged in to AWS"; + qCWarning(dcCloud()) << "Cannot refresh tokens. Not logged in to AWS"; return; } @@ -1111,7 +1116,7 @@ void AWSClient::refreshAccessToken() // settings.setValue("idToken", m_idToken); // settings.setValue("refreshToken", m_refreshToken); - qDebug() << "AWS login successful" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); + qCInfo(dcCloud()) << "AWS login successful" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); emit isLoggedInChanged(); }); @@ -1217,7 +1222,7 @@ void AWSDevices::remove(const QString &uuid) } } if (idx == -1) { - qWarning() << "Cannot remove AWS with id" << uuid << "as there is no such device"; + qCWarning(dcCloud()) << "Cannot remove AWS with id" << uuid << "as there is no such device"; return; } beginRemoveRows(QModelIndex(), idx, idx); diff --git a/libnymea-app/connection/discovery/upnpdiscovery.cpp b/libnymea-app/connection/discovery/upnpdiscovery.cpp index 1de798ca..eba09ba6 100644 --- a/libnymea-app/connection/discovery/upnpdiscovery.cpp +++ b/libnymea-app/connection/discovery/upnpdiscovery.cpp @@ -35,6 +35,10 @@ #include #include +#include "logging.h" + +NYMEA_LOGGING_CATEGORY(dcUPnP, "UPnP") + UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) : QObject(parent), m_nymeaHosts(nymeaHosts) @@ -61,10 +65,10 @@ UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) : } if (port == 65535 || socket->state() != QUdpSocket::BoundState) { socket->deleteLater(); - qWarning() << "UPnP: Discovery could not bind to interface" << netAddressEntry.ip(); + qCWarning(dcUPnP()) << "Discovery could not bind to interface" << netAddressEntry.ip(); continue; } - qDebug() << "UPnP: Discovering on" << netAddressEntry.ip() << port; + qCInfo(dcUPnP()) << "Discovering on" << netAddressEntry.ip() << port; m_sockets.append(socket); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError))); connect(socket, &QUdpSocket::readyRead, this, &UpnpDiscovery::readData); @@ -86,11 +90,11 @@ bool UpnpDiscovery::available() const void UpnpDiscovery::discover() { if (!available()) { - qWarning() << "UPnP: UPnP not available. Discovery not started."; + qCWarning(dcUPnP()) << "UPnP not available. Discovery not started."; return; } - qDebug() << "UPNP: Discovery started..."; + qCInfo(dcUPnP()) << "Discovery started..."; m_repeatTimer.start(); m_foundDevices.clear(); writeDiscoveryPacket(); @@ -99,7 +103,7 @@ void UpnpDiscovery::discover() void UpnpDiscovery::stopDiscovery() { - qDebug() << "UPNP: Discovery stopped."; + qCInfo(dcUPnP()) << "Discovery stopped."; m_repeatTimer.stop(); emit discoveringChanged(); } @@ -112,13 +116,13 @@ void UpnpDiscovery::writeDiscoveryPacket() "MX:2\r\n" "ST: ssdp:all\r\n\r\n"); -// qDebug() << "sending discovery package"; + qCDebug(dcUPnP()) << "sending discovery package"; foreach (QUdpSocket* socket, m_sockets) { qint64 ret = socket->writeDatagram(ssdpSearchMessage, QHostAddress("239.255.255.250"), 1900); if (ret != ssdpSearchMessage.length()) { - qWarning() << "UPnP: Error sending SSDP query on socket" << socket->localAddress(); + // Leaving a debug message because this happens on many platforms and spams logs. + qCDebug(dcUPnP()) << "Error sending SSDP query on socket" << socket->localAddress(); } - } } @@ -140,7 +144,7 @@ void UpnpDiscovery::readData() data.resize(socket->pendingDatagramSize()); socket->readDatagram(data.data(), data.size(), &hostAddress, &port); -// qDebug() << "Received UPnP datagram:" << data; + qCDebug(dcUPnP()) << "Received UPnP datagram:" << data; // if the data contains the HTTP OK header... if (data.contains("HTTP/1.1 200 OK")) { @@ -168,7 +172,7 @@ void UpnpDiscovery::readData() if (!m_foundDevices.contains(location) && isNymea) { m_foundDevices.append(location); - // qDebug() << "Getting server data from:" << location; + qCDebug(dcUPnP()) << "Getting server data from:" << location; QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(location)); connect(reply, &QNetworkReply::sslErrors, [reply](const QList &errors){ reply->ignoreSslErrors(errors); @@ -186,7 +190,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (reply->error() != QNetworkReply::NoError || status != 200) { - qWarning() << "UPnP: Error fetching discovery data:" << status << reply->error() << reply->errorString(); + qCWarning(dcUPnP()) << "UPnP: Error fetching discovery data:" << status << reply->error() << reply->errorString(); return; } @@ -244,13 +248,13 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) } } - qDebug() << "UPnP: Discovered device" << name << discoveredAddress << version << connections /*<< data*/; + qCDebug(dcUPnP()) << "Discovered device" << name << discoveredAddress << version << connections /*<< data*/; NymeaHost* device = m_nymeaHosts->find(uuid); if (!device) { device = new NymeaHost(m_nymeaHosts); device->setUuid(uuid); - qDebug() << "UPnP: Adding new host to model"; + qCInfo(dcUPnP()) << "Adding new host to model" << device->name() << device->uuid(); m_nymeaHosts->addHost(device); } device->setName(name); @@ -258,7 +262,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) foreach (const QUrl &url, connections) { Connection *connection = device->connections()->find(url); if (!connection) { - qDebug() << "UPnP: Adding new connection to host:" << device->name() << url; + qCInfo(dcUPnP()) << "Adding new connection to host:" << device->name() << url; bool sslEnabled = url.scheme() == "nymeas" || url.scheme() == "wss"; QString displayName = QString("%1:%2").arg(url.host()).arg(url.port()); Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan; @@ -266,7 +270,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) connection->setOnline(true); device->connections()->addConnection(connection); } else { - qDebug() << "UPnP: Setting connection online:" << device->name() << url.toString(); + qCInfo(dcUPnP()) << "Setting connection online:" << device->name() << url.toString(); connection->setOnline(true); } } diff --git a/libnymea-app/connection/discovery/zeroconfdiscovery.cpp b/libnymea-app/connection/discovery/zeroconfdiscovery.cpp index d2816d11..9f0ca5c7 100644 --- a/libnymea-app/connection/discovery/zeroconfdiscovery.cpp +++ b/libnymea-app/connection/discovery/zeroconfdiscovery.cpp @@ -33,6 +33,9 @@ #include #include "../nymeahost.h" +#include "logging.h" + +NYMEA_LOGGING_CATEGORY(dcZeroConf, "ZeroConf") ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) : QObject(parent), @@ -50,9 +53,9 @@ ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) : if (m_zeroconfJsonRPC->isValid()) { m_zeroconfJsonRPC->startBrowser("_jsonrpc._tcp", QAbstractSocket::IPv4Protocol); - qDebug() << "ZeroConf: Created service browser for _jsonrpc._tcp:" << m_zeroconfJsonRPC->browserExists(); + qCInfo(dcZeroConf()) << "Created service browser for _jsonrpc._tcp:" << m_zeroconfJsonRPC->browserExists(); } else { - qWarning() << "Zeroconf init failed for _jsonprc._tcp"; + qCWarning(dcZeroConf()) << "Failed to initialize service broeser for _jsonprc._tcp"; } m_zeroconfWebSocket = new QZeroConf(this); @@ -61,19 +64,19 @@ ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) : connect(m_zeroconfWebSocket, &QZeroConf::serviceRemoved, this, &ZeroconfDiscovery::serviceEntryRemoved); if (m_zeroconfWebSocket->isValid()) { m_zeroconfWebSocket->startBrowser("_ws._tcp", QAbstractSocket::IPv4Protocol); - qDebug() << "ZeroConf: Created service browser for _ws._tcp:" << m_zeroconfWebSocket->browserExists(); + qCInfo(dcZeroConf()) << "Created service browser for _ws._tcp:" << m_zeroconfWebSocket->browserExists(); } else { - qWarning() << "Zeroconf init failed for _ws._tcp"; + qCWarning(dcZeroConf()) << "Failed to initialize service browserr for _ws._tcp"; } #else - qDebug() << "Zeroconf support not compiled in. Zeroconf will not be available."; + qCInfo(dcZeroConf()) << "Zeroconf support not compiled in. Zeroconf will not be available."; #endif } ZeroconfDiscovery::~ZeroconfDiscovery() { - qDebug() << "ZeroConf: Shutting down service browsers"; + qCInfo(dcZeroConf()) << "Shutting down service browsers"; } bool ZeroconfDiscovery::available() const @@ -95,21 +98,21 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) { if (!entry->name().startsWith("nymea")) { // Skip non-nymea services altogether - qDebug() << "Skipping Avahi entry:" << entry << entry->ip() << entry->txt() << entry->type(); + qCDebug(dcZeroConf()) << "Skipping service entry:" << entry << entry->ip() << entry->txt() << entry->type(); return; } if (entry->ip().isNull()) { // Skip entries that don't have an ip address at all for some reason - qDebug() << "Skipping Avahi entry:" << entry << entry->ip() << entry->txt() << entry->type(); + qCDebug(dcZeroConf()) << "Skipping service entry:" << entry << entry->ip() << entry->txt() << entry->type(); return; } if (entry->ip().toString().startsWith("fe80")) { // Skip link-local-IPv6 results - qDebug() << "Skipping Avahi entry:" << entry << entry->ip() << entry->txt() << entry->type(); + qCDebug(dcZeroConf()) << "Skipping service entry:" << entry << entry->ip() << entry->txt() << entry->type(); return; } -// qDebug() << "zeroconf service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt(); + qCDebug(dcZeroConf()) << "Service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt(); QString uuid; bool sslEnabled = false; @@ -130,14 +133,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) version = txtRecord.second; } } -// qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled; + qCDebug(dcZeroConf()) << "Service entry added" << serverName << uuid << sslEnabled; NymeaHost* host = m_nymeaHosts->find(uuid); if (!host) { host = new NymeaHost(m_nymeaHosts); host->setUuid(uuid); - qDebug() << "ZeroConf: Adding new host:" << serverName << uuid; + qCInfo(dcZeroConf()) << "Adding new host:" << serverName << uuid; m_nymeaHosts->addHost(host); } host->setName(serverName); @@ -153,14 +156,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) url.setPort(entry->port()); Connection *connection = host->connections()->find(url); if (!connection) { - qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString(); + qCInfo(dcZeroConf()) << "Adding new connection to host:" << host->name() << url.toString(); QString displayName = QString("%1:%2").arg(url.host()).arg(url.port()); Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan; connection = new Connection(url, bearerType, sslEnabled, displayName); connection->setOnline(true); host->connections()->addConnection(connection); } else { - qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString(); + qCInfo(dcZeroConf()) << "Setting connection online:" << host->name() << url.toString(); connection->setOnline(true); } } @@ -191,7 +194,7 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry) } } -// qDebug() << "Zeroconf: Service entry removed" << entry->name(); + qCDebug(dcZeroConf()) << "Service entry removed" << entry->name(); NymeaHost* host = m_nymeaHosts->find(uuid); if (!host) { @@ -213,7 +216,7 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry) return; } - qDebug() << "Zeroconf: Setting connection offline:" << host->name() << url.toString(); + qCInfo(dcZeroConf()) << "Setting connection offline:" << host->name() << url.toString(); connection->setOnline(false); } #endif diff --git a/libnymea-app/connection/nymeaconnection.cpp b/libnymea-app/connection/nymeaconnection.cpp index 4593c812..3a97ced9 100644 --- a/libnymea-app/connection/nymeaconnection.cpp +++ b/libnymea-app/connection/nymeaconnection.cpp @@ -44,6 +44,9 @@ #include #include "nymeatransportinterface.h" +#include "logging.h" + +NYMEA_LOGGING_CATEGORY(dcNymeaConnection, "NymeaConnection") NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent) { @@ -51,18 +54,18 @@ NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent) QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationAdded, this, [this](const QNetworkConfiguration &config){ Q_UNUSED(config) -// qDebug() << "Network configuration added:" << config.name() << config.bearerTypeName() << config.purpose(); + qCDebug(dcNymeaConnection()) << "Network configuration added:" << config.name() << config.bearerTypeName() << config.purpose(); updateActiveBearers(); }); QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationRemoved, this, [this](const QNetworkConfiguration &config){ Q_UNUSED(config) -// qDebug() << "Network configuration removed:" << config.name() << config.bearerTypeName() << config.purpose(); + qCDebug(dcNymeaConnection()) << "Network configuration removed:" << config.name() << config.bearerTypeName() << config.purpose(); updateActiveBearers(); }); QGuiApplication *app = static_cast(QGuiApplication::instance()); - QObject::connect(app, &QGuiApplication::applicationStateChanged, this, [this](Qt::ApplicationState /*state*/) { -// qDebug() << "Application state changed to:" << state; + QObject::connect(app, &QGuiApplication::applicationStateChanged, this, [this](Qt::ApplicationState state) { + qCDebug(dcNymeaConnection()) << "Application state changed to:" << state; updateActiveBearers(); }); @@ -116,11 +119,11 @@ void NymeaConnection::setCurrentHost(NymeaHost *host) emit currentHostChanged(); if (!m_currentHost) { - qDebug() << "No current host."; + qCInfo(dcNymeaConnection()) << "Current host cleared. Not connecting."; return; } - qDebug() << "Nymea host is" << m_currentHost->name() << m_currentHost->uuid(); + qCInfo(dcNymeaConnection()) << "Nymea host set to:" << m_currentHost->name() << m_currentHost->uuid(); connect(m_currentHost, &NymeaHost::connectionChanged, this, &NymeaConnection::hostConnectionsUpdated); @@ -132,9 +135,6 @@ void NymeaConnection::setCurrentHost(NymeaHost *host) Connection *NymeaConnection::currentConnection() const { - qDebug() << "Current connection:" << m_currentHost << m_currentTransport << m_transportCandidates.count(); - qDebug() << m_transportCandidates.keys(); - qDebug() << m_transportCandidates.value(m_currentTransport); if (!m_currentHost || !m_currentTransport) { return nullptr; } @@ -143,11 +143,10 @@ Connection *NymeaConnection::currentConnection() const void NymeaConnection::sendData(const QByteArray &data) { -// qDebug() << "sending data:" << data; if (connected()) { m_currentTransport->sendData(data); } else { - qWarning() << "Connection: Not connected. Cannot send."; + qCWarning(dcNymeaConnection()) << "Connection: Not connected. Cannot send."; } } @@ -155,19 +154,19 @@ void NymeaConnection::onSslErrors(const QList &errors) { NymeaTransportInterface *transport = qobject_cast(sender()); - qDebug() << "SSL errors for url:" << transport->url(); + qCDebug(dcNymeaConnection()) << "SSL errors for url:" << transport->url(); QList ignoredErrors; foreach (const QSslError &error, errors) { qDebug() << error.errorString(); if (error.error() == QSslError::HostNameMismatch) { - qDebug() << "Ignoring host mismatch on certificate."; + qCInfo(dcNymeaConnection()) << "Ignoring host mismatch on certificate."; ignoredErrors.append(error); } else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) { - qDebug() << "Ignoring self signed certificate."; + qCInfo(dcNymeaConnection()) << "Ignoring self signed certificate."; ignoredErrors.append(error); } else { // Reject the connection on all other errors... - qDebug() << "SSL Error:" << error.errorString() << error.certificate(); + qCritical(dcNymeaConnection()) << "SSL Error:" << error.errorString() << error.certificate(); } } if (ignoredErrors == errors) { @@ -213,7 +212,7 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error) } if (transport == m_currentTransport) { - qDebug() << "Current transport failed:" << error; + qCCritical(dcNymeaConnection()) << "Current transport failed:" << error; // The current transport failed, forward the error m_connectionStatus = errorStatus; emit connectionStatusChanged(); @@ -226,9 +225,9 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error) m_transportCandidates.remove(transport); transport->deleteLater(); } - qDebug() << "A transport error happened for" << transport->url() << error << "(Still trying on" << m_transportCandidates.count() << "connections)"; + qCWarning(dcNymeaConnection()) << "A transport error happened for" << transport->url() << error << "(Still trying on" << m_transportCandidates.count() << "connections)"; foreach (Connection *c, m_transportCandidates) { - qDebug() << "Connection candidate:" << c->url(); + qCDebug(dcNymeaConnection()) << "Connection candidate:" << c->url(); } if (m_transportCandidates.isEmpty()) { m_connectionStatus = errorStatus; @@ -237,6 +236,7 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error) if (m_connectionStatus != ConnectionStatusSslUntrusted) { QTimer::singleShot(1000, this, [this](){ if (m_currentHost) { + qCInfo(dcNymeaConnection()) << "Reconnecting..."; connectInternal(m_currentHost); } }); @@ -250,20 +250,18 @@ void NymeaConnection::onConnected() NymeaTransportInterface* newTransport = qobject_cast(sender()); if (!m_currentTransport) { m_currentTransport = newTransport; - qDebug() << "NymeaConnection: Connected to" << m_currentHost->name() << "via" << m_currentTransport->url() << m_currentTransport->isEncrypted(); + qCInfo(dcNymeaConnection()) << "Connected to" << m_currentHost->name() << "via" << m_currentTransport->url() << m_currentTransport->isEncrypted(); emit currentConnectionChanged(); emit connectedChanged(true); return; } if (m_currentTransport != newTransport) { - qDebug() << "Alternative connection established:" << newTransport->url(); - // In theory, we could roam from one connection to another. // However, in practice it turns out there are too many issues for this to be reliable // So lets just tear down any alternative connection that comes up again. - qDebug() << "Dropping alternative connection again..."; + qCInfo(dcNymeaConnection()) << "Dropping successfully established alternative connection to" << newTransport->url() << "again..."; m_transportCandidates.remove(newTransport); newTransport->deleteLater(); @@ -288,23 +286,24 @@ void NymeaConnection::onDisconnected() { NymeaTransportInterface* t = qobject_cast(sender()); if (m_currentTransport != t) { - qWarning() << "NymeaConnection: An inactive transport for url" << t->url() << "disconnected... Cleaning up..."; + qCDebug(dcNymeaConnection()) << "An inactive transport for url" << t->url() << "disconnected... Cleaning up..."; if (m_transportCandidates.contains(t)) { m_transportCandidates.remove(t); } t->deleteLater(); + qCInfo(dcNymeaConnection()) << "Current transport:" << m_currentTransport << "Remaining connections:" << m_transportCandidates.count() << "Current host:" << m_currentHost; + if (!m_currentTransport && m_transportCandidates.isEmpty()) { - qDebug() << "Last connection dropped."; + qCWarning(dcNymeaConnection()) << "Last connection dropped."; QTimer::singleShot(1000, this, [this](){ if (m_currentHost && m_connectionStatus != ConnectionStatusSslUntrusted) { - qDebug() << "Trying to reconnect.."; + qCInfo(dcNymeaConnection()) << "Trying to reconnect.."; connectInternal(m_currentHost); } }); } - qDebug() << "Current transport:" << m_currentTransport << "Remaining connections:" << m_transportCandidates.count() << "Current host:" << m_currentHost; return; } m_transportCandidates.remove(m_currentTransport); @@ -313,7 +312,7 @@ void NymeaConnection::onDisconnected() foreach (NymeaTransportInterface *candidate, m_transportCandidates.keys()) { if (candidate->connectionState() == NymeaTransportInterface::ConnectionStateConnected) { - qDebug() << "Alternative connection is still up. Roaming to:" << candidate->url(); + qCInfo(dcNymeaConnection()) << "Alternative connection is still up. Roaming to:" << candidate->url(); m_currentTransport = candidate; break; } @@ -322,7 +321,7 @@ void NymeaConnection::onDisconnected() emit currentConnectionChanged(); if (!m_currentTransport) { - qDebug() << "NymeaConnection: disconnected."; + qCInfo(dcNymeaConnection()) << "Disconnected."; emit connectedChanged(false); } @@ -334,6 +333,7 @@ void NymeaConnection::onDisconnected() if (m_connectionStatus != ConnectionStatusSslUntrusted) { QTimer::singleShot(1000, this, [this](){ if (m_currentHost) { + qCInfo(dcNymeaConnection()) << "Trying to reconnect after disconnect..."; connectInternal(m_currentHost); } }); @@ -344,9 +344,9 @@ void NymeaConnection::updateActiveBearers() { NymeaConnection::BearerTypes availableBearerTypes; QList configs = m_networkConfigManager->allConfigurations(QNetworkConfiguration::Active); -// qDebug() << "Network configuations:" << configs.count(); + qCDebug(dcNymeaConnection()) << "Network configuations:" << configs.count(); foreach (const QNetworkConfiguration &config, configs) { -// qDebug() << "Active network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName(); + qCDebug(dcNymeaConnection()) << "Active network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName(); // NOTE: iOS doesn't correctly report bearer types. It'll be Unknown all the time. Let's hardcode it to WiFi for that... #if defined(Q_OS_IOS) @@ -357,27 +357,27 @@ void NymeaConnection::updateActiveBearers() } if (availableBearerTypes == NymeaConnection::BearerTypeNone) { // This is just debug info... On some platform bearer management seems a bit broken, so let's get some infos right away... - qDebug() << "No active bearer available. Inactive bearers are:"; + qCDebug(dcNymeaConnection()) << "No active bearer available. Inactive bearers are:"; QList configs = m_networkConfigManager->allConfigurations(); foreach (const QNetworkConfiguration &config, configs) { - qDebug() << "Inactive network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName(); + qCDebug(dcNymeaConnection()) << "Inactive network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName(); } - qWarning() << "Updating network manager"; + qCDebug(dcNymeaConnection()) << "Updating network manager"; m_networkConfigManager->updateConfigurations(); } if (m_availableBearerTypes != availableBearerTypes) { -// qDebug() << "Available Bearer Types changed:" << availableBearerTypes; + qCInfo(dcNymeaConnection()) << "Available Bearer Types changed to:" << availableBearerTypes; m_availableBearerTypes = availableBearerTypes; emit availableBearerTypesChanged(); } else { -// qDebug() << "Available Bearer Types:" << availableBearerTypes; + qCDebug(dcNymeaConnection()) << "Available Bearer Types:" << availableBearerTypes; } if (!m_currentHost) { // No host set... Nothing to do... - qDebug() << "No current host... Nothing to do..."; + qCInfo(dcNymeaConnection()) << "No current host... Nothing to do..."; return; } @@ -392,14 +392,17 @@ void NymeaConnection::updateActiveBearers() if (!m_currentTransport) { // There's a host but no connection. Try connecting now... - qDebug() << "There's a host but no connection. Trying to connect now..."; + qCInfo(dcNymeaConnection()) << "There's a host but no connection. Trying to connect now..."; connectInternal(m_currentHost); } } void NymeaConnection::hostConnectionsUpdated() { - connectInternal(m_currentHost); + if (!m_currentTransport) { + qCInfo(dcNymeaConnection()) << "Possible connections for host" << m_currentHost->name() << "updated."; + connectInternal(m_currentHost); + } } void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory) @@ -418,10 +421,10 @@ void NymeaConnection::connectToHost(NymeaHost *nymeaHost, Connection *connection m_preferredConnection = nullptr; if (connection) { if (nymeaHost->connections()->find(connection->url())) { - qDebug() << "Setting preferred connection to" << connection->url(); + qCInfo(dcNymeaConnection()) << "Setting preferred connection to" << connection->url(); m_preferredConnection = connection; } else { - qWarning() << "Connection" << connection << "is not a candidate for" << nymeaHost->name() << "Not setting preferred connection."; + qCWarning(dcNymeaConnection()) << "Connection" << connection << "is not a candidate for" << nymeaHost->name() << "Not setting preferred connection."; } } @@ -432,47 +435,48 @@ void NymeaConnection::connectInternal(NymeaHost *host) { if (m_preferredConnection) { if (isConnectionBearerAvailable(m_preferredConnection->bearerType())) { - qDebug() << "Preferred connection is set. Using" << m_preferredConnection->url(); + qCInfo(dcNymeaConnection()) << "Preferred connection is set. Using" << m_preferredConnection->url(); connectInternal(m_preferredConnection); return; } - qDebug() << "Preferred connection set but no bearer available for it."; + qCWarning(dcNymeaConnection()) << "Preferred connection set but no bearer available for it."; } Connection *loopbackConnection = host->connections()->bestMatch(Connection::BearerTypeLoopback); if (loopbackConnection) { - qDebug() << "Best candidate Loopback connection:" << loopbackConnection->url(); + qCDebug(dcNymeaConnection()) << "Best candidate Loopback connection:" << loopbackConnection->url(); connectInternal(loopbackConnection); } else if (m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeWiFi) || m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeEthernet)) { Connection* lanConnection = host->connections()->bestMatch(Connection::BearerTypeLan | Connection::BearerTypeWan); if (lanConnection) { - qDebug() << "Best candidate LAN/WAN connection:" << lanConnection->url(); + qCDebug(dcNymeaConnection()) << "Best candidate LAN/WAN connection:" << lanConnection->url(); connectInternal(lanConnection); } else { - qDebug() << "No available LAN/WAN connection to" << host->name(); + qCDebug(dcNymeaConnection()) << "No available LAN/WAN connection to" << host->name(); } } else if (m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeMobileData)) { Connection* wanConnection = host->connections()->bestMatch(Connection::BearerTypeWan); if (wanConnection) { - qDebug() << "Best candidate WAN connection:" << wanConnection->url(); + qCDebug(dcNymeaConnection()) << "Best candidate WAN connection:" << wanConnection->url(); connectInternal(wanConnection); } else { - qDebug() << "No available WAN connection to" << host->name(); + qCDebug(dcNymeaConnection()) << "No available WAN connection to" << host->name(); } } Connection* cloudConnection = host->connections()->bestMatch(Connection::BearerTypeCloud); if (cloudConnection) { - qDebug() << "Best candidate Cloud connection:" << cloudConnection->url(); + qCDebug(dcNymeaConnection()) << "Best candidate Cloud connection:" << cloudConnection->url(); connectInternal(cloudConnection); } else { - qDebug() << "No available Cloud connection to" << host->name(); + qCDebug(dcNymeaConnection()) << "No available Cloud connection to" << host->name(); } if (m_transportCandidates.isEmpty()) { + qCWarning(dcNymeaConnection()) << "No available bearers available for host:" << host->name() << host->uuid(); m_connectionStatus = ConnectionStatusNoBearerAvailable; } else { m_connectionStatus = ConnectionStatusConnecting; @@ -483,12 +487,12 @@ void NymeaConnection::connectInternal(NymeaHost *host) bool NymeaConnection::connectInternal(Connection *connection) { if (!m_transportFactories.contains(connection->url().scheme())) { - qWarning() << "Cannot connect to urls of scheme" << connection->url().scheme() << "Supported schemes are" << m_transportFactories.keys(); + qCCritical(dcNymeaConnection()) << "Cannot connect to urls of scheme" << connection->url().scheme() << "Supported schemes are" << m_transportFactories.keys(); return false; } if (m_transportCandidates.values().contains(connection)) { - qDebug() << "Already have a connection (or connection attempt) for" << connection->url(); + qCInfo(dcNymeaConnection()) << "Already have a connection (or connection attempt) for" << connection->url(); return false; } @@ -511,7 +515,7 @@ bool NymeaConnection::connectInternal(Connection *connection) // } m_transportCandidates.insert(newTransport, connection); - qDebug() << "Connecting to:" << connection->url() << newTransport << m_transportCandidates.value(newTransport); + qCInfo(dcNymeaConnection()) << "Connecting to:" << connection->url() << newTransport << m_transportCandidates.value(newTransport); return newTransport->connect(connection->url()); } @@ -519,6 +523,7 @@ NymeaConnection::BearerType NymeaConnection::qBearerTypeToNymeaBearerType(QNetwo { switch (type) { case QNetworkConfiguration::BearerUnknown: + // Unable to determine the connection type. Assume it's something we can establish any connection type on return BearerTypeAll; case QNetworkConfiguration::BearerEthernet: return BearerTypeEthernet; diff --git a/libnymea-app/libnymea-app-core.h b/libnymea-app/libnymea-app-core.h index 8de2204d..903e9a5f 100644 --- a/libnymea-app/libnymea-app-core.h +++ b/libnymea-app/libnymea-app-core.h @@ -126,6 +126,7 @@ #include "zigbee/zigbeeadaptersproxy.h" #include "zigbee/zigbeenetwork.h" #include "zigbee/zigbeenetworks.h" +#include "applogcontroller.h" #include @@ -158,6 +159,9 @@ void registerQmlTypes() { qmlRegisterType(uri, 1, 0, "Engine"); + qmlRegisterSingletonType("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider); + qmlRegisterType("Nymea", 1, 0, "LogMessages"); + qmlRegisterUncreatableType(uri, 1, 0, "ThingManager", "Can't create this in QML. Get it from the Engine."); qmlRegisterUncreatableType(uri, 1, 0, "JsonRpcClient", "Can't create this in QML. Get it from the Engine."); qmlRegisterUncreatableType(uri, 1, 0, "NymeaConnection", "Can't create this in QML. Get it from the Engine."); diff --git a/libnymea-app/libnymea-app.pri b/libnymea-app/libnymea-app.pri index 5b867965..5116640e 100644 --- a/libnymea-app/libnymea-app.pri +++ b/libnymea-app/libnymea-app.pri @@ -20,7 +20,9 @@ INCLUDEPATH += \ $$top_srcdir/QtZeroConf SOURCES += \ - $$PWD/wifisetup/btwifisetup.cpp \ + $${PWD}/logging.cpp \ + $${PWD}/applogcontroller.cpp \ + $${PWD}/wifisetup/btwifisetup.cpp \ $${PWD}/configuration/networkmanager.cpp \ $${PWD}/engine.cpp \ $${PWD}/models/barseriesadapter.cpp \ @@ -163,7 +165,9 @@ SOURCES += \ HEADERS += \ - $$PWD/wifisetup/btwifisetup.h \ + $${PWD}/logging.h \ + $${PWD}/applogcontroller.h \ + $${PWD}/wifisetup/btwifisetup.h \ $${PWD}/configuration/networkmanager.h \ $${PWD}/engine.h \ $${PWD}/models/barseriesadapter.h \ diff --git a/libnymea-app/logging.cpp b/libnymea-app/logging.cpp new file mode 100644 index 00000000..0d1b3e90 --- /dev/null +++ b/libnymea-app/logging.cpp @@ -0,0 +1,6 @@ +#include "logging.h" + +QStringList& nymeaLoggingCategories() { + static QStringList _nymeaLoggingCategories; + return _nymeaLoggingCategories; +} diff --git a/libnymea-app/logging.h b/libnymea-app/logging.h new file mode 100644 index 00000000..988f7565 --- /dev/null +++ b/libnymea-app/logging.h @@ -0,0 +1,20 @@ +#ifndef LOGGING_H +#define LOGGING_H + +#include + +QStringList& nymeaLoggingCategories(); + +#define NYMEA_LOGGING_CATEGORY(name, string) \ + class NymeaLoggingCategory##name: public QLoggingCategory { \ + public: \ + NymeaLoggingCategory##name(): QLoggingCategory(string) { nymeaLoggingCategories().append(string); } \ + }; \ + static NymeaLoggingCategory##name s_##name; \ + const QLoggingCategory &name() \ + { \ + return s_##name; \ + } \ + + +#endif // LOGGING_H diff --git a/libnymea-app/thingmanager.cpp b/libnymea-app/thingmanager.cpp index 4ceeced5..b73942a3 100644 --- a/libnymea-app/thingmanager.cpp +++ b/libnymea-app/thingmanager.cpp @@ -41,6 +41,9 @@ #include #include +#include "logging.h" +NYMEA_LOGGING_CATEGORY(dcThingManager, "ThingManager") + ThingManager::ThingManager(JsonRpcClient* jsonclient, QObject *parent) : JsonHandler(parent), m_vendors(new Vendors(this)), @@ -122,7 +125,9 @@ void ThingManager::notificationReceived(const QVariantMap &data) if (notification == "Integrations.StateChanged") { Thing *thing = m_things->getThing(data.value("params").toMap().value("thingId").toUuid()); if (!thing) { - qWarning() << "Thing state change notification received for an unknown thing"; + if (!m_fetchingData) { + qCWarning(dcThingManager()) << "Thing state change notification received for an unknown thing"; + } return; } QUuid stateTypeId = data.value("params").toMap().value("stateTypeId").toUuid(); @@ -188,7 +193,9 @@ void ThingManager::notificationReceived(const QVariantMap &data) Thing *thing = m_things->getThing(thingId); if (!thing) { - qWarning() << "received an event from a thing we don't know..." << thingId << qUtf8Printable(QJsonDocument::fromVariant(data).toJson()); + if (!m_fetchingData) { + qCWarning(dcThingManager()) << "received an event from a thing we don't know..." << thingId << qUtf8Printable(QJsonDocument::fromVariant(data).toJson()); + } return; } // qDebug() << "Event received" << thingId.toString() << eventTypeId.toString() << qUtf8Printable(QJsonDocument::fromVariant(event).toJson()); diff --git a/nymea-app/applogcontroller.cpp b/nymea-app/applogcontroller.cpp deleted file mode 100644 index c842815e..00000000 --- a/nymea-app/applogcontroller.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU General Public License as published by the Free Software -* Foundation, GNU version 3. This project 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 project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "applogcontroller.h" - -#include -#include -#include -#include -#include -#include -#include - -QtMessageHandler AppLogController::s_oldLogMessageHandler = nullptr; - - -QObject *AppLogController::appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine) -{ - Q_UNUSED(engine) - Q_UNUSED(scriptEngine) - return instance(); -} - -AppLogController *AppLogController::instance() -{ - static AppLogController* thiz = nullptr; - if (!thiz) { - thiz = new AppLogController(); - } - return thiz; -} - -AppLogController::AppLogController(QObject *parent) : QAbstractListModel(parent) -{ - - QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); - QString fileName = path + "/nymea-app.log"; - m_logFile.setFileName(fileName); - - QByteArray oldContent; - if (QFile::exists(fileName)) { - if (QFile::exists(fileName + ".old")) { - QFile::remove(fileName + ".old"); - } - QFile::rename(fileName, fileName + ".old"); - QFile oldFile(fileName + ".old"); - if (oldFile.open(QFile::ReadOnly)) { - oldFile.seek(qMax((long long)0, oldFile.size() - 1024 * 1024)); - oldContent = oldFile.readAll(); - oldFile.close(); - - m_buffer.append(QString(oldContent).split('\n')); - for (int i = 0; i < m_buffer.count(); i++) { - m_types.append(TypeInfo); - } - m_types.append(TypeWarning); - m_buffer.append("**** App restart ****"); - - oldContent.append("\n\n**** App restart ****\n\n"); - } - } - QDir dir(path); - if (!dir.exists()) { - if (!dir.mkpath(path)) { - qWarning() << "Cannot create cache location. Logging will not work."; - return; - } - } - - if (!m_logFile.open(QFile::ReadWrite | QFile::Truncate)) { - qWarning() << "Cannot open logfile for writing."; - return; - } - qDebug() << "App log opened at" << fileName; - m_logFile.write(oldContent); - - - if (enabled()) { - activate(); - } -} - -bool AppLogController::canWriteLogs() const -{ - return m_logFile.isOpen(); -} - -bool AppLogController::enabled() const -{ - QSettings settings; - return settings.value("AppLoggingEnabled", false).toBool(); -} - -void AppLogController::setEnabled(bool enabled) -{ - if (enabled == this->enabled()) { - return; - } - - if (enabled) { - if (!canWriteLogs()) { - qWarning() << "Cannot write log file. Not enabling logging."; - return; - } - activate(); - } else { - deactivate(); - } - QSettings settings; - settings.setValue("AppLoggingEnabled", enabled); - - emit enabledChanged(); - -} - -int AppLogController::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_buffer.count(); -} - -QVariant AppLogController::data(const QModelIndex &index, int role) const -{ - switch (role) { - case RoleText: - return m_buffer.at(index.row()); - case RoleType: - return m_types.at(index.row()); - } - return QVariant(); -} - -QHash AppLogController::roleNames() const -{ - QHash roles; - roles.insert(RoleText, "text"); - roles.insert(RoleType, "type"); - return roles; -} - -void AppLogController::toClipboard() -{ - m_logFile.seek(0); - QByteArray completeLog = m_logFile.readAll(); - QGuiApplication::clipboard()->setText(completeLog); -} - -QString AppLogController::logFile() const -{ - return m_logFile.fileName(); -} - -void AppLogController::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) -{ - s_oldLogMessageHandler(type, context, message); - instance()->append(message, type == QtWarningMsg ? TypeWarning : TypeInfo); -} - -void AppLogController::append(const QString &message, AppLogController::Type type) -{ - QMutexLocker locker(&m_mutex); - QString finalMessage = message + "\n"; - m_logFile.write(finalMessage.toUtf8()); - m_logFile.flush(); - - beginInsertRows(QModelIndex(), m_buffer.count(), m_buffer.count()); - m_buffer.append(message); - m_types.append(type); - endInsertRows(); - - int maxEntries = 1024; - if (m_buffer.size() > maxEntries) { - beginRemoveRows(QModelIndex(), 0, 0); - m_buffer.removeFirst(); - m_types.removeFirst(); - endRemoveRows(); - } -} - -void AppLogController::activate() -{ - qDebug() << "Activating log file writing to" << m_logFile.fileName(); - - s_oldLogMessageHandler = qInstallMessageHandler(&logMessageHandler); -} - -void AppLogController::deactivate() -{ - qInstallMessageHandler(s_oldLogMessageHandler); - s_oldLogMessageHandler = nullptr; - -} diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 6b271538..fd342d19 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -42,12 +42,15 @@ #include "stylecontroller.h" #include "pushnotifications.h" -#include "applogcontroller.h" #include "ruletemplates/messages.h" #include "nfchelper.h" #include "nfcthingactionwriter.h" #include "platformhelper.h" +#include "logging.h" + +NYMEA_LOGGING_CATEGORY(dcApplication, "Application") +NYMEA_LOGGING_CATEGORY(qml, "qml") int main(int argc, char *argv[]) { @@ -94,15 +97,15 @@ int main(int argc, char *argv[]) qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); application.installTranslator(&qtTranslator); - qDebug() << "nymea:app" << APP_VERSION << "running on" << QSysInfo::machineHostName() << QSysInfo::prettyProductName() << QSysInfo::productType() << QSysInfo::productVersion(); - qDebug() << "Locale info:" << QLocale() << QLocale().name() << QLocale().language() << QLocale().system(); + qCInfo(dcApplication()) << "nymea:app" << APP_VERSION << "running on" << QSysInfo::machineHostName() << QSysInfo::prettyProductName() << QSysInfo::productType() << QSysInfo::productVersion(); + qCInfo(dcApplication()) << "Locale info:" << QLocale() << QLocale().name() << QLocale().language() << QLocale().system(); QTranslator appTranslator; bool translationResult = appTranslator.load("nymea-app-" + QLocale().name(), ":/translations/"); if (translationResult) { - qDebug() << "Loaded translation for locale" << QLocale(); + qCDebug(dcApplication()) << "Loaded translation for locale" << QLocale(); } else { - qWarning() << "Failed to load translations for locale" << QLocale(); + qCInfo(dcApplication()) << "Failed to load translations for locale" << QLocale(); } application.installTranslator(&appTranslator); @@ -116,7 +119,7 @@ int main(int argc, char *argv[]) #else StyleController styleController(parser.value(defaultStyleOption)); if (parser.isSet(styleOption)) { - qDebug() << "Setting style to" << parser.value(styleOption); + qCInfo(dcApplication()) << "Setting style to" << parser.value(styleOption); styleController.lockToStyle(parser.value(styleOption)); } #endif @@ -128,7 +131,7 @@ int main(int argc, char *argv[]) QFontDatabase::addApplicationFont(fi.absoluteFilePath()); } foreach (const QFileInfo &fi, QDir(":/styles/" + styleController.currentStyle() + "/fonts/").entryInfoList()) { - qDebug() << "Adding style font:" << fi.absoluteFilePath(); + qCDebug(dcApplication()) << "Adding style font:" << fi.absoluteFilePath(); QFontDatabase::addApplicationFont(fi.absoluteFilePath()); } @@ -141,7 +144,6 @@ int main(int argc, char *argv[]) qmlRegisterType("Nymea", 1, 0, "NfcThingActionWriter"); qmlRegisterSingletonType("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider); - qmlRegisterSingletonType("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider); qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" ); engine->rootContext()->setContextProperty("appVersion", APP_VERSION); diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 1fdd2c71..b5306293 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -24,7 +24,6 @@ HEADERS += \ pushnotifications.h \ platformhelper.h \ platformintegration/generic/platformhelpergeneric.h \ - applogcontroller.h \ ruletemplates/messages.h SOURCES += main.cpp \ @@ -37,7 +36,6 @@ SOURCES += main.cpp \ pushnotifications.cpp \ platformhelper.cpp \ platformintegration/generic/platformhelpergeneric.cpp \ - applogcontroller.cpp RESOURCES += resources.qrc \ ruletemplates.qrc \ diff --git a/nymea-app/platformhelper.cpp b/nymea-app/platformhelper.cpp index ea76316b..584f4e2f 100644 --- a/nymea-app/platformhelper.cpp +++ b/nymea-app/platformhelper.cpp @@ -32,6 +32,8 @@ #include #include +#include +#include #if defined Q_OS_ANDROID #include @@ -44,6 +46,9 @@ #include "platformintegration/generic/platformhelpergeneric.h" #endif +#include "logging.h" +NYMEA_LOGGING_CATEGORY(dcPlatformIntegration, "PlatformIntegration") + PlatformHelper* PlatformHelper::s_instance = nullptr; PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent) @@ -183,6 +188,11 @@ QString PlatformHelper::fromClipBoard() return QApplication::clipboard()->text(); } +void PlatformHelper::shareFile(const QString &fileName) +{ + QDesktopServices::openUrl(QUrl(fileName)); +} + QObject *PlatformHelper::platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) diff --git a/nymea-app/platformhelper.h b/nymea-app/platformhelper.h index 72e18ccd..90bfc9bf 100644 --- a/nymea-app/platformhelper.h +++ b/nymea-app/platformhelper.h @@ -91,6 +91,8 @@ public: Q_INVOKABLE virtual void toClipBoard(const QString &text); Q_INVOKABLE virtual QString fromClipBoard(); + Q_INVOKABLE virtual void shareFile(const QString &fileName); + static QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine); signals: void permissionsRequestFinished(); diff --git a/nymea-app/platformintegration/android/java/io/guh/nymeaapp/NymeaAppActivity.java b/nymea-app/platformintegration/android/java/io/guh/nymeaapp/NymeaAppActivity.java index f63a0463..92e24bf2 100644 --- a/nymea-app/platformintegration/android/java/io/guh/nymeaapp/NymeaAppActivity.java +++ b/nymea-app/platformintegration/android/java/io/guh/nymeaapp/NymeaAppActivity.java @@ -1,4 +1,7 @@ package io.guh.nymeaapp; + +import java.io.File; + import android.util.Log; import android.content.Intent; import android.content.Context; @@ -7,6 +10,8 @@ import android.os.Build; import android.telephony.TelephonyManager; import android.provider.Settings.Secure; import android.os.Vibrator; +import android.net.Uri; +import android.support.v4.content.FileProvider; public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActivity { @@ -48,4 +53,17 @@ public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActiv Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(duration); } + + public void shareFile(String fileName) { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.setType("text/plain"); + Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".fileprovider", new File(fileName)); + sendIntent.putExtra(Intent.EXTRA_STREAM, uri); + if (sendIntent.resolveActivity(getPackageManager()) != null) { + startActivity(sendIntent); + } else { + Log.d(TAG, "Intent not resolved"); + } + } } diff --git a/nymea-app/platformintegration/android/platformhelperandroid.cpp b/nymea-app/platformintegration/android/platformhelperandroid.cpp index 697f1cf4..e058da4c 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.cpp +++ b/nymea-app/platformintegration/android/platformhelperandroid.cpp @@ -175,7 +175,14 @@ void PlatformHelperAndroid::setBottomPanelColor(const QColor &color) PlatformHelper::setBottomPanelColor(color); if (QtAndroid::androidSdkVersion() < 21) - return; + return; +} + +void PlatformHelperAndroid::shareFile(const QString &fileName) +{ + QtAndroid::androidActivity().callMethod("shareFile", "(Ljava/lang/String;)V", + QAndroidJniObject::fromString(fileName).object() + ); } void PlatformHelperAndroid::permissionRequestFinished(const QtAndroid::PermissionResultMap &result) diff --git a/nymea-app/platformintegration/android/platformhelperandroid.h b/nymea-app/platformintegration/android/platformhelperandroid.h index fed19a80..62980518 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.h +++ b/nymea-app/platformintegration/android/platformhelperandroid.h @@ -62,6 +62,8 @@ public: void setTopPanelTheme(Theme theme); void setBottomPanelColor(const QColor &color) override; + void shareFile(const QString &fileName) override; + private: static void permissionRequestFinished(const QtAndroid::PermissionResultMap &); }; diff --git a/nymea-app/platformintegration/generic/platformhelpergeneric.cpp b/nymea-app/platformintegration/generic/platformhelpergeneric.cpp index 4bc2d306..93e7e717 100644 --- a/nymea-app/platformintegration/generic/platformhelpergeneric.cpp +++ b/nymea-app/platformintegration/generic/platformhelpergeneric.cpp @@ -30,6 +30,10 @@ #include "platformhelpergeneric.h" +#include "logging.h" + +Q_DECLARE_LOGGING_CATEGORY(dcPlatformIntergration) + PlatformHelperGeneric::PlatformHelperGeneric(QObject *parent) : PlatformHelper(parent) { m_piHelper = new ScreenHelper(this); diff --git a/nymea-app/platformintegration/generic/screenhelper.cpp b/nymea-app/platformintegration/generic/screenhelper.cpp index 383160c8..2e99c0de 100644 --- a/nymea-app/platformintegration/generic/screenhelper.cpp +++ b/nymea-app/platformintegration/generic/screenhelper.cpp @@ -36,32 +36,35 @@ #include #include #include +#include + +Q_DECLARE_LOGGING_CATEGORY(dcPlatformIntegration) ScreenHelper::ScreenHelper(QObject *parent) : QObject(parent) { // Try generic backlight QDir backlightDir("/sys/class/backlight"); foreach (const QFileInfo &fi, backlightDir.entryInfoList({"*_backlight"}, QDir::Dirs)) { - qDebug() << "Checking backlight directory:" << fi.absoluteFilePath(); + qCDebug(dcPlatformIntegration()) << "Checking backlight directory:" << fi.absoluteFilePath(); m_powerFile.setFileName(fi.absoluteFilePath() + "/bl_power"); m_brightnessFile.setFileName(fi.absoluteFilePath() + "/brightness"); if (!m_powerFile.open(QFile::ReadWrite | QFile::Text)) { - qWarning() << "Cannot open" << m_powerFile.fileName() << "for writing"; + qCDebug(dcPlatformIntegration()) << "Cannot open" << m_powerFile.fileName() << "for writing"; continue; } if (!m_brightnessFile.open(QFile::ReadWrite | QFile::Text)) { - qWarning() << "Cannot open" << m_brightnessFile.fileName() << "for writing"; + qCDebug(dcPlatformIntegration()) << "Cannot open" << m_brightnessFile.fileName() << "for writing"; continue; } QFile maxBrightnessFile(fi.absoluteFilePath() + "/max_brightness"); if (!maxBrightnessFile.open(QFile::ReadOnly)) { - qWarning() << "Cannot open" << m_brightnessFile.fileName() << "for reading"; + qCDebug(dcPlatformIntegration()) << "Cannot open" << m_brightnessFile.fileName() << "for reading"; continue; } bool ok; m_maxBrightness = maxBrightnessFile.readLine().trimmed().toInt(&ok); if (!ok) { - qWarning() << "Error reading max brightness value from" << maxBrightnessFile.fileName(); + qCDebug(dcPlatformIntegration()) << "Error reading max brightness value from" << maxBrightnessFile.fileName(); m_maxBrightness = -1; continue; } @@ -70,15 +73,15 @@ ScreenHelper::ScreenHelper(QObject *parent) : QObject(parent) } if (!m_powerFile.isOpen() || !m_brightnessFile.isOpen()) { - qWarning() << "No backlight support on this platform"; + qCInfo(dcPlatformIntegration()) << "No backlight support on this platform"; return; } - qDebug() << "Backlight control enabled on" << m_powerFile.fileName(); + qCInfo(dcPlatformIntegration()) << "Backlight control enabled on" << m_powerFile.fileName(); bool ok; int currentBrightness = m_brightnessFile.readLine().trimmed().toInt(&ok); m_currentBrightness = currentBrightness * 100 / m_maxBrightness; - qDebug().nospace() << "Brigness: Absolute: " << currentBrightness << "/" << m_maxBrightness << " Percentage:" << m_currentBrightness; + qCInfo(dcPlatformIntegration()).nospace() << "Brigness: Absolute: " << currentBrightness << "/" << m_maxBrightness << " Percentage:" << m_currentBrightness; screenOn(); @@ -190,20 +193,20 @@ bool ScreenHelper::eventFilter(QObject *watched, QEvent *event) void ScreenHelper::screenOn() { - qDebug() << "Turning screen on"; + qCInfo(dcPlatformIntegration()) << "Turning screen on"; int ret = m_powerFile.write("0\n"); m_powerFile.flush(); if (ret < 0) { - qWarning() << "Failed to power on screen"; + qCWarning(dcPlatformIntegration()) << "Failed to power on screen"; } } void ScreenHelper::screenOff() { - qDebug() << "Turning screen off"; + qCInfo(dcPlatformIntegration()) << "Turning screen off"; int ret = m_powerFile.write("1\n"); m_powerFile.flush(); if (ret < 0) { - qWarning() << "Failed to power off screen"; + qCWarning(dcPlatformIntegration()) << "Failed to power off screen"; } } diff --git a/nymea-app/platformintegration/ios/platformhelperios.h b/nymea-app/platformintegration/ios/platformhelperios.h index 05f30dab..ec431e47 100644 --- a/nymea-app/platformintegration/ios/platformhelperios.h +++ b/nymea-app/platformintegration/ios/platformhelperios.h @@ -57,6 +57,8 @@ public: void setTopPanelColor(const QColor &color) override; void setBottomPanelColor(const QColor &color) override; + void shareFile(const QString &fileName) override; + private: // defined in platformhelperios.mm QString readKeyChainEntry(const QString &service, const QString &key); diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 57b89867..a95d5b6c 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -232,5 +232,6 @@ ui/customviews/ThermostatController.qml ui/devicepages/ThermostatDevicePage.qml ui/components/BigThingTile.qml + ui/appsettings/LoggingCategories.qml diff --git a/nymea-app/ui/MainMenu.qml b/nymea-app/ui/MainMenu.qml index fba2901a..31d3f2fd 100644 --- a/nymea-app/ui/MainMenu.qml +++ b/nymea-app/ui/MainMenu.qml @@ -53,7 +53,7 @@ Drawer { } Label { Layout.fillWidth: true - text: root.currentEngine.jsonRpcClient.currentConnection.url + text: root.currentEngine.jsonRpcClient.currentConnection ? root.currentEngine.jsonRpcClient.currentConnection.url : "" font.pixelSize: app.extraSmallFont enabled: false } diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 1e01358b..e5be9b25 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -110,6 +110,12 @@ ApplicationWindow { value: settings.units === "metric" ? Types.UnitSystemMetric : Types.UnitSystemImperial } + Binding { + target: AWSClient + property: "config" + value: "cloudEnvironment" in app ? app.cloudEnvironment : settings.cloudEnvironment + } + property alias mainMenu: m MainMenu { id: m diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 1c6c2522..c2deef0b 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -157,12 +157,6 @@ Item { property int connectionTabIndex: index // onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.jsonRpcClient.url - Binding { - target: AWSClient - property: "config" - value: "cloudEnvironment" in app ? app.cloudEnvironment : settings.cloudEnvironment - } - Binding { target: nymeaDiscovery property: "discovering" @@ -469,6 +463,7 @@ Item { } RowLayout { + Layout.fillWidth: true visible: settings.showConnectionTabs spacing: 0 @@ -477,6 +472,7 @@ Item { Layout.fillWidth: true Material.elevation: 2 position: TabBar.Footer + property int tabWidth: Math.max(150, root.width / tabModel.count) Repeater { model: tabModel.count @@ -486,7 +482,7 @@ Item { property var engine: mainRepeater.itemAt(index)._engine property string serverName: engine.nymeaConfiguration.serverName Material.elevation: index - width: Math.max(150, tabbar.width / tabModel.count) + width: tabbar.tabWidth Rectangle { anchors.fill: parent diff --git a/nymea-app/ui/StyleBase.qml b/nymea-app/ui/StyleBase.qml index ef40cfb4..9bbb7d3d 100644 --- a/nymea-app/ui/StyleBase.qml +++ b/nymea-app/ui/StyleBase.qml @@ -17,10 +17,39 @@ Item { property color tileOverlayIconColor: iconColor property int tileRadius: 6 + readonly property int smallMargins: 8 + readonly property int margins: 16 + + readonly property int smallDelegateHeight: 50 + readonly property int delegateHeight: 60 // Note: Font files need to be provided in a "fonts" folder in the style property string fontFamily: "Ubuntu" + + // Fonts + readonly property font extraSmallFont: Qt.font({ + family: "Ubuntu", + pixelSize: 10 + }) + readonly property font smallFont: Qt.font({ + family: "Ubuntu", + pixelSize: 13 + }) + readonly property font font: Qt.font({ + family: "Ubuntu", + pixelSize: 16 + }) + readonly property font largeFont: Qt.font({ + family: "Ubuntu", + pixelSize: 20 + }) + readonly property font hugeFont: Qt.font({ + family: "Ubuntu", + pixelSize: 40 + }) + + // Icon/graph colors for various interfaces property var interfaceColors: { "temperaturesensor": "red", diff --git a/nymea-app/ui/appsettings/AppLogPage.qml b/nymea-app/ui/appsettings/AppLogPage.qml index 9a3e4aaa..023b3f60 100644 --- a/nymea-app/ui/appsettings/AppLogPage.qml +++ b/nymea-app/ui/appsettings/AppLogPage.qml @@ -36,12 +36,15 @@ import "../components" Page { header: NymeaHeader { - text: qsTr("App log") + text: qsTr("Application logs") backButtonVisible: true onBackPressed: pageStack.pop() HeaderButton { - imageSource: "../images/edit-copy.svg" - onClicked: AppLogController.toClipboard() + imageSource: "../images/state-out.svg" + onClicked: { + var exportedFile = AppLogController.exportLogs() + PlatformHelper.shareFile(exportedFile) + } } } @@ -51,14 +54,29 @@ Page { ScrollBar.vertical: ScrollBar {} - model: AppLogController - delegate: Text { + model: LogMessages { + + } + + delegate: Label { width: listView.width maximumLineCount: 2 wrapMode: Text.WrapAtWordBoundaryOrAnywhere text: model.text - color: model.type === AppLogController.TypeWarning ? "red" : Style.foregroundColor - font.pixelSize: app.smallFont + color: { + switch (model.level) { + case AppLogController.LogLevelCritical: + return "red"; + case AppLogController.LogLevelWarning: + return "orange"; + case AppLogController.LogLevelInfo: + return Style.foregroundColor; + case AppLogController.LogLevelDebug: + return Qt.tint(Style.foregroundColor, Qt.rgba(Style.backgroundColor.r, Style.backgroundColor.g, Style.backgroundColor.b, .4)) + + } + } + font: Style.smallFont } } } diff --git a/nymea-app/ui/appsettings/DeveloperOptionsPage.qml b/nymea-app/ui/appsettings/DeveloperOptionsPage.qml index 6e56c392..fe8571e2 100644 --- a/nymea-app/ui/appsettings/DeveloperOptionsPage.qml +++ b/nymea-app/ui/appsettings/DeveloperOptionsPage.qml @@ -42,21 +42,25 @@ SettingsPageBase { text: qsTr("Logging") } - CheckDelegate { - text: qsTr("Enable app logging") - enabled: AppLogController.canWriteLogs + SwitchDelegate { + text: qsTr("Application logs enabled") checked: AppLogController.enabled - onCheckedChanged: AppLogController.enabled = checked; + onCheckedChanged: AppLogController.enabled = checked Layout.fillWidth: true } - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true - text: qsTr("View log") + text: qsTr("View live log") onClicked: pageStack.push(Qt.resolvedUrl("../appsettings/AppLogPage.qml")) - enabled: AppLogController.enabled + visible: AppLogController.enabled } + NymeaItemDelegate { + Layout.fillWidth: true + text: qsTr("Configure logging categories") + onClicked: pageStack.push(Qt.resolvedUrl("../appsettings/LoggingCategories.qml")) + } SettingsPageSectionHeader { text: qsTr("Advanced options") diff --git a/nymea-app/ui/appsettings/LoggingCategories.qml b/nymea-app/ui/appsettings/LoggingCategories.qml new file mode 100644 index 00000000..549f218d --- /dev/null +++ b/nymea-app/ui/appsettings/LoggingCategories.qml @@ -0,0 +1,130 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project 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 project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Nymea 1.0 +import "../components" + +SettingsPageBase { + header: NymeaHeader { + text: qsTr("Logging categories") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + RowLayout { + Layout.margins: Style.margins + Item { + Layout.fillWidth: true + } + Label { + Layout.preferredWidth: Style.smallDelegateHeight + horizontalAlignment: Text.AlignHCenter + text: qsTr("Critical") + elide: Text.ElideRight + font: Style.smallFont + } + Label { + Layout.preferredWidth: Style.smallDelegateHeight + horizontalAlignment: Text.AlignHCenter + text: qsTr("Warning") + elide: Text.ElideRight + font: Style.smallFont + } + Label { + Layout.preferredWidth: Style.smallDelegateHeight + horizontalAlignment: Text.AlignHCenter + text: qsTr("Info") + elide: Text.ElideRight + font: Style.smallFont + } + Label { + Layout.preferredWidth: Style.smallDelegateHeight + horizontalAlignment: Text.AlignHCenter + text: qsTr("Debug") + elide: Text.ElideRight + font: Style.smallFont + } + } + ThinDivider {} + + Repeater { + model: AppLogController.loggingCategories + delegate: ItemDelegate { + Layout.fillWidth: true + Layout.preferredHeight: Style.smallDelegateHeight + contentItem: RowLayout { + height: parent.height + Label { + Layout.fillWidth: true + text: model.name + elide: Text.ElideRight + } + RadioButton { + Layout.fillHeight: true + Layout.preferredWidth: Style.smallDelegateHeight + checked: model.logLevel === AppLogController.LogLevelCritical + onClicked: AppLogController.setLogLevel(model.name, AppLogController.LogLevelCritical) + } + RadioButton { + Layout.fillHeight: true + Layout.preferredWidth: Style.smallDelegateHeight + checked: model.logLevel === AppLogController.LogLevelWarning + onClicked: AppLogController.setLogLevel(model.name, AppLogController.LogLevelWarning) + } + RadioButton { + Layout.fillHeight: true + Layout.preferredWidth: Style.smallDelegateHeight + checked: model.logLevel === AppLogController.LogLevelInfo + onClicked: AppLogController.setLogLevel(model.name, AppLogController.LogLevelInfo) + } + RadioButton { + Layout.fillHeight: true + Layout.preferredWidth: Style.smallDelegateHeight + checked: model.logLevel === AppLogController.LogLevelDebug + onClicked: AppLogController.setLogLevel(model.name, AppLogController.LogLevelDebug) + } + +// Slider { +// from: 0 +// to: 3 +// stepSize: 1 +// Layout.preferredWidth: 200 +// value: model.logLevel +// onMoved: { +// AppLogController.setLogLevel(model.name, value) +// } +// } + } + } + } +} diff --git a/nymea-app/ui/components/NymeaItemDelegate.qml b/nymea-app/ui/components/NymeaItemDelegate.qml index 555d55d3..fbac58d1 100644 --- a/nymea-app/ui/components/NymeaItemDelegate.qml +++ b/nymea-app/ui/components/NymeaItemDelegate.qml @@ -41,7 +41,6 @@ import Nymea 1.0 ItemDelegate { id: root - implicitWidth: 200 property string subText property bool progressive: true diff --git a/nymea-app/ui/delegates/InterfaceTile.qml b/nymea-app/ui/delegates/InterfaceTile.qml index ce09a64b..53e858e4 100644 --- a/nymea-app/ui/delegates/InterfaceTile.qml +++ b/nymea-app/ui/delegates/InterfaceTile.qml @@ -158,6 +158,10 @@ MainPageTile { rightMargin: app.margins / 2 } sourceComponent: { + if (!root.iface) { + return "" + } + switch (iface.name) { case "sensor": case "weather": diff --git a/nymea-app/ui/mainviews/EnergyView.qml b/nymea-app/ui/mainviews/EnergyView.qml index 29fcbe9e..013a5439 100644 --- a/nymea-app/ui/mainviews/EnergyView.qml +++ b/nymea-app/ui/mainviews/EnergyView.qml @@ -182,9 +182,9 @@ MainViewBase { ValueAxis { id: yAxis - readonly property XYSeriesAdapter adapter: consumersRepeater.itemAt(consumersRepeater.count - 1).adapter; - max: Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05)) - min: Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05)) + readonly property XYSeriesAdapter adapter: consumersRepeater.count > 0 ? consumersRepeater.itemAt(consumersRepeater.count - 1).adapter : null + max: adapter ? Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05)) : 1 + min: adapter ? Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05)) : 0 // This seems to crash occationally // onMinChanged: applyNiceNumbers(); // onMaxChanged: applyNiceNumbers(); diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml index a2a8d033..bea9988f 100644 --- a/nymea-app/ui/utils/NymeaUtils.qml +++ b/nymea-app/ui/utils/NymeaUtils.qml @@ -1,6 +1,5 @@ pragma Singleton import QtQuick 2.9 -import Nymea 1.0 Item { id: root diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index 5fea09f0..75bb562f 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -142,6 +142,16 @@ + + + + diff --git a/packaging/android/res/xml/file_paths.xml b/packaging/android/res/xml/file_paths.xml new file mode 100644 index 00000000..ed6c087b --- /dev/null +++ b/packaging/android/res/xml/file_paths.xml @@ -0,0 +1,3 @@ + + + diff --git a/packaging/ios/platformhelperios.mm b/packaging/ios/platformhelperios.mm index 1c851f48..22d6fb6a 100644 --- a/packaging/ios/platformhelperios.mm +++ b/packaging/ios/platformhelperios.mm @@ -128,3 +128,11 @@ void PlatformHelperIOS::setBottomPanelColorInternal(const QColor &color) app.windows.firstObject.backgroundColor = [UIColor colorWithRed:color.redF() green:color.greenF() blue:color.blueF() alpha:color.alphaF()]; } +void PlatformHelperIOS::shareFile(const QString &fileName) +{ + UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:@[[NSURL fileURLWithPath:fileName.toNSString()]] applicationActivities:nil]; + UIViewController *qtController = [[UIApplication sharedApplication].keyWindow rootViewController]; + [qtController presentViewController:activityController animated:YES completion:nil]; +} + +