Load configuration from disk
- Add FileIO backend - Fill login form with saved configuration
This commit is contained in:
@@ -15,6 +15,7 @@ find_package(Qt6 COMPONENTS Widgets Qml QuickControls2 REQUIRED)
|
|||||||
|
|
||||||
add_executable(${PROJECT}
|
add_executable(${PROJECT}
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/FileIO.cpp
|
||||||
src/resources.qrc
|
src/resources.qrc
|
||||||
src/Gui/Style/resources.qrc)
|
src/Gui/Style/resources.qrc)
|
||||||
|
|
||||||
|
|||||||
159
src/FileIO.cpp
Normal file
159
src/FileIO.cpp
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* @file FileIO.cpp
|
||||||
|
* @author Juny (marisa@fwmari.net)
|
||||||
|
* @brief File I/O backend implementation
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2023-12-27
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023-2024
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FileIO.hpp"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
static FileIO *m_fileIO = nullptr;
|
||||||
|
|
||||||
|
QObject *FileIO::singletonProvider(QQmlEngine *engine, QJSEngine *) {
|
||||||
|
if (!m_fileIO)
|
||||||
|
m_fileIO = new FileIO((QObject *)engine);
|
||||||
|
|
||||||
|
return (QObject *)m_fileIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileIO::registerTypes(const char *uri) {
|
||||||
|
qmlRegisterSingletonType<FileIO>(uri, 0, 1, "FileIO",
|
||||||
|
FileIO::singletonProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FileIO::read(const QUrl &_path) {
|
||||||
|
QString path(_path.toLocalFile());
|
||||||
|
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
emit this->error("Invalid path");
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(path);
|
||||||
|
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
emit this->error("Could not open file");
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString line, content;
|
||||||
|
QTextStream stream((QIODevice *)&file);
|
||||||
|
|
||||||
|
do {
|
||||||
|
line = stream.readLine();
|
||||||
|
content += line;
|
||||||
|
} while (!line.isNull());
|
||||||
|
|
||||||
|
((QFileDevice *)&file)->close();
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIO::write(const QUrl &_path, const QString &data) {
|
||||||
|
QString path(_path.toLocalFile());
|
||||||
|
QString tempPath(_path.toLocalFile() + ".temp");
|
||||||
|
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
emit this->error("Invalid path: " + path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(tempPath);
|
||||||
|
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
emit this->error("Could not open file: " + tempPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 result = file.write(data.toUtf8());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
emit this->error("Failed to write data to file: " + tempPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->move(tempPath, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIO::move(const QUrl &_src, const QUrl &_dst) {
|
||||||
|
QString src(_src.toLocalFile());
|
||||||
|
QString dst(_dst.toLocalFile());
|
||||||
|
|
||||||
|
return this->move(src, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIO::move(const QString &src, const QString &dst) {
|
||||||
|
if (src.isEmpty() || dst.isEmpty()) {
|
||||||
|
emit this->error("Invalid path: " + src);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QFile::exists(dst))
|
||||||
|
if (!QFile::remove(dst)) {
|
||||||
|
emit this->error("Could not remove: " + dst);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QFile::rename(src, dst)) {
|
||||||
|
emit this->error("Could not move to: " + dst);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIO::copy(const QUrl &_src, const QUrl &_dst) {
|
||||||
|
QString src(_src.toLocalFile());
|
||||||
|
QString dst(_dst.toLocalFile());
|
||||||
|
|
||||||
|
return this->copy(src, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIO::copy(const QString &src, const QString &dst) {
|
||||||
|
if (src.isEmpty() || dst.isEmpty()) {
|
||||||
|
emit this->error("Invalid path: " + src);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QFile::exists(dst))
|
||||||
|
if (!QFile::remove(dst)) {
|
||||||
|
emit this->error("Could not remove: " + dst);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QFile::copy(src, dst)) {
|
||||||
|
emit this->error("Could not copy to: " + dst);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIO::mkpath(const QUrl &_path) {
|
||||||
|
QString path(_path.toLocalFile());
|
||||||
|
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
emit this->error("Invalid path");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QDir().mkpath(path)) {
|
||||||
|
emit this->error("Failed to make path");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIO::exists(const QUrl &_path) {
|
||||||
|
QString path(_path.toLocalFile());
|
||||||
|
|
||||||
|
return QDir(path).exists() || QFile(path).exists();
|
||||||
|
}
|
||||||
41
src/FileIO.hpp
Normal file
41
src/FileIO.hpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @file FileIO.hpp
|
||||||
|
* @author Juny (marisa@fwmari.net)
|
||||||
|
* @brief File I/O backend
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2023-12-27
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023-2024
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QJSEngine>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
class FileIO : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FileIO(QObject *parent = nullptr) : QObject(parent){};
|
||||||
|
|
||||||
|
static void registerTypes(const char *uri);
|
||||||
|
static QObject *singletonProvider(QQmlEngine *engine, QJSEngine *);
|
||||||
|
|
||||||
|
Q_INVOKABLE QString read(const QUrl &path);
|
||||||
|
Q_INVOKABLE bool write(const QUrl &path, const QString &data);
|
||||||
|
Q_INVOKABLE bool move(const QUrl &src, const QUrl &dst);
|
||||||
|
Q_INVOKABLE bool copy(const QUrl &src, const QUrl &dst);
|
||||||
|
|
||||||
|
Q_INVOKABLE bool mkpath(const QUrl &path);
|
||||||
|
Q_INVOKABLE bool exists(const QUrl &path);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void error(const QString &msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool move(const QString &src, const QString &dst);
|
||||||
|
bool copy(const QString &src, const QString &dst);
|
||||||
|
};
|
||||||
@@ -2,6 +2,31 @@ pragma Singleton
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
|
import "Settings"
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: app
|
id: app
|
||||||
|
|
||||||
|
property string brokerURL: Settings._json.brokerURL || ""
|
||||||
|
property string brokerHost
|
||||||
|
property string brokerPort
|
||||||
|
|
||||||
|
property string brokerUsername: Settings._json.brokerUsername || ""
|
||||||
|
property string brokerPassword: Settings._json.brokerPassword || ""
|
||||||
|
|
||||||
|
onBrokerURLChanged: {
|
||||||
|
brokerHost = brokerURL.split(":")[0]
|
||||||
|
brokerPort = brokerURL.split(":")[1] || 1883
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveBroker(url, user, pass) {
|
||||||
|
Settings._json.brokerURL = url
|
||||||
|
Settings._json.brokerUsername = user
|
||||||
|
Settings._json.brokerPassword = pass
|
||||||
|
Settings.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
Settings.load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
95
src/Gui/Login/BrokerInput.qml
Normal file
95
src/Gui/Login/BrokerInput.qml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
import "../PStyle"
|
||||||
|
import ".."
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
signal tryConnect(url: string, username: string, password: string)
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: focusTimer
|
||||||
|
running: true
|
||||||
|
onTriggered: brokerURL.pTf.forceActiveFocus()
|
||||||
|
repeat: false
|
||||||
|
interval: 250
|
||||||
|
}
|
||||||
|
|
||||||
|
PText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: qsTr("QuteSpectrometer")
|
||||||
|
font: PStyle.getFont(36)
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
PText {
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
Layout.bottomMargin: 8
|
||||||
|
text: "mqtt://"
|
||||||
|
}
|
||||||
|
|
||||||
|
PInputField {
|
||||||
|
id: brokerURL
|
||||||
|
Layout.preferredWidth: 200
|
||||||
|
label: qsTr("URL")
|
||||||
|
placeholderText: "example.com:1883"
|
||||||
|
tfText: App.brokerURL || ""
|
||||||
|
|
||||||
|
onTfAccepted: _tryConnect()
|
||||||
|
|
||||||
|
onTfChanged: (text) => {
|
||||||
|
let url;
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(`https://${text}`)
|
||||||
|
} catch (_) {
|
||||||
|
btn.enabled = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PGroupBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
title: qsTr("Authentication (optional)")
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
PInputField {
|
||||||
|
id: brokerUsername
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
label: qsTr("Username")
|
||||||
|
tfText: App.brokerUsername || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
PInputField {
|
||||||
|
id: brokerPassword
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
label: qsTr("Password")
|
||||||
|
tfText: App.brokerPassword || ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PTextButton {
|
||||||
|
id: btn
|
||||||
|
text: qsTr("Connect")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
// enabled: false
|
||||||
|
|
||||||
|
onClicked: _tryConnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _tryConnect() {
|
||||||
|
tryConnect(brokerURL.tfText, brokerUsername.tfText, brokerPassword.tfText)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/Gui/Login/LoadingInfo.qml
Normal file
20
src/Gui/Login/LoadingInfo.qml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
import "../PStyle"
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 24
|
||||||
|
|
||||||
|
PBusyIndicator {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
running: true
|
||||||
|
}
|
||||||
|
|
||||||
|
PText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: qsTr("Trying to connect...")
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/Gui/Login/Login.qml
Normal file
39
src/Gui/Login/Login.qml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQml
|
||||||
|
|
||||||
|
import "../PStyle"
|
||||||
|
import ".."
|
||||||
|
|
||||||
|
PStackView {
|
||||||
|
id: login
|
||||||
|
initialItem: brokerInput
|
||||||
|
|
||||||
|
property var instanceInfo
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: brokerInput
|
||||||
|
|
||||||
|
PBackground {
|
||||||
|
BrokerInput {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
onTryConnect: (url, user, pass) => {
|
||||||
|
App.saveBroker(url, user, pass)
|
||||||
|
login.push(loadInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: loadInstance
|
||||||
|
|
||||||
|
PBackground {
|
||||||
|
LoadingInfo {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/Gui/Settings/Settings.qml
Normal file
39
src/Gui/Settings/Settings.qml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtCore
|
||||||
|
|
||||||
|
import QSpec
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
property string configDir: StandardPaths.writableLocation(StandardPaths.AppConfigLocation)
|
||||||
|
property string dataDir: StandardPaths.writableLocation(StandardPaths.AppDataLocation)
|
||||||
|
|
||||||
|
property var _json
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
if (FileIO.exists(configDir) === false)
|
||||||
|
FileIO.mkpath(configDir)
|
||||||
|
|
||||||
|
if (FileIO.exists(dataDir) === false)
|
||||||
|
FileIO.mkpath(dataDir)
|
||||||
|
|
||||||
|
try {
|
||||||
|
_json = JSON.parse(FileIO.read(`${configDir}/config.json`))
|
||||||
|
} catch (_) {
|
||||||
|
print(`Failed to read ${configDir}/config.json`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_json)
|
||||||
|
return true
|
||||||
|
|
||||||
|
print(`Empty or invalid configuration at ${configDir}/config.json`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
print(`Saving settings to ${configDir}/config.json`)
|
||||||
|
FileIO.write(`${configDir}/config.json`, JSON.stringify(_json))
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/Gui/Settings/qmldir
Normal file
1
src/Gui/Settings/qmldir
Normal file
@@ -0,0 +1 @@
|
|||||||
|
singleton Settings 1.0 Settings.qml
|
||||||
16
src/main.cpp
16
src/main.cpp
@@ -1,3 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @author Juny <marisa@fwmari.net>
|
||||||
|
* @brief Main program entry
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2024-02-13
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
@@ -8,6 +19,8 @@
|
|||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
|
|
||||||
|
#include "FileIO.hpp"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||||
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
|
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
|
||||||
@@ -21,6 +34,9 @@ int main(int argc, char *argv[]) {
|
|||||||
QQmlEngine *engine = new QQmlEngine();
|
QQmlEngine *engine = new QQmlEngine();
|
||||||
|
|
||||||
QObject::connect(engine, &QQmlEngine::quit, &QApplication::quit);
|
QObject::connect(engine, &QQmlEngine::quit, &QApplication::quit);
|
||||||
|
|
||||||
|
FileIO::registerTypes("QSpec");
|
||||||
|
|
||||||
engine->addImportPath("qrc:/");
|
engine->addImportPath("qrc:/");
|
||||||
|
|
||||||
QQmlContext *ctx = new QQmlContext(engine->rootContext());
|
QQmlContext *ctx = new QQmlContext(engine->rootContext());
|
||||||
|
|||||||
@@ -6,7 +6,11 @@
|
|||||||
|
|
||||||
<!-- login -->
|
<!-- login -->
|
||||||
<file alias="Login/Login.qml">Gui/Login/Login.qml</file>
|
<file alias="Login/Login.qml">Gui/Login/Login.qml</file>
|
||||||
<file alias="Login/InstanceInput.qml">Gui/Login/InstanceInput.qml</file>
|
<file alias="Login/BrokerInput.qml">Gui/Login/BrokerInput.qml</file>
|
||||||
<file alias="Login/LoadingInfo.qml">Gui/Login/LoadingInfo.qml</file>
|
<file alias="Login/LoadingInfo.qml">Gui/Login/LoadingInfo.qml</file>
|
||||||
|
|
||||||
|
<!-- settings -->
|
||||||
|
<file alias="Settings/Settings.qml">Gui/Settings/Settings.qml</file>
|
||||||
|
<file alias="Settings/qmldir">Gui/Settings/qmldir</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
Reference in New Issue
Block a user