Skip to content

fix: optimize handleLongNameExtract by batching extraction#416

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:release/eaglefrom
LiHua000:release/eagle
May 22, 2026
Merged

fix: optimize handleLongNameExtract by batching extraction#416
deepin-bot[bot] merged 1 commit into
linuxdeepin:release/eaglefrom
LiHua000:release/eagle

Conversation

@LiHua000
Copy link
Copy Markdown
Contributor

@LiHua000 LiHua000 commented May 22, 2026

Instead of running separate 7z rn + 7z x for every file, only rename
files that actually need it and do a single batch extraction for all
files at once, reducing process launches from N*2 to M+1.

log: fix bug

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

Summary by Sourcery

Optimize long-filename extraction by separating rename handling from extraction and performing a single batch extract.

Bug Fixes:

  • Ensure processes are properly cleaned up and errors returned when directory creation or password handling fails during extraction.
  • Improve password prompt handling for batch extraction, correctly propagating wrong-password and cancelled-password states.

Enhancements:

  • Reduce external 7z process invocations by batching extraction after performing necessary renames.
  • Add logging for total files, rename operations, and batch extraction counts to aid debugging long-name extraction issues.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 22, 2026

Reviewer's Guide

Refactors handleLongNameExtract to decouple long‑name renaming from extraction, batching all 7z extract calls into a single process while preserving password handling, directory creation, and long-name error logic.

Sequence diagram for batched handleLongNameExtract extraction

sequenceDiagram
    participant CliInterface
    participant KPtyProcess
    participant moveProgram
    participant extractProgram
    participant PasswordNeededQuery

    CliInterface->>CliInterface: handleLongNameExtract(files)
    CliInterface->>CliInterface: classify files into renameEntries, normalFileList

    loop for each entry in renameEntries
        CliInterface->>KPtyProcess: setProgram(moveProgram, moveArgs(...))
        CliInterface->>KPtyProcess: start()
        KPtyProcess->>moveProgram: 7z rn
        KPtyProcess-->>CliInterface: waitForFinished(-1)
    end

    CliInterface->>CliInterface: build allFileList from renameEntries and normalFileList

    CliInterface->>KPtyProcess: setProgram(extractProgram, extractArgs(allFileList,...))
    CliInterface->>KPtyProcess: start()
    KPtyProcess->>extractProgram: 7z x (batch)

    alt password is empty
        loop until finished
            KPtyProcess-->>CliInterface: output
            CliInterface->>CliInterface: isPasswordPrompt(line)
            alt isPasswordPrompt
                CliInterface->>KPtyProcess: kill()
                KPtyProcess-->>CliInterface: waitForFinished(3000)

                CliInterface->>PasswordNeededQuery: PasswordNeededQuery(m_strArchiveName)
                CliInterface-->>PasswordNeededQuery: signalQuery(&query)
                PasswordNeededQuery-->>CliInterface: waitForResponse()

                alt user cancelled
                    CliInterface->>CliInterface: set m_eErrorType = ET_NeedPassword
                    CliInterface-->>KPtyProcess: deleteLater()
                    CliInterface-->>CliInterface: return false
                else user entered password
                    CliInterface->>CliInterface: update archiveData.strPassword
                    CliInterface->>KPtyProcess: setProgram(extractProgram, extractArgs(allFileList,..., password))
                    CliInterface->>KPtyProcess: start()
                    KPtyProcess-->>CliInterface: waitForFinished(-1)
                    alt exitCode != 0 and output contains Wrong password
                        CliInterface->>CliInterface: set m_eErrorType = ET_WrongPassword
                        CliInterface-->>KPtyProcess: deleteLater()
                        CliInterface-->>CliInterface: return false
                    end
                end
            end
        end
    else password provided
        KPtyProcess-->>CliInterface: waitForFinished(-1)
    end

    CliInterface-->>KPtyProcess: deleteLater()
    CliInterface-->>CliInterface: return true
Loading

