Skip to content

fix: prevent file recreation at original path after deletion - #680

Open
pengfeixx wants to merge 1 commit into
linuxdeepin:masterfrom
pengfeixx:agent/pms-bug-bot/c3edf65ec0cf
Open

pengfeixx wants to merge 1 commit into
linuxdeepin:masterfrom
pengfeixx:agent/pms-bug-bot/c3edf65ec0cf

Conversation

@pengfeixx

@pengfeixx pengfeixx commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

fix: prevent file recreation at original path after deletion

  1. Root cause: closeTab() saved to original file path without checking
    if the file still existed, recreating a deleted file at its original
    location and conflicting with trash entries showing "locked" state
  2. Fix: add fileInfo.exists() check in closeTab() save branch to call
    saveAsFile() when file is deleted; guard dialog.setDirectory() with
    fileInfo.exists() in saveAsFileToDisk(), saveBlankFileToDisk(), and
    saveAsOtherTabFile() to use default save path when file is deleted
  3. Impact: only affects files deleted externally while open in editor,
    normal save behavior unchanged for existing files

Log: fix file showing locked in trash after editor save

Influence:

  1. Test editing a file, deleting it externally, then closing editor
  2. Test saving deleted file triggers Save As dialog with default path
  3. Test Save As / Save Blank / Save As Other Tab with deleted source file
  4. Verify normal save still works for existing files

fix: 修复源文件删除后编辑器保存导致回收站显示锁定

  1. 根因:closeTab() 保存时未检查文件是否仍存在,直接保存到原始路径
    导致已删除文件在原路径重建,与回收站条目冲突显示"锁定"状态
  2. 方案:closeTab() 保存分支增加 fileInfo.exists() 检查,文件已删
    除时调用 saveAsFile();saveAsFileToDisk()、saveBlankFileToDisk()
    和 saveAsOtherTabFile() 在文件不存在时不覆盖默认保存路径,避免
    使用原始文件目录
  3. 影响:仅影响被外部删除后仍打开的文件,正常保存行为不受影响

Log: 修复源文件删除后编辑器保存导致回收站显示锁定

Influence:

  1. 测试编辑文件后外部删除,再关闭编辑器保存
  2. 测试保存已删除文件时弹出另存为对话框并使用默认路径
  3. 测试另存为/空白文件另存/其他标签页另存时源文件已删除的场景
  4. 验证正常文件保存功能不受影响

PMS: BUG-366573

Summary by Sourcery

Preserve deleted-file state by routing saves to Save As instead of restoring files at their original paths.

Bug Fixes:

  • Prevent deleted files from being recreated at their original paths when closing modified editor tabs.
  • Use the default save location when saving or saving-as files whose source has been deleted.

1. Root cause: closeTab() saved to original file path without checking
   if the file still existed, recreating a deleted file at its original
   location and conflicting with trash entries showing "locked" state
2. Fix: add fileInfo.exists() check in closeTab() save branch to call
   saveAsFile() when file is deleted; guard dialog.setDirectory() with
   fileInfo.exists() in saveAsFileToDisk(), saveBlankFileToDisk(), and
   saveAsOtherTabFile() to use default save path when file is deleted
3. Impact: only affects files deleted externally while open in editor,
   normal save behavior unchanged for existing files

Log: fix file showing locked in trash after editor save

Influence:
1. Test editing a file, deleting it externally, then closing editor
2. Test saving deleted file triggers Save As dialog with default path
3. Test Save As / Save Blank / Save As Other Tab with deleted source file
4. Verify normal save still works for existing files

fix: 修复源文件删除后编辑器保存导致回收站显示锁定

1. 根因:closeTab() 保存时未检查文件是否仍存在,直接保存到原始路径
   导致已删除文件在原路径重建,与回收站条目冲突显示"锁定"状态
2. 方案:closeTab() 保存分支增加 fileInfo.exists() 检查,文件已删
   除时调用 saveAsFile();saveAsFileToDisk()、saveBlankFileToDisk()
   和 saveAsOtherTabFile() 在文件不存在时不覆盖默认保存路径,避免
   使用原始文件目录
3. 影响:仅影响被外部删除后仍打开的文件,正常保存行为不受影响

Log: 修复源文件删除后编辑器保存导致回收站显示锁定

