Skip to content

Conversation

@GongHeng2017
Copy link

@GongHeng2017 GongHeng2017 commented Nov 20, 2025

-- Compressed file seek detection

Log: fix issue
Bug: Bug: https://pms.uniontech.com/bug-view-278613.html

Summary by Sourcery

Implement filesystem seek support detection and gracefully handle cases where seek is not supported during archive operations

Bug Fixes:

  • Prevent hangs on non-seekable filesystems by detecting unsupported seek operations and aborting archive actions

Enhancements:

  • Introduce Common::isSupportSeek to check QFile seek capability
  • Add ET_FileSeekError error type and early-exit logic in CLI and plugin code when seek is unsupported
  • Update UI to display user-friendly messages for file seek errors

-- Compressed file seek detection

Log: fix issue
Bug: Bug: https://pms.uniontech.com/bug-view-278613.html
@sourcery-ai
Copy link

sourcery-ai bot commented Nov 20, 2025

Reviewer's Guide

This PR introduces a file‐seek support check for compressed archives by adding a new utility function, integrating early‐fail seek detection into CLI and plugin operations, and extending the UI to surface a dedicated file‐seek error.

Sequence diagram for early seek support check in archive operations

sequenceDiagram
participant "CliInterface"
participant "Common"
participant "MainWindow"
participant "User"

User->>"CliInterface": Initiate archive operation (list/extract/add)
"CliInterface"->>"Common": isSupportSeek(m_strArchiveName)
"Common"-->>"CliInterface": true/false
alt Seek not supported
    "CliInterface"->>"MainWindow": Emit error ET_FileSeekError
    "MainWindow"->>"User": Show error message (EI_FileSeekError)
end
Loading

Class diagram for updated Common class and error enums

classDiagram
class Common {
    +bool isSupportSeek(QString sFileName)
    +bool isSubpathOfDlnfs(const QString &path)
    +QString handleLongNameforPath(...)
    -bool findDlnfsPath(...)
}

class ErrorType {
    ET_UserCancelOpertion
    ET_ExistVolume
    ET_FileSeekError
}

class ErrorInfo {
    EI_InsufficientDiskSpace
    EI_ArchiveNoData
    EI_ExistVolume
    EI_FileSeekError
}

Common --> ErrorType
Common --> ErrorInfo
Loading

File-Level Changes

Change Details Files
Add isSupportSeek utility to detect file seek support
  • Implement Common::isSupportSeek to test QFile seek on existing files
  • Fallback to creating and testing a QTemporaryFile when source file is missing
  • Declare the new method in common.h and include QTemporaryFile
3rdparty/interface/common.cpp
3rdparty/interface/common.h
Integrate seek check into CLI interface operations
  • Include QTimer in cliinterface.cpp
  • Before list(), extractFiles(), and addFiles(), call isSupportSeek and emit ET_FileSeekError on failure
  • Use QTimer::singleShot to defer error signals and return early
3rdparty/interface/archiveinterface/cliinterface.cpp
Handle ET_FileSeekError in the main UI
  • Add cases for ET_FileSeekError in handleJobErrorFinished for compress, load, and uncompress jobs
  • Map EI_FileSeekError to user‐friendly messages in showErrorMessage
src/source/mainwindow.cpp
Enforce seek support in archive plugins
  • Early return ET_FileSeekError in libzipplugin list() and extractFiles()
  • Early return ET_FileSeekError in libarchiveplugin list() for tar decompression
3rdparty/libzipplugin/libzipplugin.cpp
3rdparty/libarchive/libarchive/libarchiveplugin.cpp
Introduce new error codes for file seek failures
  • Add ET_FileSeekError to ErrorType enum
  • Add EI_FileSeekError to ErrorInfo enum
3rdparty/interface/commonstruct.h
src/source/common/uistruct.h

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. 代码逻辑和功能
    这段代码主要实现了对文件seek操作的支持检查,主要用于处理FTP等不支持seek操作的文件系统场景。新增了isSupportSeek函数来检查文件是否支持seek操作,并在多个关键操作前进行检查。

  2. 存在的问题和改进建议:

a) isSupportSeek函数实现问题:

