Skip to content

fix: 模组翻译数据库构建可能会留下空文件#2615

Merged
LinQingYuu merged 7 commits intodevfrom
fix/comp-db
Mar 19, 2026
Merged

fix: 模组翻译数据库构建可能会留下空文件#2615
LinQingYuu merged 7 commits intodevfrom
fix/comp-db

Conversation

@tangge233
Copy link
Copy Markdown
Contributor

@tangge233 tangge233 commented Mar 17, 2026

一会顺便把模组翻译数据库也更新下 qwq

Close #2529

Summary by Sourcery

确保模组翻译的 SQLite 数据库在解压、校验以及在缓存目录中的创建过程中是安全且原子化的,从而避免遗留空的或无效的数据库文件。

Bug 修复:

  • 在使用前对缓存的 SQLite 文件进行验证,并在文件无效时重新构建,防止重复使用过期或空的模组翻译数据库文件。
  • 通过先构建到一个临时文件中,并仅在构建成功后再将其移动到目标位置,避免留下空的或部分构建完成的模组翻译数据库文件。

增强:

  • 通过在批量插入期间使用显式事务,并简化与压缩相关的代码导入,提高模组翻译数据库构建的性能和可靠性。
Original summary in English

Summary by Sourcery

Ensure the mod translation SQLite database is safely decompressed, validated, and atomically created in the cache directory to avoid leaving behind empty or invalid database files.

Bug Fixes:

  • Prevent stale or empty mod translation database files from being reused by validating the cached SQLite file before use and rebuilding it when invalid.
  • Avoid leaving empty or partially built mod translation database files by building into a temporary file and moving it into place only after a successful build.

Enhancements:

  • Improve mod translation database build performance and reliability by using an explicit transaction during bulk insert and simplifying compression-related code imports.

由 Sourcery 提供的摘要

确保模组翻译的 SQLite 缓存数据库能够被安全地构建和缓存,避免遗留空文件或无效文件。

Bug 修复:

  • 通过在使用前验证缓存的 SQLite 文件,并在其无效时重新构建,防止重复使用过期或空的模组翻译数据库文件。
  • 通过先在临时文件中构建数据库,并仅在构建成功后再将其移动到目标位置,避免留下空的或部分构建的模组翻译数据库文件。

增强功能:

  • 通过将模式(schema)创建和批量插入操作包装在显式事务中,提高模组翻译数据库构建的性能和可靠性。
Original summary in English

Summary by Sourcery

Ensure the mod translation SQLite cache database is built and cached safely to avoid leaving behind empty or invalid files.

Bug Fixes:

  • Prevent reuse of stale or empty mod translation database files by validating the cached SQLite file before use and rebuilding it when invalid.
  • Avoid leaving empty or partially built mod translation database files by constructing the database in a temporary file and only moving it into place after a successful build.

Enhancements:

  • Improve mod translation database build performance and reliability by wrapping schema creation and bulk inserts in an explicit transaction.

@pcl-ce-automation pcl-ce-automation bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 size: M PR 大小评估:中型 labels Mar 17, 2026
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 17, 2026

审阅者指南

确保模组翻译 SQLite 缓存数据库以原子方式构建到一个临时文件中,在重用前完成校验,并在事务中构建,以避免生成空/无效文件并提升性能。

原子化模组翻译数据库初始化的时序图

sequenceDiagram
    participant ModComp
    participant ResourceStream as ResourceStream_mcmod_buf
    participant GZip as GZipStream
    participant Memory as MemoryStream
    participant SHA1 as SHA1Provider
    participant FS as FileSystem
    participant Sqlite as SqliteConnection

    ModComp->>ResourceStream: GetResourceStream(Resources_mcmod_buf)
    ResourceStream-->>ModComp: compressedDbData
    ModComp->>GZip: new GZipStream(compressedDbData, Decompress)
    GZip->>Memory: CopyTo(ms)
    Memory->>SHA1: ComputeHash(ms)
    SHA1-->>ModComp: fileHash
    ModComp->>FS: Combine(PathTemp, Cache, ModData_fileHash_sqlite)
    FS-->>ModComp: dbPath

    ModComp->>FS: Exists(dbPath)
    alt dbPath exists
        ModComp->>ModComp: IsDatabaseValid(dbPath)
        alt database invalid
            ModComp->>FS: Delete(dbPath)
        end
    end

    ModComp->>FS: Exists(dbPath)
    alt dbPath does not exist
        ModComp->>Memory: Seek(0)
        ModComp->>ModComp: Deserialize entries from ms
        ModComp->>FS: CreateDirectory(dbDir)
        ModComp->>FS: Build tempPath = dbPath_tmp
        ModComp->>FS: Delete(tempPath) if exists

        ModComp->>Sqlite: Open(DataSource=tempPath)
        Sqlite->>Sqlite: BeginTransaction()
        Sqlite->>Sqlite: CREATE TABLE ModTranslation
        Sqlite->>Sqlite: CREATE INDEX idx_curseforge
        Sqlite->>Sqlite: CREATE INDEX idx_modrinth
        Sqlite->>Sqlite: CREATE INDEX idx_chinesename

        loop for each entry in entries
            ModComp->>Sqlite: INSERT INTO ModTranslation(entry)
        end

        Sqlite->>Sqlite: Commit transaction
        Sqlite-->>ModComp: Close

        ModComp->>FS: Move(tempPath, dbPath, overwrite=true)
    end

    ModComp-->>ModComp: return connection string for dbPath