Influence:
1. 测试编辑文件后外部删除,再关闭编辑器保存
2. 测试保存已删除文件时弹出另存为对话框并使用默认路径
3. 测试另存为/空白文件另存/其他标签页另存时源文件已删除的场景
4. 验证正常文件保存功能不受影响

PMS: BUG-366573
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: pengfeixx

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

@sourcery-ai

sourcery-ai Bot commented Oct 21, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates close-time saving and related save-dialog setup to detect externally deleted files, use Save As with the default save location instead of recreating the original path, and preserve existing behavior for files that still exist.

Sequence diagram for saving an externally deleted file on tab close

sequenceDiagram
    participant User
    participant Window
    participant QFileInfo
    participant EditWrapper
    participant SaveDialog

    User->>Window: closeTab(filePath)
    Window->>QFileInfo: exists()
    alt file no longer exists
        Window->>Window: saveAsFile()
        Window->>SaveDialog: setDirectory(default save path)
        User->>SaveDialog: choose destination
        SaveDialog-->>Window: selected path
    else file still exists
        Window->>EditWrapper: saveFile()
        EditWrapper-->>Window: save result
    end
Loading

File-Level Changes

Change Details Files
Prevent closing an edited tab from recreating a file that was externally deleted.
  • Check file existence before the normal close-time save.
  • Route deleted files through Save As and keep the tab open if Save As is cancelled or fails.
src/widgets/window.cpp
Avoid preselecting a deleted file’s original directory in save dialogs.
  • Only set the original directory when the source file still exists.
  • Apply the fallback behavior to Save As, Save Blank, and Save As Other Tab while preserving the current filename selection.
