From d9ce398830c0b951a3ceef356ae12dd91ff79f0a Mon Sep 17 00:00:00 2001 From: ZhangTingan Date: Fri, 22 May 2026 11:03:45 +0800 Subject: [PATCH] fix: optimize handleLongNameExtract by batching extraction 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 --- .../archiveinterface/cliinterface.cpp | 194 ++++++++++-------- 1 file changed, 109 insertions(+), 85 deletions(-) diff --git a/3rdparty/interface/archiveinterface/cliinterface.cpp b/3rdparty/interface/archiveinterface/cliinterface.cpp index e1016572..4d56c1f5 100644 --- a/3rdparty/interface/archiveinterface/cliinterface.cpp +++ b/3rdparty/interface/archiveinterface/cliinterface.cpp @@ -1242,7 +1242,6 @@ bool CliInterface::handleLongNameExtract(const QList &files) { ExtractionOptions &options = m_extractOptions; QString password; - QStringList fileList; if (options.password.isEmpty()) { // 对列表加密文件进行追加解压的时候使用压缩包的密码 password = DataManager::get_instance().archiveData().isListEncrypted ? DataManager::get_instance().archiveData().strPassword : QString(); @@ -1255,9 +1254,14 @@ bool CliInterface::handleLongNameExtract(const QList &files) QFile tmpFile(absoluteDestinationPath); if (tmpFile.exists()) tmpFile.remove(); QFile::copy(m_strArchiveName, absoluteDestinationPath); - KPtyProcess *pProcess = new KPtyProcess; - for (FileEntry entry : files) { - qInfo() << "file path --- " << entry.strFullPath; + + // 第一遍:遍历所有文件,分离需要重命名的文件和普通文件,同时创建目录结构 + QList renameEntries; + QStringList normalFileList; + QScopedPointer pProcess(new KPtyProcess); + qInfo() << "handleLongNameExtract: total files:" << files.count(); + + for (const FileEntry &entry : files) { QFileInfo info(entry.strFullPath); QString strFilePath = info.path(); QString strFileName = entry.strFullPath; @@ -1271,13 +1275,13 @@ bool CliInterface::handleLongNameExtract(const QList &files) strFileName = sDir + info.fileName(); } } - if (NAME_MAX < QString(info.fileName()).toLocal8Bit().length() && !entry.strFullPath.endsWith(QDir::separator())) { - QString strTemp = info.fileName().left(TRUNCATION_FILE_LONG); - // 保存文件路径,不同目录下的同名文件分开计数,文件解压结束后才添加计数, - QString tempFilePathName = strFilePath + QDir::separator() + strTemp; // 路径加截取后的文件名 + bool needRename = (NAME_MAX < QString(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); // 发送当前正在解压的文件名 + emit signalCurFileName(entry.strFullPath); m_eErrorType = ET_LongNameError; return false; } @@ -1288,9 +1292,9 @@ bool CliInterface::handleLongNameExtract(const QList &files) strFileName = strFilePath + QDir::separator() + strTempFileName; } } + QString strDestFileName = options.strTargetPath + QDir::separator() + strFileName; - if (!m_extractOptions.bAllExtract) { //部分提取截断目录结构,保证拖动文件进入对应的目标目录中。 - //去除文件的头目录结构,由于长文件夹命名限制,长文件夹重命名格式不一致,使用头目录文件分隔符的个数为基准去除目录头 + if (!m_extractOptions.bAllExtract) { int nCnt = m_rootNode.count(QDir::separator()); QString destFileName = strFileName; for (int i = 0; i < nCnt; i++) { @@ -1301,96 +1305,116 @@ bool CliInterface::handleLongNameExtract(const QList &files) } strDestFileName = options.strTargetPath + QDir::separator() + destFileName; } - qInfo() << "m_rootNode --- " << m_rootNode; - qInfo() << "dest file name --- " << strDestFileName; + if (entry.strFullPath.endsWith(QDir::separator())) { - // 解压完整文件名(含路径) if (!QDir(strDestFileName).exists()) { - if (!QDir(strDestFileName).mkpath(strDestFileName)) + if (!QDir(strDestFileName).mkpath(strDestFileName)) { return false; + } } } else { - QList lstFile; - entry.strAlias = QFileInfo(strFileName).fileName(); - 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); - - //解压到当前 - fileList.clear(); - if (info.path() != ".") { - fileList.append(info.path() + QDir::separator() + entry.strAlias); + FileEntry newEntry = entry; + newEntry.strAlias = QFileInfo(strFileName).fileName(); + if (needRename) { + renameEntries.append(newEntry); } else { - fileList.append(entry.strAlias); + if (info.path() != ".") { + normalFileList.append(info.path() + QDir::separator() + newEntry.strAlias); + } else { + normalFileList.append(newEntry.strAlias); + } } - QDir::setCurrent(QFileInfo(strDestFileName).path()); - 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, fileList, 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, fileList, false, password)); - pProcess->start(); - pProcess->waitForFinished(-1); - - if (pProcess->exitCode() != 0) { - QByteArray output = pProcess->readAllStandardOutput(); - if (output.contains("Wrong password")) { - m_eErrorType = ET_WrongPassword; - return false; - } - } + // 第二步:对需要重命名的文件逐个执行 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 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); + } - bPasswordEntered = true; - break; + // 第三步:一次性批量解压所有文件(包括重命名后的和普通的) + 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); + qInfo() << "handleLongNameExtract: batch extract files:" << allFileList.count(); + + if (!allFileList.isEmpty()) { + 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; } - } - if (bPasswordEntered) + + 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) { + QByteArray output = pProcess->readAllStandardOutput(); + if (output.contains("Wrong password")) { + m_eErrorType = ET_WrongPassword; + return false; + } + } + + bPasswordEntered = true; break; + } } + if (bPasswordEntered) + break; } - } else { - pProcess->waitForFinished(-1); } + } else { + pProcess->waitForFinished(-1); } } - pProcess->deleteLater(); - pProcess = nullptr; + return true; }