Loading

更新后的 ModComp 模组数据库构建类图

classDiagram
    class ModComp {
        +String InitializeModDbAndGetConnectionString()
        -Boolean IsDatabaseValid(String dbPath)
        +SqliteConnection CompDB
        +String CompDBConnectionString
    }

    class SqliteConnection {
        +Open()
        +Close()
        +BeginTransaction()
        +Execute(String sql)
        +ExecuteScalar~T~(String sql)
    }

    class FileSystem {
        +Boolean Exists(String path)
        +Void Delete(String path)
        +Void Move(String source, String dest, Boolean overwrite)
        +Void CreateDirectory(String path)
        +String Combine(String path1, String path2)
    }

    class SHA1Provider {
        +Byte[] ComputeHash(Stream data)
    }

    class GZipStream {
        +Void CopyTo(Stream destination)
    }

    ModComp --> SqliteConnection : uses
    ModComp --> FileSystem : uses
    ModComp --> SHA1Provider : uses
    ModComp --> GZipStream : uses
Loading

文件级更改

Change Details Files
使模组翻译 SQLite 缓存文件的创建具备原子性,并在重用前校验缓存数据库,以避免空文件或陈旧文件。
  • 重构数据库路径构建逻辑,使用稳定的缓存目录以及基于解压后资源流计算出的哈希作为文件名。
  • 为已有的 SQLite 缓存文件添加校验逻辑:当缺少预期的 ModTranslation 表、表为空或文件无效时,删除并重新构建缓存文件。
  • 先在临时 .tmp 文件中构建 SQLite 数据库,仅在成功构建后再移动到目标位置,防止留下空的或部分写入的数据库文件。
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
提升构建模组翻译数据库的性能与可靠性。
  • 将批量插入 ModTranslation 条目包裹在显式事务中,大幅加快数据库创建速度,并确保插入操作的原子性。
  • 通过导入 System.IO.Compression 简化压缩相关代码,直接使用 GZipStream,而不是使用完全限定名。
  • 调整 SQL 执行顺序,使模式(schema)创建在用于批量插入的同一事务中执行。
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

针对关联 Issue 的评估

Issue Objective Addressed Explanation
#2529 修复模组翻译信息获取失败的问题,该问题会在任意模组界面引发错误,原因很可能是模组翻译 SQLite 数据库无效或为空。

提示与命令

与 Sourcery 交互

  • 触发新的代码审阅: 在 Pull Request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论即可继续讨论。
  • 从审阅评论生成 GitHub Issue: 回复 Sourcery 的审阅评论,要求其从该评论创建 Issue。你也可以直接回复审阅评论并写上 @sourcery-ai issue 来从中创建 Issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题的任意位置写上 @sourcery-ai 即可随时生成标题。你也可以在 Pull Request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 Pull Request 总结: 在 Pull Request 正文的任意位置写上 @sourcery-ai summary,即可在你希望的位置生成 PR 总结。你也可以在 Pull Request 中评论 @sourcery-ai summary 来(重新)生成总结。
  • 生成审阅者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不希望再看到它们,这会很有用。
  • 清除所有 Sourcery 审阅: 在 Pull Request 中评论 @sourcery-ai dismiss,即可清除所有现有的 Sourcery 审阅。这在你希望从一次全新的审阅开始时特别有用——别忘了再评论 @sourcery-ai review 来触发新的审阅!

自定义你的使用体验

访问你的 控制面板 以:

  • 启用或禁用诸如 Sourcery 自动生成的 Pull Request 总结、审阅者指南等审阅功能。
  • 更改审阅语言。
  • 添加、移除或编辑自定义审阅说明。
  • 调整其他审阅相关设置。

获取帮助

Original review guide in English

Reviewer's Guide

Ensures the mod translation SQLite cache database is built atomically into a temporary file, validated before reuse, and constructed within a transaction to avoid empty/invalid files and improve performance.

Sequence diagram for atomic mod translation database initialization

sequenceDiagram
    participant ModComp
    participant ResourceStream as ResourceStream_mcmod_buf
    participant GZip as GZipStream
    participant Memory as MemoryStream
    participant SHA1 as SHA1Provider
    participant FS as FileSystem
    participant Sqlite as SqliteConnection

    ModComp->>ResourceStream: GetResourceStream(Resources_mcmod_buf)
    ResourceStream-->>ModComp: compressedDbData
    ModComp->>GZip: new GZipStream(compressedDbData, Decompress)
    GZip->>Memory: CopyTo(ms)
    Memory->>SHA1: ComputeHash(ms)
    SHA1-->>ModComp: fileHash
    ModComp->>FS: Combine(PathTemp, Cache, ModData_fileHash_sqlite)
    FS-->>ModComp: dbPath

    ModComp->>FS: Exists(dbPath)
    alt dbPath exists
        ModComp->>ModComp: IsDatabaseValid(dbPath)
        alt database invalid
            ModComp->>FS: Delete(dbPath)
        end
    end

    ModComp->>FS: Exists(dbPath)
    alt dbPath does not exist
        ModComp->>Memory: Seek(0)
        ModComp->>ModComp: Deserialize entries from ms
        ModComp->>FS: CreateDirectory(dbDir)
        ModComp->>FS: Build tempPath = dbPath_tmp
        ModComp->>FS: Delete(tempPath) if exists

        ModComp->>Sqlite: Open(DataSource=tempPath)
        Sqlite->>Sqlite: BeginTransaction()
        Sqlite->>Sqlite: CREATE TABLE ModTranslation
        Sqlite->>Sqlite: CREATE INDEX idx_curseforge
        Sqlite->>Sqlite: CREATE INDEX idx_modrinth
        Sqlite->>Sqlite: CREATE INDEX idx_chinesename

        loop for each entry in entries
            ModComp->>Sqlite: INSERT INTO ModTranslation(entry)
        end

        Sqlite->>Sqlite: Commit transaction
        Sqlite-->>ModComp: Close

        ModComp->>FS: Move(tempPath, dbPath, overwrite=true)
    end

    ModComp-->>ModComp: return connection string for dbPath
Loading

Class diagram for updated ModComp mod database construction

classDiagram
    class ModComp {
        +String InitializeModDbAndGetConnectionString()
        -Boolean IsDatabaseValid(String dbPath)
        +SqliteConnection CompDB
        +String CompDBConnectionString
    }

    class SqliteConnection {
        +Open()
        +Close()
        +BeginTransaction()
        +Execute(String sql)
        +ExecuteScalar~T~(String sql)
    }

    class FileSystem {
        +Boolean Exists(String path)
        +Void Delete(String path)
        +Void Move(String source, String dest, Boolean overwrite)
        +Void CreateDirectory(String path)
        +String Combine(String path1, String path2)
    }

    class SHA1Provider {
        +Byte[] ComputeHash(Stream data)
    }

    class GZipStream {
        +Void CopyTo(Stream destination)
    }

    ModComp --> SqliteConnection : uses
    ModComp --> FileSystem : uses
    ModComp --> SHA1Provider : uses
    ModComp --> GZipStream : uses
Loading

File-Level Changes

Change Details Files
Make mod translation SQLite cache file creation atomic and validate cached DB before reuse to avoid empty or stale files.
  • Refactor DB path construction to use a stable cache directory and hash-based filename from the decompressed resource stream.
  • Add validation of existing SQLite cache file, deleting and rebuilding it if the expected ModTranslation table is missing or empty, or if the file is otherwise invalid.
  • Build the SQLite database in a temporary .tmp file and move it into place only after successful construction to prevent leaving empty or partially written DB files.
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
Improve performance and reliability of building the mod translation database.
  • Wrap bulk insertion of ModTranslation entries in an explicit transaction to significantly speed up DB creation and ensure atomicity of insert operations.
  • Simplify compression usage by importing System.IO.Compression and using GZipStream directly instead of fully-qualified names.
  • Adjust SQL execution so schema creation occurs inside the transaction used for bulk insertion.
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

Assessment against linked issues

Issue Objective Addressed Explanation
#2529 Fix the failure to retrieve mod translation information that causes errors on any mod interface, likely due to an invalid or empty mod translation SQLite database.

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
Copy Markdown

@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 - 我在这里给出了一些高层面的反馈:

  • tempPath 删除后再移动到 dbPath 的这个顺序操作并不是完全原子化的,在并发初始化的情况下,可能会有一个短暂的时间窗口使得 dbPath 不存在,或者发生冲突;可以考虑使用 File.Replace 或者更原子的交换策略,来避免多个初始化器之间的竞争条件。
  • IsDatabaseValid 中的宽泛 Catch 会悄无声息地吞掉所有异常;建议至少记录失败路径(包括数据库路径和异常类型),这样在数据库被反复视为无效并被重建时,更容易排查问题。
