Skip to content

Conversation

@LiHua000
Copy link
Contributor

@LiHua000 LiHua000 commented Nov 11, 2025

  • copy aliased entries into a temp directory before invoking createArchive
  • update FileEntry paths to point at the real copies and clear aliases
  • cleanup the temp copies on success, failure, cancel, and reset
  • ensures libzip sees actual files instead of symlink paths

Log: fix bug

Bug: https://pms.uniontech.com/bug-view-340081.html

Summary by Sourcery

Fix compression of renamed files by copying aliased entries into a temporary directory, updating their paths for archiving, and cleaning up afterwards.

Bug Fixes:

  • Ensure renamed files are actually copied and preserved during compression by staging aliased entries in a real temporary directory before archiving.

Enhancements:

  • Add prepareCompressAliasEntries and cleanupCompressAliasEntries methods along with a copyDirectoryRecursively utility to handle file copying and cleanup.
  • Integrate alias preparation and cleanup calls throughout the compression workflow (start, success, error, cancel, reset).
  • Introduce MainWindow members to track the temporary alias directory and cleanup state.

- copy aliased entries into a temp directory before invoking createArchive
- update FileEntry paths to point at the real copies and clear aliases
- cleanup the temp copies on success, failure, cancel, and reset
- ensures libzip sees actual files instead of symlink paths

Log: fix bug

Bug: https://pms.uniontech.com/bug-view-340081.html
@sourcery-ai
Copy link

sourcery-ai bot commented Nov 11, 2025

Reviewer's Guide

Adds a temp‐copy workflow for aliased entries so compression always operates on real files: introduces a recursive directory copy helper, prepare/cleanup routines for alias entries, updates FileEntry paths, and hooks these routines into the compression success, failure, cancel, and reset flows.

Sequence diagram for compression workflow with alias file preparation and cleanup

sequenceDiagram
    participant User
    participant MainWindow
    participant CompressPage
    participant FileSystem
    participant ArchiveJob

    User->>MainWindow: Initiate compression
    MainWindow->>CompressPage: getEntrys()
    CompressPage-->>MainWindow: Return listEntry
    MainWindow->>MainWindow: prepareCompressAliasEntries(listEntry)
    MainWindow->>FileSystem: Copy aliased files/directories to temp
    FileSystem-->>MainWindow: Temp files/directories created
    MainWindow->>ArchiveJob: Start compression with updated paths
    ArchiveJob-->>MainWindow: Compression result (success/failure/cancel)
    MainWindow->>MainWindow: cleanupCompressAliasEntries()
    MainWindow->>FileSystem: Remove temp files/directories
    FileSystem-->>MainWindow: Temp cleaned up
Loading

Class diagram for MainWindow and FileEntry changes

classDiagram
    class MainWindow {
        +QString m_strCompressAliasRoot
        +bool m_needCleanupCompressAlias
        +bool prepareCompressAliasEntries(QList<FileEntry>&)
        +void cleanupCompressAliasEntries()
    }
    class FileEntry {
        +QString strFullPath
        +QString strAlias
        +bool isDirectory
    }
    MainWindow "1" -- "*" FileEntry : manages
Loading

Flow diagram for alias file preparation and cleanup during compression

flowchart TD
    A["Start Compression"] --> B["Get FileEntry list"]
    B --> C{"Any entries with alias?"}
    C -- Yes --> D["Create temp directory"]
    D --> E["Copy aliased files/directories to temp"]
    E --> F["Update FileEntry paths, clear alias"]
    F --> G["Proceed with compression"]
    G --> H{"Compression finished (success/failure/cancel/reset)?"}
    H -- Yes --> I["Cleanup temp alias files"]
    C -- No --> G
    I --> J["End"]
Loading

File-Level Changes

Change Details Files
Add directory copy helper and required includes
  • Imported QFile, QFileInfo, QDir, QDirIterator, QUuid
  • Defined copyDirectoryRecursively in anonymous namespace for file/dir copying
src/source/mainwindow.cpp
Implement prepareCompressAliasEntries to stage renamed entries
  • Create per-process temp root and UUID subdirectory
  • Copy each aliased file or directory into temp, update entry.strFullPath and clear strAlias
  • Set m_strCompressAliasRoot and m_needCleanupCompressAlias flags
src/source/mainwindow.cpp
src/source/mainwindow.h
Implement cleanupCompressAliasEntries to remove temp files
  • Recursively delete aliasRoot and clear flags
