All checks were successful
Native code generation / deterministic (push) Successful in 15m16s
Imaging and meshing gate / native (push) Successful in 5m9s
JPEG 2000 feature / linux (push) Successful in 2m47s
Native Rust workspace compile / compile (push) Successful in 5m14s
Skia feature / linux (push) Successful in 31m28s
238 lines
8.1 KiB
Rust
238 lines
8.1 KiB
Rust
//! Capability-backed interest-list mode control.
|
|
|
|
#![allow(clippy::missing_errors_doc)]
|
|
#![allow(clippy::must_use_candidate)]
|
|
#![allow(clippy::needless_pass_by_value)]
|
|
#![allow(clippy::unnecessary_wraps)] // Fixed mapped constructor is fallible.
|
|
|
|
use crate::{Error, GridClient, Simulator};
|
|
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
|
|
use libremetaverse_types::compat::CancellationToken;
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct InterestListMessage {
|
|
mode: String,
|
|
interest_list_mode: crate::messages::linden::InterestListMode,
|
|
}
|
|
impl InterestListMessage {
|
|
pub fn new() -> Result<Self, Error> {
|
|
Ok(Self {
|
|
mode: "default".into(),
|
|
interest_list_mode: crate::messages::linden::InterestListMode::Default,
|
|
})
|
|
}
|
|
pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
|
|
let mode = map
|
|
.get("mode")
|
|
.map(|value| value.as_string())
|
|
.transpose()?
|
|
.unwrap_or_else(|| "default".into());
|
|
self.set_mode(mode);
|
|
Ok(())
|
|
}
|
|
pub fn serialize(&self) -> Result<OSDMap, Error> {
|
|
OSDMap::new_with_dictionary(std::collections::HashMap::from([(
|
|
"mode".into(),
|
|
OSD::String(self.mode.clone()),
|
|
)]))
|
|
}
|
|
pub fn interest_list_mode(&self) -> crate::messages::linden::InterestListMode {
|
|
self.interest_list_mode
|
|
}
|
|
pub fn set_interest_list_mode(&mut self, value: crate::messages::linden::InterestListMode) {
|
|
self.interest_list_mode = value;
|
|
self.mode = mode_name(value).into();
|
|
}
|
|
pub fn mode(&self) -> String {
|
|
self.mode.clone()
|
|
}
|
|
pub fn set_mode(&mut self, value: String) {
|
|
self.interest_list_mode = if matches!(
|
|
value.to_ascii_lowercase().as_str(),
|
|
"360" | "panoramic360" | "panoramic_360"
|
|
) {
|
|
crate::messages::linden::InterestListMode::Panoramic360
|
|
} else {
|
|
crate::messages::linden::InterestListMode::Default
|
|
};
|
|
self.mode = mode_name(self.interest_list_mode).into();
|
|
}
|
|
}
|
|
impl crate::interfaces::IMessage for InterestListMessage {
|
|
fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> {
|
|
InterestListMessage::deserialize(self, map)
|
|
}
|
|
fn serialize(&self) -> Result<OSDMap, Error> {
|
|
InterestListMessage::serialize(self)
|
|
}
|
|
}
|
|
fn mode_name(mode: crate::messages::linden::InterestListMode) -> &'static str {
|
|
match mode {
|
|
crate::messages::linden::InterestListMode::Default => "default",
|
|
crate::messages::linden::InterestListMode::Panoramic360 => "360",
|
|
}
|
|
}
|
|
|
|
pub(crate) struct InterestListManagerInner {
|
|
client: Arc<GridClient>,
|
|
current_mode: RwLock<crate::messages::linden::InterestListMode>,
|
|
}
|
|
#[derive(Clone)]
|
|
pub struct InterestListManager {
|
|
pub(crate) inner: Arc<InterestListManagerInner>,
|
|
}
|
|
impl InterestListManager {
|
|
pub fn new(client: GridClient) -> Result<Self, Error> {
|
|
Self::native_new(Arc::new(client))
|
|
}
|
|
pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
|
|
if let Some(inner) = client.cached_interest_list_manager_inner() {
|
|
return Ok(Self { inner });
|
|
}
|
|
let inner = Arc::new(InterestListManagerInner {
|
|
client: Arc::clone(&client),
|
|
current_mode: RwLock::new(crate::messages::linden::InterestListMode::Default),
|
|
});
|
|
Ok(Self {
|
|
inner: client.install_interest_list_manager_inner(inner),
|
|
})
|
|
}
|
|
pub fn dispose(&self) -> Result<(), Error> {
|
|
Ok(())
|
|
}
|
|
pub fn current_mode(&self) -> crate::messages::linden::InterestListMode {
|
|
*self
|
|
.inner
|
|
.current_mode
|
|
.read()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
}
|
|
pub async fn reset(&self, cancellation_token: Option<CancellationToken>) -> Result<(), Error> {
|
|
self.set_mode(
|
|
crate::messages::linden::InterestListMode::Default,
|
|
cancellation_token,
|
|
)
|
|
.await
|
|
}
|
|
pub async fn set_mode(
|
|
&self,
|
|
mode: crate::messages::linden::InterestListMode,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<(), Error> {
|
|
let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
|
|
token.throw_if_cancellation_requested()?;
|
|
let simulations = self.inner.client.network().simulators.snapshot();
|
|
let mut accepted = false;
|
|
for simulator in simulations {
|
|
if self
|
|
.set_mode_on_sim(simulator, mode, Some(token.clone()))
|
|
.await?
|
|
{
|
|
accepted = true;
|
|
}
|
|
}
|
|
if accepted || self.inner.client.network().simulators.is_empty() {
|
|
*self
|
|
.inner
|
|
.current_mode
|
|
.write()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = mode;
|
|
}
|
|
Ok(())
|
|
}
|
|
pub async fn reset_on_sim(
|
|
&self,
|
|
simulator: Simulator,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<bool, Error> {
|
|
self.set_mode_on_sim(
|
|
simulator,
|
|
crate::messages::linden::InterestListMode::Default,
|
|
cancellation_token,
|
|
)
|
|
.await
|
|
}
|
|
pub async fn set_mode_on_sim(
|
|
&self,
|
|
simulator: Simulator,
|
|
mode: crate::messages::linden::InterestListMode,
|
|
cancellation_token: Option<CancellationToken>,
|
|
) -> Result<bool, Error> {
|
|
let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
|
|
token.throw_if_cancellation_requested()?;
|
|
let Some(caps) = simulator.native_caps() else {
|
|
return Ok(false);
|
|
};
|
|
let Some(uri) = caps.capability_uri("InterestList".into())? else {
|
|
return Ok(false);
|
|
};
|
|
let mut message = InterestListMessage::new()?;
|
|
message.set_interest_list_mode(mode);
|
|
let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(message.serialize()?.snapshot()))?;
|
|
let (response, bytes) = self
|
|
.inner
|
|
.client
|
|
.native_http_caps_client()
|
|
.post_with_uri_string_bytes_cancellation_token_i_progress(
|
|
uri,
|
|
"application/llsd+xml".into(),
|
|
body,
|
|
token.clone(),
|
|
None,
|
|
)
|
|
.await?;
|
|
token.throw_if_cancellation_requested()?;
|
|
if bytes.len() > 1024 * 1024 {
|
|
return Err(Error::Argument);
|
|
}
|
|
let ok = (200..300).contains(&response.status_code);
|
|
if ok {
|
|
*self
|
|
.inner
|
|
.current_mode
|
|
.write()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = mode;
|
|
}
|
|
Ok(ok)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use libremetaverse_types::compat::CancellationTokenSource;
|
|
#[test]
|
|
fn message_round_trip() {
|
|
let mut value = InterestListMessage::new().unwrap();
|
|
value.set_interest_list_mode(crate::messages::linden::InterestListMode::Panoramic360);
|
|
let mut copy = InterestListMessage::new().unwrap();
|
|
copy.deserialize(value.serialize().unwrap()).unwrap();
|
|
assert_eq!(copy.mode(), "360");
|
|
assert_eq!(
|
|
copy.interest_list_mode(),
|
|
crate::messages::linden::InterestListMode::Panoramic360
|
|
);
|
|
}
|
|
#[tokio::test]
|
|
async fn cancelled_mode_change_does_not_commit() {
|
|
let client = GridClient::new().unwrap();
|
|
let manager = InterestListManager::new(client).unwrap();
|
|
let source = CancellationTokenSource::new();
|
|
source.cancel();
|
|
assert_eq!(
|
|
manager
|
|
.set_mode(
|
|
crate::messages::linden::InterestListMode::Panoramic360,
|
|
Some(source.token())
|
|
)
|
|
.await,
|
|
Err(Error::Cancelled)
|
|
);
|
|
assert_eq!(
|
|
manager.current_mode(),
|
|
crate::messages::linden::InterestListMode::Default
|
|
);
|
|
}
|
|
}
|