给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- The delete-then-move sequence for `tempPath``dbPath` is not fully atomic and may cause a brief window where `dbPath` doesn't exist or collide under concurrent initialization; consider using `File.Replace` or a more atomic swap strategy to avoid race conditions between multiple initializers.
- The broad `Catch` in `IsDatabaseValid` swallows all exceptions silently; consider at least logging the failure path (with the db path and exception type) so it’s easier to diagnose cases where the DB is repeatedly being treated as invalid and rebuilt.

Sourcery 对开源项目是免费的——如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的审查。
Original comment in English

Hey - I've left some high level feedback:

  • The delete-then-move sequence for tempPathdbPath is not fully atomic and may cause a brief window where dbPath doesn't exist or collide under concurrent initialization; consider using File.Replace or a more atomic swap strategy to avoid race conditions between multiple initializers.
  • The broad Catch in IsDatabaseValid swallows all exceptions silently; consider at least logging the failure path (with the db path and exception type) so it’s easier to diagnose cases where the DB is repeatedly being treated as invalid and rebuilt.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The delete-then-move sequence for `tempPath``dbPath` is not fully atomic and may cause a brief window where `dbPath` doesn't exist or collide under concurrent initialization; consider using `File.Replace` or a more atomic swap strategy to avoid race conditions between multiple initializers.
- The broad `Catch` in `IsDatabaseValid` swallows all exceptions silently; consider at least logging the failure path (with the db path and exception type) so it’s easier to diagnose cases where the DB is repeatedly being treated as invalid and rebuilt.

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.

@tangge233
Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

Copy link
Copy Markdown

@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 - 我给出了一些整体性的反馈:

  • IsDatabaseValid 中,所有异常都被静默吞掉了;建议至少记录一下失败原因,这样在实际使用中诊断损坏/无效的数据库文件会更容易。
  • 在验证数据库时,你可能也需要检查一下模式结构(例如预期的列或某个 pragma),而不仅仅是检查表是否存在以及数据是否非空,以避免接受在结构上不兼容的数据库文件。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- In `IsDatabaseValid`, all exceptions are swallowed silently; consider at least logging the failure reason so that diagnosing corrupt/invalid DB files is easier in the field.
- When validating the database, you might also want to check for schema shape (e.g., expected columns or a pragma) rather than only table existence and non-empty data, to avoid accepting structurally incompatible DB files.

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,请考虑分享给更多人 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈来改进后续的评审。
Original comment in English

Hey - I've left some high level feedback:

  • In IsDatabaseValid, all exceptions are swallowed silently; consider at least logging the failure reason so that diagnosing corrupt/invalid DB files is easier in the field.
  • When validating the database, you might also want to check for schema shape (e.g., expected columns or a pragma) rather than only table existence and non-empty data, to avoid accepting structurally incompatible DB files.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `IsDatabaseValid`, all exceptions are swallowed silently; consider at least logging the failure reason so that diagnosing corrupt/invalid DB files is easier in the field.
- When validating the database, you might also want to check for schema shape (e.g., expected columns or a pragma) rather than only table existence and non-empty data, to avoid accepting structurally incompatible DB files.

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.

LinQingYuu
LinQingYuu previously approved these changes Mar 18, 2026
Copy link
Copy Markdown
Member

@LinQingYuu LinQingYuu left a comment

Choose a reason for hiding this comment

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

LGTM

@pcl-ce-automation pcl-ce-automation bot added size: L PR 大小评估:大型 size: M PR 大小评估:中型 and removed size: M PR 大小评估:中型 size: L PR 大小评估:大型 labels Mar 19, 2026
@pcl-ce-automation pcl-ce-automation bot added size: L PR 大小评估:大型 and removed size: M PR 大小评估:中型 labels Mar 19, 2026
@pcl-ce-automation pcl-ce-automation bot added 🕑 等待合并 已处理完毕,正在等待代码合并入主分支 and removed 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 labels Mar 19, 2026
@LinQingYuu LinQingYuu merged commit 8bd7cc9 into dev Mar 19, 2026
3 checks passed
@LinQingYuu LinQingYuu deleted the fix/comp-db branch March 19, 2026 06:43
@pcl-ce-automation pcl-ce-automation bot added 👌 完成 相关问题已修复或功能已实现,计划在下次版本更新时正式上线 and removed 🕑 等待合并 已处理完毕,正在等待代码合并入主分支 labels Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: L PR 大小评估:大型 👌 完成 相关问题已修复或功能已实现,计划在下次版本更新时正式上线

Projects

None yet

Development

Successfully merging this pull request may close these issues.

获取模组翻译信息失败

3 participants