Merge PR #553: Move AppLogController to libnymea-app
This commit is contained in:
commit
e2030de2f2
380
libnymea-app/applogcontroller.cpp
Normal file
380
libnymea-app/applogcontroller.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <QStandardPaths>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QMutexLocker>
|
||||||
|
|
||||||
|
#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<LogLevel>(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<LogLevel, QString> 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<QString, AppLogController::LogLevel> map = {
|
||||||
|
{"C", AppLogController::LogLevelCritical},
|
||||||
|
{"W", AppLogController::LogLevelWarning},
|
||||||
|
{"I", AppLogController::LogLevelInfo},
|
||||||
|
{"D", AppLogController::LogLevelDebug}
|
||||||
|
};
|
||||||
|
while (!f.atEnd()) {
|
||||||
|
QByteArray line = f.readLine().trimmed();
|
||||||
|
QList<QByteArray> 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<int, QByteArray> LogMessages::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> 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<int, QByteArray> LoggingCategories::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles.insert(RoleName, "name");
|
||||||
|
roles.insert(RoleLevel, "logLevel");
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
@ -37,60 +37,120 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
|
||||||
class AppLogController : public QAbstractListModel
|
class LogMessages;
|
||||||
|
class LoggingCategories;
|
||||||
|
|
||||||
|
class AppLogController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool canWriteLogs READ canWriteLogs CONSTANT)
|
|
||||||
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
|
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:
|
public:
|
||||||
enum Type {
|
// Note: QtMsgType is sorted in a way that we can't compare for >= etc
|
||||||
TypeInfo,
|
enum LogLevel {
|
||||||
TypeWarning
|
LogLevelCritical = 0,
|
||||||
|
LogLevelWarning = 1,
|
||||||
|
LogLevelInfo = 2,
|
||||||
|
LogLevelDebug = 3
|
||||||
};
|
};
|
||||||
Q_ENUM(Type)
|
Q_ENUM(LogLevel)
|
||||||
|
static LogLevel qtMsgTypeToLogLevel(QtMsgType msgType);
|
||||||
enum Roles {
|
static QtMsgType logLevelToQtMsgType(LogLevel logLevel);
|
||||||
RoleText,
|
|
||||||
RoleType
|
|
||||||
};
|
|
||||||
Q_ENUM(Roles)
|
|
||||||
|
|
||||||
static QObject* appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
|
static QObject* appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
|
||||||
static AppLogController* instance();
|
static AppLogController* instance();
|
||||||
|
|
||||||
bool canWriteLogs() const;
|
|
||||||
|
|
||||||
bool enabled() const;
|
bool enabled() const;
|
||||||
void setEnabled(bool enabled);
|
void setEnabled(bool enabled);
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent) const override;
|
LoggingCategories* loggingCategories() const;
|
||||||
QVariant data(const QModelIndex &parent, int role) const override;
|
LogLevel logLevel(const QString &category) const;
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
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();
|
Q_INVOKABLE QString exportLogs();
|
||||||
|
|
||||||
QString logFile() const;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void enabledChanged();
|
void enabledChanged();
|
||||||
|
void logToModelChanged();
|
||||||
|
|
||||||
|
void categoryChanged(const QString &category, LogLevel level);
|
||||||
|
void messageAdded(const QString &category, const QString &message, LogLevel level);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit AppLogController(QObject *parent = nullptr);
|
explicit AppLogController(QObject *parent = nullptr);
|
||||||
|
|
||||||
static QtMessageHandler s_oldLogMessageHandler;
|
static QtMessageHandler s_oldLogMessageHandler;
|
||||||
static void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
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 updateFilters();
|
||||||
void deactivate();
|
void openLogFile();
|
||||||
|
|
||||||
|
QHash<QString, LogLevel> m_logLevels;
|
||||||
|
|
||||||
QFile m_logFile;
|
|
||||||
QStringList m_buffer;
|
|
||||||
QList<Type> m_types;
|
|
||||||
QMutex m_mutex;
|
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<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
void append(const QString &category, const QString &message, AppLogController::LogLevel level);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<LogMessage> 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<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
AppLogController *m_controller = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APPLOGCONTROLLER_H
|
#endif // APPLOGCONTROLLER_H
|
||||||
@ -41,9 +41,12 @@
|
|||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
|
||||||
#include "sigv4utils.h"
|
#include "sigv4utils.h"
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
AWSClient* AWSClient::s_instance = nullptr;
|
AWSClient* AWSClient::s_instance = nullptr;
|
||||||
|
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcCloud, "Cloud")
|
||||||
|
|
||||||
// This is Symantec's root CA certificate and most platforms should
|
// This is Symantec's root CA certificate and most platforms should
|
||||||
// have this in their certificate storage already, but as we can't
|
// have this in their certificate storage already, but as we can't
|
||||||
// be certain about the core's setup, let's deploy it ourselves.
|
// 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)
|
void AWSClient::login(const QString &username, const QString &password)
|
||||||
{
|
{
|
||||||
if (m_usedConfig.isEmpty()) {
|
if (m_usedConfig.isEmpty()) {
|
||||||
qDebug() << "AWS config not set. Not logging in.";
|
qCInfo(dcCloud()) << "AWS config not set. Not logging in.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_loginInProgress) {
|
if (m_loginInProgress) {
|
||||||
qWarning() << "Login already pending...";
|
qCDebug(dcCloud()) << "Login already pending...";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_loginInProgress = true;
|
m_loginInProgress = true;
|
||||||
@ -229,7 +232,7 @@ void AWSClient::login(const QString &username, const QString &password)
|
|||||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
||||||
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
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);
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply, username, password]() {
|
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;
|
m_loginInProgress = false;
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
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);
|
emit loginResult(LoginErrorNetworkError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
|
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
|
||||||
qWarning() << "Looks like a wrong password.";
|
qCWarning(dcCloud()) << "Looks like a wrong password.";
|
||||||
m_username.clear();
|
m_username.clear();
|
||||||
m_password.clear();
|
m_password.clear();
|
||||||
emit loginResult(LoginErrorInvalidUserOrPass);
|
emit loginResult(LoginErrorInvalidUserOrPass);
|
||||||
return;
|
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);
|
emit loginResult(LoginErrorUnknownError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -256,7 +259,7 @@ void AWSClient::login(const QString &username, const QString &password)
|
|||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
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_username.clear();
|
||||||
m_password.clear();
|
m_password.clear();
|
||||||
emit loginResult(LoginErrorUnknownError);
|
emit loginResult(LoginErrorUnknownError);
|
||||||
@ -272,7 +275,7 @@ void AWSClient::login(const QString &username, const QString &password)
|
|||||||
// qDebug() << "AWS ID token" << m_idToken;
|
// qDebug() << "AWS ID token" << m_idToken;
|
||||||
QList<QByteArray> jwtParts = m_idToken.split('.');
|
QList<QByteArray> jwtParts = m_idToken.split('.');
|
||||||
if (jwtParts.count() != 3) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// qDebug() << "decoded header:" << QByteArray::fromBase64(jwtParts.at(0));
|
// 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);
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
||||||
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
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);
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
qDebug() << "AWS signup reply:" << data;
|
qCDebug(dcCloud()) << "AWS signup reply:" << data;
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
|
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
|
||||||
emit signupResult(LoginErrorInvalidUserOrPass);
|
emit signupResult(LoginErrorInvalidUserOrPass);
|
||||||
@ -345,7 +348,7 @@ void AWSClient::signup(const QString &username, const QString &password)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
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_username.clear();
|
||||||
m_password.clear();
|
m_password.clear();
|
||||||
emit signupResult(LoginErrorUnknownError);
|
emit signupResult(LoginErrorUnknownError);
|
||||||
@ -382,13 +385,13 @@ void AWSClient::confirmRegistration(const QString &code)
|
|||||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
||||||
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
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);
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
qDebug() << "AWS signup reply:" << data;
|
qCDebug(dcCloud()) << "AWS signup reply:" << data;
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
|
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
@ -405,7 +408,7 @@ void AWSClient::confirmRegistration(const QString &code)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
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);
|
emit confirmationResult(LoginErrorUnknownError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -440,7 +443,7 @@ void AWSClient::forgotPassword(const QString &username)
|
|||||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
||||||
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
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);
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
@ -456,12 +459,12 @@ void AWSClient::forgotPassword(const QString &username)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
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);
|
emit forgotPasswordResult(LoginErrorUnknownError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "AWS forgotPassword success:" << data;
|
qCInfo(dcCloud()) << "AWS forgotPassword success:" << data;
|
||||||
emit forgotPasswordResult(LoginErrorNoError);
|
emit forgotPasswordResult(LoginErrorNoError);
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -491,7 +494,8 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co
|
|||||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
||||||
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
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);
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
@ -499,12 +503,12 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co
|
|||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
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);
|
emit confirmForgotPasswordResult(LoginErrorUnknownError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "AWS ConfirmForgotPassword success:" << data;
|
qCInfo(dcCloud()) << "Password reset successfully.";
|
||||||
emit confirmForgotPasswordResult(LoginErrorNoError);
|
emit confirmForgotPasswordResult(LoginErrorNoError);
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -513,26 +517,26 @@ void AWSClient::confirmForgotPassword(const QString &username, const QString &co
|
|||||||
void AWSClient::deleteAccount()
|
void AWSClient::deleteAccount()
|
||||||
{
|
{
|
||||||
if (!isLoggedIn()) {
|
if (!isLoggedIn()) {
|
||||||
qWarning() << "Not logged in at AWS. Can't delete account";
|
qCWarning(dcCloud()) << "Not logged in at AWS. Can't delete account";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tokensExpired()) {
|
if (tokensExpired()) {
|
||||||
qDebug() << "Cannot unpair device. Need to refresh our tokens";
|
qCInfo(dcCloud()) << "Cannot unpair device. Need to refresh our tokens";
|
||||||
refreshAccessToken();
|
refreshAccessToken();
|
||||||
QueuedCall::enqueue(m_callQueue, QueuedCall("deleteAccount"));
|
QueuedCall::enqueue(m_callQueue, QueuedCall("deleteAccount"));
|
||||||
return;
|
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));
|
QUrl url(QString("https://%1/users/profiles/%2").arg(m_configs.value(m_usedConfig).apiEndpoint).arg(m_userId));
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("x-api-idToken", m_idToken);
|
request.setRawHeader("x-api-idToken", m_idToken);
|
||||||
|
|
||||||
qDebug() << "DELETE" << url.toString();
|
qCDebug(dcCloud()) << "DELETE" << url.toString();
|
||||||
qDebug() << "HEADERS:";
|
qCDebug(dcCloud()) << "HEADERS:";
|
||||||
foreach (const QByteArray &hdr, request.rawHeaderList()) {
|
foreach (const QByteArray &hdr, request.rawHeaderList()) {
|
||||||
qDebug() << hdr << ":" << request.rawHeader(hdr);
|
qCDebug(dcCloud()) << hdr << ":" << request.rawHeader(hdr);
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply *reply = m_nam->deleteResource(request);
|
QNetworkReply *reply = m_nam->deleteResource(request);
|
||||||
@ -540,36 +544,36 @@ void AWSClient::deleteAccount()
|
|||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
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);
|
emit deleteAccountResult(LoginErrorUnknownError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
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);
|
emit deleteAccountResult(LoginErrorUnknownError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit deleteAccountResult(LoginErrorNoError);
|
emit deleteAccountResult(LoginErrorNoError);
|
||||||
logout();
|
logout();
|
||||||
qDebug() << "Account deleted" << data;
|
qCInfo(dcCloud()) << "Account deleted" << data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AWSClient::unpairDevice(const QString &coreId)
|
void AWSClient::unpairDevice(const QString &coreId)
|
||||||
{
|
{
|
||||||
if (!isLoggedIn()) {
|
if (!isLoggedIn()) {
|
||||||
qWarning() << "Not logged in at AWS. Can't unpair device";
|
qCWarning(dcCloud()) << "Not logged in at AWS. Can't unpair device";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tokensExpired()) {
|
if (tokensExpired()) {
|
||||||
qDebug() << "Cannot unpair device. Need to refresh our tokens";
|
qCInfo(dcCloud()) << "Cannot unpair device. Need to refresh our tokens";
|
||||||
refreshAccessToken();
|
refreshAccessToken();
|
||||||
QueuedCall::enqueue(m_callQueue, QueuedCall("unpairDevice", coreId));
|
QueuedCall::enqueue(m_callQueue, QueuedCall("unpairDevice", coreId));
|
||||||
return;
|
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));
|
QUrl url(QString("https://%1/users/devices/%2").arg(m_configs.value(m_usedConfig).apiEndpoint).arg(coreId));
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
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);
|
qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
qDebug() << "Device unpaired" << data;
|
qCInfo(dcCloud()) << "Device" << coreId << "unpaired from user" << m_username;
|
||||||
m_devices->remove(coreId);
|
m_devices->remove(coreId);
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -628,19 +632,19 @@ void AWSClient::getId()
|
|||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
qWarning() << "Error calling GetId" << reply->error() << reply->errorString();
|
qCWarning(dcCloud()) << "Error calling GetId" << reply->error() << reply->errorString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
m_identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray();
|
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);
|
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)
|
void AWSClient::registerPushNotificationEndpoint(const QString ®istrationId, const QString &deviceDisplayName, const QString mobileDeviceId, const QString &mobileDeviceManufacturer, const QString &mobileDeviceModel)
|
||||||
{
|
{
|
||||||
if (!isLoggedIn()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (tokensExpired()) {
|
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));
|
QueuedCall::enqueue(m_callQueue, QueuedCall("registerPushNotificationEndpoint", registrationId, deviceDisplayName, mobileDeviceId, mobileDeviceManufacturer, mobileDeviceModel));
|
||||||
refreshAccessToken();
|
refreshAccessToken();
|
||||||
return;
|
return;
|
||||||
@ -681,7 +685,7 @@ void AWSClient::registerPushNotificationEndpoint(const QString ®istrationId,
|
|||||||
|
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(payload);
|
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() << "POST" << url.toString();
|
||||||
// qDebug() << "HEADERS:";
|
// qDebug() << "HEADERS:";
|
||||||
// foreach (const QByteArray &hdr, request.rawHeaderList()) {
|
// foreach (const QByteArray &hdr, request.rawHeaderList()) {
|
||||||
@ -694,10 +698,10 @@ void AWSClient::registerPushNotificationEndpoint(const QString ®istrationId,
|
|||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
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;
|
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::function<void(const Q
|
|||||||
request.setRawHeader("X-api-deviceId", fixedUuid.toUtf8());
|
request.setRawHeader("X-api-deviceId", fixedUuid.toUtf8());
|
||||||
request.setRawHeader("X-api-serialId", "69696969");
|
request.setRawHeader("X-api-serialId", "69696969");
|
||||||
QNetworkReply *reply = m_nam->get(request);
|
QNetworkReply *reply = m_nam->get(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]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply, callback]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
qWarning() << "Error deploying certificate" << data;
|
qCWarning(dcCloud()) << "Error deploying certificate" << data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Error parsing certificate json" << data;
|
qCWarning(dcCloud()) << "Error parsing certificate json" << data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray certificate = jsonDoc.toVariant().toMap().value("certificatePem").toByteArray();
|
QByteArray certificate = jsonDoc.toVariant().toMap().value("certificatePem").toByteArray();
|
||||||
QByteArray publicKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PublicKey").toByteArray();
|
QByteArray publicKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PublicKey").toByteArray();
|
||||||
QByteArray privateKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PrivateKey").toByteArray();
|
QByteArray privateKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PrivateKey").toByteArray();
|
||||||
qDebug() << "Certificate received" << certificate;
|
qCDebug(dcCloud()) << "Certificate received" << certificate;
|
||||||
qDebug() << "Public key" << publicKey;
|
qCDebug(dcCloud()) << "Public key" << publicKey;
|
||||||
qDebug() << "Private key" << privateKey;
|
qCDebug(dcCloud()) << "Private key" << privateKey;
|
||||||
callback(rootCA, certificate, publicKey, privateKey, m_configs.value(m_usedConfig).mqttEndpoint);
|
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_usedConfig != fixedConfig) {
|
||||||
if (!m_configs.contains(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;
|
return;
|
||||||
}
|
}
|
||||||
qDebug() << "Setting AWS configuration to" << fixedConfig;
|
qCInfo(dcCloud()) << "Setting AWS configuration to" << fixedConfig;
|
||||||
m_usedConfig = fixedConfig;
|
m_usedConfig = fixedConfig;
|
||||||
emit configChanged();
|
emit configChanged();
|
||||||
}
|
}
|
||||||
@ -814,7 +818,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
|
|||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
qWarning() << "Error calling GetCredentialsForIdentity" << reply->errorString();
|
qCWarning(dcCloud()) << "Error calling GetCredentialsForIdentity" << reply->errorString();
|
||||||
emit loginResult(LoginErrorUnknownError);
|
emit loginResult(LoginErrorUnknownError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -822,7 +826,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
|
|||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
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);
|
emit loginResult(LoginErrorUnknownError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -832,14 +836,11 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
|
|||||||
m_secretKey = credentialsMap.value("SecretKey").toByteArray();
|
m_secretKey = credentialsMap.value("SecretKey").toByteArray();
|
||||||
m_sessionToken = credentialsMap.value("SessionToken").toByteArray();
|
m_sessionToken = credentialsMap.value("SessionToken").toByteArray();
|
||||||
m_sessionTokenExpiry = QDateTime::fromSecsSinceEpoch(credentialsMap.value("Expiration").toLongLong());
|
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;
|
QSettings settings;
|
||||||
qDebug() << "settings has:" << settings.childGroups();
|
|
||||||
|
|
||||||
bool newLogin = !settings.childGroups().contains("cloud");
|
bool newLogin = !settings.childGroups().contains("cloud");
|
||||||
|
|
||||||
settings.remove("cloud");
|
settings.remove("cloud");
|
||||||
@ -860,7 +861,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
|
|||||||
emit loginResult(LoginErrorNoError);
|
emit loginResult(LoginErrorNoError);
|
||||||
|
|
||||||
if (newLogin) {
|
if (newLogin) {
|
||||||
qDebug() << "new login!";
|
qCInfo(dcCloud()) << "New login!";
|
||||||
emit isLoggedInChanged();
|
emit isLoggedInChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -890,11 +891,11 @@ bool AWSClient::tokensExpired() const
|
|||||||
bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject* sender, std::function<void (bool)> callback)
|
bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject* sender, std::function<void (bool)> callback)
|
||||||
{
|
{
|
||||||
if (!isLoggedIn()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (tokensExpired()) {
|
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();
|
refreshAccessToken();
|
||||||
QueuedCall::enqueue(m_callQueue, QueuedCall("postToMQTT", coreId, nonce, sender, callback));
|
QueuedCall::enqueue(m_callQueue, QueuedCall("postToMQTT", coreId, nonce, sender, callback));
|
||||||
return true; // So far it looks we're doing ok... let's return true
|
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
|
// Workaround MQTT broker url weirdness as described above
|
||||||
request.setUrl("https://" + m_configs.value(m_usedConfig).mqttEndpoint + path1);
|
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:";
|
// qDebug() << "HEADERS:";
|
||||||
// foreach (const QByteArray &headerName, request.rawHeaderList()) {
|
// foreach (const QByteArray &headerName, request.rawHeaderList()) {
|
||||||
// qDebug() << headerName << ":" << request.rawHeader(headerName);
|
// qDebug() << headerName << ":" << request.rawHeader(headerName);
|
||||||
@ -948,23 +949,23 @@ bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject*
|
|||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
// qDebug() << "MQTT post reply" << data;
|
// qDebug() << "MQTT post reply" << data;
|
||||||
if (senderWatcher.isNull()) {
|
if (senderWatcher.isNull()) {
|
||||||
qDebug() << "Request object disappeared. Discarding MQTT reply...";
|
qCDebug(dcCloud()) << "Request object disappeared. Discarding MQTT reply...";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
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);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
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);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (jsonDoc.toVariant().toMap().value("message").toString() != "OK") {
|
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);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -976,18 +977,22 @@ bool AWSClient::postToMQTT(const QString &coreId, const QString &nonce, QObject*
|
|||||||
|
|
||||||
void AWSClient::fetchDevices()
|
void AWSClient::fetchDevices()
|
||||||
{
|
{
|
||||||
|
if (m_usedConfig.isEmpty()) {
|
||||||
|
qCWarning(dcCloud()) << "Cloud environment not set. Not fetching cloud devices";
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!isLoggedIn()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (tokensExpired()) {
|
if (tokensExpired()) {
|
||||||
qDebug() << "Cannot fetch devices. Need to refresh our tokens";
|
qCDebug(dcCloud()) << "Cannot fetch devices. Need to refresh our tokens";
|
||||||
refreshAccessToken();
|
refreshAccessToken();
|
||||||
QueuedCall::enqueue(m_callQueue, QueuedCall("fetchDevices"));
|
QueuedCall::enqueue(m_callQueue, QueuedCall("fetchDevices"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// qDebug() << "Fetching cloud devices";
|
|
||||||
QUrl url(QString("https://%1/users/devices").arg(m_configs.value(m_usedConfig).apiEndpoint));
|
QUrl url(QString("https://%1/users/devices").arg(m_configs.value(m_usedConfig).apiEndpoint));
|
||||||
|
qCDebug(dcCloud()) << "Fetching cloud devices" << url.toString();
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("x-api-idToken", m_idToken);
|
request.setRawHeader("x-api-idToken", m_idToken);
|
||||||
@ -999,13 +1004,13 @@ void AWSClient::fetchDevices()
|
|||||||
m_devices->setBusy(false);
|
m_devices->setBusy(false);
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
QList<QUuid> actualDevices;
|
QList<QUuid> actualDevices;
|
||||||
@ -1013,7 +1018,7 @@ void AWSClient::fetchDevices()
|
|||||||
QString deviceId = entry.toMap().value("deviceId").toString();
|
QString deviceId = entry.toMap().value("deviceId").toString();
|
||||||
QString name = entry.toMap().value("name").toString();
|
QString name = entry.toMap().value("name").toString();
|
||||||
bool online = entry.toMap().value("online").toBool();
|
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);
|
AWSDevice *d = m_devices->getDevice(deviceId);
|
||||||
if (!d) {
|
if (!d) {
|
||||||
@ -1044,7 +1049,7 @@ void AWSClient::fetchDevices()
|
|||||||
void AWSClient::refreshAccessToken()
|
void AWSClient::refreshAccessToken()
|
||||||
{
|
{
|
||||||
if (!isLoggedIn()) {
|
if (!isLoggedIn()) {
|
||||||
qDebug() << "Cannot refresh tokens. Not logged in to AWS";
|
qCWarning(dcCloud()) << "Cannot refresh tokens. Not logged in to AWS";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1111,7 +1116,7 @@ void AWSClient::refreshAccessToken()
|
|||||||
// settings.setValue("idToken", m_idToken);
|
// settings.setValue("idToken", m_idToken);
|
||||||
// settings.setValue("refreshToken", m_refreshToken);
|
// 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();
|
emit isLoggedInChanged();
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -1217,7 +1222,7 @@ void AWSDevices::remove(const QString &uuid)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (idx == -1) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
beginRemoveRows(QModelIndex(), idx, idx);
|
beginRemoveRows(QModelIndex(), idx, idx);
|
||||||
|
|||||||
@ -35,6 +35,10 @@
|
|||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcUPnP, "UPnP")
|
||||||
|
|
||||||
UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
m_nymeaHosts(nymeaHosts)
|
m_nymeaHosts(nymeaHosts)
|
||||||
@ -61,10 +65,10 @@ UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
|||||||
}
|
}
|
||||||
if (port == 65535 || socket->state() != QUdpSocket::BoundState) {
|
if (port == 65535 || socket->state() != QUdpSocket::BoundState) {
|
||||||
socket->deleteLater();
|
socket->deleteLater();
|
||||||
qWarning() << "UPnP: Discovery could not bind to interface" << netAddressEntry.ip();
|
qCWarning(dcUPnP()) << "Discovery could not bind to interface" << netAddressEntry.ip();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
qDebug() << "UPnP: Discovering on" << netAddressEntry.ip() << port;
|
qCInfo(dcUPnP()) << "Discovering on" << netAddressEntry.ip() << port;
|
||||||
m_sockets.append(socket);
|
m_sockets.append(socket);
|
||||||
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
|
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
|
||||||
connect(socket, &QUdpSocket::readyRead, this, &UpnpDiscovery::readData);
|
connect(socket, &QUdpSocket::readyRead, this, &UpnpDiscovery::readData);
|
||||||
@ -86,11 +90,11 @@ bool UpnpDiscovery::available() const
|
|||||||
void UpnpDiscovery::discover()
|
void UpnpDiscovery::discover()
|
||||||
{
|
{
|
||||||
if (!available()) {
|
if (!available()) {
|
||||||
qWarning() << "UPnP: UPnP not available. Discovery not started.";
|
qCWarning(dcUPnP()) << "UPnP not available. Discovery not started.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "UPNP: Discovery started...";
|
qCInfo(dcUPnP()) << "Discovery started...";
|
||||||
m_repeatTimer.start();
|
m_repeatTimer.start();
|
||||||
m_foundDevices.clear();
|
m_foundDevices.clear();
|
||||||
writeDiscoveryPacket();
|
writeDiscoveryPacket();
|
||||||
@ -99,7 +103,7 @@ void UpnpDiscovery::discover()
|
|||||||
|
|
||||||
void UpnpDiscovery::stopDiscovery()
|
void UpnpDiscovery::stopDiscovery()
|
||||||
{
|
{
|
||||||
qDebug() << "UPNP: Discovery stopped.";
|
qCInfo(dcUPnP()) << "Discovery stopped.";
|
||||||
m_repeatTimer.stop();
|
m_repeatTimer.stop();
|
||||||
emit discoveringChanged();
|
emit discoveringChanged();
|
||||||
}
|
}
|
||||||
@ -112,13 +116,13 @@ void UpnpDiscovery::writeDiscoveryPacket()
|
|||||||
"MX:2\r\n"
|
"MX:2\r\n"
|
||||||
"ST: ssdp:all\r\n\r\n");
|
"ST: ssdp:all\r\n\r\n");
|
||||||
|
|
||||||
// qDebug() << "sending discovery package";
|
qCDebug(dcUPnP()) << "sending discovery package";
|
||||||
foreach (QUdpSocket* socket, m_sockets) {
|
foreach (QUdpSocket* socket, m_sockets) {
|
||||||
qint64 ret = socket->writeDatagram(ssdpSearchMessage, QHostAddress("239.255.255.250"), 1900);
|
qint64 ret = socket->writeDatagram(ssdpSearchMessage, QHostAddress("239.255.255.250"), 1900);
|
||||||
if (ret != ssdpSearchMessage.length()) {
|
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());
|
data.resize(socket->pendingDatagramSize());
|
||||||
socket->readDatagram(data.data(), data.size(), &hostAddress, &port);
|
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 the data contains the HTTP OK header...
|
||||||
if (data.contains("HTTP/1.1 200 OK")) {
|
if (data.contains("HTTP/1.1 200 OK")) {
|
||||||
@ -168,7 +172,7 @@ void UpnpDiscovery::readData()
|
|||||||
|
|
||||||
if (!m_foundDevices.contains(location) && isNymea) {
|
if (!m_foundDevices.contains(location) && isNymea) {
|
||||||
m_foundDevices.append(location);
|
m_foundDevices.append(location);
|
||||||
// qDebug() << "Getting server data from:" << location;
|
qCDebug(dcUPnP()) << "Getting server data from:" << location;
|
||||||
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(location));
|
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(location));
|
||||||
connect(reply, &QNetworkReply::sslErrors, [reply](const QList<QSslError> &errors){
|
connect(reply, &QNetworkReply::sslErrors, [reply](const QList<QSslError> &errors){
|
||||||
reply->ignoreSslErrors(errors);
|
reply->ignoreSslErrors(errors);
|
||||||
@ -186,7 +190,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
|
|||||||
|
|
||||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
if (reply->error() != QNetworkReply::NoError || status != 200) {
|
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;
|
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);
|
NymeaHost* device = m_nymeaHosts->find(uuid);
|
||||||
if (!device) {
|
if (!device) {
|
||||||
device = new NymeaHost(m_nymeaHosts);
|
device = new NymeaHost(m_nymeaHosts);
|
||||||
device->setUuid(uuid);
|
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);
|
m_nymeaHosts->addHost(device);
|
||||||
}
|
}
|
||||||
device->setName(name);
|
device->setName(name);
|
||||||
@ -258,7 +262,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
|
|||||||
foreach (const QUrl &url, connections) {
|
foreach (const QUrl &url, connections) {
|
||||||
Connection *connection = device->connections()->find(url);
|
Connection *connection = device->connections()->find(url);
|
||||||
if (!connection) {
|
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";
|
bool sslEnabled = url.scheme() == "nymeas" || url.scheme() == "wss";
|
||||||
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
|
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
|
||||||
Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan;
|
Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan;
|
||||||
@ -266,7 +270,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
|
|||||||
connection->setOnline(true);
|
connection->setOnline(true);
|
||||||
device->connections()->addConnection(connection);
|
device->connections()->addConnection(connection);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "UPnP: Setting connection online:" << device->name() << url.toString();
|
qCInfo(dcUPnP()) << "Setting connection online:" << device->name() << url.toString();
|
||||||
connection->setOnline(true);
|
connection->setOnline(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,9 @@
|
|||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
#include "../nymeahost.h"
|
#include "../nymeahost.h"
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcZeroConf, "ZeroConf")
|
||||||
|
|
||||||
ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
@ -50,9 +53,9 @@ ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
|||||||
|
|
||||||
if (m_zeroconfJsonRPC->isValid()) {
|
if (m_zeroconfJsonRPC->isValid()) {
|
||||||
m_zeroconfJsonRPC->startBrowser("_jsonrpc._tcp", QAbstractSocket::IPv4Protocol);
|
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 {
|
} else {
|
||||||
qWarning() << "Zeroconf init failed for _jsonprc._tcp";
|
qCWarning(dcZeroConf()) << "Failed to initialize service broeser for _jsonprc._tcp";
|
||||||
}
|
}
|
||||||
|
|
||||||
m_zeroconfWebSocket = new QZeroConf(this);
|
m_zeroconfWebSocket = new QZeroConf(this);
|
||||||
@ -61,19 +64,19 @@ ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
|||||||
connect(m_zeroconfWebSocket, &QZeroConf::serviceRemoved, this, &ZeroconfDiscovery::serviceEntryRemoved);
|
connect(m_zeroconfWebSocket, &QZeroConf::serviceRemoved, this, &ZeroconfDiscovery::serviceEntryRemoved);
|
||||||
if (m_zeroconfWebSocket->isValid()) {
|
if (m_zeroconfWebSocket->isValid()) {
|
||||||
m_zeroconfWebSocket->startBrowser("_ws._tcp", QAbstractSocket::IPv4Protocol);
|
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 {
|
} else {
|
||||||
qWarning() << "Zeroconf init failed for _ws._tcp";
|
qCWarning(dcZeroConf()) << "Failed to initialize service browserr for _ws._tcp";
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ZeroconfDiscovery::~ZeroconfDiscovery()
|
ZeroconfDiscovery::~ZeroconfDiscovery()
|
||||||
{
|
{
|
||||||
qDebug() << "ZeroConf: Shutting down service browsers";
|
qCInfo(dcZeroConf()) << "Shutting down service browsers";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ZeroconfDiscovery::available() const
|
bool ZeroconfDiscovery::available() const
|
||||||
@ -95,21 +98,21 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
|
|||||||
{
|
{
|
||||||
if (!entry->name().startsWith("nymea")) {
|
if (!entry->name().startsWith("nymea")) {
|
||||||
// Skip non-nymea services altogether
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
if (entry->ip().isNull()) {
|
if (entry->ip().isNull()) {
|
||||||
// Skip entries that don't have an ip address at all for some reason
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
if (entry->ip().toString().startsWith("fe80")) {
|
if (entry->ip().toString().startsWith("fe80")) {
|
||||||
// Skip link-local-IPv6 results
|
// 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;
|
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;
|
QString uuid;
|
||||||
bool sslEnabled = false;
|
bool sslEnabled = false;
|
||||||
@ -130,14 +133,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
|
|||||||
version = txtRecord.second;
|
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);
|
NymeaHost* host = m_nymeaHosts->find(uuid);
|
||||||
if (!host) {
|
if (!host) {
|
||||||
host = new NymeaHost(m_nymeaHosts);
|
host = new NymeaHost(m_nymeaHosts);
|
||||||
host->setUuid(uuid);
|
host->setUuid(uuid);
|
||||||
qDebug() << "ZeroConf: Adding new host:" << serverName << uuid;
|
qCInfo(dcZeroConf()) << "Adding new host:" << serverName << uuid;
|
||||||
m_nymeaHosts->addHost(host);
|
m_nymeaHosts->addHost(host);
|
||||||
}
|
}
|
||||||
host->setName(serverName);
|
host->setName(serverName);
|
||||||
@ -153,14 +156,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
|
|||||||
url.setPort(entry->port());
|
url.setPort(entry->port());
|
||||||
Connection *connection = host->connections()->find(url);
|
Connection *connection = host->connections()->find(url);
|
||||||
if (!connection) {
|
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());
|
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
|
||||||
Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan;
|
Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan;
|
||||||
connection = new Connection(url, bearerType, sslEnabled, displayName);
|
connection = new Connection(url, bearerType, sslEnabled, displayName);
|
||||||
connection->setOnline(true);
|
connection->setOnline(true);
|
||||||
host->connections()->addConnection(connection);
|
host->connections()->addConnection(connection);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString();
|
qCInfo(dcZeroConf()) << "Setting connection online:" << host->name() << url.toString();
|
||||||
connection->setOnline(true);
|
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);
|
NymeaHost* host = m_nymeaHosts->find(uuid);
|
||||||
if (!host) {
|
if (!host) {
|
||||||
@ -213,7 +216,7 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Zeroconf: Setting connection offline:" << host->name() << url.toString();
|
qCInfo(dcZeroConf()) << "Setting connection offline:" << host->name() << url.toString();
|
||||||
connection->setOnline(false);
|
connection->setOnline(false);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -44,6 +44,9 @@
|
|||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
|
|
||||||
#include "nymeatransportinterface.h"
|
#include "nymeatransportinterface.h"
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcNymeaConnection, "NymeaConnection")
|
||||||
|
|
||||||
NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent)
|
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){
|
QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationAdded, this, [this](const QNetworkConfiguration &config){
|
||||||
Q_UNUSED(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();
|
updateActiveBearers();
|
||||||
});
|
});
|
||||||
QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationRemoved, this, [this](const QNetworkConfiguration &config){
|
QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationRemoved, this, [this](const QNetworkConfiguration &config){
|
||||||
Q_UNUSED(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();
|
updateActiveBearers();
|
||||||
});
|
});
|
||||||
|
|
||||||
QGuiApplication *app = static_cast<QGuiApplication*>(QGuiApplication::instance());
|
QGuiApplication *app = static_cast<QGuiApplication*>(QGuiApplication::instance());
|
||||||
QObject::connect(app, &QGuiApplication::applicationStateChanged, this, [this](Qt::ApplicationState /*state*/) {
|
QObject::connect(app, &QGuiApplication::applicationStateChanged, this, [this](Qt::ApplicationState state) {
|
||||||
// qDebug() << "Application state changed to:" << state;
|
qCDebug(dcNymeaConnection()) << "Application state changed to:" << state;
|
||||||
updateActiveBearers();
|
updateActiveBearers();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -116,11 +119,11 @@ void NymeaConnection::setCurrentHost(NymeaHost *host)
|
|||||||
emit currentHostChanged();
|
emit currentHostChanged();
|
||||||
|
|
||||||
if (!m_currentHost) {
|
if (!m_currentHost) {
|
||||||
qDebug() << "No current host.";
|
qCInfo(dcNymeaConnection()) << "Current host cleared. Not connecting.";
|
||||||
return;
|
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);
|
connect(m_currentHost, &NymeaHost::connectionChanged, this, &NymeaConnection::hostConnectionsUpdated);
|
||||||
|
|
||||||
@ -132,9 +135,6 @@ void NymeaConnection::setCurrentHost(NymeaHost *host)
|
|||||||
|
|
||||||
Connection *NymeaConnection::currentConnection() const
|
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) {
|
if (!m_currentHost || !m_currentTransport) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -143,11 +143,10 @@ Connection *NymeaConnection::currentConnection() const
|
|||||||
|
|
||||||
void NymeaConnection::sendData(const QByteArray &data)
|
void NymeaConnection::sendData(const QByteArray &data)
|
||||||
{
|
{
|
||||||
// qDebug() << "sending data:" << data;
|
|
||||||
if (connected()) {
|
if (connected()) {
|
||||||
m_currentTransport->sendData(data);
|
m_currentTransport->sendData(data);
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Connection: Not connected. Cannot send.";
|
qCWarning(dcNymeaConnection()) << "Connection: Not connected. Cannot send.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,19 +154,19 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
|
|||||||
{
|
{
|
||||||
NymeaTransportInterface *transport = qobject_cast<NymeaTransportInterface*>(sender());
|
NymeaTransportInterface *transport = qobject_cast<NymeaTransportInterface*>(sender());
|
||||||
|
|
||||||
qDebug() << "SSL errors for url:" << transport->url();
|
qCDebug(dcNymeaConnection()) << "SSL errors for url:" << transport->url();
|
||||||
QList<QSslError> ignoredErrors;
|
QList<QSslError> ignoredErrors;
|
||||||
foreach (const QSslError &error, errors) {
|
foreach (const QSslError &error, errors) {
|
||||||
qDebug() << error.errorString();
|
qDebug() << error.errorString();
|
||||||
if (error.error() == QSslError::HostNameMismatch) {
|
if (error.error() == QSslError::HostNameMismatch) {
|
||||||
qDebug() << "Ignoring host mismatch on certificate.";
|
qCInfo(dcNymeaConnection()) << "Ignoring host mismatch on certificate.";
|
||||||
ignoredErrors.append(error);
|
ignoredErrors.append(error);
|
||||||
} else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) {
|
} else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) {
|
||||||
qDebug() << "Ignoring self signed certificate.";
|
qCInfo(dcNymeaConnection()) << "Ignoring self signed certificate.";
|
||||||
ignoredErrors.append(error);
|
ignoredErrors.append(error);
|
||||||
} else {
|
} else {
|
||||||
// Reject the connection on all other errors...
|
// 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) {
|
if (ignoredErrors == errors) {
|
||||||
@ -213,7 +212,7 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (transport == m_currentTransport) {
|
if (transport == m_currentTransport) {
|
||||||
qDebug() << "Current transport failed:" << error;
|
qCCritical(dcNymeaConnection()) << "Current transport failed:" << error;
|
||||||
// The current transport failed, forward the error
|
// The current transport failed, forward the error
|
||||||
m_connectionStatus = errorStatus;
|
m_connectionStatus = errorStatus;
|
||||||
emit connectionStatusChanged();
|
emit connectionStatusChanged();
|
||||||
@ -226,9 +225,9 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error)
|
|||||||
m_transportCandidates.remove(transport);
|
m_transportCandidates.remove(transport);
|
||||||
transport->deleteLater();
|
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) {
|
foreach (Connection *c, m_transportCandidates) {
|
||||||
qDebug() << "Connection candidate:" << c->url();
|
qCDebug(dcNymeaConnection()) << "Connection candidate:" << c->url();
|
||||||
}
|
}
|
||||||
if (m_transportCandidates.isEmpty()) {
|
if (m_transportCandidates.isEmpty()) {
|
||||||
m_connectionStatus = errorStatus;
|
m_connectionStatus = errorStatus;
|
||||||
@ -237,6 +236,7 @@ void NymeaConnection::onError(QAbstractSocket::SocketError error)
|
|||||||
if (m_connectionStatus != ConnectionStatusSslUntrusted) {
|
if (m_connectionStatus != ConnectionStatusSslUntrusted) {
|
||||||
QTimer::singleShot(1000, this, [this](){
|
QTimer::singleShot(1000, this, [this](){
|
||||||
if (m_currentHost) {
|
if (m_currentHost) {
|
||||||
|
qCInfo(dcNymeaConnection()) << "Reconnecting...";
|
||||||
connectInternal(m_currentHost);
|
connectInternal(m_currentHost);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -250,20 +250,18 @@ void NymeaConnection::onConnected()
|
|||||||
NymeaTransportInterface* newTransport = qobject_cast<NymeaTransportInterface*>(sender());
|
NymeaTransportInterface* newTransport = qobject_cast<NymeaTransportInterface*>(sender());
|
||||||
if (!m_currentTransport) {
|
if (!m_currentTransport) {
|
||||||
m_currentTransport = newTransport;
|
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 currentConnectionChanged();
|
||||||
emit connectedChanged(true);
|
emit connectedChanged(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_currentTransport != newTransport) {
|
if (m_currentTransport != newTransport) {
|
||||||
qDebug() << "Alternative connection established:" << newTransport->url();
|
|
||||||
|
|
||||||
// In theory, we could roam from one connection to another.
|
// 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
|
// 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.
|
// 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);
|
m_transportCandidates.remove(newTransport);
|
||||||
newTransport->deleteLater();
|
newTransport->deleteLater();
|
||||||
|
|
||||||
@ -288,23 +286,24 @@ void NymeaConnection::onDisconnected()
|
|||||||
{
|
{
|
||||||
NymeaTransportInterface* t = qobject_cast<NymeaTransportInterface*>(sender());
|
NymeaTransportInterface* t = qobject_cast<NymeaTransportInterface*>(sender());
|
||||||
if (m_currentTransport != t) {
|
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)) {
|
if (m_transportCandidates.contains(t)) {
|
||||||
m_transportCandidates.remove(t);
|
m_transportCandidates.remove(t);
|
||||||
}
|
}
|
||||||
t->deleteLater();
|
t->deleteLater();
|
||||||
|
|
||||||
|
qCInfo(dcNymeaConnection()) << "Current transport:" << m_currentTransport << "Remaining connections:" << m_transportCandidates.count() << "Current host:" << m_currentHost;
|
||||||
|
|
||||||
if (!m_currentTransport && m_transportCandidates.isEmpty()) {
|
if (!m_currentTransport && m_transportCandidates.isEmpty()) {
|
||||||
qDebug() << "Last connection dropped.";
|
qCWarning(dcNymeaConnection()) << "Last connection dropped.";
|
||||||
QTimer::singleShot(1000, this, [this](){
|
QTimer::singleShot(1000, this, [this](){
|
||||||
if (m_currentHost && m_connectionStatus != ConnectionStatusSslUntrusted) {
|
if (m_currentHost && m_connectionStatus != ConnectionStatusSslUntrusted) {
|
||||||
qDebug() << "Trying to reconnect..";
|
qCInfo(dcNymeaConnection()) << "Trying to reconnect..";
|
||||||
connectInternal(m_currentHost);
|
connectInternal(m_currentHost);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Current transport:" << m_currentTransport << "Remaining connections:" << m_transportCandidates.count() << "Current host:" << m_currentHost;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_transportCandidates.remove(m_currentTransport);
|
m_transportCandidates.remove(m_currentTransport);
|
||||||
@ -313,7 +312,7 @@ void NymeaConnection::onDisconnected()
|
|||||||
|
|
||||||
foreach (NymeaTransportInterface *candidate, m_transportCandidates.keys()) {
|
foreach (NymeaTransportInterface *candidate, m_transportCandidates.keys()) {
|
||||||
if (candidate->connectionState() == NymeaTransportInterface::ConnectionStateConnected) {
|
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;
|
m_currentTransport = candidate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -322,7 +321,7 @@ void NymeaConnection::onDisconnected()
|
|||||||
emit currentConnectionChanged();
|
emit currentConnectionChanged();
|
||||||
|
|
||||||
if (!m_currentTransport) {
|
if (!m_currentTransport) {
|
||||||
qDebug() << "NymeaConnection: disconnected.";
|
qCInfo(dcNymeaConnection()) << "Disconnected.";
|
||||||
emit connectedChanged(false);
|
emit connectedChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,6 +333,7 @@ void NymeaConnection::onDisconnected()
|
|||||||
if (m_connectionStatus != ConnectionStatusSslUntrusted) {
|
if (m_connectionStatus != ConnectionStatusSslUntrusted) {
|
||||||
QTimer::singleShot(1000, this, [this](){
|
QTimer::singleShot(1000, this, [this](){
|
||||||
if (m_currentHost) {
|
if (m_currentHost) {
|
||||||
|
qCInfo(dcNymeaConnection()) << "Trying to reconnect after disconnect...";
|
||||||
connectInternal(m_currentHost);
|
connectInternal(m_currentHost);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -344,9 +344,9 @@ void NymeaConnection::updateActiveBearers()
|
|||||||
{
|
{
|
||||||
NymeaConnection::BearerTypes availableBearerTypes;
|
NymeaConnection::BearerTypes availableBearerTypes;
|
||||||
QList<QNetworkConfiguration> configs = m_networkConfigManager->allConfigurations(QNetworkConfiguration::Active);
|
QList<QNetworkConfiguration> configs = m_networkConfigManager->allConfigurations(QNetworkConfiguration::Active);
|
||||||
// qDebug() << "Network configuations:" << configs.count();
|
qCDebug(dcNymeaConnection()) << "Network configuations:" << configs.count();
|
||||||
foreach (const QNetworkConfiguration &config, configs) {
|
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...
|
// 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)
|
#if defined(Q_OS_IOS)
|
||||||
@ -357,27 +357,27 @@ void NymeaConnection::updateActiveBearers()
|
|||||||
}
|
}
|
||||||
if (availableBearerTypes == NymeaConnection::BearerTypeNone) {
|
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...
|
// 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<QNetworkConfiguration> configs = m_networkConfigManager->allConfigurations();
|
QList<QNetworkConfiguration> configs = m_networkConfigManager->allConfigurations();
|
||||||
foreach (const QNetworkConfiguration &config, configs) {
|
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();
|
m_networkConfigManager->updateConfigurations();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_availableBearerTypes != availableBearerTypes) {
|
if (m_availableBearerTypes != availableBearerTypes) {
|
||||||
// qDebug() << "Available Bearer Types changed:" << availableBearerTypes;
|
qCInfo(dcNymeaConnection()) << "Available Bearer Types changed to:" << availableBearerTypes;
|
||||||
m_availableBearerTypes = availableBearerTypes;
|
m_availableBearerTypes = availableBearerTypes;
|
||||||
emit availableBearerTypesChanged();
|
emit availableBearerTypesChanged();
|
||||||
} else {
|
} else {
|
||||||
// qDebug() << "Available Bearer Types:" << availableBearerTypes;
|
qCDebug(dcNymeaConnection()) << "Available Bearer Types:" << availableBearerTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_currentHost) {
|
if (!m_currentHost) {
|
||||||
// No host set... Nothing to do...
|
// No host set... Nothing to do...
|
||||||
qDebug() << "No current host... Nothing to do...";
|
qCInfo(dcNymeaConnection()) << "No current host... Nothing to do...";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,14 +392,17 @@ void NymeaConnection::updateActiveBearers()
|
|||||||
|
|
||||||
if (!m_currentTransport) {
|
if (!m_currentTransport) {
|
||||||
// There's a host but no connection. Try connecting now...
|
// 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);
|
connectInternal(m_currentHost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NymeaConnection::hostConnectionsUpdated()
|
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)
|
void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory)
|
||||||
@ -418,10 +421,10 @@ void NymeaConnection::connectToHost(NymeaHost *nymeaHost, Connection *connection
|
|||||||
m_preferredConnection = nullptr;
|
m_preferredConnection = nullptr;
|
||||||
if (connection) {
|
if (connection) {
|
||||||
if (nymeaHost->connections()->find(connection->url())) {
|
if (nymeaHost->connections()->find(connection->url())) {
|
||||||
qDebug() << "Setting preferred connection to" << connection->url();
|
qCInfo(dcNymeaConnection()) << "Setting preferred connection to" << connection->url();
|
||||||
m_preferredConnection = connection;
|
m_preferredConnection = connection;
|
||||||
} else {
|
} 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 (m_preferredConnection) {
|
||||||
if (isConnectionBearerAvailable(m_preferredConnection->bearerType())) {
|
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);
|
connectInternal(m_preferredConnection);
|
||||||
return;
|
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);
|
Connection *loopbackConnection = host->connections()->bestMatch(Connection::BearerTypeLoopback);
|
||||||
if (loopbackConnection) {
|
if (loopbackConnection) {
|
||||||
qDebug() << "Best candidate Loopback connection:" << loopbackConnection->url();
|
qCDebug(dcNymeaConnection()) << "Best candidate Loopback connection:" << loopbackConnection->url();
|
||||||
connectInternal(loopbackConnection);
|
connectInternal(loopbackConnection);
|
||||||
|
|
||||||
} else if (m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeWiFi)
|
} else if (m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeWiFi)
|
||||||
|| m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeEthernet)) {
|
|| m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeEthernet)) {
|
||||||
Connection* lanConnection = host->connections()->bestMatch(Connection::BearerTypeLan | Connection::BearerTypeWan);
|
Connection* lanConnection = host->connections()->bestMatch(Connection::BearerTypeLan | Connection::BearerTypeWan);
|
||||||
if (lanConnection) {
|
if (lanConnection) {
|
||||||
qDebug() << "Best candidate LAN/WAN connection:" << lanConnection->url();
|
qCDebug(dcNymeaConnection()) << "Best candidate LAN/WAN connection:" << lanConnection->url();
|
||||||
connectInternal(lanConnection);
|
connectInternal(lanConnection);
|
||||||
} else {
|
} 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)) {
|
} else if (m_availableBearerTypes.testFlag(NymeaConnection::BearerTypeMobileData)) {
|
||||||
Connection* wanConnection = host->connections()->bestMatch(Connection::BearerTypeWan);
|
Connection* wanConnection = host->connections()->bestMatch(Connection::BearerTypeWan);
|
||||||
if (wanConnection) {
|
if (wanConnection) {
|
||||||
qDebug() << "Best candidate WAN connection:" << wanConnection->url();
|
qCDebug(dcNymeaConnection()) << "Best candidate WAN connection:" << wanConnection->url();
|
||||||
connectInternal(wanConnection);
|
connectInternal(wanConnection);
|
||||||
} else {
|
} 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);
|
Connection* cloudConnection = host->connections()->bestMatch(Connection::BearerTypeCloud);
|
||||||
if (cloudConnection) {
|
if (cloudConnection) {
|
||||||
qDebug() << "Best candidate Cloud connection:" << cloudConnection->url();
|
qCDebug(dcNymeaConnection()) << "Best candidate Cloud connection:" << cloudConnection->url();
|
||||||
connectInternal(cloudConnection);
|
connectInternal(cloudConnection);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "No available Cloud connection to" << host->name();
|
qCDebug(dcNymeaConnection()) << "No available Cloud connection to" << host->name();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_transportCandidates.isEmpty()) {
|
if (m_transportCandidates.isEmpty()) {
|
||||||
|
qCWarning(dcNymeaConnection()) << "No available bearers available for host:" << host->name() << host->uuid();
|
||||||
m_connectionStatus = ConnectionStatusNoBearerAvailable;
|
m_connectionStatus = ConnectionStatusNoBearerAvailable;
|
||||||
} else {
|
} else {
|
||||||
m_connectionStatus = ConnectionStatusConnecting;
|
m_connectionStatus = ConnectionStatusConnecting;
|
||||||
@ -483,12 +487,12 @@ void NymeaConnection::connectInternal(NymeaHost *host)
|
|||||||
bool NymeaConnection::connectInternal(Connection *connection)
|
bool NymeaConnection::connectInternal(Connection *connection)
|
||||||
{
|
{
|
||||||
if (!m_transportFactories.contains(connection->url().scheme())) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_transportCandidates.values().contains(connection)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,7 +515,7 @@ bool NymeaConnection::connectInternal(Connection *connection)
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
m_transportCandidates.insert(newTransport, 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());
|
return newTransport->connect(connection->url());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +523,7 @@ NymeaConnection::BearerType NymeaConnection::qBearerTypeToNymeaBearerType(QNetwo
|
|||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case QNetworkConfiguration::BearerUnknown:
|
case QNetworkConfiguration::BearerUnknown:
|
||||||
|
// Unable to determine the connection type. Assume it's something we can establish any connection type on
|
||||||
return BearerTypeAll;
|
return BearerTypeAll;
|
||||||
case QNetworkConfiguration::BearerEthernet:
|
case QNetworkConfiguration::BearerEthernet:
|
||||||
return BearerTypeEthernet;
|
return BearerTypeEthernet;
|
||||||
|
|||||||
@ -126,6 +126,7 @@
|
|||||||
#include "zigbee/zigbeeadaptersproxy.h"
|
#include "zigbee/zigbeeadaptersproxy.h"
|
||||||
#include "zigbee/zigbeenetwork.h"
|
#include "zigbee/zigbeenetwork.h"
|
||||||
#include "zigbee/zigbeenetworks.h"
|
#include "zigbee/zigbeenetworks.h"
|
||||||
|
#include "applogcontroller.h"
|
||||||
|
|
||||||
#include <QtQml/qqml.h>
|
#include <QtQml/qqml.h>
|
||||||
|
|
||||||
@ -158,6 +159,9 @@ void registerQmlTypes() {
|
|||||||
|
|
||||||
qmlRegisterType<Engine>(uri, 1, 0, "Engine");
|
qmlRegisterType<Engine>(uri, 1, 0, "Engine");
|
||||||
|
|
||||||
|
qmlRegisterSingletonType<AppLogController>("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider);
|
||||||
|
qmlRegisterType<LogMessages>("Nymea", 1, 0, "LogMessages");
|
||||||
|
|
||||||
qmlRegisterUncreatableType<ThingManager>(uri, 1, 0, "ThingManager", "Can't create this in QML. Get it from the Engine.");
|
qmlRegisterUncreatableType<ThingManager>(uri, 1, 0, "ThingManager", "Can't create this in QML. Get it from the Engine.");
|
||||||
qmlRegisterUncreatableType<JsonRpcClient>(uri, 1, 0, "JsonRpcClient", "Can't create this in QML. Get it from the Engine.");
|
qmlRegisterUncreatableType<JsonRpcClient>(uri, 1, 0, "JsonRpcClient", "Can't create this in QML. Get it from the Engine.");
|
||||||
qmlRegisterUncreatableType<NymeaConnection>(uri, 1, 0, "NymeaConnection", "Can't create this in QML. Get it from the Engine.");
|
qmlRegisterUncreatableType<NymeaConnection>(uri, 1, 0, "NymeaConnection", "Can't create this in QML. Get it from the Engine.");
|
||||||
|
|||||||
@ -20,7 +20,9 @@ INCLUDEPATH += \
|
|||||||
$$top_srcdir/QtZeroConf
|
$$top_srcdir/QtZeroConf
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
$$PWD/wifisetup/btwifisetup.cpp \
|
$${PWD}/logging.cpp \
|
||||||
|
$${PWD}/applogcontroller.cpp \
|
||||||
|
$${PWD}/wifisetup/btwifisetup.cpp \
|
||||||
$${PWD}/configuration/networkmanager.cpp \
|
$${PWD}/configuration/networkmanager.cpp \
|
||||||
$${PWD}/engine.cpp \
|
$${PWD}/engine.cpp \
|
||||||
$${PWD}/models/barseriesadapter.cpp \
|
$${PWD}/models/barseriesadapter.cpp \
|
||||||
@ -163,7 +165,9 @@ SOURCES += \
|
|||||||
|
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
$$PWD/wifisetup/btwifisetup.h \
|
$${PWD}/logging.h \
|
||||||
|
$${PWD}/applogcontroller.h \
|
||||||
|
$${PWD}/wifisetup/btwifisetup.h \
|
||||||
$${PWD}/configuration/networkmanager.h \
|
$${PWD}/configuration/networkmanager.h \
|
||||||
$${PWD}/engine.h \
|
$${PWD}/engine.h \
|
||||||
$${PWD}/models/barseriesadapter.h \
|
$${PWD}/models/barseriesadapter.h \
|
||||||
|
|||||||
6
libnymea-app/logging.cpp
Normal file
6
libnymea-app/logging.cpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
QStringList& nymeaLoggingCategories() {
|
||||||
|
static QStringList _nymeaLoggingCategories;
|
||||||
|
return _nymeaLoggingCategories;
|
||||||
|
}
|
||||||
20
libnymea-app/logging.h
Normal file
20
libnymea-app/logging.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef LOGGING_H
|
||||||
|
#define LOGGING_H
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
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
|
||||||
@ -41,6 +41,9 @@
|
|||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcThingManager, "ThingManager")
|
||||||
|
|
||||||
ThingManager::ThingManager(JsonRpcClient* jsonclient, QObject *parent) :
|
ThingManager::ThingManager(JsonRpcClient* jsonclient, QObject *parent) :
|
||||||
JsonHandler(parent),
|
JsonHandler(parent),
|
||||||
m_vendors(new Vendors(this)),
|
m_vendors(new Vendors(this)),
|
||||||
@ -122,7 +125,9 @@ void ThingManager::notificationReceived(const QVariantMap &data)
|
|||||||
if (notification == "Integrations.StateChanged") {
|
if (notification == "Integrations.StateChanged") {
|
||||||
Thing *thing = m_things->getThing(data.value("params").toMap().value("thingId").toUuid());
|
Thing *thing = m_things->getThing(data.value("params").toMap().value("thingId").toUuid());
|
||||||
if (!thing) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
QUuid stateTypeId = data.value("params").toMap().value("stateTypeId").toUuid();
|
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);
|
Thing *thing = m_things->getThing(thingId);
|
||||||
if (!thing) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// qDebug() << "Event received" << thingId.toString() << eventTypeId.toString() << qUtf8Printable(QJsonDocument::fromVariant(event).toJson());
|
// qDebug() << "Event received" << thingId.toString() << eventTypeId.toString() << qUtf8Printable(QJsonDocument::fromVariant(event).toJson());
|
||||||
|
|||||||
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* 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 <QStandardPaths>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QClipboard>
|
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QMutexLocker>
|
|
||||||
|
|
||||||
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<int, QByteArray> AppLogController::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> 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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -42,12 +42,15 @@
|
|||||||
|
|
||||||
#include "stylecontroller.h"
|
#include "stylecontroller.h"
|
||||||
#include "pushnotifications.h"
|
#include "pushnotifications.h"
|
||||||
#include "applogcontroller.h"
|
|
||||||
#include "ruletemplates/messages.h"
|
#include "ruletemplates/messages.h"
|
||||||
#include "nfchelper.h"
|
#include "nfchelper.h"
|
||||||
#include "nfcthingactionwriter.h"
|
#include "nfcthingactionwriter.h"
|
||||||
#include "platformhelper.h"
|
#include "platformhelper.h"
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcApplication, "Application")
|
||||||
|
NYMEA_LOGGING_CATEGORY(qml, "qml")
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
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));
|
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
||||||
application.installTranslator(&qtTranslator);
|
application.installTranslator(&qtTranslator);
|
||||||
|
|
||||||
qDebug() << "nymea:app" << APP_VERSION << "running on" << QSysInfo::machineHostName() << QSysInfo::prettyProductName() << QSysInfo::productType() << QSysInfo::productVersion();
|
qCInfo(dcApplication()) << "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()) << "Locale info:" << QLocale() << QLocale().name() << QLocale().language() << QLocale().system();
|
||||||
|
|
||||||
QTranslator appTranslator;
|
QTranslator appTranslator;
|
||||||
bool translationResult = appTranslator.load("nymea-app-" + QLocale().name(), ":/translations/");
|
bool translationResult = appTranslator.load("nymea-app-" + QLocale().name(), ":/translations/");
|
||||||
if (translationResult) {
|
if (translationResult) {
|
||||||
qDebug() << "Loaded translation for locale" << QLocale();
|
qCDebug(dcApplication()) << "Loaded translation for locale" << QLocale();
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Failed to load translations for locale" << QLocale();
|
qCInfo(dcApplication()) << "Failed to load translations for locale" << QLocale();
|
||||||
}
|
}
|
||||||
application.installTranslator(&appTranslator);
|
application.installTranslator(&appTranslator);
|
||||||
|
|
||||||
@ -116,7 +119,7 @@ int main(int argc, char *argv[])
|
|||||||
#else
|
#else
|
||||||
StyleController styleController(parser.value(defaultStyleOption));
|
StyleController styleController(parser.value(defaultStyleOption));
|
||||||
if (parser.isSet(styleOption)) {
|
if (parser.isSet(styleOption)) {
|
||||||
qDebug() << "Setting style to" << parser.value(styleOption);
|
qCInfo(dcApplication()) << "Setting style to" << parser.value(styleOption);
|
||||||
styleController.lockToStyle(parser.value(styleOption));
|
styleController.lockToStyle(parser.value(styleOption));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -128,7 +131,7 @@ int main(int argc, char *argv[])
|
|||||||
QFontDatabase::addApplicationFont(fi.absoluteFilePath());
|
QFontDatabase::addApplicationFont(fi.absoluteFilePath());
|
||||||
}
|
}
|
||||||
foreach (const QFileInfo &fi, QDir(":/styles/" + styleController.currentStyle() + "/fonts/").entryInfoList()) {
|
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());
|
QFontDatabase::addApplicationFont(fi.absoluteFilePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +144,6 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<NfcThingActionWriter>("Nymea", 1, 0, "NfcThingActionWriter");
|
qmlRegisterType<NfcThingActionWriter>("Nymea", 1, 0, "NfcThingActionWriter");
|
||||||
|
|
||||||
qmlRegisterSingletonType<PushNotifications>("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider);
|
qmlRegisterSingletonType<PushNotifications>("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider);
|
||||||
qmlRegisterSingletonType<AppLogController>("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider);
|
|
||||||
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
|
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
|
||||||
|
|
||||||
engine->rootContext()->setContextProperty("appVersion", APP_VERSION);
|
engine->rootContext()->setContextProperty("appVersion", APP_VERSION);
|
||||||
|
|||||||
@ -24,7 +24,6 @@ HEADERS += \
|
|||||||
pushnotifications.h \
|
pushnotifications.h \
|
||||||
platformhelper.h \
|
platformhelper.h \
|
||||||
platformintegration/generic/platformhelpergeneric.h \
|
platformintegration/generic/platformhelpergeneric.h \
|
||||||
applogcontroller.h \
|
|
||||||
ruletemplates/messages.h
|
ruletemplates/messages.h
|
||||||
|
|
||||||
SOURCES += main.cpp \
|
SOURCES += main.cpp \
|
||||||
@ -37,7 +36,6 @@ SOURCES += main.cpp \
|
|||||||
pushnotifications.cpp \
|
pushnotifications.cpp \
|
||||||
platformhelper.cpp \
|
platformhelper.cpp \
|
||||||
platformintegration/generic/platformhelpergeneric.cpp \
|
platformintegration/generic/platformhelpergeneric.cpp \
|
||||||
applogcontroller.cpp
|
|
||||||
|
|
||||||
RESOURCES += resources.qrc \
|
RESOURCES += resources.qrc \
|
||||||
ruletemplates.qrc \
|
ruletemplates.qrc \
|
||||||
|
|||||||
@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#if defined Q_OS_ANDROID
|
#if defined Q_OS_ANDROID
|
||||||
#include <QtAndroidExtras/QtAndroid>
|
#include <QtAndroidExtras/QtAndroid>
|
||||||
@ -44,6 +46,9 @@
|
|||||||
#include "platformintegration/generic/platformhelpergeneric.h"
|
#include "platformintegration/generic/platformhelpergeneric.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcPlatformIntegration, "PlatformIntegration")
|
||||||
|
|
||||||
PlatformHelper* PlatformHelper::s_instance = nullptr;
|
PlatformHelper* PlatformHelper::s_instance = nullptr;
|
||||||
|
|
||||||
PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent)
|
PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent)
|
||||||
@ -183,6 +188,11 @@ QString PlatformHelper::fromClipBoard()
|
|||||||
return QApplication::clipboard()->text();
|
return QApplication::clipboard()->text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelper::shareFile(const QString &fileName)
|
||||||
|
{
|
||||||
|
QDesktopServices::openUrl(QUrl(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
QObject *PlatformHelper::platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
QObject *PlatformHelper::platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||||
{
|
{
|
||||||
Q_UNUSED(engine)
|
Q_UNUSED(engine)
|
||||||
|
|||||||
@ -91,6 +91,8 @@ public:
|
|||||||
Q_INVOKABLE virtual void toClipBoard(const QString &text);
|
Q_INVOKABLE virtual void toClipBoard(const QString &text);
|
||||||
Q_INVOKABLE virtual QString fromClipBoard();
|
Q_INVOKABLE virtual QString fromClipBoard();
|
||||||
|
|
||||||
|
Q_INVOKABLE virtual void shareFile(const QString &fileName);
|
||||||
|
|
||||||
static QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
|
static QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
|
||||||
signals:
|
signals:
|
||||||
void permissionsRequestFinished();
|
void permissionsRequestFinished();
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
package io.guh.nymeaapp;
|
package io.guh.nymeaapp;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -7,6 +10,8 @@ import android.os.Build;
|
|||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.provider.Settings.Secure;
|
import android.provider.Settings.Secure;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.v4.content.FileProvider;
|
||||||
|
|
||||||
public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActivity
|
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);
|
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
v.vibrate(duration);
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -175,7 +175,14 @@ void PlatformHelperAndroid::setBottomPanelColor(const QColor &color)
|
|||||||
PlatformHelper::setBottomPanelColor(color);
|
PlatformHelper::setBottomPanelColor(color);
|
||||||
|
|
||||||
if (QtAndroid::androidSdkVersion() < 21)
|
if (QtAndroid::androidSdkVersion() < 21)
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformHelperAndroid::shareFile(const QString &fileName)
|
||||||
|
{
|
||||||
|
QtAndroid::androidActivity().callMethod<void>("shareFile", "(Ljava/lang/String;)V",
|
||||||
|
QAndroidJniObject::fromString(fileName).object<jstring>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformHelperAndroid::permissionRequestFinished(const QtAndroid::PermissionResultMap &result)
|
void PlatformHelperAndroid::permissionRequestFinished(const QtAndroid::PermissionResultMap &result)
|
||||||
|
|||||||
@ -62,6 +62,8 @@ public:
|
|||||||
void setTopPanelTheme(Theme theme);
|
void setTopPanelTheme(Theme theme);
|
||||||
void setBottomPanelColor(const QColor &color) override;
|
void setBottomPanelColor(const QColor &color) override;
|
||||||
|
|
||||||
|
void shareFile(const QString &fileName) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,6 +30,10 @@
|
|||||||
|
|
||||||
#include "platformhelpergeneric.h"
|
#include "platformhelpergeneric.h"
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(dcPlatformIntergration)
|
||||||
|
|
||||||
PlatformHelperGeneric::PlatformHelperGeneric(QObject *parent) : PlatformHelper(parent)
|
PlatformHelperGeneric::PlatformHelperGeneric(QObject *parent) : PlatformHelper(parent)
|
||||||
{
|
{
|
||||||
m_piHelper = new ScreenHelper(this);
|
m_piHelper = new ScreenHelper(this);
|
||||||
|
|||||||
@ -36,32 +36,35 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(dcPlatformIntegration)
|
||||||
|
|
||||||
ScreenHelper::ScreenHelper(QObject *parent) : QObject(parent)
|
ScreenHelper::ScreenHelper(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
// Try generic backlight
|
// Try generic backlight
|
||||||
QDir backlightDir("/sys/class/backlight");
|
QDir backlightDir("/sys/class/backlight");
|
||||||
foreach (const QFileInfo &fi, backlightDir.entryInfoList({"*_backlight"}, QDir::Dirs)) {
|
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_powerFile.setFileName(fi.absoluteFilePath() + "/bl_power");
|
||||||
m_brightnessFile.setFileName(fi.absoluteFilePath() + "/brightness");
|
m_brightnessFile.setFileName(fi.absoluteFilePath() + "/brightness");
|
||||||
if (!m_powerFile.open(QFile::ReadWrite | QFile::Text)) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (!m_brightnessFile.open(QFile::ReadWrite | QFile::Text)) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
QFile maxBrightnessFile(fi.absoluteFilePath() + "/max_brightness");
|
QFile maxBrightnessFile(fi.absoluteFilePath() + "/max_brightness");
|
||||||
if (!maxBrightnessFile.open(QFile::ReadOnly)) {
|
if (!maxBrightnessFile.open(QFile::ReadOnly)) {
|
||||||
qWarning() << "Cannot open" << m_brightnessFile.fileName() << "for reading";
|
qCDebug(dcPlatformIntegration()) << "Cannot open" << m_brightnessFile.fileName() << "for reading";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bool ok;
|
bool ok;
|
||||||
m_maxBrightness = maxBrightnessFile.readLine().trimmed().toInt(&ok);
|
m_maxBrightness = maxBrightnessFile.readLine().trimmed().toInt(&ok);
|
||||||
if (!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;
|
m_maxBrightness = -1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -70,15 +73,15 @@ ScreenHelper::ScreenHelper(QObject *parent) : QObject(parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!m_powerFile.isOpen() || !m_brightnessFile.isOpen()) {
|
if (!m_powerFile.isOpen() || !m_brightnessFile.isOpen()) {
|
||||||
qWarning() << "No backlight support on this platform";
|
qCInfo(dcPlatformIntegration()) << "No backlight support on this platform";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
qDebug() << "Backlight control enabled on" << m_powerFile.fileName();
|
qCInfo(dcPlatformIntegration()) << "Backlight control enabled on" << m_powerFile.fileName();
|
||||||
|
|
||||||
bool ok;
|
bool ok;
|
||||||
int currentBrightness = m_brightnessFile.readLine().trimmed().toInt(&ok);
|
int currentBrightness = m_brightnessFile.readLine().trimmed().toInt(&ok);
|
||||||
m_currentBrightness = currentBrightness * 100 / m_maxBrightness;
|
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();
|
screenOn();
|
||||||
|
|
||||||
@ -190,20 +193,20 @@ bool ScreenHelper::eventFilter(QObject *watched, QEvent *event)
|
|||||||
|
|
||||||
void ScreenHelper::screenOn()
|
void ScreenHelper::screenOn()
|
||||||
{
|
{
|
||||||
qDebug() << "Turning screen on";
|
qCInfo(dcPlatformIntegration()) << "Turning screen on";
|
||||||
int ret = m_powerFile.write("0\n");
|
int ret = m_powerFile.write("0\n");
|
||||||
m_powerFile.flush();
|
m_powerFile.flush();
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
qWarning() << "Failed to power on screen";
|
qCWarning(dcPlatformIntegration()) << "Failed to power on screen";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenHelper::screenOff()
|
void ScreenHelper::screenOff()
|
||||||
{
|
{
|
||||||
qDebug() << "Turning screen off";
|
qCInfo(dcPlatformIntegration()) << "Turning screen off";
|
||||||
int ret = m_powerFile.write("1\n");
|
int ret = m_powerFile.write("1\n");
|
||||||
m_powerFile.flush();
|
m_powerFile.flush();
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
qWarning() << "Failed to power off screen";
|
qCWarning(dcPlatformIntegration()) << "Failed to power off screen";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,8 @@ public:
|
|||||||
void setTopPanelColor(const QColor &color) override;
|
void setTopPanelColor(const QColor &color) override;
|
||||||
void setBottomPanelColor(const QColor &color) override;
|
void setBottomPanelColor(const QColor &color) override;
|
||||||
|
|
||||||
|
void shareFile(const QString &fileName) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// defined in platformhelperios.mm
|
// defined in platformhelperios.mm
|
||||||
QString readKeyChainEntry(const QString &service, const QString &key);
|
QString readKeyChainEntry(const QString &service, const QString &key);
|
||||||
|
|||||||
@ -232,5 +232,6 @@
|
|||||||
<file>ui/customviews/ThermostatController.qml</file>
|
<file>ui/customviews/ThermostatController.qml</file>
|
||||||
<file>ui/devicepages/ThermostatDevicePage.qml</file>
|
<file>ui/devicepages/ThermostatDevicePage.qml</file>
|
||||||
<file>ui/components/BigThingTile.qml</file>
|
<file>ui/components/BigThingTile.qml</file>
|
||||||
|
<file>ui/appsettings/LoggingCategories.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -53,7 +53,7 @@ Drawer {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: root.currentEngine.jsonRpcClient.currentConnection.url
|
text: root.currentEngine.jsonRpcClient.currentConnection ? root.currentEngine.jsonRpcClient.currentConnection.url : ""
|
||||||
font.pixelSize: app.extraSmallFont
|
font.pixelSize: app.extraSmallFont
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,6 +110,12 @@ ApplicationWindow {
|
|||||||
value: settings.units === "metric" ? Types.UnitSystemMetric : Types.UnitSystemImperial
|
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
|
property alias mainMenu: m
|
||||||
MainMenu {
|
MainMenu {
|
||||||
id: m
|
id: m
|
||||||
|
|||||||
@ -157,12 +157,6 @@ Item {
|
|||||||
property int connectionTabIndex: index
|
property int connectionTabIndex: index
|
||||||
// onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.jsonRpcClient.url
|
// onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.jsonRpcClient.url
|
||||||
|
|
||||||
Binding {
|
|
||||||
target: AWSClient
|
|
||||||
property: "config"
|
|
||||||
value: "cloudEnvironment" in app ? app.cloudEnvironment : settings.cloudEnvironment
|
|
||||||
}
|
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
target: nymeaDiscovery
|
target: nymeaDiscovery
|
||||||
property: "discovering"
|
property: "discovering"
|
||||||
@ -469,6 +463,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
visible: settings.showConnectionTabs
|
visible: settings.showConnectionTabs
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
@ -477,6 +472,7 @@ Item {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Material.elevation: 2
|
Material.elevation: 2
|
||||||
position: TabBar.Footer
|
position: TabBar.Footer
|
||||||
|
property int tabWidth: Math.max(150, root.width / tabModel.count)
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: tabModel.count
|
model: tabModel.count
|
||||||
@ -486,7 +482,7 @@ Item {
|
|||||||
property var engine: mainRepeater.itemAt(index)._engine
|
property var engine: mainRepeater.itemAt(index)._engine
|
||||||
property string serverName: engine.nymeaConfiguration.serverName
|
property string serverName: engine.nymeaConfiguration.serverName
|
||||||
Material.elevation: index
|
Material.elevation: index
|
||||||
width: Math.max(150, tabbar.width / tabModel.count)
|
width: tabbar.tabWidth
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@ -17,10 +17,39 @@ Item {
|
|||||||
property color tileOverlayIconColor: iconColor
|
property color tileOverlayIconColor: iconColor
|
||||||
property int tileRadius: 6
|
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
|
// Note: Font files need to be provided in a "fonts" folder in the style
|
||||||
property string fontFamily: "Ubuntu"
|
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
|
// Icon/graph colors for various interfaces
|
||||||
property var interfaceColors: {
|
property var interfaceColors: {
|
||||||
"temperaturesensor": "red",
|
"temperaturesensor": "red",
|
||||||
|
|||||||
@ -36,12 +36,15 @@ import "../components"
|
|||||||
|
|
||||||
Page {
|
Page {
|
||||||
header: NymeaHeader {
|
header: NymeaHeader {
|
||||||
text: qsTr("App log")
|
text: qsTr("Application logs")
|
||||||
backButtonVisible: true
|
backButtonVisible: true
|
||||||
onBackPressed: pageStack.pop()
|
onBackPressed: pageStack.pop()
|
||||||
HeaderButton {
|
HeaderButton {
|
||||||
imageSource: "../images/edit-copy.svg"
|
imageSource: "../images/state-out.svg"
|
||||||
onClicked: AppLogController.toClipboard()
|
onClicked: {
|
||||||
|
var exportedFile = AppLogController.exportLogs()
|
||||||
|
PlatformHelper.shareFile(exportedFile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,14 +54,29 @@ Page {
|
|||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
|
||||||
model: AppLogController
|
model: LogMessages {
|
||||||
delegate: Text {
|
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Label {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
maximumLineCount: 2
|
maximumLineCount: 2
|
||||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
text: model.text
|
text: model.text
|
||||||
color: model.type === AppLogController.TypeWarning ? "red" : Style.foregroundColor
|
color: {
|
||||||
font.pixelSize: app.smallFont
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,21 +42,25 @@ SettingsPageBase {
|
|||||||
text: qsTr("Logging")
|
text: qsTr("Logging")
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckDelegate {
|
SwitchDelegate {
|
||||||
text: qsTr("Enable app logging")
|
text: qsTr("Application logs enabled")
|
||||||
enabled: AppLogController.canWriteLogs
|
|
||||||
checked: AppLogController.enabled
|
checked: AppLogController.enabled
|
||||||
onCheckedChanged: AppLogController.enabled = checked;
|
onCheckedChanged: AppLogController.enabled = checked
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
NymeaSwipeDelegate {
|
NymeaItemDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: qsTr("View log")
|
text: qsTr("View live log")
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("../appsettings/AppLogPage.qml"))
|
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 {
|
SettingsPageSectionHeader {
|
||||||
text: qsTr("Advanced options")
|
text: qsTr("Advanced options")
|
||||||
|
|||||||
130
nymea-app/ui/appsettings/LoggingCategories.qml
Normal file
130
nymea-app/ui/appsettings/LoggingCategories.qml
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -41,7 +41,6 @@ import Nymea 1.0
|
|||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
id: root
|
id: root
|
||||||
implicitWidth: 200
|
|
||||||
|
|
||||||
property string subText
|
property string subText
|
||||||
property bool progressive: true
|
property bool progressive: true
|
||||||
|
|||||||
@ -158,6 +158,10 @@ MainPageTile {
|
|||||||
rightMargin: app.margins / 2
|
rightMargin: app.margins / 2
|
||||||
}
|
}
|
||||||
sourceComponent: {
|
sourceComponent: {
|
||||||
|
if (!root.iface) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
switch (iface.name) {
|
switch (iface.name) {
|
||||||
case "sensor":
|
case "sensor":
|
||||||
case "weather":
|
case "weather":
|
||||||
|
|||||||
@ -182,9 +182,9 @@ MainViewBase {
|
|||||||
|
|
||||||
ValueAxis {
|
ValueAxis {
|
||||||
id: yAxis
|
id: yAxis
|
||||||
readonly property XYSeriesAdapter adapter: consumersRepeater.itemAt(consumersRepeater.count - 1).adapter;
|
readonly property XYSeriesAdapter adapter: consumersRepeater.count > 0 ? consumersRepeater.itemAt(consumersRepeater.count - 1).adapter : null
|
||||||
max: Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05))
|
max: adapter ? Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05)) : 1
|
||||||
min: Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05))
|
min: adapter ? Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05)) : 0
|
||||||
// This seems to crash occationally
|
// This seems to crash occationally
|
||||||
// onMinChanged: applyNiceNumbers();
|
// onMinChanged: applyNiceNumbers();
|
||||||
// onMaxChanged: applyNiceNumbers();
|
// onMaxChanged: applyNiceNumbers();
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import Nymea 1.0
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@ -142,6 +142,16 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="io.guh.nymeaapp.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||||
|
|||||||
3
packaging/android/res/xml/file_paths.xml
Normal file
3
packaging/android/res/xml/file_paths.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<cache-path name="cache" path="." />
|
||||||
|
</paths>
|
||||||
@ -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()];
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user