fix: 修复 SLS 实例删除残留外部数据与重装共用文件夹
Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
删除实例残留外部数据: - remove_instance 原先只在 game_dir_override 指向 versions/<name> 隔离目录时才删除外部目录;SLS(hosted)实例使用版本共享布局 (外部目录直接作为游戏根),删除时落到托管目录分支,外部 mods/ 存档/配置残留。 - 现在 game_dir_override 只要指向实例独占目录(非共享 .minecraft 根)就一并删除;新增 is_shared_minecraft_root 判据:含 libraries/ 或 assets/ 的目录视为共享游戏根,删除实例时保留。 重装共用同一文件夹: - hosted::create 生成 game_dir_override 时直接拼接 `<root>/<pack name>`,无冲突处理;同一整合包安装两次会指向同一 目录,两个实例共用一份游戏数据。 - 新增 unique_game_dir:目标目录已存在时依次尝试 `<name> (1)`、 `<name> (2)` …,与 create_instance::resolve_instance_path 的 实例目录去重逻辑保持一致。
This commit is contained in:
@ -436,16 +436,23 @@ pub async fn create(
|
|||||||
// e.g. `<root>/<pack name>`. Avoid a `versions/<name>` layout: that shape
|
// e.g. `<root>/<pack name>`. Avoid a `versions/<name>` layout: that shape
|
||||||
// is reserved for externally linked launcher instances and would make the
|
// is reserved for externally linked launcher instances and would make the
|
||||||
// launcher expect a Minecraft version JSON beside the pack.
|
// launcher expect a Minecraft version JSON beside the pack.
|
||||||
let game_dir_override = game_dir_root
|
let game_dir_override = match game_dir_root
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
.filter(|root| !root.is_empty())
|
.filter(|root| !root.is_empty())
|
||||||
.map(|root| {
|
{
|
||||||
Path::new(root)
|
Some(root) => {
|
||||||
.join(&publication.manifest.name)
|
// The pack's game files live in their own folder under the chosen
|
||||||
.to_string_lossy()
|
// root, e.g. `<root>/<pack name>`. If that folder already exists
|
||||||
.into_owned()
|
// (a previous install of the same pack, or a name clash), pick a
|
||||||
});
|
// suffixed sibling instead of sharing the folder with another
|
||||||
|
// instance.
|
||||||
|
let base = Path::new(root).join(&publication.manifest.name);
|
||||||
|
let resolved = unique_game_dir(&base);
|
||||||
|
Some(resolved.to_string_lossy().into_owned())
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
let instance = crate::state::create_instance(
|
let instance = crate::state::create_instance(
|
||||||
crate::state::CreateInstance {
|
crate::state::CreateInstance {
|
||||||
name: publication.manifest.name.clone(),
|
name: publication.manifest.name.clone(),
|
||||||
@ -480,6 +487,29 @@ pub async fn create(
|
|||||||
Ok(instance.id)
|
Ok(instance.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `base` when its directory does not exist yet; otherwise returns the
|
||||||
|
/// first `base (n)` (n = 1, 2, …) whose directory is still free. Mirrors the
|
||||||
|
/// instance-folder de-duplication in `create_instance::resolve_instance_path`,
|
||||||
|
/// so re-installing the same hosted pack no longer makes two instances share a
|
||||||
|
/// single game folder.
|
||||||
|
fn unique_game_dir(base: &Path) -> PathBuf {
|
||||||
|
if !base.exists() {
|
||||||
|
return base.to_path_buf();
|
||||||
|
}
|
||||||
|
let parent = base.parent().unwrap_or_else(|| Path::new(""));
|
||||||
|
let name = base
|
||||||
|
.file_name()
|
||||||
|
.map(|n| n.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or_else(|| "instance".to_string());
|
||||||
|
let mut which = 1u32;
|
||||||
|
loop {
|
||||||
|
let candidate = parent.join(format!("{name} ({which})"));
|
||||||
|
if !candidate.exists() {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
which += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
pub async fn binding(instance_id: &str) -> crate::Result<Option<Binding>> {
|
pub async fn binding(instance_id: &str) -> crate::Result<Option<Binding>> {
|
||||||
read_json(&crate::instance::get_full_path(instance_id).await?, BINDING)
|
read_json(&crate::instance::get_full_path(instance_id).await?, BINDING)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -32,12 +32,17 @@ pub(crate) async fn remove_instance(
|
|||||||
.game_dir_override
|
.game_dir_override
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.filter(|path| is_version_isolated_game_dir(path))
|
.filter(|path| {
|
||||||
|
// Delete the external game directory when the instance owns it.
|
||||||
|
// A version-isolated `versions/<name>` folder is obviously
|
||||||
|
// exclusive; a plain `<root>/<pack name>` folder is also owned by
|
||||||
|
// this instance. A shared `.minecraft` root, however, holds the
|
||||||
|
// game's libraries/assets and must never be deleted with one
|
||||||
|
// instance.
|
||||||
|
is_version_isolated_game_dir(path)
|
||||||
|
|| !is_shared_minecraft_root(path)
|
||||||
|
})
|
||||||
{
|
{
|
||||||
// New instances created against a configured `.minecraft` root use
|
|
||||||
// a private `versions/<name>` directory. Remove that external
|
|
||||||
// directory when the instance is deleted, while preserving shared
|
|
||||||
// (non-isolated) overrides for backwards compatibility.
|
|
||||||
game_dir_override
|
game_dir_override
|
||||||
} else {
|
} else {
|
||||||
state.directories.instances_dir().join(&instance.path)
|
state.directories.instances_dir().join(&instance.path)
|
||||||
@ -69,3 +74,12 @@ fn is_version_isolated_game_dir(path: &Path) -> bool {
|
|||||||
.and_then(|name| name.to_str())
|
.and_then(|name| name.to_str())
|
||||||
== Some("versions")
|
== Some("versions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Heuristic: a shared `.minecraft` root holds the game's libraries and
|
||||||
|
/// assets, which must survive the removal of any single instance that points
|
||||||
|
/// at it. Instance-owned external folders (e.g. a hosted modpack's
|
||||||
|
/// `<root>/<pack name>` directory) contain only mods/saves/config and no such
|
||||||
|
/// shared game body.
|
||||||
|
fn is_shared_minecraft_root(path: &Path) -> bool {
|
||||||
|
path.join("libraries").is_dir() || path.join("assets").is_dir()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user