浏览代码

QuickAccess: change link address from hard link to VxURL (#2635)

Use the format '#signature:fileFullName' (VxURL) for QuickAccess instead of absolute file paths (though compatibility support for absolute paths is still maintained).

Now each record in QuickAccess dynamically retrieves its absolute address when opened, which helps prevent broken links caused by file renaming or moving.

Co-authored-by: schips <[email protected]>
Schips 2 月之前
父节点
当前提交
f094b24b90

+ 1 - 0
src/utils/CMakeLists.txt

@@ -13,6 +13,7 @@ target_sources(vnote PRIVATE
     processutils.cpp processutils.h
     urldragdroputils.cpp urldragdroputils.h
     utils.cpp utils.h
+    vxurlutils.cpp vxurlutils.h
     webutils.cpp webutils.h
     widgetutils.cpp widgetutils.h
 )

+ 153 - 0
src/utils/vxurlutils.cpp

@@ -0,0 +1,153 @@
+#include "vxurlutils.h"
+
+#include <QFile>
+#include <QDir>
+#include <QDirIterator>
+#include <QTemporaryFile>
+
+#include <core/exception.h>
+#include <core/global.h>
+
+#include "pathutils.h"
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonParseError>
+#include <QJsonValue>
+
+using namespace vnotex;
+
+QString VxUrlUtils::generateVxURL(const QString &p_signature, const QString &p_filePath)
+{
+    return QString("#%1:%2").arg(p_signature, p_filePath);
+}
+
+QString VxUrlUtils::getSignatureFromVxURL(const QString &p_vxUrl)
+{
+    QString signature = p_vxUrl;
+    // check if file is "#signature:fileFullName"
+    if (signature.startsWith('#')) {
+        signature = signature.mid(1); // remove '#'
+        if (signature.contains(':')) {
+            signature = signature.split(':').first(); // get 'signature'
+            return signature;
+        }
+    } // if not 'signature', return original 'vxUrl'
+    return p_vxUrl;
+}
+
+QString VxUrlUtils::getFilePathFromVxURL(const QString &p_vxUrl) {
+    QString filePath = p_vxUrl;
+    // check if file is "#signature:fileFullName"
+    if (p_vxUrl.startsWith('#')) {
+        int colonPos = p_vxUrl.indexOf(':');
+        if (colonPos != -1) {
+            filePath = p_vxUrl.mid(colonPos + 1);
+            filePath = PathUtils::fileName(filePath);   // get 'filePath'
+            return filePath;
+        }
+    } // if not 'filePath', return original 'vxUrl'
+    return p_vxUrl;
+}
+
+QString VxUrlUtils::getSignatureFromFilePath(const QString &p_filePath)
+{
+    QFileInfo fileInfo(p_filePath);
+    QString vxJsonPath;
+    QString currentFileName;
+    // get file's signature from vx.json
+    if (fileInfo.isFile()) {
+        QString dirPath = PathUtils::parentDirPath(p_filePath);
+        vxJsonPath = PathUtils::concatenateFilePath(dirPath, "vx.json");
+        currentFileName = PathUtils::fileName(p_filePath);
+    } else if (fileInfo.isDir()) {
+        vxJsonPath = PathUtils::concatenateFilePath(p_filePath, "vx.json");
+        currentFileName = PathUtils::fileName(p_filePath);
+    } else {
+        return QString();
+    }
+
+    QFile vxFile(vxJsonPath);
+    if (!vxFile.open(QIODevice::ReadOnly)) {
+        return QString();
+    }
+
+    QByteArray data = vxFile.readAll();
+    vxFile.close();
+
+    QJsonParseError parseError;
+    QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
+    if (parseError.error != QJsonParseError::NoError) {
+        return QString();
+    }
+
+    QJsonObject obj = doc.object();
+    QString signature;
+
+    if (obj.contains("files") && obj["files"].isArray()) {
+        QJsonArray filesArray = obj["files"].toArray();
+        for (const QJsonValue &fileVal : filesArray) {
+            QJsonObject fileObj = fileVal.toObject();
+            if (fileObj["name"].toString() == currentFileName) {
+                signature = fileObj["signature"].toString();
+                return signature;
+            }
+        }
+    }
+
+    if (signature.isEmpty() && obj.contains("signature")) {
+        signature = obj["signature"].toString();
+    }
+
+    return signature;
+}
+
+QString VxUrlUtils::getFilePathFromSignature(const QString &p_startPath, const QString &p_signature)
+{
+    // Find the file with the specified signature in all vx.json files under the specified directory
+    QDirIterator it(p_startPath, {"vx.json"}, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
+
+    while (it.hasNext()) {
+        const QString vxPath = it.next();
+        QFile vxFile(vxPath);
+        if (!vxFile.open(QIODevice::ReadOnly)) {
+            continue;
+        }
+
+        const QByteArray data = vxFile.readAll();
+        vxFile.close();
+
+        QJsonParseError parseError;
+        const QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
+        if (parseError.error != QJsonParseError::NoError) {
+            continue;
+        }
+
+        const QJsonObject json = doc.object();
+        QString signature;
+        QString fileName;
+
+        // Find signature in files array
+        const auto filesArray = json.value("files").toArray();
+        for (const auto &fileItem : filesArray) {
+            const auto fileObj = fileItem.toObject();
+            if (fileObj["signature"].toString() == p_signature) {
+                fileName = fileObj["name"].toString();
+                signature = p_signature;
+                break;
+            }
+        }
+        // If not found in files array, use directory signature
+        if (signature.isEmpty()) {
+            signature = json.value("signature").toString();
+        }
+
+        if (!signature.isEmpty() && signature == p_signature) {
+            const QString dirPath = QFileInfo(vxPath).absolutePath();
+            const QString fullPath = PathUtils::concatenateFilePath(dirPath, fileName);
+            return fullPath;
+        }
+    }
+
+    return QString();
+}

+ 35 - 0
src/utils/vxurlutils.h

@@ -0,0 +1,35 @@
+#ifndef VXURLUTILS_H
+#define VXURLUTILS_H
+
+#include <QByteArray>
+#include <QString>
+#include <QJsonObject>
+#include <QDir>
+
+class QTemporaryFile;
+
+namespace vnotex
+{
+    class VxUrlUtils
+    {
+    public:
+        VxUrlUtils() = delete;
+
+        // Generate vxUrl.
+        static QString generateVxURL(const QString &p_signature, const QString &p_filePath);
+
+        // Get signature from vxUrl.
+        static QString getSignatureFromVxURL(const QString &p_vxUrl);
+
+        // Get file path from vxUrl.
+        static QString getFilePathFromVxURL(const QString &p_vxUrl);
+
+        // Get signature from file path.
+        static QString getSignatureFromFilePath(const QString &p_filePath);
+
+        // Get file path from signature.
+        static QString getFilePathFromSignature(const QString &p_startPath, const QString &p_signature);
+    };
+} // ns vnotex
+
+#endif // VXURLUTILS_H

+ 13 - 1
src/widgets/notebooknodeexplorer.cpp

@@ -26,6 +26,7 @@
 #include "dialogs/viewtagsdialog.h"
 #include <utils/widgetutils.h>
 #include <utils/pathutils.h>
+#include <utils/vxurlutils.h>
 #include <utils/clipboardutils.h>
 #include "notebookmgr.h"
 #include "widgetsfactory.h"
@@ -1348,14 +1349,25 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent, boo
                 this, [this, p_master]() {
                     auto nodes = p_master ? getMasterSelectedNodesAndExternalNodes() : getSlaveSelectedNodesAndExternalNodes();
                     QStringList files;
+                    QStringList vxUrls;
                     for (const auto &node : nodes.first) {
                         files.push_back(node->fetchAbsolutePath());
                     }
                     for (const auto &node : nodes.second) {
                         files.push_back(node->fetchAbsolutePath());
                     }
+                    // find file signature and pinToQuickAccess as VxUrl.
+                    for (const auto &file : files) {
+                        QFileInfo fileInfo(file);
+                        QString signature = VxUrlUtils::getSignatureFromFilePath(file);
+
+                        if (!signature.isEmpty()) {
+                            QString item = VxUrlUtils::generateVxURL(signature, file);
+                            vxUrls.append(item);
+                        }
+                     }
                     if (!files.isEmpty()) {
-                        emit VNoteX::getInst().pinToQuickAccessRequested(files);
+                        emit VNoteX::getInst().pinToQuickAccessRequested(vxUrls);
                     }
                 });
         break;

+ 42 - 2
src/widgets/toolbarhelper.cpp

@@ -18,6 +18,7 @@
 #include <utils/widgetutils.h>
 #include <utils/docsutils.h>
 #include <utils/pathutils.h>
+#include <utils/vxurlutils.h>
 #include "fullscreentoggleaction.h"
 #include <core/configmgr.h>
 #include <core/coreconfig.h>
@@ -26,6 +27,7 @@
 #include <core/markdowneditorconfig.h>
 #include <core/fileopenparameters.h>
 #include <core/htmltemplatehelper.h>
+#include <core/notebookmgr.h>
 #include <core/exception.h>
 #include <task/taskmgr.h>
 #include <unitedentry/unitedentry.h>
@@ -493,7 +495,10 @@ void ToolBarHelper::updateQuickAccessMenu(QMenu *p_menu)
 
     for (const auto &file : quickAccess) {
         auto act = new QWidgetAction(p_menu);
-        auto widget = new LabelWithButtonsWidget(PathUtils::fileName(file), LabelWithButtonsWidget::Delete);
+        QString displayName = PathUtils::fileName(file);
+        QString displayFullName = VxUrlUtils::getFilePathFromVxURL(file);
+
+        auto widget = new LabelWithButtonsWidget(displayName, LabelWithButtonsWidget::Delete);
         p_menu->connect(widget, &LabelWithButtonsWidget::triggered,
                         p_menu, [p_menu, act]() {
                             const auto qaFile = act->data().toString();
@@ -506,7 +511,7 @@ void ToolBarHelper::updateQuickAccessMenu(QMenu *p_menu)
         // @act will own @widget.
         act->setDefaultWidget(widget);
         act->setData(file);
-        act->setToolTip(file);
+        act->setToolTip(displayFullName);
 
         // Must call after setDefaultWidget().
         p_menu->addAction(act);
@@ -776,6 +781,16 @@ void ToolBarHelper::setupMenuButton(MainWindow *p_win, QToolBar *p_toolBar)
 }
 
 void ToolBarHelper::activateQuickAccess(const QString &p_file)
+{
+    if (p_file.startsWith('#')) {
+        activateQuickAccessFromVxUrl(p_file);
+    } else
+    {
+        activateQuickAccessFilePath(p_file);
+    }
+}
+
+void ToolBarHelper::activateQuickAccessFilePath(const QString &p_file)
 {
     const auto &coreConfig = ConfigMgr::getInst().getCoreConfig();
     auto paras = QSharedPointer<FileOpenParameters>::create();
@@ -783,3 +798,28 @@ void ToolBarHelper::activateQuickAccess(const QString &p_file)
 
     emit VNoteX::getInst().openFileRequested(p_file, paras);
 }
+
+void ToolBarHelper::activateQuickAccessFromVxUrl(const QString &p_vx_url)
+{
+    auto notebook = VNoteX::getInst().getNotebookMgr().getCurrentNotebook();
+    if (!notebook) {
+        return;
+    }
+
+    // get 'signature' from format '#signature:filename'
+    QString signature = VxUrlUtils::getSignatureFromVxURL(p_vx_url);
+
+    // get FilePath from Signature from currentNotebook
+    const QString rootPath = notebook->getRootFolderAbsolutePath();
+    const QString filePath = VxUrlUtils::getFilePathFromSignature(rootPath, signature);
+
+    const auto &coreConfig = ConfigMgr::getInst().getCoreConfig();
+    auto paras = QSharedPointer<FileOpenParameters>::create();
+    paras->m_mode = coreConfig.getDefaultOpenMode();
+
+    if (filePath.isEmpty()) {
+        return;
+    }
+
+    emit VNoteX::getInst().openFileRequested(filePath, paras);
+}

+ 5 - 0
src/widgets/toolbarhelper.h

@@ -49,6 +49,11 @@ namespace vnotex
         static void setupMenuButton(MainWindow *p_win, QToolBar *p_toolBar);
 
         static void activateQuickAccess(const QString &p_file);
+
+        static void activateQuickAccessFilePath(const QString &p_file);
+
+        static void activateQuickAccessFromVxUrl(const QString &p_vx_url);
+
     };
 } // ns vnotex
 

+ 9 - 2
src/widgets/viewsplit.cpp

@@ -18,6 +18,7 @@
 #include <core/thememgr.h>
 #include <utils/iconutils.h>
 #include <utils/pathutils.h>
+#include <utils/vxurlutils.h>
 #include "widgetsfactory.h"
 #include <utils/widgetutils.h>
 #include <utils/urldragdroputils.h>
@@ -662,8 +663,14 @@ void ViewSplit::createContextMenuOnTabBar(QMenu *p_menu, int p_tabIdx)
                       [this, p_tabIdx]() {
                           auto win = getViewWindow(p_tabIdx);
                           if (win) {
-                              const QStringList files(win->getBuffer()->getPath());
-                              emit VNoteX::getInst().pinToQuickAccessRequested(files);
+                              const QString filePath = win->getBuffer()->getPath();
+                              QString signature = VxUrlUtils::getSignatureFromFilePath(filePath);
+
+                              if (!signature.isEmpty()) {
+                                  QString quickAccessItem = VxUrlUtils::generateVxURL(signature, filePath);
+                                  const QStringList files(quickAccessItem);
+                                  emit VNoteX::getInst().pinToQuickAccessRequested(files);
+                              }
                           }
                       });