Skip to content

feat: 添加从 server.jar 提取版本 ID 的功能#434

Open
MimiRabbit87 wants to merge 4 commits intoSeaLantern-Studio:devfrom
MimiRabbit87:feat/extract-version-id-new
Open

feat: 添加从 server.jar 提取版本 ID 的功能#434
MimiRabbit87 wants to merge 4 commits intoSeaLantern-Studio:devfrom
MimiRabbit87:feat/extract-version-id-new

Conversation

@MimiRabbit87
Copy link

@MimiRabbit87 MimiRabbit87 commented Feb 25, 2026

提交前检查清单

  • 已阅读 提交前测试必读!!!.md 并完成要求测试
  • 本地/CI 测试通过
  • 代码审查 (Self-review) 完成

变更分类(必选其一)

  • feat 新功能
  • fix Bug 修复
  • docs 文档/模板
  • style 代码格式(不影响功能)
  • refactor 重构(既不修复 bug 也不添加功能)
  • perf 性能优化
  • test 测试相关
  • chore 构建/CI/依赖/工具链
  • revert 回滚
  • security 安全修复

影响范围(可多选)

  • 前端 Frontend

    • UI 样式/布局
    • 组件/状态/路由逻辑
    • 依赖变更 (package.json)
  • 后端 Backend

    • API 接口变更
    • 业务逻辑
    • 依赖升级
  • 基础设施 Infrastructure

    • CI/CD 配置
    • 部署脚本
    • 数据库迁移

导入规范检查: 使用别名导入,避免相对路径 ../

变更详情

摘要

加了个功能获取 server.jar 里的 version.json .id

动机/背景

#426 中提到的自动检测服务器版本

具体改动

server_manager.rs 里面加了个 get_server_version(),可以获取 server.jar 里的 version.json .id

界面变动(如适用)

没有

关联 Issue

Addresses #426


自动化审查说明

@chatgpt-codex-connector
Copy link

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 25, 2026

Reviewer's Guide

添加了一个后端功能,通过读取 server.jar 中的 version.json 来提取 Minecraft 服务器版本 ID,并将其暴露为一个新的 Tauri 命令,另外还包括一些生成的 Vite 依赖元数据文件。

从 server.jar 提取服务器版本 ID 的时序图

sequenceDiagram
    actor Frontend
    participant TauriCore
    participant ServerCommands
    participant ServerManager
    participant FileSystem
    participant ZipArchive
    participant JSONParser

    Frontend->>TauriCore: invoke get_server_version(jar_path)
    TauriCore->>ServerCommands: get_server_version(jar_path)
    ServerCommands->>ServerManager: get_server_version(&jar_path)

    ServerManager->>FileSystem: File::open(jar_path)
    FileSystem-->>ServerManager: Result<File, String>

    ServerManager->>ZipArchive: ZipArchive::new(file)
    ZipArchive-->>ServerManager: Result<ZipArchive, String>

    ServerManager->>ZipArchive: by_name(version.json)
    ZipArchive-->>ServerManager: Result<version_file, String>

    ServerManager->>ServerManager: read_to_string(content)
    ServerManager->>JSONParser: serde_json::from_str(content)
    JSONParser-->>ServerManager: Result<Value, String>

    ServerManager->>ServerManager: extract json["id"].as_str()
    ServerManager-->>ServerCommands: Ok(id)
    ServerCommands-->>TauriCore: Ok(id)
    TauriCore-->>Frontend: Ok(id)
Loading

ServerManager 和新版本提取命令的类图

classDiagram
    class ServerManager {
        +get_server_version(jar_path: &String) Result~String, String~
        +delete_server(id: &str) Result~(), String~
        +get_server_status(id: &str) ServerStatusInfo
    }

    class ServerCommands {
        <<module>>
        +get_server_version(jar_path: String) Result~String, String~
        +get_server_status(id: String) ServerStatusInfo
        +delete_server(id: String) Result~(), String~
    }

    class TauriLib {
        +run() void
    }

    class ManagerProvider {
        <<utility>>
        +manager() ServerManager
    }

    ServerCommands --> ManagerProvider : uses
    ManagerProvider --> ServerManager : returns
    TauriLib --> ServerCommands : registers Tauri commands
    TauriLib --> ManagerProvider : indirectly uses via commands
Loading

文件级变更

