[system/kio-snapshot] /: add support for external volumes to snapshot worker

Bharadwaj Raju <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 87ddea8d20b714e0245141e8db2984fb67506721 by Bharadwaj Raju.
Committed on 17/07/2026 at 12:06.
Pushed by bharadwaj-raju into branch 'master'.

add support for external volumes to snapshot worker

M  +21   -6    contextmenu/snapshotfileitemaction.cpp
M  +47   -11   kioworker/snapshot.cpp

https://invent.kde.org/system/kio-snapshot/-/commit/87ddea8d20b714e0245141e8db2984fb67506721

diff --git a/contextmenu/snapshotfileitemaction.cpp b/contextmenu/snapshotfileitemaction.cpp
index c834638..0665c96 100644
--- a/contextmenu/snapshotfileitemaction.cpp
+++ b/contextmenu/snapshotfileitemaction.cpp
@@ -14,6 +14,7 @@
 
 #include <Solid/Device>
 #include <Solid/StorageAccess>
+#include <Solid/StorageVolume>
 
 #include <KFileItem>
 #include <KLocalizedString>
@@ -51,22 +52,36 @@ QList<QAction *> SnapshotFileItemAction::actions(const KFileItemListProperties &
 
     if (item.isDir()) {
         QString localPath = itemUrl.toLocalFile();
-        auto fsRoot = Solid::Device::storageAccessFromPath(localPath).as<Solid::StorageAccess>();
-        if (!fsRoot) {
+        auto fsDevice = Solid::Device::storageAccessFromPath(localPath);
+        auto fsAccess = fsDevice.as<Solid::StorageAccess>();
+        if (!fsAccess) {
             qCCritical(SNAPSHOT_FILEITEMACTION()) << "could not determine fs root path for" << localPath;
             return actions;
         }
-        QString fsRootPath = fsRoot->filePath();
+        QString fsRootPath = fsAccess->filePath();
+        auto fsVolume = fsDevice.as<Solid::StorageVolume>();
+        if (!fsVolume) {
+            qCCritical(SNAPSHOT_FILEITEMACTION()) << "could not determine fs storage volume for" << localPath;
+            return actions;
+        }
+        QString fsUuid = fsVolume->uuid();
+
         if (!BtrfsSnapshots::getSnapshotsForSubvolume(itemUrl.toLocalFile(), fsRootPath).empty()) {
-            auto subvolumeIdOpt = BtrfsSnapshots::getSubvolumeForPath(itemUrl.toLocalFile());
+            auto subvolumeIdOpt = BtrfsSnapshots::getSubvolumeForPath(itemUrl.toLocalFile(), fsRootPath);
             if (!subvolumeIdOpt.has_value()) {
                 qCCritical(SNAPSHOT_FILEITEMACTION()) << "found snapshots for dir" << itemUrl.toLocalFile() << "but it did not have a subvolume id";
                 return actions;
             }
             auto subvolumeId = subvolumeIdOpt.value();
             QAction *action = new QAction(QIcon::fromTheme("view-history"_L1), i18nc("@action:inmenu", "Browse snapshots…"), parentWidget);
-            connect(action, &QAction::triggered, this, [this, subvolumeId]() {
-                KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl("snapshot:///%1"_L1.arg(QString::number(subvolumeId))), "inode/directory"_L1, this);
+            connect(action, &QAction::triggered, this, [this, subvolumeId, fsRootPath, fsUuid]() {
+                QUrl targetUrl;
+                targetUrl.setScheme("snapshot"_L1);
+                if (fsRootPath != "/"_L1) {
+                    targetUrl.setHost(fsUuid);
+                }
+                targetUrl.setPath("/"_L1 + QString::number(subvolumeId));
+                KIO::OpenUrlJob *job = new KIO::OpenUrlJob(targetUrl, "inode/directory"_L1, this);
                 job->start();
             });
             actions << action;
diff --git a/kioworker/snapshot.cpp b/kioworker/snapshot.cpp
index c7aa8c5..f2d0571 100644
--- a/kioworker/snapshot.cpp
+++ b/kioworker/snapshot.cpp
@@ -13,6 +13,10 @@
 #include <KIO/Global>
 #include <KIO/UDSEntry>
 
+#include <Solid/Device>
+#include <Solid/StorageAccess>
+#include <Solid/StorageVolume>
+
 #include <KLocalizedString>
 
 #include <QCoreApplication>
@@ -20,6 +24,7 @@
 #include <QDir>
 #include <QLocale>
 #include <QUrl>
+#include <QUuid>
 
 using namespace Qt::StringLiterals;
 
@@ -46,12 +51,39 @@ extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv)
 
 class SnapshotUrl : public QUrl
 {
+private:
+    bool hasUuid() const
+    {
+        QString firstSegment = path().section('/'_L1, 0, 0, QString::SectionFlag::SectionSkipEmpty);
+        return firstSegment.startsWith("uuid="_L1);
+    }
+
 public:
     SnapshotUrl(const QUrl &url)
         : QUrl(url)
     {
     }
 
+    QString fsRoot() const
+    {
+        if (host().isEmpty()) {
+            return "/"_L1;
+        }
+        QUuid uuid(host());
+        if (!uuid.isNull()) {
+            const auto deviceList =
+                Solid::Device::listFromQuery("StorageVolume.uuid == '%1'"_L1.arg(uuid.toString(QUuid::StringFormat::WithoutBraces).toLower()));
+            if (!deviceList.isEmpty()) {
+                auto device = deviceList.first();
+                auto storageAccess = device.as<Solid::StorageAccess>();
+                if (storageAccess) {
+                    return storageAccess->filePath();
+                }
+            }
+        }
+        return "/"_L1;
+    }
+
     std::optional<qulonglong> subvolumeId() const
     {
         bool ok;
@@ -91,7 +123,7 @@ bool SnapshotProtocol::rewriteUrl(const QUrl &url, QUrl &newUrl)
 {
     const SnapshotUrl snapshotUrl(url);
     auto snapshotId = snapshotUrl.snapshotId();
-    auto snapshotPathOpt = BtrfsSnapshots::getPathForSubvolume(snapshotId.value());
+    auto snapshotPathOpt = BtrfsSnapshots::getPathForSubvolume(snapshotId.value(), snapshotUrl.fsRoot());
     if (!snapshotPathOpt.has_value()) {
         warning(i18nc("@info warning", "Could not open snapshot"));
         return false;
@@ -109,17 +141,21 @@ KIO::WorkerResult SnapshotProtocol::listDir(const QUrl &url)
     qCDebug(KIO_SNAPSHOT) << "url" << url << "url.host" << url.host() << "subvolume" << snapshotUrl.subvolumeId() << "snapshotId" << snapshotUrl.snapshotId()
                           << "actualPath" << snapshotUrl.actualPath();
 
+    const QString fsRoot = snapshotUrl.fsRoot();
+
     if (!snapshotUrl.subvolumeId().has_value()) {
         KIO::UDSEntryList udsList;
-        for (const auto [id, path] : BtrfsSnapshots::getNonSnapshotSubvolumes().asKeyValueRange()) {
-            if (!BtrfsSnapshots::getSnapshotsForSubvolume(path).empty()) {
+        for (const auto [id, path] : BtrfsSnapshots::getNonSnapshotSubvolumes(fsRoot).asKeyValueRange()) {
+            if (!BtrfsSnapshots::getSnapshotsForSubvolume(path, fsRoot).empty()) {
                 KIO::UDSEntry entry;
                 entry.fastInsert(KIO::UDSEntry::UDS_NAME, "subvolume%1"_L1.arg(QString::number(id)));
                 entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME,
                                  i18nc("@title denoting a listing of snapshots for a directory; %1 is the path to the directory", "Snapshots for %1", path));
                 entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "view-history"_L1);
                 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, QT_STAT_DIR);
-                entry.fastInsert(KIO::UDSEntry::UDS_URL, "snapshot:///%1"_L1.arg(QString::number(id)));
+                QUrl targetUrl = url;
+                targetUrl.setPath("/"_L1 + QString::number(id));
+                entry.fastInsert(KIO::UDSEntry::UDS_URL, targetUrl.toString(QUrl::FullyEncoded));
                 udsList << entry;
             }
         }
@@ -131,12 +167,12 @@ KIO::WorkerResult SnapshotProtocol::listDir(const QUrl &url)
         return KIO::ForwardingWorkerBase::listDir(url);
     }
 
-    auto subvolumePathOpt = BtrfsSnapshots::getPathForSubvolume(snapshotUrl.subvolumeId().value());
+    auto subvolumePathOpt = BtrfsSnapshots::getPathForSubvolume(snapshotUrl.subvolumeId().value(), fsRoot);
     if (!subvolumePathOpt.has_value()) {
         return KIO::WorkerResult::fail(KIO::ERR_ACCESS_DENIED);
     }
 
-    const QList<BtrfsSnapshots::SubvolumeSnapshot> snapshots = BtrfsSnapshots::getSnapshotsForSubvolume(subvolumePathOpt.value());
+    const QList<BtrfsSnapshots::SubvolumeSnapshot> snapshots = BtrfsSnapshots::getSnapshotsForSubvolume(subvolumePathOpt.value(), fsRoot);
 
     KIO::UDSEntryList udsList;
     for (const auto &snapshot : snapshots) {
@@ -152,8 +188,7 @@ KIO::WorkerResult SnapshotProtocol::listDir(const QUrl &url)
         entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, snapshot.snapshotted.toSecsSinceEpoch());
         entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, QT_STAT_DIR);
         entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"_L1);
-        QUrl targetUrl;
-        targetUrl.setScheme("snapshot"_L1);
+        QUrl targetUrl = url;
         targetUrl.setPath(QDir::cleanPath("/%1/%2/"_L1.arg(QString::number(snapshotUrl.subvolumeId().value())).arg(QString::number(snapshot.subvolumeId))));
         entry.fastInsert(KIO::UDSEntry::UDS_TARGET_URL, targetUrl.toString(QUrl::FullyEncoded));
         qCDebug(KIO_SNAPSHOT) << entry;
@@ -190,6 +225,7 @@ KIO::WorkerResult SnapshotProtocol::stat(const QUrl &url)
     }
 
     qulonglong subvolumeId = snapshotUrl.subvolumeId().value();
+    const QString fsRoot = snapshotUrl.fsRoot();
 
     if (snapshotUrl.snapshotId().has_value() && !snapshotUrl.actualPath().isEmpty()) {
         qCDebug(KIO_SNAPSHOT()) << "forwarding stat...";
@@ -203,11 +239,11 @@ KIO::WorkerResult SnapshotProtocol::stat(const QUrl &url)
         if (snapshotInfoMap.contains(snapshotId)) {
             snapshotInfo = snapshotInfoMap[snapshotId];
         } else {
-            auto snapshotPathOpt = BtrfsSnapshots::getPathForSubvolume(subvolumeId);
+            auto snapshotPathOpt = BtrfsSnapshots::getPathForSubvolume(subvolumeId, fsRoot);
             if (!snapshotPathOpt.has_value()) {
                 return KIO::WorkerResult::fail(KIO::ERR_ACCESS_DENIED);
             }
-            const auto snapshotQuery = BtrfsSnapshots::getSnapshotsForSubvolume(snapshotPathOpt.value());
+            const auto snapshotQuery = BtrfsSnapshots::getSnapshotsForSubvolume(snapshotPathOpt.value(), fsRoot);
             for (const auto &snapshot : snapshotQuery) {
                 snapshotInfoMap[snapshot.subvolumeId] = snapshot;
                 if (snapshot.subvolumeId == snapshotId) {
@@ -234,7 +270,7 @@ KIO::WorkerResult SnapshotProtocol::stat(const QUrl &url)
         return KIO::WorkerResult::pass();
     }
 
-    auto snapshotPathOpt = BtrfsSnapshots::getPathForSubvolume(subvolumeId);
+    auto snapshotPathOpt = BtrfsSnapshots::getPathForSubvolume(subvolumeId, fsRoot);
     if (!snapshotPathOpt.has_value()) {
         return KIO::WorkerResult::fail(KIO::ERR_ACCESS_DENIED);
     }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.