bool Common::isSupportSeek(QString sFileName)
{
    QFileInfo info(sFileName);
    if(info.exists()) {
        // ... 文件存在时的处理
    } else {
        // 文件不存在时创建临时文件测试
        QString tempDir = info.absoluteDir().path();
        QString fileTemplate = tempDir + "/tempfile_XXXXXX";
        QTemporaryFile tempFile(fileTemplate);
        // ...
    }
}

问题:

  • 文件不存在时的测试逻辑可能不准确,因为临时文件可能创建在不同文件系统上
  • 临时文件创建和测试逻辑可能造成不必要的IO开销

建议改进:

bool Common::isSupportSeek(const QString &sFileName)
{
    // 直接检查目标路径所在的文件系统
    QFileInfo info(sFileName);
    QString path = info.exists() ? sFileName : info.absolutePath();
    
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) {
        return false;
    }
    
    bool canSeek = file.seek(0);
    file.close();
    return canSeek;
}

b) 错误处理不一致:

// 在 extractFiles 中
return PFT_Nomral;  // 拼写错误且返回值不匹配

// 在其他地方
return PFT_Error;   // 正确的错误返回值

建议:

  • 修正拼写:PFT_Nomral -> PFT_Normal
  • 统一错误处理逻辑,所有不支持seek的情况都返回PFT_Error

c) 代码重复:
多个地方都有相同的seek检查逻辑:

if (!m_common->isSupportSeek(m_strArchiveName)) {
    // 相同的错误处理代码
}

建议:

  • 将这个检查逻辑封装成一个公共函数
  • 使用RAII模式确保资源正确释放

d) 性能优化:

QTimer::singleShot(1000, this, [=]() {
    // 错误处理
});

问题:

  • 使用了1秒的固定延迟,可能影响用户体验
  • lambda捕获可能导致内存泄漏风险

建议:

  • 移除不必要的延迟
  • 使用弱引用避免内存泄漏

e) 安全性问题:

  • 临时文件创建时没有考虑权限问题
  • 文件路径处理可能存在路径遍历风险
  1. 其他建议:

a) 添加详细的错误日志:

qDebug() << "Seek operation not supported for file:" << sFileName;

b) 考虑添加缓存机制,避免重复检查同一文件系统

c) 添加单元测试覆盖各种场景:

  • 本地文件系统
  • 网络文件系统
  • 特殊文件系统
  • 权限受限的情况

d) 文档完善:

  • 添加函数注释说明返回值含义
  • 说明不支持seek的典型场景
  • 添加使用示例
  1. 代码风格改进:
  • 统一使用const引用传递QString参数
  • 统一错误处理模式
  • 规范化命名约定

这些修改将提高代码的健壮性、性能和可维护性。

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> `3rdparty/interface/common.cpp:474` </location>
<code_context>
+            tempFile.flush();
+        }
+        tempFile.close();
+        QString sFileName = tempFile.fileName();
+        QFile file(sFileName);
+        if(file.open(QIODevice::ReadOnly)) {
</code_context>

<issue_to_address>
**nitpick:** Variable shadowing of sFileName may cause confusion.

Consider renaming the inner 'sFileName' variable to avoid shadowing the function parameter and improve code clarity.
</issue_to_address>

### Comment 2
<location> `3rdparty/interface/archiveinterface/cliinterface.cpp:110` </location>
<code_context>
+            emit signalprogress(100);
+            emit signalFinished(PFT_Error);
+        });
+        return PFT_Nomral;
+    }
+
</code_context>

<issue_to_address>
**issue (typo):** Typo in return value: 'PFT_Nomral' should be 'PFT_Normal'.

Please update the return value to 'PFT_Normal' to ensure correctness.

```suggestion
+        return PFT_Normal;
```
</issue_to_address>

### Comment 3
<location> `3rdparty/interface/common.cpp:449` </location>
<code_context>
     });
 }

+bool Common::isSupportSeek(QString sFileName)
+{
+    QFileInfo info(sFileName);
</code_context>

<issue_to_address>
**issue (complexity):** Consider extracting the file open/seek logic into a helper function to eliminate duplication and clarify intent.

```suggestion
Extract the “open-and-seek” logic into a small helper, then collapse the two branches into a single flow. For example:

```cpp
// Common.h
class Common {

    static bool trySeekFile(const QString& path);
    bool isSupportSeek(const QString& sFileName);
};

// Common.cpp
bool Common::trySeekFile(const QString& path)
{
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly))
        return false;
    bool ok = file.seek(0);
    file.close();
    return ok;
}