Change Details Files
将从 server.jar 中提取 version.json 的 id 的逻辑添加到 ServerManager,并通过 Tauri 命令对外暴露。
  • 在 ServerManager 上实现 get_server_version:将 server.jar 作为 ZIP 打开,读取 version.json,解析 JSON,并在使用基于字符串的错误处理的情况下返回 id 字段。
  • 将 get_server_version 暴露为一个 Tauri 命令,该命令接受一个 jar_path 字符串并委托给 ServerManager。
  • 在 Tauri 命令列表中注册新的 get_server_version 命令,以便前端可以调用它。
src-tauri/src/services/server_manager.rs
src-tauri/src/commands/server.rs
src-tauri/src/lib.rs
在 .vite/deps 下添加 Vite 依赖元数据文件。
  • 在 .vite/deps 中添加一个 package.json,将模块类型配置为 ESM。
  • 在 .vite/deps 中添加一个空的或生成的 _metadata.json 文件(可能是构建工具产物)。
.vite/deps/package.json
.vite/deps/_metadata.json

Tips and commands

与 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 来触发新的评审!

自定义你的体验

访问你的 dashboard 以:

  • 启用或禁用评审功能,例如 Sourcery 生成的 pull request 摘要、评审者指南等。
  • 更改评审语言。
  • 添加、移除或编辑自定义评审指令。
  • 调整其他评审设置。

获取帮助

Original review guide in English

Reviewer's Guide

Adds a backend feature to extract the Minecraft server version ID by reading version.json inside server.jar and exposes it as a new Tauri command, plus some generated Vite dependency metadata files.

Sequence diagram for extracting server version ID from server.jar

sequenceDiagram
    actor Frontend
    participant TauriCore
    participant ServerCommands
    participant ServerManager
    participant FileSystem
    participant ZipArchive
    participant JSONParser

    Frontend->>TauriCore: invoke get_server_version(jar_path)
    TauriCore->>ServerCommands: get_server_version(jar_path)
    ServerCommands->>ServerManager: get_server_version(&jar_path)

    ServerManager->>FileSystem: File::open(jar_path)
    FileSystem-->>ServerManager: Result<File, String>

    ServerManager->>ZipArchive: ZipArchive::new(file)
    ZipArchive-->>ServerManager: Result<ZipArchive, String>

    ServerManager->>ZipArchive: by_name(version.json)
    ZipArchive-->>ServerManager: Result<version_file, String>

    ServerManager->>ServerManager: read_to_string(content)
    ServerManager->>JSONParser: serde_json::from_str(content)
    JSONParser-->>ServerManager: Result<Value, String>

    ServerManager->>ServerManager: extract json["id"].as_str()
    ServerManager-->>ServerCommands: Ok(id)
    ServerCommands-->>TauriCore: Ok(id)
    TauriCore-->>Frontend: Ok(id)
Loading

Class diagram for ServerManager and new version extraction command

classDiagram
    class ServerManager {
        +get_server_version(jar_path: &String) Result~String, String~
        +delete_server(id: &str) Result~(), String~
        +get_server_status(id: &str) ServerStatusInfo
    }

    class ServerCommands {
        <<module>>
        +get_server_version(jar_path: String) Result~String, String~
        +get_server_status(id: String) ServerStatusInfo
        +delete_server(id: String) Result~(), String~
    }

    class TauriLib {
        +run() void
    }

    class ManagerProvider {
        <<utility>>
        +manager() ServerManager
    }

    ServerCommands --> ManagerProvider : uses
    ManagerProvider --> ServerManager : returns
    TauriLib --> ServerCommands : registers Tauri commands
    TauriLib --> ManagerProvider : indirectly uses via commands
Loading

File-Level Changes

Change Details Files
Add server.jar version.json id extraction logic to ServerManager and expose it via a Tauri command.
  • Implement get_server_version on ServerManager to open server.jar as a ZIP, read version.json, parse JSON, and return the id field with string-based error handling.
  • Expose get_server_version as a Tauri command that accepts a jar_path string and delegates to ServerManager.
  • Register the new get_server_version command in the Tauri command list so it is callable from the frontend.
src-tauri/src/services/server_manager.rs
src-tauri/src/commands/server.rs
src-tauri/src/lib.rs
Add Vite dependency metadata files under .vite/deps.
  • Add a package.json in .vite/deps configuring module type as ESM.
  • Add an empty or generated _metadata.json file in .vite/deps (likely build tooling artifact).