src/source/mainwindow.cpp
src/source/mainwindow.h
Integrate prepare/cleanup routines into compression flow
  • Call prepareCompressAliasEntries at start of slotCompress
  • Invoke cleanupCompressAliasEntries on success, error, cancel, reset, and plugin-absent paths
src/source/mainwindow.cpp

Tips and commands

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!

Customizing Your Experience

Access your dashboard 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

@deepin-ci-robot
Copy link

deepin pr auto review

我来对这个代码变更进行详细审查:

  1. 代码逻辑分析:
  • 新增了一个递归复制目录的函数copyDirectoryRecursively
  • 添加了两个新成员函数prepareCompressAliasEntriescleanupCompressAliasEntries
  • 添加了两个新的成员变量m_strCompressAliasRootm_needCleanupCompressAlias
  1. 代码质量问题:
  • copyDirectoryRecursively函数中,使用了QDir::separator()来拼接路径,但更好的做法是使用QDir::toNativeSeparators()或使用QFileInfo来处理路径,这样可以确保跨平台兼容性。
  • prepareCompressAliasEntries中,错误处理存在重复代码,可以抽取为单独的函数。
  1. 性能问题:
  • copyDirectoryRecursively函数没有对大文件进行优化,可能会在复制大文件时造成UI阻塞。建议使用异步方式处理大文件复制。
  • 临时目录的创建和清理操作可能会频繁触发磁盘IO,可以考虑使用内存文件系统来提升性能。
  1. 安全问题:
  • 临时目录使用了可预测的路径(TEMPPATH + processID),这可能存在安全风险。建议使用更安全的临时目录生成方式。
  • 在复制文件时没有检查文件权限和磁盘空间,可能导致操作失败。

改进建议:

namespace {
bool copyDirectoryRecursively(const QString &sourcePath, const QString &targetPath)
{
    QDir sourceDir(sourcePath);
    if (!sourceDir.exists()) {
        return false;
    }

    // 使用QFileInfo处理路径,确保跨平台兼容性
    QFileInfo targetInfo(targetPath);
    QString targetPathClean = targetInfo.absoluteFilePath();
    
    if (!QDir().mkpath(targetPathClean)) {
        return false;
    }

    const QFileInfoList entries = sourceDir.entryInfoList(
        QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files | QDir::Hidden | QDir::System);
    
    for (const QFileInfo &info : entries) {
        QString srcFilePath = info.absoluteFilePath();
        QString dstFilePath = targetPathClean + QDir::separator() + info.fileName();
        
        if (info.isDir()) {
            if (!copyDirectoryRecursively(srcFilePath, dstFilePath)) {
                return false;
            }
        } else {
            // 检查目标文件是否存在,存在则先删除
            QFile::remove(dstFilePath);
            if (!QFile::copy(srcFilePath, dstFilePath)) {
                return false;
            }
        }
    }
    return true;
}

// 抽取错误处理函数
bool handleCopyError(const QString &aliasName, const QString &aliasRoot)
{
    showWarningDialog(tr("Failed to prepare renamed item \"%1\" for compression, please check permissions and available space.")
                      .arg(aliasName));
    if (!aliasRoot.isEmpty()) {
        QDir(aliasRoot).removeRecursively();
    }
    return false;
}
}

bool MainWindow::prepareCompressAliasEntries(QList<FileEntry> &listEntry)
{
    m_needCleanupCompressAlias = false;
    m_strCompressAliasRoot.clear();

    bool hasAlias = false;
    QString aliasRoot;

    // 使用更安全的临时目录生成方式
    QString baseDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
    baseDir += QDir::separator() + m_strProcessID + QDir::separator() + "compress_alias_" + 
               QUuid::createUuid().toString(QUuid::WithoutBraces);
    
    if (!QDir().mkpath(baseDir)) {
        showWarningDialog(tr("Failed to create temporary directory, please check and try again."));
        return false;
    }

    for (FileEntry &entry : listEntry) {
        if (entry.strAlias.isEmpty() || entry.strFullPath.isEmpty()) {
            continue;
        }

        const QString aliasName = entry.strAlias;
        const QString targetPath = baseDir + QDir::separator() + aliasName;

        if (entry.isDirectory) {
            QDir(targetPath).removeRecursively();
            if (!copyDirectoryRecursively(entry.strFullPath, targetPath)) {
                return handleCopyError(aliasName, baseDir);
            }
        } else {
            QFile::remove(targetPath);
            if (!QFile::copy(entry.strFullPath, targetPath)) {
                return handleCopyError(aliasName, baseDir);
            }
        }

        entry.strFullPath = targetPath;
        entry.strAlias.clear();
        hasAlias = true;
    }

    if (hasAlias) {
        m_strCompressAliasRoot = baseDir;
        m_needCleanupCompressAlias = true;
    } else {
        QDir(baseDir).removeRecursively();  // 清理未使用的临时目录
    }

    return true;
}