bool Common::isSupportSeek(const QString& sFileName)
{
    QFileInfo info(sFileName);
    if (info.exists()) {
        return trySeekFile(sFileName);
    }

    // Create a temp file in the same directory
    QString templatePath = info.absoluteDir().filePath("tempfile_XXXXXX");
    QTemporaryFile tempFile(templatePath);
    tempFile.setAutoRemove(true);
    if (!tempFile.open())
        return false;
    tempFile.write("test\n");
    tempFile.close();

    return trySeekFile(tempFile.fileName());
}
```

Benefits:
- No duplicate open/seek/close
- Single return points for each branch
- Clear intent in `trySeekFile`
```
</issue_to_address>

### Comment 4
<location> `3rdparty/interface/archiveinterface/cliinterface.cpp:78` </location>
<code_context>
     m_workStatus = WT_List;

+    // 是否支持seek
+    if(!m_common->isSupportSeek(m_strArchiveName)) {
+        QTimer::singleShot(1000, this, [=]() {
+            m_eErrorType = ET_FileSeekError;
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the repeated QTimer error-handling code into a helper function and macro to reduce duplication.

Here’s one way to collapse those three identical QTimer blocks into a single helper + guard macro.  This preserves your exact return-values but only writes the “error timer” code once.

```cpp
// In CliInterface.h (or a common private section)
private:
    void emitSeekErrorAndFinish() {
        QTimer::singleShot(1000, this, [this]() {
            m_eErrorType = ET_FileSeekError;
            emit signalprogress(100);
            emit signalFinished(PFT_Error);
        });
    }

#define RETURN_IF_NO_SEEK(retval)           \
    do {                                    \
      if (!m_common->isSupportSeek(m_strArchiveName)) { \
        emitSeekErrorAndFinish();           \
        return retval;                      \
      }                                     \
    } while (0)
```

Then your three methods collapse to:

```cpp
PluginFinishType CliInterface::list() {
    setPassword(QString());
    DataManager::get_instance().resetArchiveData();
    m_setHasRootDirs.clear();
    m_setHasHandlesDirs.clear();
    m_workStatus = WT_List;

    RETURN_IF_NO_SEEK(PFT_Error);

    bool ret = runProcess( /* … */ );
    return ret ? PFT_Nomral : PFT_Error;
}

PluginFinishType CliInterface::extractFiles(...) {
    RETURN_IF_NO_SEEK(PFT_Nomral);

    // … rest of extractFiles …
}

PluginFinishType CliInterface::addFiles(...) {
    // only guard when files not empty
    if (!files.isEmpty()) {
        RETURN_IF_NO_SEEK(PFT_Nomral);
    }

    // … rest of addFiles …
}
```

That removes three copies of the QTimer+lambda while keeping every return‐value exactly as before.
</issue_to_address>

### Comment 5
<location> `src/source/mainwindow.cpp:1812` </location>
<code_context>
             showErrorMessage(FI_Compress, EI_ExistVolume, true);
             break;
+        // ftp目录不支持seek操作
+        case ET_FileSeekError:
+            showErrorMessage(FI_Compress, EI_FileSeekError, true);
+            break;
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring repeated ET_FileSeekError/EI_FileSeekError handling into helper functions to avoid code duplication.

```markdown
You’re repeating the same ET_FileSeekError/EI_FileSeekError handling in every switch. You can pull that logic out into one helper per subsystem and early‐return, or at least factor the UI messages into a lookup—then each switch only needs:

  1) one `if (eErrorType == ET_FileSeekError) { … }`
  2) a normal switch without the duplicated case

Example for the job‐level error→popup section:

```cpp
// new helper at top of file:
bool handleSeekError(ArchiveJob::JobType jt, ErrorType e, bool needsConfirm)
{
    if (e != ET_FileSeekError)
        return false;

    // map job → FI_*
    FileInterface fi;
    switch (jt) {
    case ArchiveJob::JT_Compress: fi = FI_Compress; break;
    case ArchiveJob::JT_Load:     fi = FI_Load;     break;
    case ArchiveJob::JT_Extract:
    case ArchiveJob::JT_StepExtract: fi = FI_Uncompress; break;
    default:                       fi = FI_Compress; /* fallback */ }

    showErrorMessage(fi, EI_FileSeekError, needsConfirm);
    return true;
}

// in your outer job switch:
case ArchiveJob::JT_Compress: {
    bool needsConfirm = true; // existing logic
    if (handleSeekError(m_eJobType, eErrorType, needsConfirm))
        break;

    switch (eErrorType) {
      // … now no ET_FileSeekError here …
    }
} break;
```

And for the UI‐mapping you can centralize the detail strings:

```cpp
// helper in .cpp
static QString seekDetail(FileInterface fi) {
    switch (fi) {
    case FI_Compress:
      return tr("No compression support in current directory. Download the files to a local device.");
    case FI_Load:
      return tr("Can't open compressed packages in current directory. Download the compressed package to a local device.");
    case FI_Uncompress:
      return tr("No extraction support in current directory. Download the compressed package to a local device.");
    default:
      return tr("Operation not supported in current directory.");
    }
}

// then in your failure‐page switch:
case EI_FileSeekError:
    m_pFailurePage->setFailureDetail(seekDetail(currentFI));
    break;
```

These two small helpers remove all the repetitive `case ET_FileSeekError` and `case EI_FileSeekError` blocks while keeping your behavior identical.
</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.

tempFile.flush();
}
tempFile.close();
QString sFileName = tempFile.fileName();
Copy link

Choose a reason for hiding this comment

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

nitpick: Variable shadowing of sFileName may cause confusion.

Consider renaming the inner 'sFileName' variable to avoid shadowing the function parameter and improve code clarity.

emit signalprogress(100);
emit signalFinished(PFT_Error);
});
return PFT_Nomral;
Copy link

Choose a reason for hiding this comment

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

issue (typo): Typo in return value: 'PFT_Nomral' should be 'PFT_Normal'.

Please update the return value to 'PFT_Normal' to ensure correctness.

Suggested change
return PFT_Nomral;
+ return PFT_Normal;

m_workStatus = WT_List;

// 是否支持seek
if(!m_common->isSupportSeek(m_strArchiveName)) {
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider refactoring the repeated QTimer error-handling code into a helper function and macro to reduce duplication.

Here’s one way to collapse those three identical QTimer blocks into a single helper + guard macro. This preserves your exact return-values but only writes the “error timer” code once.

// In CliInterface.h (or a common private section)
private:
    void emitSeekErrorAndFinish() {
        QTimer::singleShot(1000, this, [this]() {
            m_eErrorType = ET_FileSeekError;
            emit signalprogress(100);
            emit signalFinished(PFT_Error);
        });
    }

#define RETURN_IF_NO_SEEK(retval)           \
    do {                                    \
      if (!m_common->isSupportSeek(m_strArchiveName)) { \
        emitSeekErrorAndFinish();           \
        return retval;                      \
      }                                     \
    } while (0)

Then your three methods collapse to:

PluginFinishType CliInterface::list() {
    setPassword(QString());
    DataManager::get_instance().resetArchiveData();
    m_setHasRootDirs.clear();
    m_setHasHandlesDirs.clear();
    m_workStatus = WT_List;

    RETURN_IF_NO_SEEK(PFT_Error);

    bool ret = runProcess( /**/ );
    return ret ? PFT_Nomral : PFT_Error;
}

PluginFinishType CliInterface::extractFiles(...) {
    RETURN_IF_NO_SEEK(PFT_Nomral);

    // … rest of extractFiles …
}

PluginFinishType CliInterface::addFiles(...) {
    // only guard when files not empty
    if (!files.isEmpty()) {
        RETURN_IF_NO_SEEK(PFT_Nomral);
    }

    // … rest of addFiles …
}

That removes three copies of the QTimer+lambda while keeping every return‐value exactly as before.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: GongHeng2017, 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

@GongHeng2017
Copy link
Author

/merge

@deepin-bot deepin-bot bot merged commit fc66b9e into linuxdeepin:develop/snipe Nov 20, 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