.vite/deps/package.json
.vite/deps/_metadata.json

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
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 - 我发现了 1 个问题,并给出了一些整体性的反馈:

  • 建议将 get_server_version(&self, jar_path: &String) 修改为接收 &Path/&PathBuf,或者至少是 &str,这样在使用上会更符合 Rust 习惯,也更好地与文件系统相关 API 保持一致。
  • 当前在 get_server_version 中使用的 map_err(|e| e.to_string()) 错误处理链会丢失错误的上下文和类型信息;使用自定义错误类型,或至少为每个错误添加更具描述性的上下文,有助于更容易地诊断失败原因。
  • 新增的 .vite/deps/package.json(以及 .vite/deps 下的 _metadata.json)看起来像是构建产物,通常不应该提交到版本控制中。
给 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- Consider changing `get_server_version(&self, jar_path: &String)` to take a `&Path`/`&PathBuf` or at least `&str` so usage is more idiomatic and better aligned with filesystem APIs.
- The current `map_err(|e| e.to_string())` chains in `get_server_version` lose error context and type information; using a custom error type or at least adding more descriptive context to each error would make failures easier to diagnose.
- The newly added `.vite/deps/package.json` (and `_metadata.json`) under `.vite/deps` looks like a build artifact and probably shouldn’t be checked into source control.

## Individual Comments

### Comment 1
<location path="src-tauri/src/services/server_manager.rs" line_range="1229-1238" />
<code_context>
     }

+    /// 从 server.jar 中提取 version.json 的 id 字段
+    pub fn get_server_version(&self, jar_path: &String) -> Result<String, String> {
+        let file = File::open(jar_path).map_err(|e| e.to_string())?;
+        let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?;
+        let mut version_file = archive.by_name("version.json").map_err(|e| e.to_string())?;
+        let mut content = String::new();
+        version_file
+            .read_to_string(&mut content)
+            .map_err(|e| e.to_string())?;
+        let json: serde_json::Value = serde_json::from_str(&content).map_err(|e| e.to_string())?;
+        let id = json["id"]
+            .as_str()
+            .ok_or_else(|| "Missing or invalid 'id' field in version.json".to_string())?
</code_context>
<issue_to_address>
**suggestion:** Use more ergonomic argument type and avoid repeated `to_string()` conversions in error handling.

1. Prefer `jar_path: &str` for a read-only path to keep the API more flexible and decoupled from `String`.

2. Instead of converting every error to `String` with `map_err(|e| e.to_string())?`, use a richer error type (e.g. a custom error or `anyhow::Error`) and add context where needed (e.g. `format!("failed to open {jar_path}: {e}")`), or let `?` propagate the original error so you don’t lose structured information.

Suggested implementation:

```rust
use std::fs::File;
use std::io::Read;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use zip::ZipArchive;

use anyhow::{anyhow, Context};
use crate::models::server::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;

```

```rust
    /// 从 server.jar 中提取 version.json 的 id 字段
    pub fn get_server_version(&self, jar_path: &str) -> anyhow::Result<String> {
        let file = File::open(jar_path)
            .with_context(|| format!("failed to open server jar at path: {jar_path}"))?;

        let mut archive =
            ZipArchive::new(file).context("failed to read server jar as ZIP archive")?;

        let mut version_file = archive
            .by_name("version.json")
            .context("version.json not found in server jar")?;

        let mut content = String::new();
        version_file
            .read_to_string(&mut content)
            .context("failed to read version.json from server jar")?;

        let json: Value =
            serde_json::from_str(&content).context("failed to parse version.json as JSON")?;

        let id = json
            .get("id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow!("missing or invalid 'id' field in version.json"))?;

        Ok(id.to_owned())
    }

```

1. All call sites of `get_server_version` must be updated from `get_server_version(&some_string)` to `get_server_version(some_string)` or `get_server_version(some_str)` to match the new `&str` parameter type.
2. Callers must handle the new `anyhow::Result<String>` return type:
   - Either propagate it with `?` from functions that also return `anyhow::Result<_>`, or
   - Convert it back to `Result<_, String>` where needed, e.g. `get_server_version(...).map_err(|e| e.to_string())`.
3. Ensure `anyhow` is included in `Cargo.toml` (e.g. `anyhow = "1"`). If your project has a centralized error type, you may instead want to wrap or convert the `anyhow::Error` into that type at the call sites.
</issue_to_address>

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

Hey - I've found 1 issue, and left some high level feedback:

  • Consider changing get_server_version(&self, jar_path: &String) to take a &Path/&PathBuf or at least &str so usage is more idiomatic and better aligned with filesystem APIs.
  • The current map_err(|e| e.to_string()) chains in get_server_version lose error context and type information; using a custom error type or at least adding more descriptive context to each error would make failures easier to diagnose.
  • The newly added .vite/deps/package.json (and _metadata.json) under .vite/deps looks like a build artifact and probably shouldn’t be checked into source control.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider changing `get_server_version(&self, jar_path: &String)` to take a `&Path`/`&PathBuf` or at least `&str` so usage is more idiomatic and better aligned with filesystem APIs.
- The current `map_err(|e| e.to_string())` chains in `get_server_version` lose error context and type information; using a custom error type or at least adding more descriptive context to each error would make failures easier to diagnose.
- The newly added `.vite/deps/package.json` (and `_metadata.json`) under `.vite/deps` looks like a build artifact and probably shouldn’t be checked into source control.

## Individual Comments

### Comment 1
<location path="src-tauri/src/services/server_manager.rs" line_range="1229-1238" />
<code_context>
     }

