Skip to content

BUG: fix(diagnostic): 修复诊断工具误报与“再次诊断”无响应#109

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
JWWTSL:master
Sep 16, 2025
Merged

BUG: fix(diagnostic): 修复诊断工具误报与“再次诊断”无响应#109
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
JWWTSL:master

Conversation

@JWWTSL
Copy link
Contributor

@JWWTSL JWWTSL commented Sep 16, 2025

问题

  • 下载成功但诊断工具仍提示“DTS 下载失败”
  • DHT 状态偶发误判
  • 诊断结果需鼠标点一下窗口才刷新

原因

  • aria2 返回的 enable-dht 可能为字符串/布尔,原判定不够稳健
  • 诊断模型行数固定为 6,却按“插入行”方式更新,未正确触发视图刷新

最小改动与解决

  • src/src/ui/mainFrame/mainframe.cpp
    • DHT 判定使用 obj.value("enable-dht").toVariant().toBool(),兼容不同类型,避免误判
    • 打开诊断窗口前调用 Aria2RPCInterface::getGlobalOption() 预热,消除时序导致的误判(不改诊断工具逻辑)
  • src/src/ui/settings/diagnostictool.cpp
    • appendData:不再插入行,改为在内部列表追加后对该行三列 emit dataChanged,保证视图立即刷新
    • clearData:使用 beginResetModel/endResetModel,确保清空时完整重置

验证

  • 成功下载后打开“诊断工具”,DHT/HTTP/BT/磁链在网络可用时显示“正常”
  • 点击“再次诊断”,列表结果自动更新,无需与窗口交互

@sourcery-ai
Copy link

sourcery-ai bot commented Sep 16, 2025

Reviewer's Guide

Refactors the diagnostic tool’s sequencing to await aria2 options before running checks, swaps external network probes for Qt HEAD requests, relaxes DHT detection by removing file checks, and leverages proper model reset to enable stable “redo diagnostics” behavior.

Sequence diagram for improved diagnostic tool workflow

sequenceDiagram
    participant User as actor User
    participant DiagnosticTool
    participant Aria2RPCInterface
    participant DiagnosticModel
    User->>DiagnosticTool: Click "再次诊断" button
    DiagnosticTool->>DiagnosticModel: clearData() (beginResetModel/endResetModel)
    DiagnosticTool->>Aria2RPCInterface: getGlobalOption()
    Aria2RPCInterface-->>DiagnosticTool: ariaOption(tracker, enable-dht)
    DiagnosticTool->>DiagnosticTool: Set m_OptionReady = true
    DiagnosticTool->>DiagnosticModel: Run diagnostic checks (appendData)
    DiagnosticTool->>User: Enable "再次诊断" button
Loading

Updated class diagram for DiagnosticTool and DiagnosticModel

classDiagram
    class DiagnosticTool {
        - bool m_IsHasTracks
        - bool m_IsHasDHT
        - bool m_OptionReady
        + void startDiagnostic()
        + void onAriaOption(bool, bool)
    }
    class DiagnosticModel {
        - QList<bool> m_DiagnosticStatusList
        + void appendData(bool)
        + void clearData()
        + bool setData(const QModelIndex &, const QVariant &, int)
    }
    DiagnosticTool --> DiagnosticModel : uses
Loading

Class diagram for Func network connectivity check refactor

classDiagram
    class Func {
        + bool isNetConnect()
        + bool isLanConnect()
    }
    Func : isNetConnect() now uses QNetworkAccessManager
    Func : isNetConnect() falls back to isLanConnect() if HTTP probe fails
Loading

File-Level Changes

Change Details Files
Synchronize diagnostic start with aria2 options
  • Introduce m_OptionReady flag and reset logic
  • Replace staggered timers with a runChecks lambda
  • Add polling loop to wait up to 2.5s for options
src/src/ui/settings/diagnostictool.cpp
src/include/ui/settings/diagnostictool.h
Switch network connectivity check to Qt HEAD requests
  • Implement QNetworkAccessManager-based HTTP HEAD probe
  • Recognize 204/2xx/3xx as success
  • Fallback to LAN check only on HTTP failure