void MainWindow::cleanupCompressAliasEntries()
{
    if (m_strCompressAliasRoot.isEmpty()) {
        m_needCleanupCompressAlias = false;
        return;
    }

    QDir aliasRoot(m_strCompressAliasRoot);
    if (aliasRoot.exists()) {
        aliasRoot.removeRecursively();
    }
    m_strCompressAliasRoot.clear();
    m_needCleanupCompressAlias = false;
}

主要改进点:

  1. 使用QFileInfo处理路径,提高跨平台兼容性
  2. 使用更安全的临时目录生成方式
  3. 抽取错误处理函数,减少代码重复
  4. 优化了临时目录的清理逻辑
  5. 添加了更多的错误检查和状态处理

这些改进提高了代码的可维护性、安全性和跨平台兼容性。

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/source/mainwindow.cpp:2178-2179` </location>
<code_context>
+                return false;
+            }
+        } else {
+            QFile::remove(targetPath);
+            if (!QFile::copy(entry.strFullPath, targetPath)) {
+                showWarningDialog(tr("Failed to prepare renamed item \"%1\" for compression, please check permissions and available space.")
+                                  .arg(aliasName));
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Unconditionally removing targetPath may cause issues if the file is in use.

If QFile::remove fails due to the file being in use, the subsequent copy may also fail. Please handle the return value of QFile::remove to ensure robust error handling.
</issue_to_address>

### Comment 2
<location> `src/source/mainwindow.cpp:2213-2214` </location>
<code_context>
+    }
+
+    QDir aliasRoot(m_strCompressAliasRoot);
+    if (aliasRoot.exists()) {
+        aliasRoot.removeRecursively();
+    }
+    m_strCompressAliasRoot.clear();
</code_context>

<issue_to_address>
**issue (bug_risk):** removeRecursively may fail silently if files are locked or permissions are insufficient.

Currently, failures from removeRecursively are ignored, which can result in orphaned files. Please check its return value and handle errors to ensure proper cleanup.
</issue_to_address>

### Comment 3
<location> `src/source/mainwindow.cpp:79` </location>
<code_context>
+        return false;
+    }
+
+    const QFileInfoList entries = sourceDir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files | QDir::Hidden | QDir::System);
+    for (const QFileInfo &info : entries) {
+        const QString srcFilePath = info.absoluteFilePath();
</code_context>

<issue_to_address>
**🚨 question (security):** Including QDir::Hidden and QDir::System may copy unintended files.

Review whether you need to include hidden and system files; if not, remove these flags to avoid potential security or privacy issues.
</issue_to_address>

### Comment 4
<location> `src/source/mainwindow.cpp:2134` </location>
<code_context>
     }
 }

+bool MainWindow::prepareCompressAliasEntries(QList<FileEntry> &listEntry)
+{
+    m_needCleanupCompressAlias = false;
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring all alias preparation and cleanup logic into a dedicated CompressAliasManager class to simplify MainWindow and encapsulate file operations.

Here’s a sketch of how you can pull _all_ of the alias-preparation/copy/cleanup logic out of MainWindow into its own class.  This not only hides all the QDir/QFile details but also collapses dozens of lines in MainWindow down to two calls.

1) Create `CompressAliasManager`:

```cpp
// CompressAliasManager.h
#pragma once

#include <QString>
#include <QList>
#include "FileEntry.h"  // wherever FileEntry is defined

class CompressAliasManager
{
public:
    CompressAliasManager(const QString &tempRootBase, const QString &pid);
    ~CompressAliasManager();

    // return false on error (dialog already shown), in which case no cleanup needed
    bool prepare(QList<FileEntry> &entries);

    // always safe to call (will only remove if it was created)
    void cleanup();

private:
    bool copyRecursive(const QString &src, const QString &dst);
    QString m_aliasRoot;
    bool    m_needsCleanup = false;
    QString m_tempRootBase;
    QString m_pid;
};
```

```cpp
// CompressAliasManager.cpp
#include "CompressAliasManager.h"
#include <QDir>
#include <QFile>
#include <QUuid>
#include <QMessageBox>

CompressAliasManager::CompressAliasManager(const QString &tempRootBase,
                                           const QString &pid)
  : m_tempRootBase(tempRootBase), m_pid(pid)
{}

CompressAliasManager::~CompressAliasManager()
{
    cleanup();
}

bool CompressAliasManager::prepare(QList<FileEntry> &entries)
{
    QStringList toAlias;
    for (auto &e : entries) {
        if (!e.strAlias.isEmpty() && !e.strFullPath.isEmpty())
            toAlias << e.strAlias;
    }
    if (toAlias.isEmpty())
        return true;  // nothing to do

    // build aliasRoot
    QString base = m_tempRootBase + QDir::separator() + m_pid + QDir::separator() + "compress_alias";
    if (!QDir().mkpath(base))
        return false;
    m_aliasRoot = base + QDir::separator() + QUuid::createUuid().toString(QUuid::WithoutBraces);
    if (!QDir().mkpath(m_aliasRoot))
        return false;

    // copy each entry under aliasRoot
    for (auto &e : entries) {
        if (e.strAlias.isEmpty()) continue;
        QString dst = m_aliasRoot + QDir::separator() + e.strAlias;
        if (e.isDirectory) {
            if (!copyRecursive(e.strFullPath, dst)) {
                QMessageBox::warning(nullptr, tr("Error"),
                    tr("Failed to prepare renamed item \"%1\" for compression.").arg(e.strAlias));
                QDir(m_aliasRoot).removeRecursively();
                return false;
            }
        } else {
            QFile::remove(dst);
            if (!QFile::copy(e.strFullPath, dst)) {
                QMessageBox::warning(nullptr, tr("Error"),
                    tr("Failed to prepare renamed item \"%1\" for compression.").arg(e.strAlias));
                QDir(m_aliasRoot).removeRecursively();
                return false;
            }
        }
        e.strFullPath = dst;
        e.strAlias.clear();
    }

    m_needsCleanup = true;
    return true;
}

void CompressAliasManager::cleanup()
{
    if (m_needsCleanup && !m_aliasRoot.isEmpty())
        QDir(m_aliasRoot).removeRecursively();
    m_needsCleanup = false;
}

bool CompressAliasManager::copyRecursive(const QString &src, const QString &dst)
{
    QDir s(src);
    if (!s.exists()) return false;
    if (!QDir().mkpath(dst)) return false;
    for (auto &info : s.entryInfoList(QDir::NoDotAndDotDot |
                                      QDir::AllDirs | QDir::Files |
                                      QDir::Hidden | QDir::System)) {
        QString childDst = dst + QDir::separator() + info.fileName();
        if (info.isDir()) {
            if (!copyRecursive(info.absoluteFilePath(), childDst))
                return false;
        } else {
            QFile::remove(childDst);
            if (!QFile::copy(info.absoluteFilePath(), childDst))
                return false;
        }
    }
    return true;
}
```

2) In `MainWindow` replace all of the in-class free functions + member flags with:

```cpp
#include "CompressAliasManager.h"

class MainWindow : public DMainWindow {
    // ...
    std::unique_ptr<CompressAliasManager> m_aliasMgr;
    // ...
}

MainWindow::MainWindow(...) {
    // after you compute PID:
    m_aliasMgr.reset(new CompressAliasManager(TEMPPATH, m_strProcessID));
}

// in your slotCompress:
if (!m_aliasMgr->prepare(listEntry))
    return;
// ...
// on error / in every slotJobFinished / resetMainWindow:
m_aliasMgr->cleanup();
```

Advantages:
- MainWindow.cpp shrinks by several hundred lines.
- All QDir/QFile details live in one focused class.
- Cleanup/RAII guarantees no temp-dir leaks.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +2178 to +2179
QFile::remove(targetPath);
if (!QFile::copy(entry.strFullPath, targetPath)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Unconditionally removing targetPath may cause issues if the file is in use.

If QFile::remove fails due to the file being in use, the subsequent copy may also fail. Please handle the return value of QFile::remove to ensure robust error handling.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: LiHua000, max-lvs

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@LiHua000
Copy link
Contributor Author

/forcemerge

@deepin-bot deepin-bot bot merged commit fdb26ba into linuxdeepin:release/eagle Nov 11, 2025
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants