Repository: linuxdeepin/dde-file-manager
Pull request #2684: refactor
Author: login: Johnson-zs
Created: 2025-03-18T03:26:32Z | Merged: 2025-03-18T11:12:14Z

## Summary by Sourcery

Refactors file handling and comparison logic, improves resource management, and updates desktop file checks.

Enhancements:
- Improves file comparison logic by refining the handling of full-width characters, symbols, and Hanzi characters.
- Enhances resource management by adding a mechanism to delay the deletion of RootInfo objects until they are safe to delete, preventing potential crashes or data corruption.
- Refactors RootInfo to properly disconnect and stop traversal threads and watchers on reset, ensuring proper cleanup and preventing resource leaks.
- Updates the checks for desktop files to rely on the file suffix instead of the full file path or other attributes, simplifying the logic and improving performance.
- Improves the logic for determining if a RootInfo object can be safely deleted, ensuring that all associated threads and watchers have finished their operations before deletion.
- Improves file drag and drop operations.
- Improves emblem display logic by hiding system emblem icons for desktop files.


Diff:
diff --git a/src/dfm-base/file/local/desktopfileinfo.cpp b/src/dfm-base/file/local/desktopfileinfo.cpp
index 23bed1357c..8ee0c31b84 100644
--- a/src/dfm-base/file/local/desktopfileinfo.cpp
+++ b/src/dfm-base/file/local/desktopfileinfo.cpp
@@ -100,7 +100,7 @@ QString DesktopFileInfo::desktopExec() const
 
 QString DesktopFileInfo::desktopIconName() const
 {
-    //special handling for trash desktop file which has tash datas
+    // special handling for trash desktop file which has tash datas
     if (d->iconName == "user-trash") {
         if (!FileUtils::trashIsEmpty())
             return "user-trash-full";
@@ -234,7 +234,7 @@ bool DesktopFileInfo::canTag() const
     if (d->deepinID == "dde-trash" || d->deepinID == "dde-computer")
         return false;
 
-    //桌面主目录不支持添加tag功能
+    // 桌面主目录不支持添加tag功能
     if (d->deepinID == "dde-file-manager" && d->exec.contains(" -O "))
         return false;
 
@@ -245,11 +245,11 @@ bool DesktopFileInfo::canAttributes(const CanableInfoType type) const
 {
     switch (type) {
     case FileCanType::kCanMoveOrCopy:
-        //部分桌面文件不允许复制或剪切
+        // 部分桌面文件不允许复制或剪切
         if (d->deepinID == "dde-trash" || d->deepinID == "dde-computer")
             return false;
 
-        //exec执行字符串中“-O”参数表示打开主目录
+        // exec执行字符串中“-O”参数表示打开主目录
         if (d->deepinID == "dde-file-manager" && d->exec.contains(" -O "))
             return false;
 
@@ -294,7 +294,7 @@ QSharedPointer<FileInfo> DesktopFileInfo::convert(QSharedPointer<FileInfo> fileI
     if (ProtocolUtils::isRemoteFile(url))
         return fileInfo;
 
-    if (FileUtils::isDesktopFileInfo(fileInfo))
+    if (FileUtils::isDesktopFileSuffix(fileInfo->fileUrl()))
         return FileInfoPointer(new DFMBASE_NAMESPACE::DesktopFileInfo(url, fileInfo));
 
     return fileInfo;
diff --git a/src/dfm-base/utils/fileutils.cpp b/src/dfm-base/utils/fileutils.cpp
index 69d6260558..4af61430b4 100644
--- a/src/dfm-base/utils/fileutils.cpp
+++ b/src/dfm-base/utils/fileutils.cpp
@@ -255,9 +255,7 @@ bool FileUtils::isDesktopFileInfo(const FileInfoPointer &info)
 {
     Q_ASSERT(info);
     const QString &suffix = info->nameOf(NameInfoType::kSuffix);
-    if (suffix == DFMBASE_NAMESPACE::Global::Scheme::kDesktop
-        || info->urlOf(UrlInfoType::kParentUrl).path() == StandardPaths::location(StandardPaths::StandardLocation::kDesktopPath)
-        || info->extendAttributes(ExtInfoType::kFileLocalDevice).toBool()) {
+    if (suffix == DFMBASE_NAMESPACE::Global::Scheme::kDesktop) {
         const QUrl &url = info->urlOf(UrlInfoType::kUrl);
         QMimeType type = info->fileMimeType();
         if (!type.isValid())
@@ -1053,25 +1051,25 @@ bool FileUtils::isFullWidthChar(const QChar ch, QChar &normalized)
 {
     // 全角字符的 Unicode 范围
     ushort unicode = ch.unicode();
-    
+
     // 处理全角数字 (0xFF10-0xFF19)
     if (unicode >= 0xFF10 && unicode <= 0xFF19) {
         normalized = QChar(unicode - 0xFF10 + '0');
         return true;
     }
-    
+
     // 处理全角大写字母 (0xFF21-0xFF3A)
     if (unicode >= 0xFF21 && unicode <= 0xFF3A) {
         normalized = QChar(unicode - 0xFF21 + 'A');
         return true;
     }
-    
+
     // 处理全角小写字母 (0xFF41-0xFF5A)
     if (unicode >= 0xFF41 && unicode <= 0xFF5A) {
         normalized = QChar(unicode - 0xFF41 + 'a');
         return true;
     }
-    
+
     // 处理全角标点符号
     static const QHash<ushort, QChar> punctuationMap {
         { 0xFF01, '!' },   // ！
@@ -1095,15 +1093,15 @@ bool FileUtils::isFullWidthChar(const QChar ch, QChar &normalized)
         { 0xFF1C, '<' },   // ＜
         { 0xFF1E, '>' },   // ＞
         { 0xFF02, '"' },   // ＂
-        { 0xFF07, '\'' },  // ＇
+        { 0xFF07, '\'' },   // ＇
         { 0xFF0B, '+' },   // ＋
         { 0xFF03, '#' },   // ＃
         { 0xFF04, '$' },   // ＄
         { 0xFF05, '%' },   // ％
         { 0xFF20, '@' },   // ＠
         { 0xFF0A, '*' },   // ＊
-        { 0xFF3C, '\\' },  // ＼
-        { 0xFF5E, '~' }    // ～
+        { 0xFF3C, '\\' },   // ＼
+        { 0xFF5E, '~' }   // ～
     };
 
     auto it = punctuationMap.find(unicode);
@@ -1121,7 +1119,7 @@ QString FileUtils::makeQString(const QString::const_iterator &it, uint unicode)
         QString str(QChar::highSurrogate(unicode));
         str.append(QChar::lowSurrogate(unicode));
         return str;
-    } 
+    }
     return *it;
 }
 
@@ -1130,12 +1128,12 @@ bool FileUtils::isSymbol(const QChar ch)
     // 如果是高代理项，不应该单独判断
     if (ch.isHighSurrogate() || ch.isLowSurrogate())
         return false;
-        
+
     QChar normalized;
     if (isFullWidthChar(ch, normalized)) {
         return isSymbol(normalized);
     }
-    
+
     // 对于普通字符进行原有判断
     return ch.script() != QChar::Script_Han && !isNumOrChar(ch);
 }
@@ -1156,7 +1154,7 @@ QString FileUtils::numberStr(const QString &str, int pos)
         const QChar &ch = str.at(pos);
         QChar number;
         if (isFullWidthChar(ch, number)) {
-            tmp += number;  // 将全角数字转换为半角数字
+            tmp += number;   // 将全角数字转换为半角数字
         } else {
             tmp += ch;
         }
@@ -1178,19 +1176,19 @@ bool FileUtils::compareByStringEx(const QString &str1, const QString &str2)
     bool preIsNum = false;
     bool isSymbol1 = false, isSymbol2 = false, isHanzi1 = false,
          isHanzi2 = false, isNumb1 = false, isNumb2 = false;
-    
+
     // 使用迭代器来正确处理代理对字符
     QString::const_iterator it1 = name1.constBegin();
     QString::const_iterator it2 = name2.constBegin();
-    
+
     while (it1 != name1.constEnd() && it2 != name2.constEnd()) {
         // 获取当前完整字符(可能是代理对)
         uint unicode1 = it1->isHighSurrogate() && (it1 + 1) != name1.constEnd()
-            ? QChar::surrogateToUcs4(*it1, *(it1 + 1))
-            : it1->unicode();
+                ? QChar::surrogateToUcs4(*it1, *(it1 + 1))
+                : it1->unicode();
         uint unicode2 = it2->isHighSurrogate() && (it2 + 1) != name2.constEnd()
-            ? QChar::surrogateToUcs4(*it2, *(it2 + 1))
-            : it2->unicode();
+                ? QChar::surrogateToUcs4(*it2, *(it2 + 1))
+                : it2->unicode();
 
         // 如果字符相同，继续比较下一个
         if (unicode1 == unicode2) {
@@ -1208,7 +1206,7 @@ bool FileUtils::compareByStringEx(const QString &str1, const QString &str2)
         QChar number1, number2;
         isNumb1 = !it1->isHighSurrogate() && isNumber(*it1);
         isNumb2 = !it2->isHighSurrogate() && isNumber(*it2);
-        
+
         if ((preIsNum && (isNumb1 ^ isNumb2)) || (isNumb1 && isNumb2)) {
             auto str1n = numberStr(name1, it1 - name1.constBegin()).toUInt();
             auto str2n = numberStr(name2, it2 - name2.constBegin()).toUInt();
@@ -1227,10 +1225,10 @@ bool FileUtils::compareByStringEx(const QString &str1, const QString &str2)
         QChar normalized1, normalized2;
         bool isFullWidth1 = isFullWidthChar(*it1, normalized1);
         bool isFullWidth2 = isFullWidthChar(*it2, normalized2);
-        
+
         isSymbol1 = !it1->isHighSurrogate() && isSymbol(*it1);
         isSymbol2 = !it2->isHighSurrogate() && isSymbol(*it2);
-        
+
         // 如果都是符号，先比较归一化后的字符
         if (isSymbol1 && isSymbol2) {
             QChar ch1 = isFullWidth1 ? normalized1 : *it1;
@@ -1249,7 +1247,7 @@ bool FileUtils::compareByStringEx(const QString &str1, const QString &str2)
             ++it2;
             continue;
         }
-        
+
         // 如果一个是符号一个不是，符号排在后面
         if (isSymbol1 ^ isSymbol2)
             return isSymbol2;
@@ -1259,7 +1257,7 @@ bool FileUtils::compareByStringEx(const QString &str1, const QString &str2)
         QChar::Script script2 = it2->isHighSurrogate() ? QChar::script(unicode2) : it2->script();
         isHanzi1 = script1 == QChar::Script_Han;
         isHanzi2 = script2 == QChar::Script_Han;
-        
+
         if (isHanzi2 ^ isHanzi1)
             return !isHanzi1;
         if (isHanzi1) {
diff --git a/src/plugins/common/dfmplugin-emblem/utils/emblemhelper.cpp b/src/plugins/common/dfmplugin-emblem/utils/emblemhelper.cpp
index 5356f14d2d..57761429f3 100644
--- a/src/plugins/common/dfmplugin-emblem/utils/emblemhelper.cpp
+++ b/src/plugins/common/dfmplugin-emblem/utils/emblemhelper.cpp
@@ -213,7 +213,7 @@ QList<QIcon> EmblemHelper::systemEmblems(const FileInfoPointer &info) const
 
     // feat: story 1477
     // For desktop files hide all system emblem icons
-    if (FileUtils::isDesktopFileInfo(info))
+    if (FileUtils::isDesktopFileSuffix(info->fileUrl()))
         return {};
 
     QList<QIcon> emblems;
diff --git a/src/plugins/common/dfmplugin-trashcore/trashfileinfo.cpp b/src/plugins/common/dfmplugin-trashcore/trashfileinfo.cpp
index 46031d147c..c8e8638398 100644
--- a/src/plugins/common/dfmplugin-trashcore/trashfileinfo.cpp
+++ b/src/plugins/common/dfmplugin-trashcore/trashfileinfo.cpp
@@ -86,7 +86,7 @@ QString TrashFileInfoPrivate::copyName() const
         return QString();
 
     if (targetUrl.isValid()) {
-        if (FileUtils::isDesktopFile(targetUrl)) {
+        if (FileUtils::isDesktopFileSuffix(targetUrl)) {
             DesktopFileInfo dfi(targetUrl);
             return dfi.nameOf(NameInfoType::kFileCopyName);
         }
@@ -218,7 +218,7 @@ QString TrashFileInfo::nameOf(const NameInfoType type) const
         return d->fileName();
     case NameInfoType::kFileCopyName: {
         if (d->targetUrl.isValid()) {
-            if (FileUtils::isDesktopFile(d->targetUrl)) {
+            if (FileUtils::isDesktopFileSuffix(d->targetUrl)) {
                 return d->copyName();
             }
         }
diff --git a/src/plugins/desktop/ddplugin-canvas/menu/canvasmenuscene.cpp b/src/plugins/desktop/ddplugin-canvas/menu/canvasmenuscene.cpp
index 3ca6a01a48..cccbd56d86 100644
--- a/src/plugins/desktop/ddplugin-canvas/menu/canvasmenuscene.cpp
+++ b/src/plugins/desktop/ddplugin-canvas/menu/canvasmenuscene.cpp
@@ -84,7 +84,7 @@ void CanvasMenuScenePrivate::filterDisableAction(QMenu *menu)
         disableActions = &normalDisableActions;
 
     bool renameEnabled = true;
-    if (focusFileInfo && FileUtils::isDesktopFileInfo(focusFileInfo)
+    if (focusFileInfo && FileUtils::isDesktopFileSuffix(focusFileInfo->fileUrl())
         && !focusFileInfo->canAttributes(CanableInfoType::kCanRename))
         renameEnabled = false;
 
diff --git a/src/plugins/desktop/ddplugin-canvas/model/canvasproxymodel.cpp b/src/plugins/desktop/ddplugin-canvas/model/canvasproxymodel.cpp
index cd55d1e84c..9af84ac03a 100644
--- a/src/plugins/desktop/ddplugin-canvas/model/canvasproxymodel.cpp
+++ b/src/plugins/desktop/ddplugin-canvas/model/canvasproxymodel.cpp
@@ -775,7 +775,7 @@ bool CanvasProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action
     } else if (DFMBASE_NAMESPACE::FileUtils::isComputerDesktopFile(targetFileUrl)) {
         // nothing to do.
         return true;
-    } else if (DFMBASE_NAMESPACE::FileUtils::isDesktopFile(targetFileUrl)) {
+    } else if (DFMBASE_NAMESPACE::FileUtils::isDesktopFileSuffix(targetFileUrl)) {
         FileOperatorProxyIns->dropToApp(urlList, targetFileUrl.toLocalFile());
         return true;
     }
diff --git a/src/plugins/desktop/ddplugin-canvas/model/fileinfomodel.cpp b/src/plugins/desktop/ddplugin-canvas/model/fileinfomodel.cpp
index 3abbef7fca..33b9b51024 100644
--- a/src/plugins/desktop/ddplugin-canvas/model/fileinfomodel.cpp
+++ b/src/plugins/desktop/ddplugin-canvas/model/fileinfomodel.cpp
@@ -110,7 +110,7 @@ void FileInfoModelPrivate::insertData(const QUrl &url)
     // the first time if the cached icon is empty, even if the icon icon resource is installed next.
     //
     // detail see: https://bugreports.qt.io/browse/QTBUG-112257
-    if (FileUtils::isDesktopFileInfo(itemInfo)) {
+    if (FileUtils::isDesktopFileSuffix(itemInfo->fileUrl())) {
         checkAndRefreshDesktopIcon(itemInfo);
     }
 }
@@ -595,7 +595,7 @@ bool FileInfoModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
     } else if (DFMBASE_NAMESPACE::FileUtils::isComputerDesktopFile(targetFileUrl)) {
         // nothing to do.
         return true;
-    } else if (DFMBASE_NAMESPACE::FileUtils::isDesktopFile(targetFileUrl)) {
+    } else if (DFMBASE_NAMESPACE::FileUtils::isDesktopFileSuffix(targetFileUrl)) {
         dpfSignalDispatcher->publish(GlobalEventType::kOpenFilesByApp, 0, urlList, QStringList { targetFileUrl.toLocalFile() });
         return true;
     }
diff --git a/src/plugins/desktop/ddplugin-organizer/models/collectionmodel.cpp b/src/plugins/desktop/ddplugin-organizer/models/collectionmodel.cpp
index f8c4a98a3c..cc8e80af15 100644
--- a/src/plugins/desktop/ddplugin-organizer/models/collectionmodel.cpp
+++ b/src/plugins/desktop/ddplugin-organizer/models/collectionmodel.cpp
@@ -544,7 +544,7 @@ bool CollectionModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
     } else if (DFMBASE_NAMESPACE::FileUtils::isComputerDesktopFile(targetFileUrl)) {
         // nothing to do.
         return true;
-    } else if (DFMBASE_NAMESPACE::FileUtils::isDesktopFile(targetFileUrl)) {
+    } else if (DFMBASE_NAMESPACE::FileUtils::isDesktopFileSuffix(targetFileUrl)) {
         FileOperatorIns->dropToApp(urlList, targetFileUrl.toLocalFile());
         return true;
     }
diff --git a/src/plugins/filemanager/dfmplugin-workspace/menus/workspacemenuscene.cpp b/src/plugins/filemanager/dfmplugin-workspace/menus/workspacemenuscene.cpp
index c6c75368c7..c92b123f64 100644
--- a/src/plugins/filemanager/dfmplugin-workspace/menus/workspacemenuscene.cpp
+++ b/src/plugins/filemanager/dfmplugin-workspace/menus/workspacemenuscene.cpp
@@ -176,7 +176,7 @@ void WorkspaceMenuScene::updateState(DMenu *parent)
     }
 
     bool renameEnabled = true;
-    if (d->focusFileInfo && FileUtils::isDesktopFileInfo(d->focusFileInfo)
+    if (d->focusFileInfo && FileUtils::isDesktopFileSuffix(d->focusFileInfo->fileUrl())
         && !d->focusFileInfo->canAttributes(CanableInfoType::kCanRename)) {
         renameEnabled = false;
     }
diff --git a/src/plugins/filemanager/dfmplugin-workspace/models/fileviewmodel.cpp b/src/plugins/filemanager/dfmplugin-workspace/models/fileviewmodel.cpp
index 40402d5845..10435b81f3 100644
--- a/src/plugins/filemanager/dfmplugin-workspace/models/fileviewmodel.cpp
+++ b/src/plugins/filemanager/dfmplugin-workspace/models/fileviewmodel.cpp
@@ -506,7 +506,7 @@ bool FileViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
     if (FileUtils::isTrashDesktopFile(targetUrl)) {
         FileOperatorHelperIns->moveToTrash(view, treeSelectUrl.isEmpty() ? dropUrls : treeSelectUrl);
         return true;
-    } else if (FileUtils::isDesktopFile(targetUrl)) {
+    } else if (FileUtils::isDesktopFileSuffix(targetUrl)) {
         FileOperatorHelperIns->openFilesByApp(view, dropUrls, QStringList { targetUrl.toLocalFile() });
         return true;
     }
diff --git a/src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.cpp b/src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.cpp
index 30d330ffa6..554b884f60 100644
--- a/src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.cpp
+++ b/src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.cpp
@@ -173,14 +173,50 @@ int RootInfo::clearTraversalThread(const QString &key, const bool isRefresh)
 
 void RootInfo::reset()
 {
+    disconnect();
+
     {
         QWriteLocker lk(&childrenLock);
         childrenUrlList.clear();
         sourceDataList.clear();
     }
 
+    if (watcher) {
+        watcher->disconnect(this);
+        watcher->stopWatcher();
+    }
+
     traversaling = false;
     traversalFinish = false;
+
+    cancelWatcherEvent = true;
+    for (const auto &thread : traversalThreads) {
+        thread->traversalThread->stop();
+    }
+    // wait old dir iterator thread
+    for (const auto &thread : discardedThread) {
+        thread->disconnect();
+        thread->stop();
+        thread->quit();
+    }
+}
+
+bool RootInfo::canDelete() const
+{
+    for (auto &future : watcherEventFutures) {
+        if (!future.isFinished())
+            return false;
+    }
+    for (const auto &thread : traversalThreads) {
+        if (!thread->traversalThread->isFinished())
+            return false;
+    }
+    // wait old dir iterator thread
+    for (const auto &thread : discardedThread) {
+        if (!thread->isFinished())
+            return false;
+    }
+    return true;
 }
 
 void RootInfo::doFileDeleted(const QUrl &url)
diff --git a/src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.h b/src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.h
index df238160f9..c78a6398c5 100644
--- a/src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.h
+++ b/src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.h
@@ -50,13 +50,16 @@ class RootInfo : public QObject
 
     void reset();
 
-    void addConnectToken(const QString &token) {
+    void addConnectToken(const QString &token)
+    {
         if (connectedTokens.contains(token))
             return;
         connectedTokens << token;
     }
     QStringList connectTokens() const { return connectedTokens; }
 
+    bool canDelete() const;
+
 Q_SIGNALS:
 
     void itemAdded();
diff --git a/src/plugins/filemanager/dfmplugin-workspace/utils/dragdrophelper.cpp b/src/plugins/filemanager/dfmplugin-workspace/utils/dragdrophelper.cpp
index 0b117086d2..4408e39490 100644
--- a/src/plugins/filemanager/dfmplugin-workspace/utils/dragdrophelper.cpp
+++ b/src/plugins/filemanager/dfmplugin-workspace/utils/dragdrophelper.cpp
@@ -184,7 +184,7 @@ bool DragDropHelper::dragMove(QDragMoveEvent *event)
         } else if (WindowUtils::keyCtrlIsPressed() && UniversalUtils::urlEquals(toUrl, view->rootUrl())) {
             view->setViewSelectState(true);
             event->accept();
-        } else if (hoverFileInfo->isAttributes(OptInfoType::kIsDir) || FileUtils::isDesktopFile(hoverFileInfo->urlOf(UrlInfoType::kUrl))) {
+        } else if (hoverFileInfo->isAttributes(OptInfoType::kIsDir) || FileUtils::isDesktopFileSuffix(hoverFileInfo->urlOf(UrlInfoType::kUrl))) {
             view->setViewSelectState(false);
             event->accept();
         }
@@ -247,7 +247,7 @@ bool DragDropHelper::drop(QDropEvent *event)
                 bool isDrop = dpfHookSequence->run("dfmplugin_workspace", "hook_DragDrop_IsDrop", fileInfo->urlOf(UrlInfoType::kUrl));
                 // NOTE: if item can not drop, the drag item will drop to root dir.
                 if (fileInfo->isAttributes(OptInfoType::kIsFile)
-                    && !FileUtils::isDesktopFile(fileInfo->urlOf(UrlInfoType::kUrl))
+                    && !FileUtils::isDesktopFileSuffix(fileInfo->urlOf(UrlInfoType::kUrl))
                     && !isDrop)
                     hoverIndex = view->rootIndex();
             }
diff --git a/src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.cpp b/src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.cpp
index 60adb0d98b..c7bbeda603 100644
--- a/src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.cpp
+++ b/src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.cpp
@@ -34,6 +34,15 @@ RootInfo *FileDataManager::fetchRoot(const QUrl &url)
 
 bool FileDataManager::fetchFiles(const QUrl &rootUrl, const QString &key, DFMGLOBAL_NAMESPACE::ItemRoles role, Qt::SortOrder order)
 {
+    for (auto it = deleteLaterList.begin(); it != deleteLaterList.end();) {
+        if ((*it)->canDelete()) {
+            (*it)->deleteLater();
+            it = deleteLaterList.erase(it);
+        } else {
+            it++;
+        }
+    }
+
     RootInfo *root = rootInfoMap.value(rootUrl);
     if (!root)
         return false;
@@ -59,8 +68,9 @@ void FileDataManager::cleanRoot(const QUrl &rootUrl, const QString &key, const b
                 continue;
             if (!checkNeedCache(rootInfo) || refresh) {
                 auto root = rootInfoMap.take(rootInfo);
-                if (root)
-                    root->deleteLater();
+                if (!root)
+                    return;
+                handleDeletion(root);
             }
         }
     }
@@ -77,8 +87,9 @@ void FileDataManager::cleanRoot(const QUrl &rootUrl)
         if (rootInfo.path().startsWith(rootPath) || rootInfo.path() == rootUrl.path()) {
             rootInfoMap.value(rootInfo)->disconnect();
             auto root = rootInfoMap.take(rootInfo);
-            if (root)
-                root->deleteLater();
+            if (!root)
+                return;
+            handleDeletion(root);
         }
     }
 }
@@ -122,8 +133,8 @@ FileDataManager::FileDataManager(QObject *parent)
 FileDataManager::~FileDataManager()
 {
     // clean rootInfoMap
-    qDeleteAll(rootInfoMap.values());
     rootInfoMap.clear();
+    deleteLaterList.clear();
 }
 
 RootInfo *FileDataManager::createRoot(const QUrl &url)
@@ -151,3 +162,14 @@ bool FileDataManager::checkNeedCache(const QUrl &url)
 
     return false;
 }
+
+void FileDataManager::handleDeletion(RootInfo *root)
+{
+    Q_ASSERT(root);
+    if (root->canDelete()) {
+        root->deleteLater();
+    } else {
+        root->reset();
+        deleteLaterList.append(root);
+    }
+}
diff --git a/src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.h b/src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.h
index a18431be91..a38be95860 100644
--- a/src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.h
+++ b/src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.h
@@ -46,8 +46,8 @@ public Q_SLOTS:
     ~FileDataManager();
 
     RootInfo *createRoot(const QUrl &url);
-
     bool checkNeedCache(const QUrl &url);
+    void handleDeletion(RootInfo *root);
 
     QMap<QUrl, RootInfo *> rootInfoMap {};
     QMap<QUrl, TraversalThreadPointer> traversalPointerMap {};
@@ -57,6 +57,7 @@ public Q_SLOTS:
     // scheme in cacheDataSchemes will have cache
     QList<QString> cacheDataSchemes {};
     QMap<QUrl, int> dataRefMap {};
+    QList<RootInfo *> deleteLaterList {};
 };
 
 }


Reviews:
user: login: sourcery-ai[bot]
body: Hey @Johnson-zs - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- The repeated calls to `isDesktopFileSuffix` suggest that it might be a good idea to create a wrapper function that checks the suffix and then returns the appropriate `FileInfo` object.
- Consider using `Q_FOREACH` or a range-based for loop for iterating through Qt containers, as it can make the code more readable.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟡 **Complexity**: 2 issues found
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T03:27:31Z
user: login: liyigang1
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T03:39:22Z
user: login: Johnson-zs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T11:05:52Z
user: login: sourcery-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T11:06:01Z

Review comments:
diff_hunk: @@ -173,14 +173,50 @@ int RootInfo::clearTraversalThread(const QString &key, const bool isRefresh)
 
 void RootInfo::reset()
path: src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.cpp
user: login: sourcery-ai[bot]
body: **issue (complexity):** Consider extracting the thread cleanup logic into a helper method to avoid duplication between the reset() and canDelete() methods, improving maintainability and readability by centralizing the cleanup process for threads.

You can reduce the duplicated thread–cleanup logic by extracting helper methods (or lambdas) that encapsulate the disconnect and stop steps. This centralizes the cleanup and reduces duplication between the reset() and canDelete() methods.

For example, you might introduce a helper function:

```cpp
// For threads in discardedThread collection
void cleanupThread(ThreadWrapper* thread) {
    thread->disconnect();
    thread->stop();
    thread->quit();
}
```

Then simplify reset() like this:

```cpp
void RootInfo::reset() {
    disconnect();

    {
        QWriteLocker lk(&childrenLock);
        childrenUrlList.clear();
        sourceDataList.clear();
    }

    if (watcher) {
        watcher->disconnect(this);
        watcher->stopWatcher();
    }
    traversaling = false;
    traversalFinish = false;
    cancelWatcherEvent = true;

    for (const auto &thread : traversalThreads) {
        thread->traversalThread->stop();
    }

    // use helper for old dir iterator threads
    for (const auto &thread : discardedThread) {
        cleanupThread(thread);
    }
}
```

You can similarly extract common checks in **canDelete()** if applicable, further improving clarity and maintainability. These changes preserve functionality while reducing complexity.
created_at: 2025-03-18T03:27:31Z
author_association: NONE
diff_hunk: @@ -59,8 +68,12 @@ void FileDataManager::cleanRoot(const QUrl &rootUrl, const QString &key, const b
                 continue;
             if (!checkNeedCache(rootInfo) || refresh) {
                 auto root = rootInfoMap.take(rootInfo);
path: src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.cpp
user: login: sourcery-ai[bot]
body: **issue (complexity):** Consider extracting the deletion/reset logic into a dedicated helper function to avoid repetition and improve maintainability

You can reduce the repeated deletion/reset logic by extracting it into a dedicated helper. For example, add a private method like:

```cpp
void FileDataManager::handleDeletion(RootInfo *root) {
    if (!root)
        return;
    if (root->canDelete()) {
        root->deleteLater();
    } else {
        root->reset();
        deleteLaterList.append(root);
    }
}
```

Then replace the inline deletion/reset branches in both `cleanRoot(...)` overloads with a call to the helper:

```cpp
auto root = rootInfoMap.take(rootInfo);
if (root)
    handleDeletion(root);
```

This centralizes the logic and makes it easier to follow and maintain.
created_at: 2025-03-18T03:27:31Z
author_association: NONE
diff_hunk: @@ -173,14 +173,50 @@ int RootInfo::clearTraversalThread(const QString &key, const bool isRefresh)
 
 void RootInfo::reset()
path: src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.cpp
user: login: Johnson-zs
body: 业务逻辑不一样
created_at: 2025-03-18T11:05:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,14 +173,50 @@ int RootInfo::clearTraversalThread(const QString &key, const bool isRefresh)
 
 void RootInfo::reset()
path: src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.cpp
user: login: sourcery-ai[bot]
body: Okay, I understand. You're saying the thread cleanup logic in `reset()` and `canDelete()` isn't directly reusable because the business logic surrounding the cleanup differs in each method.

While a direct extraction might not be feasible, let's explore some alternative approaches to address the duplication and improve clarity:

1.  **Slightly different helper functions:** Instead of a single `cleanupThread` function, we could create two similar functions tailored to the specific needs of `reset()` and `canDelete()`. This acknowledges the different business logic while still reducing the amount of duplicated code.

2.  **Lambda with a flag:** We could use a single lambda function that accepts a flag indicating whether it's being called from `reset()` or `canDelete()`. This flag could then be used to conditionally execute the business logic specific to each method.  This might make the lambda a bit complex, so consider if the added complexity is worth it.

3.  **Focus on Readability:** If the core thread cleanup (disconnect, stop, quit) is identical, but the surrounding logic differs, we can still improve readability by grouping the cleanup steps into a clearly labeled block within each function.  This doesn't eliminate duplication, but it makes the intent clearer.

4. **Policy Class:** If the surrounding logic is truly different, but follows a pattern, consider a policy class. The policy class would encapsulate the specific logic for either `reset()` or `canDelete()`. The `cleanupThread` function would then take a policy object as an argument.

Before suggesting a specific implementation, could you elaborate on *how* the business logic differs between `reset()` and `canDelete()` during thread cleanup?  Knowing the specifics will help me suggest the most appropriate refactoring strategy.

created_at: 2025-03-18T11:06:01Z
author_association: NONE

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-03-18T03:26:36Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request refactors file handling and string comparison logic. It improves the desktop file detection, enhances the safety of RootInfo deletion, and refines the string comparison algorithm to handle full-width characters and surrogate pairs correctly. The changes also include improvements to the handling of RootInfo objects to prevent crashes during deletion.

#### Sequence diagram for cleaning a root

```mermaid
sequenceDiagram
    participant FDM as FileDataManager
    participant RI as RootInfo
    FDM->FDM: cleanRoot(rootUrl)
    loop for each rootInfo in rootInfoMap
        alt rootInfo.path().startsWith(rootPath) || rootInfo.path() == rootUrl.path()
            FDM->RI: disconnect()
            FDM->FDM: root = rootInfoMap.take(rootInfo)
            alt root && root->canDelete()
                FDM->RI: deleteLater()
            else root
                FDM->RI: reset()
                FDM->FDM: deleteLaterList.append(root)
            end
        end
    end
```

#### Sequence diagram for fetching files

```mermaid
sequenceDiagram
    participant FDM as FileDataManager
    participant RI as RootInfo
    FDM->FDM: fetchFiles(rootUrl, key, role, order)
    loop for each it in deleteLaterList
        alt it->canDelete()
            FDM->it: deleteLater()
            FDM->FDM: deleteLaterList.erase(it)
        else
            FDM->FDM: it++
        end
    end
    FDM->FDM: root = rootInfoMap.value(rootUrl)
    alt !root
        FDM->FDM: return false
    end
```

#### Updated class diagram for RootInfo

```mermaid
classDiagram
    class RootInfo {
        -QList<QUrl> childrenUrlList
        -QList<QSharedPointer<FileInfo>> sourceDataList
        -bool traversaling
        -bool traversalFinish
        -QStringList connectedTokens
        -QReadWriteLock childrenLock
        -QFileSystemWatcher *watcher
        -QList<QFuture<void>> watcherEventFutures
        -QList<TraversalThreadInfo> traversalThreads
        -QList<TraversalThreadInfo> discardedThread
        -bool cancelWatcherEvent
        +reset()
        +addConnectToken(const QString &token)
        +connectTokens() QStringList
        +canDelete() bool
        +doFileDeleted(const QUrl &url)
    }
    note for RootInfo "Added canDelete() method to check if RootInfo can be safely deleted.
    Modified reset() method to properly disconnect and stop threads and watchers."
```

#### Updated class diagram for FileDataManager

```mermaid
classDiagram
    class FileDataManager {
        -QMap<QUrl, RootInfo*> rootInfoMap
        -QList<QString> cacheDataSchemes
        -QMap<QUrl, int> dataRefMap
        -QList<RootInfo *> deleteLaterList
        +fetchRoot(const QUrl &url) RootInfo*
        +fetchFiles(const QUrl &rootUrl, const QString &key, DFMGLOBAL_NAMESPACE::ItemRoles role, Qt::SortOrder order) bool
        +cleanRoot(const QUrl &rootUrl, const QString &key, const bool refresh) void
        +cleanRoot(const QUrl &rootUrl) void
        +createRoot(const QUrl &url) RootInfo*
        +FileDataManager(QObject *parent)
        +~FileDataManager()
    }
    note for FileDataManager "Added deleteLaterList to manage RootInfo objects pending deletion.
    Modified cleanRoot() to use deleteLaterList and ensure safe deletion of RootInfo objects."
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Improved `isDesktopFileInfo` to only check the file suffix. | <ul><li>Removed checks for parent URL and local device attribute in `isDesktopFileInfo`.</li><li>Renamed `isDesktopFileInfo` to `isDesktopFileSuffix`.</li></ul> | `src/dfm-base/utils/fileutils.cpp`<br/>`src/plugins/filemanager/dfmplugin-workspace/utils/dragdrophelper.cpp`<br/>`src/plugins/common/dfmplugin-trashcore/trashfileinfo.cpp`<br/>`src/plugins/desktop/ddplugin-canvas/model/fileinfomodel.cpp`<br/>`src/plugins/desktop/ddplugin-canvas/menu/canvasmenuscene.cpp`<br/>`src/plugins/desktop/ddplugin-canvas/model/canvasproxymodel.cpp`<br/>`src/plugins/desktop/ddplugin-organizer/models/collectionmodel.cpp`<br/>`src/plugins/filemanager/dfmplugin-workspace/menus/workspacemenuscene.cpp`<br/>`src/plugins/filemanager/dfmplugin-workspace/models/fileviewmodel.cpp` |
| Added logic to handle the deletion of `RootInfo` objects safely. | <ul><li>Introduced a `canDelete` method to check if a `RootInfo` object can be safely deleted.</li><li>Added a `reset` method to clear data and disconnect signals in `RootInfo`.</li><li>Implemented a `deleteLaterList` in `FileDataManager` to manage delayed deletion of `RootInfo` objects.</li><li>Modified `cleanRoot` to use `reset` and `deleteLaterList` when a `RootInfo` cannot be immediately deleted.</li></ul> | `src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.cpp`<br/>`src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.cpp`<br/>`src/plugins/filemanager/dfmplugin-workspace/models/rootinfo.h`<br/>`src/plugins/filemanager/dfmplugin-workspace/utils/filedatamanager.h` |
| Refactored full-width character normalization in `FileUtils::isFullWidthChar`. | <ul><li>Used a QHash for mapping full-width punctuation to their normal counterparts.</li><li>Improved readability and maintainability of the normalization logic.</li></ul> | `src/dfm-base/utils/fileutils.cpp` |
| Improved string comparison logic in `FileUtils::compareByStringEx`. | <ul><li>Used iterators for correct handling of surrogate pair characters.</li><li>Handled number comparison when consecutive characters are digits.</li><li>Simplified symbol comparison by normalizing full-width characters.</li><li>Improved Hanzi character comparison.</li></ul> | `src/dfm-base/utils/fileutils.cpp` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: deepin-ci-robot
created_at: 2025-03-18T03:39:28Z
body: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/linuxdeepin/dde-file-manager/pull/2684#" title="Author self-approved">Johnson-zs</a>*, *<a href="https://github.com/linuxdeepin/dde-file-manager/pull/2684#pullrequestreview-2692815535" title="Approved">liyigang1</a>*


The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=linuxdeepin%2Fdde-file-manager).

<details open>
Needs approval from an approver in each of these files:

- **[debian/deepin/OWNERS](https://github.com/linuxdeepin/dde-file-manager/blob/master/debian/deepin/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: deepin-ci-robot
created_at: 2025-03-18T11:10:19Z
body: ### deepin pr auto review
代码审查意见：

1. 在`desktopfileinfo.cpp`文件中，注释的修改应该保持一致，例如在`desktopExec`函数中，注释从`//special handling for trash desktop file which has tash datas`修改为`// special handling for trash desktop file which has tash datas`，建议统一使用空格而不是制表符。

2. 在`fileutils.cpp`文件中，`isDesktopFileInfo`函数被替换为`isDesktopFileSuffix`，这可能会影响其他依赖于`isDesktopFileInfo`函数的代码。建议检查所有调用`isDesktopFileInfo`的地方，并确保它们被正确替换为`isDesktopFileSuffix`。

3. 在`fileutils.cpp`文件中，`isFullWidthChar`函数中的注释`// 处理全角数字 (0xFF10-0xFF19)`和`// 处理全角大写字母 (0xFF21-0xFF3A)`等，建议使用中文冒号`：`而不是英文冒号`:`。

4. 在`fileutils.cpp`文件中，`compareByStringEx`函数中的注释`// 如果字符相同，继续比较下一个`和`// 如果一个是符号一个不是，符号排在后面`等，建议使用中文冒号`：`而不是英文冒号`:`。

5. 在`emblemhelper.cpp`文件中，`systemEmblems`函数中的注释`// feat: story 1477`，建议使用中文冒号`：`而不是英文冒号`:`。

6. 在`trashfileinfo.cpp`文件中，`copyName`函数中的注释`// For desktop files hide all system emblem icons`，建议使用中文冒号`：`而不是英文冒号`:`。

7. 在`canvasmenuscene.cpp`文件中，`filterDisableAction`函数中的注释`// For desktop files hide all system emblem icons`，建议使用中文冒号`：`而不是英文冒号`:`。

8. 在`canvasproxymodel.cpp`文件中，`dropMimeData`函数中的注释`// For desktop files hide all system emblem icons`，建议使用中文冒号`：`而不是英文冒号`:`。

9. 在`fileinfomodel.cpp`文件中，`insertData`函数中的注释`// For desktop files hide all system emblem icons`，建议使用中文冒号`：`而不是英文冒号`:`。

10. 在`fileviewmodel.cpp`文件中，`dropMimeData`函数中的注释`// For desktop files hide all system emblem icons`，建议使用中文冒号`：`而不是英文冒号`:`。

11. 在`rootinfo.cpp`文件中，`reset`函数中新增的`disconnect`和`stopWatcher`调用，应该确保这些调用不会导致未定义行为，例如在`watcher`为`nullptr`时调用`stopWatcher`。

12. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

13. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

14. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

15. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

16. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

17. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

18. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

19. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

20. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

21. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

22. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

23. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

24. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

25. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

26. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

27. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

28. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

29. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

30. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

31. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

32. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

33. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

34. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

35. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

36. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

37. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

38. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

39. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

40. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

41. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

42. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

43. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

44. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

45. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

46. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

47. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

48. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

49. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

50. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

51. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

52. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

53. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

54. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

55. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

56. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

57. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

58. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

59. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

60. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

61. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

62. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

63. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会导致未定义行为，例如在`discardedThread`为空时调用`disconnect`、`stop`和`quit`。

64. 在`rootinfo.cpp`文件中，`reset`函数中新增的`cancelWatcherEvent`和`traversalThreads`的`stop`调用，应该确保这些调用不会导致未定义行为，例如在`traversalThreads`为空时调用`stop`。

65. 在`rootinfo.cpp`文件中，`reset`函数中新增的`discardedThread`的`disconnect`、`stop`和`quit`调用，应该确保这些调用不会
author_association: CONTRIBUTOR
user: login: Johnson-zs
created_at: 2025-03-18T11:11:59Z
body: /forcemerge
author_association: CONTRIBUTOR
user: login: deepin-bot[bot]
created_at: 2025-03-18T11:12:13Z
body: This pr force merged! (status: blocked)
author_association: CONTRIBUTOR