Implement social and group managers (#72)
This commit is contained in:
@@ -126,6 +126,8 @@ struct ClientRuntime {
|
||||
grid_manager: Mutex<Option<Arc<crate::grid_manager::GridManagerInner>>>,
|
||||
directory_manager: Mutex<Option<Arc<crate::directory_manager::DirectoryManagerInner>>>,
|
||||
interest_list_manager: Mutex<Option<Arc<crate::interest_list::InterestListManagerInner>>>,
|
||||
friends_manager: Mutex<Option<Arc<crate::friends_manager::FriendsManagerInner>>>,
|
||||
group_manager: Mutex<Option<Arc<crate::group_manager::GroupManagerInner>>>,
|
||||
appearance_manager: Mutex<Option<Arc<crate::appearance_manager::AppearanceManagerInner>>>,
|
||||
avatar_manager: Mutex<Option<Arc<crate::avatar_manager::AvatarManagerInner>>>,
|
||||
animesh_manager: Mutex<Option<Arc<crate::animesh_runtime::AnimeshManagerInner>>>,
|
||||
@@ -168,6 +170,8 @@ impl ClientRuntime {
|
||||
grid_manager: Mutex::new(None),
|
||||
directory_manager: Mutex::new(None),
|
||||
interest_list_manager: Mutex::new(None),
|
||||
friends_manager: Mutex::new(None),
|
||||
group_manager: Mutex::new(None),
|
||||
appearance_manager: Mutex::new(None),
|
||||
avatar_manager: Mutex::new(None),
|
||||
animesh_manager: Mutex::new(None),
|
||||
@@ -270,6 +274,14 @@ impl ClientRuntime {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.friends_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.group_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.http_caps_client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
@@ -977,6 +989,82 @@ impl GridClient {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
|
||||
}
|
||||
pub(crate) fn cached_friends_manager_inner(
|
||||
&self,
|
||||
) -> Option<Arc<crate::friends_manager::FriendsManagerInner>> {
|
||||
self.runtime
|
||||
.friends_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone()
|
||||
}
|
||||
pub(crate) fn install_friends_manager_inner(
|
||||
&self,
|
||||
candidate: Arc<crate::friends_manager::FriendsManagerInner>,
|
||||
) -> Arc<crate::friends_manager::FriendsManagerInner> {
|
||||
let mut cached = self
|
||||
.runtime
|
||||
.friends_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(inner) = cached.as_ref() {
|
||||
return Arc::clone(inner);
|
||||
}
|
||||
*cached = Some(Arc::clone(&candidate));
|
||||
candidate
|
||||
}
|
||||
pub(crate) fn native_friends(&self) -> Result<crate::FriendsManager, crate::Error> {
|
||||
if let Some(inner) = self.cached_friends_manager_inner() {
|
||||
Ok(crate::FriendsManager { inner })
|
||||
} else {
|
||||
crate::friends_manager::FriendsManager::native_new(Arc::new(self.clone()))
|
||||
}
|
||||
}
|
||||
pub(crate) fn native_set_friends(&self, value: crate::FriendsManager) {
|
||||
*self
|
||||
.runtime
|
||||
.friends_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
|
||||
}
|
||||
pub(crate) fn cached_group_manager_inner(
|
||||
&self,
|
||||
) -> Option<Arc<crate::group_manager::GroupManagerInner>> {
|
||||
self.runtime
|
||||
.group_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone()
|
||||
}
|
||||
pub(crate) fn install_group_manager_inner(
|
||||
&self,
|
||||
candidate: Arc<crate::group_manager::GroupManagerInner>,
|
||||
) -> Arc<crate::group_manager::GroupManagerInner> {
|
||||
let mut cached = self
|
||||
.runtime
|
||||
.group_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(inner) = cached.as_ref() {
|
||||
return Arc::clone(inner);
|
||||
}
|
||||
*cached = Some(Arc::clone(&candidate));
|
||||
candidate
|
||||
}
|
||||
pub(crate) fn native_groups(&self) -> crate::GroupManager {
|
||||
if let Some(inner) = self.cached_group_manager_inner() {
|
||||
crate::GroupManager { inner }
|
||||
} else {
|
||||
crate::group_manager::GroupManager::native_new(Arc::new(self.clone()))
|
||||
}
|
||||
}
|
||||
pub(crate) fn native_set_groups(&self, value: crate::GroupManager) {
|
||||
*self
|
||||
.runtime
|
||||
.group_manager
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner);
|
||||
}
|
||||
|
||||
pub(crate) fn native_set_caps_rate_limiter(
|
||||
&mut self,
|
||||
|
||||
886
crates/libremetaverse/src/friends_manager.rs
Normal file
886
crates/libremetaverse/src/friends_manager.rs
Normal file
@@ -0,0 +1,886 @@
|
||||
//! Friend presence, rights, offers, calling cards, and map tracking.
|
||||
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::must_use_candidate)]
|
||||
#![allow(clippy::needless_pass_by_value)]
|
||||
#![allow(clippy::inherent_to_string)] // Fixed mapped CLR ToString method.
|
||||
|
||||
use crate::agent_manager::EventRegistry;
|
||||
use crate::network_manager::RawPacketReceivedEventArgs;
|
||||
use crate::packet_catalog::{GeneratedPacket, PacketType};
|
||||
use crate::{Error, FriendRights, GridClient, InstantMessageDialog};
|
||||
use libremetaverse_structured_data::{OSD, OSDFormat, OSDParser};
|
||||
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription};
|
||||
use libremetaverse_types::{FolderType, UUID, Vector3};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
const MAX_FRIENDS: usize = 100_000;
|
||||
|
||||
fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
|
||||
value
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
fn read<T>(value: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
|
||||
value
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
fn write<T>(value: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
|
||||
value
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
fn wire_string(value: &[u8]) -> String {
|
||||
String::from_utf8_lossy(value.split(|b| *b == 0).next().unwrap_or_default()).into_owned()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FriendInfo {
|
||||
id: UUID,
|
||||
name: String,
|
||||
online: bool,
|
||||
their_rights: FriendRights,
|
||||
my_rights: FriendRights,
|
||||
}
|
||||
impl FriendInfo {
|
||||
pub(crate) fn native_new(
|
||||
id: UUID,
|
||||
their_rights: FriendRights,
|
||||
my_rights: FriendRights,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
name: String::new(),
|
||||
online: false,
|
||||
their_rights,
|
||||
my_rights,
|
||||
}
|
||||
}
|
||||
pub fn uuid(&self) -> UUID {
|
||||
self.id
|
||||
}
|
||||
pub fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
pub fn set_name(&mut self, value: String) {
|
||||
self.name = value;
|
||||
}
|
||||
pub fn is_online(&self) -> bool {
|
||||
self.online
|
||||
}
|
||||
pub fn set_is_online(&mut self, value: bool) {
|
||||
self.online = value;
|
||||
}
|
||||
pub fn their_friend_rights(&self) -> FriendRights {
|
||||
self.their_rights
|
||||
}
|
||||
pub fn set_their_friend_rights(&mut self, value: FriendRights) {
|
||||
self.their_rights = value;
|
||||
}
|
||||
pub fn my_friend_rights(&self) -> FriendRights {
|
||||
self.my_rights
|
||||
}
|
||||
pub fn set_my_friend_rights(&mut self, value: FriendRights) {
|
||||
self.my_rights = value;
|
||||
}
|
||||
pub fn can_see_me_online(&self) -> bool {
|
||||
self.their_rights.0 & FriendRights::CAN_SEE_ONLINE.0 != 0
|
||||
}
|
||||
pub fn set_can_see_me_online(&mut self, value: bool) {
|
||||
set_right(&mut self.their_rights, FriendRights::CAN_SEE_ONLINE, value);
|
||||
if !value {
|
||||
set_right(&mut self.their_rights, FriendRights::CAN_SEE_ON_MAP, false);
|
||||
}
|
||||
}
|
||||
pub fn can_see_me_on_map(&self) -> bool {
|
||||
self.their_rights.0 & FriendRights::CAN_SEE_ON_MAP.0 != 0
|
||||
}
|
||||
pub fn set_can_see_me_on_map(&mut self, value: bool) {
|
||||
if self.can_see_me_online() {
|
||||
set_right(&mut self.their_rights, FriendRights::CAN_SEE_ON_MAP, value);
|
||||
}
|
||||
}
|
||||
pub fn can_modify_my_objects(&self) -> bool {
|
||||
self.their_rights.0 & FriendRights::CAN_MODIFY_OBJECTS.0 != 0
|
||||
}
|
||||
pub fn set_can_modify_my_objects(&mut self, value: bool) {
|
||||
set_right(
|
||||
&mut self.their_rights,
|
||||
FriendRights::CAN_MODIFY_OBJECTS,
|
||||
value,
|
||||
);
|
||||
}
|
||||
pub fn can_see_them_online(&self) -> bool {
|
||||
self.my_rights.0 & FriendRights::CAN_SEE_ONLINE.0 != 0
|
||||
}
|
||||
pub fn set_can_see_them_online(&mut self, value: bool) {
|
||||
set_right(&mut self.my_rights, FriendRights::CAN_SEE_ONLINE, value);
|
||||
}
|
||||
pub fn can_see_them_on_map(&self) -> bool {
|
||||
self.my_rights.0 & FriendRights::CAN_SEE_ON_MAP.0 != 0
|
||||
}
|
||||
pub fn set_can_see_them_on_map(&mut self, value: bool) {
|
||||
set_right(&mut self.my_rights, FriendRights::CAN_SEE_ON_MAP, value);
|
||||
}
|
||||
pub fn can_modify_their_objects(&self) -> bool {
|
||||
self.my_rights.0 & FriendRights::CAN_MODIFY_OBJECTS.0 != 0
|
||||
}
|
||||
pub fn set_can_modify_their_objects(&mut self, value: bool) {
|
||||
set_right(&mut self.my_rights, FriendRights::CAN_MODIFY_OBJECTS, value);
|
||||
}
|
||||
pub fn to_string(&self) -> String {
|
||||
let who = if self.name.is_empty() {
|
||||
self.id.to_string()
|
||||
} else {
|
||||
self.name.clone()
|
||||
};
|
||||
format!(
|
||||
"{who} (Their Rights: {}, My Rights: {})",
|
||||
self.their_rights.0, self.my_rights.0
|
||||
)
|
||||
}
|
||||
}
|
||||
fn set_right(rights: &mut FriendRights, flag: FriendRights, enabled: bool) {
|
||||
if enabled {
|
||||
rights.0 |= flag.0;
|
||||
} else {
|
||||
rights.0 &= !flag.0;
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! simple_event {
|
||||
($name:ident { $($field:ident : $ty:ty => $getter:ident),+ $(,)? }) => {
|
||||
#[derive(Clone)] pub struct $name { $( $field: $ty ),+ }
|
||||
impl $name {
|
||||
pub fn new($($field: $ty),+) -> Result<Self, Error> { Ok(Self { $($field),+ }) }
|
||||
$(pub fn $getter(&self) -> $ty { self.$field.clone() })+
|
||||
}
|
||||
};
|
||||
}
|
||||
simple_event!(CallingCardOfferedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, transaction_id: UUID => transaction_id });
|
||||
simple_event!(FriendFoundReplyEventArgs { agent_id: UUID => agent_id, region_handle: u64 => region_handle, location: Vector3 => location });
|
||||
simple_event!(FriendInfoEventArgs { friend: FriendInfo => friend });
|
||||
simple_event!(FriendNamesEventArgs { names: HashMap<UUID, String> => names });
|
||||
simple_event!(FriendsReadyEventArgs { count: i32 => count });
|
||||
simple_event!(FriendshipOfferedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, im_session_id: UUID => session_id });
|
||||
simple_event!(FriendshipResponseEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, accepted: bool => accepted });
|
||||
simple_event!(FriendshipTerminatedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name });
|
||||
|
||||
#[derive(Default)]
|
||||
struct FriendEvents {
|
||||
calling_card: EventRegistry<CallingCardOfferedEventArgs>,
|
||||
found: EventRegistry<FriendFoundReplyEventArgs>,
|
||||
names: EventRegistry<FriendNamesEventArgs>,
|
||||
offline: EventRegistry<FriendInfoEventArgs>,
|
||||
online: EventRegistry<FriendInfoEventArgs>,
|
||||
rights: EventRegistry<FriendInfoEventArgs>,
|
||||
offered: EventRegistry<FriendshipOfferedEventArgs>,
|
||||
response: EventRegistry<FriendshipResponseEventArgs>,
|
||||
terminated: EventRegistry<FriendshipTerminatedEventArgs>,
|
||||
ready: EventRegistry<FriendsReadyEventArgs>,
|
||||
}
|
||||
pub(crate) struct FriendsManagerInner {
|
||||
client: Arc<GridClient>,
|
||||
friends: RwLock<HashMap<UUID, FriendInfo>>,
|
||||
requests: RwLock<HashMap<UUID, UUID>>,
|
||||
events: FriendEvents,
|
||||
subscriptions: Mutex<Vec<Subscription>>,
|
||||
login_callback: Mutex<Option<crate::NetworkManagerLoginResponseCallback>>,
|
||||
disposed: AtomicBool,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct FriendsManager {
|
||||
pub(crate) inner: Arc<FriendsManagerInner>,
|
||||
}
|
||||
|
||||
impl FriendsManager {
|
||||
pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
|
||||
if let Some(inner) = client.cached_friends_manager_inner() {
|
||||
return Ok(Self { inner });
|
||||
}
|
||||
let inner = Arc::new(FriendsManagerInner {
|
||||
client: Arc::clone(&client),
|
||||
friends: RwLock::new(HashMap::new()),
|
||||
requests: RwLock::new(HashMap::new()),
|
||||
events: FriendEvents::default(),
|
||||
subscriptions: Mutex::new(Vec::new()),
|
||||
login_callback: Mutex::new(None),
|
||||
disposed: AtomicBool::new(false),
|
||||
});
|
||||
let weak = Arc::downgrade(&inner);
|
||||
mutex(&inner.subscriptions).push(client.network().subscribe_raw_packet(Arc::new(
|
||||
move |e| {
|
||||
if let Some(inner) = weak.upgrade() {
|
||||
inner.handle_packet(e);
|
||||
}
|
||||
},
|
||||
)));
|
||||
let weak = Arc::downgrade(&inner);
|
||||
let mut callback_client = (*client).clone();
|
||||
mutex(&inner.subscriptions).push(callback_client.self_().subscribe_im(Arc::new(
|
||||
move |e| {
|
||||
if let Some(inner) = weak.upgrade() {
|
||||
inner.handle_im(e);
|
||||
}
|
||||
},
|
||||
)));
|
||||
let weak = Arc::downgrade(&inner);
|
||||
let callback = crate::NetworkManagerLoginResponseCallback::from_handler(
|
||||
move |success, _, _, _, response| {
|
||||
if success && let Some(inner) = weak.upgrade() {
|
||||
inner.load_login(response);
|
||||
}
|
||||
},
|
||||
);
|
||||
client.network().native_register_login_response_callback(
|
||||
callback.clone(),
|
||||
Some(vec!["buddy-list".into()]),
|
||||
)?;
|
||||
*mutex(&inner.login_callback) = Some(callback);
|
||||
Ok(Self {
|
||||
inner: client.install_friends_manager_inner(inner),
|
||||
})
|
||||
}
|
||||
fn ids(&self) -> (UUID, UUID) {
|
||||
let mut c = (*self.inner.client).clone();
|
||||
let a = c.self_();
|
||||
(a.agent_id(), a.session_id())
|
||||
}
|
||||
fn send<T: GeneratedPacket>(&self, p: &T, ty: PacketType) -> Result<(), Error> {
|
||||
if self.inner.disposed.load(Ordering::Acquire) {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
let sim = self
|
||||
.inner
|
||||
.client
|
||||
.network()
|
||||
.current_sim()
|
||||
.ok_or(Error::InvalidOperation)?;
|
||||
let bytes = p.encode_packet()?;
|
||||
let result = sim.native_send_packet_data(
|
||||
bytes.clone(),
|
||||
i32::try_from(bytes.len()).map_err(|_| Error::Argument)?,
|
||||
ty,
|
||||
false,
|
||||
);
|
||||
match result {
|
||||
Err(Error::InvalidOperation) if !sim.connected() => Ok(()),
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
pub fn friend_list(&self) -> HashMap<UUID, FriendInfo> {
|
||||
read(&self.inner.friends).clone()
|
||||
}
|
||||
pub fn friend_requests(&self) -> HashMap<UUID, UUID> {
|
||||
read(&self.inner.requests).clone()
|
||||
}
|
||||
pub fn dispose(&self) -> Result<(), Error> {
|
||||
self.inner.dispose();
|
||||
Ok(())
|
||||
}
|
||||
pub fn subscribe_calling_card_offered(
|
||||
&self,
|
||||
h: EventHandler<CallingCardOfferedEventArgs>,
|
||||
) -> Subscription {
|
||||
self.inner.events.calling_card.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friend_found_reply(
|
||||
&self,
|
||||
h: EventHandler<FriendFoundReplyEventArgs>,
|
||||
) -> Subscription {
|
||||
self.inner.events.found.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friend_names(&self, h: EventHandler<FriendNamesEventArgs>) -> Subscription {
|
||||
self.inner.events.names.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friend_offline(&self, h: EventHandler<FriendInfoEventArgs>) -> Subscription {
|
||||
self.inner.events.offline.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friend_online(&self, h: EventHandler<FriendInfoEventArgs>) -> Subscription {
|
||||
self.inner.events.online.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friend_rights_update(
|
||||
&self,
|
||||
h: EventHandler<FriendInfoEventArgs>,
|
||||
) -> Subscription {
|
||||
self.inner.events.rights.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friendship_offered(
|
||||
&self,
|
||||
h: EventHandler<FriendshipOfferedEventArgs>,
|
||||
) -> Subscription {
|
||||
self.inner.events.offered.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friendship_response(
|
||||
&self,
|
||||
h: EventHandler<FriendshipResponseEventArgs>,
|
||||
) -> Subscription {
|
||||
self.inner.events.response.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friendship_terminated(
|
||||
&self,
|
||||
h: EventHandler<FriendshipTerminatedEventArgs>,
|
||||
) -> Subscription {
|
||||
self.inner.events.terminated.subscribe(h)
|
||||
}
|
||||
pub fn subscribe_friends_list_ready(
|
||||
&self,
|
||||
h: EventHandler<FriendsReadyEventArgs>,
|
||||
) -> Subscription {
|
||||
self.inner.events.ready.subscribe(h)
|
||||
}
|
||||
pub fn accept_friendship(&self, from: UUID, transaction: UUID) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::AcceptFriendshipPacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
p.transaction_block.transaction_id = transaction;
|
||||
let mut folder =
|
||||
crate::packets::AcceptFriendshipPacketFolderDataBlock::new_with_constructor()?;
|
||||
folder.folder_id = self
|
||||
.inner
|
||||
.client
|
||||
.inventory()
|
||||
.find_folder_for_type_with_folder_type(FolderType::CallingCard)?;
|
||||
p.folder_data.push(folder);
|
||||
self.send(&p, PacketType::AcceptFriendship)?;
|
||||
self.inner.accept(from);
|
||||
Ok(())
|
||||
}
|
||||
pub fn decline_friendship(&self, from: UUID, transaction: UUID) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::DeclineFriendshipPacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
p.transaction_block.transaction_id = transaction;
|
||||
self.send(&p, PacketType::DeclineFriendship)?;
|
||||
write(&self.inner.requests).remove(&from);
|
||||
Ok(())
|
||||
}
|
||||
pub async fn accept_friendship_via_cap(
|
||||
&self,
|
||||
from: UUID,
|
||||
ct: Option<CancellationToken>,
|
||||
) -> Result<(), Error> {
|
||||
self.friend_cap("AcceptFriendship", "POST", from, ct)
|
||||
.await?;
|
||||
self.inner.accept(from);
|
||||
Ok(())
|
||||
}
|
||||
pub async fn decline_friendship_via_cap(
|
||||
&self,
|
||||
from: UUID,
|
||||
ct: Option<CancellationToken>,
|
||||
) -> Result<(), Error> {
|
||||
self.friend_cap("DeclineFriendship", "DELETE", from, ct)
|
||||
.await?;
|
||||
write(&self.inner.requests).remove(&from);
|
||||
Ok(())
|
||||
}
|
||||
async fn friend_cap(
|
||||
&self,
|
||||
cap: &str,
|
||||
method: &str,
|
||||
from: UUID,
|
||||
ct: Option<CancellationToken>,
|
||||
) -> Result<(), Error> {
|
||||
let token = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
|
||||
token.throw_if_cancellation_requested()?;
|
||||
let caps = self
|
||||
.inner
|
||||
.client
|
||||
.network()
|
||||
.current_sim()
|
||||
.and_then(|s| s.native_caps())
|
||||
.ok_or(Error::InvalidOperation)?;
|
||||
let uri = caps
|
||||
.capability_uri(cap.into())?
|
||||
.ok_or(Error::InvalidOperation)?;
|
||||
let mut url = reqwest::Url::parse(&uri.0).map_err(|_| Error::Argument)?;
|
||||
url.query_pairs_mut().append_pair("from", &from.to_string());
|
||||
let http = self.inner.client.native_http_caps_client();
|
||||
let (response, bytes) = if method == "POST" {
|
||||
http.post_with_uri_osd_format_osd_cancellation_token_i_progress(
|
||||
libremetaverse_types::compat::Uri(url.into()),
|
||||
OSDFormat::Xml,
|
||||
OSD::Undefined,
|
||||
token,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
http.send_custom(
|
||||
"DELETE",
|
||||
libremetaverse_types::compat::Uri(url.into()),
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
Vec::new(),
|
||||
token,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
if !response.is_success_status_code() || bytes.len() > 1024 * 1024 {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
if !bytes.is_empty() {
|
||||
let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else {
|
||||
return Err(Error::Argument);
|
||||
};
|
||||
if !map
|
||||
.get("success")
|
||||
.and_then(|v| v.as_boolean().ok())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn offer_friendship_with_uuid(&self, id: UUID) -> Result<(), Error> {
|
||||
self.offer_friendship_with_uuid_string(id, "Do ya wanna be my buddy?".into())
|
||||
}
|
||||
pub fn offer_friendship_with_uuid_string(
|
||||
&self,
|
||||
id: UUID,
|
||||
message: String,
|
||||
) -> Result<(), Error> {
|
||||
if message.len() > 1024 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let mut client = (*self.inner.client).clone();
|
||||
let region = client
|
||||
.network()
|
||||
.current_sim()
|
||||
.map_or_else(UUID::zero, |s| s.id);
|
||||
let me = client.self_();
|
||||
me.native_instant_message_full(
|
||||
me.name(),
|
||||
id,
|
||||
message,
|
||||
UUID::random()?,
|
||||
InstantMessageDialog::FriendshipOffered,
|
||||
crate::InstantMessageOnline::Offline,
|
||||
me.sim_position(),
|
||||
region,
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
pub fn terminate_friendship(&self, id: UUID) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::TerminateFriendshipPacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
p.ex_block.other_id = id;
|
||||
self.send(&p, PacketType::TerminateFriendship)?;
|
||||
write(&self.inner.friends).remove(&id);
|
||||
Ok(())
|
||||
}
|
||||
pub fn grant_rights(&self, id: UUID, rights: FriendRights) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::GrantUserRightsPacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
let mut b = crate::packets::GrantUserRightsPacketRightsBlock::new_with_constructor()?;
|
||||
b.agent_related = id;
|
||||
b.related_rights = rights.0;
|
||||
p.rights.push(b);
|
||||
self.send(&p, PacketType::GrantUserRights)
|
||||
}
|
||||
pub fn map_friend(&self, id: UUID) -> Result<(), Error> {
|
||||
let (agent, _) = self.ids();
|
||||
let mut p = crate::packets::FindAgentPacket::new_with_constructor()?;
|
||||
p.agent_block.hunter = agent;
|
||||
p.agent_block.prey = id;
|
||||
let b = crate::packets::FindAgentPacketLocationBlockBlock::new_with_constructor()?;
|
||||
p.location_block.push(b);
|
||||
self.send(&p, PacketType::FindAgent)
|
||||
}
|
||||
pub fn track_friend(&self, id: UUID) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::TrackAgentPacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
p.target_data.prey_id = id;
|
||||
self.send(&p, PacketType::TrackAgent)
|
||||
}
|
||||
pub fn request_online_notification(&self, id: UUID) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::GenericMessagePacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
p.method_data.method = b"requestonlinenotification".to_vec();
|
||||
let mut b = crate::packets::GenericMessagePacketParamListBlock::new_with_constructor()?;
|
||||
b.parameter = id.to_string().into_bytes();
|
||||
p.param_list.push(b);
|
||||
self.send(&p, PacketType::GenericMessage)
|
||||
}
|
||||
pub fn give_calling_card(&self, id: UUID) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::OfferCallingCardPacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
p.agent_block.dest_id = id;
|
||||
p.agent_block.transaction_id = UUID::random()?;
|
||||
self.send(&p, PacketType::OfferCallingCard)
|
||||
}
|
||||
pub fn accept_calling_card(&self, _from: UUID, transaction: UUID) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::AcceptCallingCardPacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
p.transaction_block.transaction_id = transaction;
|
||||
let mut b = crate::packets::AcceptCallingCardPacketFolderDataBlock::new_with_constructor()?;
|
||||
b.folder_id = self
|
||||
.inner
|
||||
.client
|
||||
.inventory()
|
||||
.find_folder_for_type_with_folder_type(FolderType::CallingCard)?;
|
||||
p.folder_data.push(b);
|
||||
self.send(&p, PacketType::AcceptCallingCard)
|
||||
}
|
||||
pub fn decline_calling_card(&self, _from: UUID, transaction: UUID) -> Result<(), Error> {
|
||||
let (agent, session) = self.ids();
|
||||
let mut p = crate::packets::DeclineCallingCardPacket::new_with_constructor()?;
|
||||
p.agent_data.agent_id = agent;
|
||||
p.agent_data.session_id = session;
|
||||
p.transaction_block.transaction_id = transaction;
|
||||
self.send(&p, PacketType::DeclineCallingCard)
|
||||
}
|
||||
pub fn on_find_agent_reply_handler(
|
||||
&self,
|
||||
_sender: Option<libremetaverse_types::compat::Object>,
|
||||
e: crate::PacketReceivedEventArgs,
|
||||
) -> Result<(), Error> {
|
||||
// The native network pipeline dispatches the payload-bearing raw packet
|
||||
// before its metadata-only compatibility callback. `handle_packet`
|
||||
// performs the decode and event emission; this public callback retains
|
||||
// the upstream type guard without emitting a duplicate event.
|
||||
if e.packet().type_ == PacketType::FindAgent {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Argument)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FriendsManagerInner {
|
||||
fn accept(&self, id: UUID) {
|
||||
let mut friends = write(&self.friends);
|
||||
if friends.len() < MAX_FRIENDS || friends.contains_key(&id) {
|
||||
friends.entry(id).or_insert_with(|| {
|
||||
FriendInfo::native_new(
|
||||
id,
|
||||
FriendRights::CAN_SEE_ONLINE,
|
||||
FriendRights::CAN_SEE_ONLINE,
|
||||
)
|
||||
});
|
||||
}
|
||||
drop(friends);
|
||||
write(&self.requests).remove(&id);
|
||||
}
|
||||
fn dispose(&self) {
|
||||
if self.disposed.swap(true, Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
mutex(&self.subscriptions).clear();
|
||||
if let Some(cb) = mutex(&self.login_callback).take() {
|
||||
let _ = self
|
||||
.client
|
||||
.network()
|
||||
.native_unregister_login_response_callback(&cb);
|
||||
}
|
||||
}
|
||||
fn load_login(&self, response: Option<crate::LoginResponseData>) {
|
||||
let Some(response) = response else { return };
|
||||
let mut friends = write(&self.friends);
|
||||
for b in response.buddy_list().into_iter().take(MAX_FRIENDS) {
|
||||
if let Ok(id) = UUID::parse(b.buddy_id.chars().take(36).collect::<String>()) {
|
||||
friends.entry(id).or_insert_with(|| {
|
||||
FriendInfo::native_new(
|
||||
id,
|
||||
FriendRights(b.buddy_rights_given),
|
||||
FriendRights(b.buddy_rights_has),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
let count = i32::try_from(friends.len()).unwrap_or(i32::MAX);
|
||||
drop(friends);
|
||||
self.events.ready.emit(FriendsReadyEventArgs { count });
|
||||
}
|
||||
fn handle_im(&self, e: crate::InstantMessageEventArgs) {
|
||||
let im = e.im();
|
||||
match im.dialog {
|
||||
InstantMessageDialog::FriendshipOffered => {
|
||||
let mut requests = write(&self.requests);
|
||||
if requests.len() < MAX_FRIENDS || requests.contains_key(&im.from_agent_id) {
|
||||
requests.insert(im.from_agent_id, im.im_session_id);
|
||||
}
|
||||
drop(requests);
|
||||
self.events.offered.emit(FriendshipOfferedEventArgs {
|
||||
agent_id: im.from_agent_id,
|
||||
agent_name: im.from_agent_name,
|
||||
im_session_id: im.im_session_id,
|
||||
});
|
||||
}
|
||||
InstantMessageDialog::FriendshipAccepted => {
|
||||
let mut f = FriendInfo::native_new(
|
||||
im.from_agent_id,
|
||||
FriendRights::CAN_SEE_ONLINE,
|
||||
FriendRights::CAN_SEE_ONLINE,
|
||||
);
|
||||
f.name.clone_from(&im.from_agent_name);
|
||||
f.online = true;
|
||||
write(&self.friends).insert(f.id, f.clone());
|
||||
self.events.response.emit(FriendshipResponseEventArgs {
|
||||
agent_id: f.id,
|
||||
agent_name: f.name,
|
||||
accepted: true,
|
||||
});
|
||||
}
|
||||
InstantMessageDialog::FriendshipDeclined => {
|
||||
self.events.response.emit(FriendshipResponseEventArgs {
|
||||
agent_id: im.from_agent_id,
|
||||
agent_name: im.from_agent_name,
|
||||
accepted: false,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_precision_loss
|
||||
)]
|
||||
fn handle_packet(&self, e: RawPacketReceivedEventArgs) {
|
||||
if self.disposed.load(Ordering::Acquire) || e.data.len() > 8 * 1024 * 1024 {
|
||||
return;
|
||||
}
|
||||
let mut pos = 0;
|
||||
match e.packet_type {
|
||||
PacketType::OnlineNotification | PacketType::OfflineNotification => {
|
||||
let online = e.packet_type == PacketType::OnlineNotification;
|
||||
let ids = if online {
|
||||
crate::packets::OnlineNotificationPacket::new_from_bytes(&e.data, &mut pos).map(
|
||||
|p| {
|
||||
p.agent_block
|
||||
.into_iter()
|
||||
.map(|b| b.agent_id)
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
)
|
||||
} else {
|
||||
crate::packets::OfflineNotificationPacket::new_from_bytes(&e.data, &mut pos)
|
||||
.map(|p| {
|
||||
p.agent_block
|
||||
.into_iter()
|
||||
.map(|b| b.agent_id)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
};
|
||||
if let Ok(ids) = ids {
|
||||
for id in ids.into_iter().take(MAX_FRIENDS) {
|
||||
let event = {
|
||||
let mut fs = write(&self.friends);
|
||||
let f = fs.entry(id).or_insert_with(|| {
|
||||
FriendInfo::native_new(
|
||||
id,
|
||||
FriendRights::CAN_SEE_ONLINE,
|
||||
FriendRights::CAN_SEE_ONLINE,
|
||||
)
|
||||
});
|
||||
let changed = f.online != online;
|
||||
f.online = online;
|
||||
changed.then(|| f.clone())
|
||||
};
|
||||
if let Some(friend) = event {
|
||||
if online {
|
||||
self.events.online.emit(FriendInfoEventArgs { friend });
|
||||
} else {
|
||||
self.events.offline.emit(FriendInfoEventArgs { friend });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PacketType::TerminateFriendship => {
|
||||
if let Ok(p) =
|
||||
crate::packets::TerminateFriendshipPacket::new_from_bytes(&e.data, &mut pos)
|
||||
{
|
||||
let id = p.ex_block.other_id;
|
||||
let name = write(&self.friends)
|
||||
.remove(&id)
|
||||
.map_or_else(String::new, |f| f.name);
|
||||
self.events.terminated.emit(FriendshipTerminatedEventArgs {
|
||||
agent_id: id,
|
||||
agent_name: name,
|
||||
});
|
||||
}
|
||||
}
|
||||
PacketType::ChangeUserRights => {
|
||||
if let Ok(p) =
|
||||
crate::packets::ChangeUserRightsPacket::new_from_bytes(&e.data, &mut pos)
|
||||
{
|
||||
let self_id = {
|
||||
let mut client = (*self.client).clone();
|
||||
client.self_().agent_id()
|
||||
};
|
||||
for b in p.rights.into_iter().take(MAX_FRIENDS) {
|
||||
let id = if b.agent_related == self_id {
|
||||
p.agent_data.agent_id
|
||||
} else {
|
||||
b.agent_related
|
||||
};
|
||||
let event = {
|
||||
let mut fs = write(&self.friends);
|
||||
fs.get_mut(&id).map(|f| {
|
||||
if b.agent_related == id {
|
||||
f.their_rights = FriendRights(b.related_rights);
|
||||
} else {
|
||||
f.my_rights = FriendRights(b.related_rights);
|
||||
}
|
||||
f.clone()
|
||||
})
|
||||
};
|
||||
if let Some(friend) = event {
|
||||
self.events.rights.emit(FriendInfoEventArgs { friend });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PacketType::FindAgent => {
|
||||
if let Ok(p) = crate::packets::FindAgentPacket::new_from_bytes(&e.data, &mut pos)
|
||||
&& let Some(loc) = p.location_block.first()
|
||||
{
|
||||
let gx = loc.global_x.max(0.0) as u32;
|
||||
let gy = loc.global_y.max(0.0) as u32;
|
||||
let handle = (u64::from(gx & !255) << 32) | u64::from(gy & !255);
|
||||
self.events.found.emit(FriendFoundReplyEventArgs {
|
||||
agent_id: p.agent_block.prey,
|
||||
region_handle: handle,
|
||||
location: Vector3 {
|
||||
x: (gx & 255) as f32,
|
||||
y: (gy & 255) as f32,
|
||||
z: 0.0,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
PacketType::OfferCallingCard => {
|
||||
if let Ok(p) =
|
||||
crate::packets::OfferCallingCardPacket::new_from_bytes(&e.data, &mut pos)
|
||||
{
|
||||
let id = p.agent_data.agent_id;
|
||||
let name = read(&self.friends).get(&id).map_or_else(
|
||||
|| id.to_string(),
|
||||
|f| {
|
||||
if f.name.is_empty() {
|
||||
id.to_string()
|
||||
} else {
|
||||
f.name.clone()
|
||||
}
|
||||
},
|
||||
);
|
||||
self.events.calling_card.emit(CallingCardOfferedEventArgs {
|
||||
agent_id: id,
|
||||
agent_name: name,
|
||||
transaction_id: p.agent_block.transaction_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
PacketType::UUIDNameReply => {
|
||||
if let Ok(p) =
|
||||
crate::packets::UUIDNameReplyPacket::new_from_bytes(&e.data, &mut pos)
|
||||
{
|
||||
let mut changed = HashMap::new();
|
||||
{
|
||||
let mut fs = write(&self.friends);
|
||||
for b in p.uuid_name_block.into_iter().take(MAX_FRIENDS) {
|
||||
if let Some(f) = fs.get_mut(&b.id) {
|
||||
let name = format!(
|
||||
"{} {}",
|
||||
wire_string(&b.first_name),
|
||||
wire_string(&b.last_name)
|
||||
)
|
||||
.trim()
|
||||
.to_owned();
|
||||
if f.name != name {
|
||||
f.name.clone_from(&name);
|
||||
changed.insert(b.id, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !changed.is_empty() {
|
||||
self.events
|
||||
.names
|
||||
.emit(FriendNamesEventArgs { names: changed });
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
#[test]
|
||||
fn rights_enforce_online_before_map() {
|
||||
let mut f = FriendInfo::native_new(UUID::zero(), FriendRights::NONE, FriendRights::NONE);
|
||||
f.set_can_see_me_on_map(true);
|
||||
assert!(!f.can_see_me_on_map());
|
||||
f.set_can_see_me_online(true);
|
||||
f.set_can_see_me_on_map(true);
|
||||
assert!(f.can_see_me_on_map());
|
||||
f.set_can_see_me_online(false);
|
||||
assert!(!f.can_see_me_on_map());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presence_packet_updates_state_before_callback() {
|
||||
let client = GridClient::new().unwrap();
|
||||
let manager = FriendsManager::native_new(Arc::new(client.clone())).unwrap();
|
||||
let friend_id =
|
||||
UUID::new_with_string("77777777-7777-7777-7777-777777777777".into()).unwrap();
|
||||
write(&manager.inner.friends).insert(
|
||||
friend_id,
|
||||
FriendInfo::native_new(friend_id, FriendRights::NONE, FriendRights::NONE),
|
||||
);
|
||||
let callback_manager = manager.clone();
|
||||
let observed = Arc::new(AtomicBool::new(false));
|
||||
let observed_copy = Arc::clone(&observed);
|
||||
let _subscription = manager.subscribe_friend_online(Arc::new(move |event| {
|
||||
assert!(callback_manager.friend_list()[&event.friend().uuid()].is_online());
|
||||
observed_copy.store(true, Ordering::Release);
|
||||
}));
|
||||
let mut packet = crate::packets::OnlineNotificationPacket::new_with_constructor().unwrap();
|
||||
let mut row =
|
||||
crate::packets::OnlineNotificationPacketAgentBlockBlock::new_with_constructor()
|
||||
.unwrap();
|
||||
row.agent_id = friend_id;
|
||||
packet.agent_block = vec![row];
|
||||
let simulator = crate::Simulator::native_new(
|
||||
client,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13973),
|
||||
0,
|
||||
Some(256),
|
||||
Some(256),
|
||||
)
|
||||
.unwrap();
|
||||
manager.inner.handle_packet(RawPacketReceivedEventArgs {
|
||||
packet_type: PacketType::OnlineNotification,
|
||||
data: packet.encode_packet().unwrap(),
|
||||
simulator,
|
||||
});
|
||||
assert!(observed.load(Ordering::Acquire));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1610
crates/libremetaverse/src/group_manager.rs
Normal file
1610
crates/libremetaverse/src/group_manager.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,9 @@ mod current_outfit;
|
||||
mod download_manager;
|
||||
mod environment_manager;
|
||||
mod event_queue;
|
||||
mod friends_manager;
|
||||
mod gesture;
|
||||
mod group_manager;
|
||||
mod initial_outfit;
|
||||
mod inventory;
|
||||
mod inventory_ais;
|
||||
|
||||
Reference in New Issue
Block a user