src/widgets/window.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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/widgets/window.cpp" line_range="1568" />
<code_context>
     } else {
-        dialog.setDirectory(fileInfo.dir());
+        // 源文件已被删除(如移入回收站)时,使用默认保存路径而非原始文件目录
+        if (fileInfo.exists()) {
+            dialog.setDirectory(fileInfo.dir());
+        }
</code_context>
<issue_to_address>
**Save As recreates deleted file**

When the source is deleted and the configured Save As directory still points to its existing parent, `saveAsFileToDisk()` keeps the directory selected by `getCurrentOpenFilePath()`, and `selectFile()` preselects the original filename. Accepting the defaults therefore saves to the deleted file’s original path and recreates it.

When the source no longer exists, use the default Save As directory and avoid preselecting the deleted file’s original path.

Also at `src/widgets/window.cpp:1569-1570`.
</issue_to_address>

### Comment 2
<location path="src/widgets/window.cpp" line_range="1107" />
<code_context>
                     qDebug() << "File is not a backup file, saving normally";
-                    if (wrapper->saveFile()) {
+                    // 源文件可能已被外部删除(如移入回收站),需先检查文件是否存在
+                    if (!fileInfo.exists()) {
+                        qDebug() << "File no longer exists, using Save As instead";
+                        if (!saveAsFile()) {
</code_context>
<issue_to_address>
**Preview save recreates deleted file**

When the source of an edited invalid-character preview is deleted and the user chooses “Save Anyway”, the preview branch in `closeTab()` calls `forceSaveInvalidCharFile()` before the existence check, so it writes directly to the original path and recreates the deleted file.

Route “Save Anyway” through a path that checks for a missing source and does not write to its original path.
</issue_to_address>

### Comment 3
<location path="src/widgets/window.cpp" line_range="1109-1112" />
<code_context>
+                    // 源文件可能已被外部删除(如移入回收站),需先检查文件是否存在
+                    if (!fileInfo.exists()) {
+                        qDebug() << "File no longer exists, using Save As instead";
+                        if (!saveAsFile()) {
+                            qWarning() << "Save as file cancelled or failed, keep tab open";
+                            return false;
+                        }
+                    } else if (wrapper->saveFile()) {
                         qDebug() << "File saved, removing wrapper and closing tab";
</code_context>
<issue_to_address>
**Saved tab remains open**

When a deleted file is being closed and Save As succeeds, `closeTab()` returns success without removing the wrapper or closing the tab, so callers such as `closeAllFiles()` count it as closed while it remains visible.

After Save As succeeds, remove the wrapper and close the requested tab before reporting success.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/widgets/window.cpp
} else {
dialog.setDirectory(fileInfo.dir());
// 源文件已被删除(如移入回收站)时,使用默认保存路径而非原始文件目录
if (fileInfo.exists()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Save As recreates deleted file

When the source is deleted and the configured Save As directory still points to its existing parent, saveAsFileToDisk() keeps the directory selected by getCurrentOpenFilePath(), and selectFile() preselects the original filename. Accepting the defaults therefore saves to the deleted file’s original path and recreates it.

When the source no longer exists, use the default Save As directory and avoid preselecting the deleted file’s original path.

Also at src/widgets/window.cpp:1569-1570.

Prompt for AI agents
In `src/widgets/window.cpp` at line 1568:

**Save As recreates deleted file**

When the source is deleted and the configured Save As directory still points to its existing parent, `saveAsFileToDisk()` keeps the directory selected by `getCurrentOpenFilePath()`, and `selectFile()` preselects the original filename. Accepting the defaults therefore saves to the deleted file’s original path and recreates it.

When the source no longer exists, use the default Save As directory and avoid preselecting the deleted file’s original path.

Also at `src/widgets/window.cpp:1569-1570`.

Comment thread src/widgets/window.cpp
qDebug() << "File is not a backup file, saving normally";
if (wrapper->saveFile()) {
// 源文件可能已被外部删除(如移入回收站),需先检查文件是否存在
if (!fileInfo.exists()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Preview save recreates deleted file

When the source of an edited invalid-character preview is deleted and the user chooses “Save Anyway”, the preview branch in closeTab() calls forceSaveInvalidCharFile() before the existence check, so it writes directly to the original path and recreates the deleted file.

Route “Save Anyway” through a path that checks for a missing source and does not write to its original path.

Prompt for AI agents
In `src/widgets/window.cpp` at line 1107:

**Preview save recreates deleted file**

When the source of an edited invalid-character preview is deleted and the user chooses “Save Anyway”, the preview branch in `closeTab()` calls `forceSaveInvalidCharFile()` before the existence check, so it writes directly to the original path and recreates the deleted file.

Route “Save Anyway” through a path that checks for a missing source and does not write to its original path.

Comment thread src/widgets/window.cpp
Comment on lines +1109 to +1112
if (!saveAsFile()) {
qWarning() << "Save as file cancelled or failed, keep tab open";
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Saved tab remains open

When a deleted file is being closed and Save As succeeds, closeTab() returns success without removing the wrapper or closing the tab, so callers such as closeAllFiles() count it as closed while it remains visible.

After Save As succeeds, remove the wrapper and close the requested tab before reporting success.

Prompt for AI agents
In `src/widgets/window.cpp` at lines 1109-1112:

**Saved tab remains open**

When a deleted file is being closed and Save As succeeds, `closeTab()` returns success without removing the wrapper or closing the tab, so callers such as `closeAllFiles()` count it as closed while it remains visible.

After Save As succeeds, remove the wrapper and close the requested tab before reporting success.

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 100 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 本次提交修复了文件被外部删除(如移入回收站)后关闭标签页时仍尝试在原路径保存导致文件被重新创建的问题。变更在 closeTab 中增加了源文件存在性检查,并在 saveAsFileToDisk/saveBlankFileToDisk/saveAsOtherTabFile 三处对源文件已删除的情况跳过设置原目录、改用默认保存路径。逻辑正确且与现有代码风格一致,注释完整,无安全漏洞。启发式扫描、OCR 审查、AI 四维度分析均未发现问题。

🔍 详细分析

1. 语法逻辑 ✅

评价: 语法正确,逻辑清晰 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: []


2. 代码质量 ✅

评价: 代码结构清晰,注释完整 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['可选优化:saveAsFileToDisk/saveBlankFileToDisk/saveAsOtherTabFile 三处对 fileInfo.exists() 的守卫逻辑相同,后续可考虑抽取为公共辅助方法以进一步减少重复,但当前不构成扣分项。']


3. 代码性能 ✅

评价: 性能良好,资源使用合理 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: []


4. 代码安全 🔒

评价: 存在0个安全漏洞 ✅ 通过

🔐 发现 0 个安全漏洞

安全漏洞详情:
✅ 未发现安全漏洞

建议: []


💡 改进建议代码示例

// 当前实现已正确修复问题,无需额外修改。
// 如需进一步减少重复,可抽取公共守卫方法:
// void setDialogDirectoryIfFileExists(QFileDialog &dialog, const QFileInfo &fileInfo) {
//     if (fileInfo.exists()) {
//         dialog.setDirectory(fileInfo.dir());
//     }
// }

本报告由 AI 代码审查工具自动生成

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.

2 participants