+    /// 从 server.jar 中提取 version.json 的 id 字段
+    pub fn get_server_version(&self, jar_path: &String) -> Result<String, String> {
+        let file = File::open(jar_path).map_err(|e| e.to_string())?;
+        let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?;
+        let mut version_file = archive.by_name("version.json").map_err(|e| e.to_string())?;
+        let mut content = String::new();
+        version_file
+            .read_to_string(&mut content)
+            .map_err(|e| e.to_string())?;
+        let json: serde_json::Value = serde_json::from_str(&content).map_err(|e| e.to_string())?;
+        let id = json["id"]
+            .as_str()
+            .ok_or_else(|| "Missing or invalid 'id' field in version.json".to_string())?
</code_context>
<issue_to_address>
**suggestion:** Use more ergonomic argument type and avoid repeated `to_string()` conversions in error handling.

1. Prefer `jar_path: &str` for a read-only path to keep the API more flexible and decoupled from `String`.

2. Instead of converting every error to `String` with `map_err(|e| e.to_string())?`, use a richer error type (e.g. a custom error or `anyhow::Error`) and add context where needed (e.g. `format!("failed to open {jar_path}: {e}")`), or let `?` propagate the original error so you don’t lose structured information.

Suggested implementation:

```rust
use std::fs::File;
use std::io::Read;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use zip::ZipArchive;

use anyhow::{anyhow, Context};
use crate::models::server::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;

```

```rust
    /// 从 server.jar 中提取 version.json 的 id 字段
    pub fn get_server_version(&self, jar_path: &str) -> anyhow::Result<String> {
        let file = File::open(jar_path)
            .with_context(|| format!("failed to open server jar at path: {jar_path}"))?;

        let mut archive =
            ZipArchive::new(file).context("failed to read server jar as ZIP archive")?;

        let mut version_file = archive
            .by_name("version.json")
            .context("version.json not found in server jar")?;

        let mut content = String::new();
        version_file
            .read_to_string(&mut content)
            .context("failed to read version.json from server jar")?;

        let json: Value =
            serde_json::from_str(&content).context("failed to parse version.json as JSON")?;

        let id = json
            .get("id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow!("missing or invalid 'id' field in version.json"))?;

        Ok(id.to_owned())
    }

```

1. All call sites of `get_server_version` must be updated from `get_server_version(&some_string)` to `get_server_version(some_string)` or `get_server_version(some_str)` to match the new `&str` parameter type.
2. Callers must handle the new `anyhow::Result<String>` return type:
   - Either propagate it with `?` from functions that also return `anyhow::Result<_>`, or
   - Convert it back to `Result<_, String>` where needed, e.g. `get_server_version(...).map_err(|e| e.to_string())`.
3. Ensure `anyhow` is included in `Cargo.toml` (e.g. `anyhow = "1"`). If your project has a centralized error type, you may instead want to wrap or convert the `anyhow::Error` into that type at the call sites.
</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 1229 to 1238
pub fn get_server_version(&self, jar_path: &String) -> Result<String, String> {
let file = File::open(jar_path).map_err(|e| e.to_string())?;
let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?;
let mut version_file = archive.by_name("version.json").map_err(|e| e.to_string())?;
let mut content = String::new();
version_file
.read_to_string(&mut content)
.map_err(|e| e.to_string())?;
let json: serde_json::Value = serde_json::from_str(&content).map_err(|e| e.to_string())?;
let id = json["id"]
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: 使用更符合习惯的参数类型,并避免在错误处理时重复调用 to_string()

  1. 对于只读路径,更推荐使用 jar_path: &str,以便让 API 更加灵活,也不要和 String 强耦合。

  2. 与其在每个错误分支中通过 map_err(|e| e.to_string())? 把错误都转换成 String,不如使用更丰富的错误类型(例如自定义错误或 anyhow::Error),并在需要时添加上下文信息(例如 format!("failed to open {jar_path}: {e}"));或者让 ? 直接向上传播原始错误,这样就不会丢失结构化的错误信息。

推荐实现方式:

use std::fs::File;
use std::io::Read;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use zip::ZipArchive;

use anyhow::{anyhow, Context};
use crate::models::server::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
    /// 从 server.jar 中提取 version.json 的 id 字段
    pub fn get_server_version(&self, jar_path: &str) -> anyhow::Result<String> {
        let file = File::open(jar_path)
            .with_context(|| format!("failed to open server jar at path: {jar_path}"))?;

        let mut archive =
            ZipArchive::new(file).context("failed to read server jar as ZIP archive")?;

        let mut version_file = archive
            .by_name("version.json")
            .context("version.json not found in server jar")?;

        let mut content = String::new();
        version_file
            .read_to_string(&mut content)
            .context("failed to read version.json from server jar")?;

        let json: Value =
            serde_json::from_str(&content).context("failed to parse version.json as JSON")?;

        let id = json
            .get("id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow!("missing or invalid 'id' field in version.json"))?;

        Ok(id.to_owned())
    }
  1. 所有调用 get_server_version 的地方,都需要从 get_server_version(&some_string) 更新为 get_server_version(some_string)get_server_version(some_str),以匹配新的 &str 参数类型。
  2. 调用方需要处理新的返回类型 anyhow::Result<String>
    • 要么在同样返回 anyhow::Result<_> 的函数中使用 ? 继续向外传播;
    • 要么在需要的地方把它转换回 Result<_, String>,例如使用 get_server_version(...).map_err(|e| e.to_string())
  3. 确保在 Cargo.toml 中已经引入 anyhow(例如 anyhow = "1")。如果你的项目有统一的错误类型,也可以在调用端把 anyhow::Error 包装或转换为该错误类型。
Original comment in English

suggestion: Use more ergonomic argument type and avoid repeated to_string() conversions in error handling.

  1. Prefer jar_path: &str for a read-only path to keep the API more flexible and decoupled from String.

  2. Instead of converting every error to String with map_err(|e| e.to_string())?, use a richer error type (e.g. a custom error or anyhow::Error) and add context where needed (e.g. format!("failed to open {jar_path}: {e}")), or let ? propagate the original error so you don’t lose structured information.

Suggested implementation:

use std::fs::File;
use std::io::Read;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use zip::ZipArchive;

use anyhow::{anyhow, Context};
use crate::models::server::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
    /// 从 server.jar 中提取 version.json 的 id 字段
    pub fn get_server_version(&self, jar_path: &str) -> anyhow::Result<String> {
        let file = File::open(jar_path)
            .with_context(|| format!("failed to open server jar at path: {jar_path}"))?;

        let mut archive =
            ZipArchive::new(file).context("failed to read server jar as ZIP archive")?;

        let mut version_file = archive
            .by_name("version.json")
            .context("version.json not found in server jar")?;

        let mut content = String::new();
        version_file
            .read_to_string(&mut content)
            .context("failed to read version.json from server jar")?;

        let json: Value =
            serde_json::from_str(&content).context("failed to parse version.json as JSON")?;

        let id = json
            .get("id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow!("missing or invalid 'id' field in version.json"))?;

        Ok(id.to_owned())
    }
  1. All call sites of get_server_version must be updated from get_server_version(&some_string) to get_server_version(some_string) or get_server_version(some_str) to match the new &str parameter type.
  2. Callers must handle the new anyhow::Result<String> return type:
    • Either propagate it with ? from functions that also return anyhow::Result<_>, or
    • Convert it back to Result<_, String> where needed, e.g. get_server_version(...).map_err(|e| e.to_string()).
  3. Ensure anyhow is included in Cargo.toml (e.g. anyhow = "1"). If your project has a centralized error type, you may instead want to wrap or convert the anyhow::Error into that type at the call sites.

@LingyeNBird
Copy link
Collaborator

功能没问题。位置有问题,目前创建服务器时会解包找服务端类型,你做成独立函数会导致多次重复的解包行为,应当做到一起。
你未提供前端调用,且你不是开发组成员,没有沟通会导致可维护性问题。
想做贡献请在accepted issue中做,没有issue或者没有accepted往往是有原因的,比如与预定的设计相悖,或者正在规划中。

请解决以上问题或者close。

@MimiRabbit87
Copy link
Author

功能没问题。位置有问题,目前创建服务器时会解包找服务端类型,你做成独立函数会导致多次重复的解包行为,应当做到一起。 你未提供前端调用,且你不是开发组成员,没有沟通会导致可维护性问题。 想做贡献请在accepted issue中做,没有issue或者没有accepted往往是有原因的,比如与预定的设计相悖,或者正在规划中。

请解决以上问题或者close。

好的,请问是加到 server_installer.rs 里吗,我看 detect_core_type_with_main_class() 那边把路径传过去拿了主类,然后继续判断服务器的核心种类。我在那里新增一个 JAR 包的解包管理,让 read_jar_main_class() 从解包管理那边拿 ZipAchieve,然后在 把 server_manager.rs 那边的实现搬到 server_installer.rs 来,与 detect_mc_version_from_mods() 的逻辑合并,接着从 server.rs scan_startup_candidates_blocking() 根据模组判断版本号那边调用,这样可以吗?

以及 Issue #426 那边似乎是 accepted 了呀?

@MimiRabbit87
Copy link
Author

功能没问题。位置有问题,目前创建服务器时会解包找服务端类型,你做成独立函数会导致多次重复的解包行为,应当做到一起。 你未提供前端调用,且你不是开发组成员,没有沟通会导致可维护性问题。 想做贡献请在accepted issue中做,没有issue或者没有accepted往往是有原因的,比如与预定的设计相悖,或者正在规划中。
请解决以上问题或者close。

好的,请问是加到 server_installer.rs 里吗,我看 detect_core_type_with_main_class() 那边把路径传过去拿了主类,然后继续判断服务器的核心种类。我在那里新增一个 JAR 包的解包管理,让 read_jar_main_class() 从解包管理那边拿 ZipAchieve,然后在 把 server_manager.rs 那边的实现搬到 server_installer.rs 来,与 detect_mc_version_from_mods() 的逻辑合并,接着从 server.rs scan_startup_candidates_blocking() 根据模组判断版本号那边调用,这样可以吗?

以及 Issue #426 那边似乎是 accepted 了呀?

或者可以在 ParsedServerCoreInfo 结构体中增加 version_id 字段,把逻辑塞进 read_jar_main_class() 里

@LingyeNBird
Copy link
Collaborator

对的,这样写可以的👍。
要不然你交了之后我还得删了自己移位置啥的,而且我本来就有这个开发计划,不是平白增加审查工作量了吗。

426我以为你只说了多版本命令。

@LingyeNBird
Copy link
Collaborator

目前的这个地方是条件显示,你改成固定显示好了。把结果放进去。取id的方式比统计模组名好一点,优先按取id的结果显示mc版本吧

PixPin_2026-02-25_21-55-40

- 在 ParsedServerCoreInfo 中添加 version_id 字段
- 重构 read_jar_main_class 为 read_jar_metadata,同时读取 version.json
- 更新 detect_core_type_with_metadata 返回包含 id 的完整信息
- 在 parse_server_core_type 中集成新逻辑
- 优化错误处理
@MimiRabbit87 MimiRabbit87 force-pushed the feat/extract-version-id-new branch from a998fd1 to 3f5bccb Compare February 25, 2026 14:04
- 在 scan_startup_candidates_blocking 中优先使用 parsed.version_id 作为 detected_mc_version;若 JAR 中无 version.json,则回退到原有的 mods 目录检测
- 更新前端类型 ParsedServerCoreInfo 和 API 映射以传递 version_id
- 在 scan_startup_candidates_blocking 中优先使用 parsed.version_id 作为 detected_mc_version;若 JAR 中无 version.json,则回退到原有的 mods 目录检测
- 更新前端类型 ParsedServerCoreInfo 和 API 映射以传递 version_id
@Nanaloveyuki Nanaloveyuki added the suspend merger Suspend merger | 暂缓合并 label Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

suspend merger Suspend merger | 暂缓合并

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants