Skip to content

[WIP] refactor/java provider and add java catalog cache#110

Open
BegoniaHe wants to merge 13 commits intoHydroRoll-Team:mainfrom
BegoniaHe:refactor/java-provider
Open

[WIP] refactor/java provider and add java catalog cache#110
BegoniaHe wants to merge 13 commits intoHydroRoll-Team:mainfrom
BegoniaHe:refactor/java-provider

Conversation

@BegoniaHe
Copy link
Copy Markdown
Contributor

@BegoniaHe BegoniaHe commented Feb 27, 2026

描述

对 Java 模块(src-tauri/src/core/java/)进行系统性重构,将单体文件拆分为职责清晰的子模块,同时增强下载队列的线程安全性、错误处理能力和 Adoptium Provider 的并发请求能力。本 PR 不引入新的用户可见功能,专注于代码质量与可维护性提升。

更改类型

  • Bug 修复(修复问题的非破坏性更改)
  • 新功能(添加功能的非破坏性更改)
  • 破坏性更改(会导致现有功能无法正常工作的修复或功能)
  • 文档更新
  • UI/UX 改进
  • 性能优化
  • 代码重构(无功能性更改)
  • 配置更改
  • 测试添加或更新

LLM 生成代码声明

  • 此 PR 包含 LLM 生成的代码,我提供质量担保

相关 Issue

关闭 #
相关 #


更改内容

后端 (Rust)

1. Java 检测模块拆分(detection.rsdetection/ 子模块)

将原来的单体 detection.rs(311 行)拆分为按平台职责分离的子模块:

文件 职责
detection/common.rs 跨平台:PATHwhich/where)与 JAVA_HOME 检测,带 2 秒超时保护防止挂起
detection/linux.rs Linux 特定路径(/usr/lib/jvm/opt/java 等)及 sdkman/mise 工具链
detection/macos.rs macOS 特定路径,含 Homebrew ARM(/opt/homebrew/Cellar/openjdk)支持,sdkman/mise 工具链
detection/windows.rs Windows 特定注册表/路径检测
detection/unix.rs Linux/macOS 共用的 sdkman、mise 检测逻辑
detection/mod.rs 模块入口,统一聚合各平台候选 Java 路径