src/src/ui/func.cpp
Relax DHT status determination
  • Use only enable-dht flag for DHT�0
  • Remove dht-file-path existence requirement
src/src/ui/mainFrame/mainframe.cpp
Ensure model clears and refreshes correctly
  • Wrap clearData with beginResetModel()/endResetModel()
  • Clear data before starting new diagnostics
src/src/ui/settings/diagnostictool.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

@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 - here's some feedback:

  • Use logical operators (&&, ||) for combining boolean checks instead of bitwise (&, |) for clarity and readability.
  • Rather than polling m_OptionReady with a QTimer and a heap‐allocated counter, consider invoking runChecks directly from the onAriaOption callback to simplify flow and avoid manual memory management.
  • Avoid dynamically allocating the attempts counter; use a local variable captured by reference or a QSharedPointer to keep the code more idiomatic and safer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Use logical operators (&&, ||) for combining boolean checks instead of bitwise (&, |) for clarity and readability.
- Rather than polling m_OptionReady with a QTimer and a heap‐allocated counter, consider invoking runChecks directly from the onAriaOption callback to simplify flow and avoid manual memory management.
- Avoid dynamically allocating the attempts counter; use a local variable captured by reference or a QSharedPointer to keep the code more idiomatic and safer.

## Individual Comments

