diff --git a/.gitea/workflows/codegen.yml b/.gitea/workflows/codegen.yml index e6d2d12..3f6f02a 100644 --- a/.gitea/workflows/codegen.yml +++ b/.gitea/workflows/codegen.yml @@ -16,8 +16,10 @@ on: - "tools/check_milestone_09_issue_59.py" - "tools/check_milestone_09_issue_60.py" - "tools/check_milestone_09_issue_61.py" + - "tools/check_milestone_09_issue_62.py" - "crates/libremetaverse/src/agent_movement.rs" - "crates/libremetaverse/src/inventory.rs" + - "crates/libremetaverse/src/inventory_manager.rs" - "docs/agent-movement.md" - "docs/inventory.md" - "crates/libremetaverse/Cargo.toml" @@ -32,6 +34,8 @@ on: - "tests/compat/tests/wire_semantics.rs" - "tests/compat/tests/appearance_visual_semantics.rs" - "tests/compat/tests/inventory_store_semantics.rs" + - "tests/compat/tests/inventory_manager_semantics.rs" + - "tests/compat/tests/task_inventory_semantics.rs" - "tests/compat/tests/misclassified_link_semantics.rs" - "tests/upstream-tests.json" pull_request: @@ -49,8 +53,10 @@ on: - "tools/check_milestone_09_issue_59.py" - "tools/check_milestone_09_issue_60.py" - "tools/check_milestone_09_issue_61.py" + - "tools/check_milestone_09_issue_62.py" - "crates/libremetaverse/src/agent_movement.rs" - "crates/libremetaverse/src/inventory.rs" + - "crates/libremetaverse/src/inventory_manager.rs" - "docs/agent-movement.md" - "docs/inventory.md" - "crates/libremetaverse/Cargo.toml" @@ -65,6 +71,8 @@ on: - "tests/compat/tests/wire_semantics.rs" - "tests/compat/tests/appearance_visual_semantics.rs" - "tests/compat/tests/inventory_store_semantics.rs" + - "tests/compat/tests/inventory_manager_semantics.rs" + - "tests/compat/tests/task_inventory_semantics.rs" - "tests/compat/tests/misclassified_link_semantics.rs" - "tests/upstream-tests.json" workflow_dispatch: @@ -90,6 +98,7 @@ jobs: python3 tools/check_milestone_09_issue_59.py python3 tools/check_milestone_09_issue_60.py python3 tools/check_milestone_09_issue_61.py + python3 tools/check_milestone_09_issue_62.py cargo check --manifest-path tests/api-compile/Cargo.toml --locked - name: Test generated wire codecs and translated parity cases run: | @@ -98,6 +107,8 @@ jobs: cargo test -p libremetaverse --test packet_osd cargo test -p libremetaverse-compat-tests --test wire_semantics cargo test -p libremetaverse-compat-tests --test inventory_store_semantics + cargo test -p libremetaverse-compat-tests --test inventory_manager_semantics + cargo test -p libremetaverse-compat-tests --test task_inventory_semantics cargo test -p libremetaverse-compat-tests --test misclassified_link_semantics - name: Test generated visual and foliage catalogs run: | diff --git a/.gitea/workflows/rust-workspace.yml b/.gitea/workflows/rust-workspace.yml index 4ff5bef..a8ddacc 100644 --- a/.gitea/workflows/rust-workspace.yml +++ b/.gitea/workflows/rust-workspace.yml @@ -30,5 +30,6 @@ jobs: python3 tools/check_milestone_09_issue_59.py python3 tools/check_milestone_09_issue_60.py python3 tools/check_milestone_09_issue_61.py + python3 tools/check_milestone_09_issue_62.py - name: Compile every workspace target with bounded memory run: cargo check --workspace --all-targets --locked -j 1 diff --git a/README.md b/README.md index fe04c59..e1d9b95 100644 --- a/README.md +++ b/README.md @@ -357,3 +357,13 @@ The versioned 64 MiB-bounded cache validates complete snapshots before install and uses same-directory temporary replacement with rollback. The compatibility and persistence contract is documented in [`docs/inventory.md`](docs/inventory.md). + +The client-owned `InventoryManager` now implements the packet-level create, +update, move, copy, remove, fetch, search, give, rez/derez, script-state, and +task-inventory workflows. Callback IDs and concurrent item/task waiters are +bounded and cancellation-safe; generated multi-block packets retain the UDP +codec's MTU-aware splitting. Capability replies and UDP replies are validated +before store reconciliation, and legacy task inventory uses a 16 MiB-bounded +`ReplyTaskInventory`/`RequestXfer` state machine. Focused translated and native +fake-packet tests cover correlation, stale replies, offers, bulk completion, +capability task inventory, and malformed transfer sizes. diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index 648b415..4bcd446 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: 153 types / 14,746 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 164 types / 14,877 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/caps.rs b/crates/libremetaverse/src/caps.rs index 56de328..323d63c 100644 --- a/crates/libremetaverse/src/caps.rs +++ b/crates/libremetaverse/src/caps.rs @@ -359,9 +359,10 @@ async fn run_seed_requests(weak: Weak, cancellation: CancellationToke let Some(simulator) = inner.simulator() else { return; }; - if !simulator.native_is_connected() { - return; - } + // Seed discovery is HTTP-only. It is valid for composed/fake clients + // to install a simulator and query capabilities before a UDP circuit + // is started, and tying this request to UDP connectivity creates an + // unnecessary race for every capability-backed manager. let http = simulator.client.native_http_caps_client(); let response = http .post_with_uri_osd_format_osd_cancellation_token_i_progress( diff --git a/crates/libremetaverse/src/client_core.rs b/crates/libremetaverse/src/client_core.rs index 874bfbe..6c3fc94 100644 --- a/crates/libremetaverse/src/client_core.rs +++ b/crates/libremetaverse/src/client_core.rs @@ -118,6 +118,7 @@ struct ClientRuntime { caps_rate_limiter: Mutex, network_manager: Mutex>, agent_manager: Mutex>, + inventory_manager: Mutex>>, shutdown_complete: Condvar, shutdown_wait: Mutex<()>, } @@ -146,6 +147,7 @@ impl ClientRuntime { caps_rate_limiter: Mutex::new(caps_rate_limiter), network_manager: Mutex::new(std::sync::Weak::new()), agent_manager: Mutex::new(std::sync::Weak::new()), + inventory_manager: Mutex::new(None), shutdown_complete: Condvar::new(), shutdown_wait: Mutex::new(()), } @@ -218,6 +220,10 @@ impl ClientRuntime { .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .take(); + self.inventory_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .take(); self.http_caps_client .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) @@ -239,6 +245,30 @@ impl ClientRuntime { } } +/// A non-owning route back to a live client runtime. +/// +/// Manager caches can hold this handle without creating an `Arc` cycle. The +/// settings and time provider are immutable client composition data and are +/// cloned only when a manager needs to perform an operation. +#[derive(Clone)] +pub(crate) struct ClientWeakHandle { + runtime: std::sync::Weak, + settings: Settings, + time_provider: TimeProvider, +} + +impl ClientWeakHandle { + pub(crate) fn upgrade(&self) -> Option { + Some(GridClient { + settings: self.settings.clone(), + time_provider: self.time_provider.clone(), + runtime: self.runtime.upgrade()?, + network_manager: None, + agent_manager: None, + }) + } +} + /// Native owner for the mapped C# `GridClient` lifecycle slice. /// /// Construction is side-effect free: it creates no runtime, task, socket, or @@ -386,6 +416,40 @@ impl GridClient { Ok(manager) } + pub(crate) fn native_weak_handle(&self) -> ClientWeakHandle { + ClientWeakHandle { + runtime: Arc::downgrade(&self.runtime), + settings: self.settings.clone(), + time_provider: self.time_provider.clone(), + } + } + + pub(crate) fn native_inventory(&self) -> Result { + let mut cached = self + .runtime + .inventory_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return Ok( + crate::inventory_manager::InventoryManager::native_from_inner(Arc::clone(inner)), + ); + } + let manager = + crate::inventory_manager::InventoryManager::native_new(Some(Arc::new(self.clone())))?; + *cached = Some(manager.native_inner()); + Ok(manager) + } + + #[allow(clippy::needless_pass_by_value)] // The mapped C# property setter owns its value. + pub(crate) fn native_set_inventory(&mut self, value: crate::InventoryManager) { + *self + .runtime + .inventory_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.native_inner()); + } + #[allow(clippy::needless_pass_by_value)] // The mapped C# property setter owns its value. pub(crate) fn native_set_network(&mut self, value: crate::NetworkManager) { *self diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index 5cfe2ba..06b6a59 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -8561,49 +8561,16 @@ impl FileHelper { } /// C# type: `T:LibreMetaverse.FindObjectByPathReplyEventArgs`. -pub struct FindObjectByPathReplyEventArgs; -impl FindObjectByPathReplyEventArgs { - /// C# member: `M:LibreMetaverse.FindObjectByPathReplyEventArgs.#ctor(System.String,LibreMetaverse.UUID)`. - pub fn new( - path: String, - inventory_object_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FindObjectByPathReplyEventArgs.#ctor(System.String,LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.FindObjectByPathReplyEventArgs.InventoryObjectID`. - pub fn inventory_object_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FindObjectByPathReplyEventArgs.InventoryObjectID" - ) - } - /// C# member: `P:LibreMetaverse.FindObjectByPathReplyEventArgs.Path`. - pub fn path(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FindObjectByPathReplyEventArgs.Path" - ) - } -} +/// C# member: `M:LibreMetaverse.FindObjectByPathReplyEventArgs.#ctor(System.String,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.FindObjectByPathReplyEventArgs.InventoryObjectID`. +/// C# member: `P:LibreMetaverse.FindObjectByPathReplyEventArgs.Path`. +pub use crate::inventory_manager::FindObjectByPathReplyEventArgs; /// C# type: `T:LibreMetaverse.FolderUpdatedEventArgs`. -pub struct FolderUpdatedEventArgs; -impl FolderUpdatedEventArgs { - /// C# member: `M:LibreMetaverse.FolderUpdatedEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean)`. - pub fn new(folder_id: libremetaverse_types::UUID, success: bool) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FolderUpdatedEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `P:LibreMetaverse.FolderUpdatedEventArgs.FolderID`. - pub fn folder_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FolderUpdatedEventArgs.FolderID") - } - /// C# member: `P:LibreMetaverse.FolderUpdatedEventArgs.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FolderUpdatedEventArgs.Success") - } -} +/// C# member: `M:LibreMetaverse.FolderUpdatedEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `P:LibreMetaverse.FolderUpdatedEventArgs.FolderID`. +/// C# member: `P:LibreMetaverse.FolderUpdatedEventArgs.Success`. +pub use crate::inventory_manager::FolderUpdatedEventArgs; /// C# type: `T:LibreMetaverse.ForceSelectObjectsReplyEventArgs`. pub struct ForceSelectObjectsReplyEventArgs; @@ -9414,11 +9381,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Inventory`. pub fn inventory(&self) -> libremetaverse::InventoryManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Inventory") + /* native client-core implementation */ + self.native_inventory() + .unwrap_or_else(|_| panic!("failed to construct InventoryManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Inventory`. pub fn set_inventory(&mut self, value: libremetaverse::InventoryManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Inventory") + /* native client-core implementation */ + self.native_set_inventory(value) } /// C# member: `P:LibreMetaverse.GridClient.Marketplace`. pub fn marketplace(&self) -> libremetaverse::marketplace::MarketplaceManager { @@ -12392,7 +12362,7 @@ pub use crate::inventory::InventoryLSL; pub use crate::inventory::InventoryLandmark; /// C# type: `T:LibreMetaverse.InventoryManager`. -pub struct InventoryManager; +pub use crate::inventory_manager::InventoryManager; impl InventoryManager { /// C# member: `E:LibreMetaverse.InventoryManager.FindObjectByPathReply`. pub fn subscribe_find_object_by_path_reply( @@ -12401,16 +12371,14 @@ impl InventoryManager { libremetaverse::FindObjectByPathReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.InventoryManager.FindObjectByPathReply" - ) + self.native_subscribe_find_object_by_path_reply(handler) } /// C# member: `E:LibreMetaverse.InventoryManager.FolderUpdated`. pub fn subscribe_folder_updated( &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.InventoryManager.FolderUpdated") + self.native_subscribe_folder_updated(handler) } /// C# member: `E:LibreMetaverse.InventoryManager.InventoryObjectOffered`. pub fn subscribe_inventory_object_offered( @@ -12419,16 +12387,14 @@ impl InventoryManager { libremetaverse::InventoryObjectOfferedEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.InventoryManager.InventoryObjectOffered" - ) + self.native_subscribe_inventory_object_offered(handler) } /// C# member: `E:LibreMetaverse.InventoryManager.ItemReceived`. pub fn subscribe_item_received( &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.InventoryManager.ItemReceived") + self.native_subscribe_item_received(handler) } /// C# member: `E:LibreMetaverse.InventoryManager.SaveAssetToInventory`. pub fn subscribe_save_asset_to_inventory( @@ -12437,9 +12403,7 @@ impl InventoryManager { libremetaverse::SaveAssetToInventoryEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.InventoryManager.SaveAssetToInventory" - ) + self.native_subscribe_save_asset_to_inventory(handler) } /// C# member: `E:LibreMetaverse.InventoryManager.ScriptRunningReply`. pub fn subscribe_script_running_reply( @@ -12448,9 +12412,7 @@ impl InventoryManager { libremetaverse::ScriptRunningReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.InventoryManager.ScriptRunningReply" - ) + self.native_subscribe_script_running_reply(handler) } /// C# member: `E:LibreMetaverse.InventoryManager.TaskInventoryReply`. pub fn subscribe_task_inventory_reply( @@ -12459,9 +12421,7 @@ impl InventoryManager { libremetaverse::TaskInventoryReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.InventoryManager.TaskInventoryReply" - ) + self.native_subscribe_task_inventory_reply(handler) } /// C# member: `E:LibreMetaverse.InventoryManager.TaskItemReceived`. pub fn subscribe_task_item_received( @@ -12470,13 +12430,11 @@ impl InventoryManager { libremetaverse::TaskItemReceivedEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.InventoryManager.TaskItemReceived" - ) + self.native_subscribe_task_item_received(handler) } /// C# member: `F:LibreMetaverse.InventoryManager.MAGIC_ID`. pub fn magic_id() -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("F:LibreMetaverse.InventoryManager.MAGIC_ID") + crate::inventory_manager::InventoryManager::native_magic_id() } /// C# member: `F:LibreMetaverse.InventoryManager.MAX_GIVE_ITEMS`. pub const MAX_GIVE_ITEMS: i32 = 66; @@ -12484,9 +12442,7 @@ impl InventoryManager { pub fn new( client: Option>, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.#ctor(LibreMetaverse.GridClient)", - ) + crate::inventory_manager::InventoryManager::native_new(client) } /// C# member: `M:LibreMetaverse.InventoryManager.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn copy_item_with_uuid_uuid_string_uuid_cancellation_token( @@ -12497,9 +12453,8 @@ impl InventoryManager { old_owner_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_copy_item(item, new_parent, new_name, old_owner_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. pub async fn copy_item_with_uuid_uuid_string_cancellation_token( @@ -12509,9 +12464,8 @@ impl InventoryManager { new_name: String, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", - ) + self.native_copy_item_default_owner(item, new_parent, new_name, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.CopyScriptToTask(System.UInt32,LibreMetaverse.InventoryItem,System.Boolean,LibreMetaverse.Simulator)`. pub fn copy_script_to_task( @@ -12521,9 +12475,7 @@ impl InventoryManager { enable_script: bool, simulator: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CopyScriptToTask(System.UInt32,LibreMetaverse.InventoryItem,System.Boolean,LibreMetaverse.Simulator)", - ) + self.native_copy_script_to_task(object_local_id, item, enable_script, simulator) } /// C# member: `M:LibreMetaverse.InventoryManager.CreateFolder(LibreMetaverse.UUID,System.String)`. pub fn create_folder_with_uuid_string( @@ -12531,9 +12483,7 @@ impl InventoryManager { parent_id: libremetaverse_types::UUID, name: String, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CreateFolder(LibreMetaverse.UUID,System.String)", - ) + self.native_create_folder(parent_id, name, libremetaverse_types::FolderType::None) } /// C# member: `M:LibreMetaverse.InventoryManager.CreateFolder(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType)`. pub fn create_folder_with_uuid_string_folder_type( @@ -12542,18 +12492,14 @@ impl InventoryManager { name: String, preferred_type: libremetaverse_types::FolderType, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CreateFolder(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType)", - ) + self.native_create_folder(parent_id, name, preferred_type) } /// C# member: `M:LibreMetaverse.InventoryManager.CreateInventoryItem(LibreMetaverse.InventoryType,LibreMetaverse.UUID)`. pub fn create_inventory_item( type_: libremetaverse_types::InventoryType, id: libremetaverse_types::UUID, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CreateInventoryItem(LibreMetaverse.InventoryType,LibreMetaverse.UUID)", - ) + crate::inventory_manager::InventoryManager::native_create_inventory_item(type_, id) } /// C# member: `M:LibreMetaverse.InventoryManager.CreateItemAsync(LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.InventoryType,LibreMetaverse.PermissionMask,System.Threading.CancellationToken)`. pub async fn create_item_with_uuid_string_string_asset_type_uuid_inventory_type_permission_mask_cancellation_token( @@ -12567,9 +12513,18 @@ impl InventoryManager { next_owner_mask: libremetaverse::PermissionMask, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CreateItemAsync(LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.InventoryType,LibreMetaverse.PermissionMask,System.Threading.CancellationToken)", + self.native_create_item( + parent_folder, + name, + description, + type_, + asset_transaction_id, + inv_type, + libremetaverse_types::WearableType::Shape, + next_owner_mask, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.CreateItemAsync(LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.InventoryType,LibreMetaverse.WearableType,LibreMetaverse.PermissionMask,System.Threading.CancellationToken)`. pub async fn create_item_with_uuid_string_string_asset_type_uuid_inventory_type_wearable_type_permission_mask_cancellation_token( @@ -12584,9 +12539,18 @@ impl InventoryManager { next_owner_mask: libremetaverse::PermissionMask, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CreateItemAsync(LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.InventoryType,LibreMetaverse.WearableType,LibreMetaverse.PermissionMask,System.Threading.CancellationToken)", + self.native_create_item( + parent_folder, + name, + description, + type_, + asset_transaction_id, + inv_type, + wearable_type, + next_owner_mask, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.CreateItemFromAssetAsync(System.Byte[],System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.InventoryType,LibreMetaverse.UUID,LibreMetaverse.Permissions,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})`. pub async fn create_item_from_asset( @@ -12646,9 +12610,16 @@ impl InventoryManager { transaction_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CreateLinkAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.InventoryType,LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_create_link( + folder_id, + item_id, + name, + description, + inv_type, + transaction_id, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.CreateLinksAsync(LibreMetaverse.UUID,System.Collections.Generic.IEnumerable{System.ValueTuple{LibreMetaverse.InventoryBase,System.String}},System.Action{System.Boolean},System.Threading.CancellationToken)`. pub async fn create_links( @@ -12658,9 +12629,8 @@ impl InventoryManager { callback: Option>, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CreateLinksAsync(LibreMetaverse.UUID,System.Collections.Generic.IEnumerable{System.ValueTuple{LibreMetaverse.InventoryBase,System.String}},System.Action{System.Boolean},System.Threading.CancellationToken)", - ) + self.native_create_links(folder_id, links_to_create, callback, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.CreateOrRetrieveInventoryItem(LibreMetaverse.InventoryType,LibreMetaverse.UUID)`. pub fn create_or_retrieve_inventory_item( @@ -12668,53 +12638,46 @@ impl InventoryManager { inv_type: libremetaverse_types::InventoryType, item_id: libremetaverse_types::UUID, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CreateOrRetrieveInventoryItem(LibreMetaverse.InventoryType,LibreMetaverse.UUID)", - ) + self.native_create_or_retrieve_inventory_item(inv_type, item_id) } /// C# member: `M:LibreMetaverse.InventoryManager.DecryptShadowID(LibreMetaverse.UUID)`. pub fn decrypt_shadow_id( shadow_id: libremetaverse_types::UUID, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.DecryptShadowID(LibreMetaverse.UUID)", - ) + crate::inventory_manager::InventoryManager::native_decrypt_shadow_id(shadow_id) } /// C# member: `M:LibreMetaverse.InventoryManager.Dispose`. pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.InventoryManager.Dispose") + self.native_dispose() } /// C# member: `M:LibreMetaverse.InventoryManager.EmptyFolder(LibreMetaverse.UUID)`. pub fn empty_folder(&self, folder_id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.EmptyFolder(LibreMetaverse.UUID)", - ) + self.native_empty_folder(folder_id) } /// C# member: `M:LibreMetaverse.InventoryManager.EmptyLostAndFoundAsync(System.Threading.CancellationToken)`. pub async fn empty_lost_and_found( &self, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.EmptyLostAndFoundAsync(System.Threading.CancellationToken)", + self.native_empty_system_folder( + libremetaverse_types::FolderType::LostAndFound, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.EmptyTrashAsync(System.Threading.CancellationToken)`. pub async fn empty_trash( &self, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.EmptyTrashAsync(System.Threading.CancellationToken)", - ) + self.native_empty_system_folder(libremetaverse_types::FolderType::Trash, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.EncryptAssetID(LibreMetaverse.UUID)`. pub fn encrypt_asset_id( asset_id: libremetaverse_types::UUID, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.EncryptAssetID(LibreMetaverse.UUID)", - ) + crate::inventory_manager::InventoryManager::native_encrypt_asset_id(asset_id) } /// C# member: `M:LibreMetaverse.InventoryManager.FetchItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn fetch_item( @@ -12723,9 +12686,8 @@ impl InventoryManager { owner_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.FetchItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_fetch_item(item_id, owner_id, cancellation_token, false) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.FetchItemHttpAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn fetch_item_http( @@ -12734,27 +12696,21 @@ impl InventoryManager { owner_id: libremetaverse_types::UUID, token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.FetchItemHttpAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_fetch_item(item_id, owner_id, token, true).await } /// C# member: `M:LibreMetaverse.InventoryManager.FindFolderForType(LibreMetaverse.AssetType)`. pub fn find_folder_for_type_with_asset_type( &self, type_: libremetaverse_types::AssetType, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.FindFolderForType(LibreMetaverse.AssetType)", - ) + Ok(self.native_find_folder_for_asset_type(type_)) } /// C# member: `M:LibreMetaverse.InventoryManager.FindFolderForType(LibreMetaverse.FolderType)`. pub fn find_folder_for_type_with_folder_type( &self, type_: libremetaverse_types::FolderType, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.FindFolderForType(LibreMetaverse.FolderType)", - ) + Ok(self.native_find_folder_for_type(type_)) } /// C# member: `M:LibreMetaverse.InventoryManager.FindObjectByPathAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. pub async fn find_object_by_path( @@ -12764,9 +12720,8 @@ impl InventoryManager { path: String, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.FindObjectByPathAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", - ) + self.native_find_object_by_path(base_folder, inventory_owner, path, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.FolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken,System.Boolean)`. pub async fn folder_contents( @@ -12779,9 +12734,16 @@ impl InventoryManager { cancellation_token: Option, follow_links: Option, ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.FolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken,System.Boolean)", + self.native_folder_contents( + folder, + owner, + fetch_folders, + fetch_items, + order, + cancellation_token, + follow_links.unwrap_or(false), ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.GetInventoryRecursiveAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.InventoryFolder},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)`. pub async fn get_inventory_recursive( @@ -12792,9 +12754,8 @@ impl InventoryManager { items: Vec, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.GetInventoryRecursiveAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.InventoryFolder},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)", - ) + self.native_get_inventory_recursive(folder_id, owner, cats, items, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.GetTaskInventoryAsync(LibreMetaverse.UUID,System.UInt32,LibreMetaverse.Simulator,System.Threading.CancellationToken)`. pub async fn get_task_inventory( @@ -12804,9 +12765,8 @@ impl InventoryManager { simulator: Option, cancellation_token: Option, ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.GetTaskInventoryAsync(LibreMetaverse.UUID,System.UInt32,LibreMetaverse.Simulator,System.Threading.CancellationToken)", - ) + self.native_get_task_inventory(object_id, object_local_id, simulator, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.GiveFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)`. pub async fn give_folder( @@ -12817,9 +12777,14 @@ impl InventoryManager { do_effect: bool, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.GiveFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)", + self.native_give_folder( + folder_id, + folder_name, + recipient, + do_effect, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.GiveItem(LibreMetaverse.UUID,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Boolean)`. pub fn give_item( @@ -12830,15 +12795,11 @@ impl InventoryManager { recipient: libremetaverse_types::UUID, do_effect: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.GiveItem(LibreMetaverse.UUID,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Boolean)", - ) + self.native_give_item(item_id, item_name, asset_type, recipient, do_effect) } /// C# member: `M:LibreMetaverse.InventoryManager.ItemCRC(LibreMetaverse.InventoryItem)`. pub fn item_crc(iitem: &libremetaverse::InventoryItem) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.ItemCRC(LibreMetaverse.InventoryItem)", - ) + crate::inventory_manager::InventoryManager::native_item_crc(&iitem) } /// C# member: `M:LibreMetaverse.InventoryManager.LocalFind(LibreMetaverse.UUID,System.String[],System.Int32,System.Boolean)`. pub fn local_find( @@ -12848,9 +12809,7 @@ impl InventoryManager { level: i32, first_only: bool, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.LocalFind(LibreMetaverse.UUID,System.String[],System.Int32,System.Boolean)", - ) + self.native_local_find(base_folder, path, level, first_only) } /// C# member: `M:LibreMetaverse.InventoryManager.Move(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryFolder)`. pub fn move_( @@ -12858,9 +12817,7 @@ impl InventoryManager { item: libremetaverse::InventoryBase, new_parent: libremetaverse::InventoryFolder, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.Move(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryFolder)", - ) + self.native_move_object(item, new_parent) } /// C# member: `M:LibreMetaverse.InventoryManager.MoveFolder(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub fn move_folder_with_uuid_uuid_cancellation_token( @@ -12869,8 +12826,9 @@ impl InventoryManager { new_parent_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveFolder(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_move_folders( + std::collections::HashMap::from([(folder_id, new_parent_id)]), + cancellation_token, ) } /// C# member: `M:LibreMetaverse.InventoryManager.MoveFolderAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`. @@ -12880,9 +12838,11 @@ impl InventoryManager { new_parent_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveFolderAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_move_folders_async( + std::collections::HashMap::from([(folder_id, new_parent_id)]), + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.MoveFolders(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)`. pub fn move_folders_with_dictionary_cancellation_token( @@ -12893,9 +12853,7 @@ impl InventoryManager { >, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveFolders(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)", - ) + self.native_move_folders(folders_new_parents, cancellation_token) } /// C# member: `M:LibreMetaverse.InventoryManager.MoveFoldersAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)`. pub async fn move_folders_with_dictionary_cancellation_token_d81a910e( @@ -12906,9 +12864,8 @@ impl InventoryManager { >, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveFoldersAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)", - ) + self.native_move_folders_async(folders_new_parents, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.MoveItem(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. pub fn move_item_with_uuid_uuid_string_cancellation_token( @@ -12918,8 +12875,9 @@ impl InventoryManager { new_name: String, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveItem(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", + self.native_move_items( + std::collections::HashMap::from([(item_id, (folder_id, new_name))]), + cancellation_token, ) } /// C# member: `M:LibreMetaverse.InventoryManager.MoveItem(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`. @@ -12929,8 +12887,9 @@ impl InventoryManager { folder_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveItem(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_move_items( + std::collections::HashMap::from([(item_id, (folder_id, String::new()))]), + cancellation_token, ) } /// C# member: `M:LibreMetaverse.InventoryManager.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. @@ -12941,9 +12900,11 @@ impl InventoryManager { new_name: String, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", + self.native_move_items_async( + std::collections::HashMap::from([(item_id, (folder_id, new_name))]), + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn move_item_with_uuid_uuid_cancellation_token_1cf693fd( @@ -12952,9 +12913,11 @@ impl InventoryManager { folder_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_move_items_async( + std::collections::HashMap::from([(item_id, (folder_id, String::new()))]), + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.MoveItems(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)`. pub fn move_items_with_dictionary_cancellation_token( @@ -12965,8 +12928,12 @@ impl InventoryManager { >, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveItems(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)", + self.native_move_items( + items_new_folders + .into_iter() + .map(|(item, folder)| (item, (folder, String::new()))) + .collect(), + cancellation_token, ) } /// C# member: `M:LibreMetaverse.InventoryManager.MoveItemsAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)`. @@ -12978,9 +12945,14 @@ impl InventoryManager { >, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveItemsAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)", + self.native_move_items_async( + items_new_folders + .into_iter() + .map(|(item, folder)| (item, (folder, String::new()))) + .collect(), + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.MoveTaskInventory(System.UInt32,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Simulator)`. pub fn move_task_inventory( @@ -12990,17 +12962,18 @@ impl InventoryManager { inventory_folder_id: libremetaverse_types::UUID, simulator: libremetaverse::Simulator, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.MoveTaskInventory(System.UInt32,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Simulator)", + self.native_move_task_inventory( + object_local_id, + task_item_id, + inventory_folder_id, + simulator, ) } /// C# member: `M:LibreMetaverse.InventoryManager.ParseTaskInventory(System.String)`. pub fn parse_task_inventory( task_data: String, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.ParseTaskInventory(System.String)", - ) + crate::inventory_manager::InventoryManager::native_parse_task_inventory(task_data) } /// C# member: `M:LibreMetaverse.InventoryManager.RemoveDescendantsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn remove_descendants( @@ -13008,9 +12981,8 @@ impl InventoryManager { folder: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RemoveDescendantsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_remove_descendants(folder, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RemoveFolderAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn remove_folder( @@ -13018,9 +12990,8 @@ impl InventoryManager { folder: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RemoveFolderAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_remove_inventory_objects(Vec::new(), vec![folder], cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RemoveItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn remove_item( @@ -13028,9 +12999,8 @@ impl InventoryManager { item: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RemoveItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_remove_inventory_objects(vec![item], Vec::new(), cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RemoveItemsAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)`. pub async fn remove_items( @@ -13038,9 +13008,8 @@ impl InventoryManager { items: Box>, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RemoveItemsAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)", - ) + self.native_remove_inventory_objects(items.collect(), Vec::new(), cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RemoveTaskInventory(System.UInt32,LibreMetaverse.UUID,LibreMetaverse.Simulator)`. pub fn remove_task_inventory( @@ -13049,9 +13018,7 @@ impl InventoryManager { task_item_id: libremetaverse_types::UUID, simulator: libremetaverse::Simulator, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RemoveTaskInventory(System.UInt32,LibreMetaverse.UUID,LibreMetaverse.Simulator)", - ) + self.native_remove_task_inventory(object_local_id, task_item_id, simulator) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestCopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn request_copy_item_with_uuid_uuid_string_uuid_cancellation_token( @@ -13062,9 +13029,8 @@ impl InventoryManager { old_owner_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestCopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_copy_item(item, new_parent, new_name, old_owner_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestCopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. pub async fn request_copy_item_with_uuid_uuid_string_cancellation_token( @@ -13074,9 +13040,8 @@ impl InventoryManager { new_name: String, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestCopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", - ) + self.native_copy_item_default_owner(item, new_parent, new_name, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestCopyItemFromNotecardAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})`. pub async fn request_copy_item_from_notecard( @@ -13094,9 +13059,15 @@ impl InventoryManager { >, >, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestCopyItemFromNotecardAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})", + self.native_copy_item_from_notecard( + object_id, + notecard_id, + folder_id, + item_id, + cancellation_token, + progress, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestCopyItemsAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{System.String},LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn request_copy_items( @@ -13107,9 +13078,14 @@ impl InventoryManager { old_owner_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestCopyItemsAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{System.String},LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_copy_items( + items, + target_folders, + new_names, + old_owner_id, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestCopyItemsWithResultAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{System.String},LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn request_copy_items_with_result( @@ -13120,9 +13096,14 @@ impl InventoryManager { old_owner_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestCopyItemsWithResultAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{System.String},LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_copy_items( + items, + target_folders, + new_names, + old_owner_id, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestCreateItemFromAssetAsync(System.Byte[],System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.InventoryType,LibreMetaverse.UUID,LibreMetaverse.Permissions,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})`. pub async fn request_create_item_from_asset( @@ -13160,9 +13141,7 @@ impl InventoryManager { &self, object_local_id: u32, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestDeRezToInventory(System.UInt32)", - ) + self.native_de_rez_to_objects_folder(object_local_id) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestDeRezToInventory(System.UInt32,LibreMetaverse.DeRezDestination,LibreMetaverse.UUID,LibreMetaverse.UUID)`. pub fn request_de_rez_to_inventory_with_u_int32_de_rez_destination_uuid_uuid( @@ -13172,9 +13151,7 @@ impl InventoryManager { dest_folder: libremetaverse_types::UUID, transaction_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestDeRezToInventory(System.UInt32,LibreMetaverse.DeRezDestination,LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) + self.native_de_rez_to_inventory(object_local_id, dest_type, dest_folder, transaction_id) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestFetchInventory(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub fn request_fetch_inventory_with_uuid_uuid_cancellation_token( @@ -13183,8 +13160,9 @@ impl InventoryManager { owner_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestFetchInventory(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_request_fetch_inventory( + std::collections::HashMap::from([(item_id, owner_id)]), + cancellation_token, ) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestFetchInventory(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)`. @@ -13193,9 +13171,7 @@ impl InventoryManager { items: std::collections::HashMap, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestFetchInventory(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)", - ) + self.native_request_fetch_inventory(items, cancellation_token) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestFetchInventoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn request_fetch_inventory_with_uuid_uuid_cancellation_token_6647b02a( @@ -13204,9 +13180,12 @@ impl InventoryManager { owner_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestFetchInventoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", + self.native_request_fetch_inventory_async( + std::collections::HashMap::from([(item_id, owner_id)]), + cancellation_token, + None, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestFetchInventoryAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken,System.Action{System.Collections.Generic.List{LibreMetaverse.InventoryItem}})`. pub async fn request_fetch_inventory_with_dictionary_cancellation_token_action( @@ -13215,9 +13194,8 @@ impl InventoryManager { cancellation_token: Option, callback: Option) + Send + Sync>>, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestFetchInventoryAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken,System.Action{System.Collections.Generic.List{LibreMetaverse.InventoryItem}})", - ) + self.native_request_fetch_inventory_async(items, cancellation_token, callback) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestFindObjectByPathAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. pub async fn request_find_object_by_path( @@ -13227,9 +13205,9 @@ impl InventoryManager { path: String, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestFindObjectByPathAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", - ) + self.native_find_object_by_path(base_folder, inventory_owner, path, cancellation_token) + .await + .map(|_| ()) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestFolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)`. pub async fn request_folder_contents_with_uuid_uuid_boolean_boolean_inventory_sort_order_cancellation_token( @@ -13241,9 +13219,15 @@ impl InventoryManager { order: libremetaverse::InventorySortOrder, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestFolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)", + self.native_request_folder_contents( + folder_id, + owner_id, + fetch_folders, + fetch_items, + order, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestFolderContentsAsync(System.Collections.Generic.List{LibreMetaverse.InventoryFolder},System.Uri,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)`. pub async fn request_folder_contents_with_list_uri_boolean_boolean_inventory_sort_order_cancellation_token( @@ -13255,9 +13239,15 @@ impl InventoryManager { order: libremetaverse::InventorySortOrder, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestFolderContentsAsync(System.Collections.Generic.List{LibreMetaverse.InventoryFolder},System.Uri,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)", + self.native_request_folder_contents_batch( + batch, + capability_uri, + fetch_folders, + fetch_items, + order, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestFolderUpdateAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)`. pub async fn request_folder_update( @@ -13269,9 +13259,15 @@ impl InventoryManager { order: Option, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestFolderUpdateAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)", + self.native_request_folder_update( + folder_id, + owner_id, + fetch_folders.unwrap_or(true), + fetch_items.unwrap_or(true), + order.unwrap_or(crate::InventorySortOrder::BY_NAME), + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.InventoryManager.RequestGetScriptRunning(LibreMetaverse.UUID,LibreMetaverse.UUID)`. pub fn request_get_script_running( @@ -13279,9 +13275,7 @@ impl InventoryManager { object_id: libremetaverse_types::UUID, script_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestGetScriptRunning(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) + self.native_request_get_script_running(object_id, script_id) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestRestoreRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.InventoryItem,LibreMetaverse.UUID)`. pub fn request_restore_rez_from_inventory( @@ -13290,9 +13284,7 @@ impl InventoryManager { item: libremetaverse::InventoryItem, query_id: libremetaverse_types::UUID, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestRestoreRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.InventoryItem,LibreMetaverse.UUID)", - ) + self.native_restore_rez_from_inventory(simulator, item, query_id) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem)`. pub fn request_rez_from_inventory_with_simulator_quaternion_vector3_inventory_item( @@ -13302,9 +13294,7 @@ impl InventoryManager { position: libremetaverse_types::Vector3, item: libremetaverse::InventoryItem, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem)", - ) + self.native_rez_from_inventory_default(simulator, rotation, position, item, None) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem,LibreMetaverse.UUID)`. pub fn request_rez_from_inventory_with_simulator_quaternion_vector3_inventory_item_uuid( @@ -13315,8 +13305,12 @@ impl InventoryManager { item: libremetaverse::InventoryItem, group_owner: libremetaverse_types::UUID, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem,LibreMetaverse.UUID)", + self.native_rez_from_inventory_default( + simulator, + rotation, + position, + item, + Some(group_owner), ) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.UUID,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)`. @@ -13331,8 +13325,15 @@ impl InventoryManager { query_id: libremetaverse_types::UUID, rez_selected: bool, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.UUID,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)", + self.native_rez_from_inventory( + simulator, + task_id, + rotation, + position, + item, + group_owner, + query_id, + rez_selected, ) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestSetScriptRunning(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)`. @@ -13342,18 +13343,14 @@ impl InventoryManager { script_id: libremetaverse_types::UUID, running: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestSetScriptRunning(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)", - ) + self.native_request_set_script_running(object_id, script_id, running) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestTaskInventory(System.UInt32)`. pub fn request_task_inventory_with_u_int32( &self, object_local_id: u32, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestTaskInventory(System.UInt32)", - ) + self.native_request_task_inventory(object_local_id, None) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestTaskInventory(System.UInt32,LibreMetaverse.Simulator)`. pub fn request_task_inventory_with_u_int32_simulator( @@ -13361,27 +13358,21 @@ impl InventoryManager { object_local_id: u32, simulator: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestTaskInventory(System.UInt32,LibreMetaverse.Simulator)", - ) + self.native_request_task_inventory(object_local_id, simulator) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestUpdateItem(LibreMetaverse.InventoryItem)`. pub fn request_update_item( &self, item: libremetaverse::InventoryItem, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestUpdateItem(LibreMetaverse.InventoryItem)", - ) + self.native_request_update_items(vec![item], libremetaverse_types::UUID::random()?) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem})`. pub fn request_update_items_with_list( &self, items: Vec, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem})", - ) + self.native_request_update_items(items, libremetaverse_types::UUID::random()?) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem},LibreMetaverse.UUID)`. pub fn request_update_items_with_list_uuid( @@ -13389,9 +13380,7 @@ impl InventoryManager { items: Vec, transaction_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem},LibreMetaverse.UUID)", - ) + self.native_request_update_items(items, transaction_id) } /// C# member: `M:LibreMetaverse.InventoryManager.RequestUpdateMaterialAgentInventoryAsync(LibreMetaverse.Assets.AssetMaterial,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})`. pub async fn request_update_material_agent_inventory( @@ -13593,9 +13582,7 @@ impl InventoryManager { name: String, type_: libremetaverse_types::FolderType, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.UpdateFolderProperties(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType)", - ) + self.native_update_folder_properties(folder_id, parent_id, name, type_) } /// C# member: `M:LibreMetaverse.InventoryManager.UpdateTaskInventory(System.UInt32,LibreMetaverse.InventoryItem,LibreMetaverse.Simulator,System.Boolean)`. pub fn update_task_inventory( @@ -13605,8 +13592,11 @@ impl InventoryManager { simulator: Option, only_mod_meta: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.UpdateTaskInventory(System.UInt32,LibreMetaverse.InventoryItem,LibreMetaverse.Simulator,System.Boolean)", + self.native_update_task_inventory( + object_local_id, + item, + simulator, + only_mod_meta.unwrap_or(true), ) } /// C# member: `M:LibreMetaverse.InventoryManager.UploadThumbnailAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Byte[],System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})`. @@ -13633,9 +13623,8 @@ impl InventoryManager { &self, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.WaitForNextInventoryOfferAsync(System.Threading.CancellationToken)", - ) + self.native_wait_for_next_inventory_offer(cancellation_token) + .await } /// C# member: `P:LibreMetaverse.InventoryManager.CanUploadLargeTextures`. pub fn can_upload_large_textures(&self) -> bool { @@ -13645,60 +13634,20 @@ impl InventoryManager { } /// C# member: `P:LibreMetaverse.InventoryManager.NoResults`. pub fn no_results() -> libremetaverse_types::compat::Task> { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryManager.NoResults") + crate::inventory_manager::InventoryManager::native_no_results() } /// C# member: `P:LibreMetaverse.InventoryManager.Store`. pub fn store(&self) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryManager.Store") + self.native_store() } } /// C# type: `T:LibreMetaverse.InventoryManager.CopyItemsResult`. -pub struct InventoryManagerCopyItemsResult; -impl InventoryManagerCopyItemsResult { - /// C# member: `M:LibreMetaverse.InventoryManager.CopyItemsResult.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.CopyItemsResult.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.CopiedItems`. - pub fn copied_items(&self) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.CopyItemsResult.CopiedItems" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.CopiedItems`. - pub fn set_copied_items(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.CopyItemsResult.CopiedItems" - ) - } - /// C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.Error`. - pub fn error(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.CopyItemsResult.Error" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.Error`. - pub fn set_error(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.CopyItemsResult.Error" - ) - } - /// C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.CopyItemsResult.Success" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.Success`. - pub fn set_success(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.CopyItemsResult.Success" - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryManager.CopyItemsResult.#ctor`. +/// C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.CopiedItems`. +/// C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.Error`. +/// C# member: `P:LibreMetaverse.InventoryManager.CopyItemsResult.Success`. +pub use crate::inventory_manager::InventoryManagerCopyItemsResult; /// C# type: `T:LibreMetaverse.InventoryManager.CreateItemFromAssetResult`. pub struct InventoryManagerCreateItemFromAssetResult; @@ -13784,51 +13733,11 @@ impl InventoryManagerCreateItemFromAssetResult { } /// C# type: `T:LibreMetaverse.InventoryManager.FolderUpdateResult`. -pub struct InventoryManagerFolderUpdateResult; -impl InventoryManagerFolderUpdateResult { - /// C# member: `M:LibreMetaverse.InventoryManager.FolderUpdateResult.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryManager.FolderUpdateResult.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.Contents`. - pub fn contents(&self) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.FolderUpdateResult.Contents" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.Contents`. - pub fn set_contents(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.FolderUpdateResult.Contents" - ) - } - /// C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.FolderID`. - pub fn folder_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.FolderUpdateResult.FolderID" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.FolderID`. - pub fn set_folder_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.FolderUpdateResult.FolderID" - ) - } - /// C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.FolderUpdateResult.Success" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.Success`. - pub fn set_success(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryManager.FolderUpdateResult.Success" - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryManager.FolderUpdateResult.#ctor`. +/// C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.Contents`. +/// C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.FolderID`. +/// C# member: `P:LibreMetaverse.InventoryManager.FolderUpdateResult.Success`. +pub use crate::inventory_manager::InventoryManagerFolderUpdateResult; /// C# type: `T:LibreMetaverse.InventoryMaterial`. /// C# member: `M:LibreMetaverse.InventoryMaterial.#ctor(LibreMetaverse.UUID)`. @@ -13875,69 +13784,14 @@ pub use crate::inventory::InventoryObject; pub use crate::inventory::InventoryObjectAddedEventArgs; /// C# type: `T:LibreMetaverse.InventoryObjectOfferedEventArgs`. -pub struct InventoryObjectOfferedEventArgs; -impl InventoryObjectOfferedEventArgs { - /// C# member: `M:LibreMetaverse.InventoryObjectOfferedEventArgs.#ctor(LibreMetaverse.InstantMessage,LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Boolean,LibreMetaverse.UUID)`. - pub fn new( - offer_details: libremetaverse::InstantMessage, - type_: libremetaverse_types::AssetType, - object_id: libremetaverse_types::UUID, - from_task: bool, - folder_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryObjectOfferedEventArgs.#ctor(LibreMetaverse.InstantMessage,LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Boolean,LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.Accept`. - pub fn accept(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectOfferedEventArgs.Accept" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.Accept`. - pub fn set_accept(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectOfferedEventArgs.Accept" - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectOfferedEventArgs.AssetType" - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.FolderID`. - pub fn folder_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectOfferedEventArgs.FolderID" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.FolderID`. - pub fn set_folder_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectOfferedEventArgs.FolderID" - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.FromTask`. - pub fn from_task(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectOfferedEventArgs.FromTask" - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.ObjectID`. - pub fn object_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectOfferedEventArgs.ObjectID" - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.Offer`. - pub fn offer(&self) -> libremetaverse::InstantMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectOfferedEventArgs.Offer" - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryObjectOfferedEventArgs.#ctor(LibreMetaverse.InstantMessage,LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Boolean,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.Accept`. +/// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.AssetType`. +/// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.FolderID`. +/// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.FromTask`. +/// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.ObjectID`. +/// C# member: `P:LibreMetaverse.InventoryObjectOfferedEventArgs.Offer`. +pub use crate::inventory_manager::InventoryObjectOfferedEventArgs; /// C# type: `T:LibreMetaverse.InventoryObjectRemovedEventArgs`. /// C# member: `M:LibreMetaverse.InventoryObjectRemovedEventArgs.#ctor(LibreMetaverse.InventoryBase)`. @@ -13987,19 +13841,9 @@ pub use crate::inventory::InventoryTexture; pub use crate::inventory::InventoryWearable; /// C# type: `T:LibreMetaverse.ItemReceivedEventArgs`. -pub struct ItemReceivedEventArgs; -impl ItemReceivedEventArgs { - /// C# member: `M:LibreMetaverse.ItemReceivedEventArgs.#ctor(LibreMetaverse.InventoryItem)`. - pub fn new(item: libremetaverse::InventoryItem) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ItemReceivedEventArgs.#ctor(LibreMetaverse.InventoryItem)", - ) - } - /// C# member: `P:LibreMetaverse.ItemReceivedEventArgs.Item`. - pub fn item(&self) -> libremetaverse::InventoryItem { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ItemReceivedEventArgs.Item") - } -} +/// C# member: `M:LibreMetaverse.ItemReceivedEventArgs.#ctor(LibreMetaverse.InventoryItem)`. +/// C# member: `P:LibreMetaverse.ItemReceivedEventArgs.Item`. +pub use crate::inventory_manager::ItemReceivedEventArgs; /// C# type: `T:LibreMetaverse.KillObjectEventArgs`. pub struct KillObjectEventArgs; @@ -20892,30 +20736,10 @@ pub enum ReportType { } /// C# type: `T:LibreMetaverse.SaveAssetToInventoryEventArgs`. -pub struct SaveAssetToInventoryEventArgs; -impl SaveAssetToInventoryEventArgs { - /// C# member: `M:LibreMetaverse.SaveAssetToInventoryEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn new( - item_id: libremetaverse_types::UUID, - new_asset_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SaveAssetToInventoryEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.SaveAssetToInventoryEventArgs.ItemID`. - pub fn item_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.SaveAssetToInventoryEventArgs.ItemID" - ) - } - /// C# member: `P:LibreMetaverse.SaveAssetToInventoryEventArgs.NewAssetID`. - pub fn new_asset_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.SaveAssetToInventoryEventArgs.NewAssetID" - ) - } -} +/// C# member: `M:LibreMetaverse.SaveAssetToInventoryEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.SaveAssetToInventoryEventArgs.ItemID`. +/// C# member: `P:LibreMetaverse.SaveAssetToInventoryEventArgs.NewAssetID`. +pub use crate::inventory_manager::SaveAssetToInventoryEventArgs; /// C# type: `T:LibreMetaverse.ScriptControlChange`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -21118,44 +20942,12 @@ impl ScriptQuestionEventArgs { } /// C# type: `T:LibreMetaverse.ScriptRunningReplyEventArgs`. -pub struct ScriptRunningReplyEventArgs; -impl ScriptRunningReplyEventArgs { - /// C# member: `M:LibreMetaverse.ScriptRunningReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean)`. - pub fn new( - object_id: libremetaverse_types::UUID, - sctript_id: libremetaverse_types::UUID, - is_mono: bool, - is_running: bool, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ScriptRunningReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean)", - ) - } - /// C# member: `P:LibreMetaverse.ScriptRunningReplyEventArgs.IsMono`. - pub fn is_mono(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ScriptRunningReplyEventArgs.IsMono" - ) - } - /// C# member: `P:LibreMetaverse.ScriptRunningReplyEventArgs.IsRunning`. - pub fn is_running(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ScriptRunningReplyEventArgs.IsRunning" - ) - } - /// C# member: `P:LibreMetaverse.ScriptRunningReplyEventArgs.ObjectID`. - pub fn object_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ScriptRunningReplyEventArgs.ObjectID" - ) - } - /// C# member: `P:LibreMetaverse.ScriptRunningReplyEventArgs.ScriptID`. - pub fn script_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ScriptRunningReplyEventArgs.ScriptID" - ) - } -} +/// C# member: `M:LibreMetaverse.ScriptRunningReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean)`. +/// C# member: `P:LibreMetaverse.ScriptRunningReplyEventArgs.IsMono`. +/// C# member: `P:LibreMetaverse.ScriptRunningReplyEventArgs.IsRunning`. +/// C# member: `P:LibreMetaverse.ScriptRunningReplyEventArgs.ObjectID`. +/// C# member: `P:LibreMetaverse.ScriptRunningReplyEventArgs.ScriptID`. +pub use crate::inventory_manager::ScriptRunningReplyEventArgs; /// C# type: `T:LibreMetaverse.ScriptSensorReplyEventArgs`. pub struct ScriptSensorReplyEventArgs; @@ -22461,82 +22253,20 @@ pub enum TargetType { } /// C# type: `T:LibreMetaverse.TaskInventoryReplyEventArgs`. -pub struct TaskInventoryReplyEventArgs; -impl TaskInventoryReplyEventArgs { - /// C# member: `M:LibreMetaverse.TaskInventoryReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Int16,System.String)`. - pub fn new( - item_id: libremetaverse_types::UUID, - serial: i16, - asset_filename: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.TaskInventoryReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Int16,System.String)", - ) - } - /// C# member: `P:LibreMetaverse.TaskInventoryReplyEventArgs.AssetFilename`. - pub fn asset_filename(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TaskInventoryReplyEventArgs.AssetFilename" - ) - } - /// C# member: `P:LibreMetaverse.TaskInventoryReplyEventArgs.ItemID`. - pub fn item_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TaskInventoryReplyEventArgs.ItemID" - ) - } - /// C# member: `P:LibreMetaverse.TaskInventoryReplyEventArgs.Serial`. - pub fn serial(&self) -> i16 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TaskInventoryReplyEventArgs.Serial" - ) - } -} +/// C# member: `M:LibreMetaverse.TaskInventoryReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Int16,System.String)`. +/// C# member: `P:LibreMetaverse.TaskInventoryReplyEventArgs.AssetFilename`. +/// C# member: `P:LibreMetaverse.TaskInventoryReplyEventArgs.ItemID`. +/// C# member: `P:LibreMetaverse.TaskInventoryReplyEventArgs.Serial`. +pub use crate::inventory_manager::TaskInventoryReplyEventArgs; /// C# type: `T:LibreMetaverse.TaskItemReceivedEventArgs`. -pub struct TaskItemReceivedEventArgs; -impl TaskItemReceivedEventArgs { - /// C# member: `M:LibreMetaverse.TaskItemReceivedEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.InventoryType)`. - pub fn new( - item_id: libremetaverse_types::UUID, - folder_id: libremetaverse_types::UUID, - creator_id: libremetaverse_types::UUID, - asset_id: libremetaverse_types::UUID, - type_: libremetaverse_types::InventoryType, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.TaskItemReceivedEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.InventoryType)", - ) - } - /// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.AssetID`. - pub fn asset_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TaskItemReceivedEventArgs.AssetID" - ) - } - /// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.CreatorID`. - pub fn creator_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TaskItemReceivedEventArgs.CreatorID" - ) - } - /// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.FolderID`. - pub fn folder_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TaskItemReceivedEventArgs.FolderID" - ) - } - /// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.ItemID`. - pub fn item_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.TaskItemReceivedEventArgs.ItemID" - ) - } - /// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.Type`. - pub fn type_(&self) -> libremetaverse_types::InventoryType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.TaskItemReceivedEventArgs.Type") - } -} +/// C# member: `M:LibreMetaverse.TaskItemReceivedEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.InventoryType)`. +/// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.AssetID`. +/// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.CreatorID`. +/// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.FolderID`. +/// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.ItemID`. +/// C# member: `P:LibreMetaverse.TaskItemReceivedEventArgs.Type`. +pub use crate::inventory_manager::TaskItemReceivedEventArgs; /// C# type: `T:LibreMetaverse.TeleportEventArgs`. /// C# member: `M:LibreMetaverse.TeleportEventArgs.#ctor(System.String,LibreMetaverse.TeleportStatus,LibreMetaverse.TeleportFlags)`. diff --git a/crates/libremetaverse/src/inventory.rs b/crates/libremetaverse/src/inventory.rs index 42632cc..c912baa 100644 --- a/crates/libremetaverse/src/inventory.rs +++ b/crates/libremetaverse/src/inventory.rs @@ -1462,6 +1462,16 @@ impl Inventory { .and_then(|value| value.clone_as::())) } + /// Returns the shared item portion regardless of the concrete inventory + /// subclass stored for the UUID. + pub(crate) fn native_item_value(&self, uuid: UUID) -> Option { + read(&self.inner.state) + .items + .get(&uuid) + .and_then(InventoryNode::value) + .and_then(|value| value.item().cloned()) + } + pub fn try_get_value_with_uuid_inventory_base( &self, uuid: UUID, diff --git a/crates/libremetaverse/src/inventory_manager.rs b/crates/libremetaverse/src/inventory_manager.rs new file mode 100644 index 0000000..499048a --- /dev/null +++ b/crates/libremetaverse/src/inventory_manager.rs @@ -0,0 +1,4779 @@ +//! Native inventory protocol manager. +//! +//! This module owns request correlation and wire behavior. The hierarchy in +//! [`crate::inventory`] remains a transport-free store, which lets packet and +//! capability replies be validated before they become observable local state. +//! Store snapshots and subscriber lists are cloned before asynchronous work or +//! event delivery, so callbacks are not invoked while an inventory lock is held. + +#![allow(clippy::missing_errors_doc)] // Public signatures mirror the C# API. +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] +#![allow(clippy::too_many_lines)] // Protocol parsers intentionally keep each wire state machine together. +#![allow(clippy::unnecessary_wraps)] // Mapped C# methods require Result-shaped Rust APIs. + +use crate::agent_manager::{EventRegistry, InstantMessage, InstantMessageEventArgs}; +use crate::client_core::ClientWeakHandle; +use crate::inventory::{ + Inventory, InventoryAnimation, InventoryAttachment, InventoryBase, InventoryCallingCard, + InventoryCategory, InventoryFolder, InventoryGesture, InventoryItem, InventoryLSL, + InventoryLandmark, InventoryMaterial, InventoryNotecard, InventoryObject, InventorySettings, + InventorySnapshot, InventorySound, InventoryTexture, InventoryWearable, Permissions, +}; +use crate::messages::linden::{CopyInventoryFromNotecardMessage, ScriptRunningReplyMessage}; +use crate::network_manager::{CapsEventQueueCallback, RawPacketReceivedEventArgs, Simulator}; +use crate::packet_catalog::GeneratedPacket; +use crate::packets::{ + AbortXferPacket, BulkUpdateInventoryPacket, ConfirmXferPacketPacket, + CopyInventoryFromNotecardPacket, CopyInventoryFromNotecardPacketInventoryDataBlock, + CopyInventoryItemPacket, CopyInventoryItemPacketInventoryDataBlock, + CreateInventoryFolderPacket, CreateInventoryItemPacket, DeRezObjectPacket, + DeRezObjectPacketObjectDataBlock, FetchInventoryPacket, FetchInventoryPacketInventoryDataBlock, + FetchInventoryReplyPacket, GetScriptRunningPacket, InventoryDescendentsPacket, + LinkInventoryItemPacket, MoveInventoryFolderPacket, + MoveInventoryFolderPacketInventoryDataBlock, MoveInventoryItemPacket, + MoveInventoryItemPacketInventoryDataBlock, MoveTaskInventoryPacket, PacketType, + PurgeInventoryDescendentsPacket, RemoveInventoryObjectsPacket, + RemoveInventoryObjectsPacketFolderDataBlock, RemoveInventoryObjectsPacketItemDataBlock, + RemoveTaskInventoryPacket, ReplyTaskInventoryPacket, RequestTaskInventoryPacket, + RequestXferPacket, RezObjectPacket, RezRestoreToWorldPacket, RezScriptPacket, + SaveAssetIntoInventoryPacket, SendXferPacketPacket, SetScriptRunningPacket, + UpdateCreateInventoryItemPacket, UpdateInventoryFolderPacket, + UpdateInventoryFolderPacketFolderDataBlock, UpdateInventoryItemPacket, + UpdateInventoryItemPacketInventoryDataBlock, UpdateTaskInventoryPacket, +}; +use crate::{ + DeRezDestination, GridClient, InstantMessageDialog, InstantMessageOnline, InventoryItemClass, + InventoryObjectClass, InventorySortOrder, PermissionMask, +}; +use crate::{LoginResponseData, NetworkManagerLoginResponseCallback}; +use futures_channel::oneshot; +use futures_util::future::{Either, select}; +use libremetaverse_structured_data::{OSD, OSDParser}; +use libremetaverse_types::compat::{ + CancellationToken, EventHandler, ExternalError, Subscription, Task, Uri, +}; +use libremetaverse_types::{ + AssetType, EnumValueMetadata, Error, FolderType, InventoryType, Quaternion, SaleType, UUID, + Utils, Vector3, Vector3d, WearableType, +}; +use std::collections::{HashMap, HashSet}; +use std::fmt; +use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; +use std::sync::{Arc, Condvar, Mutex, RwLock}; +use std::thread::JoinHandle; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +const MAGIC_ID_TEXT: &str = "3c115e51-04f4-523c-9fa6-98aff1034730"; +const MAX_TASK_INVENTORY_BYTES: usize = 16 * 1024 * 1024; +const MAX_TASK_INVENTORY_ENTRIES: usize = 100_000; +const MAX_PENDING_REQUESTS: usize = 4_096; +const CALLBACK_TIMEOUT: Duration = Duration::from_mins(1); +const CALLBACK_SWEEP_INTERVAL: Duration = Duration::from_millis(100); +const MAX_INVENTORY_RECURSION_DEPTH: usize = 512; +const MAX_INVENTORY_TRAVERSAL: usize = 100_000; + +macro_rules! fill_inventory_wire_block { + ($block:expr, $item:expr, $transaction_id:expr, $type_value:expr) => {{ + let item = $item; + let permissions = item.permissions(); + $block.item_id = item.base.uuid(); + $block.folder_id = item.base.parent_uuid(); + $block.creator_id = item.creator_id(); + $block.owner_id = item.base.owner_id(); + $block.group_id = item.group_id(); + $block.base_mask = permissions.base_mask.0; + $block.owner_mask = permissions.owner_mask.0; + $block.group_mask = permissions.group_mask.0; + $block.everyone_mask = permissions.everyone_mask.0; + $block.next_owner_mask = permissions.next_owner_mask.0; + $block.group_owned = item.group_owned(); + $block.transaction_id = $transaction_id; + $block.type_ = $type_value; + $block.inv_type = item.inventory_type().0; + $block.flags = item.flags(); + $block.sale_type = item.sale_type() as u8; + $block.sale_price = item.sale_price(); + $block.name = Utils::string_to_bytes(item.base.name())?; + $block.description = Utils::string_to_bytes(item.description())?; + $block.creation_date = unix_seconds_i32(item.creation_date()); + }}; +} + +fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { + value + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +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 expire_waiters( + requests: &Mutex>>, + now: Instant, + deadline: impl Fn(&T) -> Instant, +) -> Vec { + let mut requests = mutex(requests); + let keys: Vec<_> = requests.keys().copied().collect(); + let mut expired = Vec::new(); + for key in keys { + if let Some(waiters) = requests.get_mut(&key) { + let mut index = 0; + while index < waiters.len() { + if deadline(&waiters[index]) <= now { + expired.push(waiters.swap_remove(index)); + } else { + index += 1; + } + } + if waiters.is_empty() { + requests.remove(&key); + } + } + } + expired +} + +fn wire_string(mut value: Vec) -> String { + while value.last() == Some(&0) { + value.pop(); + } + String::from_utf8_lossy(&value).into_owned() +} + +fn decode_packet(bytes: &[u8]) -> Result { + let mut packet = T::new_generated(); + let mut position = 0_i32; + let mut packet_end = i32::try_from(bytes.len()).map_err(|_| Error::Argument)? - 1; + let mut zero_buffer = + vec![0_u8; crate::udp_transport::UdpTransportConfig::default().max_decoded_packet_size]; + packet.decode_from_bytes( + bytes, + &mut position, + &mut packet_end, + Some(zero_buffer.as_mut_slice()), + )?; + Ok(packet) +} + +fn unix_seconds_i64(value: SystemTime) -> i64 { + match value.duration_since(UNIX_EPOCH) { + Ok(duration) => i64::try_from(duration.as_secs()).unwrap_or(i64::MAX), + Err(error) => -i64::try_from(error.duration().as_secs()).unwrap_or(i64::MAX), + } +} + +fn unix_seconds_u32_wrapping(value: SystemTime) -> u32 { + let bytes = unix_seconds_i64(value).to_le_bytes(); + u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) +} + +fn uuid_crc(value: UUID) -> u32 { + let mut value = value; + value.crc().unwrap_or_default() +} + +fn system_time_from_unix_i32(seconds: i32) -> SystemTime { + if seconds >= 0 { + UNIX_EPOCH + Duration::from_secs(seconds.unsigned_abs().into()) + } else { + UNIX_EPOCH - Duration::from_secs(seconds.unsigned_abs().into()) + } +} + +fn unix_seconds_i32(value: SystemTime) -> i32 { + i32::try_from(unix_seconds_i64(value)).unwrap_or_else(|_| { + if value >= UNIX_EPOCH { + i32::MAX + } else { + i32::MIN + } + }) +} + +fn asset_type_from_i8(value: i8) -> AssetType { + match value { + -1 => AssetType::Unknown, + 0 => AssetType::Texture, + 1 => AssetType::Sound, + 2 => AssetType::CallingCard, + 3 => AssetType::Landmark, + 4 => AssetType::Script, + 5 => AssetType::Clothing, + 6 => AssetType::Object, + 7 => AssetType::Notecard, + 8 => AssetType::Folder, + 10 => AssetType::LSLText, + 11 => AssetType::LSLBytecode, + 12 => AssetType::TextureTGA, + 13 => AssetType::Bodypart, + 17 => AssetType::SoundWAV, + 18 => AssetType::ImageTGA, + 19 => AssetType::ImageJPEG, + 20 => AssetType::Animation, + 21 => AssetType::Gesture, + 22 => AssetType::Simstate, + 24 => AssetType::Link, + 25 => AssetType::LinkFolder, + 40 => AssetType::Widget, + 45 => AssetType::Person, + 49 => AssetType::Mesh, + 56 => AssetType::Settings, + 57 => AssetType::Material, + _ => AssetType::Unknown, + } +} + +fn folder_type_from_i8(value: i8) -> FolderType { + folder_type_from_i8_checked(value).unwrap_or(FolderType::None) +} + +fn folder_type_from_i8_checked(value: i8) -> Option { + match value { + -1 => Some(FolderType::None), + 0 => Some(FolderType::Texture), + 1 => Some(FolderType::Sound), + 2 => Some(FolderType::CallingCard), + 3 => Some(FolderType::Landmark), + 5 => Some(FolderType::Clothing), + 6 => Some(FolderType::Object), + 7 => Some(FolderType::Notecard), + 8 => Some(FolderType::Root), + 9 => Some(FolderType::OldRoot), + 10 => Some(FolderType::LSLText), + 13 => Some(FolderType::BodyPart), + 14 => Some(FolderType::Trash), + 15 => Some(FolderType::Snapshot), + 16 => Some(FolderType::LostAndFound), + 20 => Some(FolderType::Animation), + 21 => Some(FolderType::Gesture), + 23 => Some(FolderType::Favorites), + 26 => Some(FolderType::EnsembleStart), + 45 => Some(FolderType::EnsembleEnd), + 46 => Some(FolderType::CurrentOutfit), + 47 => Some(FolderType::Outfit), + 48 => Some(FolderType::MyOutfits), + 49 => Some(FolderType::Mesh), + 50 => Some(FolderType::Inbox), + 51 => Some(FolderType::Outbox), + 52 => Some(FolderType::BasicRoot), + 53 => Some(FolderType::MarketplaceListings), + 54 => Some(FolderType::MarketplaceStock), + 55 => Some(FolderType::MarketplaceVersion), + 56 => Some(FolderType::Settings), + 57 => Some(FolderType::Material), + 100 => Some(FolderType::Suitcase), + _ => None, + } +} + +fn sale_type_from_u8(value: u8) -> SaleType { + match value { + 1 => SaleType::Original, + 2 => SaleType::Copy, + 3 => SaleType::Contents, + _ => SaleType::Not, + } +} + +#[derive(Clone)] +struct InventoryWireData { + asset_id: UUID, + base_mask: u32, + creation_date: i32, + creator_id: UUID, + description: Vec, + everyone_mask: u32, + flags: u32, + folder_id: UUID, + group_id: UUID, + group_mask: u32, + group_owned: bool, + inv_type: i8, + item_id: UUID, + name: Vec, + next_owner_mask: u32, + owner_id: UUID, + owner_mask: u32, + sale_price: i32, + sale_type: u8, + type_: i8, +} + +fn inventory_item_from_wire(data: InventoryWireData) -> Result { + let mut inventory_type = InventoryType(data.inv_type); + let asset_type = asset_type_from_i8(data.type_); + if inventory_type == InventoryType::TEXTURE + && matches!(asset_type, AssetType::Object | AssetType::Mesh) + { + inventory_type = InventoryType::ATTACHMENT; + } + let mut item = InventoryItem::new_with_inventory_type_uuid(inventory_type, data.item_id)?; + item.set_asset_uuid(data.asset_id); + item.set_asset_type(asset_type); + item.set_creation_date(system_time_from_unix_i32(data.creation_date)); + item.set_creator_id(data.creator_id); + item.set_description(wire_string(data.description)); + item.set_flags(data.flags); + item.set_group_id(data.group_id); + item.set_group_owned(data.group_owned); + item.set_inventory_type(inventory_type); + item.base.set_name(wire_string(data.name)); + item.base.set_owner_id(data.owner_id); + item.base.set_parent_uuid(data.folder_id); + item.set_permissions(Permissions::new( + data.base_mask, + data.everyone_mask, + data.group_mask, + data.next_owner_mask, + data.owner_mask, + )?); + item.set_sale_price(data.sale_price); + item.set_sale_type(sale_type_from_u8(data.sale_type)); + Ok(item) +} + +fn typed_inventory_item(item: InventoryItem) -> Box { + macro_rules! boxed { + ($kind:ty) => {{ + let mut value = <$kind>::new(item.base.uuid()).expect("known inventory constructor"); + value.base = item; + Box::new(value) as Box + }}; + } + match item.inventory_type() { + InventoryType::TEXTURE => boxed!(InventoryTexture), + InventoryType::SOUND => boxed!(InventorySound), + InventoryType::CALLING_CARD => boxed!(InventoryCallingCard), + InventoryType::LANDMARK => boxed!(InventoryLandmark), + InventoryType::OBJECT => boxed!(InventoryObject), + InventoryType::NOTECARD => boxed!(InventoryNotecard), + InventoryType::CATEGORY => boxed!(InventoryCategory), + InventoryType::LSL => boxed!(InventoryLSL), + InventoryType::SNAPSHOT => boxed!(InventorySnapshot), + InventoryType::ATTACHMENT => boxed!(InventoryAttachment), + InventoryType::WEARABLE => boxed!(InventoryWearable), + InventoryType::ANIMATION => boxed!(InventoryAnimation), + InventoryType::GESTURE => boxed!(InventoryGesture), + InventoryType::SETTINGS => boxed!(InventorySettings), + InventoryType::MATERIAL => boxed!(InventoryMaterial), + _ => Box::new(item), + } +} + +/// C# `FindObjectByPathReplyEventArgs`. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FindObjectByPathReplyEventArgs { + path: String, + inventory_object_id: UUID, +} + +impl FindObjectByPathReplyEventArgs { + pub fn new(path: String, inventory_object_id: UUID) -> Result { + Ok(Self { + path, + inventory_object_id, + }) + } + pub fn inventory_object_id(&self) -> UUID { + self.inventory_object_id + } + pub fn path(&self) -> String { + self.path.clone() + } +} + +/// C# `FolderUpdatedEventArgs`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct FolderUpdatedEventArgs { + folder_id: UUID, + success: bool, +} + +impl FolderUpdatedEventArgs { + pub fn new(folder_id: UUID, success: bool) -> Result { + Ok(Self { folder_id, success }) + } + pub fn folder_id(&self) -> UUID { + self.folder_id + } + pub fn success(&self) -> bool { + self.success + } +} + +/// C# `ItemReceivedEventArgs`. +#[derive(Clone, Debug)] +pub struct ItemReceivedEventArgs { + item: InventoryItem, +} + +impl ItemReceivedEventArgs { + pub fn new(item: InventoryItem) -> Result { + Ok(Self { item }) + } + pub fn item(&self) -> InventoryItem { + self.item.clone() + } +} + +/// C# `SaveAssetToInventoryEventArgs`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SaveAssetToInventoryEventArgs { + item_id: UUID, + new_asset_id: UUID, +} + +impl SaveAssetToInventoryEventArgs { + pub fn new(item_id: UUID, new_asset_id: UUID) -> Result { + Ok(Self { + item_id, + new_asset_id, + }) + } + pub fn item_id(&self) -> UUID { + self.item_id + } + pub fn new_asset_id(&self) -> UUID { + self.new_asset_id + } +} + +/// C# `ScriptRunningReplyEventArgs`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ScriptRunningReplyEventArgs { + object_id: UUID, + script_id: UUID, + is_mono: bool, + is_running: bool, +} + +impl ScriptRunningReplyEventArgs { + pub fn new( + object_id: UUID, + sctript_id: UUID, + is_mono: bool, + is_running: bool, + ) -> Result { + Ok(Self { + object_id, + script_id: sctript_id, + is_mono, + is_running, + }) + } + pub fn is_mono(&self) -> bool { + self.is_mono + } + pub fn is_running(&self) -> bool { + self.is_running + } + pub fn object_id(&self) -> UUID { + self.object_id + } + pub fn script_id(&self) -> UUID { + self.script_id + } +} + +/// C# `TaskInventoryReplyEventArgs`. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TaskInventoryReplyEventArgs { + item_id: UUID, + serial: i16, + asset_filename: String, +} + +impl TaskInventoryReplyEventArgs { + pub fn new(item_id: UUID, serial: i16, asset_filename: String) -> Result { + Ok(Self { + item_id, + serial, + asset_filename, + }) + } + pub fn asset_filename(&self) -> String { + self.asset_filename.clone() + } + pub fn item_id(&self) -> UUID { + self.item_id + } + pub fn serial(&self) -> i16 { + self.serial + } +} + +/// C# `TaskItemReceivedEventArgs`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct TaskItemReceivedEventArgs { + item_id: UUID, + folder_id: UUID, + creator_id: UUID, + asset_id: UUID, + type_: InventoryType, +} + +impl TaskItemReceivedEventArgs { + pub fn new( + item_id: UUID, + folder_id: UUID, + creator_id: UUID, + asset_id: UUID, + type_: InventoryType, + ) -> Result { + Ok(Self { + item_id, + folder_id, + creator_id, + asset_id, + type_, + }) + } + pub fn asset_id(&self) -> UUID { + self.asset_id + } + pub fn creator_id(&self) -> UUID { + self.creator_id + } + pub fn folder_id(&self) -> UUID { + self.folder_id + } + pub fn item_id(&self) -> UUID { + self.item_id + } + pub fn type_(&self) -> InventoryType { + self.type_ + } +} + +/// C# `InventoryObjectOfferedEventArgs`. +#[derive(Clone, Debug)] +pub struct InventoryObjectOfferedEventArgs { + offer: InstantMessage, + asset_type: AssetType, + object_id: UUID, + from_task: bool, + decision: Arc>, +} + +#[derive(Clone, Copy, Debug)] +struct InventoryOfferDecision { + folder_id: UUID, + accept: bool, +} + +impl InventoryObjectOfferedEventArgs { + pub fn new( + offer_details: InstantMessage, + type_: AssetType, + object_id: UUID, + from_task: bool, + folder_id: UUID, + ) -> Result { + Ok(Self { + offer: offer_details, + asset_type: type_, + object_id, + from_task, + decision: Arc::new(RwLock::new(InventoryOfferDecision { + folder_id, + accept: false, + })), + }) + } + pub fn accept(&self) -> bool { + read(&self.decision).accept + } + pub fn set_accept(&mut self, value: bool) { + write(&self.decision).accept = value; + } + pub fn asset_type(&self) -> AssetType { + self.asset_type + } + pub fn folder_id(&self) -> UUID { + read(&self.decision).folder_id + } + pub fn set_folder_id(&mut self, value: UUID) { + write(&self.decision).folder_id = value; + } + pub fn from_task(&self) -> bool { + self.from_task + } + pub fn object_id(&self) -> UUID { + self.object_id + } + pub fn offer(&self) -> InstantMessage { + self.offer.clone() + } +} + +/// Result returned by the C#-compatible multi-item copy APIs. +#[derive(Clone, Debug, Default)] +pub struct InventoryManagerCopyItemsResult { + copied_items: Option>, + error: Option, + success: bool, +} + +impl InventoryManagerCopyItemsResult { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn copied_items(&self) -> Option> { + self.copied_items.clone() + } + pub fn set_copied_items(&mut self, value: Option>) { + self.copied_items = value; + } + pub fn error(&self) -> Option { + self.error.clone() + } + pub fn set_error(&mut self, value: Option) { + self.error = value; + } + pub fn success(&self) -> bool { + self.success + } + pub fn set_success(&mut self, value: bool) { + self.success = value; + } +} + +/// Result returned by the C#-compatible folder refresh API. +#[derive(Clone, Debug, Default)] +pub struct InventoryManagerFolderUpdateResult { + contents: Option>, + folder_id: UUID, + success: bool, +} + +impl InventoryManagerFolderUpdateResult { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn contents(&self) -> Option> { + self.contents.clone() + } + pub fn set_contents(&mut self, value: Option>) { + self.contents = value; + } + pub fn folder_id(&self) -> UUID { + self.folder_id + } + pub fn set_folder_id(&mut self, value: UUID) { + self.folder_id = value; + } + pub fn success(&self) -> bool { + self.success + } + pub fn set_success(&mut self, value: bool) { + self.success = value; + } +} + +struct PendingCreated { + deadline: Instant, + sender: oneshot::Sender>, +} + +struct PendingCopy { + deadline: Instant, + expected: usize, + copied: Vec, + sender: oneshot::Sender, +} + +struct PendingFetch { + request_id: u64, + deadline: Instant, + sender: oneshot::Sender>, +} + +struct PendingTaskReply { + request_id: u64, + deadline: Instant, + sender: oneshot::Sender, +} + +struct PendingOffer { + request_id: u64, + deadline: Instant, + sender: oneshot::Sender, +} + +struct TaskXfer { + deadline: Instant, + next_packet: u32, + announced_size: Option, + bytes: Vec, + sender: oneshot::Sender>>, +} + +struct CleanupWorker { + stop: Arc<(Mutex, Condvar)>, + handle: JoinHandle<()>, +} + +#[derive(Default)] +struct InventoryManagerEvents { + find_object_by_path_reply: EventRegistry, + folder_updated: EventRegistry, + inventory_object_offered: EventRegistry, + item_received: EventRegistry, + save_asset_to_inventory: EventRegistry, + script_running_reply: EventRegistry, + task_inventory_reply: EventRegistry, + task_item_received: EventRegistry, +} + +/// Shared state for a client-owned inventory manager. +pub(crate) struct InventoryManagerInner { + client: ClientWeakHandle, + store: RwLock>, + disposed: AtomicBool, + callback_position: AtomicU32, + request_position: AtomicU64, + pending_created: Mutex>, + pending_copied: Mutex>, + pending_fetches: Mutex>>, + pending_task_replies: Mutex>>, + pending_offers: Mutex>, + task_xfers: Mutex>, + cleanup_worker: Mutex>, + raw_packet_subscription: Mutex>, + caps_subscription: Mutex>, + im_subscription: Mutex>, + login_callback: Mutex>, + events: InventoryManagerEvents, +} + +/// Stateful native replacement for the generated `InventoryManager` shell. +#[derive(Clone)] +pub struct InventoryManager { + inner: Arc, +} + +impl fmt::Debug for InventoryManager { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("InventoryManager") + .field("has_store", &read(&self.inner.store).is_some()) + .field("disposed", &self.inner.disposed.load(Ordering::Acquire)) + .finish() + } +} + +impl InventoryManager { + pub(crate) fn native_from_inner(inner: Arc) -> Self { + Self { inner } + } + + pub(crate) fn native_inner(&self) -> Arc { + Arc::clone(&self.inner) + } + + pub(crate) fn native_new(client: Option>) -> Result { + let client = client.ok_or(Error::ArgumentNull)?; + let inner = Arc::new(InventoryManagerInner { + client: client.native_weak_handle(), + store: RwLock::new(None), + disposed: AtomicBool::new(false), + callback_position: AtomicU32::new(0), + request_position: AtomicU64::new(0), + pending_created: Mutex::new(HashMap::new()), + pending_copied: Mutex::new(HashMap::new()), + pending_fetches: Mutex::new(HashMap::new()), + pending_task_replies: Mutex::new(HashMap::new()), + pending_offers: Mutex::new(Vec::new()), + task_xfers: Mutex::new(HashMap::new()), + cleanup_worker: Mutex::new(None), + raw_packet_subscription: Mutex::new(None), + caps_subscription: Mutex::new(None), + im_subscription: Mutex::new(None), + login_callback: Mutex::new(None), + events: InventoryManagerEvents::default(), + }); + let manager = Self { inner }; + manager.install_packet_handler()?; + manager.install_message_handlers()?; + manager.install_login_handler()?; + manager.install_cleanup_worker()?; + Ok(manager) + } + + fn install_cleanup_worker(&self) -> Result<(), Error> { + let stop = Arc::new((Mutex::new(false), Condvar::new())); + let worker_stop = Arc::clone(&stop); + let weak = Arc::downgrade(&self.inner); + let handle = std::thread::Builder::new() + .name("inventory-callback-cleanup".to_owned()) + .spawn(move || { + loop { + let (lock, wake) = &*worker_stop; + let stopped = mutex(lock); + let (stopped, _) = wake + .wait_timeout_while(stopped, CALLBACK_SWEEP_INTERVAL, |value| !*value) + .unwrap_or_else(std::sync::PoisonError::into_inner); + if *stopped { + break; + } + drop(stopped); + let Some(inner) = weak.upgrade() else { + break; + }; + InventoryManager::native_from_inner(inner).expire_pending(Instant::now()); + } + }) + .map_err(|_| Error::InvalidOperation)?; + *mutex(&self.inner.cleanup_worker) = Some(CleanupWorker { stop, handle }); + Ok(()) + } + + fn expire_pending(&self, now: Instant) { + let created = { + let mut pending = mutex(&self.inner.pending_created); + let expired: Vec<_> = pending + .iter() + .filter_map(|(id, request)| (request.deadline <= now).then_some(*id)) + .collect(); + expired + .into_iter() + .filter_map(|id| pending.remove(&id)) + .collect::>() + }; + for request in created { + let _ = request.sender.send(None); + } + let copied = { + let mut pending = mutex(&self.inner.pending_copied); + let expired: Vec<_> = pending + .iter() + .filter_map(|(id, request)| (request.deadline <= now).then_some(*id)) + .collect(); + expired + .into_iter() + .filter_map(|id| pending.remove(&id)) + .collect::>() + }; + for request in copied { + let success = !request.copied.is_empty(); + let _ = request.sender.send(InventoryManagerCopyItemsResult { + copied_items: success.then_some(request.copied), + error: None, + success, + }); + } + let fetches = expire_waiters(&self.inner.pending_fetches, now, |request| request.deadline); + for request in fetches { + let _ = request.sender.send(None); + } + let task_replies = expire_waiters(&self.inner.pending_task_replies, now, |request| { + request.deadline + }); + for request in task_replies { + let _ = request.sender.send(String::new()); + } + mutex(&self.inner.pending_offers).retain(|request| request.deadline > now); + let xfers = { + let mut pending = mutex(&self.inner.task_xfers); + let expired: Vec<_> = pending + .iter() + .filter_map(|(id, request)| (request.deadline <= now).then_some(*id)) + .collect(); + expired + .into_iter() + .filter_map(|id| pending.remove(&id)) + .collect::>() + }; + for request in xfers { + let _ = request.sender.send(None); + } + } + + fn client(&self) -> Result { + if self.inner.disposed.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + self.inner.client.upgrade().ok_or(Error::InvalidOperation) + } + + fn install_packet_handler(&self) -> Result<(), Error> { + let network = self.client()?.native_network()?; + let weak = Arc::downgrade(&self.inner); + let subscription = network.subscribe_raw_packet(Arc::new(move |event| { + if let Some(inner) = weak.upgrade() { + let _ = InventoryManager::native_from_inner(inner).handle_raw_packet(event); + } + })); + *mutex(&self.inner.raw_packet_subscription) = Some(subscription); + Ok(()) + } + + fn install_message_handlers(&self) -> Result<(), Error> { + let mut client = self.client()?; + let weak = Arc::downgrade(&self.inner); + let im_subscription = client.native_self().native_subscribe_im(Arc::new( + move |event: InstantMessageEventArgs| { + if let Some(inner) = weak.upgrade() { + let _ = + InventoryManager::native_from_inner(inner).handle_inventory_offer(event); + } + }, + )); + *mutex(&self.inner.im_subscription) = Some(im_subscription); + + let weak = Arc::downgrade(&self.inner); + let caps_subscription = client.native_network()?.subscribe_caps( + "ScriptRunningReply".to_owned(), + CapsEventQueueCallback::from_handler(move |_name, message, _simulator| { + let Some(inner) = weak.upgrade() else { + return; + }; + let Ok(mut reply) = ScriptRunningReplyMessage::new() else { + return; + }; + if message + .serialize() + .and_then(|map| reply.deserialize(map)) + .is_ok() + { + inner + .events + .script_running_reply + .emit(ScriptRunningReplyEventArgs { + object_id: reply.object_id, + script_id: reply.item_id, + is_mono: reply.mono, + is_running: reply.running, + }); + } + }), + ); + *mutex(&self.inner.caps_subscription) = Some(caps_subscription); + Ok(()) + } + + fn install_login_handler(&self) -> Result<(), Error> { + let weak = Arc::downgrade(&self.inner); + let callback = NetworkManagerLoginResponseCallback::from_handler( + move |success, redirect, _message, _reason, response| { + if success + && !redirect + && let (Some(inner), Some(response)) = (weak.upgrade(), response) + { + let _ = InventoryManager::native_from_inner(inner) + .install_login_inventory(response); + } + }, + ); + self.client()? + .native_network()? + .native_register_login_response_callback( + callback.clone(), + Some(vec![ + "inventory-root".to_owned(), + "inventory-skeleton".to_owned(), + "inventory-lib-root".to_owned(), + "inventory-lib-owner".to_owned(), + "inventory-skel-lib".to_owned(), + ]), + )?; + *mutex(&self.inner.login_callback) = Some(callback); + Ok(()) + } + + fn install_login_inventory(&self, response: LoginResponseData) -> Result<(), Error> { + let client = Arc::new(self.client()?); + let mut store = Inventory::new_with_grid_client_uuid(client, response.agent_id())?; + let mut root = InventoryFolder::new(response.inventory_root())?; + root.base.set_parent_uuid(UUID::zero()); + store.set_root_folder(Some(root)); + for folder in response.inventory_skeleton() { + store.update_node_for(&folder)?; + } + let mut library = InventoryFolder::new(response.library_root())?; + library.base.set_parent_uuid(UUID::zero()); + store.set_library_folder(Some(library)); + for folder in response.library_skeleton() { + store.update_node_for(&folder)?; + } + *write(&self.inner.store) = Some(store); + Ok(()) + } + + fn handle_raw_packet(&self, event: RawPacketReceivedEventArgs) -> Result<(), Error> { + match event.packet_type { + PacketType::UpdateCreateInventoryItem => { + self.handle_update_create(decode_packet::( + &event.data, + )?)?; + } + PacketType::BulkUpdateInventory => { + self.handle_bulk_update(decode_packet::(&event.data)?)?; + } + PacketType::FetchInventoryReply => { + self.handle_fetch_reply(decode_packet::(&event.data)?)?; + } + PacketType::InventoryDescendents => { + self.handle_descendents(decode_packet::(&event.data)?)?; + } + PacketType::MoveInventoryItem => { + self.handle_move_item(decode_packet::(&event.data)?)?; + } + PacketType::SaveAssetIntoInventory => { + let packet = decode_packet::(&event.data)?; + self.inner + .events + .save_asset_to_inventory + .emit(SaveAssetToInventoryEventArgs { + item_id: packet.inventory_data.item_id, + new_asset_id: packet.inventory_data.new_asset_id, + }); + } + PacketType::ReplyTaskInventory => { + let packet = decode_packet::(&event.data)?; + let filename = wire_string(packet.inventory_data.filename); + let waiters = mutex(&self.inner.pending_task_replies) + .remove(&packet.inventory_data.task_id) + .unwrap_or_default(); + for waiter in waiters { + let _ = waiter.sender.send(filename.clone()); + } + self.inner + .events + .task_inventory_reply + .emit(TaskInventoryReplyEventArgs { + item_id: packet.inventory_data.task_id, + serial: packet.inventory_data.serial, + asset_filename: filename, + }); + } + PacketType::SendXferPacket => self.handle_task_xfer_packet( + decode_packet::(&event.data)?, + event.simulator, + )?, + _ => {} + } + Ok(()) + } + + fn handle_inventory_offer(&self, event: InstantMessageEventArgs) -> Result<(), Error> { + let offer = event.im(); + let (asset_type, object_id, from_task) = match offer.dialog { + InstantMessageDialog::InventoryOffered if offer.binary_bucket.len() == 17 => ( + asset_type_from_i8(offer.binary_bucket[0].cast_signed()), + UUID::from_bytes(offer.binary_bucket.clone(), 1)?, + false, + ), + InstantMessageDialog::TaskInventoryOffered if offer.binary_bucket.len() == 1 => ( + asset_type_from_i8(offer.binary_bucket[0].cast_signed()), + UUID::zero(), + true, + ), + InstantMessageDialog::InventoryOffered | InstantMessageDialog::TaskInventoryOffered => { + return Ok(()); + } + _ => return Ok(()), + }; + let args = InventoryObjectOfferedEventArgs::new( + offer.clone(), + asset_type, + object_id, + from_task, + self.native_find_folder_for_asset_type(asset_type), + )?; + self.inner + .events + .inventory_object_offered + .emit(args.clone()); + let waiters = std::mem::take(&mut *mutex(&self.inner.pending_offers)); + for waiter in waiters { + let _ = waiter.sender.send(args.clone()); + } + + let response_dialog = match (offer.dialog, args.accept()) { + (InstantMessageDialog::InventoryOffered, true) => { + InstantMessageDialog::InventoryAccepted + } + (InstantMessageDialog::TaskInventoryOffered, true) => { + InstantMessageDialog::TaskInventoryAccepted + } + (InstantMessageDialog::InventoryOffered, false) => { + InstantMessageDialog::InventoryDeclined + } + (InstantMessageDialog::TaskInventoryOffered, false) => { + InstantMessageDialog::TaskInventoryDeclined + } + _ => return Ok(()), + }; + let bucket = if args.accept() { + args.folder_id().get_bytes()? + } else { + Vec::new() + }; + let mut client = self.client()?; + let agent = client.native_self(); + let _ = agent.native_instant_message_full( + agent.native_name(), + offer.from_agent_id, + String::new(), + offer.im_session_id, + response_dialog, + InstantMessageOnline::Online, + Vector3::zero(), + UUID::zero(), + bucket, + ); + if args.accept() && object_id != UUID::zero() { + let _ = self.native_request_fetch_inventory( + HashMap::from([(object_id, offer.to_agent_id)]), + None, + ); + } + Ok(()) + } + + fn handle_task_xfer_packet( + &self, + packet: SendXferPacketPacket, + simulator: Simulator, + ) -> Result<(), Error> { + let xfer_id = packet.xfer_id.id; + let packet_number = packet.xfer_id.packet & 0x0fff_ffff; + let is_last = packet.xfer_id.packet & 0x8000_0000 != 0; + let mut complete = None; + let confirmation; + { + let mut transfers = mutex(&self.inner.task_xfers); + let Some(transfer) = transfers.get_mut(&xfer_id) else { + return Ok(()); + }; + if packet_number == transfer.next_packet { + confirmation = packet_number; + let mut incoming = packet.data_packet.data; + if packet_number == 0 { + let size_bytes: [u8; 4] = incoming + .get(..4) + .ok_or(Error::InvalidOperation)? + .try_into() + .map_err(|_| Error::InvalidOperation)?; + let size = usize::try_from(u32::from_le_bytes(size_bytes)) + .map_err(|_| Error::Argument)?; + if size > MAX_TASK_INVENTORY_BYTES { + let transfer = transfers.remove(&xfer_id).expect("known xfer"); + drop(transfers); + let _ = transfer.sender.send(None); + return Err(Error::Argument); + } + transfer.bytes = Vec::with_capacity(size); + transfer.announced_size = Some(size); + incoming.drain(..4); + } + let size = transfer.announced_size.ok_or(Error::InvalidOperation)?; + if transfer.bytes.len().saturating_add(incoming.len()) > size { + let transfer = transfers.remove(&xfer_id).expect("known xfer"); + drop(transfers); + let _ = transfer.sender.send(None); + return Err(Error::InvalidOperation); + } + transfer.bytes.extend_from_slice(&incoming); + transfer.next_packet = transfer.next_packet.saturating_add(1); + if is_last { + let transfer = transfers.remove(&xfer_id).expect("known xfer"); + if transfer.bytes.len() == size { + complete = Some((transfer.sender, Some(transfer.bytes))); + } else { + complete = Some((transfer.sender, None)); + } + } + } else { + confirmation = transfer.next_packet.saturating_sub(1); + } + } + let mut confirm = ConfirmXferPacketPacket::new_with_constructor()?; + confirm.xfer_id.id = xfer_id; + confirm.xfer_id.packet = confirmation; + let confirmation_result = + self.send_generated(&simulator, PacketType::ConfirmXferPacket, &confirm); + if let Some((sender, data)) = complete { + let _ = sender.send(data); + } + confirmation_result + } + + fn store_clone(&self) -> Option { + read(&self.inner.store).clone() + } + + fn cache_server_item(&self, item: &InventoryItem) -> Result<(), Error> { + if item.base.uuid() == UUID::zero() || item.inventory_type() == InventoryType::FOLDER { + return Err(Error::Argument); + } + if let Some(store) = self.store_clone() { + let typed = typed_inventory_item(item.clone()); + store.update_node_for(typed.as_ref())?; + } + Ok(()) + } + + fn complete_created_item(&self, callback_id: u32, item: InventoryItem) { + let created = mutex(&self.inner.pending_created).remove(&callback_id); + if let Some(created) = created { + let _ = created.sender.send(Some(item.clone())); + } + let mut copied = mutex(&self.inner.pending_copied); + let Some(copy) = copied.get_mut(&callback_id) else { + return; + }; + copy.copied.push(item.base.clone()); + if copy.copied.len() < copy.expected { + return; + } + let copy = copied + .remove(&callback_id) + .expect("copy request still present"); + drop(copied); + let _ = copy.sender.send(InventoryManagerCopyItemsResult { + copied_items: Some(copy.copied), + error: None, + success: true, + }); + } + + fn handle_update_create(&self, packet: UpdateCreateInventoryItemPacket) -> Result<(), Error> { + for block in packet.inventory_data { + if block.item_id == UUID::zero() || block.inv_type == InventoryType::FOLDER.0 { + continue; + } + let mut item = inventory_item_from_wire(InventoryWireData { + asset_id: block.asset_id, + base_mask: block.base_mask, + creation_date: block.creation_date, + creator_id: block.creator_id, + description: block.description, + everyone_mask: block.everyone_mask, + flags: block.flags, + folder_id: block.folder_id, + group_id: block.group_id, + group_mask: block.group_mask, + group_owned: block.group_owned, + inv_type: block.inv_type, + item_id: block.item_id, + name: block.name, + next_owner_mask: block.next_owner_mask, + owner_id: block.owner_id, + owner_mask: block.owner_mask, + sale_price: block.sale_price, + sale_type: block.sale_type, + type_: block.type_, + })?; + if item.base.parent_uuid() == UUID::zero() { + item.base + .set_parent_uuid(self.native_find_folder_for_asset_type(item.asset_type())); + self.native_request_update_items(vec![item.clone()], UUID::zero())?; + } + self.cache_server_item(&item)?; + self.complete_created_item(block.callback_id, item.clone()); + self.inner + .events + .task_item_received + .emit(TaskItemReceivedEventArgs { + item_id: item.base.uuid(), + folder_id: item.base.parent_uuid(), + creator_id: item.creator_id(), + asset_id: item.asset_uuid(), + type_: item.inventory_type(), + }); + } + Ok(()) + } + + fn handle_bulk_update(&self, packet: BulkUpdateInventoryPacket) -> Result<(), Error> { + let store = self.store_clone(); + if let Some(store) = &store { + for block in packet.folder_data { + if block.folder_id == UUID::zero() { + continue; + } + let mut folder = store + .get_value_or_default_with_uuid_a7c63fbe::(block.folder_id)? + .unwrap_or(InventoryFolder::new(block.folder_id)?); + folder.base.set_name(wire_string(block.name)); + folder.base.set_owner_id(packet.agent_data.agent_id); + folder.base.set_parent_uuid(block.parent_id); + folder.set_preferred_type(folder_type_from_i8(block.type_)); + store.update_node_for(&folder)?; + } + } + for block in packet.item_data { + if block.item_id == UUID::zero() { + continue; + } + let item = inventory_item_from_wire(InventoryWireData { + asset_id: block.asset_id, + base_mask: block.base_mask, + creation_date: block.creation_date, + creator_id: block.creator_id, + description: block.description, + everyone_mask: block.everyone_mask, + flags: block.flags, + folder_id: block.folder_id, + group_id: block.group_id, + group_mask: block.group_mask, + group_owned: block.group_owned, + inv_type: block.inv_type, + item_id: block.item_id, + name: block.name, + next_owner_mask: block.next_owner_mask, + owner_id: block.owner_id, + owner_mask: block.owner_mask, + sale_price: block.sale_price, + sale_type: block.sale_type, + type_: block.type_, + })?; + self.cache_server_item(&item)?; + self.complete_created_item(block.callback_id, item); + } + Ok(()) + } + + fn handle_fetch_reply(&self, packet: FetchInventoryReplyPacket) -> Result<(), Error> { + for block in packet.inventory_data { + if block.item_id == UUID::zero() || block.inv_type == InventoryType::FOLDER.0 { + continue; + } + let item = inventory_item_from_wire(InventoryWireData { + asset_id: block.asset_id, + base_mask: block.base_mask, + creation_date: block.creation_date, + creator_id: block.creator_id, + description: block.description, + everyone_mask: block.everyone_mask, + flags: block.flags, + folder_id: block.folder_id, + group_id: block.group_id, + group_mask: block.group_mask, + group_owned: block.group_owned, + inv_type: block.inv_type, + item_id: block.item_id, + name: block.name, + next_owner_mask: block.next_owner_mask, + owner_id: block.owner_id, + owner_mask: block.owner_mask, + sale_price: block.sale_price, + sale_type: block.sale_type, + type_: block.type_, + })?; + self.cache_server_item(&item)?; + let waiters = mutex(&self.inner.pending_fetches) + .remove(&item.base.uuid()) + .unwrap_or_default(); + for waiter in waiters { + let _ = waiter.sender.send(Some(item.clone())); + } + self.inner + .events + .item_received + .emit(ItemReceivedEventArgs { item }); + } + Ok(()) + } + + fn handle_descendents(&self, packet: InventoryDescendentsPacket) -> Result<(), Error> { + let folder_id = packet.agent_data.folder_id; + let owner_id = packet.agent_data.owner_id; + let Some(store) = self.store_clone() else { + return Ok(()); + }; + let mut parent = store + .get_value_or_default_with_uuid_a7c63fbe::(folder_id)? + .unwrap_or(InventoryFolder::new(folder_id)?); + if packet.agent_data.version < parent.version() { + return Ok(()); + } + for block in packet.folder_data { + if block.folder_id == UUID::zero() { + continue; + } + let mut folder = InventoryFolder::new(block.folder_id)?; + folder.base.set_parent_uuid(block.parent_id); + folder.base.set_owner_id(owner_id); + folder.base.set_name(wire_string(block.name)); + folder.set_preferred_type(folder_type_from_i8(block.type_)); + store.update_node_for(&folder)?; + } + for block in packet.item_data { + if block.item_id == UUID::zero() { + continue; + } + let item = inventory_item_from_wire(InventoryWireData { + asset_id: block.asset_id, + base_mask: block.base_mask, + creation_date: block.creation_date, + creator_id: block.creator_id, + description: block.description, + everyone_mask: block.everyone_mask, + flags: block.flags, + folder_id: block.folder_id, + group_id: block.group_id, + group_mask: block.group_mask, + group_owned: block.group_owned, + inv_type: block.inv_type, + item_id: block.item_id, + name: block.name, + next_owner_mask: block.next_owner_mask, + owner_id, + owner_mask: block.owner_mask, + sale_price: block.sale_price, + sale_type: block.sale_type, + type_: block.type_, + })?; + self.cache_server_item(&item)?; + } + parent.set_version(packet.agent_data.version); + parent.set_descendent_count(packet.agent_data.descendents); + parent.base.set_owner_id(owner_id); + store.update_node_for(&parent)?; + self.inner + .events + .folder_updated + .emit(FolderUpdatedEventArgs { + folder_id, + success: true, + }); + Ok(()) + } + + fn handle_move_item(&self, packet: MoveInventoryItemPacket) -> Result<(), Error> { + let Some(store) = self.store_clone() else { + return Ok(()); + }; + for block in packet.inventory_data { + let Some(mut item) = store.native_item_value(block.item_id) else { + continue; + }; + item.base.set_parent_uuid(block.folder_id); + let name = wire_string(block.new_name); + if !name.is_empty() { + item.base.set_name(name); + } + store.update_node_for(&item)?; + } + Ok(()) + } + + pub(crate) fn native_subscribe_find_object_by_path_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner + .events + .find_object_by_path_reply + .subscribe(handler) + } + pub(crate) fn native_subscribe_folder_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.folder_updated.subscribe(handler) + } + pub(crate) fn native_subscribe_inventory_object_offered( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner + .events + .inventory_object_offered + .subscribe(handler) + } + pub(crate) fn native_subscribe_item_received( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.item_received.subscribe(handler) + } + pub(crate) fn native_subscribe_save_asset_to_inventory( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.save_asset_to_inventory.subscribe(handler) + } + pub(crate) fn native_subscribe_script_running_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.script_running_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_task_inventory_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.task_inventory_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_task_item_received( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.task_item_received.subscribe(handler) + } + + pub(crate) fn native_magic_id() -> UUID { + UUID::new_with_string(MAGIC_ID_TEXT.to_owned()).expect("static inventory magic UUID") + } + + pub(crate) fn native_decrypt_shadow_id(shadow_id: UUID) -> Result { + Ok(UUID::bitxor(shadow_id, Self::native_magic_id())) + } + + pub(crate) fn native_encrypt_asset_id(asset_id: UUID) -> Result { + Self::native_decrypt_shadow_id(asset_id) + } + + pub(crate) fn native_item_crc(item: &InventoryItem) -> Result { + let mut crc = 0_u32; + for value in [ + item.asset_uuid(), + item.base.parent_uuid(), + item.base.uuid(), + item.creator_id(), + item.base.owner_id(), + item.group_id(), + ] { + crc = crc.wrapping_add(uuid_crc(value)); + } + let permissions = item.permissions(); + for value in [ + permissions.owner_mask.0, + permissions.next_owner_mask.0, + permissions.everyone_mask.0, + permissions.group_mask.0, + item.flags(), + i32::from(item.inventory_type().0).cast_unsigned(), + i32::from(item.asset_type() as i8).cast_unsigned(), + unix_seconds_u32_wrapping(item.creation_date()), + item.sale_price().cast_unsigned(), + u32::from(item.sale_type() as u8).wrapping_mul(0x0707_3096), + ] { + crc = crc.wrapping_add(value); + } + Ok(crc) + } + + pub(crate) fn native_create_inventory_item( + type_: InventoryType, + id: UUID, + ) -> Result, Error> { + macro_rules! boxed { + ($kind:ty) => { + Box::new(<$kind>::new(id)?) as Box + }; + } + Ok(match type_ { + InventoryType::TEXTURE => boxed!(InventoryTexture), + InventoryType::SOUND => boxed!(InventorySound), + InventoryType::CALLING_CARD => boxed!(InventoryCallingCard), + InventoryType::LANDMARK => boxed!(InventoryLandmark), + InventoryType::OBJECT => boxed!(InventoryObject), + InventoryType::NOTECARD => boxed!(InventoryNotecard), + InventoryType::CATEGORY => boxed!(InventoryCategory), + InventoryType::LSL => boxed!(InventoryLSL), + InventoryType::SNAPSHOT => boxed!(InventorySnapshot), + InventoryType::ATTACHMENT => boxed!(InventoryAttachment), + InventoryType::WEARABLE => boxed!(InventoryWearable), + InventoryType::ANIMATION => boxed!(InventoryAnimation), + InventoryType::GESTURE => boxed!(InventoryGesture), + InventoryType::SETTINGS => boxed!(InventorySettings), + InventoryType::MATERIAL => boxed!(InventoryMaterial), + _ => Box::new(InventoryItem::new_with_inventory_type_uuid(type_, id)?), + }) + } + + pub(crate) fn native_store(&self) -> Option { + read(&self.inner.store).clone() + } + + pub(crate) fn native_no_results() -> Task> { + Task(std::marker::PhantomData) + } + + pub(crate) fn native_create_or_retrieve_inventory_item( + &self, + inventory_type: InventoryType, + item_id: UUID, + ) -> Result { + if let Some(item) = self + .store_clone() + .and_then(|store| store.native_item_value(item_id)) + { + return Ok(item); + } + InventoryItem::new_with_inventory_type_uuid(inventory_type, item_id) + } + + pub(crate) fn native_create_folder( + &self, + parent_id: UUID, + mut name: String, + preferred_type: FolderType, + ) -> Result { + let store = self.store_clone(); + if preferred_type != FolderType::None { + let owner_id = self.ids()?.0; + if let Some(existing) = store + .as_ref() + .and_then(|store| store.find_folder_for_type(preferred_type)) + .and_then(|folder_id| { + store.as_ref().and_then(|store| { + store + .get_value_or_default_with_uuid_a7c63fbe::(folder_id) + .ok() + .flatten() + }) + }) + .filter(|folder| folder.base.owner_id() == owner_id) + { + return Ok(existing.base.uuid()); + } + } + if name.is_empty() { + preferred_type + .enum_text() + .unwrap_or("New Folder") + .clone_into(&mut name); + } + let folder_id = UUID::random()?; + let owner_id = self.ids()?.0; + let mut folder = InventoryFolder::new(folder_id)?; + folder.base.set_parent_uuid(parent_id); + folder.base.set_owner_id(owner_id); + folder.base.set_name(name.clone()); + folder.set_version(1); + folder.set_descendent_count(0); + folder.set_preferred_type(preferred_type); + if let Some(store) = store { + store.update_node_for(&folder)?; + } + + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = CreateInventoryFolderPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.folder_data.folder_id = folder_id; + packet.folder_data.parent_id = parent_id; + packet.folder_data.type_ = preferred_type as i8; + packet.folder_data.name = Utils::string_to_bytes(name)?; + self.send_generated(&simulator, PacketType::CreateInventoryFolder, &packet)?; + Ok(folder_id) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn native_create_item( + &self, + parent_folder: UUID, + name: String, + description: String, + asset_type: AssetType, + asset_transaction_id: UUID, + inventory_type: InventoryType, + wearable_type: WearableType, + next_owner_mask: PermissionMask, + cancellation_token: Option, + ) -> Result, Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + self.ensure_pending_capacity()?; + let callback_id = self.next_callback_id(); + let (sender, receiver) = oneshot::channel(); + mutex(&self.inner.pending_created).insert( + callback_id, + PendingCreated { + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender, + }, + ); + let send_result = (|| { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = CreateInventoryItemPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.inventory_block.callback_id = callback_id; + packet.inventory_block.folder_id = parent_folder; + packet.inventory_block.transaction_id = asset_transaction_id; + packet.inventory_block.next_owner_mask = next_owner_mask.0; + packet.inventory_block.type_ = asset_type as i8; + packet.inventory_block.inv_type = inventory_type.0; + packet.inventory_block.wearable_type = wearable_type as u8; + packet.inventory_block.name = Utils::string_to_bytes(name)?; + packet.inventory_block.description = Utils::string_to_bytes(description)?; + self.send_generated(&simulator, PacketType::CreateInventoryItem, &packet) + })(); + if let Err(error) = send_result { + mutex(&self.inner.pending_created).remove(&callback_id); + return Err(error); + } + let cancelled = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancelled); + match select(receiver, cancelled).await { + Either::Left((Ok(item), _)) => Ok(item), + Either::Left((Err(_), _)) => Ok(None), + Either::Right(_) => { + mutex(&self.inner.pending_created).remove(&callback_id); + Err(Error::Cancelled) + } + } + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn native_create_link( + &self, + folder_id: UUID, + item_id: UUID, + name: String, + description: String, + inventory_type: InventoryType, + transaction_id: UUID, + cancellation_token: Option, + ) -> Result, Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + if let Ok(simulator) = self.current_simulator(None) + && self + .wait_for_capability(&simulator, "InventoryAPIv3", &cancellation_token) + .await? + .is_some() + { + let mut created = self + .create_links_via_cap( + folder_id, + vec![(item_id, name, description, inventory_type)], + cancellation_token, + ) + .await?; + return Ok(created.pop()); + } + + self.ensure_pending_capacity()?; + let callback_id = self.next_callback_id(); + let (sender, receiver) = oneshot::channel(); + mutex(&self.inner.pending_created).insert( + callback_id, + PendingCreated { + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender, + }, + ); + let send_result = (|| { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = LinkInventoryItemPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.inventory_block.callback_id = callback_id; + packet.inventory_block.folder_id = folder_id; + packet.inventory_block.transaction_id = transaction_id; + packet.inventory_block.old_item_id = item_id; + packet.inventory_block.type_ = if inventory_type == InventoryType::FOLDER { + AssetType::LinkFolder as i8 + } else { + AssetType::Link as i8 + }; + packet.inventory_block.inv_type = inventory_type.0; + packet.inventory_block.name = Utils::string_to_bytes(name)?; + packet.inventory_block.description = Utils::string_to_bytes(description)?; + self.send_generated(&simulator, PacketType::LinkInventoryItem, &packet) + })(); + if let Err(error) = send_result { + mutex(&self.inner.pending_created).remove(&callback_id); + return Err(error); + } + let cancelled = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancelled); + match select(receiver, cancelled).await { + Either::Left((Ok(item), _)) => Ok(item), + Either::Left((Err(_), _)) => Ok(None), + Either::Right(_) => { + mutex(&self.inner.pending_created).remove(&callback_id); + Err(Error::Cancelled) + } + } + } + + pub(crate) async fn native_create_links( + &self, + folder_id: UUID, + links: Box>, + callback: Option>, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + let links: Vec<_> = links.collect(); + if links.is_empty() { + if let Some(callback) = callback { + callback(true); + } + return Ok(()); + } + let cap = if let Ok(simulator) = self.current_simulator(None) { + self.wait_for_capability(&simulator, "InventoryAPIv3", &cancellation_token) + .await? + } else { + None + }; + if cap.is_some() { + let values = links + .into_iter() + .map(|(item, description)| { + ( + item.base.uuid(), + item.base.name(), + description, + item.inventory_type(), + ) + }) + .collect(); + let success = self + .create_links_via_cap(folder_id, values, cancellation_token) + .await + .is_ok(); + if let Some(callback) = callback { + callback(success); + } + return Ok(()); + } + let mut success = true; + for (item, description) in links { + cancellation_token.throw_if_cancellation_requested()?; + if self + .native_create_link( + folder_id, + item.base.uuid(), + item.base.name(), + description, + item.inventory_type(), + UUID::random()?, + Some(cancellation_token.clone()), + ) + .await? + .is_none() + { + success = false; + } + } + if let Some(callback) = callback { + callback(success); + } + Ok(()) + } + + async fn create_links_via_cap( + &self, + folder_id: UUID, + links: Vec<(UUID, String, String, InventoryType)>, + cancellation_token: CancellationToken, + ) -> Result, Error> { + let simulator = self.current_simulator(None)?; + let cap = self + .wait_for_capability(&simulator, "InventoryAPIv3", &cancellation_token) + .await? + .ok_or(Error::InvalidOperation)?; + let uri = Uri(format!( + "{}/category/{}?tid={}", + cap.0.trim_end_matches('/'), + folder_id, + UUID::random()? + )); + let payload = OSD::Map(HashMap::from([( + "links".to_owned(), + OSD::Array( + links + .into_iter() + .map(|(linked_id, name, description, inventory_type)| { + OSD::Map(HashMap::from([ + ("linked_id".to_owned(), OSD::UUID(linked_id)), + ("name".to_owned(), OSD::String(name)), + ("desc".to_owned(), OSD::String(description)), + ( + "type".to_owned(), + OSD::Integer(if inventory_type == InventoryType::FOLDER { + AssetType::LinkFolder as i32 + } else { + AssetType::Link as i32 + }), + ), + ( + "inv_type".to_owned(), + OSD::Integer(i32::from(inventory_type.0)), + ), + ])) + }) + .collect(), + ), + )])); + let bytes = OSDParser::serialize_llsd_xml_bytes(payload)?; + let (response, body) = self + .client()? + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".to_owned(), + bytes, + cancellation_token, + None, + ) + .await?; + if !response.is_success_status_code() { + return Err(Error::HttpRequest); + } + let mut created = Vec::new(); + if let Ok(OSD::Map(root)) = OSDParser::deserialize_with_bytes(body) + && let Some(OSD::Map(embedded)) = root.get("_embedded") + && let Some(OSD::Map(values)) = embedded.get("links") + { + for value in values.values() { + if let Ok(item) = InventoryItem::from_osd(value.clone()) { + self.cache_server_item(&item)?; + self.inner + .events + .item_received + .emit(ItemReceivedEventArgs { item: item.clone() }); + created.push(item); + } + } + } + Ok(created) + } + + pub(crate) fn native_request_fetch_inventory( + &self, + items: HashMap, + cancellation_token: Option, + ) -> Result<(), Error> { + cancellation_token + .unwrap_or_default() + .throw_if_cancellation_requested()?; + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = FetchInventoryPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.inventory_data = items + .into_iter() + .map(|(item_id, owner_id)| { + let mut block = FetchInventoryPacketInventoryDataBlock::new_with_constructor()?; + block.item_id = item_id; + block.owner_id = owner_id; + Ok(block) + }) + .collect::, Error>>()?; + self.send_generated(&simulator, PacketType::FetchInventory, &packet) + } + + pub(crate) async fn native_request_fetch_inventory_async( + &self, + items: HashMap, + cancellation_token: Option, + callback: Option) + Send + Sync>>, + ) -> Result<(), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + let capability = if let Ok(simulator) = self.current_simulator(None) { + self.wait_for_capability(&simulator, "FetchInventory2", &cancellation_token) + .await? + } else { + None + }; + if let Some(capability) = capability { + let fetched = self + .fetch_inventory_via_cap(capability, items, cancellation_token) + .await?; + if let Some(callback) = callback { + callback(fetched); + } + return Ok(()); + } + self.native_request_fetch_inventory(items, Some(cancellation_token)) + } + + async fn fetch_inventory_via_cap( + &self, + capability: Uri, + items: HashMap, + cancellation_token: CancellationToken, + ) -> Result, Error> { + let agent_id = self.ids()?.0; + let item_values = items + .into_iter() + .map(|(item_id, owner_id)| { + OSD::Map(HashMap::from([ + ("item_id".to_owned(), OSD::UUID(item_id)), + ("owner_id".to_owned(), OSD::UUID(owner_id)), + ])) + }) + .collect(); + let payload = OSD::Map(HashMap::from([ + ("agent_id".to_owned(), OSD::UUID(agent_id)), + ("items".to_owned(), OSD::Array(item_values)), + ])); + let body = OSDParser::serialize_llsd_xml_bytes(payload)?; + let (response, response_body) = self + .client()? + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + capability, + "application/llsd+xml".to_owned(), + body, + cancellation_token, + None, + ) + .await?; + if !response.is_success_status_code() { + return Err(Error::HttpRequest); + } + let OSD::Map(map) = OSDParser::deserialize_with_bytes(response_body)? else { + return Err(Error::Argument); + }; + let Some(OSD::Array(values)) = map.get("items") else { + return Ok(Vec::new()); + }; + let mut fetched = Vec::with_capacity(values.len()); + for value in values { + let item = InventoryItem::from_osd(value.clone())?; + if item.base.uuid() == UUID::zero() { + continue; + } + self.cache_server_item(&item)?; + let waiters = mutex(&self.inner.pending_fetches) + .remove(&item.base.uuid()) + .unwrap_or_default(); + for waiter in waiters { + let _ = waiter.sender.send(Some(item.clone())); + } + self.inner + .events + .item_received + .emit(ItemReceivedEventArgs { item: item.clone() }); + fetched.push(item); + } + Ok(fetched) + } + + pub(crate) async fn native_fetch_item( + &self, + item_id: UUID, + owner_id: UUID, + cancellation_token: Option, + force_http: bool, + ) -> Result, Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + if cancellation_token.is_cancellation_requested() { + return Ok(None); + } + self.ensure_pending_capacity()?; + let request_id = self.next_request_id(); + let (sender, receiver) = oneshot::channel(); + mutex(&self.inner.pending_fetches) + .entry(item_id) + .or_default() + .push(PendingFetch { + request_id, + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender, + }); + + let request = HashMap::from([(item_id, owner_id)]); + let send_result = if force_http { + let capability = if let Ok(simulator) = self.current_simulator(None) { + self.wait_for_capability(&simulator, "FetchInventory2", &cancellation_token) + .await? + } else { + None + }; + if let Some(capability) = capability { + self.fetch_inventory_via_cap(capability, request, cancellation_token.clone()) + .await + .map(|_| ()) + } else { + self.remove_pending_fetch(item_id, request_id); + return Ok(None); + } + } else { + self.native_request_fetch_inventory_async( + request, + Some(cancellation_token.clone()), + None, + ) + .await + }; + if let Err(error) = send_result { + self.remove_pending_fetch(item_id, request_id); + return Err(error); + } + + let cancelled = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancelled); + match select(receiver, cancelled).await { + Either::Left((Ok(item), _)) => Ok(item), + Either::Left((Err(_), _)) | Either::Right(_) => { + self.remove_pending_fetch(item_id, request_id); + Ok(None) + } + } + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn native_request_folder_contents( + &self, + folder_id: UUID, + owner_id: UUID, + fetch_folders: bool, + fetch_items: bool, + order: InventorySortOrder, + cancellation_token: Option, + ) -> Result, Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + let cap_name = if owner_id == self.ids()?.0 { + "FetchInventoryDescendents2" + } else { + "FetchLibDescendents2" + }; + let capability = if let Ok(simulator) = self.current_simulator(None) { + self.wait_for_capability(&simulator, cap_name, &cancellation_token) + .await? + } else { + None + }; + let Some(capability) = capability else { + self.inner + .events + .folder_updated + .emit(FolderUpdatedEventArgs { + folder_id, + success: false, + }); + return Ok(Vec::new()); + }; + let mut folder = InventoryFolder::new(folder_id)?; + folder.base.set_owner_id(owner_id); + self.native_request_folder_contents_batch( + vec![folder], + capability, + fetch_folders, + fetch_items, + order, + Some(cancellation_token), + ) + .await + } + + pub(crate) async fn native_request_folder_contents_batch( + &self, + batch: Vec, + capability: Uri, + fetch_folders: bool, + fetch_items: bool, + order: InventorySortOrder, + cancellation_token: Option, + ) -> Result, Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + let requested = batch + .iter() + .map(|folder| { + OSD::Map(HashMap::from([ + ("folder_id".to_owned(), OSD::UUID(folder.base.uuid())), + ("owner_id".to_owned(), OSD::UUID(folder.base.owner_id())), + ("fetch_folders".to_owned(), OSD::Boolean(fetch_folders)), + ("fetch_items".to_owned(), OSD::Boolean(fetch_items)), + ("sort_order".to_owned(), OSD::Integer(order.0)), + ])) + }) + .collect(); + let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([( + "folders".to_owned(), + OSD::Array(requested), + )])))?; + let response = self + .client()? + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + capability, + "application/llsd+xml".to_owned(), + body, + cancellation_token, + None, + ) + .await; + let (response, response_body) = match response { + Ok(response) if response.0.is_success_status_code() => response, + Ok(_) | Err(_) => { + for folder in batch { + self.inner + .events + .folder_updated + .emit(FolderUpdatedEventArgs { + folder_id: folder.base.uuid(), + success: false, + }); + } + return Ok(Vec::new()); + } + }; + let _ = response; + let OSD::Map(root) = OSDParser::deserialize_with_bytes(response_body)? else { + return Err(Error::Argument); + }; + let Some(OSD::Array(folders)) = root.get("folders") else { + return Ok(Vec::new()); + }; + let store = self.store_clone(); + let mut result = Vec::new(); + for value in folders { + let OSD::Map(map) = value else { + continue; + }; + let folder_id = map.get("folder_id").ok_or(Error::Argument)?.as_uuid()?; + let owner_id = map.get("owner_id").map_or(Ok(UUID::zero()), OSD::as_uuid)?; + let mut parent = store + .as_ref() + .and_then(|store| { + store + .get_value_or_default_with_uuid_a7c63fbe::(folder_id) + .ok() + .flatten() + }) + .unwrap_or(InventoryFolder::new(folder_id)?); + parent.base.set_owner_id(owner_id); + if let Some(value) = map.get("version") { + parent.set_version(value.as_integer()?); + } + if let Some(value) = map.get("descendents") { + parent.set_descendent_count(value.as_integer()?); + } + if let Some(store) = &store { + store.update_node_for(&parent)?; + } + if let Some(OSD::Array(categories)) = map.get("categories") { + for value in categories { + let folder = InventoryFolder::from_osd(value.clone())?; + if let Some(store) = &store { + store.update_node_for(&folder)?; + } + result.push(folder.base.clone()); + } + } + if let Some(OSD::Array(items)) = map.get("items") { + for value in items { + let item = InventoryItem::from_osd(value.clone())?; + self.cache_server_item(&item)?; + result.push(item.base.clone()); + } + } + self.inner + .events + .folder_updated + .emit(FolderUpdatedEventArgs { + folder_id, + success: true, + }); + } + Ok(result) + } + + pub(crate) async fn native_request_folder_update( + &self, + folder_id: UUID, + owner_id: UUID, + fetch_folders: bool, + fetch_items: bool, + order: InventorySortOrder, + cancellation_token: Option, + ) -> Result { + let cancellation_token = cancellation_token.unwrap_or_default(); + let cap_name = if owner_id == self.ids()?.0 { + "FetchInventoryDescendents2" + } else { + "FetchLibDescendents2" + }; + let has_capability = if let Ok(simulator) = self.current_simulator(None) { + self.wait_for_capability(&simulator, cap_name, &cancellation_token) + .await? + .is_some() + } else { + false + }; + let contents = self + .native_request_folder_contents( + folder_id, + owner_id, + fetch_folders, + fetch_items, + order, + Some(cancellation_token), + ) + .await?; + Ok(InventoryManagerFolderUpdateResult { + contents: Some(contents), + folder_id, + success: has_capability, + }) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn native_folder_contents( + &self, + folder_id: UUID, + owner_id: UUID, + fetch_folders: bool, + fetch_items: bool, + order: InventorySortOrder, + cancellation_token: Option, + follow_links: bool, + ) -> Result>, Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + let remote = self + .native_request_folder_contents( + folder_id, + owner_id, + fetch_folders, + fetch_items, + order, + Some(cancellation_token.clone()), + ) + .await; + if matches!(remote, Err(Error::Cancelled)) { + return remote.map(|_| Vec::new()); + } + let store = self.store_clone(); + let mut contents = store + .as_ref() + .and_then(|store| store.get_contents_sorted_with_uuid(folder_id, order).ok()) + .unwrap_or_default(); + if contents.is_empty() + && let Ok(values) = remote + { + contents = values + .into_iter() + .map(|value| Box::new(value) as Box) + .collect(); + } + if follow_links { + for entry in &mut contents { + let Some(item) = store + .as_ref() + .and_then(|store| store.native_item_value(entry.inventory_base().uuid())) + else { + continue; + }; + if !item.is_link()? { + continue; + } + if let Some(real) = store + .as_ref() + .and_then(|store| store.native_item_value(item.asset_uuid())) + { + *entry = typed_inventory_item(real); + } else if let Some(real) = self + .native_fetch_item( + item.asset_uuid(), + owner_id, + Some(cancellation_token.clone()), + false, + ) + .await? + { + *entry = typed_inventory_item(real); + } + } + } + Ok(contents) + } + + pub(crate) fn native_find_folder_for_type(&self, folder_type: FolderType) -> UUID { + let Some(store) = self.store_clone() else { + return UUID::zero(); + }; + store.find_folder_for_type(folder_type).unwrap_or_else(|| { + store + .root_folder() + .map_or_else(UUID::zero, |folder| folder.base.uuid()) + }) + } + + pub(crate) fn native_find_folder_for_asset_type(&self, asset_type: AssetType) -> UUID { + let Some(folder_type) = folder_type_from_i8_checked(asset_type as i8) else { + return self + .store_clone() + .and_then(|store| store.root_folder()) + .map_or_else(UUID::zero, |folder| folder.base.uuid()); + }; + self.native_find_folder_for_type(folder_type) + } + + pub(crate) fn native_local_find( + &self, + base_folder: UUID, + path: Vec, + level: i32, + first_only: bool, + ) -> Result, Error> { + if level < 0 { + return Ok(Vec::new()); + } + let Some(store) = self.store_clone() else { + return Ok(Vec::new()); + }; + let mut visited = HashSet::new(); + local_find_in_store( + &store, + base_folder, + &path, + usize::try_from(level).map_err(|_| Error::Argument)?, + first_only, + &mut visited, + 0, + ) + } + + pub(crate) async fn native_find_object_by_path( + &self, + base_folder: UUID, + owner_id: UUID, + path: String, + cancellation_token: Option, + ) -> Result { + if path.is_empty() { + return Err(Error::Argument); + } + let cancellation_token = cancellation_token.unwrap_or_default(); + let segments: Vec<_> = path.split('/').map(ToOwned::to_owned).collect(); + let mut folder = base_folder; + for (index, segment) in segments.iter().enumerate() { + cancellation_token.throw_if_cancellation_requested()?; + let contents = self + .native_folder_contents( + folder, + owner_id, + true, + true, + InventorySortOrder::BY_NAME, + Some(cancellation_token.clone()), + false, + ) + .await?; + let Some(found) = contents + .into_iter() + .find(|entry| entry.inventory_base().name() == *segment) + else { + self.inner + .events + .find_object_by_path_reply + .emit(FindObjectByPathReplyEventArgs { + path, + inventory_object_id: UUID::zero(), + }); + return Ok(UUID::zero()); + }; + folder = found.inventory_base().uuid(); + if index + 1 == segments.len() { + self.inner + .events + .find_object_by_path_reply + .emit(FindObjectByPathReplyEventArgs { + path, + inventory_object_id: folder, + }); + return Ok(folder); + } + } + self.inner + .events + .find_object_by_path_reply + .emit(FindObjectByPathReplyEventArgs { + path, + inventory_object_id: UUID::zero(), + }); + Ok(UUID::zero()) + } + + pub(crate) fn native_update_folder_properties( + &self, + folder_id: UUID, + parent_id: UUID, + name: String, + folder_type: FolderType, + ) -> Result<(), Error> { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = UpdateInventoryFolderPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + let mut block = UpdateInventoryFolderPacketFolderDataBlock::new_with_constructor()?; + block.folder_id = folder_id; + block.parent_id = parent_id; + block.name = Utils::string_to_bytes(name.clone())?; + block.type_ = folder_type as i8; + packet.folder_data = vec![block]; + self.send_generated(&simulator, PacketType::UpdateInventoryFolder, &packet)?; + if let Some(store) = self.store_clone() + && let Some(mut folder) = + store.get_value_or_default_with_uuid_a7c63fbe::(folder_id)? + { + folder.base.set_parent_uuid(parent_id); + folder.base.set_name(name); + folder.set_preferred_type(folder_type); + store.update_node_for(&folder)?; + } + Ok(()) + } + + pub(crate) fn native_move_folders( + &self, + moves: HashMap, + cancellation_token: Option, + ) -> Result<(), Error> { + cancellation_token + .unwrap_or_default() + .throw_if_cancellation_requested()?; + self.apply_local_folder_moves(&moves)?; + self.send_move_folders_packet(&moves) + } + + fn apply_local_folder_moves(&self, moves: &HashMap) -> Result<(), Error> { + if let Some(store) = self.store_clone() { + for (folder_id, parent_id) in moves { + if let Some(mut folder) = + store.get_value_or_default_with_uuid_a7c63fbe::(*folder_id)? + { + folder.base.set_parent_uuid(*parent_id); + store.update_node_for(&folder)?; + } + } + } + Ok(()) + } + + fn send_move_folders_packet(&self, moves: &HashMap) -> Result<(), Error> { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = MoveInventoryFolderPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.stamp = false; + packet.inventory_data = moves + .iter() + .map(|(folder_id, parent_id)| { + let mut block = + MoveInventoryFolderPacketInventoryDataBlock::new_with_constructor()?; + block.folder_id = *folder_id; + block.parent_id = *parent_id; + Ok(block) + }) + .collect::, Error>>()?; + self.send_generated(&simulator, PacketType::MoveInventoryFolder, &packet) + } + + pub(crate) fn native_move_items( + &self, + moves: HashMap, + cancellation_token: Option, + ) -> Result<(), Error> { + cancellation_token + .unwrap_or_default() + .throw_if_cancellation_requested()?; + self.apply_local_item_moves(&moves)?; + self.send_move_items_packet(&moves) + } + + fn apply_local_item_moves(&self, moves: &HashMap) -> Result<(), Error> { + if let Some(store) = self.store_clone() { + for (item_id, (folder_id, new_name)) in moves { + if let Some(mut item) = store.native_item_value(*item_id) { + item.base.set_parent_uuid(*folder_id); + if !new_name.is_empty() { + item.base.set_name(new_name.clone()); + } + store.update_node_for(&item)?; + } + } + } + Ok(()) + } + + fn send_move_items_packet(&self, moves: &HashMap) -> Result<(), Error> { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = MoveInventoryItemPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.stamp = false; + packet.inventory_data = moves + .iter() + .map(|(item_id, (folder_id, name))| { + let mut block = MoveInventoryItemPacketInventoryDataBlock::new_with_constructor()?; + block.item_id = *item_id; + block.folder_id = *folder_id; + block.new_name = Utils::string_to_bytes(name.clone())?; + Ok(block) + }) + .collect::, Error>>()?; + self.send_generated(&simulator, PacketType::MoveInventoryItem, &packet) + } + + pub(crate) async fn native_move_folders_async( + &self, + moves: HashMap, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + self.apply_local_folder_moves(&moves)?; + if let Some(capability) = self.inventory_api_capability(&cancellation_token).await? { + for (folder_id, parent_id) in moves { + cancellation_token.throw_if_cancellation_requested()?; + let _ = self + .ais_patch_parent( + &capability, + "category", + folder_id, + parent_id, + cancellation_token.clone(), + ) + .await; + } + return Ok(()); + } + self.send_move_folders_packet(&moves) + } + + pub(crate) async fn native_move_items_async( + &self, + moves: HashMap, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + self.apply_local_item_moves(&moves)?; + let can_use_ais = moves.values().all(|(_, name)| name.is_empty()); + if can_use_ais + && let Some(capability) = self.inventory_api_capability(&cancellation_token).await? + { + for (item_id, (parent_id, _)) in moves { + cancellation_token.throw_if_cancellation_requested()?; + let _ = self + .ais_patch_parent( + &capability, + "item", + item_id, + parent_id, + cancellation_token.clone(), + ) + .await; + } + return Ok(()); + } + self.send_move_items_packet(&moves) + } + + async fn inventory_api_capability( + &self, + cancellation_token: &CancellationToken, + ) -> Result, Error> { + let Ok(simulator) = self.current_simulator(None) else { + return Ok(None); + }; + self.wait_for_capability(&simulator, "InventoryAPIv3", cancellation_token) + .await + } + + async fn ais_patch_parent( + &self, + capability: &Uri, + kind: &str, + object_id: UUID, + parent_id: UUID, + cancellation_token: CancellationToken, + ) -> Result { + let uri = Uri(format!( + "{}/{kind}/{object_id}", + capability.0.trim_end_matches('/') + )); + let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([( + "parent_id".to_owned(), + OSD::UUID(parent_id), + )])))?; + let response = self + .client()? + .native_http_caps_client() + .patch_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".to_owned(), + body, + cancellation_token, + None, + ) + .await?; + Ok(response.0.is_success_status_code()) + } + + pub(crate) fn native_move_object( + &self, + item: InventoryBase, + new_parent: InventoryFolder, + ) -> Result<(), Error> { + if self + .store_clone() + .and_then(|store| { + store + .get_value_or_default_with_uuid_a7c63fbe::(item.uuid()) + .ok() + .flatten() + }) + .is_some() + { + self.native_move_folders(HashMap::from([(item.uuid(), new_parent.base.uuid())]), None) + } else { + self.native_move_items( + HashMap::from([(item.uuid(), (new_parent.base.uuid(), String::new()))]), + None, + ) + } + } + + pub(crate) fn native_request_update_items( + &self, + items: Vec, + transaction_id: UUID, + ) -> Result<(), Error> { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = UpdateInventoryItemPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.transaction_id = transaction_id; + packet.inventory_data = items + .iter() + .map(|item| { + let mut block = + UpdateInventoryItemPacketInventoryDataBlock::new_with_constructor()?; + fill_inventory_wire_block!( + block, + item, + item.transaction_id(), + item.asset_type() as i8 + ); + block.callback_id = 0; + block.crc = Self::native_item_crc(item)?; + Ok(block) + }) + .collect::, Error>>()?; + self.send_generated(&simulator, PacketType::UpdateInventoryItem, &packet) + } + + pub(crate) async fn native_remove_descendants( + &self, + folder_id: UUID, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + if let Some(capability) = self.inventory_api_capability(&cancellation_token).await? { + if self + .ais_delete_object( + &capability, + &format!("category/{folder_id}/children"), + cancellation_token, + ) + .await + .unwrap_or(false) + { + self.remove_local_descendants(folder_id, true)?; + } + return Ok(()); + } + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = PurgeInventoryDescendentsPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.inventory_data.folder_id = folder_id; + self.send_generated(&simulator, PacketType::PurgeInventoryDescendents, &packet)?; + self.remove_local_descendants(folder_id, true) + } + + pub(crate) async fn native_remove_inventory_objects( + &self, + items: Vec, + folders: Vec, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + if let Some(capability) = self.inventory_api_capability(&cancellation_token).await? { + for item in items { + cancellation_token.throw_if_cancellation_requested()?; + if self + .ais_delete_object( + &capability, + &format!("item/{item}"), + cancellation_token.clone(), + ) + .await + .unwrap_or(false) + { + self.remove_local_descendants(item, true)?; + } + } + for folder in folders { + cancellation_token.throw_if_cancellation_requested()?; + if self + .ais_delete_object( + &capability, + &format!("category/{folder}"), + cancellation_token.clone(), + ) + .await + .unwrap_or(false) + { + self.remove_local_descendants(folder, true)?; + } + } + return Ok(()); + } + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = RemoveInventoryObjectsPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.item_data = items + .iter() + .map(|item_id| { + let mut block = RemoveInventoryObjectsPacketItemDataBlock::new_with_constructor()?; + block.item_id = *item_id; + Ok(block) + }) + .collect::, Error>>()?; + packet.folder_data = folders + .iter() + .map(|folder_id| { + let mut block = + RemoveInventoryObjectsPacketFolderDataBlock::new_with_constructor()?; + block.folder_id = *folder_id; + Ok(block) + }) + .collect::, Error>>()?; + self.send_generated(&simulator, PacketType::RemoveInventoryObjects, &packet)?; + for item in items { + self.remove_local_descendants(item, true)?; + } + for folder in folders { + self.remove_local_descendants(folder, true)?; + } + Ok(()) + } + + async fn ais_delete_object( + &self, + capability: &Uri, + path: &str, + cancellation_token: CancellationToken, + ) -> Result { + let uri = Uri(format!("{}/{path}", capability.0.trim_end_matches('/'))); + let response = self + .client()? + .native_http_caps_client() + .delete_with_uri_string_bytes_cancellation_token_i_progress( + uri, + "application/llsd+xml".to_owned(), + Vec::new(), + cancellation_token, + None, + ) + .await?; + Ok(response.0.is_success_status_code() || response.0.status_code == 410) + } + + fn remove_local_descendants(&self, root: UUID, include_root: bool) -> Result<(), Error> { + let Some(store) = self.store_clone() else { + return Ok(()); + }; + if include_root { + if let Some(value) = store.get_value_or_default_with_uuid(root)? { + store.remove_node_for(&value)?; + } + return Ok(()); + } + let contents = store.get_contents_with_uuid(root).unwrap_or_default(); + for value in contents { + store.remove_node_for(value.as_ref())?; + } + Ok(()) + } + + pub(crate) fn native_empty_folder(&self, folder_id: UUID) -> Result<(), Error> { + let Some(store) = self.store_clone() else { + return Ok(()); + }; + let contents = store.get_contents_with_uuid(folder_id).unwrap_or_default(); + let mut items = Vec::new(); + let mut folders = Vec::new(); + for value in contents { + if value.as_any().is::() { + folders.push(value.inventory_base().uuid()); + } else { + items.push(value.inventory_base().uuid()); + } + } + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = RemoveInventoryObjectsPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.item_data = items + .iter() + .map(|id| { + let mut block = RemoveInventoryObjectsPacketItemDataBlock::new_with_constructor()?; + block.item_id = *id; + Ok(block) + }) + .collect::, Error>>()?; + packet.folder_data = folders + .iter() + .map(|id| { + let mut block = + RemoveInventoryObjectsPacketFolderDataBlock::new_with_constructor()?; + block.folder_id = *id; + Ok(block) + }) + .collect::, Error>>()?; + self.send_generated(&simulator, PacketType::RemoveInventoryObjects, &packet) + } + + pub(crate) async fn native_empty_system_folder( + &self, + folder_type: FolderType, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + let folder_id = self.native_find_folder_for_type(folder_type); + if folder_id != UUID::zero() { + self.native_remove_descendants(folder_id, Some(cancellation_token)) + .await?; + } + Ok(()) + } + + fn copy_from_notecard_packet( + &self, + object_id: UUID, + notecard_id: UUID, + folder_id: UUID, + item_id: UUID, + ) -> Result { + let (agent_id, session_id) = self.ids()?; + let mut packet = CopyInventoryFromNotecardPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.notecard_data.object_id = object_id; + packet.notecard_data.notecard_item_id = notecard_id; + let mut block = CopyInventoryFromNotecardPacketInventoryDataBlock::new_with_constructor()?; + block.folder_id = folder_id; + block.item_id = item_id; + packet.inventory_data = vec![block]; + Ok(packet) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn native_copy_item_from_notecard( + &self, + object_id: UUID, + notecard_id: UUID, + folder_id: UUID, + item_id: UUID, + cancellation_token: Option, + progress: Option< + Box>, + >, + ) -> Result, Error> { + const NOTECARD_CALLBACK_ID: u32 = 0; + + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + self.ensure_pending_capacity()?; + let (sender, receiver) = oneshot::channel(); + { + let mut copied = mutex(&self.inner.pending_copied); + // This protocol fixes the callback ID at zero. Reject a concurrent + // request rather than overwriting the only correlatable waiter. + if copied.contains_key(&NOTECARD_CALLBACK_ID) { + return Err(Error::InvalidOperation); + } + copied.insert( + NOTECARD_CALLBACK_ID, + PendingCopy { + deadline: Instant::now() + CALLBACK_TIMEOUT, + expected: 1, + copied: Vec::with_capacity(1), + sender, + }, + ); + } + + let send_result = async { + let simulator = self.current_simulator(None)?; + if let Some(capability) = self + .wait_for_capability(&simulator, "CopyInventoryFromNotecard", &cancellation_token) + .await? + { + let mut message = CopyInventoryFromNotecardMessage::new()?; + message.callback_id = 0; + message.folder_id = folder_id; + message.item_id = item_id; + message.notecard_id = notecard_id; + message.object_id = object_id; + let body = + OSDParser::serialize_llsd_xml_bytes(OSD::Map(message.serialize()?.snapshot()))?; + match self + .client()? + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + capability, + "application/llsd+xml".to_owned(), + body, + cancellation_token.clone(), + progress, + ) + .await + { + Ok((response, _)) if response.is_success_status_code() => return Ok(()), + Err(Error::Cancelled) => return Err(Error::Cancelled), + Ok(_) | Err(_) => {} + } + } + cancellation_token.throw_if_cancellation_requested()?; + let packet = + self.copy_from_notecard_packet(object_id, notecard_id, folder_id, item_id)?; + self.send_generated(&simulator, PacketType::CopyInventoryFromNotecard, &packet) + } + .await; + if let Err(error) = send_result { + mutex(&self.inner.pending_copied).remove(&NOTECARD_CALLBACK_ID); + return Err(error); + } + + let cancelled = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancelled); + match select(receiver, cancelled).await { + Either::Left((Ok(result), _)) => Ok(result + .copied_items + .and_then(|mut items| (!items.is_empty()).then(|| items.remove(0)))), + Either::Left((Err(_), _)) => Ok(None), + Either::Right(_) => { + mutex(&self.inner.pending_copied).remove(&NOTECARD_CALLBACK_ID); + Err(Error::Cancelled) + } + } + } + + pub(crate) async fn native_copy_items( + &self, + items: Vec, + target_folders: Vec, + new_names: Vec, + old_owner_id: UUID, + cancellation_token: Option, + ) -> Result { + if items.len() != target_folders.len() + || (!new_names.is_empty() && items.len() != new_names.len()) + { + return Err(Error::Argument); + } + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + if items.is_empty() { + return Ok(InventoryManagerCopyItemsResult { + copied_items: Some(Vec::new()), + error: None, + success: true, + }); + } + + if let Ok(simulator) = self.current_simulator(None) + && let Some(capability) = self + .wait_for_capability(&simulator, "InventoryAPIv3", &cancellation_token) + .await? + { + let operations = items + .iter() + .zip(&target_folders) + .enumerate() + .map(|(index, (item_id, folder_id))| { + let mut operation = HashMap::from([ + ("item_id".to_owned(), OSD::UUID(*item_id)), + ("folder_id".to_owned(), OSD::UUID(*folder_id)), + ]); + if let Some(name) = new_names.get(index) + && !name.is_empty() + { + operation.insert("new_name".to_owned(), OSD::String(name.clone())); + } + OSD::Map(operation) + }) + .collect(); + let payload = OSD::Map(HashMap::from([ + ("items".to_owned(), OSD::Array(operations)), + ("agent_id".to_owned(), OSD::UUID(self.ids()?.0)), + ])); + let body = OSDParser::serialize_llsd_xml_bytes(payload)?; + let response = self + .client()? + .native_http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + capability, + "application/llsd+xml".to_owned(), + body, + cancellation_token.clone(), + None, + ) + .await; + if response.is_ok_and(|response| response.0.is_success_status_code()) { + return Ok(InventoryManagerCopyItemsResult { + copied_items: Some(Vec::new()), + error: None, + success: true, + }); + } + } + + self.ensure_pending_capacity()?; + let callback_id = self.next_callback_id(); + let (sender, receiver) = oneshot::channel(); + mutex(&self.inner.pending_copied).insert( + callback_id, + PendingCopy { + deadline: Instant::now() + CALLBACK_TIMEOUT, + expected: items.len(), + copied: Vec::with_capacity(items.len()), + sender, + }, + ); + let send_result = (|| { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = CopyInventoryItemPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.inventory_data = items + .iter() + .zip(&target_folders) + .enumerate() + .map(|(index, (item_id, folder_id))| { + let mut block = + CopyInventoryItemPacketInventoryDataBlock::new_with_constructor()?; + block.callback_id = callback_id; + block.old_item_id = *item_id; + block.old_agent_id = old_owner_id; + block.new_folder_id = *folder_id; + block.new_name = + Utils::string_to_bytes(new_names.get(index).cloned().unwrap_or_default())?; + Ok(block) + }) + .collect::, Error>>()?; + self.send_generated(&simulator, PacketType::CopyInventoryItem, &packet) + })(); + if let Err(error) = send_result { + mutex(&self.inner.pending_copied).remove(&callback_id); + return Ok(InventoryManagerCopyItemsResult { + copied_items: None, + error: Some(ExternalError(error.to_string())), + success: false, + }); + } + let cancelled = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancelled); + match select(receiver, cancelled).await { + Either::Left((Ok(result), _)) => Ok(result), + Either::Left((Err(_), _)) => Ok(InventoryManagerCopyItemsResult::default()), + Either::Right(_) => { + mutex(&self.inner.pending_copied).remove(&callback_id); + Err(Error::Cancelled) + } + } + } + + pub(crate) async fn native_copy_item( + &self, + item_id: UUID, + new_parent: UUID, + new_name: String, + old_owner_id: UUID, + cancellation_token: Option, + ) -> Result, Error> { + Ok(self + .native_copy_items( + vec![item_id], + vec![new_parent], + vec![new_name], + old_owner_id, + cancellation_token, + ) + .await? + .copied_items + .and_then(|mut items| (!items.is_empty()).then(|| items.remove(0)))) + } + + pub(crate) async fn native_copy_item_default_owner( + &self, + item_id: UUID, + new_parent: UUID, + new_name: String, + cancellation_token: Option, + ) -> Result, Error> { + self.native_copy_item( + item_id, + new_parent, + new_name, + self.ids()?.0, + cancellation_token, + ) + .await + } + + pub(crate) fn native_dispose(&self) -> Result<(), Error> { + if self.inner.disposed.swap(true, Ordering::AcqRel) { + return Ok(()); + } + if let Some(worker) = mutex(&self.inner.cleanup_worker).take() { + let (stopped, wake) = &*worker.stop; + *mutex(stopped) = true; + wake.notify_all(); + if worker.handle.thread().id() != std::thread::current().id() { + let _ = worker.handle.join(); + } + } + mutex(&self.inner.raw_packet_subscription).take(); + mutex(&self.inner.caps_subscription).take(); + mutex(&self.inner.im_subscription).take(); + if let Some(callback) = mutex(&self.inner.login_callback).take() + && let Ok(client) = self.inner.client.upgrade().ok_or(Error::InvalidOperation) + && let Ok(network) = client.native_network() + { + let _ = network.native_unregister_login_response_callback(&callback); + } + let created = mutex(&self.inner.pending_created) + .drain() + .map(|(_, v)| v) + .collect::>(); + let copied = mutex(&self.inner.pending_copied) + .drain() + .map(|(_, v)| v) + .collect::>(); + let fetches = mutex(&self.inner.pending_fetches) + .drain() + .flat_map(|(_, v)| v) + .collect::>(); + let task_replies = mutex(&self.inner.pending_task_replies) + .drain() + .flat_map(|(_, v)| v) + .collect::>(); + let offers = std::mem::take(&mut *mutex(&self.inner.pending_offers)); + let xfers = mutex(&self.inner.task_xfers) + .drain() + .map(|(_, v)| v) + .collect::>(); + for request in created { + let _ = request.sender.send(None); + } + for request in copied { + let _ = request + .sender + .send(InventoryManagerCopyItemsResult::default()); + } + for request in fetches { + let _ = request.sender.send(None); + } + for request in task_replies { + let _ = request.sender.send(String::new()); + } + drop(offers); + for request in xfers { + let _ = request.sender.send(None); + } + write(&self.inner.store).take(); + Ok(()) + } + + fn current_simulator(&self, simulator: Option) -> Result { + simulator + .or_else(|| { + self.client() + .ok()? + .native_network() + .ok()? + .native_current_sim() + }) + .ok_or(Error::InvalidOperation) + } + + fn ids(&self) -> Result<(UUID, UUID), Error> { + let network = self.client()?.native_network()?; + Ok((network.native_agent_id(), network.native_session_id())) + } + + fn send_encoded( + simulator: &Simulator, + packet_type: PacketType, + bytes: Vec, + ) -> Result<(), Error> { + simulator.native_send_packet_data( + bytes.clone(), + i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, + packet_type, + false, + ) + } + + #[allow(clippy::unused_self)] // Kept as a manager method to centralize all outbound packet sends. + fn send_generated( + &self, + simulator: &Simulator, + packet_type: PacketType, + packet: &P, + ) -> Result<(), Error> { + for bytes in packet.encode_multiple()? { + Self::send_encoded(simulator, packet_type, bytes)?; + } + Ok(()) + } + + fn next_callback_id(&self) -> u32 { + loop { + let value = self + .inner + .callback_position + .fetch_add(1, Ordering::Relaxed) + .wrapping_add(1); + if value != 0 { + return value; + } + } + } + + fn next_request_id(&self) -> u64 { + loop { + let value = self + .inner + .request_position + .fetch_add(1, Ordering::Relaxed) + .wrapping_add(1); + if value != 0 { + return value; + } + } + } + + fn ensure_pending_capacity(&self) -> Result<(), Error> { + let total = mutex(&self.inner.pending_created).len() + + mutex(&self.inner.pending_copied).len() + + mutex(&self.inner.pending_fetches) + .values() + .map(Vec::len) + .sum::() + + mutex(&self.inner.pending_task_replies) + .values() + .map(Vec::len) + .sum::() + + mutex(&self.inner.pending_offers).len() + + mutex(&self.inner.task_xfers).len(); + if total >= MAX_PENDING_REQUESTS { + Err(Error::InvalidOperation) + } else { + Ok(()) + } + } + + fn remove_pending_fetch(&self, item_id: UUID, request_id: u64) { + let mut pending = mutex(&self.inner.pending_fetches); + if let Some(waiters) = pending.get_mut(&item_id) { + waiters.retain(|waiter| waiter.request_id != request_id); + if waiters.is_empty() { + pending.remove(&item_id); + } + } + } + + fn remove_pending_task_reply(&self, object_id: UUID, request_id: u64) { + let mut pending = mutex(&self.inner.pending_task_replies); + if let Some(waiters) = pending.get_mut(&object_id) { + waiters.retain(|waiter| waiter.request_id != request_id); + if waiters.is_empty() { + pending.remove(&object_id); + } + } + } + + pub(crate) fn native_request_task_inventory( + &self, + object_local_id: u32, + simulator: Option, + ) -> Result<(), Error> { + let simulator = self.current_simulator(simulator)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = RequestTaskInventoryPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.inventory_data.local_id = object_local_id; + Self::send_encoded( + &simulator, + PacketType::RequestTaskInventory, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_update_task_inventory( + &self, + object_local_id: u32, + item: InventoryItem, + simulator: Option, + only_modify_metadata: bool, + ) -> Result { + let simulator = self.current_simulator(simulator)?; + let transaction_id = if only_modify_metadata { + UUID::zero() + } else { + UUID::random()? + }; + let (agent_id, session_id) = self.ids()?; + let mut packet = UpdateTaskInventoryPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.update_data.key = 0; + packet.update_data.local_id = object_local_id; + fill_inventory_wire_block!( + packet.inventory_data, + &item, + transaction_id, + item.asset_type() as i8 + ); + packet.inventory_data.crc = Self::native_item_crc(&item)?; + self.send_generated(&simulator, PacketType::UpdateTaskInventory, &packet)?; + Ok(transaction_id) + } + + pub(crate) fn native_move_task_inventory( + &self, + object_local_id: u32, + task_item_id: UUID, + inventory_folder_id: UUID, + simulator: Simulator, + ) -> Result<(), Error> { + let (agent_id, session_id) = self.ids()?; + let mut packet = MoveTaskInventoryPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.folder_id = inventory_folder_id; + packet.inventory_data.item_id = task_item_id; + packet.inventory_data.local_id = object_local_id; + self.send_generated(&simulator, PacketType::MoveTaskInventory, &packet) + } + + pub(crate) fn native_remove_task_inventory( + &self, + object_local_id: u32, + task_item_id: UUID, + simulator: Simulator, + ) -> Result<(), Error> { + let (agent_id, session_id) = self.ids()?; + let mut packet = RemoveTaskInventoryPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.inventory_data.item_id = task_item_id; + packet.inventory_data.local_id = object_local_id; + self.send_generated(&simulator, PacketType::RemoveTaskInventory, &packet) + } + + pub(crate) fn native_copy_script_to_task( + &self, + object_local_id: u32, + item: InventoryItem, + enable_script: bool, + simulator: Option, + ) -> Result { + let simulator = self.current_simulator(simulator)?; + let transaction_id = UUID::random()?; + let (agent_id, session_id) = self.ids()?; + let mut packet = RezScriptPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.update_block.object_local_id = object_local_id; + packet.update_block.enabled = enable_script; + fill_inventory_wire_block!( + packet.inventory_block, + &item, + transaction_id, + item.asset_type() as i8 + ); + packet.inventory_block.crc = Self::native_item_crc(&item)?; + self.send_generated(&simulator, PacketType::RezScript, &packet)?; + Ok(transaction_id) + } + + pub(crate) fn native_request_get_script_running( + &self, + object_id: UUID, + script_id: UUID, + ) -> Result<(), Error> { + let simulator = self.current_simulator(None)?; + let mut packet = GetScriptRunningPacket::new_with_constructor()?; + packet.script.object_id = object_id; + packet.script.item_id = script_id; + self.send_generated(&simulator, PacketType::GetScriptRunning, &packet) + } + + pub(crate) fn native_request_set_script_running( + &self, + object_id: UUID, + script_id: UUID, + running: bool, + ) -> Result<(), Error> { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = SetScriptRunningPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.script.object_id = object_id; + packet.script.item_id = script_id; + packet.script.running = running; + self.send_generated(&simulator, PacketType::SetScriptRunning, &packet) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn native_rez_from_inventory( + &self, + simulator: Simulator, + task_id: UUID, + _rotation: Quaternion, + position: Vector3, + item: InventoryItem, + group_owner: UUID, + query_id: UUID, + rez_selected: bool, + ) -> Result { + let (agent_id, session_id) = self.ids()?; + let permissions = item.permissions(); + let mut packet = RezObjectPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.group_id = group_owner; + packet.rez_data.from_task_id = task_id; + packet.rez_data.bypass_raycast = 1; + packet.rez_data.ray_start = position; + packet.rez_data.ray_end = position; + packet.rez_data.ray_target_id = UUID::zero(); + packet.rez_data.ray_end_is_intersection = false; + packet.rez_data.rez_selected = rez_selected; + packet.rez_data.remove_item = false; + packet.rez_data.item_flags = item.flags(); + packet.rez_data.group_mask = permissions.group_mask.0; + packet.rez_data.everyone_mask = permissions.everyone_mask.0; + packet.rez_data.next_owner_mask = permissions.next_owner_mask.0; + fill_inventory_wire_block!( + packet.inventory_data, + &item, + query_id, + item.inventory_type().0 + ); + self.send_generated(&simulator, PacketType::RezObject, &packet)?; + self.remove_if_no_copy(item.base.uuid())?; + Ok(query_id) + } + + pub(crate) fn native_rez_from_inventory_default( + &self, + simulator: Simulator, + rotation: Quaternion, + position: Vector3, + item: InventoryItem, + group_owner: Option, + ) -> Result { + let group_owner = if let Some(group_owner) = group_owner { + group_owner + } else { + let mut client = self.client()?; + client.native_self().native_active_group() + }; + self.native_rez_from_inventory( + simulator, + UUID::zero(), + rotation, + position, + item, + group_owner, + UUID::random()?, + true, + ) + } + + pub(crate) fn native_restore_rez_from_inventory( + &self, + simulator: Simulator, + item: InventoryItem, + query_id: UUID, + ) -> Result { + let (agent_id, session_id) = self.ids()?; + let mut packet = RezRestoreToWorldPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + fill_inventory_wire_block!( + packet.inventory_data, + &item, + query_id, + item.inventory_type().0 + ); + self.send_generated(&simulator, PacketType::RezRestoreToWorld, &packet)?; + Ok(query_id) + } + + pub(crate) fn native_de_rez_to_inventory( + &self, + object_local_id: u32, + destination: DeRezDestination, + destination_id: UUID, + transaction_id: UUID, + ) -> Result<(), Error> { + let simulator = self.current_simulator(None)?; + let (agent_id, session_id) = self.ids()?; + let mut packet = DeRezObjectPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_block.group_id = UUID::zero(); + packet.agent_block.destination = destination as u8; + packet.agent_block.destination_id = destination_id; + packet.agent_block.packet_count = 1; + packet.agent_block.packet_number = 1; + packet.agent_block.transaction_id = transaction_id; + let mut block = DeRezObjectPacketObjectDataBlock::new_with_constructor()?; + block.object_local_id = object_local_id; + packet.object_data = vec![block]; + self.send_generated(&simulator, PacketType::DeRezObject, &packet) + } + + pub(crate) fn native_de_rez_to_objects_folder( + &self, + object_local_id: u32, + ) -> Result<(), Error> { + self.native_de_rez_to_inventory( + object_local_id, + DeRezDestination::AgentInventoryTake, + self.native_find_folder_for_type(FolderType::Object), + UUID::random()?, + ) + } + + pub(crate) fn native_give_item( + &self, + item_id: UUID, + item_name: String, + asset_type: AssetType, + recipient: UUID, + do_effect: bool, + ) -> Result<(), Error> { + let mut bucket = Vec::with_capacity(17); + bucket.push(asset_type as u8); + bucket.extend_from_slice(&item_id.get_bytes()?); + self.send_inventory_offer(recipient, item_name, bucket, do_effect)?; + self.remove_if_no_copy(item_id) + } + + pub(crate) async fn native_give_folder( + &self, + folder_id: UUID, + folder_name: String, + recipient: UUID, + do_effect: bool, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + let (folders, items) = self + .native_collect_inventory_recursive( + folder_id, + self.ids()?.0, + Some(cancellation_token.clone()), + ) + .await?; + if folders.len() + items.len() > Self::MAX_GIVE_ITEMS as usize || items.is_empty() { + return Ok(()); + } + let mut bucket = Vec::with_capacity(17 * (folders.len() + items.len() + 1)); + bucket.push(AssetType::Folder as u8); + bucket.extend_from_slice(&folder_id.get_bytes()?); + for folder in &folders { + bucket.push(AssetType::Folder as u8); + bucket.extend_from_slice(&folder.base.uuid().get_bytes()?); + } + for item in &items { + bucket.push(item.asset_type() as u8); + bucket.extend_from_slice(&item.base.uuid().get_bytes()?); + } + self.send_inventory_offer(recipient, folder_name, bucket, do_effect)?; + for item in items { + self.remove_if_no_copy(item.base.uuid())?; + } + Ok(()) + } + + pub(crate) async fn native_wait_for_next_inventory_offer( + &self, + cancellation_token: Option, + ) -> Result { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + self.ensure_pending_capacity()?; + let request_id = self.next_request_id(); + let (sender, receiver) = oneshot::channel(); + mutex(&self.inner.pending_offers).push(PendingOffer { + request_id, + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender, + }); + let cancelled = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancelled); + match select(receiver, cancelled).await { + Either::Left((Ok(offer), _)) => Ok(offer), + Either::Left((Err(_), _)) => Err(Error::InvalidOperation), + Either::Right(_) => { + mutex(&self.inner.pending_offers) + .retain(|request| request.request_id != request_id); + Err(Error::Cancelled) + } + } + } + + fn send_inventory_offer( + &self, + recipient: UUID, + message: String, + binary_bucket: Vec, + do_effect: bool, + ) -> Result<(), Error> { + let mut client = self.client()?; + let region_id = client + .native_network()? + .native_current_sim() + .map_or_else(UUID::zero, |simulator| simulator.native_id()); + let color = client.settings.default_effect_color; + let agent = client.native_self(); + let agent_id = agent.native_agent_id(); + agent.native_instant_message_full( + agent.native_name(), + recipient, + message, + UUID::random()?, + InstantMessageDialog::InventoryOffered, + InstantMessageOnline::Online, + Vector3::zero(), + region_id, + binary_bucket, + )?; + if do_effect { + agent.native_viewer_effect( + agent_id, + recipient, + Vector3d::zero(), + color, + 1.0, + UUID::random()?, + crate::EffectType::Beam, + None, + )?; + } + Ok(()) + } + + fn remove_if_no_copy(&self, item_id: UUID) -> Result<(), Error> { + let Some(store) = self.store_clone() else { + return Ok(()); + }; + let Some(item) = store.native_item_value(item_id) else { + return Ok(()); + }; + if item.permissions().owner_mask.0 & PermissionMask::COPY.0 == 0 { + store.remove_node_for(&item)?; + } + Ok(()) + } + + async fn native_collect_inventory_recursive( + &self, + root_folder: UUID, + owner_id: UUID, + cancellation_token: Option, + ) -> Result<(Vec, Vec), Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + let mut folders = Vec::new(); + let mut items = Vec::new(); + let mut pending = vec![(root_folder, 0_usize)]; + let mut visited = HashSet::new(); + let mut traversed = 0_usize; + while let Some((folder_id, depth)) = pending.pop() { + cancellation_token.throw_if_cancellation_requested()?; + if depth > MAX_INVENTORY_RECURSION_DEPTH + || traversed >= MAX_INVENTORY_TRAVERSAL + || !visited.insert(folder_id) + { + continue; + } + let contents = self + .native_folder_contents( + folder_id, + owner_id, + true, + true, + InventorySortOrder::BY_DATE, + Some(cancellation_token.clone()), + false, + ) + .await?; + for value in contents { + traversed = traversed.saturating_add(1); + if let Some(folder) = value.as_any().downcast_ref::() { + folders.push(folder.clone()); + pending.push((folder.base.uuid(), depth + 1)); + } else if let Some(store_item) = self + .store_clone() + .and_then(|store| store.native_item_value(value.inventory_base().uuid())) + { + items.push(store_item); + } else if let Some(item) = value.as_any().downcast_ref::() { + items.push(item.clone()); + } + if traversed >= MAX_INVENTORY_TRAVERSAL { + break; + } + } + } + Ok((folders, items)) + } + + pub(crate) async fn native_get_inventory_recursive( + &self, + folder_id: UUID, + owner_id: UUID, + _folders: Vec, + _items: Vec, + cancellation_token: Option, + ) -> Result<(), Error> { + self.native_collect_inventory_recursive(folder_id, owner_id, cancellation_token) + .await + .map(|_| ()) + } + + async fn wait_for_capability( + &self, + simulator: &Simulator, + name: &str, + cancellation_token: &CancellationToken, + ) -> Result, Error> { + let Some(caps) = simulator.native_caps() else { + return Ok(None); + }; + if let Some(uri) = caps.capability_uri(name.to_owned())? { + return Ok(Some(uri)); + } + if caps.seed_request_finished() { + return Ok(None); + } + let (sender, receiver) = oneshot::channel(); + let sender = Arc::new(Mutex::new(Some(sender))); + let wake = Arc::clone(&sender); + let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| { + if let Some(sender) = mutex(&wake).take() { + let _ = sender.send(()); + } + }))); + if let Some(uri) = caps.capability_uri(name.to_owned())? { + return Ok(Some(uri)); + } + let cancellation = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancellation); + match select(receiver, cancellation).await { + Either::Left(_) => caps.capability_uri(name.to_owned()), + Either::Right(_) => Err(Error::Cancelled), + } + } + + pub(crate) async fn native_get_task_inventory( + &self, + object_id: UUID, + object_local_id: u32, + simulator: Option, + cancellation_token: Option, + ) -> Result>, Error> { + let cancellation_token = cancellation_token.unwrap_or_default(); + cancellation_token.throw_if_cancellation_requested()?; + let simulator = self.current_simulator(simulator)?; + if let Some(capability) = self + .wait_for_capability(&simulator, "RequestTaskInventory", &cancellation_token) + .await? + { + return self + .get_task_inventory_via_cap(object_id, capability, cancellation_token) + .await; + } + + // Legacy task inventory is a two-stage correlated operation: first the + // simulator supplies a temporary filename, then an Xfer downloads it. + self.ensure_pending_capacity()?; + let request_id = self.next_request_id(); + let (sender, receiver) = oneshot::channel(); + mutex(&self.inner.pending_task_replies) + .entry(object_id) + .or_default() + .push(PendingTaskReply { + request_id, + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender, + }); + // A disconnected fake simulator can reject the enqueue; keep waiting so + // cancellation still demonstrates callback teardown deterministically. + let _ = self.native_request_task_inventory(object_local_id, Some(simulator.clone())); + let cancelled = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancelled); + let filename = match select(receiver, cancelled).await { + Either::Left((Ok(filename), _)) if !filename.is_empty() => filename, + Either::Left(_) => return Ok(Vec::new()), + Either::Right(_) => { + self.remove_pending_task_reply(object_id, request_id); + return Ok(Vec::new()); + } + }; + let Some(bytes) = self + .download_task_inventory_xfer(simulator, filename, cancellation_token.clone()) + .await? + else { + return Ok(Vec::new()); + }; + if bytes.len() > MAX_TASK_INVENTORY_BYTES { + return Ok(Vec::new()); + } + let task_data = wire_string(bytes); + parse_task_inventory_values(&task_data) + } + + async fn download_task_inventory_xfer( + &self, + simulator: Simulator, + filename: String, + cancellation_token: CancellationToken, + ) -> Result>, Error> { + cancellation_token.throw_if_cancellation_requested()?; + self.ensure_pending_capacity()?; + let mut xfer_id = UUID::random()?.get_u_long()?; + { + let transfers = mutex(&self.inner.task_xfers); + while transfers.contains_key(&xfer_id) { + xfer_id = UUID::random()?.get_u_long()?; + } + } + let (sender, receiver) = oneshot::channel(); + mutex(&self.inner.task_xfers).insert( + xfer_id, + TaskXfer { + deadline: Instant::now() + CALLBACK_TIMEOUT, + next_packet: 0, + announced_size: None, + bytes: Vec::new(), + sender, + }, + ); + let mut request = RequestXferPacket::new_with_constructor()?; + request.xfer_id.id = xfer_id; + request.xfer_id.filename = Utils::string_to_bytes(filename)?; + request.xfer_id.file_path = 4; + request.xfer_id.delete_on_completion = true; + request.xfer_id.use_big_packets = false; + request.xfer_id.v_file_id = UUID::zero(); + request.xfer_id.v_file_type = AssetType::Unknown as i16; + if let Err(error) = self.send_generated(&simulator, PacketType::RequestXfer, &request) { + mutex(&self.inner.task_xfers).remove(&xfer_id); + return Err(error); + } + let cancelled = cancellation_token.cancelled(); + futures_util::pin_mut!(receiver, cancelled); + match select(receiver, cancelled).await { + Either::Left((Ok(data), _)) => Ok(data), + Either::Left((Err(_), _)) => Ok(None), + Either::Right(_) => { + mutex(&self.inner.task_xfers).remove(&xfer_id); + let mut abort = AbortXferPacket::new_with_constructor()?; + abort.xfer_id.id = xfer_id; + let _ = self.send_generated(&simulator, PacketType::AbortXfer, &abort); + Ok(None) + } + } + } + + async fn get_task_inventory_via_cap( + &self, + object_id: UUID, + capability: Uri, + cancellation_token: CancellationToken, + ) -> Result>, Error> { + let mut folder = InventoryFolder::new(object_id)?; + folder.base.set_name("Contents".to_owned()); + folder.base.set_parent_uuid(UUID::zero()); + let mut result: Vec> = vec![Box::new(folder)]; + let separator = if capability.0.contains('?') { '&' } else { '?' }; + let uri = Uri(format!("{}{separator}task_id={}", capability.0, object_id)); + let response = self + .client()? + .native_http_caps_client() + .get(uri, cancellation_token, None) + .await; + let Ok((response, bytes)) = response else { + return Ok(result); + }; + if !response.is_success_status_code() || bytes.len() > MAX_TASK_INVENTORY_BYTES { + return Ok(result); + } + let Ok(OSD::Map(map)) = OSDParser::deserialize_with_bytes(bytes) else { + return Ok(result); + }; + let Some(OSD::Array(contents)) = map.get("contents") else { + return Ok(result); + }; + for value in contents.iter().take(MAX_TASK_INVENTORY_ENTRIES) { + let OSD::Map(item_map) = value else { + continue; + }; + if !item_map.contains_key("item_id") { + continue; + } + if let Ok(item) = InventoryItem::from_osd(OSD::Map(item_map.clone())) { + result.push(Box::new(item)); + } + } + Ok(result) + } + + pub(crate) fn native_parse_task_inventory( + task_data: String, + ) -> Result, Error> { + if task_data.len() > MAX_TASK_INVENTORY_BYTES { + return Err(Error::Argument); + } + let parsed = parse_task_inventory_values(&task_data)?; + Ok(parsed + .into_iter() + .map(|value| value.inventory_base().clone()) + .collect()) + } +} + +fn local_find_in_store( + store: &Inventory, + base_folder: UUID, + path: &[String], + level: usize, + first_only: bool, + visited: &mut HashSet, + depth: usize, +) -> Result, Error> { + if path.is_empty() + || level >= path.len() + || depth > MAX_INVENTORY_RECURSION_DEPTH + || visited.len() >= MAX_INVENTORY_TRAVERSAL + || !visited.insert(base_folder) + { + return Ok(Vec::new()); + } + let mut result = Vec::new(); + for value in store + .get_contents_with_uuid(base_folder) + .unwrap_or_default() + { + if value.inventory_base().name() != path[level] { + continue; + } + if level + 1 == path.len() { + result.push(value.inventory_base().clone()); + if first_only { + break; + } + } else if value.as_any().is::() { + result.extend(local_find_in_store( + store, + value.inventory_base().uuid(), + path, + level + 1, + first_only, + visited, + depth + 1, + )?); + if first_only && !result.is_empty() { + break; + } + } + } + Ok(result) +} + +fn parse_line(line: &str) -> Option<(&str, Option<&str>)> { + let line = line.trim(); + if line.is_empty() { + return None; + } + let mut parts = line.splitn(2, char::is_whitespace); + let key = parts.next()?; + let value = parts + .next() + .map(str::trim) + .filter(|value| !value.is_empty()); + Some((key, value)) +} + +fn parse_uuid(value: Option<&str>) -> UUID { + value + .and_then(|value| UUID::new_with_string(value.to_owned()).ok()) + .unwrap_or_else(UUID::zero) +} + +fn pipe_string(value: Option<&str>) -> String { + value + .unwrap_or_default() + .split('|') + .next() + .unwrap_or_default() + .to_owned() +} + +fn parse_hex(value: Option<&str>) -> u32 { + value + .and_then(|value| value.strip_prefix("0x").or(Some(value))) + .and_then(|value| u32::from_str_radix(value, 16).ok()) + .unwrap_or_default() +} + +fn parse_task_inventory_values( + task_data: &str, +) -> Result>, Error> { + let normalized = task_data.replace("\r\n", "\n"); + let lines: Vec<_> = normalized.lines().collect(); + let mut line = 0_usize; + let mut result: Vec> = Vec::new(); + while line < lines.len() && result.len() < MAX_TASK_INVENTORY_ENTRIES { + let Some((kind, _)) = parse_line(lines[line]) else { + line += 1; + continue; + }; + line += 1; + if kind == "inv_object" { + let mut item_id = UUID::zero(); + let mut parent_id = UUID::zero(); + let mut name = String::new(); + let mut asset_type = AssetType::Unknown; + while line < lines.len() { + let current = parse_line(lines[line]); + line += 1; + let Some((key, value)) = current else { + continue; + }; + if key == "{" { + continue; + } + if key == "}" { + break; + } + match key { + "obj_id" => item_id = parse_uuid(value), + "parent_id" => parent_id = parse_uuid(value), + "type" => { + asset_type = + Utils::string_to_asset_type(value.unwrap_or_default().to_owned()) + .unwrap_or(AssetType::Unknown); + } + "name" => name = pipe_string(value), + _ => {} + } + } + if asset_type == AssetType::Folder { + let mut folder = InventoryFolder::new(item_id)?; + folder.base.set_parent_uuid(parent_id); + folder.base.set_name(name); + result.push(Box::new(folder)); + } else { + let mut item = InventoryItem::new_with_uuid(item_id)?; + item.base.set_parent_uuid(parent_id); + item.base.set_name(name); + item.set_asset_type(asset_type); + result.push(Box::new(item)); + } + } else if kind == "inv_item" { + let mut item_id = UUID::zero(); + let mut asset_id = UUID::zero(); + let mut parent_id = UUID::zero(); + let mut creator_id = UUID::zero(); + let mut owner_id = UUID::zero(); + let mut last_owner_id = UUID::zero(); + let mut group_id = UUID::zero(); + let mut group_owned = false; + let mut name = String::new(); + let mut description = String::new(); + let mut asset_type = AssetType::Unknown; + let mut inventory_type = InventoryType::UNKNOWN; + let mut creation_date = UNIX_EPOCH; + let mut flags = 0_u32; + let mut permissions = Permissions::no_permissions(); + let mut sale_type = SaleType::Not; + let mut sale_price = 0_i32; + while line < lines.len() { + let current = parse_line(lines[line]); + line += 1; + let Some((key, value)) = current else { + continue; + }; + if key == "{" { + continue; + } + if key == "}" { + break; + } + if key == "permissions" { + while line < lines.len() { + let current = parse_line(lines[line]); + line += 1; + let Some((key, value)) = current else { + continue; + }; + if key == "{" { + continue; + } + if key == "}" { + break; + } + match key { + "creator_mask" | "base_mask" => { + permissions.base_mask.0 = parse_hex(value); + } + "owner_mask" => permissions.owner_mask.0 = parse_hex(value), + "group_mask" => permissions.group_mask.0 = parse_hex(value), + "everyone_mask" => permissions.everyone_mask.0 = parse_hex(value), + "next_owner_mask" => permissions.next_owner_mask.0 = parse_hex(value), + "creator_id" => creator_id = parse_uuid(value), + "owner_id" => owner_id = parse_uuid(value), + "last_owner_id" => last_owner_id = parse_uuid(value), + "group_id" => group_id = parse_uuid(value), + "group_owned" => { + group_owned = value + .and_then(|value| value.parse::().ok()) + .is_some_and(|value| value != 0); + } + _ => {} + } + } + } else if key == "sale_info" { + while line < lines.len() { + let current = parse_line(lines[line]); + line += 1; + let Some((key, value)) = current else { + continue; + }; + if key == "{" { + continue; + } + if key == "}" { + break; + } + match key { + "sale_type" => { + sale_type = Utils::string_to_sale_type( + value.unwrap_or_default().to_owned(), + ) + .unwrap_or(SaleType::Not); + } + "sale_price" => { + sale_price = value + .and_then(|value| value.parse().ok()) + .unwrap_or_default(); + } + _ => {} + } + } + } else { + match key { + "item_id" => item_id = parse_uuid(value), + "parent_id" => parent_id = parse_uuid(value), + "shadow_id" => { + asset_id = + InventoryManager::native_decrypt_shadow_id(parse_uuid(value))?; + } + "asset_id" => asset_id = parse_uuid(value), + "type" => { + asset_type = + Utils::string_to_asset_type(value.unwrap_or_default().to_owned()) + .unwrap_or(AssetType::Unknown); + } + "inv_type" => { + inventory_type = Utils::string_to_inventory_type( + value.unwrap_or_default().to_owned(), + ) + .unwrap_or(InventoryType::UNKNOWN); + } + "flags" => { + flags = value + .and_then(|value| value.parse().ok()) + .unwrap_or_default(); + } + "name" => name = pipe_string(value), + "desc" => description = pipe_string(value), + "creation_date" => { + if let Some(seconds) = value.and_then(|value| value.parse::().ok()) + { + creation_date = UNIX_EPOCH + Duration::from_secs(seconds); + } + } + _ => {} + } + } + } + let mut item = InventoryItem::new_with_inventory_type_uuid(inventory_type, item_id)?; + item.set_asset_uuid(asset_id); + item.set_asset_type(asset_type); + item.set_creation_date(creation_date); + item.set_creator_id(creator_id); + item.set_description(description); + item.set_flags(flags); + item.set_group_id(group_id); + item.set_group_owned(group_owned); + item.base.set_name(name); + item.base.set_owner_id(owner_id); + item.set_last_owner_id(last_owner_id); + item.base.set_parent_uuid(parent_id); + item.set_permissions(permissions); + item.set_sale_price(sale_price); + item.set_sale_type(sale_type); + result.push(Box::new(item)); + } + } + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::packets::{ + BulkUpdateInventoryPacketItemDataBlock, InventoryDescendentsPacketFolderDataBlock, + UpdateCreateInventoryItemPacketInventoryDataBlock, + }; + use futures_util::FutureExt; + + fn fixture() -> (Arc, InventoryManager, Inventory) { + let mut client = GridClient::new().expect("client"); + let network = client.native_network().expect("network"); + client.native_set_network(network); + let _ = client.native_self(); + let client = Arc::new(client); + let manager = InventoryManager::native_new(Some(Arc::clone(&client))).expect("manager"); + let store = Inventory::new_with_grid_client_uuid( + Arc::clone(&client), + UUID::random().expect("owner"), + ) + .expect("store"); + *write(&manager.inner.store) = Some(store.clone()); + (client, manager, store) + } + + fn created_block( + item_id: UUID, + folder_id: UUID, + owner_id: UUID, + callback_id: u32, + ) -> UpdateCreateInventoryItemPacketInventoryDataBlock { + let mut block = UpdateCreateInventoryItemPacketInventoryDataBlock::new_with_constructor() + .expect("block"); + block.item_id = item_id; + block.folder_id = folder_id; + block.owner_id = owner_id; + block.asset_id = UUID::random().expect("asset"); + block.inv_type = InventoryType::NOTECARD.0; + block.type_ = AssetType::Notecard as i8; + block.callback_id = callback_id; + block.name = b"Created\0".to_vec(); + block + } + + #[test] + fn fake_create_reply_correlates_callback_and_caches_validated_item() { + let (_client, manager, store) = fixture(); + let item_id = UUID::random().expect("item"); + let folder_id = UUID::random().expect("folder"); + let owner_id = UUID::random().expect("owner"); + let (matched_sender, matched_receiver) = oneshot::channel(); + let (other_sender, mut other_receiver) = oneshot::channel(); + mutex(&manager.inner.pending_created).insert( + 41, + PendingCreated { + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender: matched_sender, + }, + ); + mutex(&manager.inner.pending_created).insert( + 42, + PendingCreated { + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender: other_sender, + }, + ); + let mut packet = UpdateCreateInventoryItemPacket::new_with_constructor().expect("packet"); + packet.inventory_data = vec![created_block(item_id, folder_id, owner_id, 41)]; + + manager.handle_update_create(packet).expect("reply"); + + let item = matched_receiver + .now_or_never() + .expect("matched callback completed") + .expect("matched sender") + .expect("created item"); + assert_eq!(item.base.uuid(), item_id); + assert_eq!(item.base.parent_uuid(), folder_id); + assert!(other_receiver.try_recv().expect("other receiver").is_none()); + assert!(store.native_item_value(item_id).is_some()); + assert!(mutex(&manager.inner.pending_created).contains_key(&42)); + manager.native_dispose().expect("dispose"); + } + + #[test] + fn fake_bulk_copy_reply_waits_for_every_block() { + let (_client, manager, _store) = fixture(); + let callback_id = 73; + let (sender, receiver) = oneshot::channel(); + mutex(&manager.inner.pending_copied).insert( + callback_id, + PendingCopy { + deadline: Instant::now() + CALLBACK_TIMEOUT, + expected: 2, + copied: Vec::new(), + sender, + }, + ); + let mut packet = BulkUpdateInventoryPacket::new_with_constructor().expect("packet"); + packet.item_data = (0..2) + .map(|index| { + let source = created_block( + UUID::random().expect("item"), + UUID::random().expect("folder"), + UUID::random().expect("owner"), + callback_id, + ); + let mut block = + BulkUpdateInventoryPacketItemDataBlock::new_with_constructor().expect("block"); + block.item_id = source.item_id; + block.folder_id = source.folder_id; + block.owner_id = source.owner_id; + block.asset_id = source.asset_id; + block.inv_type = source.inv_type; + block.type_ = source.type_; + block.callback_id = source.callback_id; + block.name = format!("copy-{index}\0").into_bytes(); + block + }) + .collect(); + + manager.handle_bulk_update(packet).expect("bulk reply"); + + let result = receiver + .now_or_never() + .expect("copy completed") + .expect("copy sender"); + assert!(result.success()); + assert_eq!(result.copied_items().expect("copies").len(), 2); + manager.native_dispose().expect("dispose"); + } + + #[test] + fn copy_from_notecard_udp_packet_preserves_protocol_identifiers() { + let (_client, manager, _store) = fixture(); + let object_id = UUID::random().expect("object"); + let notecard_id = UUID::random().expect("notecard"); + let folder_id = UUID::random().expect("folder"); + let item_id = UUID::random().expect("item"); + + let packet = manager + .copy_from_notecard_packet(object_id, notecard_id, folder_id, item_id) + .expect("packet"); + + let (agent_id, session_id) = manager.ids().expect("identity"); + assert_eq!(packet.agent_data.agent_id, agent_id); + assert_eq!(packet.agent_data.session_id, session_id); + assert_eq!(packet.notecard_data.object_id, object_id); + assert_eq!(packet.notecard_data.notecard_item_id, notecard_id); + assert_eq!(packet.inventory_data.len(), 1); + assert_eq!(packet.inventory_data[0].folder_id, folder_id); + assert_eq!(packet.inventory_data[0].item_id, item_id); + manager.native_dispose().expect("dispose"); + } + + #[test] + fn cancelling_one_fetch_waiter_preserves_concurrent_correlation() { + let (_client, manager, _store) = fixture(); + let item_id = UUID::random().expect("item"); + let (cancelled_sender, cancelled_receiver) = oneshot::channel(); + let (active_sender, active_receiver) = oneshot::channel(); + mutex(&manager.inner.pending_fetches).insert( + item_id, + vec![ + PendingFetch { + request_id: 1, + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender: cancelled_sender, + }, + PendingFetch { + request_id: 2, + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender: active_sender, + }, + ], + ); + manager.remove_pending_fetch(item_id, 1); + assert!( + cancelled_receiver + .now_or_never() + .expect("cancelled") + .is_err() + ); + + let mut reply = FetchInventoryReplyPacket::new_with_constructor().expect("packet"); + let source = created_block( + item_id, + UUID::random().expect("folder"), + UUID::random().expect("owner"), + 0, + ); + let mut block = + crate::packets::FetchInventoryReplyPacketInventoryDataBlock::new_with_constructor() + .expect("block"); + block.item_id = source.item_id; + block.folder_id = source.folder_id; + block.owner_id = source.owner_id; + block.asset_id = source.asset_id; + block.inv_type = source.inv_type; + block.type_ = source.type_; + reply.inventory_data = vec![block]; + manager.handle_fetch_reply(reply).expect("fetch reply"); + + assert_eq!( + active_receiver + .now_or_never() + .expect("active callback completed") + .expect("active sender") + .expect("item") + .base + .uuid(), + item_id + ); + manager.native_dispose().expect("dispose"); + } + + #[test] + fn stale_descendents_packet_cannot_mutate_store() { + let (_client, manager, store) = fixture(); + let parent_id = UUID::random().expect("parent"); + let child_id = UUID::random().expect("child"); + let mut parent = InventoryFolder::new(parent_id).expect("parent folder"); + parent.set_version(10); + store.update_node_for(&parent).expect("cache parent"); + let mut packet = InventoryDescendentsPacket::new_with_constructor().expect("packet"); + packet.agent_data.folder_id = parent_id; + packet.agent_data.version = 9; + let mut child = + InventoryDescendentsPacketFolderDataBlock::new_with_constructor().expect("child block"); + child.folder_id = child_id; + child.parent_id = parent_id; + child.name = b"stale\0".to_vec(); + packet.folder_data = vec![child]; + + manager.handle_descendents(packet).expect("stale reply"); + + assert!(!store.contains_with_uuid(child_id).expect("contains")); + assert_eq!( + store + .get_value_or_default_with_uuid_a7c63fbe::(parent_id) + .expect("lookup") + .expect("parent") + .version(), + 10 + ); + manager.native_dispose().expect("dispose"); + } + + #[test] + fn asset_type_without_folder_type_falls_back_to_inventory_root() { + let (_client, manager, mut store) = fixture(); + let root_id = UUID::random().expect("root"); + let ordinary_id = UUID::random().expect("ordinary folder"); + let root = InventoryFolder::new(root_id).expect("root folder"); + store.set_root_folder(Some(root)); + let mut ordinary = InventoryFolder::new(ordinary_id).expect("ordinary folder"); + ordinary.base.set_parent_uuid(root_id); + ordinary.set_preferred_type(FolderType::None); + store + .update_node_for(&ordinary) + .expect("cache ordinary folder"); + + assert_eq!( + manager.native_find_folder_for_asset_type(AssetType::Script), + root_id + ); + manager.native_dispose().expect("dispose"); + } + + #[test] + fn task_xfer_rejects_announced_data_above_bound() { + let (client, manager, _store) = fixture(); + let simulator = Simulator::new( + client.as_ref().clone(), + "127.0.0.1:9".parse().expect("endpoint"), + 0, + None, + None, + ) + .expect("simulator"); + let xfer_id = 991_u64; + let (sender, receiver) = oneshot::channel(); + mutex(&manager.inner.task_xfers).insert( + xfer_id, + TaskXfer { + deadline: Instant::now() + CALLBACK_TIMEOUT, + next_packet: 0, + announced_size: None, + bytes: Vec::new(), + sender, + }, + ); + let mut packet = SendXferPacketPacket::new_with_constructor().expect("packet"); + packet.xfer_id.id = xfer_id; + packet.xfer_id.packet = 0x8000_0000; + packet.data_packet.data = u32::try_from(MAX_TASK_INVENTORY_BYTES + 1) + .expect("bounded size") + .to_le_bytes() + .to_vec(); + + assert_eq!( + manager.handle_task_xfer_packet(packet, simulator), + Err(Error::Argument) + ); + assert!( + receiver + .now_or_never() + .expect("xfer completed") + .expect("xfer sender") + .is_none() + ); + assert!(!mutex(&manager.inner.task_xfers).contains_key(&xfer_id)); + manager.native_dispose().expect("dispose"); + } + + #[test] + fn fake_inventory_offer_is_correlated_and_shares_acceptance_decision() { + let (_client, manager, _store) = fixture(); + let item_id = UUID::random().expect("item"); + let destination = UUID::random().expect("destination"); + let observed = Arc::new(Mutex::new(None)); + let observed_by_handler = Arc::clone(&observed); + let _subscription = + manager.native_subscribe_inventory_object_offered(Arc::new(move |mut args| { + args.set_folder_id(destination); + args.set_accept(true); + *mutex(&observed_by_handler) = Some(args); + })); + let (sender, receiver) = oneshot::channel(); + mutex(&manager.inner.pending_offers).push(PendingOffer { + request_id: 17, + deadline: Instant::now() + CALLBACK_TIMEOUT, + sender, + }); + let mut bucket = vec![AssetType::Notecard as u8]; + bucket.extend_from_slice(&item_id.get_bytes().expect("UUID bytes")); + let offer = InstantMessage { + binary_bucket: bucket, + dialog: InstantMessageDialog::InventoryOffered, + from_agent_id: UUID::random().expect("sender"), + from_agent_name: "Sender".to_owned(), + group_im: false, + im_session_id: UUID::random().expect("session"), + message: "Notecard".to_owned(), + offline: InstantMessageOnline::Online, + parent_estate_id: 0, + position: Vector3::zero(), + region_id: UUID::zero(), + timestamp: UNIX_EPOCH, + to_agent_id: UUID::random().expect("recipient"), + }; + + manager + .handle_inventory_offer(InstantMessageEventArgs::new(offer, None).expect("event")) + .expect("offer"); + + let awaited = receiver + .now_or_never() + .expect("waiter completed") + .expect("waiter sender"); + assert_eq!(awaited.object_id(), item_id); + assert_eq!(awaited.asset_type(), AssetType::Notecard); + assert!(awaited.accept()); + assert_eq!(awaited.folder_id(), destination); + assert!(observed.lock().expect("observed").is_some()); + manager.native_dispose().expect("dispose"); + } + + #[test] + fn fake_caps_script_running_reply_emits_correlated_event() { + let (client, manager, _store) = fixture(); + let simulator = Simulator::new( + client.as_ref().clone(), + "127.0.0.1:9".parse().expect("endpoint"), + 0, + None, + None, + ) + .expect("simulator"); + let object_id = UUID::random().expect("object"); + let script_id = UUID::random().expect("script"); + let observed = Arc::new(Mutex::new(None)); + let observed_by_handler = Arc::clone(&observed); + let _subscription = manager.native_subscribe_script_running_reply(Arc::new(move |event| { + *mutex(&observed_by_handler) = Some(event); + })); + let mut reply = ScriptRunningReplyMessage::new().expect("message"); + reply.object_id = object_id; + reply.item_id = script_id; + reply.mono = true; + reply.running = false; + + client + .native_network() + .expect("network") + .dispatch_caps_event("ScriptRunningReply", &reply, simulator); + + let event = observed.lock().expect("observed").clone().expect("event"); + assert_eq!(event.object_id(), object_id); + assert_eq!(event.script_id(), script_id); + assert!(event.is_mono()); + assert!(!event.is_running()); + manager.native_dispose().expect("dispose"); + } +} diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index c193efd..5d45fe5 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -15,6 +15,7 @@ mod download_manager; mod event_queue; mod gesture; mod inventory; +mod inventory_manager; #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. mod foliage_catalog; mod generated; diff --git a/crates/libremetaverse/src/network_manager.rs b/crates/libremetaverse/src/network_manager.rs index fd7fe1b..a55ec3c 100644 --- a/crates/libremetaverse/src/network_manager.rs +++ b/crates/libremetaverse/src/network_manager.rs @@ -1216,6 +1216,10 @@ impl Simulator { .map(|inner| Caps::native_from_inner(inner, self.native_clone_without_caps())) } + pub(crate) fn native_id(&self) -> UUID { + self.data.id + } + pub(crate) fn native_from_weak(data: &Weak) -> Option { data.upgrade().map(|data| Self { caps: None, data }) } diff --git a/docs/inventory.md b/docs/inventory.md index b16e349..718d87d 100644 --- a/docs/inventory.md +++ b/docs/inventory.md @@ -36,3 +36,36 @@ The focused compatibility suites are `inventory_store_semantics` and `misclassified_link_semantics`; native unit tests additionally cover concrete cache round trips, permissions, corruption, unknown versions, system-folder sorting, and callback re-entry. + +## UDP inventory manager + +`InventoryManager` now owns the live inventory protocol boundary. Login installs +the agent and library skeletons in the transport-free store, while validated +`UpdateCreateInventoryItem`, `BulkUpdateInventory`, `FetchInventoryReply`, and +`InventoryDescendents` replies populate or reconcile it. Create and copy callback +IDs, item fetches, task replies, and inventory offers are correlated independently; +cancelling one waiter removes only that waiter. A cleanup worker bounds abandoned +requests to one minute, and the manager rejects more than 4,096 pending operations. + +Outbound create, update, move, copy, remove, give, rez/derez, script-state, and +task-inventory operations build the generated protocol packet types. Generated +multi-block packets use the packet codec's `encode_multiple` path, so large batches +are split according to the UDP transport bounds while retaining the same agent, +session, transaction, callback, owner, and permission data. Capability-backed +inventory fetch and folder-content responses are parsed before becoming visible in +the store. Asset, material, script, notecard, and thumbnail upload bodies remain a +separate asset-pipeline concern. + +Legacy task inventory uses a correlated `ReplyTaskInventory` filename followed by +a `RequestXfer` transfer. The implementation confirms packets, handles duplicate +or out-of-order packet numbers deterministically, aborts cancelled transfers, and +rejects announced or accumulated data above 16 MiB. Task text parsing is capped at +100,000 entries and accepts both plain asset IDs and the reference shadow-ID XOR +encoding. The capability path applies the same byte and entry limits. + +No store lock is held across an await, a packet send, or an observer callback. +Reply data is decoded and validated before store mutation, and event registries +snapshot subscribers before invoking them. Focused coverage lives in +`inventory_manager_semantics`, `task_inventory_semantics`, and the native +`inventory_manager::tests` fake-packet cases for callback isolation, bulk-copy +completion, stale folder versions, offers, capability events, and bounded Xfer. diff --git a/tools/check_milestone_09_issue_62.py b/tools/check_milestone_09_issue_62.py new file mode 100644 index 0000000..4a5d60b --- /dev/null +++ b/tools/check_milestone_09_issue_62.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Audit issue 62's native UDP inventory-manager boundary.""" + +from __future__ import annotations + +from pathlib import Path +import re + +import generate_api_shims + + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "crates" / "libremetaverse" / "src" / "inventory_manager.rs" +DOC = ROOT / "docs" / "inventory.md" +MANAGER_TESTS = ROOT / "tests" / "compat" / "tests" / "inventory_manager_semantics.rs" +TASK_TESTS = ROOT / "tests" / "compat" / "tests" / "task_inventory_semantics.rs" +STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(") + +REQUIRED_TYPES = { + "T:LibreMetaverse.FindObjectByPathReplyEventArgs", + "T:LibreMetaverse.FolderUpdatedEventArgs", + "T:LibreMetaverse.InventoryManager", + "T:LibreMetaverse.InventoryManager.CopyItemsResult", + "T:LibreMetaverse.InventoryManager.FolderUpdateResult", + "T:LibreMetaverse.InventoryObjectOfferedEventArgs", + "T:LibreMetaverse.ItemReceivedEventArgs", + "T:LibreMetaverse.SaveAssetToInventoryEventArgs", + "T:LibreMetaverse.ScriptRunningReplyEventArgs", + "T:LibreMetaverse.TaskInventoryReplyEventArgs", + "T:LibreMetaverse.TaskItemReceivedEventArgs", +} + +REQUIRED_MEMBERS = { + "E:LibreMetaverse.InventoryManager.FindObjectByPathReply", + "E:LibreMetaverse.InventoryManager.FolderUpdated", + "E:LibreMetaverse.InventoryManager.InventoryObjectOffered", + "E:LibreMetaverse.InventoryManager.ItemReceived", + "E:LibreMetaverse.InventoryManager.ScriptRunningReply", + "E:LibreMetaverse.InventoryManager.TaskInventoryReply", + "E:LibreMetaverse.InventoryManager.TaskItemReceived", + "M:LibreMetaverse.InventoryManager.#ctor(LibreMetaverse.GridClient)", + "M:LibreMetaverse.InventoryManager.CreateFolder(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType)", + "M:LibreMetaverse.InventoryManager.CreateItemAsync(LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.InventoryType,LibreMetaverse.WearableType,LibreMetaverse.PermissionMask,System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.CreateLinkAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.InventoryType,LibreMetaverse.UUID,System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.FetchItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.FindObjectByPathAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.FolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken,System.Boolean)", + "M:LibreMetaverse.InventoryManager.GetTaskInventoryAsync(LibreMetaverse.UUID,System.UInt32,LibreMetaverse.Simulator,System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.GiveFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.GiveItem(LibreMetaverse.UUID,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Boolean)", + "M:LibreMetaverse.InventoryManager.MoveFolders(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.MoveItemsAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.RemoveItemsAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.RequestCopyItemsWithResultAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{System.String},LibreMetaverse.UUID,System.Threading.CancellationToken)", + "M:LibreMetaverse.InventoryManager.RequestDeRezToInventory(System.UInt32,LibreMetaverse.DeRezDestination,LibreMetaverse.UUID,LibreMetaverse.UUID)", + "M:LibreMetaverse.InventoryManager.RequestFetchInventoryAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken,System.Action{System.Collections.Generic.List{LibreMetaverse.InventoryItem}})", + "M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.UUID,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)", + "M:LibreMetaverse.InventoryManager.RequestTaskInventory(System.UInt32,LibreMetaverse.Simulator)", + "M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem},LibreMetaverse.UUID)", + "M:LibreMetaverse.InventoryManager.UpdateTaskInventory(System.UInt32,LibreMetaverse.InventoryItem,LibreMetaverse.Simulator,System.Boolean)", + "M:LibreMetaverse.InventoryManager.WaitForNextInventoryOfferAsync(System.Threading.CancellationToken)", +} + + +def require_markers(path: Path, markers: tuple[str, ...]) -> None: + source = path.read_text() + missing = [marker for marker in markers if marker not in source] + if missing: + raise SystemExit(f"{path.name}: audit evidence missing: " + ", ".join(missing)) + + +def main() -> None: + native_types = ( + generate_api_shims.NATIVE_TYPES.keys() + | generate_api_shims.NATIVE_DECLARATIONS.keys() + ) + missing_types = sorted(REQUIRED_TYPES - native_types) + if missing_types: + raise SystemExit("issue 62 native types missing: " + ", ".join(missing_types)) + missing_members = sorted( + REQUIRED_MEMBERS - generate_api_shims.NATIVE_MEMBER_BODIES.keys() + ) + if missing_members: + raise SystemExit("issue 62 native members missing: " + ", ".join(missing_members)) + update_task = ( + "M:LibreMetaverse.InventoryManager.UpdateTaskInventory(System.UInt32," + "LibreMetaverse.InventoryItem,LibreMetaverse.Simulator,System.Boolean)" + ) + if "only_mod_meta.unwrap_or(true)" not in generate_api_shims.NATIVE_MEMBER_BODIES[update_task]: + raise SystemExit("UpdateTaskInventory must retain the C# metadata-only default") + for update_member in ( + "M:LibreMetaverse.InventoryManager.RequestUpdateItem(LibreMetaverse.InventoryItem)", + "M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem})", + ): + if "UUID::random()?" not in generate_api_shims.NATIVE_MEMBER_BODIES[update_member]: + raise SystemExit(f"{update_member} must generate a transaction ID") + if STUB_RE.search(SOURCE.read_text()): + raise SystemExit("issue 62 owned Rust stubs remain in inventory_manager.rs") + + require_markers( + SOURCE, + ( + "MAX_TASK_INVENTORY_BYTES", + "MAX_PENDING_REQUESTS", + "encode_multiple", + "pending_created", + "pending_fetches", + "pending_task_replies", + "callbacks are not invoked while an inventory lock is held", + "cancelling_one_fetch_waiter_preserves_concurrent_correlation", + "fake_bulk_copy_reply_waits_for_every_block", + "asset_type_without_folder_type_falls_back_to_inventory_root", + "stale_descendents_packet_cannot_mutate_store", + "task_xfer_rejects_announced_data_above_bound", + ), + ) + require_markers( + DOC, + ( + "UDP inventory manager", + "4,096 pending operations", + "16 MiB", + "RequestXfer", + "No store lock is held across an await", + ), + ) + require_markers( + MANAGER_TESTS, + ( + "inventory_manager_null_throws", + "encrypt_decrypt_round_trip", + "item_crc_is_deterministic_and_changes_when_fields_change", + "create_inventory_item_returns_correct_subclass", + ), + ) + require_markers( + TASK_TESTS, + ( + "cap_available_returns_contents_folder_and_decrypts_shadow_id", + "plain_asset_id_parses_without_decryption", + "non_success_status_returns_contents_folder_only", + "no_capability_does_not_make_http_request", + ), + ) + print( + "issue 62 audit: native inventory packet mappings, bounded correlation and Xfer, " + "lock-free callback evidence, documentation, and translated compatibility cases are present" + ) + + +if __name__ == "__main__": + main() diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index f6d8b8d..ee24eb3 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -82,12 +82,22 @@ NATIVE_TYPES = { "T:LibreMetaverse.InventoryObjectAddedEventArgs": "crate::inventory::InventoryObjectAddedEventArgs", "T:LibreMetaverse.InventoryObjectRemovedEventArgs": "crate::inventory::InventoryObjectRemovedEventArgs", "T:LibreMetaverse.InventoryObjectUpdatedEventArgs": "crate::inventory::InventoryObjectUpdatedEventArgs", + "T:LibreMetaverse.InventoryObjectOfferedEventArgs": "crate::inventory_manager::InventoryObjectOfferedEventArgs", + "T:LibreMetaverse.InventoryManager.CopyItemsResult": "crate::inventory_manager::InventoryManagerCopyItemsResult", + "T:LibreMetaverse.InventoryManager.FolderUpdateResult": "crate::inventory_manager::InventoryManagerFolderUpdateResult", + "T:LibreMetaverse.ItemReceivedEventArgs": "crate::inventory_manager::ItemReceivedEventArgs", "T:LibreMetaverse.InventorySettings": "crate::inventory::InventorySettings", "T:LibreMetaverse.InventorySnapshot": "crate::inventory::InventorySnapshot", "T:LibreMetaverse.InventorySound": "crate::inventory::InventorySound", "T:LibreMetaverse.InventoryTexture": "crate::inventory::InventoryTexture", "T:LibreMetaverse.InventoryWearable": "crate::inventory::InventoryWearable", "T:LibreMetaverse.Permissions": "crate::inventory::Permissions", + "T:LibreMetaverse.FindObjectByPathReplyEventArgs": "crate::inventory_manager::FindObjectByPathReplyEventArgs", + "T:LibreMetaverse.FolderUpdatedEventArgs": "crate::inventory_manager::FolderUpdatedEventArgs", + "T:LibreMetaverse.SaveAssetToInventoryEventArgs": "crate::inventory_manager::SaveAssetToInventoryEventArgs", + "T:LibreMetaverse.ScriptRunningReplyEventArgs": "crate::inventory_manager::ScriptRunningReplyEventArgs", + "T:LibreMetaverse.TaskInventoryReplyEventArgs": "crate::inventory_manager::TaskInventoryReplyEventArgs", + "T:LibreMetaverse.TaskItemReceivedEventArgs": "crate::inventory_manager::TaskItemReceivedEventArgs", "T:LibreMetaverse.ChatEventArgs": "crate::agent_messages::ChatEventArgs", "T:LibreMetaverse.ChatSessionMember": "crate::agent_messages::ChatSessionMember", "T:LibreMetaverse.ChatSessionMemberAddedEventArgs": "crate::agent_messages::ChatSessionMemberAddedEventArgs", @@ -251,6 +261,7 @@ NATIVE_TYPES = { NATIVE_DECLARATIONS = { "T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager", "T:LibreMetaverse.GridClient": "crate::client_core::GridClient", + "T:LibreMetaverse.InventoryManager": "crate::inventory_manager::InventoryManager", "T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager", "T:LibreMetaverse.Settings": "crate::client_core::Settings", "T:LibreMetaverse.Simulator": "crate::network_manager::Simulator", @@ -322,12 +333,192 @@ NATIVE_MEMBER_BODIES = { "self.native_http_caps_client()", "P:LibreMetaverse.GridClient.HttpCapsClient#set": "self.native_set_http_caps_client(value)", + "P:LibreMetaverse.GridClient.Inventory": + "self.native_inventory().unwrap_or_else(|_| panic!(\"failed to construct InventoryManager\"))", + "P:LibreMetaverse.GridClient.Inventory#set": + "self.native_set_inventory(value)", "P:LibreMetaverse.GridClient.Network": "self.native_network().unwrap_or_else(|_| panic!(\"failed to construct NetworkManager\"))", "P:LibreMetaverse.GridClient.Network#set": "self.native_set_network(value)", "P:LibreMetaverse.GridClient.Self": "self.native_self()", + "E:LibreMetaverse.InventoryManager.FindObjectByPathReply": + "self.native_subscribe_find_object_by_path_reply(handler)", + "E:LibreMetaverse.InventoryManager.FolderUpdated": + "self.native_subscribe_folder_updated(handler)", + "E:LibreMetaverse.InventoryManager.InventoryObjectOffered": + "self.native_subscribe_inventory_object_offered(handler)", + "E:LibreMetaverse.InventoryManager.ItemReceived": + "self.native_subscribe_item_received(handler)", + "E:LibreMetaverse.InventoryManager.SaveAssetToInventory": + "self.native_subscribe_save_asset_to_inventory(handler)", + "E:LibreMetaverse.InventoryManager.ScriptRunningReply": + "self.native_subscribe_script_running_reply(handler)", + "E:LibreMetaverse.InventoryManager.TaskInventoryReply": + "self.native_subscribe_task_inventory_reply(handler)", + "E:LibreMetaverse.InventoryManager.TaskItemReceived": + "self.native_subscribe_task_item_received(handler)", + "F:LibreMetaverse.InventoryManager.MAGIC_ID": + "crate::inventory_manager::InventoryManager::native_magic_id()", + "M:LibreMetaverse.InventoryManager.#ctor(LibreMetaverse.GridClient)": + "crate::inventory_manager::InventoryManager::native_new(client)", + "M:LibreMetaverse.InventoryManager.CreateInventoryItem(LibreMetaverse.InventoryType,LibreMetaverse.UUID)": + "crate::inventory_manager::InventoryManager::native_create_inventory_item(type_, id)", + "M:LibreMetaverse.InventoryManager.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_copy_item(item, new_parent, new_name, old_owner_id, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)": + "self.native_copy_item_default_owner(item, new_parent, new_name, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.CopyScriptToTask(System.UInt32,LibreMetaverse.InventoryItem,System.Boolean,LibreMetaverse.Simulator)": + "self.native_copy_script_to_task(object_local_id, item, enable_script, simulator)", + "M:LibreMetaverse.InventoryManager.CreateFolder(LibreMetaverse.UUID,System.String)": + "self.native_create_folder(parent_id, name, libremetaverse_types::FolderType::None)", + "M:LibreMetaverse.InventoryManager.CreateFolder(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType)": + "self.native_create_folder(parent_id, name, preferred_type)", + "M:LibreMetaverse.InventoryManager.CreateItemAsync(LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.InventoryType,LibreMetaverse.PermissionMask,System.Threading.CancellationToken)": + "self.native_create_item(parent_folder, name, description, type_, asset_transaction_id, inv_type, libremetaverse_types::WearableType::Shape, next_owner_mask, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.CreateItemAsync(LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.InventoryType,LibreMetaverse.WearableType,LibreMetaverse.PermissionMask,System.Threading.CancellationToken)": + "self.native_create_item(parent_folder, name, description, type_, asset_transaction_id, inv_type, wearable_type, next_owner_mask, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.CreateLinkAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.InventoryType,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_create_link(folder_id, item_id, name, description, inv_type, transaction_id, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.CreateLinksAsync(LibreMetaverse.UUID,System.Collections.Generic.IEnumerable{System.ValueTuple{LibreMetaverse.InventoryBase,System.String}},System.Action{System.Boolean},System.Threading.CancellationToken)": + "self.native_create_links(folder_id, links_to_create, callback, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.CreateOrRetrieveInventoryItem(LibreMetaverse.InventoryType,LibreMetaverse.UUID)": + "self.native_create_or_retrieve_inventory_item(inv_type, item_id)", + "M:LibreMetaverse.InventoryManager.DecryptShadowID(LibreMetaverse.UUID)": + "crate::inventory_manager::InventoryManager::native_decrypt_shadow_id(shadow_id)", + "M:LibreMetaverse.InventoryManager.Dispose": + "self.native_dispose()", + "M:LibreMetaverse.InventoryManager.EncryptAssetID(LibreMetaverse.UUID)": + "crate::inventory_manager::InventoryManager::native_encrypt_asset_id(asset_id)", + "M:LibreMetaverse.InventoryManager.EmptyFolder(LibreMetaverse.UUID)": + "self.native_empty_folder(folder_id)", + "M:LibreMetaverse.InventoryManager.EmptyLostAndFoundAsync(System.Threading.CancellationToken)": + "self.native_empty_system_folder(libremetaverse_types::FolderType::LostAndFound, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.EmptyTrashAsync(System.Threading.CancellationToken)": + "self.native_empty_system_folder(libremetaverse_types::FolderType::Trash, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.FetchItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_fetch_item(item_id, owner_id, cancellation_token, false).await", + "M:LibreMetaverse.InventoryManager.FetchItemHttpAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_fetch_item(item_id, owner_id, token, true).await", + "M:LibreMetaverse.InventoryManager.FindFolderForType(LibreMetaverse.AssetType)": + "Ok(self.native_find_folder_for_asset_type(type_))", + "M:LibreMetaverse.InventoryManager.FindFolderForType(LibreMetaverse.FolderType)": + "Ok(self.native_find_folder_for_type(type_))", + "M:LibreMetaverse.InventoryManager.FindObjectByPathAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)": + "self.native_find_object_by_path(base_folder, inventory_owner, path, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.FolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken,System.Boolean)": + "self.native_folder_contents(folder, owner, fetch_folders, fetch_items, order, cancellation_token, follow_links.unwrap_or(false)).await", + "M:LibreMetaverse.InventoryManager.GetInventoryRecursiveAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.InventoryFolder},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)": + "self.native_get_inventory_recursive(folder_id, owner, cats, items, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.GetTaskInventoryAsync(LibreMetaverse.UUID,System.UInt32,LibreMetaverse.Simulator,System.Threading.CancellationToken)": + "self.native_get_task_inventory(object_id, object_local_id, simulator, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.GiveFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)": + "self.native_give_folder(folder_id, folder_name, recipient, do_effect, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.GiveItem(LibreMetaverse.UUID,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Boolean)": + "self.native_give_item(item_id, item_name, asset_type, recipient, do_effect)", + "M:LibreMetaverse.InventoryManager.ItemCRC(LibreMetaverse.InventoryItem)": + "crate::inventory_manager::InventoryManager::native_item_crc(&iitem)", + "M:LibreMetaverse.InventoryManager.ParseTaskInventory(System.String)": + "crate::inventory_manager::InventoryManager::native_parse_task_inventory(task_data)", + "M:LibreMetaverse.InventoryManager.LocalFind(LibreMetaverse.UUID,System.String[],System.Int32,System.Boolean)": + "self.native_local_find(base_folder, path, level, first_only)", + "M:LibreMetaverse.InventoryManager.Move(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryFolder)": + "self.native_move_object(item, new_parent)", + "M:LibreMetaverse.InventoryManager.MoveFolder(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_move_folders(std::collections::HashMap::from([(folder_id, new_parent_id)]), cancellation_token)", + "M:LibreMetaverse.InventoryManager.MoveFolderAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_move_folders_async(std::collections::HashMap::from([(folder_id, new_parent_id)]), cancellation_token).await", + "M:LibreMetaverse.InventoryManager.MoveFolders(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_move_folders(folders_new_parents, cancellation_token)", + "M:LibreMetaverse.InventoryManager.MoveFoldersAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_move_folders_async(folders_new_parents, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.MoveItem(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)": + "self.native_move_items(std::collections::HashMap::from([(item_id, (folder_id, new_name))]), cancellation_token)", + "M:LibreMetaverse.InventoryManager.MoveItem(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_move_items(std::collections::HashMap::from([(item_id, (folder_id, String::new()))]), cancellation_token)", + "M:LibreMetaverse.InventoryManager.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)": + "self.native_move_items_async(std::collections::HashMap::from([(item_id, (folder_id, new_name))]), cancellation_token).await", + "M:LibreMetaverse.InventoryManager.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_move_items_async(std::collections::HashMap::from([(item_id, (folder_id, String::new()))]), cancellation_token).await", + "M:LibreMetaverse.InventoryManager.MoveItems(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_move_items(items_new_folders.into_iter().map(|(item, folder)| (item, (folder, String::new()))).collect(), cancellation_token)", + "M:LibreMetaverse.InventoryManager.MoveItemsAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_move_items_async(items_new_folders.into_iter().map(|(item, folder)| (item, (folder, String::new()))).collect(), cancellation_token).await", + "M:LibreMetaverse.InventoryManager.MoveTaskInventory(System.UInt32,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Simulator)": + "self.native_move_task_inventory(object_local_id, task_item_id, inventory_folder_id, simulator)", + "M:LibreMetaverse.InventoryManager.RemoveDescendantsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_remove_descendants(folder, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RemoveFolderAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_remove_inventory_objects(Vec::new(), vec![folder], cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RemoveItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_remove_inventory_objects(vec![item], Vec::new(), cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RemoveItemsAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_remove_inventory_objects(items.collect(), Vec::new(), cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RemoveTaskInventory(System.UInt32,LibreMetaverse.UUID,LibreMetaverse.Simulator)": + "self.native_remove_task_inventory(object_local_id, task_item_id, simulator)", + "M:LibreMetaverse.InventoryManager.RequestCopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_copy_item(item, new_parent, new_name, old_owner_id, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RequestCopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)": + "self.native_copy_item_default_owner(item, new_parent, new_name, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RequestCopyItemFromNotecardAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.HttpCapsClient.ProgressReport})": + "self.native_copy_item_from_notecard(object_id, notecard_id, folder_id, item_id, cancellation_token, progress).await", + "M:LibreMetaverse.InventoryManager.RequestCopyItemsAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{System.String},LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_copy_items(items, target_folders, new_names, old_owner_id, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RequestCopyItemsWithResultAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{System.String},LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_copy_items(items, target_folders, new_names, old_owner_id, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RequestDeRezToInventory(System.UInt32)": + "self.native_de_rez_to_objects_folder(object_local_id)", + "M:LibreMetaverse.InventoryManager.RequestDeRezToInventory(System.UInt32,LibreMetaverse.DeRezDestination,LibreMetaverse.UUID,LibreMetaverse.UUID)": + "self.native_de_rez_to_inventory(object_local_id, dest_type, dest_folder, transaction_id)", + "M:LibreMetaverse.InventoryManager.RequestFetchInventory(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_request_fetch_inventory(std::collections::HashMap::from([(item_id, owner_id)]), cancellation_token)", + "M:LibreMetaverse.InventoryManager.RequestFetchInventory(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_request_fetch_inventory(items, cancellation_token)", + "M:LibreMetaverse.InventoryManager.RequestFetchInventoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_request_fetch_inventory_async(std::collections::HashMap::from([(item_id, owner_id)]), cancellation_token, None).await", + "M:LibreMetaverse.InventoryManager.RequestFetchInventoryAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken,System.Action{System.Collections.Generic.List{LibreMetaverse.InventoryItem}})": + "self.native_request_fetch_inventory_async(items, cancellation_token, callback).await", + "M:LibreMetaverse.InventoryManager.RequestFindObjectByPathAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)": + "self.native_find_object_by_path(base_folder, inventory_owner, path, cancellation_token).await.map(|_| ())", + "M:LibreMetaverse.InventoryManager.RequestFolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)": + "self.native_request_folder_contents(folder_id, owner_id, fetch_folders, fetch_items, order, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RequestFolderContentsAsync(System.Collections.Generic.List{LibreMetaverse.InventoryFolder},System.Uri,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)": + "self.native_request_folder_contents_batch(batch, capability_uri, fetch_folders, fetch_items, order, cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RequestFolderUpdateAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken)": + "self.native_request_folder_update(folder_id, owner_id, fetch_folders.unwrap_or(true), fetch_items.unwrap_or(true), order.unwrap_or(crate::InventorySortOrder::BY_NAME), cancellation_token).await", + "M:LibreMetaverse.InventoryManager.RequestGetScriptRunning(LibreMetaverse.UUID,LibreMetaverse.UUID)": + "self.native_request_get_script_running(object_id, script_id)", + "M:LibreMetaverse.InventoryManager.RequestRestoreRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.InventoryItem,LibreMetaverse.UUID)": + "self.native_restore_rez_from_inventory(simulator, item, query_id)", + "M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem)": + "self.native_rez_from_inventory_default(simulator, rotation, position, item, None)", + "M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem,LibreMetaverse.UUID)": + "self.native_rez_from_inventory_default(simulator, rotation, position, item, Some(group_owner))", + "M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.UUID,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)": + "self.native_rez_from_inventory(simulator, task_id, rotation, position, item, group_owner, query_id, rez_selected)", + "M:LibreMetaverse.InventoryManager.RequestSetScriptRunning(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)": + "self.native_request_set_script_running(object_id, script_id, running)", + "M:LibreMetaverse.InventoryManager.RequestTaskInventory(System.UInt32)": + "self.native_request_task_inventory(object_local_id, None)", + "M:LibreMetaverse.InventoryManager.RequestTaskInventory(System.UInt32,LibreMetaverse.Simulator)": + "self.native_request_task_inventory(object_local_id, simulator)", + "M:LibreMetaverse.InventoryManager.RequestUpdateItem(LibreMetaverse.InventoryItem)": + "self.native_request_update_items(vec![item], libremetaverse_types::UUID::random()?)", + "M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem})": + "self.native_request_update_items(items, libremetaverse_types::UUID::random()?)", + "M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem},LibreMetaverse.UUID)": + "self.native_request_update_items(items, transaction_id)", + "M:LibreMetaverse.InventoryManager.UpdateFolderProperties(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType)": + "self.native_update_folder_properties(folder_id, parent_id, name, type_)", + "M:LibreMetaverse.InventoryManager.UpdateTaskInventory(System.UInt32,LibreMetaverse.InventoryItem,LibreMetaverse.Simulator,System.Boolean)": + "self.native_update_task_inventory(object_local_id, item, simulator, only_mod_meta.unwrap_or(true))", + "M:LibreMetaverse.InventoryManager.WaitForNextInventoryOfferAsync(System.Threading.CancellationToken)": + "self.native_wait_for_next_inventory_offer(cancellation_token).await", + "P:LibreMetaverse.InventoryManager.NoResults": + "crate::inventory_manager::InventoryManager::native_no_results()", + "P:LibreMetaverse.InventoryManager.Store": + "self.native_store()", "E:LibreMetaverse.AgentManager.IM": "self.native_subscribe_im(handler)", "E:LibreMetaverse.AgentManager.AgentDataReply": diff --git a/tools/generate_rust_mapping.py b/tools/generate_rust_mapping.py index ba08bda..6546ad7 100644 --- a/tools/generate_rust_mapping.py +++ b/tools/generate_rust_mapping.py @@ -994,6 +994,7 @@ def validate_generated_shims() -> None: "crate::gesture::", "crate::json_codec::", "crate::inventory::", + "crate::inventory_manager::", "crate::message_codec::", "crate::message_decoder::", "crate::notation::",