From 74c9798cad05d5e4797fcbfb3110ed49b9719b32 Mon Sep 17 00:00:00 2001 From: liuwenhuan Date: Mon, 9 Nov 2026 13:11:14 +0800 Subject: [PATCH 1/2] fix: reset a reused plugin's result when a job starts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. ArchiveManager reuses the plugin interface for later operations on the same archive. The finish type and the error type of a failed operation stayed on the interface, so the next operation could end with the old error: after a wrong password, extracting again with the right password reported "The archive is damaged". 2. ReadOnlyArchiveInterface::resetResult() clears the error type; CliInterface also clears the finish type. SingleJob::start() and UpdateJob::start() call it, once per job, so an error found earlier in the same job (for example ET_LongNameError before the long-name extraction refreshes the list) is kept. Log: Forget the result of the previous operation when a new one starts. Influence: 1. Extract an encrypted archive with a wrong password, then with the right one: the second extraction succeeds. 2. Extracting a file with a long name still reports the long-name case. fix: 任务开始时重置复用插件的结果 1. ArchiveManager 对同一压缩包的后续操作复用插件对象。失败操作的结束类型 和错误类型留在插件对象上,下一次操作可能带着旧错误结束:输错密码后再用 正确密码解压,提示"压缩包已损坏"。 2. ReadOnlyArchiveInterface::resetResult() 清除错误类型,CliInterface 还 清除结束类型。SingleJob::start() 和 UpdateJob::start() 每个任务调用一 次,因此同一任务中较早发现的错误(如长文件名解压刷新列表前设置的 ET_LongNameError)得以保留。 Log: 新操作开始时清除上一次操作的结果。 Influence: 1. 加密压缩包先输错密码再输对密码解压:第二次解压成功。 2. 解压长文件名文件仍按长文件名情况处理。 --- 3rdparty/interface/archiveinterface/archiveinterface.cpp | 5 +++++ 3rdparty/interface/archiveinterface/archiveinterface.h | 6 ++++++ 3rdparty/interface/archiveinterface/cliinterface.cpp | 6 ++++++ 3rdparty/interface/archiveinterface/cliinterface.h | 6 ++++++ src/source/archivemanager/singlejob.cpp | 8 ++++++++ 5 files changed, 31 insertions(+) diff --git a/3rdparty/interface/archiveinterface/archiveinterface.cpp b/3rdparty/interface/archiveinterface/archiveinterface.cpp index e953a4bbe..0f856ed28 100644 --- a/3rdparty/interface/archiveinterface/archiveinterface.cpp +++ b/3rdparty/interface/archiveinterface/archiveinterface.cpp @@ -76,6 +76,11 @@ ErrorType ReadOnlyArchiveInterface::errorType() return m_eErrorType; } +void ReadOnlyArchiveInterface::resetResult() +{ + m_eErrorType = ET_NoError; +} + bool ReadOnlyArchiveInterface::doKill() { return false; // 修改默认为未取消 diff --git a/3rdparty/interface/archiveinterface/archiveinterface.h b/3rdparty/interface/archiveinterface/archiveinterface.h index 6721fc703..8445eab61 100644 --- a/3rdparty/interface/archiveinterface/archiveinterface.h +++ b/3rdparty/interface/archiveinterface/archiveinterface.h @@ -116,6 +116,12 @@ class ReadOnlyArchiveInterface : public QObject */ ErrorType errorType(); + /** + * @brief resetResult forget the result of the previous operation; the interface is reused for + * later operations on the same archive, so a job starts with this + */ + virtual void resetResult(); + /** * @brief pauseOperation 暂停操作 */ diff --git a/3rdparty/interface/archiveinterface/cliinterface.cpp b/3rdparty/interface/archiveinterface/cliinterface.cpp index 0db6cc57f..78ab5d7ae 100644 --- a/3rdparty/interface/archiveinterface/cliinterface.cpp +++ b/3rdparty/interface/archiveinterface/cliinterface.cpp @@ -66,6 +66,12 @@ CliInterface::~CliInterface() deleteProcess(); } +void CliInterface::resetResult() +{ + ReadOnlyArchiveInterface::resetResult(); + m_finishType = PFT_Nomral; +} + PluginFinishType CliInterface::list() { setPassword(QString()); diff --git a/3rdparty/interface/archiveinterface/cliinterface.h b/3rdparty/interface/archiveinterface/cliinterface.h index 64913354f..d6441cbfc 100644 --- a/3rdparty/interface/archiveinterface/cliinterface.h +++ b/3rdparty/interface/archiveinterface/cliinterface.h @@ -66,6 +66,12 @@ class CliInterface : public ReadWriteArchiveInterface explicit CliInterface(QObject *parent, const QVariantList &args); ~CliInterface() override; + /** + * @brief resetResult also forget the previous finish type: an operation that succeeds after a + * failed one on the same archive must not end as an error + */ + void resetResult() override; + // ReadOnlyArchiveInterface interface public: PluginFinishType list() override; diff --git a/src/source/archivemanager/singlejob.cpp b/src/source/archivemanager/singlejob.cpp index 9cb2a0e6b..0b92ffef8 100644 --- a/src/source/archivemanager/singlejob.cpp +++ b/src/source/archivemanager/singlejob.cpp @@ -56,6 +56,10 @@ void SingleJob::start() return; } + // The interface is reused for later operations on the same archive: start without the result of + // the previous one (errors found during this job, e.g. while listing first, are kept) + m_pInterface->resetResult(); + // 判断是否通过线程的方式调用 if (m_pInterface->waitForFinished()) { qCDebug(appLog) << "Executing work directly"; @@ -532,6 +536,10 @@ void UpdateJob::start() return; } + // The interface is reused for later operations on the same archive: start without the result of + // the previous one (errors found during this job, e.g. while listing first, are kept) + m_pInterface->resetResult(); + d->start(); // 开启线程,执行操作 } From 1b5fa906cfadb2b7303a52617b3c43b53c80751f Mon Sep 17 00:00:00 2001 From: liuwenhuan Date: Mon, 9 Nov 2026 13:11:46 +0800 Subject: [PATCH 2/2] fix: ask for the password again after a wrong password MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. A wrong password ended an extraction or load on the failure page. Started from the file manager (extract here / extract to) its Retry button was disabled, and Back cleared the window to an empty home page, also when the archive had been opened in the window. 2. Now a wrong password restarts the same extraction (same destination the user chose, same file-manager mode) or load, and the password dialog asks again with "Wrong password" (the existing, translated MainWindow string) and the field marked until the user types. Cancel behaves as before. Batch extraction keeps the failure page, and so does a retry that ends without showing the dialog, so it cannot loop. 3. Back on the extraction failure page returns to the file list of an archive opened in the window. 4. When the preliminary listing of "Extract here" fails, the plugin releases its process, so the retry can start a new one. 5. The retry needs "fix: reset a reused plugin's result when a job starts": without it the extraction after the right password ends with the previous run's error. Log: Ask for the password again after a wrong password instead of failing. Influence: 1. RAR (data or headers encrypted), 7z and zip: enter a wrong password, then the right one, from "Extract here" and from the window. 2. Cancel the second password dialog. 3. A failed extraction of an opened archive, then Back. fix: 密码错误后重新询问密码 1. 输错密码后解压或加载直接进入失败页面。从文件管理器"解压到当前文件夹" 启动时重试按钮不可用,返回按钮会清空窗口回到空白首页,在窗口中打开的 压缩包也一样。 2. 现在输错密码会以相同方式(用户选择的同一目标位置)重新解压或加载,密码框再次弹出并提示"密码错误" (复用已翻译的 MainWindow 字符串),输入框标红直到重新输入。取消的行为 不变。批量解压仍显示失败页;重试后未弹出密码框即结束时也显示失败页, 不会循环。 3. 在窗口中打开的压缩包解压失败后,返回按钮回到文件列表。 4. "解压到当前文件夹"的预先列出失败时,插件释放其进程,重试才能启动新进程。 5. 重试依赖"fix: 任务开始时重置复用插件的结果":否则输对密码后的解压仍以上 一次的错误结束。 Log: 输错密码后重新询问密码,而不是直接失败。 Influence: 1. RAR(加密数据或文件头)、7z、zip:先输错再输对,分别从"解压到当前 文件夹"和窗口中解压。 2. 第二次密码框点取消。 3. 在窗口中打开的压缩包解压失败后点返回。 --- .../archiveinterface/cliinterface.cpp | 3 + 3rdparty/interface/queries.cpp | 24 +++++++ 3rdparty/interface/queries.h | 12 ++++ src/source/mainwindow.cpp | 68 ++++++++++++++++++- src/source/mainwindow.h | 9 +++ 5 files changed, 114 insertions(+), 2 deletions(-) diff --git a/3rdparty/interface/archiveinterface/cliinterface.cpp b/3rdparty/interface/archiveinterface/cliinterface.cpp index 78ab5d7ae..0ce4dec1b 100644 --- a/3rdparty/interface/archiveinterface/cliinterface.cpp +++ b/3rdparty/interface/archiveinterface/cliinterface.cpp @@ -790,6 +790,9 @@ bool CliInterface::runProcess(const QString &programName, const QStringList &arg [=](int exitCode, QProcess::ExitStatus) { if (m_process) { if (exitCode != 0) { + // Release the failed listing process: the interface is used again (e.g. to + // extract after asking for the password again), and runProcess() needs none + deleteProcess(); emit signalprogress(100); emit signalFinished(PFT_Error); return; diff --git a/3rdparty/interface/queries.cpp b/3rdparty/interface/queries.cpp index d2ab9e385..d4244605d 100644 --- a/3rdparty/interface/queries.cpp +++ b/3rdparty/interface/queries.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -459,6 +460,18 @@ void ExtractToQuery::setWidgetType(QWidget *pWgt, DPalette::ColorType ct, double +bool PasswordNeededQuery::s_bPreviousPasswordWrong = false; + +void PasswordNeededQuery::setPreviousPasswordWrong(bool bWrong) +{ + s_bPreviousPasswordWrong = bWrong; +} + +bool PasswordNeededQuery::previousPasswordWrong() +{ + return s_bPreviousPasswordWrong; +} + PasswordNeededQuery::PasswordNeededQuery(const QString &strFileName, QObject *parent) : Query(parent) { @@ -514,6 +527,17 @@ void PasswordNeededQuery::execute() passwordedit->setMinimumHeight(36); passwordedit->setFixedWidth(280); + // The previous password was wrong: say so and mark the field until the user types + if (s_bPreviousPasswordWrong) { + s_bPreviousPasswordWrong = false; + m_strDesText = QCoreApplication::translate("MainWindow", "Wrong password"); + pTipLbl->setText(m_strDesText); + passwordedit->setAlert(true); + connect(passwordedit, &DPasswordEdit::textChanged, passwordedit, [passwordedit]() { + passwordedit->setAlert(false); + }); + } + dialog->addButton(QObject::tr("Cancel", "button"), true, DDialog::ButtonNormal); dialog->addButton(QObject::tr("OK", "button"), true, DDialog::ButtonRecommend); dialog->getButton(1)->setEnabled(false); diff --git a/3rdparty/interface/queries.h b/3rdparty/interface/queries.h index df56165e9..4b46c5b5a 100644 --- a/3rdparty/interface/queries.h +++ b/3rdparty/interface/queries.h @@ -249,7 +249,19 @@ class PasswordNeededQuery : public Query */ QString password(); + /** + * @brief setPreviousPasswordWrong the next password dialog says the previous password was wrong + * (set before an extraction or load is restarted after a wrong password) + */ + static void setPreviousPasswordWrong(bool bWrong); + + /** + * @brief previousPasswordWrong true until a password dialog has shown the hint + */ + static bool previousPasswordWrong(); + private: + static bool s_bPreviousPasswordWrong; QString m_strDesText; QString m_strFileName; int m_iLabelOldHeight = 0; diff --git a/src/source/mainwindow.cpp b/src/source/mainwindow.cpp index 4491aa880..ce0dba8c8 100644 --- a/src/source/mainwindow.cpp +++ b/src/source/mainwindow.cpp @@ -1323,6 +1323,11 @@ void MainWindow::slotJobFinished(ArchiveJob::JobType eJobType, PluginFinishType { qCInfo(appLog) << "操作类型:" << eJobType << "****结束类型:" << eFinishType << "****错误类型" << eErrorType; + // A retry after a wrong password asks for the password again; if the job ended without showing the + // dialog, the hint is still pending: do not retry again, and do not leave the hint for a later dialog. + m_bWrongPasswordUnanswered = PasswordNeededQuery::previousPasswordWrong(); + PasswordNeededQuery::setPreviousPasswordWrong(false); + switch (eFinishType) { case PFT_Nomral: handleJobNormalFinished(eJobType, eErrorType); // 处理job正常结束 @@ -1362,8 +1367,54 @@ void MainWindow::slotJobFinished(ArchiveJob::JobType eJobType, PluginFinishType PERF_PRINT_END("POINT-05"); } +bool MainWindow::retryAfterWrongPassword(ArchiveJob::JobType eJobType) +{ + if (m_bWrongPasswordUnanswered || m_stUnCompressParameter.bBatch) { + return false; + } + + const QString strArchive = m_stUnCompressParameter.strFullPath; + if (strArchive.isEmpty()) { + return false; + } + + if (ArchiveJob::JT_Load == eJobType) { + PasswordNeededQuery::setPreviousPasswordWrong(true); + QTimer::singleShot(0, this, [this, strArchive]() { + loadArchive(strArchive); + refreshPage(); + }); + return true; + } + + if (StartupType::ST_ExtractHere == m_eStartupType || StartupType::ST_Extractto == m_eStartupType) { + // Started from the file manager: extract the same archive the same way again + PasswordNeededQuery::setPreviousPasswordWrong(true); + const StartupType eType = m_eStartupType; + QTimer::singleShot(0, this, [this, eType, strArchive]() { + rightExtract2Path(eType, QStringList() << strArchive); + refreshPage(); + }); + return true; + } + + // Opened in the window: extract again to the destination the user chose (strExtractPath may already + // include the folder named after the archive, which would be added a second time) + const QString strPath = m_strUncompressBasePath; + if (strPath.isEmpty() || m_pUnCompressPage->archiveFullPath().isEmpty()) { + return false; + } + PasswordNeededQuery::setPreviousPasswordWrong(true); + m_ePageID = PI_UnCompress; + QTimer::singleShot(0, this, [this, strPath]() { + slotUncompressClicked(strPath); + }); + return true; +} + void MainWindow::slotUncompressClicked(const QString &strUncompressPath) { + m_strUncompressBasePath = strUncompressPath; qCDebug(appLog) << "Starting extraction to path:" << strUncompressPath; m_operationtype = Operation_Extract; //解压操作 @@ -1934,8 +1985,11 @@ void MainWindow::handleJobErrorFinished(ArchiveJob::JobType eJobType, ErrorType case ET_ArchiveDamaged: showErrorMessage(FI_Load, EI_ArchiveDamaged); break; - // 密码错误 + // 密码错误:重新询问密码,不显示失败界面 case ET_WrongPassword: + if (retryAfterWrongPassword(eJobType)) { + break; + } showErrorMessage(FI_Load, EI_WrongPassword, true); break; // ftp目录不支持seek操作 @@ -2004,8 +2058,11 @@ void MainWindow::handleJobErrorFinished(ArchiveJob::JobType eJobType, ErrorType showErrorMessage(FI_Uncompress, EI_ArchiveDamaged, !(StartupType::ST_ExtractHere == m_eStartupType || StartupType::ST_Extractto == m_eStartupType)); break; - // 密码错误 + // 密码错误:重新询问密码,不显示失败界面 case ET_WrongPassword: + if (retryAfterWrongPassword(eJobType)) { + break; + } showErrorMessage(FI_Uncompress, EI_WrongPassword, !(StartupType::ST_ExtractHere == m_eStartupType || StartupType::ST_Extractto == m_eStartupType)); break; @@ -3549,6 +3606,13 @@ void MainWindow::slotFailureReturn() } break; case FI_Uncompress: { + // An archive opened in the window: go back to its file list instead of an empty home page + if (!(StartupType::ST_ExtractHere == m_eStartupType || StartupType::ST_Extractto == m_eStartupType) + && !m_stUnCompressParameter.bBatch && !m_pUnCompressPage->archiveFullPath().isEmpty()) { + m_ePageID = PI_UnCompress; + refreshPage(); + return; + } m_pUnCompressPage->clear(); // 清空解压界面 } break; diff --git a/src/source/mainwindow.h b/src/source/mainwindow.h index 8f0d39cca..4041090d2 100644 --- a/src/source/mainwindow.h +++ b/src/source/mainwindow.h @@ -185,6 +185,13 @@ public : */ void handleJobErrorFinished(ArchiveJob::JobType eJobType, ErrorType eErrorType); + /** + * @brief retryAfterWrongPassword after a wrong password, restart the same load or extraction so the + * password dialog asks again (with a "Wrong password" hint) instead of showing the failure page + * @return false when it does not apply (batch extraction, no dialog was shown since the last retry, ...) + */ + bool retryAfterWrongPassword(ArchiveJob::JobType eJobType); + /** * @brief addFiles2Archive 向压缩包中添加文件 * @param listFiles 需要添加的文件 @@ -579,6 +586,8 @@ private Q_SLOTS: DFileWatcher *m_pFileWatcher = nullptr; // 文件监控 QString m_strFinalConvertFile; // 格式转换最终的文件全路径 QString m_fileWriteErrorName; // 创建失败的文件名 + bool m_bWrongPasswordUnanswered = false; // a retry after a wrong password ended before any password dialog + QString m_strUncompressBasePath; // the destination chosen on the file list page (before any folder is added) StartupType m_eStartupType = StartupType::ST_Normal; // 操作方式 CalculateSizeThread *m_mywork = nullptr; // 计算文件(夹)大小线程