2. 新增 Java Catalog 缓存(cache.rs

  • 新增 cache.rs,缓存 Adoptium Java Catalog 24 小时,避免每次启动均发起网络请求
  • 使用"写入临时文件 + rename"的原子写入模式,防止缓存文件在写入中途崩溃时损坏
  • 提供 load_cached_catalog_result()save_catalog_cache()clear_catalog_cache() 公开 API

3. 安装逻辑提取(install.rs

  • 将 Java 安装逻辑从 mod.rs 提取至独立的 install.rs(179 行),降低 mod.rs 的复杂度

4. 下载队列线程安全强化(downloader.rs

  • 引入全局 MutexDOWNLOAD_QUEUE_FILE_LOCK)保护对 download_queue.json 的并发读写
  • 新增高层 API:
    • DownloadQueue::update(app_handle, mutator) — 持锁、加载、修改、原子写回的事务操作
    • DownloadQueue::list_pending() — 线程安全读取待下载列表
    • DownloadQueue::add_pending() / remove_pending() — 封装单次更新操作
  • 下载队列文件写入同样改为原子写入(.json.tmprename
  • queue_path() 改为返回 Result,消除 unwrap() panic 风险

5. 持久化原子写入(persistence.rs

  • save_java_config() 改为通过临时文件原子写入,防止写入中断导致配置损坏
  • 新增 load_java_config_result() 返回 Result<JavaConfig, JavaError>,提供更精确的错误传播路径

6. 错误类型增强(error.rs

  • 新增 ResumeJavaDownloadFailureReason 枚举,将下载失败原因分类为:
    NetworkCancelledChecksumMismatchExtractionFailedVerificationFailedFilesystemInvalidArchiveUnknown
  • 实现 from_error_message(error: &str) 从原始错误字符串中自动分类原因
  • 通过 ts-rs 导出类型到 packages/ui-new/src/types/bindings/java/index.ts
  • 新增表驱动单元测试覆盖 8 种失败分类场景

7. Adoptium Provider 并发优化(providers/adoptium.rs

  • Catalog 拉取使用信号量限制最大并发请求数(默认 6 个),避免对 Adoptium API 的瞬时大量请求
  • 添加失败重试逻辑(生产环境 3 次,测试环境 2 次)及指数退避

8. ZIP 提取改进(utils/zip.rs

  • 改进归档文件提取的错误处理与健壮性

9. Cargo.toml 清理

  • 移除未使用的 section,简化依赖声明

前端 (Svelte)

  • 无直接前端变更(Rust 类型绑定通过 ts-rs 自动生成到 packages/ui-new

配置

  • 无配置文件变更

测试

测试环境

  • 操作系统:Linux (Fedora 43)、macOS 26
  • DropOut 版本:refactor/java-provider

测试用例

  • 已在 Windows 上测试
  • 已在 macOS 上测试
  • 已在 Linux 上测试
  • 已测试原版 Minecraft
  • 已测试 Fabric
  • 已测试 Forge
  • 已测试游戏启动
  • 已测试登录流程
  • 已测试 Java 检测/下载

测试步骤

  1. 运行 cargo test 验证 error.rs 中的 classify_resume_failure_reason_table_driven 单元测试通过
  2. 运行 cargo tauri dev 启动开发模式,进入设置页面验证 Java 自动检测功能正常
  3. 触发 Java 下载流程,确认下载队列文件(download_queue.json)被正确原子写入
  4. 检验 java_catalog_cache.json 在首次请求后被缓存,24 小时内复用缓存(可临时将超时改为 5 秒验证)
  5. 在 Java 下载过程中模拟中断,确认下载队列不会损坏

检查清单

代码质量

  • 我的代码遵循项目的代码风格指南
  • 我已对自己的代码进行了自审
  • 我已对难以理解的区域添加了注释
  • 我的更改没有产生新的警告或错误

测试验证

  • 我已在本地测试了我的更改
  • 我已添加测试来证明我的修复有效或功能正常工作(error.rs 表驱动单元测试)
  • 新的和现有的单元测试在本地通过
  • 我至少在一个目标平台上进行了测试(Linux, macOS)

文档更新

  • 我已相应地更新了文档(.github/copilot-instructions.md
  • 如有需要,我已更新 README

Summary by Sourcery

Refactor the Java backend module to improve provider abstraction, caching, download resilience, and error reporting without changing user-visible behavior.

Enhancements:

  • Split Java detection into platform-specific submodules with safer PATH/JAVA_HOME probing and SDKMAN/mise integrations.
  • Introduce a dedicated Java catalog cache with atomic file writes and Result-based error handling, and use it from the Adoptium provider.
  • Refine the Java provider abstraction with generic fetch helpers and ImageType typing, and improve validation to read version output from stdout when needed.
  • Harden download queue persistence via a global file lock, atomic writes, and helper methods for listing/updating pending Java downloads.
  • Extract Java installation logic into a separate module with clearer responsibilities and stricter archive layout validation, plus safer ZIP/TAR extraction paths to prevent path traversal.
  • Expose structured results and enums for resuming Java downloads, enabling the UI to classify and display failure reasons consistently.
  • Add retry with exponential backoff and bounded concurrency for Adoptium catalog requests to reduce API load spikes and handle transient failures more robustly.

Documentation:

  • Update Copilot contributor instructions to reflect the new React primary UI, workspace layout, Java workflows, and tooling/versioning conventions.

Tests:

  • Add unit tests for Java download failure classification and Adoptium catalog retry logic using a local mock HTTP server.

Copilot AI review requested due to automatic review settings February 27, 2026 01:29
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 27, 2026

@BegoniaHe is attempting to deploy a commit to the retrofor Team on Vercel.

A member of the Team first needs to authorize it.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Feb 27, 2026

Reviewer's Guide

Refactors the Rust Java core module into smaller responsibilities, adds a durable cached catalog layer and atomic persistence, hardens the download queue and ZIP extraction, and improves the Adoptium provider with bounded concurrency, retries, and richer error reporting/typing exported to the React frontend.

Class diagram for refactored Java core types and download resume result

classDiagram
    class JavaProvider {
      <<trait>>
      +fetch_catalog(app_handle: AppHandle, force_refresh: bool) Result~JavaCatalog, JavaError~
      +fetch_release(major_version: u32, image_type: ImageType) Result~JavaDownloadInfo, JavaError~
      +available_versions() Result~Vec~u32~, JavaError~
      +install_prefix() &str
      +id() &str
    }

    class AdoptiumProvider {
      +new() AdoptiumProvider
      +fetch_catalog(app_handle: AppHandle, force_refresh: bool) Result~JavaCatalog, JavaError~
      +fetch_release(major_version: u32, image_type: ImageType) Result~JavaDownloadInfo, JavaError~
      +available_versions() Result~Vec~u32~, JavaError~
      +install_prefix() &str
      +id() &str
    }

    class ImageType {
      <<enum>>
      Jre
      Jdk
      +parse(value: &str) Option~ImageType~
      +to_string() String
    }

    class JavaInstallation {
      +path: String
      +version: String
      +architecture: String
      +is_64bit: bool
    }

    class JavaReleaseInfo {
      +major_version: u32
      +image_type: ImageType
      +version: String
      +release_name: String
      +release_date: Option~String~
      +file_size: u64
      +checksum: Option~String~
      +download_url: String
      +is_lts: bool
      +is_available: bool
      +architecture: String
    }

    class JavaDownloadInfo {
      +major_version: u32
      +version: String
      +download_url: String
      +file_name: String
      +file_size: u64
      +checksum: Option~String~
      +image_type: ImageType
    }

    class ResumeJavaDownloadFailureReason {
      <<enum>>
      Network
      Cancelled
      ChecksumMismatch
      ExtractionFailed
      VerificationFailed
      Filesystem
      InvalidArchive
      Unknown
      +from_error_message(error: &str) ResumeJavaDownloadFailureReason
    }

    class ResumeJavaDownloadFailure {
      +major_version: u32
      +image_type: String
      +install_path: String
      +reason: ResumeJavaDownloadFailureReason
      +error: String
    }

    class ResumeJavaDownloadsResult {
      +successful_installations: Vec~JavaInstallation~
      +failed_downloads: Vec~ResumeJavaDownloadFailure~
      +total_pending: usize
    }

    class PendingJavaDownload {
      +major_version: u32
      +image_type: String
      +download_url: String
      +file_name: String
      +file_size: u64
      +checksum: Option~String~
      +install_path: String
      +created_at: u64
    }

    class DownloadQueue {
      +pending_downloads: Vec~PendingJavaDownload~
      +add(download: PendingJavaDownload) void
      +remove(major_version: u32, image_type: &str) void
      +update(app_handle: AppHandle, mutator: F) Result~T, String~
      +list_pending(app_handle: AppHandle) Vec~PendingJavaDownload~
      +add_pending(app_handle: AppHandle, download: PendingJavaDownload) Result~(), String~
      +remove_pending(app_handle: AppHandle, major_version: u32, image_type: &str) Result~(), String~
      +load(app_handle: AppHandle) DownloadQueue
      +save(app_handle: AppHandle) Result~(), String~
    }

    class JavaCatalog {
      +cached_at: u64
      +releases: Vec~JavaReleaseInfo~
    }

    class JavaError {
      <<enum>>
      NetworkError(String)
      SerializationError(String)
      InvalidConfig(String)
      Other(String)
    }

    class InstallModule {
      <<module>>
      +download_and_install_java_with_provider(provider: JavaProvider, app_handle: AppHandle, major_version: u32, image_type: ImageType, custom_path: Option~PathBuf~) Result~JavaInstallation, String~
    }

    class CacheModule {
      <<module>>
      +load_cached_catalog_result(app_handle: AppHandle) Result~Option~JavaCatalog~, JavaError~
      +load_cached_catalog(app_handle: AppHandle) Option~JavaCatalog~
      +save_catalog_cache(app_handle: AppHandle, catalog: &JavaCatalog) Result~(), JavaError~
      +clear_catalog_cache(app_handle: &AppHandle) Result~(), JavaError~
    }

    JavaProvider <|.. AdoptiumProvider
    AdoptiumProvider --> JavaCatalog
    AdoptiumProvider --> JavaReleaseInfo
    AdoptiumProvider --> JavaDownloadInfo
    JavaReleaseInfo --> ImageType
    JavaDownloadInfo --> ImageType
    ResumeJavaDownloadsResult --> JavaInstallation
    ResumeJavaDownloadsResult --> ResumeJavaDownloadFailure
    ResumeJavaDownloadFailure --> ResumeJavaDownloadFailureReason
    DownloadQueue --> PendingJavaDownload
    JavaCatalog --> JavaReleaseInfo
    JavaError <.. CacheModule
    JavaError <.. AdoptiumProvider
    JavaProvider <.. InstallModule
    JavaInstallation <.. InstallModule
Loading

File-Level Changes

Change Details Files
Refactor Java core module into submodules and provider-agnostic helpers while updating Tauri commands to use them.
  • Extract provider-agnostic helpers fetch_java_catalog_with, fetch_java_release_with, and fetch_available_versions_with from mod.rs so they work with any JavaProvider implementation.
  • Move download-and-install logic into new install.rs with download_and_install_java_with_provider, including Java home resolution and top-level-dir validation.
  • Adjust Tauri commands in main.rs to construct AdoptiumProvider instances and call the new generic helper functions and install module APIs instead of the old monolithic functions.
src-tauri/src/core/java/mod.rs
src-tauri/src/core/java/install.rs
src-tauri/src/main.rs
Split Java detection into platform-specific modules and add timeout-safe PATH/JAVA_HOME detection.
  • Introduce detection/common.rs with a 2-second timeout wrapper around which/where and a JAVA_HOME candidate helper.
  • Add detection/linux.rs, detection/macos.rs, and detection/windows.rs to scan typical OS-specific Java locations, plus detection/unix.rs shared helpers for sdkman/mise.
  • Wire a new detection/mod.rs as the public entry point that aggregates PATH, OS-specific, and JAVA_HOME candidates; remove the old monolithic detection.rs.
src-tauri/src/core/java/detection/common.rs
src-tauri/src/core/java/detection/linux.rs
src-tauri/src/core/java/detection/macos.rs
src-tauri/src/core/java/detection/windows.rs
src-tauri/src/core/java/detection/unix.rs
src-tauri/src/core/java/detection/mod.rs
src-tauri/src/core/java/detection.rs
Add a dedicated Java catalog cache module with atomic read/write and errorful API.
  • Create cache.rs exposing load_cached_catalog_result, load_cached_catalog, save_catalog_cache, and clear_catalog_cache with 24h TTL logic based on cached_at.
  • Use atomic write via temporary files and rename for catalog cache persistence, returning JavaError instead of String for better propagation.
  • Update AdoptiumProvider::fetch_catalog to consume the new load_cached_catalog_result and log warnings on cache load failures instead of panicking.
src-tauri/src/core/java/cache.rs
src-tauri/src/core/java/providers/adoptium.rs
Harden download queue persistence with a global lock, atomic file writes, and high-level helpers.
  • Introduce a static DOWNLOAD_QUEUE_FILE_LOCK mutex and refactor queue path resolution into DownloadQueue::queue_path returning Result<PathBuf, String>.
  • Add load_from_path and save_atomic_to_path helpers that perform JSON (de)serialization and atomic rename-based writes with parent directory creation.
  • Expose DownloadQueue::update, list_pending, add_pending, and remove_pending as the main thread-safe API; keep load/save only for legacy use behind the same lock.
src-tauri/src/core/downloader.rs
Make Java configuration persistence atomic and return structured errors.
  • Extract shared write_file_atomic helper in persistence.rs to write via *.tmp and rename with parent directory creation.
  • Introduce load_java_config_result returning Result<JavaConfig, JavaError> while keeping load_java_config as a wrapper that logs and falls back to defaults on failure.
  • Change save_java_config to use the atomic writer and propagate JavaError instead of panicking/ignoring IO issues.
src-tauri/src/core/java/persistence.rs
Improve Adoptium provider robustness with bounded concurrency, retry logic, and better typing/tests.
  • Add constants controlling catalog concurrency and retry behavior and implement fetch_available_releases_with_retry to wrap the /info/available_releases call with retries and backoff returning JavaError.
  • Add fetch_adoptium_assets_with_retry for asset endpoints, including exponential backoff and final error propagation as String.
  • Use a Semaphore to bound concurrent catalog asset requests, switch to typed ImageType in JavaReleaseInfo/JavaDownloadInfo, and fall back to synthetic "unavailable" entries when all retries fail; add targeted async tests using a custom mock HTTP server for timeout/5xx/parse failures.
src-tauri/src/core/java/providers/adoptium.rs
src-tauri/src/core/java/mod.rs
Introduce richer error typing and result struct for resuming Java downloads, exporting them to the React UI.
  • Add ResumeJavaDownloadFailureReason enum with a from_error_message classifier and table-driven tests to categorize failures (network, cancelled, checksum, extraction, verification, filesystem, invalid archive, unknown).
  • Define ResumeJavaDownloadFailure and ResumeJavaDownloadsResult structs (with TS export) to capture per-download failure metadata and aggregate resume outcomes.
  • Refactor resume_pending_downloads into resume_pending_downloads_with to use DownloadQueue::list_pending and the new install helper, populating ResumeJavaDownloadsResult with both successes and structured failures; adjust the resume_java_downloads Tauri command accordingly.
src-tauri/src/core/java/error.rs
src-tauri/src/core/java/mod.rs
src-tauri/src/main.rs
Harden archive extraction and Java validation for better safety and detection success.
  • Add sanitize_relative_path in utils/zip.rs to reject absolute paths and .. segments when extracting tarballs, and use sanitized paths for both top-level detection and disk writes in extract_tar_gz.
  • Improve Java version detection in validation.rs by preferring stderr when available but falling back to stdout if stderr is empty, making it compatible with JDKs that print java -version to stdout.
src-tauri/src/utils/zip.rs
src-tauri/src/core/java/validation.rs
Update project documentation for the new frontend structure and Java/download system behaviour.
  • Refresh .github/copilot-instructions.md to describe the React ui-new app as primary, the legacy Svelte app layout, updated dev/build/test commands, and the current Java/download system characteristics.
  • Clarify node/pnpm requirements, version management (Cargo.toml as source of truth), state management patterns across React and Svelte, and filesystem layout for instances and caches.
.github/copilot-instructions.md

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
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

@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 - I've found 3 issues, and left some high level feedback:

  • In downloader.rs, the DOWNLOAD_QUEUE_FILE_LOCK static is declared as static DOWNLOAD_QUEUE_FILE_LOCK: <()> = Mutex::new(());, which is invalid syntax and should be static DOWNLOAD_QUEUE_FILE_LOCK: Mutex<()> = Mutex::new(()); to compile correctly.
  • The new find_top_level_dir in install.rs now errors when there is not exactly one top-level directory (instead of returning an empty string as before), which changes behavior and may break extraction for archives that previously worked; consider either preserving the old fallback or explicitly validating that all existing archives are compatible with this stricter rule.
  • In resume_pending_downloads_with you use eprintln! for unknown image types; for consistency with the rest of the codebase and better log routing in Tauri, it would be preferable to use the log crate (e.g., log::warn!) instead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `downloader.rs`, the `DOWNLOAD_QUEUE_FILE_LOCK` static is declared as `static DOWNLOAD_QUEUE_FILE_LOCK: <()> = Mutex::new(());`, which is invalid syntax and should be `static DOWNLOAD_QUEUE_FILE_LOCK: Mutex<()> = Mutex::new(());` to compile correctly.
- The new `find_top_level_dir` in `install.rs` now errors when there is not exactly one top-level directory (instead of returning an empty string as before), which changes behavior and may break extraction for archives that previously worked; consider either preserving the old fallback or explicitly validating that all existing archives are compatible with this stricter rule.
- In `resume_pending_downloads_with` you use `eprintln!` for unknown image types; for consistency with the rest of the codebase and better log routing in Tauri, it would be preferable to use the `log` crate (e.g., `log::warn!`) instead.

## Individual Comments

### Comment 1
<location path="src-tauri/src/core/downloader.rs" line_range="13" />
<code_context>
 use tokio::sync::Semaphore;
 use ts_rs::TS;

+static DOWNLOAD_QUEUE_FILE_LOCK: <()> = Mutex::new(());
+
 #[derive(Debug, Clone, Serialize, Deserialize, TS)]
</code_context>
<issue_to_address>
**issue (bug_risk):** The static DOWNLOAD_QUEUE_FILE_LOCK type annotation is invalid and will not compile.

This declaration should use a concrete type, e.g. `static DOWNLOAD_QUEUE_FILE_LOCK: Mutex<()> = Mutex::new(());`, so the module compiles correctly.
</issue_to_address>

### Comment 2
<location path="src-tauri/src/core/downloader.rs" line_range="90" />
<code_context>
-    /// Load download queue from file
-    pub fn load(app_handle: &AppHandle) -> Self {
-        let queue_path = app_handle
+    fn queue_path(app_handle: &AppHandle) -> Result<PathBuf, String> {
+        app_handle
             .path()
</code_context>
<issue_to_address>
**issue (bug_risk):** queue_path uses map_err on an Option, which will not compile and also doesn’t handle the None case.

`app_handle.path().app_data_dir()` returns an `Option<PathBuf>`. You can convert it to a `Result` and make the failure explicit like this:

```rust
fn queue_path(app_handle: &AppHandle) -> Result<PathBuf, String> {
    app_handle
        .path()
        .app_data_dir()
        .map(|dir| dir.join("download_queue.json"))
        .ok_or_else(|| "Failed to resolve app_data_dir for download queue".to_string())
}
```

This makes the error type explicit and avoids a panic when the path cannot be resolved.
</issue_to_address>

### Comment 3
<location path="src-tauri/src/utils/zip.rs" line_range="31" />
<code_context>
+    Ok(safe_path)
+}
+
 pub fn extract_zip(zip_path: &Path, extract_to: &Path) -> Result<(), String> {
     let file = fs::File::open(zip_path)
         .map_err(|e| format!("Failed to open zip {}: {}", zip_path.display(), e))?;
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Path sanitization is applied to tar.gz entries but not to zip entries, leaving a potential directory traversal gap.

`sanitize_relative_path` now protects `extract_tar_gz` from `../` and absolute paths, but `extract_zip` still uses raw entry paths, so a crafted zip could write outside `extract_to`.

Please also sanitize zip entry paths (and skip empty results) before joining them with `extract_to` to close this gap.
</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.

Ok(safe_path)
}

pub fn extract_zip(zip_path: &Path, extract_to: &Path) -> Result<(), String> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚨 suggestion (security): Path sanitization is applied to tar.gz entries but not to zip entries, leaving a potential directory traversal gap.

sanitize_relative_path now protects extract_tar_gz from ../ and absolute paths, but extract_zip still uses raw entry paths, so a crafted zip could write outside extract_to.

Please also sanitize zip entry paths (and skip empty results) before joining them with extract_to to close this gap.

@HsiangNianian
Copy link
Copy Markdown
Member

@BegoniaHe is this PR still in maintained?

@BegoniaHe
Copy link
Copy Markdown
Contributor Author

Yes @HsiangNianian

BegoniaHe and others added 4 commits March 12, 2026 09:15
add src-tauri/src/core/java/cache.rs for Java catalog caching
reorganize provider interfaces and move detection/install changes
update persistence, adoptium provider and validation

Reviewed-by: Raptor mini (Preview)
- Update .github/copilot-instructions.md

Reviewed-by: GitHub Copilot CLI
@BegoniaHe BegoniaHe force-pushed the refactor/java-provider branch 2 times, most recently from 8afd383 to dedc22d Compare March 12, 2026 08:49
HsiangNianian added a commit that referenced this pull request Mar 18, 2026
# fix(modpack): 将 CurseForge API Key 改为编译期可选常量

修复 `env!()` 宏在开发者本地无 `CURSEFORGE_API_KEY` 时导致编译失败的问题,改用 `option_env!()`
+ build.rs 中的 `dotenvy` 读取 .env 文件,实现编译期嵌入、缺失时优雅降级。

## 更改类型

- [x] Bug 修复(修复问题的非破坏性更改)
- [ ] 新功能(添加功能的非破坏性更改)
- [ ] 破坏性更改(会导致现有功能无法正常工作的修复或功能)
- [ ] 文档更新
- [ ] UI/UX 改进
- [ ] 性能优化
- [ ] 代码重构(无功能性更改)
- [x] 配置更改
- [ ] 测试添加或更新

## LLM 生成代码声明

- [x] 此 PR 不包含 LLM 生成的代码,我**提供**质量担保

## 相关 Issue

相关 #110 #117

## 更改内容

### 后端 (Rust)

- modpack.rs:将 `env!("CURSEFORGE_API_KEY")` 替换为 `const
CURSEFORGE_API_KEY: Option<&str> =
option_env!("CURSEFORGE_API_KEY")`,key 不存在时编译为 `None`,调用 CurseForge
功能时返回友好错误而非 panic
- build.rs:添加 `dotenvy::dotenv()` 调用,允许通过 .env 文件在编译期注入 key,并注册
`cargo:rerun-if-changed` / `cargo:rerun-if-env-changed` 确保增量构建正确

### 前端 (Svelte)

- 无

### 配置

- Cargo.toml:在 `[build-dependencies]` 中添加 `dotenvy = { version = "0.15",
default-features = false }`
- .gitignore:添加 .env / `.env.local` 忽略规则,防止 key 被意外提交
- .env.example:新增示例文件,说明可选配置项及获取方式

## 测试

### 测试环境

- **操作系统**:Fedora Linux 6.19.6-300.fc44.x86_64 x86_64
- **DropOut 版本**:0.2.0-alpha.5
- **测试的 Minecraft 版本**:N/A
- **Mod 加载器**:N/A

### 测试用例

- [ ] 已在 Windows 上测试
- [ ] 已在 macOS 上测试
- [x] 已在 Linux 上测试
- [ ] 已测试原版 Minecraft
- [ ] 已测试 Fabric
- [ ] 已测试 Forge
- [ ] 已测试游戏启动
- [ ] 已测试登录流程
- [ ] 已测试 Java 检测/下载

### 测试步骤

1. 不设置 `CURSEFORGE_API_KEY`,不创建 .env 文件,直接执行 `cargo check` → 应编译通过(无报错)
2. 创建 .env 文件并写入 `CURSEFORGE_API_KEY=test_key`,执行 `cargo check` →
应编译通过,key 被嵌入二进制
3. 不含 key 的构建中触发 CurseForge modpack 导入 → 应返回友好错误提示而非 panic

## 检查清单

### 代码质量

- [x] 我的代码遵循项目的代码风格指南
- [x] 我已对自己的代码进行了自审
- [ ] 我已对难以理解的区域添加了注释
- [x] 我的更改没有产生新的警告或错误

### 测试验证

- [x] 我已在本地测试了我的更改
- [ ] 我已添加测试来证明我的修复有效或功能正常工作
- [x] 新的和现有的单元测试在本地通过
- [x] 我至少在一个目标平台上进行了测试

### 文档更新

- [ ] 我已相应地更新了文档
- [ ] 如有需要,我已更新 README
- [ ] 我已在必要处添加/更新代码注释

### 依赖项

- [x] 我已检查没有添加不必要的依赖项
- [x] 所有新依赖项都已正确记录
- [x] Cargo.lock 已更新

## 附加说明

`dotenvy` 仅作为 **build-dependency**,不会进入最终二进制。官方发布构建通过 CI 环境变量注入
key,普通开发者无需任何操作即可正常编译和运行。

Co-authored-by: 简律纯 <i@jyunko.cn>
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