mirror of https://github.com/synctv-org/synctv
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
7.1 KiB
Rust
203 lines
7.1 KiB
Rust
use std::collections::BTreeSet;
|
|
|
|
use synctv_proto::client as client_proto;
|
|
|
|
use crate::ApiError;
|
|
|
|
pub fn select_room_settings_patch(
|
|
mut source: client_proto::RoomSettingsPatch,
|
|
paths: &[String],
|
|
) -> Result<client_proto::RoomSettingsPatch, ApiError> {
|
|
if paths.is_empty() {
|
|
return Err(ApiError::InvalidInput(
|
|
"update_mask.paths must not be empty".to_string(),
|
|
));
|
|
}
|
|
|
|
let defaults = synctv_core::models::RoomSettings::default();
|
|
let mut selected = client_proto::RoomSettingsPatch::default();
|
|
let mut seen = BTreeSet::new();
|
|
|
|
macro_rules! select_scalar {
|
|
($field:ident, $default:expr) => {{
|
|
selected.$field = Some(source.$field.take().unwrap_or($default));
|
|
}};
|
|
}
|
|
|
|
for path in paths {
|
|
if path.is_empty() {
|
|
return Err(ApiError::InvalidInput(
|
|
"update_mask paths must not be empty".to_string(),
|
|
));
|
|
}
|
|
if !seen.insert(path.as_str()) {
|
|
return Err(ApiError::InvalidInput(format!(
|
|
"duplicate update_mask path '{path}'"
|
|
)));
|
|
}
|
|
|
|
match path.as_str() {
|
|
"allow_guest_join" => {
|
|
select_scalar!(allow_guest_join, defaults.allow_guest_join.0);
|
|
}
|
|
"max_members" => select_scalar!(max_members, defaults.max_members.0),
|
|
"require_approval" => {
|
|
select_scalar!(require_approval, defaults.require_approval.0);
|
|
}
|
|
"allow_auto_join" => {
|
|
select_scalar!(allow_auto_join, defaults.allow_auto_join.0);
|
|
}
|
|
"chat_enabled" => select_scalar!(chat_enabled, defaults.chat_enabled.0),
|
|
"voice_chat_enabled" => {
|
|
select_scalar!(voice_chat_enabled, defaults.voice_chat_enabled.0);
|
|
}
|
|
"p2p_media_enabled" => {
|
|
select_scalar!(p2p_media_enabled, defaults.p2p_media_enabled.0);
|
|
}
|
|
"auto_play.enabled" => {
|
|
let source = source.auto_play.get_or_insert_default();
|
|
selected.auto_play.get_or_insert_default().enabled = Some(
|
|
source
|
|
.enabled
|
|
.take()
|
|
.unwrap_or(defaults.auto_play.value.enabled),
|
|
);
|
|
}
|
|
"auto_play.mode" => {
|
|
let source = source.auto_play.get_or_insert_default();
|
|
selected.auto_play.get_or_insert_default().mode = Some(
|
|
source
|
|
.mode
|
|
.take()
|
|
.unwrap_or(default_play_mode(&defaults.auto_play.value.mode)),
|
|
);
|
|
}
|
|
"auto_play.delay" => {
|
|
let source = source.auto_play.get_or_insert_default();
|
|
selected.auto_play.get_or_insert_default().delay = Some(
|
|
source
|
|
.delay
|
|
.take()
|
|
.unwrap_or(defaults.auto_play.value.delay),
|
|
);
|
|
}
|
|
"admin_added_permissions" => {
|
|
select_scalar!(admin_added_permissions, defaults.admin_added_permissions.0);
|
|
}
|
|
"admin_removed_permissions" => select_scalar!(
|
|
admin_removed_permissions,
|
|
defaults.admin_removed_permissions.0
|
|
),
|
|
"member_added_permissions" => select_scalar!(
|
|
member_added_permissions,
|
|
defaults.member_added_permissions.0
|
|
),
|
|
"member_removed_permissions" => select_scalar!(
|
|
member_removed_permissions,
|
|
defaults.member_removed_permissions.0
|
|
),
|
|
"guest_added_permissions" => {
|
|
select_scalar!(guest_added_permissions, defaults.guest_added_permissions.0);
|
|
}
|
|
"guest_removed_permissions" => select_scalar!(
|
|
guest_removed_permissions,
|
|
defaults.guest_removed_permissions.0
|
|
),
|
|
_ => {
|
|
return Err(ApiError::InvalidInput(format!(
|
|
"unsupported update_mask path '{path}'"
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(selected)
|
|
}
|
|
|
|
fn default_play_mode(mode: &synctv_core::models::PlayMode) -> i32 {
|
|
(match mode {
|
|
synctv_core::models::PlayMode::Sequential => client_proto::PlayMode::Sequential,
|
|
synctv_core::models::PlayMode::RepeatOne => client_proto::PlayMode::RepeatOne,
|
|
synctv_core::models::PlayMode::RepeatAll => client_proto::PlayMode::RepeatAll,
|
|
synctv_core::models::PlayMode::Shuffle => client_proto::PlayMode::Shuffle,
|
|
}) as i32
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::select_room_settings_patch;
|
|
|
|
#[test]
|
|
fn selects_only_masked_room_settings_leaves() {
|
|
let patch = select_room_settings_patch(
|
|
synctv_proto::client::RoomSettingsPatch {
|
|
require_approval: Some(true),
|
|
chat_enabled: Some(false),
|
|
auto_play: Some(synctv_proto::client::AutoPlaySettingsPatch {
|
|
mode: Some(synctv_proto::client::PlayMode::Shuffle as i32),
|
|
..Default::default()
|
|
}),
|
|
..Default::default()
|
|
},
|
|
&["require_approval".to_string(), "auto_play.mode".to_string()],
|
|
)
|
|
.expect("valid room settings mask");
|
|
|
|
assert_eq!(patch.require_approval, Some(true));
|
|
assert_eq!(patch.chat_enabled, None);
|
|
assert_eq!(
|
|
patch.auto_play.expect("auto play").mode,
|
|
Some(synctv_proto::client::PlayMode::Shuffle as i32)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unset_room_setting_uses_server_default() {
|
|
let patch = select_room_settings_patch(
|
|
synctv_proto::client::RoomSettingsPatch::default(),
|
|
&["allow_auto_join".to_string()],
|
|
)
|
|
.expect("unset room setting");
|
|
|
|
assert_eq!(
|
|
patch.allow_auto_join,
|
|
Some(
|
|
synctv_core::models::RoomSettings::default()
|
|
.allow_auto_join
|
|
.0
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn selects_room_realtime_capabilities_independently() {
|
|
let patch = select_room_settings_patch(
|
|
synctv_proto::client::RoomSettingsPatch {
|
|
voice_chat_enabled: Some(false),
|
|
p2p_media_enabled: Some(false),
|
|
..Default::default()
|
|
},
|
|
&["voice_chat_enabled".to_string()],
|
|
)
|
|
.expect("voice capability mask should be valid");
|
|
|
|
assert_eq!(patch.voice_chat_enabled, Some(false));
|
|
assert_eq!(patch.p2p_media_enabled, None);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_invalid_room_settings_masks() {
|
|
for paths in [
|
|
Vec::<String>::new(),
|
|
vec!["auto_play".to_string()],
|
|
vec!["chat_enabled".to_string(), "chat_enabled".to_string()],
|
|
] {
|
|
assert!(select_room_settings_patch(
|
|
synctv_proto::client::RoomSettingsPatch::default(),
|
|
&paths
|
|
)
|
|
.is_err());
|
|
}
|
|
}
|
|
}
|