[fix](trx-client): keep multi-server rigs in audio cache
Merge per-server GetRigs updates into the shared remote rig cache instead of replacing it, so audio tasks from other servers are not torn down on each poll cycle. Add a regression test covering the multi-server cache update path. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -555,32 +555,78 @@ fn cache_remote_rigs(
|
||||
mapped_rigs: &[(String, &RigEntry)],
|
||||
) {
|
||||
if let Ok(mut guard) = config.known_rigs.lock() {
|
||||
let next = if has_short_names(config) {
|
||||
merge_known_rigs(config, &guard, mapped_rigs)
|
||||
} else {
|
||||
mapped_rigs
|
||||
.iter()
|
||||
.map(|(key, entry)| remote_rig_entry_from_snapshot(key, entry))
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Skip the Vec rebuild when the rig list is structurally unchanged.
|
||||
let unchanged = guard.len() == mapped_rigs.len()
|
||||
let unchanged = guard.len() == next.len()
|
||||
&& guard
|
||||
.iter()
|
||||
.zip(mapped_rigs.iter())
|
||||
.all(|(cached, (key, new))| {
|
||||
cached.rig_id == *key
|
||||
&& cached.display_name == new.display_name
|
||||
&& cached.state.initialized == new.state.initialized
|
||||
&& cached.audio_port == new.audio_port
|
||||
});
|
||||
.zip(next.iter())
|
||||
.all(|(cached, new)| same_remote_rig_entry(cached, new));
|
||||
if unchanged {
|
||||
return;
|
||||
}
|
||||
*guard = mapped_rigs
|
||||
*guard = next;
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_known_rigs(
|
||||
config: &RemoteClientConfig,
|
||||
current: &[RemoteRigEntry],
|
||||
mapped_rigs: &[(String, &RigEntry)],
|
||||
) -> Vec<RemoteRigEntry> {
|
||||
let owned_keys: std::collections::HashSet<String> =
|
||||
config.rig_id_to_short_name.values().cloned().collect();
|
||||
let mapped_by_key: std::collections::HashMap<&str, &RigEntry> = mapped_rigs
|
||||
.iter()
|
||||
.map(|(key, entry)| RemoteRigEntry {
|
||||
rig_id: key.clone(),
|
||||
.map(|(key, entry)| (key.as_str(), *entry))
|
||||
.collect();
|
||||
let mut merged = Vec::with_capacity(current.len().max(mapped_rigs.len()));
|
||||
let mut inserted: std::collections::HashSet<&str> = std::collections::HashSet::new();
|
||||
|
||||
for existing in current {
|
||||
if owned_keys.contains(&existing.rig_id) {
|
||||
if let Some(entry) = mapped_by_key.get(existing.rig_id.as_str()) {
|
||||
merged.push(remote_rig_entry_from_snapshot(&existing.rig_id, entry));
|
||||
inserted.insert(existing.rig_id.as_str());
|
||||
}
|
||||
} else {
|
||||
merged.push(existing.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for (key, entry) in mapped_rigs {
|
||||
if !inserted.contains(key.as_str()) {
|
||||
merged.push(remote_rig_entry_from_snapshot(key, entry));
|
||||
}
|
||||
}
|
||||
|
||||
merged
|
||||
}
|
||||
|
||||
fn remote_rig_entry_from_snapshot(key: &str, entry: &RigEntry) -> RemoteRigEntry {
|
||||
RemoteRigEntry {
|
||||
rig_id: key.to_string(),
|
||||
display_name: entry.display_name.clone(),
|
||||
state: entry.state.clone(),
|
||||
audio_port: entry.audio_port,
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
fn same_remote_rig_entry(left: &RemoteRigEntry, right: &RemoteRigEntry) -> bool {
|
||||
left.rig_id == right.rig_id
|
||||
&& left.display_name == right.display_name
|
||||
&& left.state.initialized == right.state.initialized
|
||||
&& left.audio_port == right.audio_port
|
||||
}
|
||||
|
||||
fn selected_rig_id(config: &RemoteClientConfig) -> Option<String> {
|
||||
config.selected_rig_id.lock().ok().and_then(|g| g.clone())
|
||||
}
|
||||
@@ -1197,4 +1243,47 @@ mod tests {
|
||||
Some("default-rig".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_remote_rigs_keeps_other_server_entries() {
|
||||
let (spectrum_tx, _spectrum_rx) = watch::channel(SharedSpectrum::default());
|
||||
let known_rigs = Arc::new(Mutex::new(vec![trx_frontend::RemoteRigEntry {
|
||||
rig_id: "lidzbark-vhf".to_string(),
|
||||
display_name: Some("Lidzbark VHF".to_string()),
|
||||
state: sample_snapshot(),
|
||||
audio_port: Some(4531),
|
||||
}]));
|
||||
let config = RemoteClientConfig {
|
||||
addr: "127.0.0.1:4530".to_string(),
|
||||
token: None,
|
||||
selected_rig_id: Arc::new(Mutex::new(None)),
|
||||
known_rigs: known_rigs.clone(),
|
||||
poll_interval: Duration::from_millis(500),
|
||||
spectrum: Arc::new(spectrum_tx),
|
||||
server_connected: Arc::new(AtomicBool::new(false)),
|
||||
rig_states: Arc::new(RwLock::new(HashMap::new())),
|
||||
rig_spectrums: Arc::new(RwLock::new(HashMap::new())),
|
||||
rig_id_to_short_name: HashMap::from([(Some("hf".to_string()), "gdansk".to_string())]),
|
||||
short_name_to_rig_id: Arc::new(RwLock::new(HashMap::new())),
|
||||
};
|
||||
let snapshot = sample_snapshot();
|
||||
let rigs = vec![RigEntry {
|
||||
rig_id: "hf".to_string(),
|
||||
display_name: Some("Gdansk HF".to_string()),
|
||||
state: snapshot,
|
||||
audio_port: Some(4532),
|
||||
}];
|
||||
let mapped = vec![("gdansk".to_string(), &rigs[0])];
|
||||
|
||||
super::cache_remote_rigs(&config, &rigs, &mapped);
|
||||
|
||||
let cached = known_rigs.lock().expect("known rigs lock");
|
||||
assert_eq!(cached.len(), 2);
|
||||
assert!(cached.iter().any(|entry| entry.rig_id == "lidzbark-vhf"));
|
||||
assert!(cached.iter().any(|entry| {
|
||||
entry.rig_id == "gdansk"
|
||||
&& entry.display_name.as_deref() == Some("Gdansk HF")
|
||||
&& entry.audio_port == Some(4532)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user