Skip to main content

Vine/Server/Notification/
UpdateScmGroup.rs

1//! Cocoon → `update_scm_group` notification.
2//! Fire-and-forget resource-state update for an SCM group. Two side effects:
3//! 1. Persist the snapshot via `VineHost::UpdateScmGroupMarkers` so the
4//!    boot-time replay (`sky:replay-events`) can re-emit it for late
5//!    subscribers (without this, only live arrivals are visible).
6//! 2. `sky://scm/updateGroup` renderer event so the SCM viewlet updates without
7//!    waiting for a request/response round-trip.
8//!
9//! `group_handle` is `"<scm_handle>/<group_id>"` per `ScmNamespace.ts:77`.
10//! Splits on `/` to expose a flat `groupId` for the renderer payload;
11//! legacy `provider_id`/`group_id` flat keys are probed as fallback.
12
13use serde_json::{Value, json};
14
15use crate::{Host::VineHost, dev_log};
16
17pub async fn UpdateScmGroup(Host:&dyn VineHost, Parameter:&Value) {
18	let ScmHandle = Parameter
19		.get("scmHandle")
20		.or_else(|| Parameter.get("scm_handle"))
21		.and_then(Value::as_u64)
22		.map(|H| H as u32);
23
24	let GroupHandle = Parameter
25		.get("groupHandle")
26		.or_else(|| Parameter.get("group_handle"))
27		.and_then(Value::as_str)
28		.unwrap_or("")
29		.to_string();
30
31	let LegacyProviderId = Parameter
32		.get("providerId")
33		.or_else(|| Parameter.get("provider_id"))
34		.and_then(Value::as_str)
35		.unwrap_or("")
36		.to_string();
37
38	let LegacyGroupId = Parameter
39		.get("groupId")
40		.or_else(|| Parameter.get("group_id"))
41		.and_then(Value::as_str)
42		.unwrap_or("")
43		.to_string();
44
45	let ResourceStates = Parameter
46		.get("resourceStates")
47		.or_else(|| Parameter.get("resource_states"))
48		.cloned()
49		.unwrap_or_else(|| Value::Array(Vec::new()));
50
51	// Split `"<scm_handle>/<group_id>"` into its components.
52	let (HandleFromString, GroupIdFromHandle) = match GroupHandle.split_once('/') {
53		Some((H, G)) => (H.parse::<u32>().ok(), G.to_string()),
54
55		None => (None, String::new()),
56	};
57
58	let ResolvedScmHandle = ScmHandle.or(HandleFromString);
59
60	let ResolvedGroupId = if !GroupIdFromHandle.is_empty() {
61		GroupIdFromHandle
62	} else if !LegacyGroupId.is_empty() {
63		LegacyGroupId
64	} else {
65		String::new()
66	};
67
68	if ResolvedScmHandle.is_none() && LegacyProviderId.is_empty() {
69		dev_log!(
70			"grpc",
71			"[ScmGroup] skip: missing scm_handle / provider_id (group_handle={:?} group={:?})",
72			GroupHandle,
73			ResolvedGroupId
74		);
75
76		return;
77	}
78
79	if ResolvedGroupId.is_empty() {
80		dev_log!(
81			"grpc",
82			"[ScmGroup] skip: missing group_id (scm_handle={:?} group_handle={:?})",
83			ResolvedScmHandle,
84			GroupHandle
85		);
86
87		return;
88	}
89
90	// Persist snapshot for boot-time replay.
91	if let Some(Handle) = ResolvedScmHandle {
92		Host.UpdateScmGroupMarkers(Handle, &ResolvedGroupId, &ResourceStates);
93	}
94
95	Host.EmitToRenderer(
96		"sky://scm/updateGroup",
97		json!({
98			"scmHandle": ResolvedScmHandle,
99			"providerId": &LegacyProviderId,
100			"groupHandle": &GroupHandle,
101			"groupId": &ResolvedGroupId,
102			"resourceStates": ResourceStates,
103		}),
104	);
105
106	dev_log!(
107		"grpc",
108		"[ScmGroup] scm_handle={:?} group={} resources={}",
109		ResolvedScmHandle,
110		ResolvedGroupId,
111		ResourceStates.as_array().map(Vec::len).unwrap_or(0)
112	);
113}