feat(sync): 跨实例同步第1轮——同步功能表、InstanceSyncedOptions 类型与实例元数据字段
Sync source to CNB / Sync Git ref (push) Has been cancelled

- 新增 4 个 migration:同步功能表/instance_servers 扩展/synced_pack_selection/禁用发现选项

- 新增 InstanceSyncedOptions 类型(per-instance 同步开关)

- InstanceMetadata 增加 synced_options 字段并在 3 个查询点填充

- instance_rows 新增 get_instance_synced_options

- 前端 GameInstance 增加 synced_options 类型
This commit is contained in:
AxTps committed 2026-10-03 21:08:05 +08:00
1 parent 91e63fdb2c
commit 5a289c3a15
10 files changed
+302 -6

No files matched your search

+1
View File
@@ -394,6 +394,7 @@ mod tests {
groups: Vec::new(),
launch_overrides: InstanceLaunchOverrides::empty(instance_id),
loader_components: Vec::new(),
synced_options: Default::default(),
},
install_stage: InstanceInstallStage::Installed,
content: None,
@@ -1834,3 +1834,31 @@ mod tests {
assert_eq!(launch.applied_content_set.revision, 5);
}
}
pub(crate) async fn get_instance_synced_options(
instance_id: &str,
pool: &SqlitePool,
) -> crate::Result<crate::state::InstanceSyncedOptions> {
let rows = sqlx::query(
"SELECT feature, enabled FROM instance_sync_preferences WHERE instance_id = ?",
)
.bind(instance_id)
.fetch_all(pool)
.await?;
let mut options = crate::state::InstanceSyncedOptions::default();
for row in rows {
let feature: String = sqlx::Row::get(&row, "feature");
let enabled: bool = sqlx::Row::get(&row, "enabled");
match feature.as_str() {
"game_options" => options.game_options = enabled,
"command_history" => options.command_history = enabled,
"multiplayer_servers" => options.multiplayer_servers = enabled,
"creative_hotbars" => options.creative_hotbars = enabled,
"screenshots" => options.screenshots = enabled,
"resource_packs" => options.resource_packs = enabled,
"data_packs" => options.data_packs = enabled,
_ => {}
}
}
Ok(options)
}
@@ -15,6 +15,8 @@ pub struct InstanceMetadata {
pub launch_overrides: InstanceLaunchOverrides,
#[serde(default)]
pub loader_components: Vec<LoaderComponent>,
#[serde(default)]
pub synced_options: crate::state::InstanceSyncedOptions,
}
pub(crate) async fn get_instance(
@@ -36,10 +38,10 @@ pub(crate) async fn get_instance_metadata(
let loader_components =
loader_component_rows::list_loader_components(instance_id, pool)
.await?;
Ok(Some(InstanceMetadata::from_record(
record,
loader_components,
)))
let mut metadata = InstanceMetadata::from_record(record, loader_components);
metadata.synced_options =
instance_rows::get_instance_synced_options(instance_id, pool).await?;
Ok(Some(metadata))
}
pub(crate) async fn get_instances_metadata(
@@ -55,7 +57,11 @@ pub(crate) async fn get_instances_metadata(
pool,
)
.await?;
metadata.push(InstanceMetadata::from_record(record, components));
let mut item = InstanceMetadata::from_record(record, components);
item.synced_options =
instance_rows::get_instance_synced_options(&item.instance.id, pool)
.await?;
metadata.push(item);
}
Ok(metadata)
}
@@ -71,7 +77,11 @@ pub(crate) async fn list_instances(
pool,
)
.await?;
metadata.push(InstanceMetadata::from_record(record, components));
let mut item = InstanceMetadata::from_record(record, components);
item.synced_options =
instance_rows::get_instance_synced_options(&item.instance.id, pool)
.await?;
metadata.push(item);
}
Ok(metadata)
}
@@ -88,6 +98,7 @@ impl InstanceMetadata {
groups: record.groups,
launch_overrides: record.launch_overrides,
loader_components,
synced_options: Default::default(),
}
}
}
@@ -468,6 +468,7 @@ mod tests {
groups: vec!["Group A".to_string()],
launch_overrides,
loader_components: Vec::new(),
synced_options: Default::default(),
}
}
@@ -88,3 +88,24 @@ pub(crate) fn playtime_to_storage(
.into()
})
}
/// Per-instance synchronization feature switches. Mirrors the Axolotl
/// `InstanceSyncedOptions` shape so the sync subsystem can be ported on top of
/// the shared `instance_sync_preferences` table.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct InstanceSyncedOptions {
#[serde(default)]
pub game_options: bool,
#[serde(default)]
pub command_history: bool,
#[serde(default)]
pub multiplayer_servers: bool,
#[serde(default)]
pub creative_hotbars: bool,
#[serde(default)]
pub screenshots: bool,
#[serde(default)]
pub resource_packs: bool,
#[serde(default)]
pub data_packs: bool,
}