File-Level Changes

Change Details Files
Refactor long-name extraction flow to batch 7z extract calls and separate renaming from extraction.
  • Copy archive to temporary path as before, but first iterate all entries to compute destination paths, detect long-name entries, and eagerly create destination directories.
  • Collect long-name entries into a dedicated list used to drive individual 7z rename (moveProgram) calls, skipping rename for files that don’t exceed NAME_MAX.
  • Accumulate non-renamed entries in a list of normal files for later batch extraction, instead of extracting them one-by-one during the loop.
  • After processing all entries, perform 7z rn sequentially for each long-name entry, then issue a single 7z x (extractProgram) with a combined file list including both renamed and normal files, run from the target directory.
  • Reuse and adapt the existing password-prompt detection and retry logic for the new single batch extraction call, updating error handling to delete the KPtyProcess on early returns and logging counts of total, renamed, and normal files for debugging.
3rdparty/interface/archiveinterface/cliinterface.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

Copy link
Copy Markdown

@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 - I've found 2 issues

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

## Individual Comments

### Comment 1
<location path="3rdparty/interface/archiveinterface/cliinterface.cpp" line_range="1334-1321" />
<code_context>
-                                }
+    // 第二步:对需要重命名的文件逐个执行 7z rn
+    qInfo() << "handleLongNameExtract: rename entries:" << renameEntries.count() << ", normal files:" << normalFileList.count();
+    for (const FileEntry &entry : renameEntries) {
+        qInfo() << "handleLongNameExtract: rename" << entry.strFullPath << "->" << entry.strAlias;
+        QList<FileEntry> lstFile;
+        lstFile.append(entry);
+        pProcess->setPtyChannels(KPtyProcess::StdinChannel);
+        pProcess->setOutputChannelMode(KProcess::MergedChannels);
+        pProcess->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text);
+        pProcess->setProgram(m_cliProps->property("moveProgram").toString(), m_cliProps->moveArgs(absoluteDestinationPath, lstFile, DataManager::get_instance().archiveData(), password));
+        pProcess->start();
+        pProcess->waitForFinished(-1);
+    }

</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider handling errors from the rename (moveProgram) step, including exit status and potential password issues.

This loop waits for `moveProgram` to finish but never checks `exitCode()` or its output. If a rename fails (invalid path, archive issues, or a password-required `rn`), the code still proceeds and the bulk extract may misbehave. Please inspect the process result here, reuse the password‑prompt handling if applicable, and set an appropriate `m_eErrorType` instead of continuing silently.

Suggested implementation:

```cpp
    for (const FileEntry &entry : renameEntries) {
        qInfo() << "handleLongNameExtract: rename" << entry.strFullPath << "->" << entry.strAlias;
        QList<FileEntry> lstFile;
        lstFile.append(entry);
        pProcess->setPtyChannels(KPtyProcess::StdinChannel);
        pProcess->setOutputChannelMode(KProcess::MergedChannels);
        pProcess->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text);
        pProcess->setProgram(
            m_cliProps->property("moveProgram").toString(),
            m_cliProps->moveArgs(
                absoluteDestinationPath,
                lstFile,
                DataManager::get_instance().archiveData(),
                password));

        pProcess->start();

        // Wait for the rename process and inspect its result; abort on any failure so we don't continue silently.
        if (!pProcess->waitForFinished(-1)) {
            qWarning() << "handleLongNameExtract: moveProgram did not finish for"
                       << entry.strFullPath
                       << "error:" << pProcess->errorString();
            return;
        }

        if (pProcess->exitStatus() != QProcess::NormalExit || pProcess->exitCode() != 0) {
            const QByteArray procOutput = pProcess->readAll();
            qWarning() << "handleLongNameExtract: moveProgram failed for"
                       << entry.strFullPath
                       << "exitStatus:" << pProcess->exitStatus()
                       << "exitCode:" << pProcess->exitCode()
                       << "output:" << procOutput;
            return;
        }
    }

```

To fully align with your review comment and the rest of the codebase, the following adjustments are likely needed elsewhere in `cliinterface.cpp`:

1. **Password‑prompt reuse:**  
   - Identify the existing logic that handles password prompts / wrong password for other 7z invocations (e.g. extract or list).  
   - Before treating a non‑zero `exitCode()` as fatal, reuse that same handler for the `moveProgram` call, so cases like “password required for `rn`” are mapped to the same `m_eErrorType` and user interaction flow.

2. **Error type wiring (`m_eErrorType`):**  
   - Locate how `m_eErrorType` is set for other 7z failures (invalid path, archive issues, password errors).  
   - In the two `return` paths added above, set `m_eErrorType` (and any associated error message field) consistently with those existing patterns instead of just returning. For example, map a non‑zero exit code from `moveProgram` to the same error type used for extract failures.

3. **Includes (if needed):**  
   - If `QProcess` isn’t already included in this translation unit, add `#include <QProcess>` to the top of the file so `QProcess::NormalExit` compiles.
</issue_to_address>

### Comment 2
<location path="3rdparty/interface/archiveinterface/cliinterface.cpp" line_range="1357" />
<code_context>
+        }
+    }
+    allFileList.append(normalFileList);
+    qInfo() << "handleLongNameExtract: batch extract files:" << allFileList.count();
+
+    if (!allFileList.isEmpty()) {
</code_context>
<issue_to_address>
**suggestion:** Batch extraction with a potentially very large allFileList may hit command-line length limits.

This now builds a single `allFileList` and passes it to `extractArgs` once. For archives with many entries, the resulting command line may exceed OS length limits (particularly on Windows), whereas the previous per-file extraction avoided this. Consider chunking `allFileList` into safely sized batches or adding a fallback if the command line would be too long.

Suggested implementation:

```cpp
    QStringList allFileList;
    for (const FileEntry &entry : renameEntries) {
        QFileInfo info(entry.strFullPath);
        if (info.path() != ".") {
            allFileList.append(info.path() + QDir::separator() + entry.strAlias);
        } else {
            allFileList.append(entry.strAlias);
        }
    }
    allFileList.append(normalFileList);

    // Estimate total command-line length contribution of file arguments to avoid OS limits (especially on Windows).
    // This is conservative and counts path characters plus a separator per file.
    qsizetype estimatedArgsLength = 0;
    for (const QString &filePath : std::as_const(allFileList)) {
        estimatedArgsLength += filePath.size() + 1; // +1 for space / separator
    }

    // Choose a safe limit well below typical Windows command-line limits (~32K), accounting for program/other args.
    constexpr qsizetype kMaxSafeCmdArgLength = 24000;

    QVector<QStringList> fileBatches;
    if (estimatedArgsLength > kMaxSafeCmdArgLength) {
        qInfo() << "handleLongNameExtract: total argument length" << estimatedArgsLength
                << "exceeds safe limit" << kMaxSafeCmdArgLength << "- chunking extraction.";

        QStringList currentBatch;
        qsizetype currentBatchLen = 0;

        for (const QString &filePath : std::as_const(allFileList)) {
            const qsizetype fileLen = filePath.size() + 1; // +1 for separator
            // If adding this file would exceed the safe limit, start a new batch.
            if (!currentBatch.isEmpty() && currentBatchLen + fileLen > kMaxSafeCmdArgLength) {
                fileBatches.append(currentBatch);
                currentBatch.clear();
                currentBatchLen = 0;
            }
            currentBatch.append(filePath);
            currentBatchLen += fileLen;
        }
        if (!currentBatch.isEmpty()) {
            fileBatches.append(currentBatch);
        }

        qInfo() << "handleLongNameExtract: created" << fileBatches.size()
                << "batches for" << allFileList.count() << "files.";
    } else {
        // Single batch with all files is safely within command-line limits.
        fileBatches.append(allFileList);
        qInfo() << "handleLongNameExtract: batch extract files:" << allFileList.count();
    }

    if (!fileBatches.isEmpty()) {
        // For now, start the first batch here. The surrounding logic should be extended to
        // process remaining batches sequentially, reusing pProcess.
        const QStringList firstBatch = fileBatches.first();

        QDir::setCurrent(options.strTargetPath);
        pProcess->setPtyChannels(KPtyProcess::StdinChannel);
        pProcess->setOutputChannelMode(KProcess::MergedChannels);
        pProcess->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text);
        pProcess->setProgram(m_cliProps->property("extractProgram").toString(),
                             m_cliProps->extractArgs(absoluteDestinationPath, firstBatch, false, password));
        pProcess->start();

        if (password.isEmpty()) {
            bool bPasswordEntered = false;

```

To fully implement multi-batch extraction and avoid command-line length limits end-to-end, you should:

1. Extend the logic around this snippet to:
   - Keep `fileBatches` (or an index into it) as state for the ongoing extraction.
   - When the current `pProcess` finishes successfully, start the next batch with a new call to `setProgram(...)`/`start()`, reusing the same `pProcess`.
2. Ensure that error handling and progress reporting account for multiple batches:
   - If any batch fails, abort remaining batches and surface the error.
   - Progress UI (if any) should reflect progress across all batches, not just the first.
3. If this function currently assumes a single `pProcess` run per invocation, you may need to:
   - Store `fileBatches` in a member of the owning class, and
   - Trigger subsequent batches from the `finished`/`errorOccurred` slots connected to `pProcess`.
This will preserve the new batch behavior while safely handling very large archives by splitting them into multiple, OS-safe command-line invocations.
</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 thread 3rdparty/interface/archiveinterface/cliinterface.cpp
Comment thread 3rdparty/interface/archiveinterface/cliinterface.cpp
Instead of running separate 7z rn + 7z x for every file, only rename
  files that actually need it and do a single batch extraction for all
  files at once, reducing process launches from N*2 to M+1.

log: fix bug

Bug: https://pms.uniontech.com/bug-view-362685.html
@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: LiHua000, lzwind

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
Copy Markdown
Contributor Author

/merge

@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这份代码的重构主要将原本“遍历文件时逐个启动进程进行移动和解压”的逻辑,优化为了“先统一处理重命名(7z rn),再批量解压”的逻辑。这大大减少了进程的创建和销毁次数,是一个很好的性能优化方向。

但在仔细审查后,发现在语法逻辑、代码性能和代码安全方面仍有一些关键问题需要修正。以下是详细的审查意见:

一、 语法与逻辑错误 (严重)

1. 进程重复启动导致状态混乱
在第二步(重命名)和第三步(批量解压)中,代码复用了同一个 pProcess 对象,并在每次循环/阶段中调用 pProcess->start()

  • 问题QProcess(或 KPtyProcess)在调用 start() 之前,必须处于 NotRunning 状态。如果前一个进程没有正常结束,或者 waitForFinished 超时/失败,再次调用 start() 会导致程序崩溃或行为未定义。
  • 改进:在每次 start() 之前,确保进程已结束;或者更推荐的做法是,对于重命名操作,在循环内部局部创建进程对象,重命名本身就是独立步骤,不应与后续的解压共享进程状态。

2. 密码检测逻辑中的变量遮蔽
在第三步的密码检测逻辑中:

QByteArray output = pProcess->readAllStandardOutput();
// ...
if (pProcess->exitCode() != 0) {
    QByteArray output = pProcess->readAllStandardOutput(); // 内部遮蔽了外部的 output
    if (output.contains("Wrong password")) {
  • 问题:内部重新声明的 output 遮蔽了外部的 output。虽然在这里刚好读取了正确的输出,但属于潜在的逻辑隐患,且不符合规范。
  • 改进:删除内部的 QByteArray output 声明,直接使用外层作用域的 output 变量,或者重命名。

3. QDir::setCurrent 的副作用被移除导致的路径错误
旧代码中有 QDir::setCurrent(QFileInfo(strDestFileName).path());,新代码将其移除,并在批量解压时增加了 pProcess->setWorkingDirectory(options.strTargetPath);

  • 问题:这改变了解压进程的工作目录上下文。如果 extractArgs 函数生成的参数是相对路径,那么工作目录的改变会导致解压找不到文件。需要确认 extractArgs 生成的路径参数是否为绝对路径,如果是相对路径,此修改会导致解压失败。

二、 代码性能

1. QListQStringList 的频繁追加

for (const FileEntry &entry : renameEntries) {
    QList<FileEntry> lstFile;
    lstFile.append(entry);
    // ... 启动进程
}
  • 问题:在循环中每次都重新创建 QList<FileEntry> 并只添加一个元素,存在不必要的内存分配开销。
  • 改进:如果底层 moveArgs 接口只支持 QList,这里无法避免;但可以考虑是否支持直接传递单个 FileEntry。另外,allFileListnormalFileList 在拼接时,可以使用 std::movereserve 来优化内存分配。

2. 字符串编码转换的重复计算

bool needRename = (NAME_MAX < QString(info.fileName()).toLocal8Bit().length() && ...);
  • 问题toLocal8Bit() 会进行动态内存分配和编码转换。如果文件名很长,这会有性能损耗。
  • 改进:对于仅判断长度是否超过 NAME_MAX,可以使用 toUtf8().size() 或者直接使用 QString::toLocal8Bit().length() 的结果缓存,避免后续(如果有)重复转换。

三、 代码安全

1. 临时文件/压缩包的拷贝未做安全校验

QFile tmpFile(absoluteDestinationPath);
if (tmpFile.exists()) tmpFile.remove();
QFile::copy(m_strArchiveName, absoluteDestinationPath);
  • 问题
    1. QFile::remove()QFile::copy() 的返回值未被检查。如果删除失败(如权限不足)或拷贝失败,后续解压操作将会在损坏或不存在的基础上进行,导致不可预知的错误。
    2. 存在TOCTOU (Time-of-check to time-of-use) 竞态条件,在高并发场景下可能被利用。
  • 改进:必须检查 remove()copy() 的返回值,失败时应当返回错误或抛出异常。

2. waitForFinished(-1) 可能导致界面冻结或死锁

pProcess->waitForFinished(-1);
  • 问题-1 表示无限期等待。如果解压程序挂起(例如等待用户输入但未被 isPasswordPrompt 捕获),主线程会被永久阻塞。
  • 改进:建议设置一个合理的超时时间(例如 30 秒或 60 秒),并在超时后执行 pProcess->kill() 并返回错误。

3. QScopedPointerdeleteLater 的冲突
新代码使用了 QScopedPointer<KPtyProcess> pProcess(new KPtyProcess);,并在函数末尾移除了 pProcess->deleteLater();

  • 问题:如果 pProcess 的生命周期被其他信号槽或事件循环引用,QScopedPointer 在函数结束时直接析构可能会导致悬空指针。不过,由于这里使用了 waitForFinished(阻塞式),进程生命周期确实在函数内,使用 QScopedPointer 是安全的。但需确保没有外部引用。

四、 代码质量与可读性

1. 魔法字符串

if (output.contains("Wrong password")) {
  • 问题:硬编码的错误提示字符串容易因解压软件版本或语言环境变化而失效。
  • 改进:建议将其定义为常量,或通过配置/正则匹配来提高鲁棒性。

2. 注释与代码逻辑的统一
第二步的注释是 // 第二步:对需要重命名的文件逐个执行 7z rn,但代码中调用的是 moveProgrammoveArgs。虽然可能底层就是 7z rn,但注释最好与代码函数名保持一致,避免维护者困惑。


改进后的代码建议

综合以上意见,以下是改进后的代码参考:

bool CliInterface::handleLongNameExtract(const QList<FileEntry> &files)
{
    ExtractionOptions &options = m_extractOptions;
    QString password;
    if (options.password.isEmpty()) {
        password = DataManager::get_instance().archiveData().isListEncrypted ? DataManager::get_instance().archiveData().strPassword : QString();
    } else {
        password = options.password;
    }

    QFile tmpFile(absoluteDestinationPath);
    if (tmpFile.exists()) {
        if (!tmpFile.remove()) {
            m_eErrorType = ET_FileRemoveError; // 建议增加错误类型
            return false;
        }
    }
    if (!QFile::copy(m_strArchiveName, absoluteDestinationPath)) {
        m_eErrorType = ET_FileCopyError; // 建议增加错误类型
        return false;
    }

    QList<FileEntry> renameEntries;
    QStringList normalFileList;
    qInfo() << "handleLongNameExtract: total files:" << files.count();

    // 第一遍:遍历所有文件,分离需要重命名的文件和普通文件,同时创建目录结构
    for (const FileEntry &entry : files) {
        QFileInfo info(entry.strFullPath);
        QString strFilePath = info.path();
        QString strFileName = entry.strFullPath;

        if (strFilePath != ".") {
            QString sDir = strFilePath + QDir::separator();
            if (!strFileName.startsWith(sDir)) {
                strFileName = sDir + info.fileName();
            }
        }

        bool needRename = (NAME_MAX < info.fileName().toLocal8Bit().length() && !entry.strFullPath.endsWith(QDir::separator()));
        if (needRename) {
            QString strTemp = info.fileName().left(TRUNCATION_FILE_LONG);
            QString tempFilePathName = strFilePath + QDir::separator() + strTemp;
            if (m_mapLongName[tempFilePathName]++ >= LONGFILE_SAME_FILES) {
                emit signalCurFileName(entry.strFullPath);
                m_eErrorType = ET_LongNameError;
                return false;
            }
            // 生成截断后的别名
            QString strTempFileName = strTemp + QString("_%1").arg(m_mapLongName[tempFilePathName]);
            if (strFilePath != ".") {
                strFileName = strFilePath + QDir::separator() + strTempFileName;
            } else {
                strFileName = strTempFileName;
            }
        }

        QString strDestFileName = options.strTargetPath + QDir::separator() + strFileName;
        if (!m_extractOptions.bAllExtract) {
            int nCnt = m_rootNode.count(QDir::separator());
            QString destFileName = strFileName;
            for (int i = 0; i < nCnt; i++) {
                destFileName = destFileName.mid(destFileName.indexOf(QDir::separator()) + 1);
            }
            strDestFileName = options.strTargetPath + QDir::separator() + destFileName;
        }

        if (entry.strFullPath.endsWith(QDir::separator())) {
            if (!QDir(strDestFileName).exists()) {
                if (!QDir(strDestFileName).mkpath(strDestFileName)) {
                    return false;
                }
            }
        } else {
            FileEntry newEntry = entry;
            newEntry.strAlias = QFileInfo(strFileName).fileName();
            if (needRename) {
                renameEntries.append(newEntry);
            } else {
                if (info.path() != ".") {
                    normalFileList.append(info.path() + QDir::separator() + newEntry.strAlias);
                } else {
                    normalFileList.append(newEntry.strAlias);
                }
            }
        }
    }

    // 第二步:对需要重命名的文件逐个执行 moveProgram (如 7z rn)
    qInfo() << "handleLongNameExtract: rename entries:" << renameEntries.count() << ", normal files:" << normalFileList.count();
    for (const FileEntry &entry : renameEntries) {
        qInfo() << "handleLongNameExtract: rename" << entry.strFullPath << "->" << entry.strAlias;
        
        // 每次重命名使用独立的进程,避免状态混乱
        KPtyProcess renameProcess;
        QList<FileEntry> lstFile;
        lstFile.append(entry);
        renameProcess.setPtyChannels(KPtyProcess::StdinChannel);
        renameProcess.setOutputChannelMode(KProcess::MergedChannels);
        renameProcess.setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text);
        renameProcess.setProgram(m_cliProps->property("moveProgram").toString(), 
                                 m_cliProps->moveArgs(absoluteDestinationPath, lstFile, DataManager::get_instance().archiveData(), password));
        renameProcess.start();
        // 设置超时时间,避免死锁
        if (!renameProcess.waitForFinished(30000)) {
            renameProcess.kill();
            renameProcess.waitForFinished(3000);
            m_eErrorType = ET_ProcessTimeout; // 建议增加错误类型
            return false;
        }
    }

    // 第三步:一次性批量解压所有文件
    QStringList allFileList;
    allFileList.reserve(renameEntries.size() + normalFileList.size()); // 优化内存分配
    for (const FileEntry &entry : renameEntries) {
        QFileInfo info(entry.strFullPath);
        if (info.path() != ".") {
            allFileList.append(info.path() + QDir::separator() + entry.strAlias);
        } else {
            allFileList.append(entry.strAlias);
        }
    }
    allFileList.append(normalFileList);
    qInfo() << "handleLongNameExtract: batch extract files:" << allFileList.count();

    if (!allFileList.isEmpty()) {
        QScopedPointer<KPtyProcess> pProcess(new KPtyProcess);
        pProcess->setWorkingDirectory(options.strTargetPath);
        pProcess->setPtyChannels(KPtyProcess::StdinChannel);
        pProcess->setOutputChannelMode(KProcess::MergedChannels);
        pProcess->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text);
        pProcess->setProgram(m_cliProps->property("extractProgram").toString(),
                             m_cliProps->extractArgs(absoluteDestinationPath, allFileList, false, password));
        pProcess->start();

        if (password.isEmpty()) {
            bool bPasswordEntered = false;
            // 设置超时时间
            while (!pProcess->waitForFinished(200)) {
                if (pProcess->bytesAvailable() > 0) {
                    QByteArray output = pProcess->readAllStandardOutput();
                    QStringList lines = QString::fromLocal8Bit(output).split('\n');
                    for (const QString &line : lines) {
                        if (isPasswordPrompt(line)) {
                            pProcess->kill();
                            pProcess->waitForFinished(3000);

                            PasswordNeededQuery query(m_strArchiveName);
                            emit signalQuery(&query);
                            query.waitForResponse();

                            if (query.responseCancelled()) {
                                m_eErrorType = ET_NeedPassword;
                                return false;
                            }

                            password = query.password();
                            DataManager::get_instance().archiveData().strPassword = password;

                            pProcess->setPtyChannels(KPtyProcess::StdinChannel);
                            pProcess->setOutputChannelMode(KProcess::MergedChannels);
                            pProcess->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text);
                            pProcess->setProgram(m_cliProps->property("extractProgram").toString(),
                                                 m_cliProps->extractArgs(absoluteDestinationPath, allFileList, false, password));
                            pProcess->start();
                            pProcess->waitForFinished(-1); // 输入密码后可等待

                            if (pProcess->exitCode() != 0) {
                                // 修复变量遮蔽问题,复用外层 output
                                output = pProcess->readAllStandardOutput();
                                if (output.contains("Wrong password")) {
                                    m_eErrorType = ET_WrongPassword;
                                    return false;
                                }
                            }

                            bPasswordEntered = true;
                            break;
                        }
                    }
                    if (bPasswordEntered)
                        break;
                }
            }
        } else {
            // 增加超时保护
            if (!pProcess->waitForFinished(60000)) {
                pProcess->kill();
                pProcess->waitForFinished(3000);
                m_eErrorType = ET_ProcessTimeout;
                return false;
            }
        }
    }

    return true;
}

@deepin-bot deepin-bot Bot merged commit 25b7f77 into linuxdeepin:release/eagle May 22, 2026
16 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