### Comment 1
<location> `src/src/ui/settings/diagnostictool.cpp:99-104` </location>
<code_context>
     m_Button->setFixedWidth(202);
     connect(m_Button, &QPushButton::clicked, this, [=]() {
         m_Model->clearData();
+        m_OptionReady = false;
         QTimer::singleShot(500, this, [=]() {
</code_context>

<issue_to_address>
**suggestion:** Calling clearData twice in startDiagnostic may be redundant.

Since clearData is invoked in both the button handler and startDiagnostic, this may lead to redundant operations. Review if both calls are necessary and remove one if possible.

```suggestion
    connect(m_Button, &QPushButton::clicked, this, [=]() {
        m_OptionReady = false;
        QTimer::singleShot(500, this, [=]() {
            startDiagnostic();
        });
```
</issue_to_address>

### Comment 2
<location> `src/src/ui/settings/diagnostictool.cpp:208-217` </location>
<code_context>
-        m_Button->setEnabled(true);
-    });
-#endif
+    if (m_OptionReady) {
+        QTimer::singleShot(0, this, runChecks);
+    } else {
+        // Wait up to ~2.5s for options; poll in small intervals
+        int *attempts = new int(0);
+        QTimer *poller = new QTimer(this);
+        poller->setInterval(100);
+        connect(poller, &QTimer::timeout, this, [=]() {
+            if (m_OptionReady || *attempts >= 25) {
+                poller->stop();
+                poller->deleteLater();
+                delete attempts;
+                runChecks();
+                return;
+            }
+            (*attempts)++;
+        });
+        poller->start();
+    }
 }
</code_context>

<issue_to_address>
**issue (bug_risk):** Polling for m_OptionReady with a QTimer may leak memory if DiagnosticTool is destroyed early.

Since both the int pointer and QTimer are dynamically allocated, ensure they are properly cleaned up if DiagnosticTool is destroyed before polling finishes. Using QPointer or handling cleanup in the destructor can help prevent memory leaks.
</issue_to_address>

### Comment 3
<location> `src/src/ui/func.cpp:26-35` </location>
<code_context>
+    QNetworkAccessManager manager;
</code_context>

<issue_to_address>
**issue (performance):** Using QNetworkAccessManager synchronously in isNetConnect may block the UI thread.

Refactor to use asynchronous network checks or move this logic to a separate thread to prevent blocking the main thread.
</issue_to_address>

### Comment 4
<location> `src/src/ui/settings/diagnostictool.cpp:180` </location>
<code_context>
     //m_Model->setData(isHasTracks);
 }

 void DiagnosticTool::startDiagnostic()
 {
     qDebug() << "Starting diagnostic process";
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the polling and lambda logic into a dedicated Qt slot to simplify control flow and resource management.

Here’s one way to collapse the manual‐polling, raw pointers and nested lambdas into a single Qt slot. The idea is:

1.  Move your `runChecks` lambda into its own `performChecks()` slot.
2.  In `startDiagnostic()`, just kick off the RPC call and clear your UI.
3.  In `onAriaOption()`, set your flags *and* trigger `performChecks()` (via a single‐shot or direct call).
4.  Remove the `new int`, `QTimer* poller`, and delete logic entirely.

— DiagnosticTool.h —
```cpp
class DiagnosticTool : public DDialog {
    Q_OBJECT
private slots:
    void startDiagnostic();
    void onAriaOption(bool isHasTracks, bool isHasDHT);
    void performChecks();
    // …
};
```

— DiagnosticTool.cpp —
```cpp
void DiagnosticTool::startDiagnostic()
{
    qDebug() << "Starting diagnostic process";
    m_OptionReady = false;
    m_Button->setEnabled(false);
    m_Model->clearData();
    Aria2RPCInterface::instance()->getGlobalOption();
    // if options already arrived
    if (m_OptionReady) {
        QTimer::singleShot(0, this, SLOT(performChecks()));
    }
}

void DiagnosticTool::onAriaOption(bool isHasTracks, bool isHasDHT)
{
    qDebug() << "Received aria2 options:" << isHasTracks << isHasDHT;
    m_IsHasTracks = isHasTracks;
    m_IsHasDHT     = isHasDHT;
    m_OptionReady  = true;
    // now that options are ready, run checks
    QTimer::singleShot(0, this, SLOT(performChecks()));
}

void DiagnosticTool::performChecks()
{
    // Row 0
    m_Model->appendData(Func::isIPV6Connect());
    // Row 1
    m_Model->appendData(m_IsHasDHT & Func::isNetConnect());
    // Row 2
    m_Model->appendData(Func::isHTTPConnect());
    // Row 3 & 4
    bool btOk = (m_IsHasTracks | m_IsHasDHT) & Func::isNetConnect();
    m_Model->appendData(btOk);
    m_Model->appendData(btOk);
    // Row 5
    m_Model->appendData(Func::isNetConnect());
    m_Button->setEnabled(true);
}
```

Benefits:

•   No raw `new int` or manual `delete`  
•   No polling loop – Qt’s signal/slot delivers exactly when options arrive  
•   All timing remains effectively “immediate” but you can still sprinkle in `QTimer::singleShot(...)` delays if you really need them (e.g. to animate row‐by‐row appending) without the bookkeeping.
</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 208 to 217
if (m_OptionReady) {
QTimer::singleShot(0, this, runChecks);
} else {
// Wait up to ~2.5s for options; poll in small intervals
int *attempts = new int(0);
QTimer *poller = new QTimer(this);
poller->setInterval(100);
connect(poller, &QTimer::timeout, this, [=]() {
if (m_OptionReady || *attempts >= 25) {
poller->stop();
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Polling for m_OptionReady with a QTimer may leak memory if DiagnosticTool is destroyed early.

Since both the int pointer and QTimer are dynamically allocated, ensure they are properly cleaned up if DiagnosticTool is destroyed before polling finishes. Using QPointer or handling cleanup in the destructor can help prevent memory leaks.

Comment on lines 26 to 35
QNetworkAccessManager manager;
auto checkUrl = [&](const QUrl &url, int timeoutMs) -> bool {
QNetworkRequest req(url);
QNetworkReply *reply = manager.head(req);
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
timer.start(timeoutMs);
Copy link

Choose a reason for hiding this comment

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

issue (performance): Using QNetworkAccessManager synchronously in isNetConnect may block the UI thread.

Refactor to use asynchronous network checks or move this logic to a separate thread to prevent blocking the main thread.

//m_Model->setData(isHasTracks);
}

void DiagnosticTool::startDiagnostic()
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 polling and lambda logic into a dedicated Qt slot to simplify control flow and resource management.

Here’s one way to collapse the manual‐polling, raw pointers and nested lambdas into a single Qt slot. The idea is:

  1. Move your runChecks lambda into its own performChecks() slot.
  2. In startDiagnostic(), just kick off the RPC call and clear your UI.
  3. In onAriaOption(), set your flags and trigger performChecks() (via a single‐shot or direct call).
  4. Remove the new int, QTimer* poller, and delete logic entirely.

— DiagnosticTool.h —

class DiagnosticTool : public DDialog {
    Q_OBJECT
private slots:
    void startDiagnostic();
    void onAriaOption(bool isHasTracks, bool isHasDHT);
    void performChecks();
    //
};

— DiagnosticTool.cpp —

void DiagnosticTool::startDiagnostic()
{
    qDebug() << "Starting diagnostic process";
    m_OptionReady = false;
    m_Button->setEnabled(false);
    m_Model->clearData();
    Aria2RPCInterface::instance()->getGlobalOption();
    // if options already arrived
    if (m_OptionReady) {
        QTimer::singleShot(0, this, SLOT(performChecks()));
    }
}

void DiagnosticTool::onAriaOption(bool isHasTracks, bool isHasDHT)
{
    qDebug() << "Received aria2 options:" << isHasTracks << isHasDHT;
    m_IsHasTracks = isHasTracks;
    m_IsHasDHT     = isHasDHT;
    m_OptionReady  = true;
    // now that options are ready, run checks
    QTimer::singleShot(0, this, SLOT(performChecks()));
}

void DiagnosticTool::performChecks()
{
    // Row 0
    m_Model->appendData(Func::isIPV6Connect());
    // Row 1
    m_Model->appendData(m_IsHasDHT & Func::isNetConnect());
    // Row 2
    m_Model->appendData(Func::isHTTPConnect());
    // Row 3 & 4
    bool btOk = (m_IsHasTracks | m_IsHasDHT) & Func::isNetConnect();
    m_Model->appendData(btOk);
    m_Model->appendData(btOk);
    // Row 5
    m_Model->appendData(Func::isNetConnect());
    m_Button->setEnabled(true);
}

Benefits:

• No raw new int or manual delete
• No polling loop – Qt’s signal/slot delivers exactly when options arrive
• All timing remains effectively “immediate” but you can still sprinkle in QTimer::singleShot(...) delays if you really need them (e.g. to animate row‐by‐row appending) without the bookkeeping.

…resh of diagnostic results

Issues
- Download succeeds but diagnostic tool still shows “DTS download failed”
- Occasional incorrect DHT status detection
- Diagnostic results require mouse click to refresh window

Cause
- aria2's `enable-dht` response may be string/boolean, causing unstable detection
- Diagnostic model fixed at 6 rows but updated via “insert row” method, failing to trigger view refresh

Minimal Changes & Fixes
- src/src/ui/mainFrame/mainframe.cpp
  - DHT check now uses obj.value(“enable-dht”).toVariant().toBool() to handle different types and prevent misjudgments
  - Pre-warm Aria2RPCInterface::getGlobalOption() before opening diagnostic window to eliminate timing-related errors (No changes to diagnostic tool logic)
- src/src/ui/settings/diagnostictool.cpp
  - appendData: No longer inserts rows; instead appends to internal list and emits dataChanged for all three columns of that row to ensure immediate view refresh
  - clearData: Uses beginResetModel/endResetModel to ensure complete reset during clearing

Verification
- After successful download, open “Diagnostic Tool”; DHT/HTTP/BT/magnet links display “Normal” when network is available
- Click “Diagnose Again”; list results update automatically without window interaction

BUG: https://pms.uniontech.com/bug-view-331441.html
@deepin-ci-robot
Copy link
Contributor

deepin pr auto review

我来对这段代码进行详细审查,并提出改进建议:

1. 代码逻辑审查

mainframe.cpp 的改进:

  1. showDiagnosticTool() 函数中添加了主动获取全局配置的代码,这是一个很好的改进,可以避免诊断时的时序问题。
  2. onRpcSuccess() 中简化了 DHT 检查逻辑,使用 toVariant().toBool() 更稳健地处理不同类型的 enable-dht 值。

diagnostictool.cpp 的改进:

  1. 修改了 appendData() 函数,限制最大行数为6,并改用 dataChanged 信号通知视图更新。
  2. clearData() 中使用 beginResetModel()endResetModel() 来通知视图重置。

2. 语法逻辑问题

  1. diagnostictool.cpp 中的 appendData 函数

    • 虽然限制了最大行数为6,但没有处理超过6行的情况。建议添加日志记录或警告。
    • 只更新了0-2列,如果模型有更多列,可能会导致其他列不更新。
  2. mainframe.cpp 中的 onRpcSuccess 函数

    • 使用 toVariant().toBool() 虽然更稳健,但没有处理可能的 null 值情况。建议添加 null 检查。

3. 代码质量改进

  1. 错误处理

    • showDiagnosticTool() 中,建议添加对 Aria2RPCInterface::instance()->getGlobalOption() 的错误处理。
    • onRpcSuccess() 中,建议添加对 JSON 解析失败的错误处理。
  2. 代码注释

    • 现有的注释很好,说明了修改原因。建议继续保持这种做法。
  3. 常量定义

    • diagnostictool.cpp 中硬编码了最大行数6,建议定义为常量。

4. 代码性能优化

  1. diagnostictool.cpp

    • 当前实现已经避免了频繁的 beginInsertRows/endInsertRows 调用,性能较好。
    • dataChanged 信号可能会频繁触发,可以考虑合并多个更新。
  2. mainframe.cpp

    • showDiagnosticTool() 中主动获取配置可能会增加不必要的网络请求,可以考虑只在需要时获取。

5. 代码安全考虑

  1. JSON 解析安全

    • onRpcSuccess() 中,建议添加对 JSON 结构的完整验证,防止因意外结构导致崩溃。
    • 对于 obj.value("bt-tracker").toString() 等操作,建议检查值是否存在。
  2. 信号连接安全

    • showDiagnosticTool() 中,信号连接应该在对话框显示前完成,建议检查连接是否成功。

改进建议代码示例:

// diagnostictool.cpp
const int MAX_DIAGNOSTIC_ROWS = 6;

void DiagnosticModel::appendData(bool b)
{
    qDebug() << "Appending diagnostic data, status:" << b;
    const int row = m_DiagnosticStatusList.size();
    if (row >= MAX_DIAGNOSTIC_ROWS) {
        qWarning() << "Diagnostic status list is full, ignoring new data";
        return;
    }
    
    m_DiagnosticStatusList.append(b);
    emit dataChanged(index(row, 0), index(row, columnCount() - 1));
}

// mainframe.cpp
void MainFrame::showDiagnosticTool()
{
    DiagnosticTool control(this);
    connect(this, &MainFrame::ariaOption, &control, &DiagnosticTool::onAriaOption);
    
    // 获取全局配置并处理可能的错误
    QJsonObject globalOptions;
    if (!Aria2RPCInterface::instance()->getGlobalOption(&globalOptions)) {
        qWarning() << "Failed to get global options for diagnostic";
        // 可以考虑使用默认值或显示错误信息
    }
    
    control.exec();
}

void MainFrame::onRpcSuccess(QString method, QJsonObject json)
{
    if (json.isEmpty()) {
        qWarning() << "Empty JSON received for method:" << method;
        return;
    }
    
    if (method == ARIA2C_METHOD_GET_GLOBAL_OPTION) {
        QJsonObject obj = json.value("result").toObject();
        if (obj.isEmpty()) {
            qWarning() << "Empty result object in getGlobalOption";
            return;
        }
        
        QString tracker = obj.value("bt-tracker").toString();
        // 安全地获取 enable-dht 值
        bool isHasDHT = false;
        if (obj.contains("enable-dht")) {
            isHasDHT = obj.value("enable-dht").toVariant().toBool();
        }
        
        emit ariaOption(!tracker.isEmpty(), isHasDHT);
    }
}

总结:这些修改提高了代码的健壮性和安全性,同时保持了良好的性能。建议在实际应用中添加适当的错误处理和日志记录,以便于问题排查。

@deepin-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

@JWWTSL
Copy link
Contributor Author

JWWTSL commented Sep 16, 2025

/forcemerge

@deepin-bot
Copy link
Contributor

deepin-bot bot commented Sep 16, 2025

This pr force merged! (status: unstable)

@deepin-bot deepin-bot bot merged commit 43ec5ca into linuxdeepin:master Sep 16, 2025
17 of 18 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