Merge pull request #6 from logos-co/feat/download

feat: download
This commit is contained in:
Arnaud 2026-02-16 09:55:31 +04:00 committed by GitHub
commit 4691d8b5df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1447 additions and 183 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ libs
app/build
CMakeLists.txt.user
generated_code
.cache

View File

@ -213,7 +213,7 @@ if(_storage_module_is_source)
# Add custom target to run the cpp generator for the storage module
add_custom_target(run_cpp_generator_storage_module
COMMAND "${CPP_GENERATOR}" "${PLUGIN_FILE}" --module-only --output-dir "${PLUGINS_OUTPUT_DIR}"
COMMAND "${CPP_GENERATOR}" "${PLUGIN_FILE}" --interface "${LOGOS_STORAGE_ROOT}/storage_module_interface.h" --module-only --output-dir "${PLUGINS_OUTPUT_DIR}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Running logos-cpp-generator on ${PLUGIN_FILE} with output-dir ${PLUGINS_OUTPUT_DIR}"
VERBATIM
@ -349,6 +349,8 @@ endif()
if(_cpp_sdk_is_source)
target_include_directories(storage_ui PRIVATE
#${PLUGINS_OUTPUT_DIR}
${LOGOS_CPP_SDK_ROOT}/cpp
${LOGOS_CPP_SDK_ROOT}/cpp/generated
)

175
flake.lock generated
View File

@ -1,32 +1,38 @@
{
"nodes": {
"circom-compat": {
"logos-capability-module": {
"inputs": {
"logos-cpp-sdk": [
"logos-cpp-sdk"
],
"logos-liblogos": "logos-liblogos",
"nixpkgs": [
"logos-storage-module",
"logos-storage",
"logos-capability-module",
"logos-liblogos",
"nixpkgs"
]
},
"locked": {
"lastModified": 1736521871,
"narHash": "sha256-d34XNLg9NGPEOARHW+BIOAWalkHdEUAwsv3mpLZQxds=",
"owner": "logos-storage",
"repo": "circom-compat-ffi",
"rev": "8cd4ed44fdafe59d4ec1184420639cae4c4dbab9",
"lastModified": 1767809111,
"narHash": "sha256-jehjsB+BpDJlVu3I7x+vFVOdXmy9MDmFTJtRqzFUONo=",
"owner": "logos-co",
"repo": "logos-capability-module",
"rev": "7b35383e0aa4e28a4633ed18a87efb57636939b1",
"type": "github"
},
"original": {
"owner": "logos-storage",
"repo": "circom-compat-ffi",
"owner": "logos-co",
"repo": "logos-capability-module",
"type": "github"
}
},
"logos-capability-module": {
"logos-capability-module_2": {
"inputs": {
"logos-cpp-sdk": "logos-cpp-sdk",
"logos-liblogos": "logos-liblogos",
"logos-cpp-sdk": "logos-cpp-sdk_3",
"logos-liblogos": "logos-liblogos_4",
"nixpkgs": [
"logos-storage-module",
"logos-liblogos",
"logos-capability-module",
"logos-liblogos",
"nixpkgs"
@ -68,6 +74,25 @@
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1770978598,
"narHash": "sha256-CR5N5v+y2ZInnBocGgjIeLaS9XqNjAKGXE2abC3E110=",
"owner": "logos-co",
"repo": "logos-cpp-sdk",
"rev": "c3477d29e32cae5f73ca637fb81e547f8a6cba58",
"type": "github"
},
"original": {
"owner": "logos-co",
"ref": "feat/logos-result",
"repo": "logos-cpp-sdk",
"type": "github"
}
},
"logos-cpp-sdk_3": {
"inputs": {
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1761230734,
"narHash": "sha256-CMRUwXH7pJZ1OI6bd/TDDDXKqQ1tQZHQEOOwK8TgYHI=",
@ -82,34 +107,16 @@
"type": "github"
}
},
"logos-cpp-sdk_3": {
"inputs": {
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1767724329,
"narHash": "sha256-UPkqxqxbKwU5Dmu00TnjiJVXUmfVylF3p1qziEuYwIE=",
"owner": "logos-co",
"repo": "logos-cpp-sdk",
"rev": "32f1d7080d784ff044d91d076ef2f0c7305d4784",
"type": "github"
},
"original": {
"owner": "logos-co",
"repo": "logos-cpp-sdk",
"type": "github"
}
},
"logos-cpp-sdk_4": {
"inputs": {
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1764699992,
"narHash": "sha256-nCmK9C9F31cHvy6lWKR5WGl99aJbS3kIsROAoZ4OrwI=",
"lastModified": 1761230734,
"narHash": "sha256-CMRUwXH7pJZ1OI6bd/TDDDXKqQ1tQZHQEOOwK8TgYHI=",
"owner": "logos-co",
"repo": "logos-cpp-sdk",
"rev": "5d0bbd0d1e00aad0532ffa7c8bf2c80f460a4f6d",
"rev": "4b143922c190df00bb3835441c9f0075cb28283b",
"type": "github"
},
"original": {
@ -141,11 +148,11 @@
"nixpkgs": "nixpkgs_6"
},
"locked": {
"lastModified": 1764699992,
"narHash": "sha256-nCmK9C9F31cHvy6lWKR5WGl99aJbS3kIsROAoZ4OrwI=",
"lastModified": 1767724329,
"narHash": "sha256-UPkqxqxbKwU5Dmu00TnjiJVXUmfVylF3p1qziEuYwIE=",
"owner": "logos-co",
"repo": "logos-cpp-sdk",
"rev": "5d0bbd0d1e00aad0532ffa7c8bf2c80f460a4f6d",
"rev": "32f1d7080d784ff044d91d076ef2f0c7305d4784",
"type": "github"
},
"original": {
@ -156,7 +163,7 @@
},
"logos-liblogos": {
"inputs": {
"logos-cpp-sdk": "logos-cpp-sdk_2",
"logos-cpp-sdk": "logos-cpp-sdk",
"nixpkgs": [
"logos-capability-module",
"logos-liblogos",
@ -180,7 +187,9 @@
},
"logos-liblogos_2": {
"inputs": {
"logos-cpp-sdk": "logos-cpp-sdk_4",
"logos-cpp-sdk": [
"logos-cpp-sdk"
],
"nixpkgs": [
"logos-liblogos",
"logos-cpp-sdk",
@ -204,7 +213,9 @@
},
"logos-liblogos_3": {
"inputs": {
"logos-cpp-sdk": "logos-cpp-sdk_6",
"logos-capability-module": "logos-capability-module_2",
"logos-cpp-sdk": "logos-cpp-sdk_5",
"logos-module": "logos-module",
"nixpkgs": [
"logos-storage-module",
"logos-liblogos",
@ -213,11 +224,11 @@
]
},
"locked": {
"lastModified": 1767722338,
"narHash": "sha256-89RYfnramvJRufy47Kdx662TMHaFVSpS88ouWff3Csw=",
"lastModified": 1770837874,
"narHash": "sha256-wr75lv1q4U1FS5+l/6ypwzJFJe06l2RyUvx1npoRS88=",
"owner": "logos-co",
"repo": "logos-liblogos",
"rev": "ca1fda7cabd70027e51d86392f5e29b3fff385f9",
"rev": "e3741c01fd3abf6b7bd9ff2fa8edf89c41fc0cea",
"type": "github"
},
"original": {
@ -226,17 +237,67 @@
"type": "github"
}
},
"logos-liblogos_4": {
"inputs": {
"logos-cpp-sdk": "logos-cpp-sdk_4",
"nixpkgs": [
"logos-storage-module",
"logos-liblogos",
"logos-capability-module",
"logos-liblogos",
"logos-cpp-sdk",
"nixpkgs"
]
},
"locked": {
"lastModified": 1761845775,
"narHash": "sha256-ulK8xq05ejK6qIgZ7WtWb/MJt2rk5BKfDA2z7mM3wq8=",
"owner": "logos-co",
"repo": "logos-liblogos",
"rev": "a92c2c1268bc70764c8f73c7bce07d21024f5af9",
"type": "github"
},
"original": {
"owner": "logos-co",
"repo": "logos-liblogos",
"type": "github"
}
},
"logos-module": {
"inputs": {
"logos-cpp-sdk": "logos-cpp-sdk_6",
"nixpkgs": [
"logos-storage-module",
"logos-liblogos",
"logos-module",
"logos-cpp-sdk",
"nixpkgs"
]
},
"locked": {
"lastModified": 1770062426,
"narHash": "sha256-zc7ZxDTlqOCYGyEHhrTA/7GS1EWh7+4amdPUKh+gGds=",
"owner": "logos-co",
"repo": "logos-module",
"rev": "f7ee69d9ad9f27c84f04f59896e9194125e951dc",
"type": "github"
},
"original": {
"owner": "logos-co",
"repo": "logos-module",
"type": "github"
}
},
"logos-storage": {
"inputs": {
"circom-compat": "circom-compat",
"nixpkgs": "nixpkgs_7"
},
"locked": {
"lastModified": 1769016529,
"narHash": "sha256-pzUrdFKSEraZQzO2a9Uj5oDMas+Vtpu6+p4bG+lRnsk=",
"lastModified": 1770982130,
"narHash": "sha256-qsTf54BP2yEJEDHVR1lgtZ1u2wm7TK5MUHgwUiX6wU0=",
"ref": "refs/heads/master",
"rev": "3c09f008bb5266a669fd19f18368f9e8b861b664",
"revCount": 877,
"rev": "e375223500ca0fd919b0e504eab65c6ddb5e4233",
"revCount": 897,
"submodules": true,
"type": "git",
"url": "https://github.com/logos-storage/logos-storage-nim"
@ -249,7 +310,9 @@
},
"logos-storage-module": {
"inputs": {
"logos-cpp-sdk": "logos-cpp-sdk_5",
"logos-cpp-sdk": [
"logos-cpp-sdk"
],
"logos-liblogos": "logos-liblogos_3",
"logos-storage": "logos-storage",
"nixpkgs": [
@ -259,11 +322,11 @@
]
},
"locked": {
"lastModified": 1769611047,
"narHash": "sha256-U8lV4hgntPTMO0wpyIDFKMN1N3jSGnxKDCGHS9WoEb8=",
"lastModified": 1771221082,
"narHash": "sha256-dpygXvcVOExNXHkUH+/XcR/xBVFKNjw/i9aChSXcZNk=",
"owner": "logos-co",
"repo": "logos-storage-module",
"rev": "14cc30674980685442e66639c37c587263520147",
"rev": "b4ecf7a871233608f63b817eeae426f6273695d9",
"type": "github"
},
"original": {
@ -370,11 +433,11 @@
},
"nixpkgs_7": {
"locked": {
"lastModified": 1736200483,
"narHash": "sha256-JO+lFN2HsCwSLMUWXHeOad6QUxOuwe9UOAF/iSl1J4I=",
"lastModified": 1751274312,
"narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3f0a8ac25fb674611b98089ca3a5dd6480175751",
"rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674",
"type": "github"
},
"original": {
@ -387,7 +450,7 @@
"root": {
"inputs": {
"logos-capability-module": "logos-capability-module",
"logos-cpp-sdk": "logos-cpp-sdk_3",
"logos-cpp-sdk": "logos-cpp-sdk_2",
"logos-liblogos": "logos-liblogos_2",
"logos-storage-module": "logos-storage-module",
"nixpkgs": [

View File

@ -4,10 +4,16 @@
inputs = {
# Follow the same nixpkgs as logos-liblogos to ensure compatibility
nixpkgs.follows = "logos-liblogos/nixpkgs";
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk";
# logos-cpp-sdk.url = "/home/arnaud/Work/logos/logos-cpp-sdk";
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk?ref=feat/logos-result";
logos-liblogos.url = "github:logos-co/logos-liblogos?ref=fix/logos-cleanup-on-terminate";
logos-storage-module.url = "github:logos-co/logos-storage-module";
#logos-storage-module.url = "path:/home/arnaud/Work/logos/logos-storage-module";
logos-capability-module.url = "github:logos-co/logos-capability-module";
logos-liblogos.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk";
logos-storage-module.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk";
logos-capability-module.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk";
};
outputs = { self, nixpkgs, logos-cpp-sdk, logos-liblogos, logos-storage-module, logos-capability-module }:

View File

@ -1,11 +1,21 @@
#include "StorageBackend.h"
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLocale>
// StorageBackend is responsible for managing the interaction with the storage module.
// It is mocked in the QML.
// There are currently 2 ways to display debug information:
// - the first one is to log only in the console using qDebug/qWarning. This is basically
// for developers: entering a function, sending a command to the storage module...
// - the second one is to use the "debug" helper that logs both in the console and in a
// QString property that can be displayed in the UI. This is more for users to understand
// what is happening.
StorageBackend::StorageBackend(LogosAPI* logosAPI, QObject* parent)
: QObject(parent), m_status(Destroyed), m_logosAPI(nullptr), m_logos(nullptr) {
qDebug() << "Initializing StorageBackend...";
@ -17,8 +27,6 @@ StorageBackend::StorageBackend(LogosAPI* logosAPI, QObject* parent)
}
m_logos = new LogosModules(m_logosAPI);
initStorage();
}
StorageBackend::~StorageBackend()
@ -27,97 +35,223 @@ StorageBackend::~StorageBackend()
m_logos = nullptr;
}
void StorageBackend::initStorage() {
LogosResult StorageBackend::init(const QString& configJson = "{}") {
qDebug() << "StorageBackend::initStorage called";
bool result = m_logos->storage_module.init("{}");
bool result = m_logos->storage_module.init(m_configJson);
qDebug() << "StorageBackend::initStorage: init result =" << result;
qDebug() << "StorageBackend::initStorage: init";
if (!result) {
qDebug() << "StorageBackend: Failed to initialise Storage module.";
setStatus(Destroyed, "Failed to initialise Storage module.");
return;
setStatus(Destroyed);
debug("Failed to init storage");
return {false, "", "Filed to init storage"};
}
setStatus(Stopped, "Storage module ready.");
setStatus(Stopped);
if (!m_logos->storage_module.on("storageStart", [this](const QVariantList& data) {
int code = data[0].toInt();
bool success = data[0].toBool();
if (code != RET_OK) {
qDebug() << "StorageBackend: storageStart event failure with code" << code;
setStatus(Stopped, "Failed to start Storage module.");
if (!success) {
QString message = data[1].toString();
setStatus(Stopped);
debug("Failed to start Storage module:" + message);
} else {
qDebug() << "StorageBackend: storageStart event success";
setStatus(Running, "Storage module started.");
setStatus(Running);
debug("Storage module started.");
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageStart events";
}
if (!m_logos->storage_module.on("storageStop", [this](const QVariantList& data) {
int code = data[0].toInt();
bool success = data[0].toBool();
if (code != RET_OK) {
qDebug() << "StorageBackend: storageStop event failure with code" << code;
setStatus(Running, "Failed to stop Storage module.");
if (!success) {
QString message = data[1].toString();
setStatus(Running);
debug("Failed to stop Storage module:" + message);
} else {
qDebug() << "StorageBackend: storageStop event success";
setStatus(Stopped, "Storage module stopped.");
setStatus(Stopped);
debug("Storage module stopped.");
emit stopped();
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageStop events";
}
startStop();
if (!m_logos->storage_module.on("storageConnect", [this](const QVariantList& data) {
bool success = data[0].toBool();
if (!success) {
QString message = data[1].toString();
debug("Failed to connect: " + message);
} else {
// TODO add the peer id
debug("Successfully connected to peer.");
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageConnect events";
}
if (!m_logos->storage_module.on("storageUploadProgress", [this](const QVariantList& data) {
bool success = data[0].toBool();
if (!success) {
QString message = data[1].toString();
debug("Failure during upload progress: " + message);
m_uploadStatus = "Error: " + message;
emit uploadStatusChanged();
} else {
QString sessionId = data[1].toString();
qint64 len = data[2].toLongLong();
m_uploadedBytes += len;
// Calcule le pourcentage
if (m_uploadTotalBytes > 0) {
m_uploadProgress = (m_uploadedBytes * 100) / m_uploadTotalBytes;
}
m_uploadStatus = QString("Uploading: %1 / %2 bytes (%3%)")
.arg(m_uploadedBytes)
.arg(m_uploadTotalBytes)
.arg(m_uploadProgress);
emit uploadProgressChanged();
emit uploadStatusChanged();
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageUploadProgress events";
}
if (!m_logos->storage_module.on("storageUploadDone", [this](const QVariantList& data) {
bool success = data[0].toBool();
if (!success) {
QString message = data[1].toString();
debug("Failed to upload: " + message);
m_uploadProgress = 0;
m_uploadStatus = "Upload failed";
emit uploadProgressChanged();
emit uploadStatusChanged();
} else {
QString sessionId = data[1].toString();
m_cid = data[2].toString();
emit cidChanged();
debug("Upload completed for session " + sessionId + " with CID " + m_cid);
// Complète la progress bar
m_uploadProgress = 100;
m_uploadStatus = "Upload completed!";
emit uploadProgressChanged();
emit uploadStatusChanged();
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageUploadProgress events";
}
if (!m_logos->storage_module.on("storageDownloadProgress", [this](const QVariantList& data) {
bool success = data[0].toBool();
if (!success) {
QString message = data[1].toString();
debug("Failure during download progress: " + message);
} else {
QString sessionId = data[1].toString();
int len = data[2].toInt();
debug("Downloaded " + QString::number(len) + " bytes for session " + sessionId);
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageDownloadProgress events";
}
if (!m_logos->storage_module.on("storageDownloadDone", [this](const QVariantList& data) {
bool success = data[0].toBool();
if (!success) {
QString message = data[1].toString();
debug("Failed to download: " + message);
} else {
QString sessionId = data[1].toString();
m_cid = data[2].toString();
emit cidChanged();
debug("Download completed for session " + sessionId + " with CID " + m_cid);
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageDownloadProgress events";
}
m_configJson = configJson;
emit configJsonChanged();
debug("config.json content is: " + m_configJson);
return {true, ""};
}
void StorageBackend::setStatus(StorageStatus newStatus, QString statusText) {
if (m_status != newStatus || m_statusText != statusText) {
void StorageBackend::setStatus(StorageStatus newStatus) {
if (m_status != newStatus) {
m_status = newStatus;
m_statusText = statusText;
emit statusChanged();
qDebug() << "StorageBackend: Status changed to" << m_status;
}
}
void StorageBackend::startStop() {
qDebug() << "StorageBackend: startStop method called";
LogosResult StorageBackend::start(const QString& newConfigJson) {
qDebug() << "StorageBackend: start method called";
if (m_status == Destroyed || m_status == Starting || m_status == Stopping) {
qDebug() << "StorageBackend: Cannot start/stop Storage in current state:" << m_status;
return;
if (newConfigJson != "") {
reloadIfChanged(newConfigJson);
}
if (m_status != Running) {
qDebug() << "StorageBackend: Starting Storage...";
setStatus(Starting, "Starting Storage module...");
bool result = m_logos->storage_module.start();
if (!result) {
qDebug() << "StorageBackend: Failed to start Storage.";
setStatus(Stopped, "Failed to start Storage module.");
return;
}
qDebug() << "StorageBackend: start command sent, waiting for events.";
} else {
stop();
if (m_status != Stopped) {
debug("The Storage Module is not initialised properly.");
return {false, "", "The Storage Module is not initialised properly."};
}
if (m_status == Running) {
debug("The Storage Module is already started.");
return {false, "", "The Storage Module is already started."};
}
setStatus(Starting);
debug("Starting Storage module...");
auto result = m_logos->storage_module.start();
if (!result) {
setStatus(Stopped);
debug("Failed to start storage");
return {false, "", "Failed to start storage"};
}
qDebug() << "StorageBackend: start command sent, waiting for events.";
return {true, ""};
}
void StorageBackend::stop() {
qDebug() << "StorageBackend: Stopping Storage...";
setStatus(Stopping, "Stopping Storage module...");
qDebug() << "StorageBackend: stop method called";
bool result = m_logos->storage_module.stop();
if (m_status == StorageStatus::Stopping) {
debug("The Storage Module is already stopping.");
return;
}
if (!result) {
qDebug() << "StorageBackend: Failed to stop Storage.";
setStatus(Running, "Failed to stop Storage module.");
if (m_status != StorageStatus::Running) {
debug("The Storage Module is not started.");
return;
}
setStatus(Stopping);
debug("Stopping Storage module...");
auto result = m_logos->storage_module.stop();
if (!result.success) {
setStatus(Running);
debug(result.getError());
return;
}
@ -128,29 +262,495 @@ void StorageBackend::destroy() {
qDebug() << "StorageBackend: destroy method called";
StorageStatus status = m_status;
int result = m_logos->storage_module.destroy();
auto result = m_logos->storage_module.destroy();
if (!result) {
qDebug() << "StorageBackend: Failed to destroy Storage module." << result;
setStatus(status, "Failed to destroy Storage module.");
if (!result.success) {
debug(result.getError());
return;
}
qDebug() << "StorageBackend: Storage module destroyed.";
}
QString StorageBackend::statusText() const { return m_statusText; }
QString StorageBackend::debugLogs() const { return m_debugLogs; };
QString StorageBackend::startStopText() const {
if (m_status != Running) {
return "Start";
} else {
return "Stop";
void StorageBackend::debug(const QString& log) {
if (!m_debugLogs.isEmpty()) {
m_debugLogs += "\n";
}
QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
m_debugLogs += timestamp + ": " + log;
emit debugLogsChanged();
qDebug() << "StorageBackend: " << log;
}
bool StorageBackend::canStartStop() const { return m_status == Running || m_status == Stopped; }
void StorageBackend::tryDebug() {
auto result = m_logos->storage_module.debug();
bool StorageBackend::isRunning() { return m_status == Running; }
debug("Debug " + result.getString());
}
void StorageBackend::tryPeerConnect(const QString& peerId) {
qDebug().noquote() << "StorageBackend: tryPeerConnect called with peerId=" << peerId;
bool StorageBackend::isInitialised() { return m_status != Destroyed; }
// LogosResult result2 = m_logos->storage_module.space();
// QVariantMap space = result2.getValue<QVariantMap>();
// int quotaMaxBytes = space["quotaMaxBytes"].toInt();
// int quotaUsedBytes = space["quotaUsedBytes"].toInt();
// int quotaReservedBytes = space["quotaReservedBytes"].toInt();
// int totalBlocks = result2.getValue<int>("totalBlocks");
// debug("totalBlocks " + QString::number(totalBlocks));
// debug("quotaMaxBytes " + QString::number(quotaMaxBytes));
// debug("quotaUsedBytes " + QString::number(quotaUsedBytes));
// debug("quotaReservedBytes " + QString::number(quotaReservedBytes));
// LogosResult result = m_logos->storage_module.dataDir();
// QString myDataDir = result.getString();
// qDebug() << "StorageBackend: tryPeerConnect dataDir=" << myDataDir;
// QString peerId = m_logos->storage_module.peerId();
// if (peerId.isEmpty()) {
// qDebug() << "StorageBackend: Peer ID is empty.";
// return;
// }
auto result = m_logos->storage_module.connect(peerId, QStringList());
qDebug() << "StorageBackend: peerConnect result =" << result.value;
// auto result = m_logos->storage_module.debug();
// debug("Debug " + result.getString());
// QString filename = "test.txt";
// QString sessionId = m_logos->storage_module.uploadInit(filename);
// qDebug() << "StorageBackend: uploadInit sessionId =" << sessionId;
// bool result = m_logos->storage_module.uploadCancel(sessionId);
// qDebug() << "StorageBackend: uploadCancel result =" << result;
}
void StorageBackend::tryUpload() {
qDebug() << "StorageBackend: tryUpload called";
// QString filename = "test.txt";
// m_sessionId = m_logos->storage_module.uploadInit(filename);
// qDebug() << "StorageBackend: uploadInit sessionId =" << m_sessionId;
// QByteArray chunk = "Sample data chunk for upload.";
// bool result = m_logos->storage_module.uploadChunk(m_sessionId, chunk);
// qDebug() << "StorageBackend: uploadChunk result =" << result;
}
void StorageBackend::tryUploadFinalize() {
qDebug() << "StorageBackend: tryFinalize called";
// m_cid = m_logos->storage_module.uploadFinalize(m_sessionId);
// qDebug() << "StorageBackend: uploadFinalize result =" << m_cid;
// emit cidChanged();
}
void StorageBackend::tryUploadFile(const QUrl& url) {
qDebug() << "StorageBackend: tryUploadFile called";
qDebug() << " URL toString():" << url.toString();
qDebug() << " URL toLocalFile():" << url.toLocalFile();
qDebug() << " URL path():" << url.path();
if (!url.isLocalFile()) {
qWarning() << "Not a local file";
debug("The provided URL is not a local file.");
return;
}
// Reset and initialize progress tracking
m_uploadProgress = 0;
m_uploadedBytes = 0;
m_uploadTotalBytes = QFileInfo(url.toLocalFile()).size();
m_uploadStatus = "Starting upload...";
emit uploadProgressChanged();
emit uploadStatusChanged();
debug(QString("Starting upload of file: %1 bytes").arg(m_uploadTotalBytes));
// QString filename = url.toLocalFile();
// // QString filename = "/home/arnaud/Work/logos/logos-storage-ui/README.md";
// QString sessionId = m_logos->storage_module.uploadInit(filename);
// qDebug() << "StorageBackend: uploadInit sessionId =" << sessionId;
// QtConcurrent::run([this, url]() {
// LogosResult result = m_logos->storage_module.uploadUrl(url);
// Go back to the main thread
// // Better to use signal
// QMetaObject::invokeMethod(
// this,
// [this, result]() {
// if (!result.success) {
// setStatus(m_status, result.getString());
// return;
// }
// QString sessionId = result.value.value<QString>();
// qDebug() << "StorageBackend: uploadFromPath result =" << sessionId;
// },
// Qt::QueuedConnection);
// });
// QTimer::singleShot(0, this, [this, url]() {
LogosResult result = m_logos->storage_module.uploadUrl(url);
if (!result.success) {
debug(result.getError());
return;
}
QString sessionId = result.value.value<QString>();
//});
qDebug() << "StorageBackend: tryUploadFile result =" << sessionId;
}
// void StorageBackend::tryUploadFile(const QUrl& url) {
// qDebug() << "StorageBackend:tryUploadFile called";
// if (!url.isLocalFile()) {
// qWarning() << "Not a local file";
// m_statusText = "The provided URL is not a local file.";
// emit statusChanged();
// return;
// }
// QString localPath = url.toLocalFile();
// qDebug() << " Uploading from:" << localPath;
// QFile file(localPath);
// if (!file.open(QIODevice::ReadOnly)) {
// qWarning() << "Cannot open file for reading:" << localPath;
// m_statusText = "Cannot open file for reading: " + localPath;
// emit statusChanged();
// return;
// }
// const qint64 chunkSize = 1024 * 64; // 64KB
// qint64 totalSize = file.size();
// qint64 bytesRead = 0;
// QFileInfo fileInfo(localPath);
// QString filename = fileInfo.fileName();
// LogosResult result = m_logos->storage_module.uploadInit("test.txt", chunkSize);
// if (!result.success) {
// debug(result.getString());
// file.close();
// return;
// }
// QString sessionId = result.getString();
// while (!file.atEnd()) {
// QByteArray chunk = file.read(chunkSize);
// bytesRead += chunk.size();
// qDebug() << " Read chunk:" << chunk.size() << "bytes"
// << "Progress:" << bytesRead << "/" << totalSize;
// result = m_logos->storage_module.uploadChunk(sessionId, chunk);
// if (!result.success) {
// qWarning("StorageBackend:tryUploadFile failed to send uploadChunk command");
// file.close();
// return;
// }
// // Calculate progress percentage
// int progress = (bytesRead * 100) / totalSize;
// qDebug() << " Progress:" << progress << "%";
// }
// file.close();
// result = m_logos->storage_module.uploadFinalize(sessionId);
// if (!result.success) {
// qWarning("StorageBackend:tryUploadFile failed to send uploadFinalize command");
// file.close();
// return;
// }
// qDebug() << "Upload complete, CID:" << result.getString();
// file.close();
// }
void StorageBackend::tryDownloadFile(const QString& cid, const QUrl& url) {
qDebug() << "StorageBackend: tryDownloadFile called";
if (!url.isLocalFile()) {
qWarning() << "Not a local file";
debug("The provided URL is not a local file.");
return;
}
// QString filename = url.toLocalFile();
// // QString filename = "/home/arnaud/Work/logos/logos-storage-ui/README.md";
// QString sessionId = m_logos->storage_module.uploadInit(filename);
// qDebug() << "StorageBackend: uploadInit sessionId =" << sessionId;
LogosResult result = m_logos->storage_module.downloadToUrl(cid, url, false);
if (!result.success) {
debug(result.getError());
return;
}
QString sessionId = result.value.value<QString>();
qDebug() << "StorageBackend: tryDownloadFile result =" << sessionId;
}
void StorageBackend::exists(const QString& cid) {
qDebug() << "StorageBackend::exists called";
LogosResult result = m_logos->storage_module.exists(cid);
if (!result.success) {
debug("StorageBackend::exists failed with error=" + result.getError());
return;
}
debug("Does " + cid + " exists ? " + QVariant(result.getValue<bool>()).toString());
}
void StorageBackend::remove(const QString& cid) {
qDebug() << "StorageBackend::remove called";
LogosResult result = m_logos->storage_module.remove(cid);
if (!result.success) {
debug("StorageBackend::remove failed with error=" + result.getError());
return;
}
debug("Cid " + cid + " removed.");
}
void StorageBackend::fetch(const QString& cid) {
qDebug() << "StorageBackend::fetch called";
LogosResult result = m_logos->storage_module.fetch(cid);
if (!result.success) {
debug("StorageBackend::fetch failed with error=" + result.getError());
return;
}
debug("Cid " + cid + " fetched.");
}
void StorageBackend::version() {
qDebug() << "StorageBackend::version called";
LogosResult result = m_logos->storage_module.version();
if (!result.success) {
debug("StorageBackend::version failed with error=" + result.getError());
return;
}
debug("Version: " + result.getString());
}
void StorageBackend::showPeerId() {
qDebug() << "StorageBackend::peerId called";
LogosResult result = m_logos->storage_module.peerId();
if (!result.success) {
debug("StorageBackend::peerId failed with error=" + result.getError());
return;
}
debug("Peer ID: " + result.getString());
}
void StorageBackend::spr() {
qDebug() << "StorageBackend::spr called";
LogosResult result = m_logos->storage_module.spr();
if (!result.success) {
debug("StorageBackend::spr failed with error=" + result.getError());
return;
}
debug("SPR: " + result.getString());
}
void StorageBackend::dataDir() {
qDebug() << "StorageBackend::dataDir called";
LogosResult result = m_logos->storage_module.dataDir();
if (!result.success) {
debug("StorageBackend::dataDir failed with error=" + result.getError());
return;
}
debug("Data dir: " + result.getString());
}
void StorageBackend::downloadManifest(const QString& cid) {
qDebug() << "StorageBackend::downloadManifest called with cid=" << cid;
LogosResult result = m_logos->storage_module.downloadManifest(cid);
if (!result.success) {
debug("StorageBackend::downloadManifest failed with error=" + result.getError());
return;
}
debug("Manifest tree cid: " + result.getString("treeCid"));
debug(QString("Manifest datasetSize %1").arg(result.getInt("datasetSize")));
debug(QString("Manifest blockSize %1").arg(result.getInt("blockSize")));
debug("Manifest filename: " + result.getString("filename"));
debug("Manifest mimetype: " + result.getString("mimetype"));
}
void StorageBackend::downloadManifests() {
qDebug() << "StorageBackend::downloadManifests called";
LogosResult result = m_logos->storage_module.manifests();
QString error = result.getError();
if (!result.success) {
debug("StorageBackend::downloadManifests failed with error=" + result.getError());
return;
}
QVariantList manifestsList = result.getList();
int count = manifestsList.size();
debug(QString("Found %1 manifests").arg(count));
// for (const QVariant& manifestVariant : manifestsList) {
// QVariantMap manifest = manifestVariant.toMap();
// QString cid = manifest["cid"].toString();
// QString treeCid = manifest["treeCid"].toString();
// QString filename = manifest["filename"].toString();
// qint64 datasetSize = manifest["datasetSize"].toLongLong();
// debug(QString("Manifest: %1, treeCid: %2, size: %3")
// .arg(filename)
// .arg(treeCid.isEmpty() ? "EMPTY" : treeCid)
// .arg(datasetSize));
// }
}
void StorageBackend::space() {
qDebug() << "StorageBackend::space called";
LogosResult result = m_logos->storage_module.space();
if (!result.success) {
debug("StorageBackend::space failed with error=" + result.getError());
return;
}
debug(QString("Space datasetSize %1").arg(result.getInt("totalBlocks")));
debug(QString("Space quotaMaxBytes %1").arg(result.getInt("quotaMaxBytes")));
debug(QString("Space quotaUsedBytes %1").arg(result.getInt("quotaUsedBytes")));
debug(QString("Space quotaReservedBytes %1").arg(result.getInt("quotaReservedBytes")));
}
void StorageBackend::updateLogLevel(const QString& logLevel) {
qDebug() << "StorageBackend::updateLogLevel called with logLevel=" << logLevel;
LogosResult result = m_logos->storage_module.updateLogLevel(logLevel);
if (!result.success) {
debug("StorageBackend::updateLogLevel failed with error=" + result.getError());
return;
}
debug("Log level updated to " + logLevel);
}
StorageBackend::StorageStatus StorageBackend::status() const { return m_status; }
QString StorageBackend::cid() const { return m_cid; }
QString StorageBackend::configJson() const { return m_configJson; }
int StorageBackend::uploadProgress() const { return m_uploadProgress; }
QString StorageBackend::uploadStatus() const { return m_uploadStatus; }
void StorageBackend::reloadIfChanged(const QString& configJson) {
if (configJson == m_configJson) {
return;
}
debug("New config detected");
QJsonDocument doc = QJsonDocument::fromJson(configJson.toUtf8());
if (doc.isNull()) {
debug("Invalid json detected !");
m_configJson = configJson;
emit configJsonChanged();
return;
}
if (m_status == StorageStatus::Running || m_status == StorageStatus::Stopping ||
m_status == StorageStatus::Starting) {
debug("Cannot reload the config while running, stopping or starting...");
return;
}
if (m_status == StorageStatus::Stopped) {
LogosResult result = m_logos->storage_module.destroy();
if (!result.success) {
debug("Failed to destroy the context error=" + result.getError());
return;
} else {
setStatus(StorageStatus::Destroyed);
}
}
bool result = m_logos->storage_module.init(configJson);
if (!result) {
debug("Failed to init context with new config, will rollback.");
bool result = m_logos->storage_module.init(m_configJson);
if (!result) {
debug("Failed to init context with old config, that's a serious issue.");
} else {
debug("Old config restored");
setStatus(StorageStatus::Stopped);
m_configJson = configJson;
emit configJsonChanged();
}
return;
}
debug("New config loaded successfully");
m_configJson = configJson;
setStatus(StorageStatus::Stopped);
}

View File

@ -8,47 +8,80 @@
#include <QtQml/qqml.h>
static const int RET_OK = 0;
static const int RET_PROGRESS = 3;
class StorageBackend : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString debugLogs READ debugLogs NOTIFY debugLogsChanged)
Q_PROPERTY(StorageStatus status READ status NOTIFY statusChanged)
Q_PROPERTY(QString cid READ cid NOTIFY cidChanged)
Q_PROPERTY(QString configJson READ configJson NOTIFY configJsonChanged)
Q_PROPERTY(int uploadProgress READ uploadProgress NOTIFY uploadProgressChanged)
Q_PROPERTY(QString uploadStatus READ uploadStatus NOTIFY uploadStatusChanged)
public:
enum StorageStatus { Stopped = 0, Starting, Running, Stopping, Destroyed };
Q_ENUM(StorageStatus)
Q_PROPERTY(bool canStartStop READ canStartStop NOTIFY statusChanged)
bool canStartStop() const;
Q_PROPERTY(QString startStopText READ startStopText NOTIFY statusChanged)
QString startStopText() const;
Q_PROPERTY(QString statusText READ statusText NOTIFY statusChanged)
QString statusText() const;
QString cid() const;
QString debugLogs() const;
StorageStatus status() const;
QString configJson() const;
int uploadProgress() const;
QString uploadStatus() const;
explicit StorageBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
~StorageBackend();
public slots:
void startStop();
LogosResult start(const QString& configJson = "");
void destroy();
bool isRunning();
bool isInitialised();
void stop();
void tryPeerConnect(const QString& peerId);
void tryDebug();
void tryUpload();
void tryUploadFinalize();
void exists(const QString& cid);
void remove(const QString& cid);
void fetch(const QString& cid);
void tryUploadFile(const QUrl& url);
void tryDownloadFile(const QString& cid, const QUrl& url);
void dataDir();
void version();
void spr();
void showPeerId();
void downloadManifest(const QString& cid);
void downloadManifests();
void space();
LogosResult init(const QString& configJson);
void updateLogLevel(const QString& logLevel);
signals:
void statusChanged();
void debugLogsChanged();
void stopped();
void cidChanged();
void configJsonChanged();
void uploadProgressChanged();
void uploadStatusChanged();
private slots:
private:
void setStatus(StorageStatus newStatus, QString statusText);
void initStorage();
void setStatus(StorageStatus newStatus);
void peerConnect(const QString& peerId);
void debug(const QString& log);
void reloadIfChanged(const QString& configJson);
StorageStatus m_status;
LogosAPI* m_logosAPI;
LogosModules* m_logos;
QString m_statusText;
StorageStatus m_status;
QString m_debugLogs;
QString m_cid;
QString m_configJson;
int m_uploadProgress = 0;
QString m_uploadStatus = "";
qint64 m_uploadTotalBytes = 0;
qint64 m_uploadedBytes = 0;
};

View File

@ -31,6 +31,35 @@ QWidget* StorageUIPlugin::createWidget(LogosAPI* logosAPI) {
root->setProperty("backend", QVariant::fromValue(static_cast<QObject*>(backend)));
QFileInfo info("config.json");
QString configJson = "{}";
if (info.exists() && info.isFile()) {
qDebug() << "StorageUIPlugin: config.json is found, let's try to load it...";
QFile file("config.json");
if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) {
configJson = QString::fromUtf8(file.readAll());
qDebug() << "StorageUIPlugin: config.json is found, let's try to load it... configJson=" << configJson;
} else {
qDebug() << "StorageUIPlugin: Failed to load config.json";
}
}
LogosResult result = backend->init(configJson);
if (!result.success) {
QString error = result.getError();
qWarning() << "StorageUIPlugin: Failed to init backend, will use mock version:" << error;
} else {
result = backend->start();
if (!result.success) {
qWarning() << "StorageUIPlugin: Failed to init backend, will use mock version:" << result.getError();
}
}
return quickWidget;
}
@ -66,13 +95,13 @@ void StorageUIPlugin::destroyWidget(QWidget* widget) {
return;
}
if (!backend->isInitialised()) {
if (backend->status() != StorageBackend::StorageStatus::Destroyed) {
qDebug() << "StorageUIPlugin::destroyWidget: backend is not initialised so let's detroy it.";
quickWidget->deleteLater();
return;
}
if (!backend->isRunning()) {
if (backend->status() == StorageBackend::StorageStatus::Running) {
qDebug() << "StorageUIPlugin::destroyWidget: backend is not running so let's detroy it.";
backend->destroy();

View File

@ -74,6 +74,7 @@ add_library(storage_generated STATIC
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_types.cpp
)
# Define the dependencies needed for storage_generated

View File

@ -1,63 +1,592 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
Rectangle {
id: root
width: 400
height: 700
color: "#000000"
//import QtQuick.Layouts
Item {
property var backend: mockBackend
readonly property int stopped: 0
readonly property int starting: 1
readonly property int running: 2
readonly property int stopping: 3
readonly property int destroyed: 4
property string peerId: ""
property string downloadDestination: ""
property url downloadCid: ""
property string logLevel: ""
property bool showDebug: false
property url uploadCid: root.backend.cid
property url configJson: root.backend.configJson
id: root
width: 400
height: 300
function getStatusLabel() {
switch (backend.status) {
case stopped:
return "Logos Storage stopped."
case starting:
return "Logos Storage is starting..."
case running:
return "Logos Storage started."
case stopping:
return "Logos Storage is stopping..."
case destroyed:
return "Logos Storage is not initialised."
}
}
function startStopText() {
if (backend.status == running) {
return "Stop"
}
return "Start"
}
function canStartStop() {
return backend.status == running || backend.status == stopped
}
function isRunning() {
return backend.status == running
}
QtObject {
id: mockBackend
property var status: root.stopped
property var statusText: "Destroyed"
property var startStopText: "Start"
property var canStartStop: true
property var debugLogs: "Hello !"
property var configJson: "{}"
property url cid: ""
property string uploadStatus: ""
property int uploadProgress: 0
function startStop() {
if (status == root.running) {
status = root.stopped
statusText = "Stopped"
startStopText = "Start"
} else {
status = root.running
statusText = "Started"
startStopText = "Stop"
function start(newConfigJson) {
status = root.running
}
function stop() {
status = root.stopped
}
function tryPeerConnect(peerId) {
console.log("Attempting peer connection...")
}
function tryDebug() {
console.log("Attempting peer connection...")
}
function spr() {}
function showPeerId() {}
function version() {}
function dataDir() {}
function tryUploadFinalize() {
console.log("Attempting upload finalize")
}
function tryUploadFile(file) {
console.log("Attempting upload file")
}
function tryDownloadFile(cid, file) {
console.log("Attempting download a file", cid, file)
}
function exists(cid) {
console.log("Attempting exists", cid)
}
function fetch(cid) {
console.log("Attempting fetch", cid)
}
function remove(cid) {
console.log("Attempting remove", cid)
}
function downloadManifest(cid) {
console.log("Attempting downloadManifest", cid)
}
function downloadManifests() {
console.log("Attempting downloadManifests")
}
function space() {}
function updateLogLevel(logLevel) {}
}
Text {
id: statusTextElement
objectName: "status"
text: root.getStatusLabel()
color: "white"
font.pointSize: 20
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Button {
id: startStopButton
objectName: "startStopButton"
anchors.leftMargin: 50
text: root.startStopText()
enabled: root.canStartStop()
onClicked: root.backend.status == root.stopped ? root.backend.start(
jsonEditor.text) : root.backend.stop()
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: statusTextElement.bottom
anchors.topMargin: 10
}
TextEdit {
id: cidTextEdit
objectName: "cid"
color: "white"
font.pointSize: 14
readOnly: true
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: startStopButton.bottom
anchors.topMargin: 10
text: root.uploadCid
}
Button {
id: openFile
text: "Open file"
onClicked: fileDialog.open()
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: cidTextEdit.bottom
anchors.topMargin: 15
enabled: root.isRunning
}
Column {
id: uploadProgressColumn
anchors.top: openFile.bottom
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
width: 300
spacing: 5
visible: root.backend.uploadProgress > 0
ProgressBar {
width: parent.width
value: root.backend.uploadProgress / 100.0
background: Rectangle {
color: "#333333"
radius: 3
implicitWidth: 300
implicitHeight: 6
}
contentItem: Item {
implicitWidth: 300
implicitHeight: 6
Rectangle {
width: parent.width * parent.parent.visualPosition
height: parent.height
radius: 3
color: "#4CAF50"
}
}
}
Text {
text: root.backend.uploadStatus
color: "#888888"
font.pixelSize: 10
anchors.horizontalCenter: parent.horizontalCenter
}
}
TextField {
id: peerIdField
placeholderText: "Enter the peer Id"
placeholderTextColor: "#999999"
color: "#000000"
selectByMouse: true
text: root.peerId
onTextChanged: root.peerId = text
anchors.top: uploadProgressColumn.bottom
anchors.topMargin: 50
anchors.horizontalCenter: parent.horizontalCenter
}
Button {
id: peerConnectButton
objectName: "peerConnectButton"
text: "Peer connect"
onClicked: root.backend.tryPeerConnect(root.peerId)
anchors.top: peerIdField.bottom
anchors.horizontalCenter: parent.horizontalCenter
enabled: root.isRunning
anchors.topMargin: 10
}
Button {
id: debugButton
objectName: "debugButton"
text: "Debug"
onClicked: root.backend.tryDebug()
anchors.top: peerConnectButton.bottom
anchors.horizontalCenter: parent.horizontalCenter
enabled: root.isRunning
anchors.topMargin: 50
}
Button {
id: peerIdButton
objectName: "peerIdButton"
text: "Peer Id"
onClicked: root.backend.showPeerId()
anchors.top: peerConnectButton.bottom
anchors.right: debugButton.left
enabled: root.isRunning
anchors.topMargin: 50
}
Button {
id: dataDirButton
objectName: "dataDirButton"
text: "Data dir"
onClicked: root.backend.dataDir()
anchors.top: peerConnectButton.bottom
anchors.right: peerIdButton.left
enabled: root.isRunning
anchors.topMargin: 50
}
Button {
id: sprButton
objectName: "sprButton"
text: "SPR"
onClicked: root.backend.spr()
anchors.top: peerConnectButton.bottom
anchors.left: debugButton.right
enabled: root.isRunning
anchors.topMargin: 50
}
Button {
id: versionButton
objectName: "versionButton"
text: "Version"
onClicked: root.backend.version()
anchors.top: peerConnectButton.bottom
anchors.left: sprButton.right
enabled: root.isRunning
anchors.topMargin: 50
}
TextField {
id: cidDownloadField
placeholderTextColor: "#999999"
placeholderText: "Enter the cid to download"
color: "black"
// text: root.downloadCid
onTextChanged: root.downloadCid = text
anchors.top: debugButton.bottom
anchors.topMargin: 50
anchors.horizontalCenter: parent.horizontalCenter
}
Button {
id: openFile2
text: "Open file"
onClicked: fileDialog2.open()
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: cidDownloadField.bottom
anchors.topMargin: 15
enabled: root.isRunning
}
Button {
id: cidDownloadButton
objectName: "cidDownloadButton"
text: "Download"
onClicked: root.backend.tryDownloadFile(root.downloadCid,
root.downloadDestination)
anchors.top: openFile2.bottom
anchors.horizontalCenter: parent.horizontalCenter
enabled: root.isRunning
anchors.topMargin: 10
}
Button {
id: existsButton
objectName: "existsButton"
text: "Exists"
onClicked: root.backend.exists(root.downloadCid)
anchors.top: openFile2.bottom
anchors.left: cidDownloadButton.right
enabled: root.isRunning
anchors.topMargin: 10
}
Button {
id: fetchButton
objectName: "fetchButton"
text: "Fetch"
onClicked: root.backend.fetch(root.downloadCid)
anchors.top: openFile2.bottom
anchors.left: existsButton.right
enabled: root.isRunning
anchors.topMargin: 10
}
Button {
id: removeButton
objectName: "removeButton"
text: "Remove"
onClicked: root.backend.remove(root.downloadCid)
anchors.top: openFile2.bottom
anchors.right: cidDownloadButton.left
enabled: root.isRunning
anchors.topMargin: 10
}
Button {
id: downloadManifestButton
objectName: "downloadManifestButton"
text: "Download manifest"
onClicked: root.backend.downloadManifest(root.downloadCid)
anchors.top: openFile2.bottom
anchors.right: removeButton.left
enabled: root.isRunning
anchors.topMargin: 10
}
Button {
id: downloadManifestsButton
objectName: "downloadManifestsButton"
text: "Manifests"
onClicked: root.backend.downloadManifests()
anchors.top: cidDownloadButton.bottom
anchors.horizontalCenter: parent.horizontalCenter
enabled: root.isRunning
anchors.topMargin: 10
}
Button {
id: spaceButton
objectName: "spaceButton"
text: "Space"
onClicked: root.backend.space()
anchors.top: cidDownloadButton.bottom
anchors.right: downloadManifestsButton.left
enabled: root.isRunning
anchors.topMargin: 10
}
TextField {
id: logLevelField
placeholderTextColor: "#999999"
placeholderText: "Enter the log level to download"
color: "black"
// text: root.downloadCid
onTextChanged: root.logLevel = text
anchors.top: downloadManifestsButton.bottom
anchors.topMargin: 50
anchors.horizontalCenter: parent.horizontalCenter
}
Button {
id: logLevelButton
objectName: "logLevelButton"
text: "Log level"
onClicked: root.backend.updateLogLevel(root.logLevel)
anchors.top: logLevelField.bottom
anchors.horizontalCenter: parent.horizontalCenter
enabled: root.isRunning
anchors.topMargin: 10
}
// TextEdit {
// id: selectableText
// anchors.fill: parent
// anchors.margins: 10
// text: "This text is selectable. You can copy it, but not edit it."
// readOnly: true // Makes the text non-editable
// selectByMouse: true // Enables selection by mouse drag (often the default for desktop)
// // Optional: Change cursor shape to IBeam when hovering
// MouseArea {
// anchors.fill: parent
// cursorShape: Qt.IBeamCursor
// acceptedButtons: Qt.NoButton // Allows TextEdit to handle mouse events
// }
// }
// Button {
// anchors.left: parent.left
// anchors.bottom: parent.bottom
// objectName: "uploadButton"
// text: "Upload"
// anchors.bottomMargin: 80
// onClicked: root.backend.tryUpload()
// }
// Button {
// anchors.left: parent.left
// anchors.bottom: parent.bottom
// objectName: "finalizeButton"
// text: "Finalize"
// onClicked: root.backend.tryUploadFinalize()
// }
// Button {
// anchors.left: parent.left
// anchors.bottom: parent.bottom
// objectName: "uploadFileButton"
// text: "Upload file"
// onClicked: root.backend.tryUploadFile()
// anchors.bottomMargin: 30
// }
FileDialog {
id: fileDialog
onAccepted: root.backend.tryUploadFile(fileDialog.selectedFile)
}
FileDialog {
id: fileDialog2
fileMode: FileDialog.SaveFile
onAccepted: {
root.downloadDestination = fileDialog2.selectedFile
console.log("Destination selected:",
root.backend.downloadDestination)
}
}
Rectangle {
anchors.fill: parent
anchors.leftMargin: 0
anchors.rightMargin: 0
anchors.topMargin: 0
anchors.bottomMargin: 0
color: "#202428"
}
id: debugPanel
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 150
color: "#111111"
visible: root.showDebug // or: visible: showDebug
Text {
objectName: "status"
text: root.backend.statusText
color: "white"
font.pointSize: 20
anchors.centerIn: parent
anchors.topMargin: 0
}
TabBar {
id: bar
width: parent.width
Button {
objectName: "startStopButton"
anchors.leftMargin: 50
text: root.backend.startStopText
enabled: root.backend.canStartStop
onClicked: root.backend.startStop()
TabButton {
text: qsTr("Logs")
}
TabButton {
text: qsTr("Config")
}
}
StackLayout {
id: stackLayout
currentIndex: bar.currentIndex
anchors.top: bar.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
Item {
id: homeTab
Flickable {
id: flick
anchors.fill: parent
clip: true
contentWidth: width
contentHeight: debugText.paintedHeight
TextEdit {
id: debugText
width: flick.width
text: root.backend.debugLogs
color: "#dddddd"
font.family: "monospace"
font.pixelSize: 12
wrapMode: Text.WrapAnywhere
readOnly: true
onTextChanged: Qt.callLater(function () {
flick.contentY = Math.max(
0, flick.contentHeight - flick.height)
})
}
}
}
Rectangle {
id: discoverTab
ScrollView {
anchors.fill: parent
TextArea {
id: jsonEditor
font.family: "monospace"
font.pixelSize: 12
color: "#d4d4d4"
width: parent.width
height: parent.height
background: Rectangle {
color: "#1e1e1e"
border.color: jsonEditor.isValid ? "#3a3a3a" : "#ff0000"
border.width: 1
}
property bool isValid: true
Connections {
target: root.backend
function onConfigJsonChanged() {
jsonEditor.text = root.backend.configJson
try {
const jsonData = JSON.parse(jsonEditor.text)
jsonEditor.isValid = true
} catch (e) {
jsonEditor.isValid = false
}
}
}
Component.onCompleted: {
text = root.backend.configJson
try {
const jsonData = JSON.parse(text)
isValid = true
} catch (e) {
isValid = false
}
}
onTextChanged: {
try {
const jsonData = JSON.parse(text)
isValid = true
} catch (e) {
isValid = false
}
}
}
}
}
}
Shortcut {
sequence: "Ctrl+D"
onActivated: root.showDebug = !root.showDebug
}
}
}

@ -1 +1 @@
Subproject commit 32f1d7080d784ff044d91d076ef2f0c7305d4784
Subproject commit c3477d29e32cae5f73ca637fb81e547f8a6cba58

@ -1 +1 @@
Subproject commit fb7a9d6888dc59dd29437e8d3a2d38fc9f0e8696
Subproject commit 1378757dc8745941ebee79ae484abe62be2f5db4

@ -1 +1 @@
Subproject commit e01d9dbc6159bf6442a670e9432714b024b7698b
Subproject commit 7dceb6a8dfd5a5bbd47dc9b00e55f80b62ab1e6e

@ -1 +1 @@
Subproject commit 7d51740f91c0ee3781d40111787f0d450bf729ae
Subproject commit e375223500ca0fd919b0e504eab65c6ddb5e4233