mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
refactor(qt6): migrate C++ sources to .hpp header-only style
Move ModPlayer, NodeRegistry, PackageRegistry from .cpp/.h pairs to single .hpp files. Add DBALRequest.hpp and DBALTypes.hpp. Update DBALClient and main.cpp for new structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
#include <QUrl>
|
||||
#include <QDir>
|
||||
|
||||
#include "src/PackageRegistry.h"
|
||||
#include "src/ModPlayer.h"
|
||||
#include "src/PackageRegistry.hpp"
|
||||
#include "src/ModPlayer.hpp"
|
||||
#include "src/DBALClient.h"
|
||||
#include "src/PackageLoader.h"
|
||||
#include "src/NodeRegistry.h"
|
||||
#include "src/NodeRegistry.hpp"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "DBALClient.h"
|
||||
// DBALTypes.hpp and DBALRequest.hpp are pulled in via DBALClient.h
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkRequest>
|
||||
#include <QUrl>
|
||||
@@ -57,10 +58,12 @@ void DBALClient::setPackageId(const QString &pkg)
|
||||
QString DBALClient::entityPath(const QString &entity) const
|
||||
{
|
||||
// REST: /api/v1/{tenant}/{package}/{entity}
|
||||
return QString("/api/v1/%1/%2/%3").arg(m_tenantId, m_packageId, entity.toLower());
|
||||
return QString("/api/v1/%1/%2/%3")
|
||||
.arg(m_tenantId, m_packageId, entity.toLower());
|
||||
}
|
||||
|
||||
QString DBALClient::entityPath(const QString &entity, const QString &id) const
|
||||
QString DBALClient::entityPath(
|
||||
const QString &entity, const QString &id) const
|
||||
{
|
||||
return entityPath(entity) + "/" + id;
|
||||
}
|
||||
@@ -71,23 +74,30 @@ void DBALClient::setError(const QString &error)
|
||||
emit errorOccurred(error);
|
||||
}
|
||||
|
||||
void DBALClient::sendRequest(const QString &method, const QString &endpoint,
|
||||
const QJsonObject &body, const QJSValue &callback)
|
||||
void DBALClient::sendRequest(
|
||||
const QString &method,
|
||||
const QString &endpoint,
|
||||
const DBALPayload &body,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
QUrl url(m_baseUrl + endpoint);
|
||||
QNetworkRequest request(url);
|
||||
|
||||
|
||||
// Set headers
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setHeader(
|
||||
QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("X-Tenant-ID", m_tenantId.toUtf8());
|
||||
|
||||
|
||||
if (!m_authToken.isEmpty()) {
|
||||
request.setRawHeader("Authorization", ("Bearer " + m_authToken).toUtf8());
|
||||
request.setRawHeader(
|
||||
"Authorization",
|
||||
("Bearer " + m_authToken).toUtf8());
|
||||
}
|
||||
|
||||
|
||||
QNetworkReply *reply = nullptr;
|
||||
QByteArray jsonData = QJsonDocument(body).toJson(QJsonDocument::Compact);
|
||||
|
||||
QByteArray jsonData =
|
||||
QJsonDocument(body).toJson(QJsonDocument::Compact);
|
||||
|
||||
if (method == "GET") {
|
||||
reply = m_networkManager->get(request);
|
||||
} else if (method == "POST") {
|
||||
@@ -97,7 +107,7 @@ void DBALClient::sendRequest(const QString &method, const QString &endpoint,
|
||||
} else if (method == "DELETE") {
|
||||
reply = m_networkManager->deleteResource(request);
|
||||
}
|
||||
|
||||
|
||||
if (reply && callback.isCallable()) {
|
||||
m_pendingCallbacks[reply] = callback;
|
||||
}
|
||||
@@ -106,12 +116,12 @@ void DBALClient::sendRequest(const QString &method, const QString &endpoint,
|
||||
void DBALClient::handleNetworkReply(QNetworkReply *reply)
|
||||
{
|
||||
reply->deleteLater();
|
||||
|
||||
QJSValue callback = m_pendingCallbacks.take(reply);
|
||||
|
||||
|
||||
DBALCallback callback = m_pendingCallbacks.take(reply);
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
setError(reply->errorString());
|
||||
|
||||
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args;
|
||||
args << QJSValue::NullValue;
|
||||
@@ -120,14 +130,14 @@ void DBALClient::handleNetworkReply(QNetworkReply *reply)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||
|
||||
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args;
|
||||
// In Qt6, QJSValue::engine() is removed. Pass data as JSON string
|
||||
// and let QML parse it, or pass QVariant directly.
|
||||
// In Qt6, QJSValue::engine() is removed. Pass data as
|
||||
// a JSON string and let QML parse it, or pass QVariant.
|
||||
if (doc.isObject()) {
|
||||
args << QJSValue(QString::fromUtf8(data));
|
||||
} else if (doc.isArray()) {
|
||||
@@ -137,7 +147,7 @@ void DBALClient::handleNetworkReply(QNetworkReply *reply)
|
||||
}
|
||||
callback.call(args);
|
||||
}
|
||||
|
||||
|
||||
// Update connected status
|
||||
if (!m_connected) {
|
||||
m_connected = true;
|
||||
@@ -145,89 +155,133 @@ void DBALClient::handleNetworkReply(QNetworkReply *reply)
|
||||
}
|
||||
}
|
||||
|
||||
// CRUD Operations — DBAL REST API: /api/v1/{tenant}/{package}/{entity}[/{id}]
|
||||
// CRUD Operations
|
||||
// REST path: /api/v1/{tenant}/{package}/{entity}[/{id}]
|
||||
|
||||
void DBALClient::create(const QString &entity, const QJsonObject &data, const QJSValue &callback)
|
||||
void DBALClient::create(
|
||||
const QString &entity,
|
||||
const DBALPayload &data,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("POST", entityPath(entity), data, callback);
|
||||
}
|
||||
|
||||
void DBALClient::read(const QString &entity, const QString &id, const QJSValue &callback)
|
||||
void DBALClient::read(
|
||||
const QString &entity,
|
||||
const QString &id,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("GET", entityPath(entity, id), QJsonObject(), callback);
|
||||
sendRequest("GET", entityPath(entity, id), DBALPayload(), callback);
|
||||
}
|
||||
|
||||
void DBALClient::update(const QString &entity, const QString &id,
|
||||
const QJsonObject &data, const QJSValue &callback)
|
||||
void DBALClient::update(
|
||||
const QString &entity,
|
||||
const QString &id,
|
||||
const DBALPayload &data,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("PUT", entityPath(entity, id), data, callback);
|
||||
}
|
||||
|
||||
void DBALClient::remove(const QString &entity, const QString &id, const QJSValue &callback)
|
||||
void DBALClient::remove(
|
||||
const QString &entity,
|
||||
const QString &id,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("DELETE", entityPath(entity, id), QJsonObject(), callback);
|
||||
sendRequest(
|
||||
"DELETE", entityPath(entity, id), DBALPayload(), callback);
|
||||
}
|
||||
|
||||
void DBALClient::list(const QString &entity, const QJsonObject &options, const QJSValue &callback)
|
||||
void DBALClient::list(
|
||||
const QString &entity,
|
||||
const DBALPayload &options,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
// Build query string from options (take, skip, where, orderBy)
|
||||
QString path = entityPath(entity);
|
||||
QStringList queryParts;
|
||||
if (options.contains("take")) queryParts << "take=" + QString::number(options["take"].toInt());
|
||||
if (options.contains("skip")) queryParts << "skip=" + QString::number(options["skip"].toInt());
|
||||
if (options.contains("orderBy")) queryParts << "orderBy=" + options["orderBy"].toString();
|
||||
if (!queryParts.isEmpty()) path += "?" + queryParts.join("&");
|
||||
if (options.contains("take"))
|
||||
queryParts << "take="
|
||||
+ QString::number(options["take"].toInt());
|
||||
if (options.contains("skip"))
|
||||
queryParts << "skip="
|
||||
+ QString::number(options["skip"].toInt());
|
||||
if (options.contains("orderBy"))
|
||||
queryParts << "orderBy=" + options["orderBy"].toString();
|
||||
if (!queryParts.isEmpty())
|
||||
path += "?" + queryParts.join("&");
|
||||
|
||||
sendRequest("GET", path, QJsonObject(), callback);
|
||||
sendRequest("GET", path, DBALPayload(), callback);
|
||||
}
|
||||
|
||||
void DBALClient::findFirst(const QString &entity, const QJsonObject &filter, const QJSValue &callback)
|
||||
void DBALClient::findFirst(
|
||||
const QString &entity,
|
||||
const DBALPayload &filter,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
// GET with query params for simple filters
|
||||
QString path = entityPath(entity);
|
||||
QStringList queryParts;
|
||||
queryParts << "take=1";
|
||||
for (auto it = filter.begin(); it != filter.end(); ++it) {
|
||||
queryParts << QUrl::toPercentEncoding(it.key()) + "=" + QUrl::toPercentEncoding(it.value().toString());
|
||||
queryParts
|
||||
<< QUrl::toPercentEncoding(it.key())
|
||||
+ "="
|
||||
+ QUrl::toPercentEncoding(it.value().toString());
|
||||
}
|
||||
if (!queryParts.isEmpty()) path += "?" + queryParts.join("&");
|
||||
if (!queryParts.isEmpty())
|
||||
path += "?" + queryParts.join("&");
|
||||
|
||||
sendRequest("GET", path, QJsonObject(), callback);
|
||||
sendRequest("GET", path, DBALPayload(), callback);
|
||||
}
|
||||
|
||||
void DBALClient::execute(const QString &operation, const QJsonObject ¶ms, const QJSValue &callback)
|
||||
void DBALClient::execute(
|
||||
const QString &operation,
|
||||
const DBALPayload ¶ms,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
// Execute maps to POST /{tenant}/{package}/{entity}/{action} or system endpoints
|
||||
QString path = QString("/api/v1/%1/%2").arg(m_tenantId, operation);
|
||||
// POST to /{tenant}/{operation} (or /{entity}/{action})
|
||||
QString path =
|
||||
QString("/api/v1/%1/%2").arg(m_tenantId, operation);
|
||||
sendRequest("POST", path, params, callback);
|
||||
}
|
||||
|
||||
void DBALClient::ping()
|
||||
{
|
||||
sendRequest("GET", "/health", QJsonObject(), QJSValue());
|
||||
sendRequest("GET", "/health", DBALPayload(), DBALCallback());
|
||||
}
|
||||
|
||||
void DBALClient::health(const QJSValue &callback)
|
||||
void DBALClient::health(const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("GET", "/health", QJsonObject(), callback);
|
||||
sendRequest("GET", "/health", DBALPayload(), callback);
|
||||
}
|
||||
|
||||
void DBALClient::version(const QJSValue &callback)
|
||||
void DBALClient::version(const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("GET", "/version", QJsonObject(), callback);
|
||||
sendRequest("GET", "/version", DBALPayload(), callback);
|
||||
}
|
||||
|
||||
void DBALClient::status(const QJSValue &callback)
|
||||
void DBALClient::status(const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("GET", "/status", QJsonObject(), callback);
|
||||
sendRequest("GET", "/status", DBALPayload(), callback);
|
||||
}
|
||||
|
||||
void DBALClient::listSchemas(const QJSValue &callback)
|
||||
void DBALClient::listSchemas(const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("GET", "/api/v1/" + m_tenantId + "/schema", QJsonObject(), callback);
|
||||
sendRequest(
|
||||
"GET",
|
||||
"/api/v1/" + m_tenantId + "/schema",
|
||||
DBALPayload(),
|
||||
callback);
|
||||
}
|
||||
|
||||
void DBALClient::getSchema(const QString &entity, const QJSValue &callback)
|
||||
void DBALClient::getSchema(
|
||||
const QString &entity,
|
||||
const DBALCallback &callback)
|
||||
{
|
||||
sendRequest("GET", "/api/v1/" + m_tenantId + "/schema/" + entity.toLower(), QJsonObject(), callback);
|
||||
sendRequest(
|
||||
"GET",
|
||||
"/api/v1/" + m_tenantId + "/schema/" + entity.toLower(),
|
||||
DBALPayload(),
|
||||
callback);
|
||||
}
|
||||
|
||||
@@ -1,58 +1,81 @@
|
||||
#ifndef DBALCLIENT_H
|
||||
#define DBALCLIENT_H
|
||||
#pragma once
|
||||
|
||||
// Qt6 DBAL Client Bridge
|
||||
//
|
||||
// Provides database access for QML components through the DBAL
|
||||
// daemon. Communicates via HTTP to the C++ DBAL backend.
|
||||
//
|
||||
// Usage in QML:
|
||||
// DBALClient {
|
||||
// id: dbal
|
||||
// baseUrl: "http://localhost:3001/api/dbal"
|
||||
// tenantId: "default"
|
||||
// Component.onCompleted: {
|
||||
// dbal.list("User", { take: 10 }, function(users) {
|
||||
// console.log("Users:", JSON.stringify(users))
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
#include "DBALTypes.hpp"
|
||||
#include "DBALRequest.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QJSValue>
|
||||
#include <QMap>
|
||||
|
||||
/**
|
||||
* @brief Qt6 DBAL Client Bridge
|
||||
*
|
||||
* Provides database access for QML components through the DBAL daemon.
|
||||
* Communicates via HTTP/WebSocket to the TypeScript or C++ DBAL backend.
|
||||
*
|
||||
* Usage in QML:
|
||||
* @code
|
||||
* DBALClient {
|
||||
* id: dbal
|
||||
* baseUrl: "http://localhost:3001/api/dbal"
|
||||
* tenantId: "default"
|
||||
*
|
||||
* Component.onCompleted: {
|
||||
* dbal.list("User", { take: 10 }, function(users) {
|
||||
* console.log("Users:", JSON.stringify(users))
|
||||
* })
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class DBALClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString baseUrl READ baseUrl WRITE setBaseUrl NOTIFY baseUrlChanged)
|
||||
Q_PROPERTY(QString tenantId READ tenantId WRITE setTenantId NOTIFY tenantIdChanged)
|
||||
Q_PROPERTY(QString packageId READ packageId WRITE setPackageId NOTIFY packageIdChanged)
|
||||
Q_PROPERTY(QString authToken READ authToken WRITE setAuthToken NOTIFY authTokenChanged)
|
||||
Q_PROPERTY(bool connected READ isConnected NOTIFY connectedChanged)
|
||||
Q_PROPERTY(QString lastError READ lastError NOTIFY errorOccurred)
|
||||
|
||||
Q_PROPERTY(
|
||||
QString baseUrl
|
||||
READ baseUrl
|
||||
WRITE setBaseUrl
|
||||
NOTIFY baseUrlChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QString tenantId
|
||||
READ tenantId
|
||||
WRITE setTenantId
|
||||
NOTIFY tenantIdChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QString packageId
|
||||
READ packageId
|
||||
WRITE setPackageId
|
||||
NOTIFY packageIdChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QString authToken
|
||||
READ authToken
|
||||
WRITE setAuthToken
|
||||
NOTIFY authTokenChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
bool connected
|
||||
READ isConnected
|
||||
NOTIFY connectedChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QString lastError
|
||||
READ lastError
|
||||
NOTIFY errorOccurred)
|
||||
|
||||
public:
|
||||
explicit DBALClient(QObject *parent = nullptr);
|
||||
~DBALClient() override;
|
||||
|
||||
// Property getters
|
||||
QString baseUrl() const { return m_baseUrl; }
|
||||
QString tenantId() const { return m_tenantId; }
|
||||
QString packageId() const { return m_packageId; }
|
||||
QString authToken() const { return m_authToken; }
|
||||
bool isConnected() const { return m_connected; }
|
||||
QString lastError() const { return m_lastError; }
|
||||
QString baseUrl() const { return m_baseUrl; }
|
||||
QString tenantId() const { return m_tenantId; }
|
||||
QString packageId() const { return m_packageId; }
|
||||
QString authToken() const { return m_authToken; }
|
||||
bool isConnected() const { return m_connected; }
|
||||
QString lastError() const { return m_lastError; }
|
||||
|
||||
// Property setters
|
||||
void setBaseUrl(const QString &url);
|
||||
@@ -61,98 +84,70 @@ public:
|
||||
void setAuthToken(const QString &token);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Create a new record
|
||||
* @param entity Entity name (e.g., "User", "AuditLog")
|
||||
* @param data Record data as JSON object
|
||||
* @param callback QML callback function(result)
|
||||
*/
|
||||
void create(const QString &entity, const QJsonObject &data, const QJSValue &callback);
|
||||
/// Create a new record. callback: function(result)
|
||||
void create(
|
||||
const QString &entity,
|
||||
const DBALPayload &data,
|
||||
const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Read a single record by ID
|
||||
* @param entity Entity name
|
||||
* @param id Record ID
|
||||
* @param callback QML callback function(result)
|
||||
*/
|
||||
void read(const QString &entity, const QString &id, const QJSValue &callback);
|
||||
/// Read a single record by ID. callback: function(result)
|
||||
void read(
|
||||
const QString &entity,
|
||||
const QString &id,
|
||||
const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Update an existing record
|
||||
* @param entity Entity name
|
||||
* @param id Record ID
|
||||
* @param data Updated fields
|
||||
* @param callback QML callback function(result)
|
||||
*/
|
||||
void update(const QString &entity, const QString &id, const QJsonObject &data, const QJSValue &callback);
|
||||
/// Update an existing record. callback: function(result)
|
||||
void update(
|
||||
const QString &entity,
|
||||
const QString &id,
|
||||
const DBALPayload &data,
|
||||
const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Delete a record
|
||||
* @param entity Entity name
|
||||
* @param id Record ID
|
||||
* @param callback QML callback function(success)
|
||||
*/
|
||||
void remove(const QString &entity, const QString &id, const QJSValue &callback);
|
||||
/// Delete a record. callback: function(success)
|
||||
void remove(
|
||||
const QString &entity,
|
||||
const QString &id,
|
||||
const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief List records with pagination and filtering
|
||||
* @param entity Entity name
|
||||
* @param options { take, skip, where, orderBy }
|
||||
* @param callback QML callback function({ items, total })
|
||||
*/
|
||||
void list(const QString &entity, const QJsonObject &options, const QJSValue &callback);
|
||||
/// List records with pagination/filtering.
|
||||
/// options: { take, skip, where, orderBy }
|
||||
/// callback: function({ items, total })
|
||||
void list(
|
||||
const QString &entity,
|
||||
const DBALPayload &options,
|
||||
const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Find first record matching filter
|
||||
* @param entity Entity name
|
||||
* @param filter Filter criteria
|
||||
* @param callback QML callback function(result)
|
||||
*/
|
||||
void findFirst(const QString &entity, const QJsonObject &filter, const QJSValue &callback);
|
||||
/// Find first record matching filter. callback: function(result)
|
||||
void findFirst(
|
||||
const QString &entity,
|
||||
const DBALPayload &filter,
|
||||
const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Execute a named query/operation
|
||||
* @param operation Operation name
|
||||
* @param params Operation parameters
|
||||
* @param callback QML callback function(result)
|
||||
*/
|
||||
void execute(const QString &operation, const QJsonObject ¶ms, const QJSValue &callback);
|
||||
/// Execute a named operation. callback: function(result)
|
||||
void execute(
|
||||
const QString &operation,
|
||||
const DBALPayload ¶ms,
|
||||
const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Check connection to DBAL backend
|
||||
*/
|
||||
/// Check connection to DBAL backend (fire-and-forget).
|
||||
void ping();
|
||||
|
||||
/**
|
||||
* @brief Get DBAL health information
|
||||
* @param callback QML callback function(result)
|
||||
*/
|
||||
void health(const QJSValue &callback);
|
||||
/// Get DBAL health info. callback: function(result)
|
||||
void health(const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Get DBAL version information
|
||||
* @param callback QML callback function(result)
|
||||
*/
|
||||
void version(const QJSValue &callback);
|
||||
/// Get DBAL version info. callback: function(result)
|
||||
void version(const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Get DBAL status/metrics
|
||||
* @param callback QML callback function(result)
|
||||
*/
|
||||
void status(const QJSValue &callback);
|
||||
/// Get DBAL status/metrics. callback: function(result)
|
||||
void status(const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief List entity schemas from DBAL
|
||||
* @param callback QML callback function(schemas)
|
||||
*/
|
||||
void listSchemas(const QJSValue &callback);
|
||||
/// List entity schemas. callback: function(schemas)
|
||||
void listSchemas(const DBALCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Get a specific entity schema
|
||||
* @param entity Entity name
|
||||
* @param callback QML callback function(schema)
|
||||
*/
|
||||
void getSchema(const QString &entity, const QJSValue &callback);
|
||||
/// Get a specific entity schema. callback: function(schema)
|
||||
void getSchema(
|
||||
const QString &entity,
|
||||
const DBALCallback &callback);
|
||||
|
||||
signals:
|
||||
void baseUrlChanged();
|
||||
@@ -161,27 +156,33 @@ signals:
|
||||
void authTokenChanged();
|
||||
void connectedChanged();
|
||||
void errorOccurred(const QString &error);
|
||||
void operationCompleted(const QString &operation, const QJsonObject &result);
|
||||
void operationCompleted(
|
||||
const QString &operation,
|
||||
const DBALPayload &result);
|
||||
|
||||
private slots:
|
||||
void handleNetworkReply(QNetworkReply *reply);
|
||||
|
||||
private:
|
||||
void sendRequest(const QString &method, const QString &endpoint,
|
||||
const QJsonObject &body, const QJSValue &callback);
|
||||
void sendRequest(
|
||||
const QString &method,
|
||||
const QString &endpoint,
|
||||
const DBALPayload &body,
|
||||
const DBALCallback &callback);
|
||||
|
||||
void setError(const QString &error);
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QString m_baseUrl;
|
||||
QString m_tenantId;
|
||||
QString m_packageId;
|
||||
QString m_authToken;
|
||||
|
||||
QString entityPath(const QString &entity) const;
|
||||
QString entityPath(const QString &entity, const QString &id) const;
|
||||
bool m_connected;
|
||||
QString m_lastError;
|
||||
QMap<QNetworkReply*, QJSValue> m_pendingCallbacks;
|
||||
};
|
||||
QString entityPath(
|
||||
const QString &entity,
|
||||
const QString &id) const;
|
||||
|
||||
#endif // DBALCLIENT_H
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QString m_baseUrl;
|
||||
QString m_tenantId;
|
||||
QString m_packageId;
|
||||
QString m_authToken;
|
||||
bool m_connected;
|
||||
QString m_lastError;
|
||||
QMap<QNetworkReply*, DBALCallback> m_pendingCallbacks;
|
||||
};
|
||||
|
||||
28
frontends/qt6/src/DBALRequest.hpp
Normal file
28
frontends/qt6/src/DBALRequest.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
// DBALRequest encapsulates a single in-flight DBAL HTTP request.
|
||||
//
|
||||
// DBALClient stores one DBALRequest per QNetworkReply* in its
|
||||
// pending-request map. Using a named struct instead of a bare
|
||||
// QJSValue map makes the bookkeeping self-documenting and easy
|
||||
// to extend (e.g. adding timeout tracking or retry counts).
|
||||
|
||||
#include "DBALTypes.hpp"
|
||||
#include <QNetworkReply>
|
||||
#include <QString>
|
||||
|
||||
/// Tracks the state of a single pending DBAL network request.
|
||||
struct DBALRequest
|
||||
{
|
||||
/// The entity name the request targets (e.g. "User").
|
||||
QString entity;
|
||||
|
||||
/// The HTTP verb used for this request ("GET", "POST", …).
|
||||
QString method;
|
||||
|
||||
/// QML callback to invoke when the reply arrives.
|
||||
DBALCallback callback;
|
||||
|
||||
/// The live network reply (used as the map key in DBALClient).
|
||||
QNetworkReply *reply = nullptr;
|
||||
};
|
||||
17
frontends/qt6/src/DBALTypes.hpp
Normal file
17
frontends/qt6/src/DBALTypes.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
// Forward declarations and type aliases for the Qt6 DBAL layer.
|
||||
//
|
||||
// All DBAL public APIs use QJSValue for QML-callable callbacks.
|
||||
// DBALCallback is the canonical alias used throughout DBALClient
|
||||
// and DBALRequest to make callback parameters self-documenting.
|
||||
|
||||
#include <QJSValue>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
|
||||
/// Alias for a QML-callable callback passed to DBAL operations.
|
||||
using DBALCallback = QJSValue;
|
||||
|
||||
/// Alias for a JSON body payload sent with DBAL requests.
|
||||
using DBALPayload = QJsonObject;
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "ModPlayer.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
|
||||
// ModPlayer stub - libopenmpt + Qt6 Multimedia not yet configured via Conan.
|
||||
// Once available, this will use QAudioSink (Qt6) + openmpt::module for .mod playback.
|
||||
|
||||
ModPlayer::ModPlayer(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ModPlayer::~ModPlayer() {
|
||||
stop();
|
||||
}
|
||||
|
||||
bool ModPlayer::play(const QString &path) {
|
||||
if (!QFile::exists(path)) {
|
||||
qWarning() << "ModPlayer: file not found:" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
qInfo() << "ModPlayer: would play" << path << "(audio backend not yet linked)";
|
||||
updatePlaying(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModPlayer::stop() {
|
||||
updatePlaying(false);
|
||||
}
|
||||
|
||||
bool ModPlayer::isPlaying() const {
|
||||
return m_playing;
|
||||
}
|
||||
|
||||
void ModPlayer::updatePlaying(bool playing) {
|
||||
if (m_playing == playing)
|
||||
return;
|
||||
m_playing = playing;
|
||||
emit playbackChanged();
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class ModPlayer : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool playing READ isPlaying NOTIFY playbackChanged)
|
||||
|
||||
public:
|
||||
explicit ModPlayer(QObject *parent = nullptr);
|
||||
~ModPlayer() override;
|
||||
|
||||
Q_INVOKABLE bool play(const QString &path);
|
||||
Q_INVOKABLE void stop();
|
||||
|
||||
bool isPlaying() const;
|
||||
|
||||
signals:
|
||||
void playbackChanged();
|
||||
|
||||
private:
|
||||
bool m_playing = false;
|
||||
void updatePlaying(bool playing);
|
||||
};
|
||||
57
frontends/qt6/src/ModPlayer.hpp
Normal file
57
frontends/qt6/src/ModPlayer.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
// ModPlayer stub - libopenmpt + Qt6 Multimedia not yet configured via Conan.
|
||||
// Once available, this will use QAudioSink (Qt6) + openmpt::module
|
||||
// for .mod playback.
|
||||
|
||||
class ModPlayer : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool playing READ isPlaying NOTIFY playbackChanged)
|
||||
|
||||
public:
|
||||
explicit ModPlayer(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
~ModPlayer() override {
|
||||
stop();
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool play(const QString &path) {
|
||||
if (!QFile::exists(path)) {
|
||||
qWarning() << "ModPlayer: file not found:" << path;
|
||||
return false;
|
||||
}
|
||||
qInfo() << "ModPlayer: would play" << path
|
||||
<< "(audio backend not yet linked)";
|
||||
updatePlaying(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void stop() {
|
||||
updatePlaying(false);
|
||||
}
|
||||
|
||||
bool isPlaying() const {
|
||||
return m_playing;
|
||||
}
|
||||
|
||||
signals:
|
||||
void playbackChanged();
|
||||
|
||||
private:
|
||||
bool m_playing = false;
|
||||
|
||||
void updatePlaying(bool playing) {
|
||||
if (m_playing == playing)
|
||||
return;
|
||||
m_playing = playing;
|
||||
emit playbackChanged();
|
||||
}
|
||||
};
|
||||
@@ -1,111 +0,0 @@
|
||||
#include "NodeRegistry.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QDebug>
|
||||
#include <algorithm>
|
||||
|
||||
NodeRegistry::NodeRegistry(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QVariantList NodeRegistry::nodeTypes() const
|
||||
{
|
||||
QVariantList list;
|
||||
for (auto it = m_nodeTypes.constBegin(); it != m_nodeTypes.constEnd(); ++it) {
|
||||
list.append(it.value().toVariantMap());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void NodeRegistry::loadRegistry(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
m_lastError = QStringLiteral("Cannot open registry file: ") + path;
|
||||
qWarning() << "NodeRegistry:" << m_lastError;
|
||||
emit errorOccurred(m_lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError parseErr;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &parseErr);
|
||||
file.close();
|
||||
|
||||
if (parseErr.error != QJsonParseError::NoError) {
|
||||
m_lastError = QStringLiteral("JSON parse error: ") + parseErr.errorString();
|
||||
qWarning() << "NodeRegistry:" << m_lastError;
|
||||
emit errorOccurred(m_lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
parseRegistry(doc.object());
|
||||
|
||||
m_loaded = true;
|
||||
emit loadedChanged();
|
||||
emit nodeTypesChanged();
|
||||
qDebug() << "NodeRegistry: loaded" << m_nodeTypes.count() << "node types in"
|
||||
<< m_groups.count() << "groups from" << path;
|
||||
}
|
||||
|
||||
void NodeRegistry::parseRegistry(const QJsonObject &root)
|
||||
{
|
||||
m_nodeTypes.clear();
|
||||
m_groups.clear();
|
||||
|
||||
const QJsonArray types = root.value(QStringLiteral("nodeTypes")).toArray();
|
||||
QSet<QString> groupSet;
|
||||
|
||||
for (const QJsonValue &val : types) {
|
||||
const QJsonObject obj = val.toObject();
|
||||
const QString name = obj.value(QStringLiteral("name")).toString();
|
||||
if (name.isEmpty())
|
||||
continue;
|
||||
|
||||
m_nodeTypes.insert(name, obj);
|
||||
|
||||
const QString group = obj.value(QStringLiteral("group")).toString();
|
||||
if (!group.isEmpty())
|
||||
groupSet.insert(group);
|
||||
}
|
||||
|
||||
m_groups = groupSet.values();
|
||||
m_groups.sort();
|
||||
}
|
||||
|
||||
QVariantMap NodeRegistry::nodeType(const QString &name) const
|
||||
{
|
||||
if (!m_nodeTypes.contains(name))
|
||||
return {};
|
||||
return m_nodeTypes.value(name).toVariantMap();
|
||||
}
|
||||
|
||||
QVariantList NodeRegistry::nodesByGroup(const QString &group) const
|
||||
{
|
||||
QVariantList list;
|
||||
for (auto it = m_nodeTypes.constBegin(); it != m_nodeTypes.constEnd(); ++it) {
|
||||
if (it.value().value(QStringLiteral("group")).toString() == group)
|
||||
list.append(it.value().toVariantMap());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
QVariantList NodeRegistry::searchNodes(const QString &query) const
|
||||
{
|
||||
if (query.isEmpty())
|
||||
return nodeTypes();
|
||||
|
||||
const QString lower = query.toLower();
|
||||
QVariantList list;
|
||||
for (auto it = m_nodeTypes.constBegin(); it != m_nodeTypes.constEnd(); ++it) {
|
||||
const QJsonObject &obj = it.value();
|
||||
const QString name = obj.value(QStringLiteral("name")).toString().toLower();
|
||||
const QString displayName = obj.value(QStringLiteral("displayName")).toString().toLower();
|
||||
const QString desc = obj.value(QStringLiteral("description")).toString().toLower();
|
||||
|
||||
if (name.contains(lower) || displayName.contains(lower) || desc.contains(lower))
|
||||
list.append(obj.toVariantMap());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#ifndef NODEREGISTRY_H
|
||||
#define NODEREGISTRY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QMap>
|
||||
#include <QVariantList>
|
||||
|
||||
class NodeRegistry : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVariantList nodeTypes READ nodeTypes NOTIFY nodeTypesChanged)
|
||||
Q_PROPERTY(QStringList groups READ groups NOTIFY nodeTypesChanged)
|
||||
Q_PROPERTY(int nodeCount READ nodeCount NOTIFY nodeTypesChanged)
|
||||
Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged)
|
||||
Q_PROPERTY(QString lastError READ lastError NOTIFY errorOccurred)
|
||||
|
||||
public:
|
||||
explicit NodeRegistry(QObject *parent = nullptr);
|
||||
|
||||
QVariantList nodeTypes() const;
|
||||
QStringList groups() const { return m_groups; }
|
||||
int nodeCount() const { return m_nodeTypes.count(); }
|
||||
bool isLoaded() const { return m_loaded; }
|
||||
QString lastError() const { return m_lastError; }
|
||||
|
||||
public slots:
|
||||
void loadRegistry(const QString &path);
|
||||
QVariantMap nodeType(const QString &name) const;
|
||||
QVariantList nodesByGroup(const QString &group) const;
|
||||
QVariantList searchNodes(const QString &query) const;
|
||||
|
||||
signals:
|
||||
void nodeTypesChanged();
|
||||
void loadedChanged();
|
||||
void errorOccurred(const QString &error);
|
||||
|
||||
private:
|
||||
void parseRegistry(const QJsonObject &root);
|
||||
|
||||
QMap<QString, QJsonObject> m_nodeTypes;
|
||||
QStringList m_groups;
|
||||
bool m_loaded = false;
|
||||
QString m_lastError;
|
||||
};
|
||||
|
||||
#endif
|
||||
185
frontends/qt6/src/NodeRegistry.hpp
Normal file
185
frontends/qt6/src/NodeRegistry.hpp
Normal file
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QMap>
|
||||
#include <QVariantList>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QDebug>
|
||||
#include <algorithm>
|
||||
|
||||
class NodeRegistry : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(
|
||||
QVariantList nodeTypes
|
||||
READ nodeTypes
|
||||
NOTIFY nodeTypesChanged)
|
||||
Q_PROPERTY(
|
||||
QStringList groups
|
||||
READ groups
|
||||
NOTIFY nodeTypesChanged)
|
||||
Q_PROPERTY(
|
||||
int nodeCount
|
||||
READ nodeCount
|
||||
NOTIFY nodeTypesChanged)
|
||||
Q_PROPERTY(
|
||||
bool loaded
|
||||
READ isLoaded
|
||||
NOTIFY loadedChanged)
|
||||
Q_PROPERTY(
|
||||
QString lastError
|
||||
READ lastError
|
||||
NOTIFY errorOccurred)
|
||||
|
||||
public:
|
||||
explicit NodeRegistry(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
QVariantList nodeTypes() const
|
||||
{
|
||||
QVariantList list;
|
||||
for (auto it = m_nodeTypes.constBegin();
|
||||
it != m_nodeTypes.constEnd(); ++it) {
|
||||
list.append(it.value().toVariantMap());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
QStringList groups() const { return m_groups; }
|
||||
int nodeCount() const { return m_nodeTypes.count(); }
|
||||
bool isLoaded() const { return m_loaded; }
|
||||
QString lastError() const { return m_lastError; }
|
||||
|
||||
public slots:
|
||||
void loadRegistry(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
m_lastError =
|
||||
QStringLiteral("Cannot open registry file: ") + path;
|
||||
qWarning() << "NodeRegistry:" << m_lastError;
|
||||
emit errorOccurred(m_lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError parseErr;
|
||||
const QJsonDocument doc =
|
||||
QJsonDocument::fromJson(file.readAll(), &parseErr);
|
||||
file.close();
|
||||
|
||||
if (parseErr.error != QJsonParseError::NoError) {
|
||||
m_lastError =
|
||||
QStringLiteral("JSON parse error: ")
|
||||
+ parseErr.errorString();
|
||||
qWarning() << "NodeRegistry:" << m_lastError;
|
||||
emit errorOccurred(m_lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
parseRegistry(doc.object());
|
||||
|
||||
m_loaded = true;
|
||||
emit loadedChanged();
|
||||
emit nodeTypesChanged();
|
||||
qDebug() << "NodeRegistry: loaded"
|
||||
<< m_nodeTypes.count()
|
||||
<< "node types in"
|
||||
<< m_groups.count()
|
||||
<< "groups from" << path;
|
||||
}
|
||||
|
||||
QVariantMap nodeType(const QString &name) const
|
||||
{
|
||||
if (!m_nodeTypes.contains(name))
|
||||
return {};
|
||||
return m_nodeTypes.value(name).toVariantMap();
|
||||
}
|
||||
|
||||
QVariantList nodesByGroup(const QString &group) const
|
||||
{
|
||||
QVariantList list;
|
||||
for (auto it = m_nodeTypes.constBegin();
|
||||
it != m_nodeTypes.constEnd(); ++it) {
|
||||
const QString g =
|
||||
it.value()
|
||||
.value(QStringLiteral("group"))
|
||||
.toString();
|
||||
if (g == group)
|
||||
list.append(it.value().toVariantMap());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
QVariantList searchNodes(const QString &query) const
|
||||
{
|
||||
if (query.isEmpty())
|
||||
return nodeTypes();
|
||||
|
||||
const QString lower = query.toLower();
|
||||
QVariantList list;
|
||||
for (auto it = m_nodeTypes.constBegin();
|
||||
it != m_nodeTypes.constEnd(); ++it) {
|
||||
const QJsonObject &obj = it.value();
|
||||
const QString name =
|
||||
obj.value(QStringLiteral("name"))
|
||||
.toString().toLower();
|
||||
const QString displayName =
|
||||
obj.value(QStringLiteral("displayName"))
|
||||
.toString().toLower();
|
||||
const QString desc =
|
||||
obj.value(QStringLiteral("description"))
|
||||
.toString().toLower();
|
||||
|
||||
if (name.contains(lower)
|
||||
|| displayName.contains(lower)
|
||||
|| desc.contains(lower))
|
||||
list.append(obj.toVariantMap());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
signals:
|
||||
void nodeTypesChanged();
|
||||
void loadedChanged();
|
||||
void errorOccurred(const QString &error);
|
||||
|
||||
private:
|
||||
void parseRegistry(const QJsonObject &root)
|
||||
{
|
||||
m_nodeTypes.clear();
|
||||
m_groups.clear();
|
||||
|
||||
const QJsonArray types =
|
||||
root.value(QStringLiteral("nodeTypes")).toArray();
|
||||
QSet<QString> groupSet;
|
||||
|
||||
for (const QJsonValue &val : types) {
|
||||
const QJsonObject obj = val.toObject();
|
||||
const QString name =
|
||||
obj.value(QStringLiteral("name")).toString();
|
||||
if (name.isEmpty())
|
||||
continue;
|
||||
|
||||
m_nodeTypes.insert(name, obj);
|
||||
|
||||
const QString group =
|
||||
obj.value(QStringLiteral("group")).toString();
|
||||
if (!group.isEmpty())
|
||||
groupSet.insert(group);
|
||||
}
|
||||
|
||||
m_groups = groupSet.values();
|
||||
m_groups.sort();
|
||||
}
|
||||
|
||||
QMap<QString, QJsonObject> m_nodeTypes;
|
||||
QStringList m_groups;
|
||||
bool m_loaded = false;
|
||||
QString m_lastError;
|
||||
};
|
||||
@@ -1,103 +0,0 @@
|
||||
#include "PackageRegistry.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSet>
|
||||
#include <QTextStream>
|
||||
|
||||
namespace {
|
||||
QString normalizedPath(const QString &path) {
|
||||
QDir dir(path);
|
||||
return dir.absolutePath();
|
||||
}
|
||||
|
||||
QString metadataFileName(const QString &packageId) {
|
||||
return packageId + "/metadata.json";
|
||||
}
|
||||
}
|
||||
|
||||
PackageRegistry::PackageRegistry(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
const auto appDir = QCoreApplication::applicationDirPath();
|
||||
m_roots << normalizedPath(appDir + "/packages");
|
||||
m_roots << normalizedPath(appDir + "/../packages");
|
||||
m_roots << normalizedPath(appDir + "/../frontends/qt6/packages");
|
||||
m_roots << normalizedPath(appDir + "/../../frontends/qt6/packages");
|
||||
}
|
||||
|
||||
QStringList PackageRegistry::packageIds() const
|
||||
{
|
||||
QSet<QString> ids;
|
||||
for (const auto &root : m_roots) {
|
||||
QDir dir(root);
|
||||
if (!dir.exists())
|
||||
continue;
|
||||
|
||||
const auto entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
for (const auto &entry : entries) {
|
||||
const auto meta = dir.filePath(metadataFileName(entry));
|
||||
if (QFile::exists(meta))
|
||||
ids.insert(entry);
|
||||
}
|
||||
}
|
||||
return ids.values();
|
||||
}
|
||||
|
||||
QString PackageRegistry::loadedPackage() const
|
||||
{
|
||||
return m_loadedPackage;
|
||||
}
|
||||
|
||||
QVariantMap PackageRegistry::loadedMetadata() const
|
||||
{
|
||||
return m_loadedMetadata;
|
||||
}
|
||||
|
||||
QVariantMap PackageRegistry::metadata(const QString &packageId) const
|
||||
{
|
||||
const auto filePath = findMetadataFile(packageId);
|
||||
if (filePath.isEmpty())
|
||||
return {};
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return {};
|
||||
const auto doc = QJsonDocument::fromJson(file.readAll());
|
||||
if (!doc.isObject())
|
||||
return {};
|
||||
return doc.object().toVariantMap();
|
||||
}
|
||||
|
||||
bool PackageRegistry::loadPackage(const QString &packageId)
|
||||
{
|
||||
const auto filePath = findMetadataFile(packageId);
|
||||
if (filePath.isEmpty())
|
||||
return false;
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
|
||||
const auto doc = QJsonDocument::fromJson(file.readAll());
|
||||
if (!doc.isObject())
|
||||
return false;
|
||||
|
||||
m_loadedPackage = packageId;
|
||||
m_loadedMetadata = doc.object().toVariantMap();
|
||||
emit packageLoaded();
|
||||
emit metadataChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString PackageRegistry::findMetadataFile(const QString &packageId) const
|
||||
{
|
||||
for (const auto &root : m_roots) {
|
||||
const auto candidate = QDir(root).filePath(metadataFileName(packageId));
|
||||
if (QFile::exists(candidate))
|
||||
return candidate;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
|
||||
class PackageRegistry : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QStringList packageIds READ packageIds NOTIFY packagesChanged)
|
||||
Q_PROPERTY(QString loadedPackage READ loadedPackage NOTIFY packageLoaded)
|
||||
Q_PROPERTY(QVariantMap loadedMetadata READ loadedMetadata NOTIFY metadataChanged)
|
||||
|
||||
public:
|
||||
explicit PackageRegistry(QObject *parent = nullptr);
|
||||
|
||||
QStringList packageIds() const;
|
||||
QString loadedPackage() const;
|
||||
QVariantMap loadedMetadata() const;
|
||||
|
||||
Q_INVOKABLE bool loadPackage(const QString &packageId);
|
||||
Q_INVOKABLE QVariantMap metadata(const QString &packageId) const;
|
||||
|
||||
signals:
|
||||
void packagesChanged();
|
||||
void packageLoaded();
|
||||
void metadataChanged();
|
||||
|
||||
private:
|
||||
QString findMetadataFile(const QString &packageId) const;
|
||||
QStringList m_roots;
|
||||
QString m_loadedPackage;
|
||||
QVariantMap m_loadedMetadata;
|
||||
};
|
||||
132
frontends/qt6/src/PackageRegistry.hpp
Normal file
132
frontends/qt6/src/PackageRegistry.hpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QTextStream>
|
||||
#include <QVariantMap>
|
||||
|
||||
namespace {
|
||||
inline QString normalizedPath(const QString &path) {
|
||||
QDir dir(path);
|
||||
return dir.absolutePath();
|
||||
}
|
||||
|
||||
inline QString metadataFileName(const QString &packageId) {
|
||||
return packageId + "/metadata.json";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class PackageRegistry : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(
|
||||
QStringList packageIds
|
||||
READ packageIds
|
||||
NOTIFY packagesChanged
|
||||
)
|
||||
Q_PROPERTY(
|
||||
QString loadedPackage
|
||||
READ loadedPackage
|
||||
NOTIFY packageLoaded
|
||||
)
|
||||
Q_PROPERTY(
|
||||
QVariantMap loadedMetadata
|
||||
READ loadedMetadata
|
||||
NOTIFY metadataChanged
|
||||
)
|
||||
|
||||
public:
|
||||
explicit PackageRegistry(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
const auto appDir = QCoreApplication::applicationDirPath();
|
||||
m_roots << normalizedPath(appDir + "/packages");
|
||||
m_roots << normalizedPath(appDir + "/../packages");
|
||||
m_roots << normalizedPath(
|
||||
appDir + "/../frontends/qt6/packages"
|
||||
);
|
||||
m_roots << normalizedPath(
|
||||
appDir + "/../../frontends/qt6/packages"
|
||||
);
|
||||
}
|
||||
|
||||
QStringList packageIds() const {
|
||||
QSet<QString> ids;
|
||||
for (const auto &root : m_roots) {
|
||||
QDir dir(root);
|
||||
if (!dir.exists())
|
||||
continue;
|
||||
const auto entries =
|
||||
dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
for (const auto &entry : entries) {
|
||||
const auto meta =
|
||||
dir.filePath(metadataFileName(entry));
|
||||
if (QFile::exists(meta))
|
||||
ids.insert(entry);
|
||||
}
|
||||
}
|
||||
return ids.values();
|
||||
}
|
||||
|
||||
QString loadedPackage() const {
|
||||
return m_loadedPackage;
|
||||
}
|
||||
|
||||
QVariantMap loadedMetadata() const {
|
||||
return m_loadedMetadata;
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool loadPackage(const QString &packageId) {
|
||||
const auto filePath = findMetadataFile(packageId);
|
||||
if (filePath.isEmpty())
|
||||
return false;
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
const auto doc = QJsonDocument::fromJson(file.readAll());
|
||||
if (!doc.isObject())
|
||||
return false;
|
||||
m_loadedPackage = packageId;
|
||||
m_loadedMetadata = doc.object().toVariantMap();
|
||||
emit packageLoaded();
|
||||
emit metadataChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVariantMap metadata(const QString &packageId) const {
|
||||
const auto filePath = findMetadataFile(packageId);
|
||||
if (filePath.isEmpty())
|
||||
return {};
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return {};
|
||||
const auto doc = QJsonDocument::fromJson(file.readAll());
|
||||
if (!doc.isObject())
|
||||
return {};
|
||||
return doc.object().toVariantMap();
|
||||
}
|
||||
|
||||
signals:
|
||||
void packagesChanged();
|
||||
void packageLoaded();
|
||||
void metadataChanged();
|
||||
|
||||
private:
|
||||
QStringList m_roots;
|
||||
QString m_loadedPackage;
|
||||
QVariantMap m_loadedMetadata;
|
||||
|
||||
QString findMetadataFile(const QString &packageId) const {
|
||||
for (const auto &root : m_roots) {
|
||||
const auto candidate =
|
||||
QDir(root).filePath(metadataFileName(packageId));
|
||||
if (QFile::exists(candidate))
|
||||
return candidate;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user