Implement remaining TestClient service commands (#94)
This commit is contained in:
@@ -10,9 +10,10 @@
|
||||
use crate::client_core::ClientWeakHandle;
|
||||
use crate::packet_catalog::PacketType;
|
||||
use crate::packets::{
|
||||
AvatarAnimationPacket, AvatarPickerRequestPacket, AvatarPropertiesRequestPacket,
|
||||
ClassifiedInfoRequestPacket, GenericMessagePacket, GenericMessagePacketParamListBlock,
|
||||
TrackAgentPacket, UUIDNameRequestPacket, UUIDNameRequestPacketUUIDNameBlockBlock,
|
||||
AvatarAnimationPacket, AvatarInterestsReplyPacket, AvatarPickerRequestPacket,
|
||||
AvatarPropertiesRequestPacket, ClassifiedInfoRequestPacket, GenericMessagePacket,
|
||||
GenericMessagePacketParamListBlock, TrackAgentPacket, UUIDNameRequestPacket,
|
||||
UUIDNameRequestPacketUUIDNameBlockBlock,
|
||||
};
|
||||
use crate::{Error, GridClient};
|
||||
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
|
||||
@@ -121,6 +122,27 @@ fn handle_avatar_animation(inner: &AvatarManagerInner, data: Vec<u8>) {
|
||||
});
|
||||
}
|
||||
|
||||
fn decode_avatar_interests(data: Vec<u8>, avatar_id: UUID) -> Option<crate::AvatarInterests> {
|
||||
let mut offset = 0;
|
||||
let packet = AvatarInterestsReplyPacket::new_with_bytes_int32(data, &mut offset).ok()?;
|
||||
if packet.agent_data.avatar_id != avatar_id {
|
||||
return None;
|
||||
}
|
||||
Some(crate::AvatarInterests {
|
||||
languages_text: String::from_utf8_lossy(&packet.properties_data.languages_text)
|
||||
.trim_end_matches('\0')
|
||||
.to_owned(),
|
||||
skills_mask: packet.properties_data.skills_mask,
|
||||
skills_text: String::from_utf8_lossy(&packet.properties_data.skills_text)
|
||||
.trim_end_matches('\0')
|
||||
.to_owned(),
|
||||
want_to_mask: packet.properties_data.want_to_mask,
|
||||
want_to_text: String::from_utf8_lossy(&packet.properties_data.want_to_text)
|
||||
.trim_end_matches('\0')
|
||||
.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
impl AvatarAnimationEventArgs {
|
||||
pub(crate) fn native_new(avatar_id: UUID, animations: Vec<Animation>) -> Result<Self, Error> {
|
||||
if animations.len() > 256 {
|
||||
@@ -476,6 +498,53 @@ impl AvatarManager {
|
||||
)
|
||||
}
|
||||
|
||||
/// Requests the legacy profile-interest block and waits for the matching
|
||||
/// simulator reply.
|
||||
///
|
||||
/// The modern `AgentProfile` capability includes groups and picks but does
|
||||
/// not include the five interest fields. Consumers that need a complete
|
||||
/// editable profile can pair this request with [`Self::request_agent_profile`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if there is no active simulator, the request cannot be
|
||||
/// encoded or sent, the matching reply channel closes, or cancellation is
|
||||
/// requested.
|
||||
pub async fn request_avatar_interests(
|
||||
&self,
|
||||
avatar_id: UUID,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
) -> Result<crate::AvatarInterests, Error> {
|
||||
let network = self.live_network()?;
|
||||
let token = cancellation_token.unwrap_or_default();
|
||||
token.throw_if_cancellation_requested()?;
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel();
|
||||
let sender = Arc::new(Mutex::new(Some(sender)));
|
||||
let callback = Arc::clone(&sender);
|
||||
let subscription = network.subscribe_raw_packet(Arc::new(move |event| {
|
||||
if event.packet_type != PacketType::AvatarInterestsReply {
|
||||
return;
|
||||
}
|
||||
let Some(interests) = decode_avatar_interests(event.data, avatar_id) else {
|
||||
return;
|
||||
};
|
||||
if let Some(sender) = callback
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take()
|
||||
{
|
||||
let _ = sender.send(interests);
|
||||
}
|
||||
}));
|
||||
self.native_request_avatar_properties(avatar_id)?;
|
||||
let result = tokio::select! {
|
||||
value = receiver => value.map_err(|_| Error::InvalidOperation),
|
||||
() = token.cancelled() => Err(Error::Cancelled),
|
||||
};
|
||||
drop(subscription);
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn native_request_avatar_name_search(
|
||||
&self,
|
||||
name: String,
|
||||
@@ -1018,4 +1087,25 @@ mod tests {
|
||||
|
||||
assert_eq!(*received.lock().expect("event lock"), [0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avatar_interests_reply_is_correlated_and_decoded() {
|
||||
let avatar_id = UUID::random().expect("avatar id");
|
||||
let mut packet = AvatarInterestsReplyPacket::new_with_constructor().expect("packet");
|
||||
packet.agent_data.avatar_id = avatar_id;
|
||||
packet.properties_data.languages_text = b"Rust, LSL\0".to_vec();
|
||||
packet.properties_data.skills_mask = 0x12;
|
||||
packet.properties_data.skills_text = b"Protocol testing\0".to_vec();
|
||||
packet.properties_data.want_to_mask = 0x34;
|
||||
packet.properties_data.want_to_text = b"Build things\0".to_vec();
|
||||
let bytes = packet.to_bytes_with_method().expect("packet bytes");
|
||||
|
||||
assert!(decode_avatar_interests(bytes.clone(), UUID::zero()).is_none());
|
||||
let interests = decode_avatar_interests(bytes, avatar_id).expect("matching reply");
|
||||
assert_eq!(interests.languages_text, "Rust, LSL");
|
||||
assert_eq!(interests.skills_mask, 0x12);
|
||||
assert_eq!(interests.skills_text, "Protocol testing");
|
||||
assert_eq!(interests.want_to_mask, 0x34);
|
||||
assert_eq!(interests.want_to_text, "Build things");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user