Skip to content

Fix: [file-enumerator] use g_file_enumerator_next_file()#260

Open
GongHeng2017 wants to merge 1 commit intolinuxdeepin:release/eaglefrom
GongHeng2017:202602252125-release-eagle-fix
Open

Fix: [file-enumerator] use g_file_enumerator_next_file()#260
GongHeng2017 wants to merge 1 commit intolinuxdeepin:release/eaglefrom
GongHeng2017:202602252125-release-eagle-fix

Conversation

@GongHeng2017
Copy link
Contributor

--Replace use of g_file_enumerator_iterate() with g_file_enumerator_next_file()
in DEnumerator::hasNext() so the enumerator's internal cursor is advanced by
GIO and error/end conditions are handled unambiguously.

--Using g_file_enumerator_next_file() yields clearer semantics: non-NULL -> next entry,
NULL + NULL error -> end, NULL + non-NULL error -> error. This avoids premature termination
caused by backend-specific iterate() quirks.

--Construct next entry URL via the existing buildUrl(...) helper and preserve
existing logic that creates the DFileInfo from the returned GFileInfo. Preserve
existing error handling and d->checkFilter() behavior and recursive skipping of filtered entries.

--Fix URL construction so joining directory path and filename does not introduce
an extra '/' (previously produced e.g. .../vault_unlocked//file). The logic now
concatenates carefully (handles root path and trailing slashes) to avoid duplicate separators.

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

    --Replace use of g_file_enumerator_iterate() with g_file_enumerator_next_file()
    in DEnumerator::hasNext() so the enumerator's internal cursor is advanced by
    GIO and error/end conditions are handled unambiguously.

    --Using g_file_enumerator_next_file() yields clearer semantics: non-NULL -> next entry,
    NULL + NULL error -> end, NULL + non-NULL error -> error. This avoids premature termination
    caused by backend-specific iterate() quirks.

    --Construct next entry URL via the existing buildUrl(...) helper and preserve
    existing logic that creates the DFileInfo from the returned GFileInfo. Preserve
    existing error handling and d->checkFilter() behavior and recursive skipping of filtered entries.

    --Fix URL construction so joining directory path and filename does not introduce
    an extra '/' (previously produced e.g. .../vault_unlocked//file). The logic now
    concatenates carefully (handles root path and trailing slashes) to avoid duplicate separators.

Log: fix issue
Bug: https://pms.uniontech.com//bug-view-351177.html
@deepin-ci-robot
Copy link

deepin pr auto review

这段代码的修改主要涉及两个部分:DEnumeratorPrivate::buildUrl 函数的路径拼接逻辑优化,以及 DEnumerator::hasNext 函数中 GLib API 的调用方式变更。下面是对这两处修改的详细审查和改进意见:

1. DEnumeratorPrivate::buildUrl 函数审查

修改内容:
将原来的三元表达式路径拼接逻辑改为显式的 if-else 结构,并增加了对 dirPath 是否以 / 结尾的检查。

审查意见:

  • 语法逻辑: 修改后的逻辑是正确的。它处理了根路径("/")和非根路径的情况,并且在非根路径下,检查了路径是否已经以 / 结尾,避免了重复添加 / 导致的路径错误(如 //foo)。
  • 代码质量:
    • 优点: 显式的 if-else 结构比嵌套的三元运算符更易读,增加了对路径分隔符的检查,使得路径构建更加健壮。
    • 建议: 可以使用 QDir::cleanPath() 来处理生成的路径,以确保路径格式规范(例如去除多余的斜杠,处理 ... 等),虽然当前的逻辑已经处理了斜杠问题,但 cleanPath 能提供更强的保障。
    • 建议: QString(fileName) 的转换略显冗余,如果 fileNameconst char*QString 有隐式转换构造函数,可以直接拼接:path = "/" + fileName;

改进后的代码建议:

QUrl DEnumeratorPrivate::buildUrl(const QUrl &url, const char *fileName)
{
    QString path;
    const QString &dirPath = url.path();
    
    if (dirPath == "/") {
        path = "/" + QString(fileName);
    } else {
        // 使用 QStringBuilder 进行更高效的拼接,并确保只有一个分隔符
        if (dirPath.endsWith('/')) {
            path = dirPath % QString(fileName);
        } else {
            path = dirPath % "/" % QString(fileName);
        }
    }

    // 使用 cleanPath 规范化路径(可选,视具体需求而定,因为可能会改变符号链接的含义)
    // path = QDir::cleanPath(path); 

    QUrl nextUrl = QUrl::fromLocalFile(path);

    // ... 后续 userInfo 逻辑保持不变 ...
    if (url.userInfo().startsWith("originPath::")) {
        // ...
    }
    
    return nextUrl;
}

2. DEnumerator::hasNext 函数审查

修改内容:
g_file_enumerator_iterate 替换为 g_file_enumerator_next_file,并重构了相关的错误处理和 URL 构建逻辑。

审查意见:

  • 语法逻辑:

    • API 变更: g_file_enumerator_next_file 确实比 iterate 更专注于获取下一个文件的信息,而不需要同时处理 GFile 对象,这在只需要文件名构建 URL 的场景下是合理的。
    • URL 构建: 修改后使用了 d->buildUrl(d->uri, g_file_info_get_name(nextInfo))。这里有一个潜在的逻辑风险:d->uri 是遍历的根目录,而 g_file_info_get_name 获取的是文件名(不含路径)。如果 hasNext 是在递归遍历子目录(通过 stackEnumerator 暗示的栈结构),直接使用 d->uri(初始根目录)拼接文件名是不正确的。它应该使用当前栈顶 enumerator 对应的目录 URL。
    • 错误处理逻辑:
      • 原代码在 iterate 返回 true 但 gfileInfo 为空时,会弹出栈顶 enumerator 并递归调用 hasNext。新代码移除了这个逻辑。如果 next_file 返回 NULL 且没有 error,通常表示遍历结束。但如果这是在处理子目录遍历,需要显式地处理“当前目录遍历结束,回到父目录”的逻辑(即原代码的 pop 和递归部分)。新代码似乎丢失了处理子目录遍历完毕后返回上级的逻辑,除非 hasNext 的调用者负责维护栈结构(但 d->stackEnumerator 的存在暗示内部维护)。
      • if (nextInfo) 块中,错误处理逻辑 if (gerror) ... 放在了获取文件成功之后。通常 next_file 成功时 gerror 应为 NULL。如果 gerror 不为空,说明虽然有返回值但发生了某种警告或错误。这里 return true 可能会导致错误被忽略(虽然 setErrorFromGError 被调用了)。
      • if (gerror) 块(位于 nextInfo 为 NULL 的分支)中,return true 意味着即使发生错误也认为有下一个元素,这可能会导致上层逻辑尝试读取无效的 nextUrldfileInfoNext。通常发生严重错误应返回 false
  • 代码性能:

    • g_file_enumerator_next_file 通常比 iterate 更轻量,因为它不分配 GFile 对象,这是一个性能优化点。
    • g_file_info_dup 会复制 GFileInfo,确保了所有权清晰,但增加了开销。需确认 createFileInfoByUri 是否会接管引用计数,如果是,则可以用 g_object_ref 代替 dup 以节省一次深拷贝。
  • 代码安全:

    • 内存泄漏风险: 原代码中 g_file_enumerator_iterategfileInfogfile 由迭代器内部管理,不需要手动释放(除非 dup)。新代码中 nextInfo 使用了 g_object_unref,这是正确的。
    • 空指针解引用: g_file_info_get_name(nextInfo)nextInfo 非空时是安全的。
    • 错误状态下的安全性: 如上所述,在 gerror 发生时返回 true 可能不安全,因为 nextUrl 可能未被正确初始化。

改进建议:

  1. 修复递归/栈遍历逻辑: 如果 stackEnumerator 用于子目录遍历,必须恢复“当前目录结束,弹出栈并递归”的逻辑。
  2. 修复 URL 构建逻辑: 应该获取当前栈顶 enumerator 对应的路径,而不是始终使用 d->uri
  3. 优化错误处理: 明确区分“遍历结束”和“发生错误”。

改进后的代码建议(假设需要处理子目录遍历):

bool DEnumerator::hasNext() const
{
    if (d->stackEnumerator.isEmpty())
        return false;

    GFileEnumerator *enumerator = d->stackEnumerator.top();

    g_autoptr(GError) gerror = nullptr;
    d->checkAndResetCancel();

    // 获取下一个文件信息
    GFileInfo *nextInfo = g_file_enumerator_next_file(enumerator, d->cancellable, &gerror);

    if (nextInfo) {
        // 获取当前正在遍历的目录的 URI
        // 注意:这里需要一种方式获取 enumerator 对应的目录 URL。
        // 原代码通过 iterate 获取的 GFile 获取 path,现在我们需要从 enumerator 或其他地方获取。
        // 假设 enumerator 没有直接提供路径的方法,可能需要在 push 时保存 URL 到另一个栈中,
        // 或者通过 g_file_enumerator_get_container(enumerator) 获取 GFile 再转 URL (如果 API 支持)。
        // 这里假设我们有一个辅助方法或变量获取当前目录 URL,记为 currentDirUrl。
        // 临时方案:如果无法获取当前目录 URL,此逻辑需要回退到 iterate 或修改数据结构。
        // 鉴于 diff 上下文,这里假设 d->uri 在递归中会被更新,或者 buildUrl 能处理上下文。
        // 但根据原代码逻辑,iterate 返回的 gfile 是绝对路径,所以 buildUrl 使用 d->uri 可能是错误的。
        // 正确做法应该是:获取当前 enumerator 的 GFile,获取其路径。
        
        // 修正:由于 GLib API 限制,获取 enumerator 对应的 GFile 可能比较麻烦。
        // 如果必须使用 next_file,建议维护一个 parallel stack 存储 QUrl 或 GFile*。
        // 这里仅演示逻辑修复:
        
        // 伪代码:获取当前目录的 URL
        QUrl currentDirUrl = ...; // 需要从 d->stackEnumerator 或 d->urlStack 获取

        d->nextUrl = d->buildUrl(currentDirUrl, g_file_info_get_name(nextInfo));

        d->dfileInfoNext = DLocalHelper::createFileInfoByUri(d->nextUrl, g_file_info_dup(nextInfo), FILE_DEFAULT_ATTRIBUTES,
                                                             d->enumLinks ? DFileInfo::FileQueryInfoFlags::kTypeNone : DFileInfo::FileQueryInfoFlags::kTypeNoFollowSymlinks);

        g_object_unref(nextInfo);

        // 检查错误(虽然有 nextInfo,但也可能有警告)
        if (gerror) {
            d->setErrorFromGError(gerror);
            // 根据业务逻辑决定是否继续,通常警告可以继续
        }

        if (!d->checkFilter())
            return this->hasNext();

        return true;
    }

    // nextInfo == NULL
    if (gerror) {
        d->setErrorFromGError(gerror);
        // 发生错误,遍历无法继续
        return false; 
    }

    // 没有错误且没有下一个文件,说明当前目录遍历完毕
    // 弹出当前 enumerator,并尝试获取下一个(递归或循环)
    GFileEnumerator *enumeratorPop = d->stackEnumerator.pop();
    g_object_unref(enumeratorPop);
    
    // 如果栈不为空,继续处理下一个目录或文件
    return !d->stackEnumerator.isEmpty() && this->hasNext();
}

总结

  1. buildUrl 改进: 逻辑正确且健壮性有所提高,建议使用 QDir::cleanPathQStringBuilder 进一步优化。
  2. hasNext 改进风险: 当前修改破坏了原有的递归遍历子目录的逻辑(丢失了 pop 和递归调用),并且 URL 构建逻辑可能不正确(使用了根 URI 而非当前目录 URI)。
  3. 建议: 必须在 hasNext 中恢复对子目录遍历结束的处理逻辑。如果坚持使用 g_file_enumerator_next_file,需要维护一个额外的数据结构(如 QStack<QUrl>)来记录当前遍历的目录路径,以便正确构建子文件的 URL。或者,回退到使用 g_file_enumerator_iterate,因为它直接提供了子文件的完整路径,更适合这种递归遍历场景。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: GongHeng2017, liyigang1

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

Copy link
Contributor

@Johnson-zs Johnson-zs left a comment

Choose a reason for hiding this comment

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

提到master

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.

4 participants