This commit is contained in:
Arnaud 2026-02-17 13:08:30 +04:00
parent ca52cf1e81
commit 38d7b0a7e8
No known key found for this signature in database
GPG Key ID: 20E40A5D3110766F
3 changed files with 143 additions and 11 deletions

View File

@ -67,6 +67,7 @@ LogosResult StorageBackend::init(const QString& configJson = "{}") {
setStatus(Running);
debug("Storage module started.");
QMetaObject::invokeMethod(this, &StorageBackend::downloadManifests, Qt::QueuedConnection);
QMetaObject::invokeMethod(this, &StorageBackend::space, Qt::QueuedConnection);
emit startCompleted();
}
})) {
@ -156,6 +157,8 @@ LogosResult StorageBackend::init(const QString& configJson = "{}") {
m_uploadStatus = "Upload completed!";
emit uploadProgressChanged();
emit uploadStatusChanged();
QMetaObject::invokeMethod(this, &StorageBackend::space, Qt::QueuedConnection);
}
})) {
qWarning() << "StorageWidget: failed to subscribe to storageUploadProgress events";
@ -550,13 +553,13 @@ void StorageBackend::remove(const QString& cid) {
LogosResult result = m_logos->storage_module.remove(cid);
if (!result.success) {
debug("StorageBackend::remove failed with error=" + result.getError());
return;
// Log but continue — manifest might not have local data, remove it from the list anyway
debug("StorageBackend::remove: storage returned error=" + result.getError() + " (removing from list regardless)");
} else {
debug("Cid " + cid + " removed from storage.");
}
debug("Cid " + cid + " removed.");
// Remove from manifests list
// Always remove from manifests list
for (int i = 0; i < m_manifests.size(); ++i) {
if (m_manifests[i].toMap().value("cid").toString() == cid) {
m_manifests.removeAt(i);
@ -564,6 +567,8 @@ void StorageBackend::remove(const QString& cid) {
break;
}
}
QMetaObject::invokeMethod(this, &StorageBackend::space, Qt::QueuedConnection);
}
void StorageBackend::fetch(const QString& cid) {
@ -711,12 +716,33 @@ void StorageBackend::space() {
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")));
qDebug() << "StorageBackend::space raw value:" << result.value;
static constexpr qint64 DEFAULT_QUOTA = 20LL * 1024 * 1024 * 1024; // 20 GB
// Check config for a quota-max-bytes override
qint64 configQuota = 0;
QJsonDocument doc = QJsonDocument::fromJson(m_configJson.toUtf8());
if (!doc.isNull()) {
configQuota = doc.object().value("quota-max-bytes").toVariant().toLongLong();
}
qint64 apiQuota = result.getInt("quotaMaxBytes");
m_quotaMaxBytes = apiQuota > 0 ? apiQuota : (configQuota > 0 ? configQuota : DEFAULT_QUOTA);
m_quotaUsedBytes = result.getInt("quotaUsedBytes");
m_quotaReservedBytes = result.getInt("quotaReservedBytes");
emit quotaChanged();
debug(QString("Space totalBlocks %1").arg(result.getInt("totalBlocks")));
debug(QString("Space quotaMaxBytes %1").arg(m_quotaMaxBytes));
debug(QString("Space quotaUsedBytes %1").arg(m_quotaUsedBytes));
debug(QString("Space quotaReservedBytes %1").arg(m_quotaReservedBytes));
}
qint64 StorageBackend::quotaMaxBytes() const { return m_quotaMaxBytes; }
qint64 StorageBackend::quotaUsedBytes() const { return m_quotaUsedBytes; }
qint64 StorageBackend::quotaReservedBytes() const { return m_quotaReservedBytes; }
void StorageBackend::updateLogLevel(const QString& logLevel) {
qDebug() << "StorageBackend::updateLogLevel called with logLevel=" << logLevel;

View File

@ -34,6 +34,9 @@ class StorageBackend : public QObject {
Q_PROPERTY(int uploadProgress READ uploadProgress NOTIFY uploadProgressChanged)
Q_PROPERTY(QString uploadStatus READ uploadStatus NOTIFY uploadStatusChanged)
Q_PROPERTY(QVariantList manifests READ manifests NOTIFY manifestsChanged)
Q_PROPERTY(qint64 quotaMaxBytes READ quotaMaxBytes NOTIFY quotaChanged)
Q_PROPERTY(qint64 quotaUsedBytes READ quotaUsedBytes NOTIFY quotaChanged)
Q_PROPERTY(qint64 quotaReservedBytes READ quotaReservedBytes NOTIFY quotaChanged)
public:
enum StorageStatus { Stopped = 0, Starting, Running, Stopping, Destroyed };
@ -46,6 +49,9 @@ class StorageBackend : public QObject {
int uploadProgress() const;
QString uploadStatus() const;
QVariantList manifests() const;
qint64 quotaMaxBytes() const;
qint64 quotaUsedBytes() const;
qint64 quotaReservedBytes() const;
Q_INVOKABLE static QString defaultDataDir();
@ -91,6 +97,7 @@ class StorageBackend : public QObject {
void uploadProgressChanged();
void uploadStatusChanged();
void manifestsChanged();
void quotaChanged();
private slots:
@ -110,4 +117,7 @@ class StorageBackend : public QObject {
qint64 m_uploadTotalBytes = 0;
qint64 m_uploadedBytes = 0;
QVariantList m_manifests;
qint64 m_quotaMaxBytes = 0;
qint64 m_quotaUsedBytes = 0;
qint64 m_quotaReservedBytes = 0;
};

View File

@ -128,6 +128,17 @@ Rectangle {
function updateLogLevel(logLevel) {}
property var manifests: []
property var quotaMaxBytes: 20 * 1024 * 1024 * 1024 // 20 GB default
property var quotaUsedBytes: 0
property var quotaReservedBytes: 0
}
function formatBytes(bytes) {
if (bytes <= 0) return "0 B"
if (bytes < 1024) return bytes + " B"
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + " MB"
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB"
}
Text {
@ -397,11 +408,96 @@ Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
}
// Disk space bar
Item {
id: spaceBarSection
anchors.top: manifestsTitle.bottom
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 40
height: root.backend.quotaMaxBytes > 0 ? 36 : 20
readonly property real total: root.backend.quotaMaxBytes
readonly property real used: root.backend.quotaUsedBytes
readonly property real reserved: root.backend.quotaReservedBytes
// No quota configured
Text {
anchors.centerIn: parent
text: "No quota configured"
color: "#555555"
font.pixelSize: 11
visible: spaceBarSection.total <= 0
}
// Background track
Rectangle {
id: spaceBarTrack
visible: spaceBarSection.total > 0
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: 14
radius: 7
color: "#2a2a2a"
border.color: "#3a3a3a"
border.width: 1
clip: true
// Used (green)
Rectangle {
width: Math.min(parent.width * (spaceBarSection.used / spaceBarSection.total), parent.width)
height: parent.height
radius: parent.radius
color: "#4CAF50"
}
// Reserved (orange), stacked after used
Rectangle {
x: parent.width * (spaceBarSection.used / spaceBarSection.total)
width: Math.min(parent.width * (spaceBarSection.reserved / spaceBarSection.total),
parent.width - x)
height: parent.height
color: "#FF9800"
}
}
// Labels
Row {
visible: spaceBarSection.total > 0
anchors.top: spaceBarTrack.bottom
anchors.topMargin: 4
anchors.horizontalCenter: parent.horizontalCenter
spacing: 16
Text {
text: "Used: " + root.formatBytes(spaceBarSection.used)
color: "#4CAF50"
font.pixelSize: 10
}
Text {
text: "Reserved: " + root.formatBytes(spaceBarSection.reserved)
color: "#FF9800"
font.pixelSize: 10
}
Text {
text: "Free: " + root.formatBytes(spaceBarSection.total - spaceBarSection.used - spaceBarSection.reserved)
color: "#888888"
font.pixelSize: 10
}
Text {
text: "Total: " + root.formatBytes(spaceBarSection.total)
color: "#555555"
font.pixelSize: 10
}
}
}
Row {
id: manifestInputRow
spacing: 8
anchors.top: manifestsTitle.bottom
anchors.topMargin: 8
anchors.top: spaceBarSection.bottom
anchors.topMargin: 16
anchors.horizontalCenter: parent.horizontalCenter
TextField {