From 763e9e5044c4e376b619c62e647f491ce27691f6 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Mon, 10 Aug 2026 19:25:31 +0000 Subject: [PATCH] Implement estate and marketplace services (#73) --- README.md | 6 + api/SHIM-COVERAGE.md | 2 +- crates/libremetaverse/src/asset_manager.rs | 32 + crates/libremetaverse/src/client_core.rs | 40 + crates/libremetaverse/src/estate_tools.rs | 1489 +++++++++++++++++ crates/libremetaverse/src/generated.rs | 1323 ++------------- crates/libremetaverse/src/lib.rs | 36 +- crates/libremetaverse/src/marketplace.rs | 708 ++++++++ crates/libremetaverse/src/user_report.rs | 266 +++ .../src/world_internal_semantics.rs | 52 +- docs/world-services.md | 86 + tests/compat/tests/user_report_semantics.rs | 242 +++ tests/compat/tests/world_manager_shims.rs | 32 +- tools/generate_api_shims.py | 20 + 14 files changed, 3132 insertions(+), 1202 deletions(-) create mode 100644 crates/libremetaverse/src/estate_tools.rs create mode 100644 crates/libremetaverse/src/marketplace.rs create mode 100644 crates/libremetaverse/src/user_report.rs create mode 100644 docs/world-services.md create mode 100644 tests/compat/tests/user_report_semantics.rs diff --git a/README.md b/README.md index d976589..9c3d149 100644 --- a/README.md +++ b/README.md @@ -414,6 +414,12 @@ position, and queue flags. Packet/capability selection, state-before-event ordering, cancellation, limits, and live-operation safety are documented in [`docs/land.md`](docs/land.md). +Estate administration, experience preferences, marketplace listings, and +abuse reporting now use native owner-message, capability, event, cache, and +legacy-packet paths. Exact schedules, list deltas, folder roles, report fields, +cancellation, and explicit live-operation gates are documented in +[`docs/world-services.md`](docs/world-services.md). + Grid and directory discovery now use native LLUDP map and search requests, correlated bounded reply events, expiring region indexes, coarse-location deltas, and exact region-handle math. SLURL parsing covers location and diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index c1d38f8..d80e8b8 100644 --- a/api/SHIM-COVERAGE.md +++ b/api/SHIM-COVERAGE.md @@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand. | Assembly | Types | Members | Status | |---|---:|---:|---| -| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 351 types / 16,636 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 369 types / 16,774 members; remaining surface is callable failure-only shims | | `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain | | `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain | | `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim | diff --git a/crates/libremetaverse/src/asset_manager.rs b/crates/libremetaverse/src/asset_manager.rs index 4546efe..b6a4643 100644 --- a/crates/libremetaverse/src/asset_manager.rs +++ b/crates/libremetaverse/src/asset_manager.rs @@ -150,6 +150,38 @@ impl std::fmt::Debug for AssetManager { } impl AssetManager { + pub(crate) fn native_stage_estate_upload(&self, data: Vec) -> Result { + if data.is_empty() || data.len() > crate::asset_models::MAX_ASSET_BYTES { + return Err(Error::Argument); + } + let id = UUID::random()?; + let upload = Arc::new(Mutex::new(UploadState { + asset_id: id, + data, + packet_num: 0, + transaction_id: id, + transferred: 0, + type_: AssetType::Unknown, + xfer_id: 0, + confirmation: None, + })); + let mut pending = mutex(&self.inner.pending_upload); + if pending.is_some() { + return Err(Error::InvalidOperation); + } + *pending = Some(upload); + Ok(id) + } + + pub(crate) fn native_cancel_staged_estate_upload(&self, id: UUID) { + let mut pending = mutex(&self.inner.pending_upload); + if pending + .as_ref() + .is_some_and(|upload| mutex(upload).transaction_id == id) + { + pending.take(); + } + } pub(crate) fn native_from_inner(inner: Arc) -> Result { inner.client.upgrade().ok_or(Error::InvalidOperation)?; Ok(Self { diff --git a/crates/libremetaverse/src/client_core.rs b/crates/libremetaverse/src/client_core.rs index f11872e..8edd2d4 100644 --- a/crates/libremetaverse/src/client_core.rs +++ b/crates/libremetaverse/src/client_core.rs @@ -120,6 +120,7 @@ struct ClientRuntime { agent_manager: Mutex>, object_manager: Mutex>>, environment_manager: Mutex>>, + estate_tools: Mutex>>, terrain_manager: Mutex>>, sound_manager: Mutex>>, parcel_manager: Mutex>>, @@ -164,6 +165,7 @@ impl ClientRuntime { agent_manager: Mutex::new(std::sync::Weak::new()), object_manager: Mutex::new(None), environment_manager: Mutex::new(None), + estate_tools: Mutex::new(None), terrain_manager: Mutex::new(None), sound_manager: Mutex::new(None), parcel_manager: Mutex::new(None), @@ -760,6 +762,44 @@ impl GridClient { .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner); } + pub(crate) fn install_estate_tools_inner( + &self, + candidate: Arc, + ) -> Arc { + let mut cached = self + .runtime + .estate_tools + .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_estate(&self) -> Result { + if let Some(inner) = self + .runtime + .estate_tools + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + { + return Ok(crate::estate_tools::EstateTools::native_from_inner(inner)); + } + crate::estate_tools::EstateTools::native_new(self.clone()) + } + + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn native_set_estate(&self, value: crate::EstateTools) { + *self + .runtime + .estate_tools + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.native_inner()); + } + pub(crate) fn cached_terrain_manager_inner( &self, ) -> Option> { diff --git a/crates/libremetaverse/src/estate_tools.rs b/crates/libremetaverse/src/estate_tools.rs new file mode 100644 index 0000000..befbaf2 --- /dev/null +++ b/crates/libremetaverse/src/estate_tools.rs @@ -0,0 +1,1489 @@ +//! Estate administration decisions, owner-message payloads, and capabilities. + +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] +#![allow(clippy::similar_names)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::fn_params_excessive_bools)] +#![allow(clippy::unnecessary_wraps)] + +use crate::agent_manager::EventRegistry; +use crate::packet_catalog::{GeneratedPacket, PacketType}; +use crate::{ + Error, EstateToolsEstateAccessDelta as AccessDelta, EstateToolsEstateReturnFlags, + EstateToolsGroundTextureHeight, EstateToolsGroundTextureHeightSettings, + EstateToolsGroundTextureSettings, EstateToolsLandStatReportType, EstateToolsRegionMaturity, + GridClient, RegionFlags, RegionRestartDays, SourceType, +}; +use futures_channel::oneshot; +use futures_util::{FutureExt, pin_mut, select_biased}; +use libremetaverse_structured_data::{OSD, OSDParser}; +use libremetaverse_types::AssetType; +use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri}; +use libremetaverse_types::{UUID, Vector3}; +use serde_json::Value; +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +fn blank_textures() -> EstateToolsGroundTextureSettings { + EstateToolsGroundTextureSettings { + low: UUID::zero(), + mid_low: UUID::zero(), + mid_high: UUID::zero(), + high: UUID::zero(), + } +} + +fn blank_limits() -> EstateToolsGroundTextureHeightSettings { + let height = || EstateToolsGroundTextureHeight { + low: 0.0, + high: 0.0, + }; + EstateToolsGroundTextureHeightSettings { + sw: height(), + nw: height(), + se: height(), + ne: height(), + } +} + +#[derive(Clone, Debug)] +pub struct RegionRestartSchedule { + daily: bool, + days: RegionRestartDays, + time: Duration, +} +impl RegionRestartSchedule { + pub fn new() -> Result { + Ok(Self { + daily: false, + days: RegionRestartDays::NONE, + time: Duration::ZERO, + }) + } + pub const fn is_daily(&self) -> bool { + self.daily + } + pub fn set_is_daily(&mut self, value: bool) { + self.daily = value; + } + pub const fn days(&self) -> RegionRestartDays { + self.days + } + pub fn set_days(&mut self, value: RegionRestartDays) { + self.days = value; + } + pub const fn time(&self) -> Duration { + self.time + } + pub fn set_time(&mut self, value: Duration) { + self.time = value; + } +} + +#[derive(Clone, Debug, Default)] +pub struct EstateTask { + pub position: Vector3, + pub score: f32, + pub mono_score: f32, + pub task_id: UUID, + pub task_local_id: u32, + pub task_name: String, + pub owner_name: String, +} +impl EstateTask { + pub fn new() -> Result { + Ok(Self::default()) + } +} + +macro_rules! list_args { + ($name:ident, $field:ident, $getter:ident) => { + #[derive(Clone, Debug)] + pub struct $name { + estate_id: u32, + count: i32, + $field: Vec, + } + impl $name { + pub fn new(estate_id: u32, count: i32, $field: Vec) -> Result { + Ok(Self { + estate_id, + count, + $field, + }) + } + pub const fn estate_id(&self) -> u32 { + self.estate_id + } + pub const fn count(&self) -> i32 { + self.count + } + pub fn $getter(&self) -> Vec { + self.$field.clone() + } + } + }; +} +list_args!(EstateBansReplyEventArgs, banned, banned); +list_args!(EstateGroupsReplyEventArgs, allowed_groups, allowed_groups); +list_args!(EstateManagersReplyEventArgs, managers, managers); +list_args!(EstateUsersReplyEventArgs, allowed_users, allowed_users); + +#[derive(Clone, Debug)] +pub struct EstateExperienceReplyEventArgs { + blocked: Vec, + trusted: Vec, + allowed: Vec, +} +impl EstateExperienceReplyEventArgs { + pub fn new(blocked: Vec, trusted: Vec, allowed: Vec) -> Result { + Ok(Self { + blocked, + trusted, + allowed, + }) + } + pub fn blocked(&self) -> Vec { + self.blocked.clone() + } + pub fn trusted(&self) -> Vec { + self.trusted.clone() + } + pub fn allowed(&self) -> Vec { + self.allowed.clone() + } +} + +#[derive(Clone, Debug)] +pub struct EstateCovenantReplyEventArgs { + covenant_id: UUID, + timestamp: i64, + estate_name: String, + estate_owner_id: UUID, +} +impl EstateCovenantReplyEventArgs { + pub fn new( + covenant_id: UUID, + timestamp: i64, + estate_name: String, + estate_owner_id: UUID, + ) -> Result { + Ok(Self { + covenant_id, + timestamp, + estate_name, + estate_owner_id, + }) + } + pub const fn covenant_id(&self) -> UUID { + self.covenant_id + } + pub const fn timestamp(&self) -> i64 { + self.timestamp + } + pub fn estate_name(&self) -> String { + self.estate_name.clone() + } + pub const fn estate_owner_id(&self) -> UUID { + self.estate_owner_id + } +} + +#[derive(Clone, Debug)] +pub struct EstateUpdateInfoReplyEventArgs { + estate_name: String, + estate_owner: UUID, + estate_id: u32, + flags: RegionFlags, + sun_hour: i32, +} +impl EstateUpdateInfoReplyEventArgs { + pub fn new( + estate_name: String, + estate_owner: UUID, + estate_id: u32, + flags: RegionFlags, + sun_hour: i32, + ) -> Result { + Ok(Self { + estate_name, + estate_owner, + estate_id, + flags, + sun_hour, + }) + } + pub fn estate_name(&self) -> String { + self.estate_name.clone() + } + pub const fn estate_owner(&self) -> UUID { + self.estate_owner + } + pub const fn estate_id(&self) -> u32 { + self.estate_id + } + pub const fn flags(&self) -> RegionFlags { + self.flags + } + pub const fn sun_hour(&self) -> i32 { + self.sun_hour + } +} + +macro_rules! task_args { + ($name:ident) => { + #[derive(Clone, Debug)] + pub struct $name { + object_count: i32, + tasks: HashMap, + } + impl $name { + pub fn new(object_count: i32, tasks: HashMap) -> Result { + Ok(Self { + object_count, + tasks, + }) + } + pub const fn object_count(&self) -> i32 { + self.object_count + } + pub fn tasks(&self) -> HashMap { + self.tasks.clone() + } + } + }; +} +task_args!(TopCollidersReplyEventArgs); +task_args!(TopScriptsReplyEventArgs); + +#[derive(Default)] +struct EstateEvents { + bans: EventRegistry, + covenant: EventRegistry, + experiences: EventRegistry, + groups: EventRegistry, + managers: EventRegistry, + update: EventRegistry, + users: EventRegistry, + colliders: EventRegistry, + scripts: EventRegistry, +} + +pub(crate) struct EstateToolsInner { + client: GridClient, + network: crate::NetworkManager, + events: EstateEvents, + disposed: AtomicBool, + live_mutations: AtomicBool, + sim_console_lock: tokio::sync::Mutex<()>, + subscriptions: Mutex>, +} + +pub struct EstateTools { + pub ground_texture_limits: EstateToolsGroundTextureHeightSettings, + pub ground_textures: EstateToolsGroundTextureSettings, + pub(crate) inner: Arc, +} + +impl Clone for EstateTools { + fn clone(&self) -> Self { + Self { + ground_texture_limits: EstateToolsGroundTextureHeightSettings { + sw: EstateToolsGroundTextureHeight { + low: self.ground_texture_limits.sw.low, + high: self.ground_texture_limits.sw.high, + }, + nw: EstateToolsGroundTextureHeight { + low: self.ground_texture_limits.nw.low, + high: self.ground_texture_limits.nw.high, + }, + se: EstateToolsGroundTextureHeight { + low: self.ground_texture_limits.se.low, + high: self.ground_texture_limits.se.high, + }, + ne: EstateToolsGroundTextureHeight { + low: self.ground_texture_limits.ne.low, + high: self.ground_texture_limits.ne.high, + }, + }, + ground_textures: EstateToolsGroundTextureSettings { + low: self.ground_textures.low, + mid_low: self.ground_textures.mid_low, + mid_high: self.ground_textures.mid_high, + high: self.ground_textures.high, + }, + inner: Arc::clone(&self.inner), + } + } +} + +impl EstateTools { + pub(crate) fn native_new(client: GridClient) -> Result { + let network = client.network(); + let candidate = Arc::new(EstateToolsInner { + client: client.clone(), + network: network.clone(), + events: EstateEvents::default(), + disposed: AtomicBool::new(false), + live_mutations: AtomicBool::new(false), + sim_console_lock: tokio::sync::Mutex::new(()), + subscriptions: Mutex::new(Vec::new()), + }); + let weak = Arc::downgrade(&candidate); + candidate + .subscriptions + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .push(network.subscribe_raw_packet(Arc::new(move |event| { + if let Some(inner) = weak.upgrade() { + inner.handle_packet(event); + } + }))); + let weak = Arc::downgrade(&candidate); + candidate + .subscriptions + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .push(network.subscribe_caps( + "LandStatReply".into(), + crate::CapsEventQueueCallback::from_handler(move |_name, message, _simulator| { + let Some(inner) = weak.upgrade() else { + return; + }; + let Ok(mut reply) = crate::messages::linden::LandStatReplyMessage::new() else { + return; + }; + if message + .serialize() + .and_then(|map| reply.deserialize(map)) + .is_ok() + { + inner.handle_land_stat_caps(reply); + } + }), + )); + let inner = client.install_estate_tools_inner(candidate); + Ok(Self::native_from_inner(inner)) + } + pub(crate) fn native_from_inner(inner: Arc) -> Self { + Self { + ground_texture_limits: blank_limits(), + ground_textures: blank_textures(), + inner, + } + } + pub(crate) fn native_inner(&self) -> Arc { + Arc::clone(&self.inner) + } + pub fn new(client: GridClient) -> Result { + Self::native_new(client) + } + pub fn enable_live_mutations(&self) { + self.inner.live_mutations.store(true, Ordering::Release); + } + pub fn dispose(&self) -> Result<(), Error> { + self.inner.disposed.store(true, Ordering::Release); + self.inner + .subscriptions + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clear(); + Ok(()) + } + + pub fn subscribe_estate_bans_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.bans.subscribe(h) + } + pub fn subscribe_estate_covenant_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.covenant.subscribe(h) + } + pub fn subscribe_estate_experience_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.experiences.subscribe(h) + } + pub fn subscribe_estate_groups_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.groups.subscribe(h) + } + pub fn subscribe_estate_managers_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.managers.subscribe(h) + } + pub fn subscribe_estate_update_info_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.update.subscribe(h) + } + pub fn subscribe_estate_users_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.users.subscribe(h) + } + pub fn subscribe_top_colliders_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.colliders.subscribe(h) + } + pub fn subscribe_top_scripts_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.scripts.subscribe(h) + } + + #[allow(dead_code)] + pub(crate) fn estate_owner_message_handler( + &self, + packet: crate::packets::EstateOwnerMessagePacket, + _simulator: crate::Simulator, + ) -> Result<(), Error> { + if self.inner.disposed.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + self.inner.handle_owner_message(packet); + Ok(()) + } + + fn ids(&self) -> (UUID, UUID, String) { + let mut client = self.inner.client.clone(); + let agent = client.self_(); + (agent.agent_id(), agent.session_id(), agent.name()) + } + + fn current_sim(&self) -> Result { + self.inner + .client + .native_network()? + .native_current_sim() + .ok_or(Error::InvalidOperation) + } + + fn ensure_udp_mutation_allowed(&self) -> Result<(), Error> { + let sim = self.current_sim()?; + if sim.connected() && !self.inner.live_mutations.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + Ok(()) + } + + fn send_packet(&self, packet: &T, kind: PacketType) -> Result<(), Error> { + if self.inner.disposed.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + let sim = self.current_sim()?; + let bytes = packet.encode_packet()?; + let result = sim.native_send_packet_data( + bytes.clone(), + i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, + kind, + false, + ); + match result { + Err(Error::InvalidOperation) if !sim.connected() => Ok(()), + other => other, + } + } + + pub fn estate_owner_message_with_string_list( + &self, + method: String, + params: Vec, + ) -> Result<(), Error> { + self.ensure_udp_mutation_allowed()?; + if method.len() > 255 || params.len() > 255 || params.iter().any(|value| value.len() > 255) + { + return Err(Error::Argument); + } + let (agent, session, _) = self.ids(); + let mut packet = crate::packets::EstateOwnerMessagePacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.agent_data.transaction_id = UUID::zero(); + packet.method_data.invoice = UUID::random()?; + packet.method_data.method = method.into_bytes(); + for value in params { + let mut block = + crate::packets::EstateOwnerMessagePacketParamListBlock::new_with_constructor()?; + block.parameter = value.into_bytes(); + packet.param_list.push(block); + } + self.send_packet(&packet, PacketType::EstateOwnerMessage) + } + pub fn estate_owner_message_with_string_string( + &self, + method: String, + param: String, + ) -> Result<(), Error> { + self.estate_owner_message_with_string_list(method, vec![param]) + } + fn owner_message(&self, method: &str, params: Vec) -> Result<(), Error> { + self.estate_owner_message_with_string_list(method.into(), params) + } + fn access_delta( + &self, + target: UUID, + local: AccessDelta, + all: AccessDelta, + all_estates: bool, + ) -> Result<(), Error> { + let (agent, _, _) = self.ids(); + self.owner_message( + "estateaccessdelta", + vec![ + agent.to_string(), + (if all_estates { + all as u32 + } else { + local as u32 + }) + .to_string(), + target.to_string(), + ], + ) + } + pub fn ban_user(&self, id: UUID, all: bool) -> Result<(), Error> { + self.access_delta( + id, + AccessDelta::BanUser, + AccessDelta::BanUserAllEstates, + all, + ) + } + pub fn unban_user(&self, id: UUID, all: bool) -> Result<(), Error> { + self.access_delta( + id, + AccessDelta::UnbanUser, + AccessDelta::UnbanUserAllEstates, + all, + ) + } + pub fn add_estate_manager(&self, id: UUID, all: bool) -> Result<(), Error> { + self.access_delta( + id, + AccessDelta::AddManager, + AccessDelta::AddManagerAllEstates, + all, + ) + } + pub fn remove_estate_manager(&self, id: UUID, all: bool) -> Result<(), Error> { + self.access_delta( + id, + AccessDelta::RemoveManager, + AccessDelta::RemoveManagerAllEstates, + all, + ) + } + pub fn add_allowed_user(&self, id: UUID, all: bool) -> Result<(), Error> { + self.access_delta( + id, + AccessDelta::AddUserAsAllowed, + AccessDelta::AddAllowedAllEstates, + all, + ) + } + pub fn remove_allowed_user(&self, id: UUID, all: bool) -> Result<(), Error> { + self.access_delta( + id, + AccessDelta::RemoveUserAsAllowed, + AccessDelta::RemoveUserAllowedAllEstates, + all, + ) + } + pub fn add_allowed_group(&self, id: UUID, all: bool) -> Result<(), Error> { + self.access_delta( + id, + AccessDelta::AddGroupAsAllowed, + AccessDelta::AddGroupAllowedAllEstates, + all, + ) + } + pub fn remove_allowed_group(&self, id: UUID, all: bool) -> Result<(), Error> { + self.access_delta( + id, + AccessDelta::RemoveGroupAsAllowed, + AccessDelta::RemoveGroupAllowedAllEstates, + all, + ) + } + pub fn request_info(&self) -> Result<(), Error> { + self.owner_message("getinfo", vec![String::new()]) + } + pub fn kick_user(&self, id: UUID) -> Result<(), Error> { + self.owner_message("kickestate", vec![id.to_string()]) + } + pub fn restart_region_with_method(&self) -> Result<(), Error> { + self.restart_region_with_int32(120) + } + pub fn restart_region_with_int32(&self, delay: i32) -> Result<(), Error> { + self.owner_message("restart", vec![delay.clamp(30, 240).to_string()]) + } + pub fn cancel_restart(&self) -> Result<(), Error> { + self.owner_message("restart", vec!["-1".into()]) + } + pub fn estate_message(&self, message: String) -> Result<(), Error> { + let (_, _, name) = self.ids(); + self.owner_message("instantmessage", vec![name, message]) + } + pub fn simulator_message(&self, message: String) -> Result<(), Error> { + let (agent, _, name) = self.ids(); + self.owner_message( + "simulatormessage", + vec!["-1".into(), "-1".into(), agent.to_string(), name, message], + ) + } + pub fn teleport_home_user(&self, id: UUID) -> Result<(), Error> { + let (agent, _, _) = self.ids(); + self.owner_message("teleporthomeuser", vec![agent.to_string(), id.to_string()]) + } + pub fn teleport_home_all_users(&self) -> Result<(), Error> { + let (agent, _, _) = self.ids(); + self.owner_message("teleporthomeallusers", vec![agent.to_string()]) + } + pub fn request_top_scripts(&self) -> Result<(), Error> { + self.land_stat_request( + 0, + EstateToolsLandStatReportType::TopScripts, + 0, + String::new(), + ) + } + pub fn request_top_colliders(&self) -> Result<(), Error> { + self.land_stat_request( + 0, + EstateToolsLandStatReportType::TopColliders, + 0, + String::new(), + ) + } + + pub fn set_region_debug( + &self, + scripts: bool, + collisions: bool, + physics: bool, + ) -> Result<(), Error> { + self.owner_message( + "setregiondebug", + vec![yn(scripts), yn(collisions), yn(physics)], + ) + } + pub fn set_region_info_udp( + &self, + terraform: bool, + fly: bool, + damage: bool, + resell: bool, + pushing: bool, + join: bool, + agents: f32, + bonus: f32, + maturity: EstateToolsRegionMaturity, + ) -> Result<(), Error> { + self.owner_message( + "setregioninfo", + vec![ + yn(terraform), + yn(fly), + yn(damage), + yn(resell), + agents.to_string(), + bonus.to_string(), + (maturity as i32).to_string(), + yn(pushing), + yn(join), + ], + ) + } + pub fn set_terrain_variables( + &self, + water: f32, + raise: f32, + lower: f32, + estate_sun: bool, + fixed_sun: bool, + sun: f32, + ) -> Result<(), Error> { + self.owner_message( + "setregionterrain", + vec![ + water.to_string(), + raise.to_string(), + lower.to_string(), + yn(estate_sun), + yn(fixed_sun), + sun.to_string(), + "Y".into(), + "N".into(), + "0.00".into(), + ], + ) + } + pub fn set_region_terrain( + &self, + low: UUID, + mid_low: UUID, + mid_high: UUID, + high: UUID, + ) -> Result<(), Error> { + self.owner_message( + "texturedetail", + vec![ + format!("0 {low}"), + format!("1 {mid_low}"), + format!("2 {mid_high}"), + format!("3 {high}"), + ], + )?; + self.owner_message("texturecommit", vec![String::new()]) + } + pub fn set_region_terrain_heights( + &self, + low_sw: f32, + high_sw: f32, + low_nw: f32, + high_nw: f32, + low_se: f32, + high_se: f32, + low_ne: f32, + high_ne: f32, + ) -> Result<(), Error> { + self.owner_message( + "textureheights", + vec![ + format!("0 {low_sw} {high_sw}"), + format!("1 {low_nw} {high_nw}"), + format!("2 {low_se} {high_se}"), + format!("3 {low_ne} {high_ne}"), + ], + )?; + self.owner_message("texturecommit", vec![String::new()]) + } + + pub fn land_stat_request( + &self, + parcel: i32, + report: EstateToolsLandStatReportType, + flags: u32, + filter: String, + ) -> Result<(), Error> { + let (agent, session, _) = self.ids(); + let mut packet = crate::packets::LandStatRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.request_data.filter = filter.into_bytes(); + packet.request_data.parcel_local_id = parcel; + packet.request_data.report_type = report as u32; + packet.request_data.request_flags = flags; + self.send_packet(&packet, PacketType::LandStatRequest) + } + pub fn request_covenant(&self) -> Result<(), Error> { + let (agent, session, _) = self.ids(); + let mut packet = crate::packets::EstateCovenantRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + self.send_packet(&packet, PacketType::EstateCovenantRequest) + } + pub fn sim_wide_return( + &self, + target: UUID, + flag: EstateToolsEstateReturnFlags, + estate_wide: bool, + ) -> Result<(), Error> { + if estate_wide { + return self.owner_message( + "estateobjectreturn", + vec![flag.0.to_string(), target.to_string()], + ); + } + self.ensure_udp_mutation_allowed()?; + let (agent, session, _) = self.ids(); + let mut packet = crate::packets::SimWideDeletesPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.data_block.target_id = target; + packet.data_block.flags = flag.0; + self.send_packet(&packet, PacketType::SimWideDeletes) + } + + async fn capability( + &self, + name: &str, + token: &CancellationToken, + ) -> Result, Error> { + token.throw_if_cancellation_requested()?; + let sim = self.current_sim()?; + if let Some(caps) = sim.native_caps() + && let Some(uri) = caps.capability_uri(name.into())? + { + return Ok(Some(uri)); + } + let Some(seed) = sim.seed_capability() else { + return Ok(None); + }; + let client = sim.native_data_arc().client.clone(); + let (response, body) = client + .native_http_caps_client() + .get(seed, token.clone(), None) + .await?; + if !(200..300).contains(&response.status_code) { + return Ok(None); + } + let OSD::Map(map) = OSDParser::deserialize_with_bytes(body)? else { + return Ok(None); + }; + Ok(map + .get(name) + .and_then(|value| value.as_uri().ok().flatten())) + } + + pub async fn get_region_restart_schedule( + &self, + ct: Option, + ) -> Result, Error> { + let token = ct.unwrap_or_default(); + let Some(uri) = self.capability("RegionSchedule", &token).await? else { + return Ok(None); + }; + let (response, body) = self + .inner + .client + .native_http_caps_client() + .get(uri, token, None) + .await?; + if !(200..300).contains(&response.status_code) { + return Ok(None); + } + let root: Value = match serde_json::from_slice(&body) { + Ok(value) => value, + Err(_) => return Ok(None), + }; + let Some(restart) = root.get("restart") else { + return Ok(None); + }; + let daily = restart.get("type").and_then(Value::as_str) != Some("W"); + let days = if daily { + RegionRestartDays::ALL + } else { + letters_to_days( + restart + .get("days") + .and_then(Value::as_str) + .unwrap_or_default(), + ) + }; + let seconds = restart + .get("time") + .and_then(Value::as_u64) + .unwrap_or_default(); + Ok(Some(RegionRestartSchedule { + daily, + days, + time: Duration::from_secs(seconds), + })) + } + + pub async fn set_region_restart_schedule( + &self, + schedule: RegionRestartSchedule, + ct: Option, + ) -> Result { + let token = ct.unwrap_or_default(); + let Some(uri) = self.capability("RegionSchedule", &token).await? else { + return Ok(false); + }; + if !crate::is_offline_fixture_uri(&uri) + && !self.inner.live_mutations.load(Ordering::Acquire) + { + return Err(Error::InvalidOperation); + } + let mut restart = HashMap::from([ + ( + "type".into(), + OSD::String(if schedule.daily { "D" } else { "W" }.into()), + ), + ( + "time".into(), + OSD::Integer(i32::try_from(schedule.time.as_secs()).unwrap_or(i32::MAX)), + ), + ]); + if !schedule.daily { + restart.insert("days".into(), OSD::String(days_to_letters(schedule.days))); + } + let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([( + "restart".into(), + OSD::Map(restart), + )])))?; + let (response, _) = self + .inner + .client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".into(), + body, + token, + None, + ) + .await?; + Ok((200..300).contains(&response.status_code)) + } + + pub async fn request_covenant_notecard_with_uuid_cancellation_token( + &self, + covenant_id: UUID, + ct: Option, + ) -> Result, Error> { + let simulator = match self.current_sim() { + Ok(simulator) => simulator, + Err(Error::InvalidOperation) => return Ok(None), + Err(error) => return Err(error), + }; + self.request_covenant_notecard_with_uuid_simulator_cancellation_token( + covenant_id, + simulator, + ct, + ) + .await + } + + pub async fn request_covenant_notecard_with_uuid_simulator_cancellation_token( + &self, + covenant_id: UUID, + _simulator: crate::Simulator, + ct: Option, + ) -> Result, Error> { + self.inner + .client + .native_assets()? + .request_asset_with_uuid_asset_type_boolean_source_type_cancellation_token( + covenant_id, + AssetType::Notecard, + true, + SourceType::SimEstate, + ct, + ) + .await + } + + pub fn send_estate_change_info_dataserver( + &self, + estate_name: String, + sun_hour: f32, + flags: RegionFlags, + ) -> Result<(), Error> { + self.owner_message( + "estatechangeinfo", + vec![ + estate_name, + flags.0.to_string(), + ((sun_hour * 1024.0) as i32).to_string(), + ], + ) + } + + pub async fn send_estate_change_info( + &self, + estate_name: String, + sun_hour: f32, + flags: RegionFlags, + ct: Option, + ) -> Result<(), Error> { + let token = ct.unwrap_or_default(); + if let Some(uri) = self.capability("EstateChangeInfo", &token).await? { + if !crate::is_offline_fixture_uri(&uri) + && !self.inner.live_mutations.load(Ordering::Acquire) + { + return Err(Error::InvalidOperation); + } + let has = |flag: RegionFlags| flags.0 & flag.0 != 0; + let payload = OSD::Map(HashMap::from([ + ("estate_name".into(), OSD::String(estate_name.clone())), + ( + "is_sun_fixed".into(), + OSD::Boolean(has(RegionFlags::SUN_FIXED)), + ), + ( + "is_externally_visible".into(), + OSD::Boolean(has(RegionFlags::EXTERNALLY_VISIBLE)), + ), + ( + "allow_direct_teleport".into(), + OSD::Boolean(has(RegionFlags::ALLOW_DIRECT_TELEPORT)), + ), + ( + "deny_anonymous".into(), + OSD::Boolean(has(RegionFlags::DENY_ANONYMOUS)), + ), + ( + "deny_age_unverified".into(), + OSD::Boolean(has(RegionFlags::DENY_AGE_UNVERIFIED)), + ), + ( + "block_bots".into(), + OSD::Boolean(has(RegionFlags::DENY_BOTS)), + ), + ( + "allow_voice_chat".into(), + OSD::Boolean(has(RegionFlags::ALLOW_VOICE)), + ), + ( + "override_public_access".into(), + OSD::Boolean(has(RegionFlags::ALLOW_ACCESS_OVERRIDE)), + ), + ("invoice".into(), OSD::UUID(UUID::random()?)), + ])); + let body = OSDParser::serialize_llsd_xml_bytes(payload)?; + let (response, _) = self + .inner + .client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".into(), + body, + token, + None, + ) + .await?; + if (200..300).contains(&response.status_code) { + return Ok(()); + } + } + self.send_estate_change_info_dataserver(estate_name, sun_hour, flags) + } + + pub async fn set_region_info( + &self, + terraform: bool, + fly: bool, + fly_over: bool, + damage: bool, + resell: bool, + pushing: bool, + join: bool, + agents: f32, + bonus: f32, + block_search: bool, + maturity: EstateToolsRegionMaturity, + ) -> Result<(), Error> { + let token = CancellationToken::default(); + if let Some(uri) = self.capability("DispatchRegionInfo", &token).await? { + if !crate::is_offline_fixture_uri(&uri) + && !self.inner.live_mutations.load(Ordering::Acquire) + { + return Err(Error::InvalidOperation); + } + let payload = OSD::Map(HashMap::from([ + ("block_terraform".into(), OSD::Boolean(terraform)), + ("block_fly".into(), OSD::Boolean(fly)), + ("block_fly_over".into(), OSD::Boolean(fly_over)), + ("allow_damage".into(), OSD::Boolean(damage)), + ("allow_land_resell".into(), OSD::Boolean(resell)), + ("agent_limit".into(), OSD::Real(f64::from(agents))), + ("prim_bonus".into(), OSD::Real(f64::from(bonus))), + ( + "sim_access".into(), + OSD::String((maturity as i32).to_string()), + ), + ("restrict_pushobject".into(), OSD::Boolean(pushing)), + ("allow_parcel_changes".into(), OSD::Boolean(join)), + ("block_parcel_search".into(), OSD::Boolean(block_search)), + ])); + let body = OSDParser::serialize_llsd_xml_bytes(payload)?; + let (response, _) = self + .inner + .client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".into(), + body, + token, + None, + ) + .await?; + if (200..300).contains(&response.status_code) { + return Ok(()); + } + } + self.set_region_info_udp( + terraform, fly, damage, resell, pushing, join, agents, bonus, maturity, + ) + } + + pub async fn send_sim_console_command( + &self, + command: String, + response_timeout: Option, + ct: Option, + ) -> Result, Error> { + let token = ct.unwrap_or_default(); + token.throw_if_cancellation_requested()?; + let console_lock = self.inner.sim_console_lock.lock().fuse(); + let lock_cancelled = token.cancelled().fuse(); + pin_mut!(console_lock, lock_cancelled); + let _console_guard = select_biased! { + guard = console_lock => guard, + () = lock_cancelled => return Err(Error::Cancelled), + }; + token.throw_if_cancellation_requested()?; + let (response_sender, response_receiver) = oneshot::channel(); + let response_sender = Arc::new(Mutex::new(Some(response_sender))); + let sender = Arc::clone(&response_sender); + let _subscription = self.inner.network.subscribe_caps( + "SimConsoleResponse".into(), + crate::CapsEventQueueCallback::from_handler(move |_name, message, _simulator| { + let body = message.serialize().ok().and_then(|map| { + map.get("body") + .or_else(|| map.get("Body")) + .and_then(|value| value.as_string().ok()) + }); + if let Some(sender) = sender + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .take() + { + let _ = sender.send(body); + } + }), + ); + + // Register the event waiter before POSTing: simulators may enqueue the + // response immediately. An unconnected client is still allowed to wait + // so recorded event-queue playback remains fully offline-testable. + match self.capability("SimConsoleAsync", &token).await { + Ok(Some(uri)) => { + if !crate::is_offline_fixture_uri(&uri) + && !self.inner.live_mutations.load(Ordering::Acquire) + { + return Err(Error::InvalidOperation); + } + let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([( + "command".into(), + OSD::String(command), + )])))?; + let (response, _) = 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?; + if !(200..300).contains(&response.status_code) { + return Ok(None); + } + } + Ok(None) | Err(Error::InvalidOperation) => {} + Err(error) => return Err(error), + } + + let (timeout_sender, timeout_receiver) = oneshot::channel(); + let timeout = response_timeout.unwrap_or(Duration::from_secs(10)); + std::thread::Builder::new() + .name("sim-console-timeout".into()) + .spawn(move || { + std::thread::sleep(timeout); + let _ = timeout_sender.send(()); + }) + .map_err(|_| Error::InvalidOperation)?; + let response = response_receiver.fuse(); + let cancelled = token.cancelled().fuse(); + let timeout = timeout_receiver.fuse(); + pin_mut!(response, cancelled, timeout); + select_biased! { + value = response => value.map_err(|_| Error::InvalidOperation), + () = cancelled => Err(Error::Cancelled), + _ = timeout => Ok(None), + } + } + + pub fn upload_terrain(&self, file_data: Vec, file_name: String) -> Result { + if file_data.is_empty() || file_data.len() > 128 * 1024 * 1024 || file_name.is_empty() { + return Err(Error::Argument); + } + let assets = self.inner.client.native_assets()?; + let id = assets.native_stage_estate_upload(file_data)?; + if let Err(error) = self.owner_message("terrain", vec!["upload filename".into(), file_name]) + { + assets.native_cancel_staged_estate_upload(id); + return Err(error); + } + Ok(id) + } +} + +impl EstateToolsInner { + fn handle_land_stat_caps(&self, message: crate::messages::linden::LandStatReplyMessage) { + if self.disposed.load(Ordering::Acquire) || message.report_data_blocks.len() > 100_000 { + return; + } + let tasks = message + .report_data_blocks + .into_iter() + .map(|row| { + let id = row.task_id; + ( + id, + EstateTask { + position: row.location, + score: row.score, + mono_score: row.mono_score, + task_id: id, + task_local_id: row.task_local_id, + task_name: row.task_name, + owner_name: row.owner_name, + }, + ) + }) + .collect(); + let object_count = i32::try_from(message.total_object_count).unwrap_or(i32::MAX); + if message.report_type == EstateToolsLandStatReportType::TopScripts as u32 { + self.events.scripts.emit(TopScriptsReplyEventArgs { + object_count, + tasks, + }); + } else if message.report_type == EstateToolsLandStatReportType::TopColliders as u32 { + self.events.colliders.emit(TopCollidersReplyEventArgs { + object_count, + tasks, + }); + } + } + + fn handle_packet(&self, event: crate::network_manager::RawPacketReceivedEventArgs) { + if self.disposed.load(Ordering::Acquire) || event.data.len() > 8 * 1024 * 1024 { + return; + } + let mut position = 0; + match event.packet_type { + PacketType::EstateOwnerMessage => { + if let Ok(packet) = crate::packets::EstateOwnerMessagePacket::new_from_bytes( + &event.data, + &mut position, + ) { + self.handle_owner_message(packet); + } + } + PacketType::EstateCovenantReply => { + if let Ok(packet) = crate::packets::EstateCovenantReplyPacket::new_from_bytes( + &event.data, + &mut position, + ) { + self.events.covenant.emit(EstateCovenantReplyEventArgs { + covenant_id: packet.data.covenant_id, + timestamp: i64::from(packet.data.covenant_timestamp), + estate_name: wire_text(&packet.data.estate_name), + estate_owner_id: packet.data.estate_owner_id, + }); + } + } + PacketType::LandStatReply => { + if let Ok(packet) = + crate::packets::LandStatReplyPacket::new_from_bytes(&event.data, &mut position) + { + let tasks: HashMap<_, _> = packet + .report_data + .into_iter() + .map(|row| { + let id = row.task_id; + ( + id, + EstateTask { + position: Vector3 { + x: row.location_x, + y: row.location_y, + z: row.location_z, + }, + score: row.score, + mono_score: 0.0, + task_id: id, + task_local_id: row.task_local_id, + task_name: wire_text(&row.task_name), + owner_name: wire_text(&row.owner_name), + }, + ) + }) + .collect(); + let count = + i32::try_from(packet.request_data.total_object_count).unwrap_or(i32::MAX); + if packet.request_data.report_type + == EstateToolsLandStatReportType::TopScripts as u32 + { + self.events.scripts.emit(TopScriptsReplyEventArgs { + object_count: count, + tasks, + }); + } else if packet.request_data.report_type + == EstateToolsLandStatReportType::TopColliders as u32 + { + self.events.colliders.emit(TopCollidersReplyEventArgs { + object_count: count, + tasks, + }); + } + } + } + _ => {} + } + } + + fn handle_owner_message(&self, packet: crate::packets::EstateOwnerMessagePacket) { + let method = wire_text(&packet.method_data.method); + let params: Vec<_> = packet + .param_list + .iter() + .map(|row| row.parameter.as_slice()) + .collect(); + if method == "estateupdateinfo" && params.len() >= 5 { + let Some(owner) = parse_uuid(params[1]) else { + return; + }; + let Some(estate_id) = parse_u32(params[2]) else { + return; + }; + let Some(flags) = parse_u64(params[3]) else { + return; + }; + let Some(sun_hour) = parse_i32(params[4]) else { + return; + }; + self.events.update.emit(EstateUpdateInfoReplyEventArgs { + estate_name: wire_text(params[0]), + estate_owner: owner, + estate_id, + flags: RegionFlags(flags), + sun_hour, + }); + } else if method == "setaccess" && params.len() > 5 { + let Some(estate_id) = parse_u32(params[0]) else { + return; + }; + let Some(access_type) = parse_u32(params[1]) else { + return; + }; + let ids: Vec<_> = params[6..] + .iter() + .filter_map(|value| parse_uuid(value)) + .collect(); + match access_type { + 17 => self.events.users.emit(EstateUsersReplyEventArgs { + estate_id, + count: parse_i32(params[2]).unwrap_or_default(), + allowed_users: ids, + }), + 18 => self.events.groups.emit(EstateGroupsReplyEventArgs { + estate_id, + count: parse_i32(params[3]).unwrap_or_default(), + allowed_groups: ids, + }), + 20 => self.events.bans.emit(EstateBansReplyEventArgs { + estate_id, + count: parse_i32(params[4]).unwrap_or_default(), + banned: ids, + }), + 24 => self.events.managers.emit(EstateManagersReplyEventArgs { + estate_id, + count: parse_i32(params[5]).unwrap_or_default(), + managers: ids, + }), + _ => {} + } + } else if method == "setexperience" && params.len() >= 5 { + let blocked_count = + usize::try_from(parse_i32(params[2]).unwrap_or_default()).unwrap_or_default(); + let trusted_count = + usize::try_from(parse_i32(params[3]).unwrap_or_default()).unwrap_or_default(); + let allowed_count = + usize::try_from(parse_i32(params[4]).unwrap_or_default()).unwrap_or_default(); + let ids: Vec<_> = params[5..] + .iter() + .filter_map(|value| parse_uuid(value)) + .collect(); + let blocked_end = blocked_count.min(ids.len()); + let trusted_end = blocked_end.saturating_add(trusted_count).min(ids.len()); + let allowed_end = trusted_end.saturating_add(allowed_count).min(ids.len()); + self.events + .experiences + .emit(EstateExperienceReplyEventArgs { + blocked: ids[..blocked_end].to_vec(), + trusted: ids[blocked_end..trusted_end].to_vec(), + allowed: ids[trusted_end..allowed_end].to_vec(), + }); + } + } +} + +fn yn(value: bool) -> String { + if value { "Y" } else { "N" }.into() +} + +fn wire_text(value: &[u8]) -> String { + String::from_utf8_lossy(value.split(|byte| *byte == 0).next().unwrap_or_default()).into_owned() +} +fn parse_u32(value: &[u8]) -> Option { + wire_text(value).parse().ok() +} +fn parse_u64(value: &[u8]) -> Option { + wire_text(value).parse().ok() +} +fn parse_i32(value: &[u8]) -> Option { + wire_text(value).parse().ok() +} +fn parse_uuid(value: &[u8]) -> Option { + UUID::new_with_bytes_int32(value.to_vec(), 0) + .ok() + .or_else(|| UUID::new_with_string(wire_text(value)).ok()) +} +fn days_to_letters(days: RegionRestartDays) -> String { + [ + ('S', RegionRestartDays::SUNDAY), + ('M', RegionRestartDays::MONDAY), + ('T', RegionRestartDays::TUESDAY), + ('W', RegionRestartDays::WEDNESDAY), + ('R', RegionRestartDays::THURSDAY), + ('F', RegionRestartDays::FRIDAY), + ('A', RegionRestartDays::SATURDAY), + ] + .into_iter() + .filter_map(|(letter, day)| (days.0 & day.0 != 0).then_some(letter)) + .collect() +} +fn letters_to_days(value: &str) -> RegionRestartDays { + RegionRestartDays(value.chars().fold(0, |days, letter| { + days | match letter.to_ascii_uppercase() { + 'S' => RegionRestartDays::SUNDAY.0, + 'M' => RegionRestartDays::MONDAY.0, + 'T' => RegionRestartDays::TUESDAY.0, + 'W' => RegionRestartDays::WEDNESDAY.0, + 'R' => RegionRestartDays::THURSDAY.0, + 'F' => RegionRestartDays::FRIDAY.0, + 'A' => RegionRestartDays::SATURDAY.0, + _ => 0, + } + })) +} diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index 1152cbc..1303956 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -5924,655 +5924,105 @@ pub enum EstateAssetType { } /// C# type: `T:LibreMetaverse.EstateBansReplyEventArgs`. -pub struct EstateBansReplyEventArgs; -impl EstateBansReplyEventArgs { - /// C# member: `M:LibreMetaverse.EstateBansReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})`. - pub fn new( - estate_id: u32, - count: i32, - banned: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateBansReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})", - ) - } - /// C# member: `P:LibreMetaverse.EstateBansReplyEventArgs.Banned`. - pub fn banned(&self) -> Vec { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.EstateBansReplyEventArgs.Banned") - } - /// C# member: `P:LibreMetaverse.EstateBansReplyEventArgs.Count`. - pub fn count(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.EstateBansReplyEventArgs.Count") - } - /// C# member: `P:LibreMetaverse.EstateBansReplyEventArgs.EstateID`. - pub fn estate_id(&self) -> u32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateBansReplyEventArgs.EstateID" - ) - } -} +/// C# member: `M:LibreMetaverse.EstateBansReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})`. +/// C# member: `P:LibreMetaverse.EstateBansReplyEventArgs.Banned`. +/// C# member: `P:LibreMetaverse.EstateBansReplyEventArgs.Count`. +/// C# member: `P:LibreMetaverse.EstateBansReplyEventArgs.EstateID`. +pub use crate::estate_tools::EstateBansReplyEventArgs; /// C# type: `T:LibreMetaverse.EstateCovenantReplyEventArgs`. -pub struct EstateCovenantReplyEventArgs; -impl EstateCovenantReplyEventArgs { - /// C# member: `M:LibreMetaverse.EstateCovenantReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Int64,System.String,LibreMetaverse.UUID)`. - pub fn new( - covenant_id: libremetaverse_types::UUID, - timestamp: i64, - estate_name: String, - estate_owner_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateCovenantReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Int64,System.String,LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.EstateCovenantReplyEventArgs.CovenantID`. - pub fn covenant_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateCovenantReplyEventArgs.CovenantID" - ) - } - /// C# member: `P:LibreMetaverse.EstateCovenantReplyEventArgs.EstateName`. - pub fn estate_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateCovenantReplyEventArgs.EstateName" - ) - } - /// C# member: `P:LibreMetaverse.EstateCovenantReplyEventArgs.EstateOwnerID`. - pub fn estate_owner_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateCovenantReplyEventArgs.EstateOwnerID" - ) - } - /// C# member: `P:LibreMetaverse.EstateCovenantReplyEventArgs.Timestamp`. - pub fn timestamp(&self) -> i64 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateCovenantReplyEventArgs.Timestamp" - ) - } -} +/// C# member: `M:LibreMetaverse.EstateCovenantReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Int64,System.String,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.EstateCovenantReplyEventArgs.CovenantID`. +/// C# member: `P:LibreMetaverse.EstateCovenantReplyEventArgs.EstateName`. +/// C# member: `P:LibreMetaverse.EstateCovenantReplyEventArgs.EstateOwnerID`. +/// C# member: `P:LibreMetaverse.EstateCovenantReplyEventArgs.Timestamp`. +pub use crate::estate_tools::EstateCovenantReplyEventArgs; /// C# type: `T:LibreMetaverse.EstateExperienceReplyEventArgs`. -pub struct EstateExperienceReplyEventArgs; -impl EstateExperienceReplyEventArgs { - /// C# member: `M:LibreMetaverse.EstateExperienceReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID})`. - pub fn new( - blocked: Vec, - trusted: Vec, - allowed: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateExperienceReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID})", - ) - } - /// C# member: `P:LibreMetaverse.EstateExperienceReplyEventArgs.Allowed`. - pub fn allowed(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateExperienceReplyEventArgs.Allowed" - ) - } - /// C# member: `P:LibreMetaverse.EstateExperienceReplyEventArgs.Blocked`. - pub fn blocked(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateExperienceReplyEventArgs.Blocked" - ) - } - /// C# member: `P:LibreMetaverse.EstateExperienceReplyEventArgs.Trusted`. - pub fn trusted(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateExperienceReplyEventArgs.Trusted" - ) - } -} +/// C# member: `M:LibreMetaverse.EstateExperienceReplyEventArgs.#ctor(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID})`. +/// C# member: `P:LibreMetaverse.EstateExperienceReplyEventArgs.Allowed`. +/// C# member: `P:LibreMetaverse.EstateExperienceReplyEventArgs.Blocked`. +/// C# member: `P:LibreMetaverse.EstateExperienceReplyEventArgs.Trusted`. +pub use crate::estate_tools::EstateExperienceReplyEventArgs; /// C# type: `T:LibreMetaverse.EstateGroupsReplyEventArgs`. -pub struct EstateGroupsReplyEventArgs; -impl EstateGroupsReplyEventArgs { - /// C# member: `M:LibreMetaverse.EstateGroupsReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})`. - pub fn new( - estate_id: u32, - count: i32, - allowed_groups: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateGroupsReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})", - ) - } - /// C# member: `P:LibreMetaverse.EstateGroupsReplyEventArgs.AllowedGroups`. - pub fn allowed_groups(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateGroupsReplyEventArgs.AllowedGroups" - ) - } - /// C# member: `P:LibreMetaverse.EstateGroupsReplyEventArgs.Count`. - pub fn count(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateGroupsReplyEventArgs.Count" - ) - } - /// C# member: `P:LibreMetaverse.EstateGroupsReplyEventArgs.EstateID`. - pub fn estate_id(&self) -> u32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateGroupsReplyEventArgs.EstateID" - ) - } -} +/// C# member: `M:LibreMetaverse.EstateGroupsReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})`. +/// C# member: `P:LibreMetaverse.EstateGroupsReplyEventArgs.AllowedGroups`. +/// C# member: `P:LibreMetaverse.EstateGroupsReplyEventArgs.Count`. +/// C# member: `P:LibreMetaverse.EstateGroupsReplyEventArgs.EstateID`. +pub use crate::estate_tools::EstateGroupsReplyEventArgs; /// C# type: `T:LibreMetaverse.EstateManagersReplyEventArgs`. -pub struct EstateManagersReplyEventArgs; -impl EstateManagersReplyEventArgs { - /// C# member: `M:LibreMetaverse.EstateManagersReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})`. - pub fn new( - estate_id: u32, - count: i32, - managers: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateManagersReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})", - ) - } - /// C# member: `P:LibreMetaverse.EstateManagersReplyEventArgs.Count`. - pub fn count(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateManagersReplyEventArgs.Count" - ) - } - /// C# member: `P:LibreMetaverse.EstateManagersReplyEventArgs.EstateID`. - pub fn estate_id(&self) -> u32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateManagersReplyEventArgs.EstateID" - ) - } - /// C# member: `P:LibreMetaverse.EstateManagersReplyEventArgs.Managers`. - pub fn managers(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateManagersReplyEventArgs.Managers" - ) - } -} +/// C# member: `M:LibreMetaverse.EstateManagersReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})`. +/// C# member: `P:LibreMetaverse.EstateManagersReplyEventArgs.Count`. +/// C# member: `P:LibreMetaverse.EstateManagersReplyEventArgs.EstateID`. +/// C# member: `P:LibreMetaverse.EstateManagersReplyEventArgs.Managers`. +pub use crate::estate_tools::EstateManagersReplyEventArgs; /// C# type: `T:LibreMetaverse.EstateTask`. -pub struct EstateTask { - /// C# member: `F:LibreMetaverse.EstateTask.MonoScore`. - pub mono_score: f32, - /// C# member: `F:LibreMetaverse.EstateTask.OwnerName`. - pub owner_name: String, - /// C# member: `F:LibreMetaverse.EstateTask.Position`. - pub position: libremetaverse_types::Vector3, - /// C# member: `F:LibreMetaverse.EstateTask.Score`. - pub score: f32, - /// C# member: `F:LibreMetaverse.EstateTask.TaskID`. - pub task_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.EstateTask.TaskLocalID`. - pub task_local_id: u32, - /// C# member: `F:LibreMetaverse.EstateTask.TaskName`. - pub task_name: String, -} -impl EstateTask { - /// C# member: `M:LibreMetaverse.EstateTask.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTask.#ctor") - } -} +/// C# member: `F:LibreMetaverse.EstateTask.MonoScore`. +/// C# member: `F:LibreMetaverse.EstateTask.OwnerName`. +/// C# member: `F:LibreMetaverse.EstateTask.Position`. +/// C# member: `F:LibreMetaverse.EstateTask.Score`. +/// C# member: `F:LibreMetaverse.EstateTask.TaskID`. +/// C# member: `F:LibreMetaverse.EstateTask.TaskLocalID`. +/// C# member: `F:LibreMetaverse.EstateTask.TaskName`. +/// C# member: `M:LibreMetaverse.EstateTask.#ctor`. +pub use crate::estate_tools::EstateTask; /// C# type: `T:LibreMetaverse.EstateTools`. -pub struct EstateTools { - /// C# member: `F:LibreMetaverse.EstateTools.GroundTextureLimits`. - pub ground_texture_limits: libremetaverse::EstateToolsGroundTextureHeightSettings, - /// C# member: `F:LibreMetaverse.EstateTools.GroundTextures`. - pub ground_textures: libremetaverse::EstateToolsGroundTextureSettings, -} -impl EstateTools { - /// C# member: `E:LibreMetaverse.EstateTools.EstateBansReply`. - pub fn subscribe_estate_bans_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::EstateBansReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.EstateTools.EstateBansReply") - } - /// C# member: `E:LibreMetaverse.EstateTools.EstateCovenantReply`. - pub fn subscribe_estate_covenant_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::EstateCovenantReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.EstateTools.EstateCovenantReply") - } - /// C# member: `E:LibreMetaverse.EstateTools.EstateExperienceReply`. - pub fn subscribe_estate_experience_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::EstateExperienceReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.EstateTools.EstateExperienceReply" - ) - } - /// C# member: `E:LibreMetaverse.EstateTools.EstateGroupsReply`. - pub fn subscribe_estate_groups_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::EstateGroupsReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.EstateTools.EstateGroupsReply") - } - /// C# member: `E:LibreMetaverse.EstateTools.EstateManagersReply`. - pub fn subscribe_estate_managers_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::EstateManagersReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.EstateTools.EstateManagersReply") - } - /// C# member: `E:LibreMetaverse.EstateTools.EstateUpdateInfoReply`. - pub fn subscribe_estate_update_info_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::EstateUpdateInfoReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.EstateTools.EstateUpdateInfoReply" - ) - } - /// C# member: `E:LibreMetaverse.EstateTools.EstateUsersReply`. - pub fn subscribe_estate_users_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::EstateUsersReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.EstateTools.EstateUsersReply") - } - /// C# member: `E:LibreMetaverse.EstateTools.TopCollidersReply`. - pub fn subscribe_top_colliders_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::TopCollidersReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.EstateTools.TopCollidersReply") - } - /// C# member: `E:LibreMetaverse.EstateTools.TopScriptsReply`. - pub fn subscribe_top_scripts_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::TopScriptsReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.EstateTools.TopScriptsReply") - } - /// C# member: `M:LibreMetaverse.EstateTools.#ctor(LibreMetaverse.GridClient)`. - pub fn new(client: libremetaverse::GridClient) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.AddAllowedGroup(LibreMetaverse.UUID,System.Boolean)`. - pub fn add_allowed_group( - &self, - group_id: libremetaverse_types::UUID, - all_estates: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.AddAllowedGroup(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.AddAllowedUser(LibreMetaverse.UUID,System.Boolean)`. - pub fn add_allowed_user( - &self, - user_id: libremetaverse_types::UUID, - all_estates: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.AddAllowedUser(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.AddEstateManager(LibreMetaverse.UUID,System.Boolean)`. - pub fn add_estate_manager( - &self, - user_id: libremetaverse_types::UUID, - all_estates: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.AddEstateManager(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.BanUser(LibreMetaverse.UUID,System.Boolean)`. - pub fn ban_user( - &self, - user_id: libremetaverse_types::UUID, - all_estates: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.BanUser(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.CancelRestart`. - pub fn cancel_restart(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTools.CancelRestart") - } - /// C# member: `M:LibreMetaverse.EstateTools.Dispose`. - pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTools.Dispose") - } - /// C# member: `M:LibreMetaverse.EstateTools.EstateMessage(System.String)`. - pub fn estate_message(&self, message: String) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.EstateMessage(System.String)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.EstateOwnerMessage(System.String,System.Collections.Generic.List{System.String})`. - pub fn estate_owner_message_with_string_list( - &self, - method: String, - list_params: Vec, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.EstateOwnerMessage(System.String,System.Collections.Generic.List{System.String})", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.EstateOwnerMessage(System.String,System.String)`. - pub fn estate_owner_message_with_string_string( - &self, - method: String, - param: String, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.EstateOwnerMessage(System.String,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.GetRegionRestartScheduleAsync(System.Threading.CancellationToken)`. - pub async fn get_region_restart_schedule( - &self, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.GetRegionRestartScheduleAsync(System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.KickUser(LibreMetaverse.UUID)`. - pub fn kick_user(&self, user_id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.KickUser(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.LandStatRequest(System.Int32,LibreMetaverse.EstateTools.LandStatReportType,System.UInt32,System.String)`. - pub fn land_stat_request( - &self, - parcel_local_id: i32, - report_type: libremetaverse::EstateToolsLandStatReportType, - request_flags: u32, - filter: String, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.LandStatRequest(System.Int32,LibreMetaverse.EstateTools.LandStatReportType,System.UInt32,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.RemoveAllowedGroup(LibreMetaverse.UUID,System.Boolean)`. - pub fn remove_allowed_group( - &self, - group_id: libremetaverse_types::UUID, - all_estates: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.RemoveAllowedGroup(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.RemoveAllowedUser(LibreMetaverse.UUID,System.Boolean)`. - pub fn remove_allowed_user( - &self, - user_id: libremetaverse_types::UUID, - all_estates: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.RemoveAllowedUser(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.RemoveEstateManager(LibreMetaverse.UUID,System.Boolean)`. - pub fn remove_estate_manager( - &self, - user_id: libremetaverse_types::UUID, - all_estates: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.RemoveEstateManager(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.RequestCovenant`. - pub fn request_covenant(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTools.RequestCovenant") - } - /// C# member: `M:LibreMetaverse.EstateTools.RequestCovenantNotecardAsync(LibreMetaverse.UUID,LibreMetaverse.Simulator,System.Threading.CancellationToken)`. - pub async fn request_covenant_notecard_with_uuid_simulator_cancellation_token( - &self, - covenant_id: libremetaverse_types::UUID, - simulator: libremetaverse::Simulator, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.RequestCovenantNotecardAsync(LibreMetaverse.UUID,LibreMetaverse.Simulator,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.RequestCovenantNotecardAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn request_covenant_notecard_with_uuid_cancellation_token( - &self, - covenant_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.RequestCovenantNotecardAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.RequestInfo`. - pub fn request_info(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTools.RequestInfo") - } - /// C# member: `M:LibreMetaverse.EstateTools.RequestTopColliders`. - pub fn request_top_colliders(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTools.RequestTopColliders") - } - /// C# member: `M:LibreMetaverse.EstateTools.RequestTopScripts`. - pub fn request_top_scripts(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTools.RequestTopScripts") - } - /// C# member: `M:LibreMetaverse.EstateTools.RestartRegion`. - pub fn restart_region_with_method(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTools.RestartRegion") - } - /// C# member: `M:LibreMetaverse.EstateTools.RestartRegion(System.Int32)`. - pub fn restart_region_with_int32(&self, delay: i32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.RestartRegion(System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SendEstateChangeInfoAsync(System.String,System.Single,LibreMetaverse.RegionFlags,System.Threading.CancellationToken)`. - pub async fn send_estate_change_info( - &self, - estate_name: String, - sun_hour: f32, - flags: libremetaverse::RegionFlags, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SendEstateChangeInfoAsync(System.String,System.Single,LibreMetaverse.RegionFlags,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SendEstateChangeInfoDataserver(System.String,System.Single,LibreMetaverse.RegionFlags)`. - pub fn send_estate_change_info_dataserver( - &self, - estate_name: String, - sun_hour: f32, - flags: libremetaverse::RegionFlags, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SendEstateChangeInfoDataserver(System.String,System.Single,LibreMetaverse.RegionFlags)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SendSimConsoleCommandAsync(System.String,System.TimeSpan,System.Threading.CancellationToken)`. - pub async fn send_sim_console_command( - &self, - command: String, - response_timeout: Option, - cancellation_token: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SendSimConsoleCommandAsync(System.String,System.TimeSpan,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SetRegionDebug(System.Boolean,System.Boolean,System.Boolean)`. - pub fn set_region_debug( - &self, - disable_scripts: bool, - disable_collisions: bool, - disable_physics: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SetRegionDebug(System.Boolean,System.Boolean,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SetRegionInfoAsync(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Single,System.Single,System.Boolean,LibreMetaverse.EstateTools.RegionMaturity)`. - pub async fn set_region_info( - &self, - block_terraform: bool, - block_fly: bool, - block_fly_over: bool, - allow_damage: bool, - allow_land_resell: bool, - restrict_pushing: bool, - allow_parcel_join_divide: bool, - agent_limit: f32, - object_bonus: f32, - block_parcel_search: bool, - maturity: libremetaverse::EstateToolsRegionMaturity, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SetRegionInfoAsync(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Single,System.Single,System.Boolean,LibreMetaverse.EstateTools.RegionMaturity)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SetRegionInfoUdp(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Single,System.Single,LibreMetaverse.EstateTools.RegionMaturity)`. - pub fn set_region_info_udp( - &self, - block_terraform: bool, - block_fly: bool, - allow_damage: bool, - allow_land_resell: bool, - restrict_pushing: bool, - allow_parcel_join_divide: bool, - agent_limit: f32, - object_bonus: f32, - maturity: libremetaverse::EstateToolsRegionMaturity, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SetRegionInfoUdp(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Single,System.Single,LibreMetaverse.EstateTools.RegionMaturity)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SetRegionRestartScheduleAsync(LibreMetaverse.RegionRestartSchedule,System.Threading.CancellationToken)`. - pub async fn set_region_restart_schedule( - &self, - schedule: libremetaverse::RegionRestartSchedule, - cancellation_token: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SetRegionRestartScheduleAsync(LibreMetaverse.RegionRestartSchedule,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SetRegionTerrain(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn set_region_terrain( - &self, - low: libremetaverse_types::UUID, - mid_low: libremetaverse_types::UUID, - mid_high: libremetaverse_types::UUID, - high: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SetRegionTerrain(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SetRegionTerrainHeights(System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single)`. - pub fn set_region_terrain_heights( - &self, - low_sw: f32, - high_sw: f32, - low_nw: f32, - high_nw: f32, - low_se: f32, - high_se: f32, - low_ne: f32, - high_ne: f32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SetRegionTerrainHeights(System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SetTerrainVariables(System.Single,System.Single,System.Single,System.Boolean,System.Boolean,System.Single)`. - pub fn set_terrain_variables( - &self, - water_height: f32, - terrain_raise_limit: f32, - terrain_lower_limit: f32, - use_estate_sun: bool, - fixed_sun: bool, - sun_position: f32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SetTerrainVariables(System.Single,System.Single,System.Single,System.Boolean,System.Boolean,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SimWideReturn(LibreMetaverse.UUID,LibreMetaverse.EstateTools.EstateReturnFlags,System.Boolean)`. - pub fn sim_wide_return( - &self, - target: libremetaverse_types::UUID, - flag: libremetaverse::EstateToolsEstateReturnFlags, - estate_wide: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SimWideReturn(LibreMetaverse.UUID,LibreMetaverse.EstateTools.EstateReturnFlags,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.SimulatorMessage(System.String)`. - pub fn simulator_message(&self, message: String) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.SimulatorMessage(System.String)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.TeleportHomeAllUsers`. - pub fn teleport_home_all_users(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.EstateTools.TeleportHomeAllUsers") - } - /// C# member: `M:LibreMetaverse.EstateTools.TeleportHomeUser(LibreMetaverse.UUID)`. - pub fn teleport_home_user(&self, pest: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.TeleportHomeUser(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.UnbanUser(LibreMetaverse.UUID,System.Boolean)`. - pub fn unban_user( - &self, - user_id: libremetaverse_types::UUID, - all_estates: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.UnbanUser(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.EstateTools.UploadTerrain(System.Byte[],System.String)`. - pub fn upload_terrain( - &self, - file_data: Vec, - file_name: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.UploadTerrain(System.Byte[],System.String)", - ) - } -} +/// C# member: `E:LibreMetaverse.EstateTools.EstateBansReply`. +/// C# member: `E:LibreMetaverse.EstateTools.EstateCovenantReply`. +/// C# member: `E:LibreMetaverse.EstateTools.EstateExperienceReply`. +/// C# member: `E:LibreMetaverse.EstateTools.EstateGroupsReply`. +/// C# member: `E:LibreMetaverse.EstateTools.EstateManagersReply`. +/// C# member: `E:LibreMetaverse.EstateTools.EstateUpdateInfoReply`. +/// C# member: `E:LibreMetaverse.EstateTools.EstateUsersReply`. +/// C# member: `E:LibreMetaverse.EstateTools.TopCollidersReply`. +/// C# member: `E:LibreMetaverse.EstateTools.TopScriptsReply`. +/// C# member: `F:LibreMetaverse.EstateTools.GroundTextureLimits`. +/// C# member: `F:LibreMetaverse.EstateTools.GroundTextures`. +/// C# member: `M:LibreMetaverse.EstateTools.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.EstateTools.AddAllowedGroup(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.AddAllowedUser(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.AddEstateManager(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.BanUser(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.CancelRestart`. +/// C# member: `M:LibreMetaverse.EstateTools.Dispose`. +/// C# member: `M:LibreMetaverse.EstateTools.EstateMessage(System.String)`. +/// C# member: `M:LibreMetaverse.EstateTools.EstateOwnerMessage(System.String,System.Collections.Generic.List{System.String})`. +/// C# member: `M:LibreMetaverse.EstateTools.EstateOwnerMessage(System.String,System.String)`. +/// C# member: `M:LibreMetaverse.EstateTools.GetRegionRestartScheduleAsync(System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.EstateTools.KickUser(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.EstateTools.LandStatRequest(System.Int32,LibreMetaverse.EstateTools.LandStatReportType,System.UInt32,System.String)`. +/// C# member: `M:LibreMetaverse.EstateTools.RemoveAllowedGroup(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.RemoveAllowedUser(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.RemoveEstateManager(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.RequestCovenant`. +/// C# member: `M:LibreMetaverse.EstateTools.RequestCovenantNotecardAsync(LibreMetaverse.UUID,LibreMetaverse.Simulator,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.EstateTools.RequestCovenantNotecardAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.EstateTools.RequestInfo`. +/// C# member: `M:LibreMetaverse.EstateTools.RequestTopColliders`. +/// C# member: `M:LibreMetaverse.EstateTools.RequestTopScripts`. +/// C# member: `M:LibreMetaverse.EstateTools.RestartRegion`. +/// C# member: `M:LibreMetaverse.EstateTools.RestartRegion(System.Int32)`. +/// C# member: `M:LibreMetaverse.EstateTools.SendEstateChangeInfoAsync(System.String,System.Single,LibreMetaverse.RegionFlags,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.EstateTools.SendEstateChangeInfoDataserver(System.String,System.Single,LibreMetaverse.RegionFlags)`. +/// C# member: `M:LibreMetaverse.EstateTools.SendSimConsoleCommandAsync(System.String,System.TimeSpan,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.EstateTools.SetRegionDebug(System.Boolean,System.Boolean,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.SetRegionInfoAsync(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Single,System.Single,System.Boolean,LibreMetaverse.EstateTools.RegionMaturity)`. +/// C# member: `M:LibreMetaverse.EstateTools.SetRegionInfoUdp(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Single,System.Single,LibreMetaverse.EstateTools.RegionMaturity)`. +/// C# member: `M:LibreMetaverse.EstateTools.SetRegionRestartScheduleAsync(LibreMetaverse.RegionRestartSchedule,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.EstateTools.SetRegionTerrain(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.EstateTools.SetRegionTerrainHeights(System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.EstateTools.SetTerrainVariables(System.Single,System.Single,System.Single,System.Boolean,System.Boolean,System.Single)`. +/// C# member: `M:LibreMetaverse.EstateTools.SimWideReturn(LibreMetaverse.UUID,LibreMetaverse.EstateTools.EstateReturnFlags,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.SimulatorMessage(System.String)`. +/// C# member: `M:LibreMetaverse.EstateTools.TeleportHomeAllUsers`. +/// C# member: `M:LibreMetaverse.EstateTools.TeleportHomeUser(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.EstateTools.UnbanUser(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `M:LibreMetaverse.EstateTools.UploadTerrain(System.Byte[],System.String)`. +pub use crate::estate_tools::EstateTools; /// C# type: `T:LibreMetaverse.EstateTools.EstateAccessDelta`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -6699,82 +6149,20 @@ pub enum EstateToolsRegionMaturity { } /// C# type: `T:LibreMetaverse.EstateUpdateInfoReplyEventArgs`. -pub struct EstateUpdateInfoReplyEventArgs; -impl EstateUpdateInfoReplyEventArgs { - /// C# member: `M:LibreMetaverse.EstateUpdateInfoReplyEventArgs.#ctor(System.String,LibreMetaverse.UUID,System.UInt32,LibreMetaverse.RegionFlags,System.Int32)`. - pub fn new( - estate_name: String, - estate_owner: libremetaverse_types::UUID, - estate_id: u32, - flags: libremetaverse::RegionFlags, - sun_hour: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateUpdateInfoReplyEventArgs.#ctor(System.String,LibreMetaverse.UUID,System.UInt32,LibreMetaverse.RegionFlags,System.Int32)", - ) - } - /// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateID`. - pub fn estate_id(&self) -> u32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateID" - ) - } - /// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateName`. - pub fn estate_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateName" - ) - } - /// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateOwner`. - pub fn estate_owner(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateOwner" - ) - } - /// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.Flags`. - pub fn flags(&self) -> libremetaverse::RegionFlags { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.Flags" - ) - } - /// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.SunHour`. - pub fn sun_hour(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.SunHour" - ) - } -} +/// C# member: `M:LibreMetaverse.EstateUpdateInfoReplyEventArgs.#ctor(System.String,LibreMetaverse.UUID,System.UInt32,LibreMetaverse.RegionFlags,System.Int32)`. +/// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateID`. +/// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateName`. +/// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.EstateOwner`. +/// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.Flags`. +/// C# member: `P:LibreMetaverse.EstateUpdateInfoReplyEventArgs.SunHour`. +pub use crate::estate_tools::EstateUpdateInfoReplyEventArgs; /// C# type: `T:LibreMetaverse.EstateUsersReplyEventArgs`. -pub struct EstateUsersReplyEventArgs; -impl EstateUsersReplyEventArgs { - /// C# member: `M:LibreMetaverse.EstateUsersReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})`. - pub fn new( - estate_id: u32, - count: i32, - allowed_users: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateUsersReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})", - ) - } - /// C# member: `P:LibreMetaverse.EstateUsersReplyEventArgs.AllowedUsers`. - pub fn allowed_users(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateUsersReplyEventArgs.AllowedUsers" - ) - } - /// C# member: `P:LibreMetaverse.EstateUsersReplyEventArgs.Count`. - pub fn count(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.EstateUsersReplyEventArgs.Count") - } - /// C# member: `P:LibreMetaverse.EstateUsersReplyEventArgs.EstateID`. - pub fn estate_id(&self) -> u32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.EstateUsersReplyEventArgs.EstateID" - ) - } -} +/// C# member: `M:LibreMetaverse.EstateUsersReplyEventArgs.#ctor(System.UInt32,System.Int32,System.Collections.Generic.List{LibreMetaverse.UUID})`. +/// C# member: `P:LibreMetaverse.EstateUsersReplyEventArgs.AllowedUsers`. +/// C# member: `P:LibreMetaverse.EstateUsersReplyEventArgs.Count`. +/// C# member: `P:LibreMetaverse.EstateUsersReplyEventArgs.EstateID`. +pub use crate::estate_tools::EstateUsersReplyEventArgs; /// C# type: `T:LibreMetaverse.EventInfoReplyEventArgs`. /// C# member: `M:LibreMetaverse.EventInfoReplyEventArgs.#ctor(LibreMetaverse.DirectoryManager.EventInfo)`. @@ -7267,11 +6655,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Estate`. pub fn estate(&self) -> libremetaverse::EstateTools { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Estate") + /* native client-core implementation */ + self.native_estate() + .unwrap_or_else(|_| panic!("failed to construct EstateTools")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Estate`. pub fn set_estate(&mut self, value: libremetaverse::EstateTools) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Estate") + /* native client-core implementation */ + self.native_set_estate(value) } /// C# member: `P:LibreMetaverse.GridClient.Friends`. pub fn friends(&self) -> libremetaverse::FriendsManager { @@ -15510,37 +14901,11 @@ impl RegionRestartDays { } /// C# type: `T:LibreMetaverse.RegionRestartSchedule`. -pub struct RegionRestartSchedule; -impl RegionRestartSchedule { - /// C# member: `M:LibreMetaverse.RegionRestartSchedule.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.RegionRestartSchedule.#ctor") - } - /// C# member: `P:LibreMetaverse.RegionRestartSchedule.Days`. - pub fn days(&self) -> libremetaverse::RegionRestartDays { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RegionRestartSchedule.Days") - } - /// Setter for C# member: `P:LibreMetaverse.RegionRestartSchedule.Days`. - pub fn set_days(&mut self, value: libremetaverse::RegionRestartDays) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RegionRestartSchedule.Days") - } - /// C# member: `P:LibreMetaverse.RegionRestartSchedule.IsDaily`. - pub fn is_daily(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RegionRestartSchedule.IsDaily") - } - /// Setter for C# member: `P:LibreMetaverse.RegionRestartSchedule.IsDaily`. - pub fn set_is_daily(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RegionRestartSchedule.IsDaily") - } - /// C# member: `P:LibreMetaverse.RegionRestartSchedule.Time`. - pub fn time(&self) -> std::time::Duration { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RegionRestartSchedule.Time") - } - /// Setter for C# member: `P:LibreMetaverse.RegionRestartSchedule.Time`. - pub fn set_time(&mut self, value: std::time::Duration) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.RegionRestartSchedule.Time") - } -} +/// C# member: `M:LibreMetaverse.RegionRestartSchedule.#ctor`. +/// C# member: `P:LibreMetaverse.RegionRestartSchedule.Days`. +/// C# member: `P:LibreMetaverse.RegionRestartSchedule.IsDaily`. +/// C# member: `P:LibreMetaverse.RegionRestartSchedule.Time`. +pub use crate::estate_tools::RegionRestartSchedule; /// C# type: `T:LibreMetaverse.Repeat`. pub struct Repeat; @@ -17132,58 +16497,16 @@ impl TimingSettings { } /// C# type: `T:LibreMetaverse.TopCollidersReplyEventArgs`. -pub struct TopCollidersReplyEventArgs; -impl TopCollidersReplyEventArgs { - /// C# member: `M:LibreMetaverse.TopCollidersReplyEventArgs.#ctor(System.Int32,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.EstateTask})`. - pub fn new( - object_count: i32, - tasks: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.TopCollidersReplyEventArgs.#ctor(System.Int32,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.EstateTask})", - ) - } - /// C# member: `P:LibreMetaverse.TopCollidersReplyEventArgs.ObjectCount`. - pub fn object_count(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TopCollidersReplyEventArgs.ObjectCount" - ) - } - /// C# member: `P:LibreMetaverse.TopCollidersReplyEventArgs.Tasks`. - pub fn tasks( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TopCollidersReplyEventArgs.Tasks" - ) - } -} +/// C# member: `M:LibreMetaverse.TopCollidersReplyEventArgs.#ctor(System.Int32,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.EstateTask})`. +/// C# member: `P:LibreMetaverse.TopCollidersReplyEventArgs.ObjectCount`. +/// C# member: `P:LibreMetaverse.TopCollidersReplyEventArgs.Tasks`. +pub use crate::estate_tools::TopCollidersReplyEventArgs; /// C# type: `T:LibreMetaverse.TopScriptsReplyEventArgs`. -pub struct TopScriptsReplyEventArgs; -impl TopScriptsReplyEventArgs { - /// C# member: `M:LibreMetaverse.TopScriptsReplyEventArgs.#ctor(System.Int32,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.EstateTask})`. - pub fn new( - object_count: i32, - tasks: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.TopScriptsReplyEventArgs.#ctor(System.Int32,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.EstateTask})", - ) - } - /// C# member: `P:LibreMetaverse.TopScriptsReplyEventArgs.ObjectCount`. - pub fn object_count(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TopScriptsReplyEventArgs.ObjectCount" - ) - } - /// C# member: `P:LibreMetaverse.TopScriptsReplyEventArgs.Tasks`. - pub fn tasks( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.TopScriptsReplyEventArgs.Tasks") - } -} +/// C# member: `M:LibreMetaverse.TopScriptsReplyEventArgs.#ctor(System.Int32,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.EstateTask})`. +/// C# member: `P:LibreMetaverse.TopScriptsReplyEventArgs.ObjectCount`. +/// C# member: `P:LibreMetaverse.TopScriptsReplyEventArgs.Tasks`. +pub use crate::estate_tools::TopScriptsReplyEventArgs; /// C# type: `T:LibreMetaverse.TransactionFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -52213,107 +51536,21 @@ pub mod interfaces { pub mod marketplace { /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs`. - pub struct MarketplaceErrorEventArgs; - impl MarketplaceErrorEventArgs { - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.#ctor(System.String,System.Exception)`. - pub fn new( - message: String, - ex: Option>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.#ctor(System.String,System.Exception)", - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.Exception`. - pub fn exception( - &self, - ) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.Exception" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.Message`. - pub fn message(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.Message" - ) - } - } + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.#ctor(System.String,System.Exception)`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.Exception`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs.Message`. + pub use crate::marketplace_runtime::MarketplaceErrorEventArgs; /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceFolderClassifier`. - pub struct MarketplaceFolderClassifier; - impl MarketplaceFolderClassifier { - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetAllListingFolderIds(LibreMetaverse.Inventory)`. - pub fn get_all_listing_folder_ids( - inventory: &libremetaverse::Inventory, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetAllListingFolderIds(LibreMetaverse.Inventory)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetListingFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. - pub fn get_listing_folder( - folder_id: libremetaverse_types::UUID, - inventory: &libremetaverse::Inventory, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetListingFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetListingsRoot(LibreMetaverse.Inventory)`. - pub fn get_listings_root( - inventory: &libremetaverse::Inventory, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetListingsRoot(LibreMetaverse.Inventory)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetRole(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. - pub fn get_role( - folder_id: libremetaverse_types::UUID, - inventory: &libremetaverse::Inventory, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetRole(LibreMetaverse.UUID,LibreMetaverse.Inventory)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetStockCount(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. - pub fn get_stock_count( - version_folder_uuid: libremetaverse_types::UUID, - inventory: &libremetaverse::Inventory, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetStockCount(LibreMetaverse.UUID,LibreMetaverse.Inventory)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetVersionFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. - pub fn get_version_folder( - listing_folder_uuid: libremetaverse_types::UUID, - inventory: &libremetaverse::Inventory, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetVersionFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.IsMarketplaceFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. - pub fn is_marketplace_folder( - folder_id: libremetaverse_types::UUID, - inventory: &libremetaverse::Inventory, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.IsMarketplaceFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.ValidateListing(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. - pub fn validate_listing( - listing_folder_uuid: libremetaverse_types::UUID, - inventory: &libremetaverse::Inventory, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.ValidateListing(LibreMetaverse.UUID,LibreMetaverse.Inventory)", - ) - } - } + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetAllListingFolderIds(LibreMetaverse.Inventory)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetListingFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetListingsRoot(LibreMetaverse.Inventory)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetRole(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetStockCount(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.GetVersionFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.IsMarketplaceFolder(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceFolderClassifier.ValidateListing(LibreMetaverse.UUID,LibreMetaverse.Inventory)`. + pub use crate::marketplace_runtime::MarketplaceFolderClassifier; /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceFolderRole`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -52334,127 +51571,21 @@ pub mod marketplace { } /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceListing`. - pub struct MarketplaceListing; - impl MarketplaceListing { - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceListing.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceListing.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.EditUrl`. - pub fn edit_url(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.EditUrl" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.EditUrl`. - pub fn set_edit_url(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.EditUrl" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.LastSyncUtc`. - pub fn last_sync_utc(&self) -> std::time::SystemTime { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.LastSyncUtc" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.LastSyncUtc`. - pub fn set_last_sync_utc(&mut self, value: std::time::SystemTime) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.LastSyncUtc" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.ListingFolderUUID`. - pub fn listing_folder_uuid(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.ListingFolderUUID" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.ListingFolderUUID`. - pub fn set_listing_folder_uuid(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.ListingFolderUUID" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.ListingId`. - pub fn listing_id(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.ListingId" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.ListingId`. - pub fn set_listing_id(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.ListingId" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.Status`. - pub fn status(&self) -> libremetaverse::marketplace::MarketplaceListingStatus { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.Status" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.Status`. - pub fn set_status(&mut self, value: libremetaverse::marketplace::MarketplaceListingStatus) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.Status" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.StockCount`. - pub fn stock_count(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.StockCount" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.StockCount`. - pub fn set_stock_count(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.StockCount" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.VersionFolderUUID`. - pub fn version_folder_uuid(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.VersionFolderUUID" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.VersionFolderUUID`. - pub fn set_version_folder_uuid(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListing.VersionFolderUUID" - ) - } - } + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceListing.#ctor`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.EditUrl`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.LastSyncUtc`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.ListingFolderUUID`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.ListingId`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.Status`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.StockCount`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListing.VersionFolderUUID`. + pub use crate::marketplace_runtime::MarketplaceListing; /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs`. - pub struct MarketplaceListingChangedEventArgs; - impl MarketplaceListingChangedEventArgs { - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.#ctor(System.Int32,LibreMetaverse.Marketplace.MarketplaceListing)`. - pub fn new( - listing_id: i32, - listing: Option>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.#ctor(System.Int32,LibreMetaverse.Marketplace.MarketplaceListing)", - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.Listing`. - pub fn listing( - &self, - ) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.Listing" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.ListingId`. - pub fn listing_id(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.ListingId" - ) - } - } + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.#ctor(System.Int32,LibreMetaverse.Marketplace.MarketplaceListing)`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.Listing`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs.ListingId`. + pub use crate::marketplace_runtime::MarketplaceListingChangedEventArgs; /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceListingStatus`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -52469,159 +51600,25 @@ pub mod marketplace { } /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceListingsSyncedEventArgs`. - pub struct MarketplaceListingsSyncedEventArgs; - impl MarketplaceListingsSyncedEventArgs { - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceListingsSyncedEventArgs.#ctor(System.Collections.Generic.IReadOnlyList{LibreMetaverse.Marketplace.MarketplaceListing})`. - pub fn new( - listings: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceListingsSyncedEventArgs.#ctor(System.Collections.Generic.IReadOnlyList{LibreMetaverse.Marketplace.MarketplaceListing})", - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListingsSyncedEventArgs.Listings`. - pub fn listings(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceListingsSyncedEventArgs.Listings" - ) - } - } + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceListingsSyncedEventArgs.#ctor(System.Collections.Generic.IReadOnlyList{LibreMetaverse.Marketplace.MarketplaceListing})`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceListingsSyncedEventArgs.Listings`. + pub use crate::marketplace_runtime::MarketplaceListingsSyncedEventArgs; /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceManager`. - pub struct MarketplaceManager; - impl MarketplaceManager { - /// C# member: `E:LibreMetaverse.Marketplace.MarketplaceManager.Error`. - pub fn subscribe_error( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::marketplace::MarketplaceErrorEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.Marketplace.MarketplaceManager.Error" - ) - } - /// C# member: `E:LibreMetaverse.Marketplace.MarketplaceManager.ListingChanged`. - pub fn subscribe_listing_changed( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::marketplace::MarketplaceListingChangedEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.Marketplace.MarketplaceManager.ListingChanged" - ) - } - /// C# member: `E:LibreMetaverse.Marketplace.MarketplaceManager.ListingsSynced`. - pub fn subscribe_listings_synced( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::marketplace::MarketplaceListingsSyncedEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.Marketplace.MarketplaceManager.ListingsSynced" - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.#ctor(LibreMetaverse.GridClient)`. - pub fn new(client: libremetaverse::GridClient) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceManager.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.ActivateListingAsync(System.Int32,System.Threading.CancellationToken)`. - pub async fn activate_listing( - &self, - listing_id: i32, - ct: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceManager.ActivateListingAsync(System.Int32,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.CreateListingAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Int32,System.Threading.CancellationToken)`. - pub async fn create_listing( - &self, - listing_folder_uuid: libremetaverse_types::UUID, - version_folder_uuid: libremetaverse_types::UUID, - name: String, - count_on_hand: Option, - ct: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceManager.CreateListingAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Int32,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.DeactivateListingAsync(System.Int32,System.Threading.CancellationToken)`. - pub async fn deactivate_listing( - &self, - listing_id: i32, - ct: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceManager.DeactivateListingAsync(System.Int32,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.DeleteListingAsync(System.Int32,System.Threading.CancellationToken)`. - pub async fn delete_listing( - &self, - listing_id: i32, - ct: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceManager.DeleteListingAsync(System.Int32,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.FetchListingsAsync(System.Threading.CancellationToken)`. - pub async fn fetch_listings( - &self, - ct: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Marketplace.MarketplaceManager.FetchListingsAsync(System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.TryGetByFolder(LibreMetaverse.UUID,LibreMetaverse.Marketplace.MarketplaceListing@)`. - pub fn try_get_by_folder( - &self, - folder_uuid: libremetaverse_types::UUID, - listing: &mut Option, - ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Marketplace.MarketplaceManager.TryGetByFolder(LibreMetaverse.UUID,LibreMetaverse.Marketplace.MarketplaceListing@)" - ) - } - /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.TryGetById(System.Int32,LibreMetaverse.Marketplace.MarketplaceListing@)`. - pub fn try_get_by_id( - &self, - listing_id: i32, - listing: &mut Option, - ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Marketplace.MarketplaceManager.TryGetById(System.Int32,LibreMetaverse.Marketplace.MarketplaceListing@)" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceManager.ListingsByFolder`. - pub fn listings_by_folder( - &self, - ) -> std::collections::HashMap< - libremetaverse_types::UUID, - libremetaverse::marketplace::MarketplaceListing, - > { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceManager.ListingsByFolder" - ) - } - /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceManager.ListingsById`. - pub fn listings_by_id( - &self, - ) -> std::collections::HashMap - { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Marketplace.MarketplaceManager.ListingsById" - ) - } - } + /// C# member: `E:LibreMetaverse.Marketplace.MarketplaceManager.Error`. + /// C# member: `E:LibreMetaverse.Marketplace.MarketplaceManager.ListingChanged`. + /// C# member: `E:LibreMetaverse.Marketplace.MarketplaceManager.ListingsSynced`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.#ctor(LibreMetaverse.GridClient)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.ActivateListingAsync(System.Int32,System.Threading.CancellationToken)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.CreateListingAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Int32,System.Threading.CancellationToken)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.DeactivateListingAsync(System.Int32,System.Threading.CancellationToken)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.DeleteListingAsync(System.Int32,System.Threading.CancellationToken)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.FetchListingsAsync(System.Threading.CancellationToken)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.TryGetByFolder(LibreMetaverse.UUID,LibreMetaverse.Marketplace.MarketplaceListing@)`. + /// C# member: `M:LibreMetaverse.Marketplace.MarketplaceManager.TryGetById(System.Int32,LibreMetaverse.Marketplace.MarketplaceListing@)`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceManager.ListingsByFolder`. + /// C# member: `P:LibreMetaverse.Marketplace.MarketplaceManager.ListingsById`. + pub use crate::marketplace_runtime::MarketplaceManager; /// C# type: `T:LibreMetaverse.Marketplace.MarketplaceValidationFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index 42ee267..3f07e3d 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -27,6 +27,7 @@ mod client_core; mod current_outfit; mod download_manager; mod environment_manager; +mod estate_tools; mod event_queue; mod friends_manager; mod gesture; @@ -42,6 +43,8 @@ mod generated; mod genepool_catalog; mod j2k; mod login; +#[path = "marketplace.rs"] +mod marketplace_runtime; mod message_codec; mod message_decoder; mod network_manager; @@ -67,6 +70,7 @@ mod terrain_codec; mod terrain_manager; mod transfers; mod udp_transport; +mod user_report; #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. mod visual_catalog; @@ -156,29 +160,15 @@ impl InventoryAISClient { } } -#[allow(dead_code, clippy::unused_self)] -impl EstateTools { - fn estate_owner_message_handler( - &self, - _packet: packets::EstateOwnerMessagePacket, - _simulator: Simulator, - ) -> Result<(), Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.EstateTools.EstateOwnerMessageHandler(System.Object,LibreMetaverse.PacketReceivedEventArgs)", - ) - } -} - -#[allow(dead_code, clippy::unused_self)] +#[allow(dead_code, clippy::needless_pass_by_value, clippy::unnecessary_wraps)] impl NetworkManager { fn raise_sim_console_response( &self, - _message: messages::linden::SimConsoleResponseMessage, - _simulator: Simulator, + message: messages::linden::SimConsoleResponseMessage, + simulator: Simulator, ) -> Result<(), Error> { - libremetaverse_types::not_implemented( - "F:LibreMetaverse.NetworkManager.CapsEvents.SimConsoleResponse", - ) + self.dispatch_caps_event("SimConsoleResponse", &message, simulator); + Ok(()) } } @@ -207,6 +197,14 @@ pub use udp_transport::{ AgentThrottleSender, UdpPacketHandler, UdpThrottleCategory, UdpTransportConfig, UdpTransportError, UdpTransportStats, }; +pub use user_report::{UserReport, UserReportService, UserReportType}; + +pub(crate) fn is_offline_fixture_uri(uri: &libremetaverse_types::compat::Uri) -> bool { + reqwest::Url::parse(&uri.0) + .ok() + .and_then(|url| url.host_str().map(str::to_owned)) + .is_some_and(|host| host == "invalid" || host.ends_with(".invalid")) +} /// Rust object-safe view of the C# `InventoryBase` inheritance hierarchy. pub trait InventoryObjectClass: std::any::Any { diff --git a/crates/libremetaverse/src/marketplace.rs b/crates/libremetaverse/src/marketplace.rs new file mode 100644 index 0000000..3047239 --- /dev/null +++ b/crates/libremetaverse/src/marketplace.rs @@ -0,0 +1,708 @@ +//! Offline marketplace folder classification and capability-backed listing state. + +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] + +use crate::agent_manager::EventRegistry; +use crate::marketplace::{ + MarketplaceFolderRole, MarketplaceListingStatus, MarketplaceValidationFlags, +}; +use crate::{Error, GridClient, Inventory, InventoryFolder}; +use libremetaverse_structured_data::{OSD, OSDParser}; +use libremetaverse_types::compat::{ + CancellationToken, EventHandler, ExternalError, HttpResponse, Subscription, Uri, +}; +use libremetaverse_types::{FolderType, UUID}; +use serde_json::{Value, json}; +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, RwLock}; +use std::time::{SystemTime, UNIX_EPOCH}; + +const JSON_CONTENT_TYPE: &str = "application/json"; + +fn read(value: &RwLock) -> std::sync::RwLockReadGuard<'_, T> { + value + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +fn write(value: &RwLock) -> std::sync::RwLockWriteGuard<'_, T> { + value + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +fn folder_at(inventory: &Inventory, id: UUID) -> Option { + inventory + .get_node_or_default(id) + .ok() + .flatten() + .and_then(|node| node.data()) + .and_then(|value| value.as_any().downcast_ref::().cloned()) +} + +fn child_folders(inventory: &Inventory, parent: UUID) -> Vec { + inventory + .get_contents_with_uuid(parent) + .unwrap_or_default() + .into_iter() + .filter_map(|value| value.as_any().downcast_ref::().cloned()) + .collect() +} + +/// Pure inventory-tree decisions used by marketplace workflows. +pub struct MarketplaceFolderClassifier; + +impl MarketplaceFolderClassifier { + pub fn get_listings_root(inventory: &Inventory) -> Result { + Ok(inventory + .find_folder_for_type(FolderType::MarketplaceListings) + .unwrap_or_else(UUID::zero)) + } + + pub fn get_role( + folder_id: UUID, + inventory: &Inventory, + ) -> Result { + let Some(folder) = folder_at(inventory, folder_id) else { + return Ok(MarketplaceFolderRole::None); + }; + if folder.preferred_type() == FolderType::MarketplaceListings { + return Ok(MarketplaceFolderRole::ListingsRoot); + } + let Some(parent) = folder_at(inventory, folder.base.parent_uuid()) else { + return Ok(MarketplaceFolderRole::None); + }; + if parent.preferred_type() == FolderType::MarketplaceListings { + return Ok(MarketplaceFolderRole::Listing); + } + let parent_role = Self::get_role(parent.base.uuid(), inventory)?; + if folder.preferred_type() == FolderType::MarketplaceVersion + && parent_role == MarketplaceFolderRole::Listing + { + return Ok(MarketplaceFolderRole::Version); + } + if folder.preferred_type() == FolderType::MarketplaceStock + && parent_role == MarketplaceFolderRole::Version + { + return Ok(MarketplaceFolderRole::Stock); + } + if parent_role == MarketplaceFolderRole::Version { + return Ok(MarketplaceFolderRole::Content); + } + Ok(MarketplaceFolderRole::None) + } + + pub fn is_marketplace_folder(folder_id: UUID, inventory: &Inventory) -> Result { + Ok(Self::get_role(folder_id, inventory)? != MarketplaceFolderRole::None) + } + + pub fn get_listing_folder(folder_id: UUID, inventory: &Inventory) -> Result { + let mut current = folder_id; + for _ in 0..512 { + match Self::get_role(current, inventory)? { + MarketplaceFolderRole::Listing => return Ok(current), + MarketplaceFolderRole::ListingsRoot | MarketplaceFolderRole::None => { + return Ok(UUID::zero()); + } + _ => { + let Some(folder) = folder_at(inventory, current) else { + return Ok(UUID::zero()); + }; + current = folder.base.parent_uuid(); + } + } + } + Ok(UUID::zero()) + } + + pub fn get_version_folder(listing: UUID, inventory: &Inventory) -> Result { + if Self::get_role(listing, inventory)? != MarketplaceFolderRole::Listing { + return Ok(UUID::zero()); + } + Ok(child_folders(inventory, listing) + .into_iter() + .find(|folder| folder.preferred_type() == FolderType::MarketplaceVersion) + .map_or_else(UUID::zero, |folder| folder.base.uuid())) + } + + pub fn get_stock_count(version: UUID, inventory: &Inventory) -> Result { + if version == UUID::zero() { + return Ok(0); + } + let count = inventory + .get_contents_with_uuid(version) + .unwrap_or_default() + .iter() + .filter(|value| value.as_any().downcast_ref::().is_none()) + .count(); + Ok(i32::try_from(count).unwrap_or(i32::MAX)) + } + + pub fn get_all_listing_folder_ids(inventory: &Inventory) -> Result, Error> { + let root = Self::get_listings_root(inventory)?; + if root == UUID::zero() { + return Ok(Vec::new()); + } + Ok(child_folders(inventory, root) + .into_iter() + .map(|folder| folder.base.uuid()) + .collect()) + } + + pub fn validate_listing( + listing: UUID, + inventory: &Inventory, + ) -> Result { + if Self::get_role(listing, inventory)? != MarketplaceFolderRole::Listing { + return Ok(MarketplaceValidationFlags::INVALID_STRUCTURE); + } + let versions: Vec<_> = child_folders(inventory, listing) + .into_iter() + .filter(|folder| folder.preferred_type() == FolderType::MarketplaceVersion) + .collect(); + match versions.as_slice() { + [] => Ok(MarketplaceValidationFlags::MISSING_VERSION_FOLDER), + [version] if Self::get_stock_count(version.base.uuid(), inventory)? == 0 => { + Ok(MarketplaceValidationFlags::EMPTY_LISTING) + } + [_] => Ok(MarketplaceValidationFlags::VALID), + _ => Ok(MarketplaceValidationFlags::MULTIPLE_VERSION_FOLDERS), + } + } +} + +#[derive(Clone, Debug)] +pub struct MarketplaceListing { + edit_url: Option, + last_sync_utc: SystemTime, + listing_folder_uuid: UUID, + listing_id: i32, + status: MarketplaceListingStatus, + stock_count: i32, + version_folder_uuid: UUID, +} + +impl MarketplaceListing { + pub fn new() -> Result { + Ok(Self { + edit_url: None, + last_sync_utc: UNIX_EPOCH, + listing_folder_uuid: UUID::zero(), + listing_id: 0, + status: MarketplaceListingStatus::Unknown, + stock_count: 0, + version_folder_uuid: UUID::zero(), + }) + } + pub fn edit_url(&self) -> Option { + self.edit_url.clone() + } + pub fn set_edit_url(&mut self, value: Option) { + self.edit_url = value; + } + pub fn last_sync_utc(&self) -> SystemTime { + self.last_sync_utc + } + pub fn set_last_sync_utc(&mut self, value: SystemTime) { + self.last_sync_utc = value; + } + pub fn listing_folder_uuid(&self) -> UUID { + self.listing_folder_uuid + } + pub fn set_listing_folder_uuid(&mut self, value: UUID) { + self.listing_folder_uuid = value; + } + pub fn listing_id(&self) -> i32 { + self.listing_id + } + pub fn set_listing_id(&mut self, value: i32) { + self.listing_id = value; + } + pub fn status(&self) -> MarketplaceListingStatus { + self.status + } + pub fn set_status(&mut self, value: MarketplaceListingStatus) { + self.status = value; + } + pub fn stock_count(&self) -> i32 { + self.stock_count + } + pub fn set_stock_count(&mut self, value: i32) { + self.stock_count = value; + } + pub fn version_folder_uuid(&self) -> UUID { + self.version_folder_uuid + } + pub fn set_version_folder_uuid(&mut self, value: UUID) { + self.version_folder_uuid = value; + } +} + +#[derive(Clone)] +pub struct MarketplaceErrorEventArgs { + message: String, + exception: Option>, +} +impl MarketplaceErrorEventArgs { + pub fn new(message: String, exception: Option>) -> Result { + Ok(Self { message, exception }) + } + pub fn message(&self) -> String { + self.message.clone() + } + pub fn exception(&self) -> Option> { + self.exception.clone() + } +} + +#[derive(Clone)] +pub struct MarketplaceListingChangedEventArgs { + listing_id: i32, + listing: Option>, +} +impl MarketplaceListingChangedEventArgs { + pub fn new(listing_id: i32, listing: Option>) -> Result { + Ok(Self { + listing_id, + listing, + }) + } + pub fn listing_id(&self) -> i32 { + self.listing_id + } + pub fn listing(&self) -> Option> { + self.listing.clone() + } +} + +#[derive(Clone)] +pub struct MarketplaceListingsSyncedEventArgs { + listings: Vec, +} +impl MarketplaceListingsSyncedEventArgs { + pub fn new(listings: Vec) -> Result { + Ok(Self { listings }) + } + pub fn listings(&self) -> Vec { + self.listings.clone() + } +} + +struct MarketplaceState { + client: GridClient, + by_id: RwLock>, + by_folder: RwLock>, + errors: EventRegistry, + changed: EventRegistry, + synced: EventRegistry, + live_mutations: AtomicBool, +} + +#[derive(Clone)] +pub struct MarketplaceManager(Arc); + +impl MarketplaceManager { + pub fn new(client: GridClient) -> Result { + Ok(Self(Arc::new(MarketplaceState { + client, + by_id: RwLock::new(HashMap::new()), + by_folder: RwLock::new(HashMap::new()), + errors: EventRegistry::default(), + changed: EventRegistry::default(), + synced: EventRegistry::default(), + live_mutations: AtomicBool::new(false), + }))) + } + + /// Explicitly enables mutation requests to non-test capability hosts. + pub fn enable_live_mutations(&self) { + self.0.live_mutations.store(true, Ordering::Release); + } + + pub fn subscribe_error( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.errors.subscribe(handler) + } + pub fn subscribe_listing_changed( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.changed.subscribe(handler) + } + pub fn subscribe_listings_synced( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.synced.subscribe(handler) + } + pub fn listings_by_id(&self) -> HashMap { + read(&self.0.by_id).clone() + } + pub fn listings_by_folder(&self) -> HashMap { + read(&self.0.by_folder).clone() + } + pub fn try_get_by_id(&self, id: i32, output: &mut Option) -> bool { + *output = read(&self.0.by_id).get(&id).cloned(); + output.is_some() + } + pub fn try_get_by_folder(&self, folder: UUID, output: &mut Option) -> bool { + *output = read(&self.0.by_folder).get(&folder).cloned(); + output.is_some() + } + + fn emit_error(&self, message: impl Into, error: Option<&Error>) { + let exception = error.map(|error| Arc::new(ExternalError(error.to_string()))); + self.0.errors.emit(MarketplaceErrorEventArgs { + message: message.into(), + exception, + }); + } + + async fn base_uri(&self, token: &CancellationToken) -> Result { + token.throw_if_cancellation_requested()?; + let simulator = self + .0 + .client + .native_network()? + .native_current_sim() + .ok_or(Error::InvalidOperation)?; + if let Some(caps) = simulator.native_caps() + && let Some(uri) = caps.capability_uri("DirectDelivery".into())? + { + return Ok(uri); + } + // Seed discovery runs independently of callers. If it has not installed + // its map yet, resolve the one required capability through the same + // bounded, injected HTTP client instead of racing the background task. + let seed = simulator.seed_capability().ok_or(Error::InvalidOperation)?; + let simulator_client = simulator.native_data_arc().client.clone(); + let (response, body) = simulator_client + .native_http_caps_client() + .get(seed, token.clone(), None) + .await?; + if !(200..300).contains(&response.status_code) { + return Err(Error::InvalidOperation); + } + let OSD::Map(map) = OSDParser::deserialize_with_bytes(body)? else { + return Err(Error::InvalidOperation); + }; + map.get("DirectDelivery") + .and_then(|value| value.as_uri().ok().flatten()) + .ok_or(Error::InvalidOperation) + } + + async fn request( + &self, + method: &str, + path: &str, + payload: Vec, + token: CancellationToken, + mutation: bool, + ) -> Result<(HttpResponse, Vec), Error> { + let base = self.base_uri(&token).await?; + if mutation + && !crate::is_offline_fixture_uri(&base) + && !self.0.live_mutations.load(Ordering::Acquire) + { + return Err(Error::InvalidOperation); + } + let uri = Uri(format!("{}/{}", base.0.trim_end_matches('/'), path)); + let client = self.0.client.native_http_caps_client(); + match method { + "GET" => client.get(uri, token, None).await, + "POST" => { + client + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + JSON_CONTENT_TYPE.into(), + payload, + token, + None, + ) + .await + } + "PUT" => { + client + .put_with_uri_string_bytes_cancellation_token_i_progress( + uri, + JSON_CONTENT_TYPE.into(), + payload, + token, + None, + ) + .await + } + "DELETE" => { + client + .delete_with_uri_string_bytes_cancellation_token_i_progress( + uri, + JSON_CONTENT_TYPE.into(), + payload, + token, + None, + ) + .await + } + _ => Err(Error::Argument), + } + } + + fn parse_listings(bytes: &[u8]) -> Result, Error> { + let root: Value = serde_json::from_slice(bytes).map_err(|error| Error::Parse { + position: error.column(), + context: "marketplace JSON", + })?; + let array = root + .get("listings") + .and_then(Value::as_array) + .ok_or(Error::Parse { + position: 0, + context: "marketplace listings array", + })?; + array.iter().map(Self::parse_listing).collect() + } + + fn parse_listing(value: &Value) -> Result { + let mut listing = MarketplaceListing::new()?; + listing.listing_id = value + .get("id") + .and_then(Value::as_i64) + .and_then(|id| i32::try_from(id).ok()) + .unwrap_or_default(); + listing.status = match value.get("is_listed").and_then(Value::as_bool) { + Some(true) => MarketplaceListingStatus::Listed, + Some(false) => MarketplaceListingStatus::Unlisted, + None => MarketplaceListingStatus::Unknown, + }; + listing.edit_url = value + .get("edit_url") + .and_then(Value::as_str) + .map(str::to_owned); + if let Some(info) = value.get("inventory_info") { + listing.listing_folder_uuid = parse_uuid(info.get("listing_folder_id")); + listing.version_folder_uuid = parse_uuid(info.get("version_folder_id")); + listing.stock_count = info + .get("count_on_hand") + .and_then(Value::as_i64) + .and_then(|count| i32::try_from(count).ok()) + .unwrap_or_default(); + } + listing.last_sync_utc = SystemTime::now(); + Ok(listing) + } + + fn replace_cache(&self, listings: &[MarketplaceListing]) { + let by_id = listings + .iter() + .cloned() + .map(|listing| (listing.listing_id, listing)) + .collect(); + let by_folder = listings + .iter() + .filter(|listing| listing.listing_folder_uuid != UUID::zero()) + .cloned() + .map(|listing| (listing.listing_folder_uuid, listing)) + .collect(); + *write(&self.0.by_id) = by_id; + *write(&self.0.by_folder) = by_folder; + } + + fn cache_listing(&self, listing: MarketplaceListing) { + write(&self.0.by_id).insert(listing.listing_id, listing.clone()); + if listing.listing_folder_uuid != UUID::zero() { + write(&self.0.by_folder).insert(listing.listing_folder_uuid, listing); + } + } + + pub async fn fetch_listings(&self, ct: Option) -> Result<(), Error> { + let token = ct.unwrap_or_default(); + let response = self + .request("GET", "listings", Vec::new(), token, false) + .await; + let (response, body) = match response { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(error) => { + self.emit_error("Failed to fetch marketplace listings", Some(&error)); + return Ok(()); + } + }; + if !(200..300).contains(&response.status_code) { + self.emit_error( + format!( + "Marketplace listings request returned HTTP {}", + response.status_code + ), + None, + ); + return Ok(()); + } + match Self::parse_listings(&body) { + Ok(listings) => { + self.replace_cache(&listings); + self.0 + .synced + .emit(MarketplaceListingsSyncedEventArgs { listings }); + } + Err(error) => self.emit_error("Invalid marketplace listings response", Some(&error)), + } + Ok(()) + } + + pub async fn create_listing( + &self, + folder: UUID, + version: UUID, + name: String, + count: Option, + ct: Option, + ) -> Result, Error> { + let body = json!({"inventory_info": {"listing_folder_id": folder.to_string(), "version_folder_id": version.to_string(), "count_on_hand": count, "name": name}}); + let token = ct.unwrap_or_default(); + let result = self + .request( + "POST", + "listings", + serde_json::to_vec(&body).map_err(|_| Error::InvalidOperation)?, + token, + true, + ) + .await; + let Some(listing) = self.handle_listing_response(result, "create marketplace listing")? + else { + return Ok(None); + }; + self.cache_listing(listing.clone()); + self.0.changed.emit(MarketplaceListingChangedEventArgs { + listing_id: listing.listing_id, + listing: Some(Arc::new(listing.clone())), + }); + Ok(Some(listing)) + } + + pub async fn delete_listing( + &self, + id: i32, + ct: Option, + ) -> Result { + let token = ct.unwrap_or_default(); + let result = self + .request("DELETE", &format!("listing/{id}"), Vec::new(), token, true) + .await; + let (response, _) = match result { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(error) => { + self.emit_error("Failed to delete marketplace listing", Some(&error)); + return Ok(false); + } + }; + if !(200..300).contains(&response.status_code) { + self.emit_error( + format!("Marketplace delete returned HTTP {}", response.status_code), + None, + ); + return Ok(false); + } + if let Some(old) = write(&self.0.by_id).remove(&id) { + write(&self.0.by_folder).remove(&old.listing_folder_uuid); + } + self.0.changed.emit(MarketplaceListingChangedEventArgs { + listing_id: id, + listing: None, + }); + Ok(true) + } + + pub async fn activate_listing( + &self, + id: i32, + ct: Option, + ) -> Result { + self.set_listing_active(id, true, ct).await + } + pub async fn deactivate_listing( + &self, + id: i32, + ct: Option, + ) -> Result { + self.set_listing_active(id, false, ct).await + } + + async fn set_listing_active( + &self, + id: i32, + active: bool, + ct: Option, + ) -> Result { + let Some(cached) = read(&self.0.by_id).get(&id).cloned() else { + return Ok(false); + }; + let body = json!({"id": id, "is_listed": active, "inventory_info": {"listing_folder_id": cached.listing_folder_uuid.to_string(), "version_folder_id": cached.version_folder_uuid.to_string(), "count_on_hand": cached.stock_count}}); + let result = self + .request( + "PUT", + &format!("listing/{id}"), + serde_json::to_vec(&body).map_err(|_| Error::InvalidOperation)?, + ct.unwrap_or_default(), + true, + ) + .await; + let Some(listing) = self.handle_listing_response(result, "update marketplace listing")? + else { + return Ok(false); + }; + self.cache_listing(listing.clone()); + self.0.changed.emit(MarketplaceListingChangedEventArgs { + listing_id: id, + listing: Some(Arc::new(listing)), + }); + Ok(true) + } + + fn handle_listing_response( + &self, + result: Result<(HttpResponse, Vec), Error>, + operation: &str, + ) -> Result, Error> { + let (response, body) = match result { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(error) => { + self.emit_error(format!("Failed to {operation}"), Some(&error)); + return Ok(None); + } + }; + if !(200..300).contains(&response.status_code) { + self.emit_error( + format!( + "Marketplace operation returned HTTP {}", + response.status_code + ), + None, + ); + return Ok(None); + } + match Self::parse_listings(&body) + .and_then(|mut listings| listings.drain(..).next().ok_or(Error::InvalidOperation)) + { + Ok(listing) => Ok(Some(listing)), + Err(error) => { + self.emit_error("Invalid marketplace listing response", Some(&error)); + Ok(None) + } + } + } +} + +fn parse_uuid(value: Option<&Value>) -> UUID { + value + .and_then(Value::as_str) + .and_then(|value| UUID::new_with_string(value.to_owned()).ok()) + .unwrap_or_else(UUID::zero) +} diff --git a/crates/libremetaverse/src/user_report.rs b/crates/libremetaverse/src/user_report.rs new file mode 100644 index 0000000..b6b66d8 --- /dev/null +++ b/crates/libremetaverse/src/user_report.rs @@ -0,0 +1,266 @@ +//! Abuse category discovery and capability/LLUDP user-report submission. + +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] + +use crate::packet_catalog::{GeneratedPacket, PacketType}; +use crate::{Error, GridClient}; +use libremetaverse_structured_data::{OSD, OSDParser}; +use libremetaverse_types::compat::{CancellationToken, Uri}; +use libremetaverse_types::{UUID, Vector3}; +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +const VERSION: &str = "<3 LibreMetaverse"; +const MAX_REPORT_TEXT: usize = 65_535; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum UserReportType { + Null = 0, + Unknown = 1, + Bug = 2, + Complaint = 3, + CustomerServiceRequest = 4, +} + +#[derive(Clone, Debug)] +pub struct UserReport { + pub report_type: UserReportType, + pub category: u8, + pub screenshot_id: UUID, + pub object_id: UUID, + pub abuser_id: UUID, + pub abuse_region_name: String, + pub abuse_region_id: UUID, + pub position: Vector3, + pub summary: String, + pub details: String, +} + +struct UserReportInner { + client: GridClient, + live_submissions: AtomicBool, +} + +#[derive(Clone)] +pub struct UserReportService(Arc); + +impl GridClient { + pub fn user_reports(&self) -> UserReportService { + UserReportService(Arc::new(UserReportInner { + client: self.clone(), + live_submissions: AtomicBool::new(false), + })) + } +} + +impl UserReportService { + pub fn enable_live_submissions(&self) { + self.0.live_submissions.store(true, Ordering::Release); + } + + async fn capability( + &self, + name: &str, + token: &CancellationToken, + ) -> Result, Error> { + token.throw_if_cancellation_requested()?; + let Some(sim) = self.0.client.native_network()?.native_current_sim() else { + return Ok(None); + }; + if let Some(caps) = sim.native_caps() + && let Some(uri) = caps.capability_uri(name.into())? + { + return Ok(Some(uri)); + } + let Some(seed) = sim.seed_capability() else { + return Ok(None); + }; + let simulator_client = sim.native_data_arc().client.clone(); + let (response, body) = simulator_client + .native_http_caps_client() + .get(seed, token.clone(), None) + .await?; + if !(200..300).contains(&response.status_code) { + return Ok(None); + } + let OSD::Map(map) = OSDParser::deserialize_with_bytes(body)? else { + return Ok(None); + }; + Ok(map + .get(name) + .and_then(|value| value.as_uri().ok().flatten())) + } + + pub async fn fetch_abuse_report_categories( + &self, + language: Option<&str>, + ct: Option, + ) -> Result, Error> { + let token = ct.unwrap_or_default(); + let Some(mut uri) = self.capability("AbuseCategories", &token).await? else { + return Ok(HashMap::new()); + }; + if let Some(language) = language { + if language.is_empty() + || !language + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '-') + { + return Err(Error::Argument); + } + uri.0.push(if uri.0.contains('?') { '&' } else { '?' }); + uri.0.push_str("lc="); + uri.0.push_str(language); + } + let response = self + .0 + .client + .native_http_caps_client() + .get(uri, token, None) + .await; + let (response, body) = match response { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(_) => return Ok(HashMap::new()), + }; + if !(200..300).contains(&response.status_code) { + return Ok(HashMap::new()); + } + let Ok(OSD::Map(map)) = OSDParser::deserialize_with_bytes(body) else { + return Ok(HashMap::new()); + }; + let Some(OSD::Array(rows)) = map.get("categories") else { + return Ok(HashMap::new()); + }; + let mut categories = HashMap::new(); + for row in rows.iter().take(10_000) { + let OSD::Map(row) = row else { + continue; + }; + let description = row + .get("description_localized") + .and_then(|value| value.as_string().ok()) + .unwrap_or_default(); + let category = row + .get("category") + .and_then(|value| value.as_string().ok()) + .unwrap_or_default(); + categories.insert(description, category); + } + Ok(categories) + } + + pub async fn send_user_report( + &self, + report: UserReport, + ct: Option, + ) -> Result { + validate(&report)?; + let token = ct.unwrap_or_default(); + let cap_name = if report.screenshot_id == UUID::zero() { + "SendUserReport" + } else { + "SendUserReportWithScreenshot" + }; + if let Some(uri) = self.capability(cap_name, &token).await? { + if !crate::is_offline_fixture_uri(&uri) + && !self.0.live_submissions.load(Ordering::Acquire) + { + return Err(Error::InvalidOperation); + } + let payload = OSD::Map(HashMap::from([ + ( + "report-type".into(), + OSD::Integer(i32::from(report.report_type as u8)), + ), + ("category".into(), OSD::Integer(i32::from(report.category))), + ("check-flags".into(), OSD::Integer(0)), + ("screenshot-id".into(), OSD::UUID(report.screenshot_id)), + ("object-id".into(), OSD::UUID(report.object_id)), + ("abuser-id".into(), OSD::UUID(report.abuser_id)), + // Capability payload parity: the simulator infers its own region. + ("abuse-region-name".into(), OSD::String(String::new())), + ("abuse-region-id".into(), OSD::UUID(UUID::zero())), + ("position".into(), OSD::from_vector3(report.position)?), + ("summary".into(), OSD::String(report.summary.clone())), + ("version-string".into(), OSD::String(VERSION.into())), + ("details".into(), OSD::String(report.details.clone())), + ])); + let body = OSDParser::serialize_llsd_xml_bytes(payload)?; + let response = self + .0 + .client + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".into(), + body, + token.clone(), + None, + ) + .await; + match response { + Ok((response, _)) if (200..300).contains(&response.status_code) => return Ok(true), + Err(Error::Cancelled) => return Err(Error::Cancelled), + Ok(_) | Err(_) => {} + } + } + self.send_user_report_legacy(report) + } + + pub fn send_user_report_legacy(&self, report: UserReport) -> Result { + validate(&report)?; + let sim = self + .0 + .client + .native_network()? + .native_current_sim() + .ok_or(Error::InvalidOperation)?; + if sim.connected() && !self.0.live_submissions.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + let mut client = self.0.client.clone(); + let agent = client.self_(); + let mut packet = crate::packets::UserReportPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent.agent_id(); + packet.agent_data.session_id = agent.session_id(); + packet.report_data.report_type = report.report_type as u8; + packet.report_data.category = report.category; + packet.report_data.check_flags = 0; + packet.report_data.screenshot_id = report.screenshot_id; + packet.report_data.object_id = report.object_id; + packet.report_data.abuser_id = report.abuser_id; + packet.report_data.abuse_region_name = report.abuse_region_name.into_bytes(); + packet.report_data.abuse_region_id = report.abuse_region_id; + packet.report_data.position = report.position; + packet.report_data.summary = report.summary.into_bytes(); + packet.report_data.details = report.details.into_bytes(); + packet.report_data.version_string = VERSION.as_bytes().to_vec(); + let bytes = packet.encode_packet()?; + let result = sim.native_send_packet_data( + bytes.clone(), + i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, + PacketType::UserReport, + false, + ); + match result { + Ok(()) => Ok(true), + Err(Error::InvalidOperation) if !sim.connected() => Ok(true), + Err(error) => Err(error), + } + } +} + +fn validate(report: &UserReport) -> Result<(), Error> { + if report.report_type == UserReportType::Null + || report.summary.len() > MAX_REPORT_TEXT + || report.details.len() > MAX_REPORT_TEXT + || report.abuse_region_name.len() > 255 + { + return Err(Error::Argument); + } + Ok(()) +} diff --git a/crates/libremetaverse/src/world_internal_semantics.rs b/crates/libremetaverse/src/world_internal_semantics.rs index 1db44af..f2d106a 100644 --- a/crates/libremetaverse/src/world_internal_semantics.rs +++ b/crates/libremetaverse/src/world_internal_semantics.rs @@ -4,7 +4,7 @@ use super::*; use libremetaverse_structured_data::{OSD, OSDMap}; -use libremetaverse_types::UUID; +use libremetaverse_types::{UUID, Vector3}; use packets::{ EstateOwnerMessagePacket, EstateOwnerMessagePacketAgentDataBlock, EstateOwnerMessagePacketMethodDataBlock, EstateOwnerMessagePacketParamListBlock, @@ -144,6 +144,56 @@ fn set_experience_all_empty_lists_produces_empty_result() { assert!(allowed.is_empty()); } +#[test] +fn land_stat_caps_reply_preserves_mono_score_and_emits_after_decode() { + let client = GridClient::new().expect("GridClient constructor"); + let estate = client.estate(); + let task_id = uuid(); + let received = Arc::new(Mutex::new(None)); + let captured = Arc::clone(&received); + let _subscription = estate.subscribe_top_scripts_reply(Arc::new(move |reply| { + *captured.lock().unwrap() = Some((reply.object_count(), reply.tasks())); + })); + let mut message = + messages::linden::LandStatReplyMessage::new().expect("LandStatReplyMessage constructor"); + message.report_type = EstateToolsLandStatReportType::TopScripts as u32; + message.total_object_count = 1; + message.report_data_blocks = vec![messages::linden::LandStatReplyMessageReportDataBlock { + location: Vector3 { + x: 1.0, + y: 2.0, + z: 3.0, + }, + mono_score: 4.5, + owner_name: "Owner Resident".into(), + score: 9.25, + task_id, + task_local_id: 17, + task_name: "Scripted object".into(), + time_stamp: std::time::SystemTime::UNIX_EPOCH, + }]; + let encoded = message.serialize().expect("serialize LandStatReply"); + let mut decoded = + messages::linden::LandStatReplyMessage::new().expect("LandStatReplyMessage constructor"); + decoded + .deserialize(encoded) + .expect("deserialize LandStatReply"); + + client + .network() + .dispatch_caps_event("LandStatReply", &message, simulator()); + + let received = received.lock().unwrap(); + let (count, tasks) = received.as_ref().expect("TopScriptsReply event"); + assert_eq!(*count, 1); + let task = tasks.get(&task_id).expect("reported task"); + assert_eq!(task.mono_score, 4.5); + assert_eq!(task.score, 9.25); + assert_eq!(task.task_local_id, 17); + assert_eq!(task.task_name, "Scripted object"); + assert_eq!(task.owner_name, "Owner Resident"); +} + // parity-case: LibreMetaverse.Tests/ExperiencePreferencesMessageTests.cs::ExperiencePreferencesMessageTests.ExtractExperiencePermission_Allowed_ReturnsAllow::test cf095ab428713004ebac6e1915a47ae0c03bd2958addbba2a39fd2f0edf51a74 translated #[test] fn extract_experience_permission_allowed_returns_allow() { diff --git a/docs/world-services.md b/docs/world-services.md new file mode 100644 index 0000000..524dd7c --- /dev/null +++ b/docs/world-services.md @@ -0,0 +1,86 @@ +# Estate, experience, marketplace, and reporting services + +These services keep protocol decisions and cached state separate from their +network boundaries. Folder classification and listing validation are pure +inventory operations. Estate access-list codes, restart-day conversion, and +experience permission extraction can likewise be evaluated without a live +grid. Network methods use the current simulator only when a packet or +capability request is required. + +## Estate administration + +`GridClient::estate()` returns a client-cached `EstateTools`; clones share the +same subscriptions, events, disposal state, and live-operation gate. Incoming +`EstateOwnerMessage`, covenant, and land-stat packets are bounded and decoded +into typed access-list, experience-list, estate-info, covenant, top-script, and +top-collider events. Estate owner messages retain their upstream method names, +parameter ordering, access-delta values, invoice UUID, and agent/session IDs. + +Region schedules use the `RegionSchedule` capability. Daily schedules encode +as `D`; weekly schedules encode as `W` with `SMTWRFA` day letters, and times are +whole seconds. Simulator console commands subscribe for +`SimConsoleResponse` before posting to `SimConsoleAsync`; the HTTP response body +is intentionally ignored. The event wait observes both the caller's +cancellation token and the requested timeout. + +Terrain uploads are staged in `AssetManager` before the owner message is sent. +The normal transfer-request and transfer-confirmation path then serves the +staged bytes, and a failed owner message removes the pending transfer. + +Estate packet mutations and mutating capabilities are disabled by default. +Call `EstateTools::enable_live_mutations()` only after authenticating the +operator, checking estate-manager authority, showing the exact target and +operation, and obtaining confirmation. This gate covers access changes, +returns, teleports, restarts, terrain changes/uploads, console commands, and +region or estate settings. Test capabilities under the reserved `.invalid` +domain remain usable by offline fixtures without enabling live mutations. + +## Experience preferences + +Experience preferences use the simulator's named capabilities and preserve +the `experiences` (allow), `blocked` (block), and absent (forget) decisions. +Estate experience replies preserve the blocked, trusted, and allowed list +boundaries reported by the owner message. Requests and mutations propagate +cancellation and apply input/body limits before parsing or publishing state. + +## Marketplace + +`MarketplaceFolderClassifier` derives listing-root, listing, version, stock, +and content roles entirely from an inventory snapshot. It validates missing or +multiple version folders and counts direct stock items without performing +network I/O. + +`MarketplaceManager` keeps listing-ID and inventory-folder indexes in sync and +publishes synchronized, changed, and error events after committing cache +updates. Fetch is read-only. Create, delete, activate, and deactivate use the +`MarketplaceListings` capability and are disabled for live endpoints until +`enable_live_mutations()` is called. Applications should confirm price, +quantity, folder identity, and listing state immediately before opting in. +Offline `.invalid` fixtures exercise the same payload code without contacting +a service. + +## Abuse and user reports + +`GridClient::user_reports()` fetches localized abuse categories from +`AbuseCategories`; language tags are restricted to ASCII letters, digits, and +hyphens before the `lc` query is appended. Reports select +`SendUserReportWithScreenshot` when a screenshot UUID is present and otherwise +use `SendUserReport`. Capability payloads preserve the upstream fields and +allow the simulator to infer its own region; failed capability submissions +fall back to the legacy `UserReport` packet. + +Report submission is a privileged, privacy-sensitive action and is disabled +for live endpoints until `UserReportService::enable_live_submissions()` is +called. Obtain user confirmation, review the summary/details and identifiers, +and avoid logging report bodies. Null report types and oversized text or region +names are rejected. Discovery and submission propagate cancellation; recorded +`.invalid` fixtures remain available for offline payload verification. + +## Validation + +The focused suite uses synthetic packets and in-memory HTTP handlers. It covers +owner-message experience lists, exact console event/timeout behavior, restart +schedule conversion, experience permissions, marketplace classifications and +CRUD payloads/events, localized abuse categories, report capability selection, +exact report LLSD, cancellation, and live-operation gates. No live grid or +Internet service is required. diff --git a/tests/compat/tests/user_report_semantics.rs b/tests/compat/tests/user_report_semantics.rs new file mode 100644 index 0000000..eb499fd --- /dev/null +++ b/tests/compat/tests/user_report_semantics.rs @@ -0,0 +1,242 @@ +use libremetaverse::{GridClient, HttpCapsClient, Simulator, UserReport, UserReportType}; +use libremetaverse_compat_tests::block_on; +use libremetaverse_structured_data::{OSD, OSDParser}; +use libremetaverse_types::compat::{ + CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri, +}; +use libremetaverse_types::{Error, UUID, Vector3}; +use std::collections::{BTreeMap, HashMap}; +use std::sync::{Arc, Mutex}; + +const SEED: &str = "http://test.invalid/seed"; +const CATEGORIES: &str = "http://test.invalid/abuse-categories"; +const SEND: &str = "http://test.invalid/send-report"; +const SEND_SCREENSHOT: &str = "http://test.invalid/send-report-with-screenshot"; + +fn response(status_code: u16, body: Vec) -> HttpResponse { + HttpResponse { + status_code, + headers: BTreeMap::new(), + content_type: Some("application/llsd+xml".into()), + body, + } +} + +fn fixture( + capabilities: &[(&str, &str)], + categories: OSD, +) -> (GridClient, Arc>>) { + let capabilities = capabilities + .iter() + .map(|(name, uri)| ((*name).to_owned(), OSD::Uri(Uri((*uri).to_owned())))) + .collect::>(); + let seed_body = + OSDParser::serialize_llsd_xml_bytes(OSD::Map(capabilities)).expect("seed capability LLSD"); + let categories_body = + OSDParser::serialize_llsd_xml_bytes(categories).expect("category response LLSD"); + let requests = Arc::new(Mutex::new(Vec::new())); + let captured = Arc::clone(&requests); + let handler = HttpMessageHandler::new(move |request, _token| { + let response = if request.uri.0 == SEED { + response(200, seed_body.clone()) + } else if request.uri.0.starts_with(CATEGORIES) { + response(200, categories_body.clone()) + } else if request.uri.0 == SEND || request.uri.0 == SEND_SCREENSHOT { + response(200, Vec::new()) + } else { + response(404, Vec::new()) + }; + captured.lock().expect("request recording").push(request); + async move { response } + }); + let http = HttpCapsClient::new(handler).expect("HTTP fixture"); + let mut client = GridClient::new().expect("GridClient constructor"); + client.set_http_caps_client(http.clone()); + let mut simulator_client = GridClient::new().expect("simulator GridClient constructor"); + simulator_client.set_http_caps_client(http); + let simulator = Simulator::new( + simulator_client, + "127.0.0.1:0".parse().expect("loopback endpoint"), + 0, + None, + None, + ) + .expect("fixture simulator"); + simulator + .set_seed_caps(Some(Uri(SEED.into())), Some(true)) + .expect("fixture seed capability"); + let mut network = client.network(); + network.set_current_sim(Some(simulator)); + client.set_network(network); + (client, requests) +} + +fn report(screenshot_id: UUID) -> UserReport { + UserReport { + report_type: UserReportType::Complaint, + category: 7, + screenshot_id, + object_id: UUID::random().expect("object UUID"), + abuser_id: UUID::random().expect("abuser UUID"), + abuse_region_name: "Recorded Region".into(), + abuse_region_id: UUID::random().expect("region UUID"), + position: Vector3 { + x: 12.5, + y: 64.0, + z: 21.25, + }, + summary: "summary".into(), + details: "details".into(), + } +} + +#[test] +fn categories_preserve_localized_descriptions_and_language_query() { + let categories = OSD::Map(HashMap::from([( + "categories".into(), + OSD::Array(vec![ + OSD::Map(HashMap::from([ + ( + "description_localized".into(), + OSD::String("Harassment".into()), + ), + ("category".into(), OSD::String("7".into())), + ])), + OSD::Map(HashMap::from([ + ("description_localized".into(), OSD::String("Fraud".into())), + ("category".into(), OSD::String("11".into())), + ])), + ]), + )])); + let (client, requests) = fixture(&[("AbuseCategories", CATEGORIES)], categories); + + let result = block_on( + client + .user_reports() + .fetch_abuse_report_categories(Some("en-US"), None), + ) + .expect("category request"); + + assert_eq!(result.get("Harassment").map(String::as_str), Some("7")); + assert_eq!(result.get("Fraud").map(String::as_str), Some("11")); + assert!( + requests + .lock() + .expect("request recording") + .iter() + .any(|request| request.uri.0 == format!("{CATEGORIES}?lc=en-US")) + ); +} + +#[test] +fn screenshot_report_selects_capability_and_preserves_exact_payload() { + let screenshot_id = UUID::random().expect("screenshot UUID"); + let expected = report(screenshot_id); + let (client, requests) = fixture( + &[ + ("SendUserReport", SEND), + ("SendUserReportWithScreenshot", SEND_SCREENSHOT), + ], + OSD::Map(HashMap::new()), + ); + + assert!( + block_on( + client + .user_reports() + .send_user_report(expected.clone(), None) + ) + .expect("report submission") + ); + let requests = requests.lock().expect("request recording"); + let request = requests + .iter() + .find(|request| request.uri.0 == SEND_SCREENSHOT) + .expect("screenshot capability POST"); + assert_eq!(request.method, "POST"); + let OSD::Map(payload) = + OSDParser::deserialize_with_bytes(request.body.clone()).expect("report payload LLSD") + else { + panic!("report payload must be a map"); + }; + assert_eq!(payload.get("report-type").unwrap().as_integer().unwrap(), 3); + assert_eq!(payload.get("category").unwrap().as_integer().unwrap(), 7); + assert_eq!(payload.get("check-flags").unwrap().as_integer().unwrap(), 0); + assert_eq!( + payload.get("screenshot-id").unwrap().as_uuid().unwrap(), + screenshot_id + ); + assert_eq!( + payload.get("object-id").unwrap().as_uuid().unwrap(), + expected.object_id + ); + assert_eq!( + payload.get("abuser-id").unwrap().as_uuid().unwrap(), + expected.abuser_id + ); + assert_eq!( + payload + .get("abuse-region-name") + .unwrap() + .as_string() + .unwrap(), + "" + ); + assert_eq!( + payload.get("abuse-region-id").unwrap().as_uuid().unwrap(), + UUID::zero() + ); + assert_eq!( + payload.get("position").unwrap().as_vector3().unwrap(), + expected.position + ); + assert_eq!( + payload.get("summary").unwrap().as_string().unwrap(), + "summary" + ); + assert_eq!( + payload.get("version-string").unwrap().as_string().unwrap(), + "<3 LibreMetaverse" + ); + assert_eq!( + payload.get("details").unwrap().as_string().unwrap(), + "details" + ); + assert!(!requests.iter().any(|request| request.uri.0 == SEND)); +} + +#[test] +fn live_report_submission_requires_explicit_opt_in() { + let live_cap = "https://example.com/send-report"; + let (client, requests) = fixture(&[("SendUserReport", live_cap)], OSD::Map(HashMap::new())); + + let error = block_on( + client + .user_reports() + .send_user_report(report(UUID::zero()), None), + ) + .expect_err("live report must be gated"); + assert_eq!(error, Error::InvalidOperation); + assert!( + !requests + .lock() + .expect("request recording") + .iter() + .any(|request| request.uri.0 == live_cap) + ); +} + +#[test] +fn category_request_propagates_preexisting_cancellation() { + let (client, _) = fixture(&[("AbuseCategories", CATEGORIES)], OSD::Map(HashMap::new())); + let cancellation = CancellationTokenSource::new(); + cancellation.cancel(); + + let error = block_on( + client + .user_reports() + .fetch_abuse_report_categories(None, Some(cancellation.token())), + ) + .expect_err("cancellation must be propagated"); + assert_eq!(error, Error::Cancelled); +} diff --git a/tests/compat/tests/world_manager_shims.rs b/tests/compat/tests/world_manager_shims.rs index d7fbe12..2299554 100644 --- a/tests/compat/tests/world_manager_shims.rs +++ b/tests/compat/tests/world_manager_shims.rs @@ -1,4 +1,6 @@ -use libremetaverse::marketplace::{MarketplaceListing, MarketplaceManager}; +use libremetaverse::marketplace::{ + MarketplaceListing, MarketplaceListingStatus, MarketplaceManager, +}; use libremetaverse::{ DirectoryManager, DirectoryManagerAgentSearchData, EstateTask, EstateTools, FriendsManager, GroupManager, GroupProfileEventArgs, ObjectManager, ParcelManager, ParcelSettings, Simulator, @@ -9,14 +11,6 @@ use libremetaverse_types::compat::{ CancellationToken, EventHandler, IAsyncEnumerable, Subscription, }; -fn member_id(result: Result) -> &'static str { - result - .err() - .expect("failure-only shim unexpectedly returned a value") - .csharp_member() - .expect("failure-only shim error") -} - fn compile_search_call(directory: &DirectoryManager, token: CancellationToken) { let _: Result, libremetaverse::Error> = directory.search_people(String::new(), Some(token)); @@ -67,15 +61,17 @@ fn world_and_social_flows_have_typed_callable_signatures() { } #[test] -fn world_facing_constructors_preserve_implemented_and_pending_behavior() { - assert_eq!( - member_id(EstateTask::new()), - "M:LibreMetaverse.EstateTask.#ctor" - ); - assert_eq!( - member_id(MarketplaceListing::new()), - "M:LibreMetaverse.Marketplace.MarketplaceListing.#ctor" - ); +fn world_facing_constructors_preserve_implemented_defaults() { + let task = EstateTask::new().expect("EstateTask constructor"); + assert_eq!(task.task_id, UUID::zero()); + assert_eq!(task.task_local_id, 0); + assert_eq!((task.score, task.mono_score), (0.0, 0.0)); + assert!(task.task_name.is_empty()); + assert!(task.owner_name.is_empty()); + let listing = MarketplaceListing::new().expect("MarketplaceListing constructor"); + assert_eq!(listing.status(), MarketplaceListingStatus::Unknown); + assert_eq!(listing.listing_id(), 0); + assert!(listing.edit_url().is_none()); let parcel = ParcelSettings::new().expect("ParcelSettings constructor"); assert!(parcel.track_parcels); assert!(parcel.always_request_acl); diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index da3b273..cab011f 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -78,6 +78,12 @@ NATIVE_TYPES = { "T:LibreMetaverse.InventoryMaterial": "crate::inventory::InventoryMaterial", "T:LibreMetaverse.InventoryNode": "crate::inventory::InventoryNode", "T:LibreMetaverse.InventoryNodeDictionary": "crate::inventory::InventoryNodeDictionary", + "T:LibreMetaverse.Marketplace.MarketplaceErrorEventArgs": "crate::marketplace_runtime::MarketplaceErrorEventArgs", + "T:LibreMetaverse.Marketplace.MarketplaceFolderClassifier": "crate::marketplace_runtime::MarketplaceFolderClassifier", + "T:LibreMetaverse.Marketplace.MarketplaceListing": "crate::marketplace_runtime::MarketplaceListing", + "T:LibreMetaverse.Marketplace.MarketplaceListingChangedEventArgs": "crate::marketplace_runtime::MarketplaceListingChangedEventArgs", + "T:LibreMetaverse.Marketplace.MarketplaceListingsSyncedEventArgs": "crate::marketplace_runtime::MarketplaceListingsSyncedEventArgs", + "T:LibreMetaverse.Marketplace.MarketplaceManager": "crate::marketplace_runtime::MarketplaceManager", "T:LibreMetaverse.InventoryNotecard": "crate::inventory::InventoryNotecard", "T:LibreMetaverse.InventoryObject": "crate::inventory::InventoryObject", "T:LibreMetaverse.InventoryObjectAddedEventArgs": "crate::inventory::InventoryObjectAddedEventArgs", @@ -143,6 +149,18 @@ NATIVE_TYPES = { "T:LibreMetaverse.Assets.GestureStepWait": "crate::gesture::GestureStepWait", "T:LibreMetaverse.ExperienceInfoEventArgs": "crate::agent_messages::ExperienceInfoEventArgs", "T:LibreMetaverse.ExperiencePreferencesEventArgs": "crate::agent_messages::ExperiencePreferencesEventArgs", + "T:LibreMetaverse.EstateBansReplyEventArgs": "crate::estate_tools::EstateBansReplyEventArgs", + "T:LibreMetaverse.EstateCovenantReplyEventArgs": "crate::estate_tools::EstateCovenantReplyEventArgs", + "T:LibreMetaverse.EstateExperienceReplyEventArgs": "crate::estate_tools::EstateExperienceReplyEventArgs", + "T:LibreMetaverse.EstateGroupsReplyEventArgs": "crate::estate_tools::EstateGroupsReplyEventArgs", + "T:LibreMetaverse.EstateManagersReplyEventArgs": "crate::estate_tools::EstateManagersReplyEventArgs", + "T:LibreMetaverse.EstateTask": "crate::estate_tools::EstateTask", + "T:LibreMetaverse.EstateTools": "crate::estate_tools::EstateTools", + "T:LibreMetaverse.EstateUpdateInfoReplyEventArgs": "crate::estate_tools::EstateUpdateInfoReplyEventArgs", + "T:LibreMetaverse.EstateUsersReplyEventArgs": "crate::estate_tools::EstateUsersReplyEventArgs", + "T:LibreMetaverse.RegionRestartSchedule": "crate::estate_tools::RegionRestartSchedule", + "T:LibreMetaverse.TopCollidersReplyEventArgs": "crate::estate_tools::TopCollidersReplyEventArgs", + "T:LibreMetaverse.TopScriptsReplyEventArgs": "crate::estate_tools::TopScriptsReplyEventArgs", "T:LibreMetaverse.GroupChatJoinedEventArgs": "crate::agent_messages::GroupChatJoinedEventArgs", "T:LibreMetaverse.RegionExperiencesEventArgs": "crate::agent_messages::RegionExperiencesEventArgs", "T:LibreMetaverse.SetDisplayNameReplyEventArgs": "crate::agent_messages::SetDisplayNameReplyEventArgs", @@ -456,6 +474,8 @@ NATIVE_DECLARATIONS = { } NATIVE_MEMBER_BODIES = { + "P:LibreMetaverse.GridClient.Estate": "self.native_estate().unwrap_or_else(|_| panic!(\"failed to construct EstateTools\"))", + "P:LibreMetaverse.GridClient.Estate#set": "self.native_set_estate(value)", "P:LibreMetaverse.GridClient.Environment": "self.native_environment().unwrap_or_else(|_| panic!(\"failed to construct EnvironmentManager\"))", "P:LibreMetaverse.GridClient.Environment#set": "self.native_set_environment(value)",