From eb7be428c654aa758dcc81a8afdb986099217c02 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Mon, 10 Aug 2026 00:06:28 +0000 Subject: [PATCH] Implement native inventory store and cache (#61) --- .gitea/workflows/codegen.yml | 13 + .gitea/workflows/rust-workspace.yml | 3 +- Cargo.lock | 1 + README.md | 9 + api/SHIM-COVERAGE.md | 2 +- crates/libremetaverse/Cargo.toml | 1 + crates/libremetaverse/src/generated.rs | 1309 ++--------- crates/libremetaverse/src/inventory.rs | 2762 ++++++++++++++++++++++++ crates/libremetaverse/src/lib.rs | 1 + docs/inventory.md | 38 + tests/api-compile/Cargo.lock | 1 + tests/api-compile/src/lib.rs | 4 +- tests/compat/tests/wire_shims.rs | 19 +- tools/check_milestone_09_issue_61.py | 76 + tools/generate_api_shims.py | 45 + tools/generate_rust_mapping.py | 1 + 16 files changed, 3131 insertions(+), 1154 deletions(-) create mode 100644 crates/libremetaverse/src/inventory.rs create mode 100644 docs/inventory.md create mode 100644 tools/check_milestone_09_issue_61.py diff --git a/.gitea/workflows/codegen.yml b/.gitea/workflows/codegen.yml index 752c2fd..e6d2d12 100644 --- a/.gitea/workflows/codegen.yml +++ b/.gitea/workflows/codegen.yml @@ -15,8 +15,11 @@ on: - "tools/check_milestone_08.py" - "tools/check_milestone_09_issue_59.py" - "tools/check_milestone_09_issue_60.py" + - "tools/check_milestone_09_issue_61.py" - "crates/libremetaverse/src/agent_movement.rs" + - "crates/libremetaverse/src/inventory.rs" - "docs/agent-movement.md" + - "docs/inventory.md" - "crates/libremetaverse/Cargo.toml" - "crates/libremetaverse/src/generated.rs" - "crates/libremetaverse/src/packets.rs" @@ -28,6 +31,8 @@ on: - "tests/compat/src/**" - "tests/compat/tests/wire_semantics.rs" - "tests/compat/tests/appearance_visual_semantics.rs" + - "tests/compat/tests/inventory_store_semantics.rs" + - "tests/compat/tests/misclassified_link_semantics.rs" - "tests/upstream-tests.json" pull_request: paths: @@ -43,8 +48,11 @@ on: - "tools/check_milestone_08.py" - "tools/check_milestone_09_issue_59.py" - "tools/check_milestone_09_issue_60.py" + - "tools/check_milestone_09_issue_61.py" - "crates/libremetaverse/src/agent_movement.rs" + - "crates/libremetaverse/src/inventory.rs" - "docs/agent-movement.md" + - "docs/inventory.md" - "crates/libremetaverse/Cargo.toml" - "crates/libremetaverse/src/generated.rs" - "crates/libremetaverse/src/packets.rs" @@ -56,6 +64,8 @@ on: - "tests/compat/src/**" - "tests/compat/tests/wire_semantics.rs" - "tests/compat/tests/appearance_visual_semantics.rs" + - "tests/compat/tests/inventory_store_semantics.rs" + - "tests/compat/tests/misclassified_link_semantics.rs" - "tests/upstream-tests.json" workflow_dispatch: @@ -79,6 +89,7 @@ jobs: python3 tools/check_milestone_08.py python3 tools/check_milestone_09_issue_59.py python3 tools/check_milestone_09_issue_60.py + python3 tools/check_milestone_09_issue_61.py cargo check --manifest-path tests/api-compile/Cargo.toml --locked - name: Test generated wire codecs and translated parity cases run: | @@ -86,6 +97,8 @@ jobs: cargo test -p libremetaverse --test packet_wire 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 misclassified_link_semantics - name: Test generated visual and foliage catalogs run: | cargo test -p libremetaverse --test generated_catalogs diff --git a/.gitea/workflows/rust-workspace.yml b/.gitea/workflows/rust-workspace.yml index 703a826..4ff5bef 100644 --- a/.gitea/workflows/rust-workspace.yml +++ b/.gitea/workflows/rust-workspace.yml @@ -25,9 +25,10 @@ jobs: components: rustfmt - name: Check formatting run: cargo fmt --all -- --check - - name: Audit native AgentManager boundaries + - name: Audit native milestone 09 boundaries run: | python3 tools/check_milestone_09_issue_59.py python3 tools/check_milestone_09_issue_60.py + python3 tools/check_milestone_09_issue_61.py - name: Compile every workspace target with bounded memory run: cargo check --workspace --all-targets --locked -j 1 diff --git a/Cargo.lock b/Cargo.lock index 0ca4986..604fd44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -844,6 +844,7 @@ version = "0.0.1" dependencies = [ "bcdec_rs", "flate2", + "futures-channel", "futures-util", "libremetaverse-imaging", "libremetaverse-structured-data", diff --git a/README.md b/README.md index 482ab81..fe04c59 100644 --- a/README.md +++ b/README.md @@ -348,3 +348,12 @@ diagnosable crossing state machine with recovery to the old simulator. Paused time tests and a loopback fake grid verify exact wire fields and clean worker shutdown. The compatibility and lifecycle contract is documented in [`docs/agent-movement.md`](docs/agent-movement.md). + +Native inventory now preserves the complete item/folder model hierarchy, +permissions, concrete subclasses, parent/descendant counts, link resolution, +root/library semantics, system-folder lookup, and C# sort flags. Local mutation +is transport-independent and observer callbacks run after locks are released. +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). diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index edc1ead..648b415 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: 128 types / 14,611 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 153 types / 14,746 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/Cargo.toml b/crates/libremetaverse/Cargo.toml index 457aa34..4b7d654 100644 --- a/crates/libremetaverse/Cargo.toml +++ b/crates/libremetaverse/Cargo.toml @@ -15,6 +15,7 @@ jpeg2000 = ["libremetaverse-imaging/jpeg2000"] [dependencies] bcdec_rs = { version = "0.2.0", optional = true } flate2 = "1.1.2" +futures-channel = "0.3.31" futures-util = "0.3.31" libremetaverse-imaging = { path = "../libremetaverse-imaging" } libremetaverse-structured-data = { path = "../libremetaverse-structured-data" } diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index ce75472..5cfe2ba 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -11790,278 +11790,38 @@ impl InterestListManager { } /// C# type: `T:LibreMetaverse.Inventory`. -pub struct Inventory; -impl Inventory { - /// C# member: `E:LibreMetaverse.Inventory.InventoryObjectAdded`. - pub fn subscribe_inventory_object_added( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::InventoryObjectAddedEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.Inventory.InventoryObjectAdded") - } - /// C# member: `E:LibreMetaverse.Inventory.InventoryObjectRemoved`. - pub fn subscribe_inventory_object_removed( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::InventoryObjectRemovedEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.Inventory.InventoryObjectRemoved" - ) - } - /// C# member: `E:LibreMetaverse.Inventory.InventoryObjectUpdated`. - pub fn subscribe_inventory_object_updated( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::InventoryObjectUpdatedEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.Inventory.InventoryObjectUpdated" - ) - } - /// C# member: `M:LibreMetaverse.Inventory.#ctor(LibreMetaverse.GridClient)`. - pub fn new_with_grid_client( - client: std::sync::Arc, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.#ctor(LibreMetaverse.GridClient,LibreMetaverse.UUID)`. - pub fn new_with_grid_client_uuid( - client: std::sync::Arc, - owner: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.#ctor(LibreMetaverse.GridClient,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.Clear`. - pub fn clear(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Inventory.Clear") - } - /// C# member: `M:LibreMetaverse.Inventory.Contains(LibreMetaverse.InventoryBase)`. - pub fn contains_with_inventory_base( - &self, - obj: &dyn libremetaverse::InventoryObjectClass, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.Contains(LibreMetaverse.InventoryBase)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.Contains(LibreMetaverse.UUID)`. - pub fn contains_with_uuid( - &self, - uuid: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.Contains(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.FindAllLinks(LibreMetaverse.UUID)`. - pub fn find_all_links( - &self, - asset_id: libremetaverse_types::UUID, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.FindAllLinks(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.GetContents(LibreMetaverse.InventoryFolder)`. - pub fn get_contents_with_inventory_folder( - &self, - folder: &libremetaverse::InventoryFolder, - ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.GetContents(LibreMetaverse.InventoryFolder)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.GetContents(LibreMetaverse.UUID)`. - pub fn get_contents_with_uuid( - &self, - folder: libremetaverse_types::UUID, - ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.GetContents(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.GetNodeFor(LibreMetaverse.UUID)`. - pub fn get_node_for( - &self, - uuid: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.GetNodeFor(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.GetNodeOrDefault(LibreMetaverse.UUID)`. - pub fn get_node_or_default( - &self, - uuid: libremetaverse_types::UUID, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.GetNodeOrDefault(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.GetValueOrDefault(LibreMetaverse.UUID)`. - pub fn get_value_or_default_with_uuid( - &self, - uuid: libremetaverse_types::UUID, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.GetValueOrDefault(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.GetValueOrDefault``1(LibreMetaverse.UUID)`. - pub fn get_value_or_default_with_uuid_a7c63fbe( - &self, - uuid: libremetaverse_types::UUID, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.GetValueOrDefault``1(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.RemoveNodeFor(LibreMetaverse.InventoryBase)`. - pub fn remove_node_for( - &self, - item: &dyn libremetaverse::InventoryObjectClass, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.RemoveNodeFor(LibreMetaverse.InventoryBase)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.RestoreFromDisk(System.String)`. - pub fn restore_from_disk_with_string(&self, filename: String) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.RestoreFromDisk(System.String)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.RestoreFromDiskAsync(System.String,System.Threading.CancellationToken)`. - pub async fn restore_from_disk_with_string_cancellation_token( - &self, - filename: String, - cancellation_token: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.RestoreFromDiskAsync(System.String,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.SaveToDisk(System.String)`. - pub fn save_to_disk_with_string(&self, filename: String) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.SaveToDisk(System.String)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.SaveToDiskAsync(System.String,System.Threading.CancellationToken)`. - pub async fn save_to_disk_with_string_cancellation_token( - &self, - filename: String, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.SaveToDiskAsync(System.String,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.TryGetNodeFor(LibreMetaverse.UUID,LibreMetaverse.InventoryNode@)`. - pub fn try_get_node_for( - &self, - uuid: libremetaverse_types::UUID, - node: &mut Option, - ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Inventory.TryGetNodeFor(LibreMetaverse.UUID,LibreMetaverse.InventoryNode@)" - ) - } - /// C# member: `M:LibreMetaverse.Inventory.TryGetValue(LibreMetaverse.UUID,LibreMetaverse.InventoryBase@)`. - pub fn try_get_value_with_uuid_inventory_base( - &self, - uuid: libremetaverse_types::UUID, - item: &mut Option>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.TryGetValue(LibreMetaverse.UUID,LibreMetaverse.InventoryBase@)", - ) - } - /// C# member: `M:LibreMetaverse.Inventory.TryGetValue``1(LibreMetaverse.UUID,``0@)`. - pub fn try_get_value_with_uuid_t( - &self, - uuid: libremetaverse_types::UUID, - item: &mut Option, - ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Inventory.TryGetValue``1(LibreMetaverse.UUID,``0@)" - ) - } - /// C# member: `M:LibreMetaverse.Inventory.UpdateNodeFor(LibreMetaverse.InventoryBase)`. - pub fn update_node_for( - &self, - item: &dyn libremetaverse::InventoryObjectClass, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Inventory.UpdateNodeFor(LibreMetaverse.InventoryBase)", - ) - } - /// C# member: `P:LibreMetaverse.Inventory.Count`. - pub fn count(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.Count") - } - /// C# member: `P:LibreMetaverse.Inventory.Item(LibreMetaverse.UUID)`. - pub fn item(&self, uuid: libremetaverse_types::UUID) -> libremetaverse::InventoryBase { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Inventory.Item(LibreMetaverse.UUID)" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Inventory.Item(LibreMetaverse.UUID)`. - pub fn set_item( - &mut self, - uuid: libremetaverse_types::UUID, - value: libremetaverse::InventoryBase, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Inventory.Item(LibreMetaverse.UUID)" - ) - } - /// C# member: `P:LibreMetaverse.Inventory.LibraryFolder`. - pub fn library_folder(&self) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.LibraryFolder") - } - /// Setter for C# member: `P:LibreMetaverse.Inventory.LibraryFolder`. - pub fn set_library_folder(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.LibraryFolder") - } - /// C# member: `P:LibreMetaverse.Inventory.LibraryRootNode`. - pub fn library_root_node(&self) -> libremetaverse::InventoryNode { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.LibraryRootNode") - } - /// Setter for C# member: `P:LibreMetaverse.Inventory.LibraryRootNode`. - pub fn set_library_root_node(&mut self, value: libremetaverse::InventoryNode) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.LibraryRootNode") - } - /// C# member: `P:LibreMetaverse.Inventory.Owner`. - pub fn owner(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.Owner") - } - /// C# member: `P:LibreMetaverse.Inventory.RootFolder`. - pub fn root_folder(&self) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.RootFolder") - } - /// Setter for C# member: `P:LibreMetaverse.Inventory.RootFolder`. - pub fn set_root_folder(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.RootFolder") - } - /// C# member: `P:LibreMetaverse.Inventory.RootNode`. - pub fn root_node(&self) -> libremetaverse::InventoryNode { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.RootNode") - } - /// Setter for C# member: `P:LibreMetaverse.Inventory.RootNode`. - pub fn set_root_node(&mut self, value: libremetaverse::InventoryNode) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Inventory.RootNode") - } -} +/// C# member: `E:LibreMetaverse.Inventory.InventoryObjectAdded`. +/// C# member: `E:LibreMetaverse.Inventory.InventoryObjectRemoved`. +/// C# member: `E:LibreMetaverse.Inventory.InventoryObjectUpdated`. +/// C# member: `M:LibreMetaverse.Inventory.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.Inventory.#ctor(LibreMetaverse.GridClient,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.Inventory.Clear`. +/// C# member: `M:LibreMetaverse.Inventory.Contains(LibreMetaverse.InventoryBase)`. +/// C# member: `M:LibreMetaverse.Inventory.Contains(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.Inventory.FindAllLinks(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.Inventory.GetContents(LibreMetaverse.InventoryFolder)`. +/// C# member: `M:LibreMetaverse.Inventory.GetContents(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.Inventory.GetNodeFor(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.Inventory.GetNodeOrDefault(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.Inventory.GetValueOrDefault(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.Inventory.GetValueOrDefault``1(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.Inventory.RemoveNodeFor(LibreMetaverse.InventoryBase)`. +/// C# member: `M:LibreMetaverse.Inventory.RestoreFromDisk(System.String)`. +/// C# member: `M:LibreMetaverse.Inventory.RestoreFromDiskAsync(System.String,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.Inventory.SaveToDisk(System.String)`. +/// C# member: `M:LibreMetaverse.Inventory.SaveToDiskAsync(System.String,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.Inventory.TryGetNodeFor(LibreMetaverse.UUID,LibreMetaverse.InventoryNode@)`. +/// C# member: `M:LibreMetaverse.Inventory.TryGetValue(LibreMetaverse.UUID,LibreMetaverse.InventoryBase@)`. +/// C# member: `M:LibreMetaverse.Inventory.TryGetValue``1(LibreMetaverse.UUID,``0@)`. +/// C# member: `M:LibreMetaverse.Inventory.UpdateNodeFor(LibreMetaverse.InventoryBase)`. +/// C# member: `P:LibreMetaverse.Inventory.Count`. +/// C# member: `P:LibreMetaverse.Inventory.Item(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.Inventory.LibraryFolder`. +/// C# member: `P:LibreMetaverse.Inventory.LibraryRootNode`. +/// C# member: `P:LibreMetaverse.Inventory.Owner`. +/// C# member: `P:LibreMetaverse.Inventory.RootFolder`. +/// C# member: `P:LibreMetaverse.Inventory.RootNode`. +pub use crate::inventory::Inventory; /// C# type: `T:LibreMetaverse.InventoryAISClient`. pub struct InventoryAISClient; @@ -12494,143 +12254,32 @@ impl InventoryAISClient { } /// C# type: `T:LibreMetaverse.InventoryAnimation`. -pub struct InventoryAnimation { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryAnimation { - /// C# member: `M:LibreMetaverse.InventoryAnimation.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryAnimation.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryAnimation.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventoryAnimation; /// C# type: `T:LibreMetaverse.InventoryAttachment`. -pub struct InventoryAttachment { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryAttachment { - /// C# member: `M:LibreMetaverse.InventoryAttachment.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryAttachment.#ctor(LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryAttachment.AttachmentPoint`. - pub fn attachment_point(&self) -> libremetaverse_types::AttachmentPoint { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryAttachment.AttachmentPoint" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryAttachment.AttachmentPoint`. - pub fn set_attachment_point(&mut self, value: libremetaverse_types::AttachmentPoint) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryAttachment.AttachmentPoint" - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryAttachment.#ctor(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.InventoryAttachment.AttachmentPoint`. +pub use crate::inventory::InventoryAttachment; /// C# type: `T:LibreMetaverse.InventoryBase`. -#[derive(Clone, Debug)] -pub struct InventoryBase { - native_uuid: libremetaverse_types::UUID, - native_owner_id: libremetaverse_types::UUID, - native_parent_uuid: libremetaverse_types::UUID, - native_name: String, -} -impl InventoryBase { - /// C# member: `M:LibreMetaverse.InventoryBase.Equals(LibreMetaverse.InventoryBase)`. - pub fn equals_with_inventory_base(&self, o: libremetaverse::InventoryBase) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.InventoryBase.Equals(LibreMetaverse.InventoryBase)" - ) - } - /// C# member: `M:LibreMetaverse.InventoryBase.Equals(System.Object)`. - pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.InventoryBase.Equals(System.Object)" - ) - } - /// C# member: `M:LibreMetaverse.InventoryBase.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.InventoryBase.GetHashCode") - } - /// C# member: `M:LibreMetaverse.InventoryBase.GetOSD`. - pub fn get_osd(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.InventoryBase.GetOSD") - } - /// C# member: `P:LibreMetaverse.InventoryBase.Name`. - pub fn name(&self) -> String { - /* native network implementation */ - self.native_name.clone() - } - /// Setter for C# member: `P:LibreMetaverse.InventoryBase.Name`. - pub fn set_name(&mut self, value: String) { - /* native network implementation */ - self.native_name = value - } - /// C# member: `P:LibreMetaverse.InventoryBase.OwnerID`. - pub fn owner_id(&self) -> libremetaverse_types::UUID { - /* native network implementation */ - self.native_owner_id - } - /// Setter for C# member: `P:LibreMetaverse.InventoryBase.OwnerID`. - pub fn set_owner_id(&mut self, value: libremetaverse_types::UUID) { - /* native network implementation */ - self.native_owner_id = value - } - /// C# member: `P:LibreMetaverse.InventoryBase.ParentUUID`. - pub fn parent_uuid(&self) -> libremetaverse_types::UUID { - /* native network implementation */ - self.native_parent_uuid - } - /// Setter for C# member: `P:LibreMetaverse.InventoryBase.ParentUUID`. - pub fn set_parent_uuid(&mut self, value: libremetaverse_types::UUID) { - /* native network implementation */ - self.native_parent_uuid = value - } - /// C# member: `P:LibreMetaverse.InventoryBase.UUID`. - pub fn uuid(&self) -> libremetaverse_types::UUID { - /* native network implementation */ - self.native_uuid - } - /// Setter for C# member: `P:LibreMetaverse.InventoryBase.UUID`. - pub fn set_uuid(&mut self, value: libremetaverse_types::UUID) { - /* native network implementation */ - self.native_uuid = value - } -} +/// C# member: `M:LibreMetaverse.InventoryBase.Equals(LibreMetaverse.InventoryBase)`. +/// C# member: `M:LibreMetaverse.InventoryBase.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.InventoryBase.GetHashCode`. +/// C# member: `M:LibreMetaverse.InventoryBase.GetOSD`. +/// C# member: `P:LibreMetaverse.InventoryBase.Name`. +/// C# member: `P:LibreMetaverse.InventoryBase.OwnerID`. +/// C# member: `P:LibreMetaverse.InventoryBase.ParentUUID`. +/// C# member: `P:LibreMetaverse.InventoryBase.UUID`. +pub use crate::inventory::InventoryBase; /// C# type: `T:LibreMetaverse.InventoryCallingCard`. -pub struct InventoryCallingCard { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryCallingCard { - /// C# member: `M:LibreMetaverse.InventoryCallingCard.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryCallingCard.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryCallingCard.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventoryCallingCard; /// C# type: `T:LibreMetaverse.InventoryCategory`. -pub struct InventoryCategory { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryCategory { - /// C# member: `M:LibreMetaverse.InventoryCategory.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryCategory.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryCategory.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventoryCategory; /// C# type: `T:LibreMetaverse.InventoryException`. pub struct InventoryException; @@ -12657,312 +12306,54 @@ impl InventoryException { } /// C# type: `T:LibreMetaverse.InventoryFolder`. -#[derive(Clone, Debug)] -pub struct InventoryFolder { - /// Rust composition for C# base type `LibreMetaverse.InventoryBase`. - pub base: libremetaverse::InventoryBase, - native_descendent_count: i32, - native_preferred_type: libremetaverse_types::FolderType, - native_version: i32, -} -impl InventoryFolder { - /// C# member: `F:LibreMetaverse.InventoryFolder.VERSION_UNKNOWN`. - pub const VERSION_UNKNOWN: i32 = -1; - /// C# member: `M:LibreMetaverse.InventoryFolder.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - /* native network implementation */ - Ok(Self { - base: crate::InventoryBase { - native_uuid: uuid, - native_owner_id: libremetaverse_types::UUID::zero(), - native_parent_uuid: libremetaverse_types::UUID::zero(), - native_name: String::new(), - }, - native_descendent_count: 0, - native_preferred_type: libremetaverse_types::FolderType::None, - native_version: Self::VERSION_UNKNOWN, - }) - } - /// C# member: `M:LibreMetaverse.InventoryFolder.Equals(LibreMetaverse.InventoryBase)`. - pub fn equals_with_inventory_base(&self, o: libremetaverse::InventoryBase) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.InventoryFolder.Equals(LibreMetaverse.InventoryBase)" - ) - } - /// C# member: `M:LibreMetaverse.InventoryFolder.Equals(LibreMetaverse.InventoryFolder)`. - pub fn equals_with_inventory_folder(&self, o: libremetaverse::InventoryFolder) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.InventoryFolder.Equals(LibreMetaverse.InventoryFolder)" - ) - } - /// C# member: `M:LibreMetaverse.InventoryFolder.Equals(System.Object)`. - pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.InventoryFolder.Equals(System.Object)" - ) - } - /// C# member: `M:LibreMetaverse.InventoryFolder.FromOSD(LibreMetaverse.StructuredData.OSD)`. - pub fn from_osd( - data: libremetaverse_structured_data::OSD, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryFolder.FromOSD(LibreMetaverse.StructuredData.OSD)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryFolder.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.InventoryFolder.GetHashCode") - } - /// C# member: `M:LibreMetaverse.InventoryFolder.GetOSD`. - pub fn get_osd(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.InventoryFolder.GetOSD") - } - /// C# member: `M:LibreMetaverse.InventoryFolder.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.InventoryFolder.ToString") - } - /// C# member: `M:LibreMetaverse.InventoryFolder.Update(LibreMetaverse.StructuredData.OSDMap)`. - pub fn update(&self, data: libremetaverse_structured_data::OSDMap) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryFolder.Update(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryFolder.DescendentCount`. - pub fn descendent_count(&self) -> i32 { - /* native network implementation */ - self.native_descendent_count - } - /// Setter for C# member: `P:LibreMetaverse.InventoryFolder.DescendentCount`. - pub fn set_descendent_count(&mut self, value: i32) { - /* native network implementation */ - self.native_descendent_count = value - } - /// C# member: `P:LibreMetaverse.InventoryFolder.PreferredType`. - pub fn preferred_type(&self) -> libremetaverse_types::FolderType { - /* native network implementation */ - self.native_preferred_type - } - /// Setter for C# member: `P:LibreMetaverse.InventoryFolder.PreferredType`. - pub fn set_preferred_type(&mut self, value: libremetaverse_types::FolderType) { - /* native network implementation */ - self.native_preferred_type = value - } - /// C# member: `P:LibreMetaverse.InventoryFolder.Version`. - pub fn version(&self) -> i32 { - /* native network implementation */ - self.native_version - } - /// Setter for C# member: `P:LibreMetaverse.InventoryFolder.Version`. - pub fn set_version(&mut self, value: i32) { - /* native network implementation */ - self.native_version = value - } -} +/// C# member: `F:LibreMetaverse.InventoryFolder.VERSION_UNKNOWN`. +/// C# member: `M:LibreMetaverse.InventoryFolder.#ctor(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.InventoryFolder.Equals(LibreMetaverse.InventoryBase)`. +/// C# member: `M:LibreMetaverse.InventoryFolder.Equals(LibreMetaverse.InventoryFolder)`. +/// C# member: `M:LibreMetaverse.InventoryFolder.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.InventoryFolder.FromOSD(LibreMetaverse.StructuredData.OSD)`. +/// C# member: `M:LibreMetaverse.InventoryFolder.GetHashCode`. +/// C# member: `M:LibreMetaverse.InventoryFolder.GetOSD`. +/// C# member: `M:LibreMetaverse.InventoryFolder.ToString`. +/// C# member: `M:LibreMetaverse.InventoryFolder.Update(LibreMetaverse.StructuredData.OSDMap)`. +/// C# member: `P:LibreMetaverse.InventoryFolder.DescendentCount`. +/// C# member: `P:LibreMetaverse.InventoryFolder.PreferredType`. +/// C# member: `P:LibreMetaverse.InventoryFolder.Version`. +pub use crate::inventory::InventoryFolder; /// C# type: `T:LibreMetaverse.InventoryGesture`. -pub struct InventoryGesture { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryGesture { - /// C# member: `M:LibreMetaverse.InventoryGesture.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryGesture.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryGesture.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventoryGesture; /// C# type: `T:LibreMetaverse.InventoryItem`. -pub struct InventoryItem { - /// Rust composition for C# base type `LibreMetaverse.InventoryBase`. - pub base: libremetaverse::InventoryBase, -} -impl InventoryItem { - /// C# member: `M:LibreMetaverse.InventoryItem.#ctor(LibreMetaverse.InventoryType,LibreMetaverse.UUID)`. - pub fn new_with_inventory_type_uuid( - type_: libremetaverse_types::InventoryType, - item_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryItem.#ctor(LibreMetaverse.InventoryType,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryItem.#ctor(LibreMetaverse.UUID)`. - pub fn new_with_uuid(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryItem.#ctor(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryItem.Equals(LibreMetaverse.InventoryBase)`. - pub fn equals_with_inventory_base(&self, o: libremetaverse::InventoryBase) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.InventoryItem.Equals(LibreMetaverse.InventoryBase)" - ) - } - /// C# member: `M:LibreMetaverse.InventoryItem.Equals(LibreMetaverse.InventoryItem)`. - pub fn equals_with_inventory_item(&self, o: libremetaverse::InventoryItem) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.InventoryItem.Equals(LibreMetaverse.InventoryItem)" - ) - } - /// C# member: `M:LibreMetaverse.InventoryItem.Equals(System.Object)`. - pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.InventoryItem.Equals(System.Object)" - ) - } - /// C# member: `M:LibreMetaverse.InventoryItem.FromOSD(LibreMetaverse.StructuredData.OSD)`. - pub fn from_osd( - data: libremetaverse_structured_data::OSD, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryItem.FromOSD(LibreMetaverse.StructuredData.OSD)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryItem.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.InventoryItem.GetHashCode") - } - /// C# member: `M:LibreMetaverse.InventoryItem.GetOSD`. - pub fn get_osd(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.InventoryItem.GetOSD") - } - /// C# member: `M:LibreMetaverse.InventoryItem.IsLink`. - pub fn is_link(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.InventoryItem.IsLink") - } - /// C# member: `M:LibreMetaverse.InventoryItem.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.InventoryItem.ToString") - } - /// C# member: `M:LibreMetaverse.InventoryItem.Update(LibreMetaverse.StructuredData.OSDMap)`. - pub fn update(&self, data: libremetaverse_structured_data::OSDMap) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryItem.Update(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryItem.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.AssetType") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.AssetType`. - pub fn set_asset_type(&mut self, value: libremetaverse_types::AssetType) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.AssetType") - } - /// C# member: `P:LibreMetaverse.InventoryItem.AssetUUID`. - pub fn asset_uuid(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.AssetUUID") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.AssetUUID`. - pub fn set_asset_uuid(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.AssetUUID") - } - /// C# member: `P:LibreMetaverse.InventoryItem.CreationDate`. - pub fn creation_date(&self) -> std::time::SystemTime { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.CreationDate") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.CreationDate`. - pub fn set_creation_date(&mut self, value: std::time::SystemTime) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.CreationDate") - } - /// C# member: `P:LibreMetaverse.InventoryItem.CreatorID`. - pub fn creator_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.CreatorID") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.CreatorID`. - pub fn set_creator_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.CreatorID") - } - /// C# member: `P:LibreMetaverse.InventoryItem.Description`. - pub fn description(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.Description") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.Description`. - pub fn set_description(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.Description") - } - /// C# member: `P:LibreMetaverse.InventoryItem.Flags`. - pub fn flags(&self) -> u32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.Flags") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.Flags`. - pub fn set_flags(&mut self, value: u32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.Flags") - } - /// C# member: `P:LibreMetaverse.InventoryItem.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.GroupID") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.GroupID`. - pub fn set_group_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.GroupID") - } - /// C# member: `P:LibreMetaverse.InventoryItem.GroupOwned`. - pub fn group_owned(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.GroupOwned") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.GroupOwned`. - pub fn set_group_owned(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.GroupOwned") - } - /// C# member: `P:LibreMetaverse.InventoryItem.InventoryType`. - pub fn inventory_type(&self) -> libremetaverse_types::InventoryType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.InventoryType") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.InventoryType`. - pub fn set_inventory_type(&mut self, value: libremetaverse_types::InventoryType) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.InventoryType") - } - /// C# member: `P:LibreMetaverse.InventoryItem.LastOwnerID`. - pub fn last_owner_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.LastOwnerID") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.LastOwnerID`. - pub fn set_last_owner_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.LastOwnerID") - } - /// C# member: `P:LibreMetaverse.InventoryItem.Permissions`. - pub fn permissions(&self) -> libremetaverse::Permissions { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.Permissions") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.Permissions`. - pub fn set_permissions(&mut self, value: libremetaverse::Permissions) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.Permissions") - } - /// C# member: `P:LibreMetaverse.InventoryItem.ResolvedAssetID`. - pub fn resolved_asset_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.ResolvedAssetID") - } - /// C# member: `P:LibreMetaverse.InventoryItem.ResolvedItemID`. - pub fn resolved_item_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.ResolvedItemID") - } - /// C# member: `P:LibreMetaverse.InventoryItem.SalePrice`. - pub fn sale_price(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.SalePrice") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.SalePrice`. - pub fn set_sale_price(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.SalePrice") - } - /// C# member: `P:LibreMetaverse.InventoryItem.SaleType`. - pub fn sale_type(&self) -> libremetaverse_types::SaleType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.SaleType") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.SaleType`. - pub fn set_sale_type(&mut self, value: libremetaverse_types::SaleType) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.SaleType") - } - /// C# member: `P:LibreMetaverse.InventoryItem.TransactionID`. - pub fn transaction_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.TransactionID") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryItem.TransactionID`. - pub fn set_transaction_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryItem.TransactionID") - } -} +/// C# member: `M:LibreMetaverse.InventoryItem.#ctor(LibreMetaverse.InventoryType,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.InventoryItem.#ctor(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.InventoryItem.Equals(LibreMetaverse.InventoryBase)`. +/// C# member: `M:LibreMetaverse.InventoryItem.Equals(LibreMetaverse.InventoryItem)`. +/// C# member: `M:LibreMetaverse.InventoryItem.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.InventoryItem.FromOSD(LibreMetaverse.StructuredData.OSD)`. +/// C# member: `M:LibreMetaverse.InventoryItem.GetHashCode`. +/// C# member: `M:LibreMetaverse.InventoryItem.GetOSD`. +/// C# member: `M:LibreMetaverse.InventoryItem.IsLink`. +/// C# member: `M:LibreMetaverse.InventoryItem.ToString`. +/// C# member: `M:LibreMetaverse.InventoryItem.Update(LibreMetaverse.StructuredData.OSDMap)`. +/// C# member: `P:LibreMetaverse.InventoryItem.AssetType`. +/// C# member: `P:LibreMetaverse.InventoryItem.AssetUUID`. +/// C# member: `P:LibreMetaverse.InventoryItem.CreationDate`. +/// C# member: `P:LibreMetaverse.InventoryItem.CreatorID`. +/// C# member: `P:LibreMetaverse.InventoryItem.Description`. +/// C# member: `P:LibreMetaverse.InventoryItem.Flags`. +/// C# member: `P:LibreMetaverse.InventoryItem.GroupID`. +/// C# member: `P:LibreMetaverse.InventoryItem.GroupOwned`. +/// C# member: `P:LibreMetaverse.InventoryItem.InventoryType`. +/// C# member: `P:LibreMetaverse.InventoryItem.LastOwnerID`. +/// C# member: `P:LibreMetaverse.InventoryItem.Permissions`. +/// C# member: `P:LibreMetaverse.InventoryItem.ResolvedAssetID`. +/// C# member: `P:LibreMetaverse.InventoryItem.ResolvedItemID`. +/// C# member: `P:LibreMetaverse.InventoryItem.SalePrice`. +/// C# member: `P:LibreMetaverse.InventoryItem.SaleType`. +/// C# member: `P:LibreMetaverse.InventoryItem.TransactionID`. +pub use crate::inventory::InventoryItem; /// C# type: `T:LibreMetaverse.InventoryItemFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -12992,44 +12383,13 @@ impl InventoryItemFlags { } /// C# type: `T:LibreMetaverse.InventoryLSL`. -pub struct InventoryLSL { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryLSL { - /// C# member: `M:LibreMetaverse.InventoryLSL.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryLSL.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryLSL.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventoryLSL; /// C# type: `T:LibreMetaverse.InventoryLandmark`. -pub struct InventoryLandmark { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryLandmark { - /// C# member: `M:LibreMetaverse.InventoryLandmark.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryLandmark.#ctor(LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryLandmark.LandmarkVisited`. - pub fn landmark_visited(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryLandmark.LandmarkVisited" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryLandmark.LandmarkVisited`. - pub fn set_landmark_visited(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryLandmark.LandmarkVisited" - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryLandmark.#ctor(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.InventoryLandmark.LandmarkVisited`. +pub use crate::inventory::InventoryLandmark; /// C# type: `T:LibreMetaverse.InventoryManager`. pub struct InventoryManager; @@ -14471,220 +13831,48 @@ impl InventoryManagerFolderUpdateResult { } /// C# type: `T:LibreMetaverse.InventoryMaterial`. -pub struct InventoryMaterial { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryMaterial { - /// C# member: `M:LibreMetaverse.InventoryMaterial.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryMaterial.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryMaterial.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventoryMaterial; /// C# type: `T:LibreMetaverse.InventoryNode`. -pub struct InventoryNode; -impl InventoryNode { - /// C# member: `M:LibreMetaverse.InventoryNode.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.InventoryNode.#ctor") - } - /// C# member: `M:LibreMetaverse.InventoryNode.#ctor(LibreMetaverse.InventoryBase)`. - pub fn new_with_inventory_base( - data: &dyn libremetaverse::InventoryObjectClass, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryNode.#ctor(LibreMetaverse.InventoryBase)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryNode.#ctor(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryNode)`. - pub fn new_with_inventory_base_inventory_node( - data: libremetaverse::InventoryBase, - parent: libremetaverse::InventoryNode, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryNode.#ctor(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryNode)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryNode.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.InventoryNode.ToString") - } - /// C# member: `P:LibreMetaverse.InventoryNode.Data`. - pub fn data(&self) -> Option> { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.Data") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryNode.Data`. - pub fn set_data(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.Data") - } - /// C# member: `P:LibreMetaverse.InventoryNode.ModifyTime`. - pub fn modify_time(&self) -> std::time::SystemTime { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.ModifyTime") - } - /// C# member: `P:LibreMetaverse.InventoryNode.NeedsUpdate`. - pub fn needs_update(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.NeedsUpdate") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryNode.NeedsUpdate`. - pub fn set_needs_update(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.NeedsUpdate") - } - /// C# member: `P:LibreMetaverse.InventoryNode.Nodes`. - pub fn nodes(&self) -> libremetaverse::InventoryNodeDictionary { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.Nodes") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryNode.Nodes`. - pub fn set_nodes(&mut self, value: libremetaverse::InventoryNodeDictionary) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.Nodes") - } - /// C# member: `P:LibreMetaverse.InventoryNode.Parent`. - pub fn parent(&self) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.Parent") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryNode.Parent`. - pub fn set_parent(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNode.Parent") - } -} +/// C# member: `M:LibreMetaverse.InventoryNode.#ctor`. +/// C# member: `M:LibreMetaverse.InventoryNode.#ctor(LibreMetaverse.InventoryBase)`. +/// C# member: `M:LibreMetaverse.InventoryNode.#ctor(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryNode)`. +/// C# member: `M:LibreMetaverse.InventoryNode.ToString`. +/// C# member: `P:LibreMetaverse.InventoryNode.Data`. +/// C# member: `P:LibreMetaverse.InventoryNode.ModifyTime`. +/// C# member: `P:LibreMetaverse.InventoryNode.NeedsUpdate`. +/// C# member: `P:LibreMetaverse.InventoryNode.Nodes`. +/// C# member: `P:LibreMetaverse.InventoryNode.Parent`. +pub use crate::inventory::InventoryNode; /// C# type: `T:LibreMetaverse.InventoryNodeDictionary`. -pub struct InventoryNodeDictionary; -impl InventoryNodeDictionary { - /// C# member: `M:LibreMetaverse.InventoryNodeDictionary.#ctor(LibreMetaverse.InventoryNode)`. - pub fn new(parent: libremetaverse::InventoryNode) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryNodeDictionary.#ctor(LibreMetaverse.InventoryNode)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryNodeDictionary.Add(LibreMetaverse.UUID,LibreMetaverse.InventoryNode)`. - pub fn add( - &self, - key: libremetaverse_types::UUID, - value: libremetaverse::InventoryNode, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryNodeDictionary.Add(LibreMetaverse.UUID,LibreMetaverse.InventoryNode)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryNodeDictionary.Contains(LibreMetaverse.UUID)`. - pub fn contains(&self, key: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryNodeDictionary.Contains(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.InventoryNodeDictionary.Remove(LibreMetaverse.UUID)`. - pub fn remove(&self, key: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryNodeDictionary.Remove(LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Count`. - pub fn count(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNodeDictionary.Count") - } - /// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Item(LibreMetaverse.UUID)`. - pub fn item(&self, key: libremetaverse_types::UUID) -> libremetaverse::InventoryNode { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryNodeDictionary.Item(LibreMetaverse.UUID)" - ) - } - /// Setter for C# member: `P:LibreMetaverse.InventoryNodeDictionary.Item(LibreMetaverse.UUID)`. - pub fn set_item( - &mut self, - key: libremetaverse_types::UUID, - value: libremetaverse::InventoryNode, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryNodeDictionary.Item(LibreMetaverse.UUID)" - ) - } - /// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Keys`. - pub fn keys(&self) -> Vec { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNodeDictionary.Keys") - } - /// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Parent`. - pub fn parent(&self) -> libremetaverse::InventoryNode { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNodeDictionary.Parent") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryNodeDictionary.Parent`. - pub fn set_parent(&mut self, value: libremetaverse::InventoryNode) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNodeDictionary.Parent") - } - /// C# member: `P:LibreMetaverse.InventoryNodeDictionary.SyncRoot`. - pub fn sync_root(&self) -> libremetaverse_types::compat::Object { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryNodeDictionary.SyncRoot" - ) - } - /// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Values`. - pub fn values(&self) -> Vec { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryNodeDictionary.Values") - } -} +/// C# member: `M:LibreMetaverse.InventoryNodeDictionary.#ctor(LibreMetaverse.InventoryNode)`. +/// C# member: `M:LibreMetaverse.InventoryNodeDictionary.Add(LibreMetaverse.UUID,LibreMetaverse.InventoryNode)`. +/// C# member: `M:LibreMetaverse.InventoryNodeDictionary.Contains(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.InventoryNodeDictionary.Remove(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Count`. +/// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Item(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Keys`. +/// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Parent`. +/// C# member: `P:LibreMetaverse.InventoryNodeDictionary.SyncRoot`. +/// C# member: `P:LibreMetaverse.InventoryNodeDictionary.Values`. +pub use crate::inventory::InventoryNodeDictionary; /// C# type: `T:LibreMetaverse.InventoryNotecard`. -pub struct InventoryNotecard { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryNotecard { - /// C# member: `M:LibreMetaverse.InventoryNotecard.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryNotecard.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryNotecard.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventoryNotecard; /// C# type: `T:LibreMetaverse.InventoryObject`. -pub struct InventoryObject { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryObject { - /// C# member: `M:LibreMetaverse.InventoryObject.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryObject.#ctor(LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryObject.AttachPoint`. - pub fn attach_point(&self) -> libremetaverse_types::AttachmentPoint { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryObject.AttachPoint") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryObject.AttachPoint`. - pub fn set_attach_point(&mut self, value: libremetaverse_types::AttachmentPoint) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryObject.AttachPoint") - } - /// C# member: `P:LibreMetaverse.InventoryObject.ItemFlags`. - pub fn item_flags(&self) -> libremetaverse::InventoryItemFlags { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryObject.ItemFlags") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryObject.ItemFlags`. - pub fn set_item_flags(&mut self, value: libremetaverse::InventoryItemFlags) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryObject.ItemFlags") - } -} +/// C# member: `M:LibreMetaverse.InventoryObject.#ctor(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.InventoryObject.AttachPoint`. +/// C# member: `P:LibreMetaverse.InventoryObject.ItemFlags`. +pub use crate::inventory::InventoryObject; /// C# type: `T:LibreMetaverse.InventoryObjectAddedEventArgs`. -pub struct InventoryObjectAddedEventArgs; -impl InventoryObjectAddedEventArgs { - /// C# member: `M:LibreMetaverse.InventoryObjectAddedEventArgs.#ctor(LibreMetaverse.InventoryBase)`. - pub fn new(obj: libremetaverse::InventoryBase) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryObjectAddedEventArgs.#ctor(LibreMetaverse.InventoryBase)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectAddedEventArgs.Obj`. - pub fn obj(&self) -> libremetaverse::InventoryBase { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectAddedEventArgs.Obj" - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryObjectAddedEventArgs.#ctor(LibreMetaverse.InventoryBase)`. +/// C# member: `P:LibreMetaverse.InventoryObjectAddedEventArgs.Obj`. +pub use crate::inventory::InventoryObjectAddedEventArgs; /// C# type: `T:LibreMetaverse.InventoryObjectOfferedEventArgs`. pub struct InventoryObjectOfferedEventArgs; @@ -14752,75 +13940,23 @@ impl InventoryObjectOfferedEventArgs { } /// C# type: `T:LibreMetaverse.InventoryObjectRemovedEventArgs`. -pub struct InventoryObjectRemovedEventArgs; -impl InventoryObjectRemovedEventArgs { - /// C# member: `M:LibreMetaverse.InventoryObjectRemovedEventArgs.#ctor(LibreMetaverse.InventoryBase)`. - pub fn new(obj: libremetaverse::InventoryBase) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryObjectRemovedEventArgs.#ctor(LibreMetaverse.InventoryBase)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectRemovedEventArgs.Obj`. - pub fn obj(&self) -> libremetaverse::InventoryBase { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectRemovedEventArgs.Obj" - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryObjectRemovedEventArgs.#ctor(LibreMetaverse.InventoryBase)`. +/// C# member: `P:LibreMetaverse.InventoryObjectRemovedEventArgs.Obj`. +pub use crate::inventory::InventoryObjectRemovedEventArgs; /// C# type: `T:LibreMetaverse.InventoryObjectUpdatedEventArgs`. -pub struct InventoryObjectUpdatedEventArgs; -impl InventoryObjectUpdatedEventArgs { - /// C# member: `M:LibreMetaverse.InventoryObjectUpdatedEventArgs.#ctor(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryBase)`. - pub fn new( - old_object: libremetaverse::InventoryBase, - new_object: libremetaverse::InventoryBase, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryObjectUpdatedEventArgs.#ctor(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryBase)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectUpdatedEventArgs.NewObject`. - pub fn new_object(&self) -> libremetaverse::InventoryBase { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectUpdatedEventArgs.NewObject" - ) - } - /// C# member: `P:LibreMetaverse.InventoryObjectUpdatedEventArgs.OldObject`. - pub fn old_object(&self) -> libremetaverse::InventoryBase { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InventoryObjectUpdatedEventArgs.OldObject" - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryObjectUpdatedEventArgs.#ctor(LibreMetaverse.InventoryBase,LibreMetaverse.InventoryBase)`. +/// C# member: `P:LibreMetaverse.InventoryObjectUpdatedEventArgs.NewObject`. +/// C# member: `P:LibreMetaverse.InventoryObjectUpdatedEventArgs.OldObject`. +pub use crate::inventory::InventoryObjectUpdatedEventArgs; /// C# type: `T:LibreMetaverse.InventorySettings`. -pub struct InventorySettings { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventorySettings { - /// C# member: `M:LibreMetaverse.InventorySettings.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventorySettings.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventorySettings.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventorySettings; /// C# type: `T:LibreMetaverse.InventorySnapshot`. -pub struct InventorySnapshot { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventorySnapshot { - /// C# member: `M:LibreMetaverse.InventorySnapshot.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventorySnapshot.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventorySnapshot.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventorySnapshot; /// C# type: `T:LibreMetaverse.InventorySortOrder`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -14838,54 +13974,17 @@ impl InventorySortOrder { } /// C# type: `T:LibreMetaverse.InventorySound`. -pub struct InventorySound { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventorySound { - /// C# member: `M:LibreMetaverse.InventorySound.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventorySound.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventorySound.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventorySound; /// C# type: `T:LibreMetaverse.InventoryTexture`. -pub struct InventoryTexture { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryTexture { - /// C# member: `M:LibreMetaverse.InventoryTexture.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryTexture.#ctor(LibreMetaverse.UUID)", - ) - } -} +/// C# member: `M:LibreMetaverse.InventoryTexture.#ctor(LibreMetaverse.UUID)`. +pub use crate::inventory::InventoryTexture; /// C# type: `T:LibreMetaverse.InventoryWearable`. -pub struct InventoryWearable { - /// Rust composition for C# base type `LibreMetaverse.InventoryItem`. - pub base: libremetaverse::InventoryItem, -} -impl InventoryWearable { - /// C# member: `M:LibreMetaverse.InventoryWearable.#ctor(LibreMetaverse.UUID)`. - pub fn new(uuid: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InventoryWearable.#ctor(LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.InventoryWearable.WearableType`. - pub fn wearable_type(&self) -> libremetaverse_types::WearableType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryWearable.WearableType") - } - /// Setter for C# member: `P:LibreMetaverse.InventoryWearable.WearableType`. - pub fn set_wearable_type(&mut self, value: libremetaverse_types::WearableType) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryWearable.WearableType") - } -} +/// C# member: `M:LibreMetaverse.InventoryWearable.#ctor(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.InventoryWearable.WearableType`. +pub use crate::inventory::InventoryWearable; /// C# type: `T:LibreMetaverse.ItemReceivedEventArgs`. pub struct ItemReceivedEventArgs; @@ -19822,97 +18921,25 @@ impl PermissionWho { } /// C# type: `T:LibreMetaverse.Permissions`. -pub struct Permissions { - /// C# member: `F:LibreMetaverse.Permissions.BaseMask`. - pub base_mask: libremetaverse::PermissionMask, - /// C# member: `F:LibreMetaverse.Permissions.EveryoneMask`. - pub everyone_mask: libremetaverse::PermissionMask, - /// C# member: `F:LibreMetaverse.Permissions.GroupMask`. - pub group_mask: libremetaverse::PermissionMask, - /// C# member: `F:LibreMetaverse.Permissions.NextOwnerMask`. - pub next_owner_mask: libremetaverse::PermissionMask, - /// C# member: `F:LibreMetaverse.Permissions.OwnerMask`. - pub owner_mask: libremetaverse::PermissionMask, -} -impl Permissions { - /// C# member: `F:LibreMetaverse.Permissions.FullPermissions`. - pub fn full_permissions() -> libremetaverse::Permissions { - libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Permissions.FullPermissions") - } - /// C# member: `F:LibreMetaverse.Permissions.NoPermissions`. - pub fn no_permissions() -> libremetaverse::Permissions { - libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Permissions.NoPermissions") - } - /// C# member: `M:LibreMetaverse.Permissions.#ctor(System.UInt32,System.UInt32,System.UInt32,System.UInt32,System.UInt32)`. - pub fn new( - base_mask: u32, - everyone_mask: u32, - group_mask: u32, - next_owner_mask: u32, - owner_mask: u32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Permissions.#ctor(System.UInt32,System.UInt32,System.UInt32,System.UInt32,System.UInt32)", - ) - } - /// C# member: `M:LibreMetaverse.Permissions.Equals(LibreMetaverse.Permissions)`. - pub fn equals_with_permissions(&self, other: libremetaverse::Permissions) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Permissions.Equals(LibreMetaverse.Permissions)" - ) - } - /// C# member: `M:LibreMetaverse.Permissions.Equals(System.Object)`. - pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Permissions.Equals(System.Object)" - ) - } - /// C# member: `M:LibreMetaverse.Permissions.FromOSD(LibreMetaverse.StructuredData.OSD)`. - pub fn from_osd( - llsd: libremetaverse_structured_data::OSD, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Permissions.FromOSD(LibreMetaverse.StructuredData.OSD)", - ) - } - /// C# member: `M:LibreMetaverse.Permissions.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Permissions.GetHashCode") - } - /// C# member: `M:LibreMetaverse.Permissions.GetNextPermissions`. - pub fn get_next_permissions(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Permissions.GetNextPermissions") - } - /// C# member: `M:LibreMetaverse.Permissions.GetOSD`. - pub fn get_osd(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Permissions.GetOSD") - } - /// C# member: `M:LibreMetaverse.Permissions.HasPermissions(LibreMetaverse.PermissionMask,LibreMetaverse.PermissionMask)`. - pub fn has_permissions( - perms: libremetaverse::PermissionMask, - check_perms: libremetaverse::PermissionMask, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Permissions.HasPermissions(LibreMetaverse.PermissionMask,LibreMetaverse.PermissionMask)", - ) - } - /// C# member: `M:LibreMetaverse.Permissions.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Permissions.ToString") - } - /// C# member: `M:LibreMetaverse.Permissions.op_Equality(LibreMetaverse.Permissions,LibreMetaverse.Permissions)`. - pub fn eq(lhs: libremetaverse::Permissions, rhs: libremetaverse::Permissions) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Permissions.op_Equality(LibreMetaverse.Permissions,LibreMetaverse.Permissions)" - ) - } - /// C# member: `M:LibreMetaverse.Permissions.op_Inequality(LibreMetaverse.Permissions,LibreMetaverse.Permissions)`. - pub fn ne(lhs: libremetaverse::Permissions, rhs: libremetaverse::Permissions) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Permissions.op_Inequality(LibreMetaverse.Permissions,LibreMetaverse.Permissions)" - ) - } -} +/// C# member: `F:LibreMetaverse.Permissions.BaseMask`. +/// C# member: `F:LibreMetaverse.Permissions.EveryoneMask`. +/// C# member: `F:LibreMetaverse.Permissions.FullPermissions`. +/// C# member: `F:LibreMetaverse.Permissions.GroupMask`. +/// C# member: `F:LibreMetaverse.Permissions.NextOwnerMask`. +/// C# member: `F:LibreMetaverse.Permissions.NoPermissions`. +/// C# member: `F:LibreMetaverse.Permissions.OwnerMask`. +/// C# member: `M:LibreMetaverse.Permissions.#ctor(System.UInt32,System.UInt32,System.UInt32,System.UInt32,System.UInt32)`. +/// C# member: `M:LibreMetaverse.Permissions.Equals(LibreMetaverse.Permissions)`. +/// C# member: `M:LibreMetaverse.Permissions.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.Permissions.FromOSD(LibreMetaverse.StructuredData.OSD)`. +/// C# member: `M:LibreMetaverse.Permissions.GetHashCode`. +/// C# member: `M:LibreMetaverse.Permissions.GetNextPermissions`. +/// C# member: `M:LibreMetaverse.Permissions.GetOSD`. +/// C# member: `M:LibreMetaverse.Permissions.HasPermissions(LibreMetaverse.PermissionMask,LibreMetaverse.PermissionMask)`. +/// C# member: `M:LibreMetaverse.Permissions.ToString`. +/// C# member: `M:LibreMetaverse.Permissions.op_Equality(LibreMetaverse.Permissions,LibreMetaverse.Permissions)`. +/// C# member: `M:LibreMetaverse.Permissions.op_Inequality(LibreMetaverse.Permissions,LibreMetaverse.Permissions)`. +pub use crate::inventory::Permissions; /// C# type: `T:LibreMetaverse.PhysicsPropertiesEventArgs`. pub struct PhysicsPropertiesEventArgs { diff --git a/crates/libremetaverse/src/inventory.rs b/crates/libremetaverse/src/inventory.rs new file mode 100644 index 0000000..42632cc --- /dev/null +++ b/crates/libremetaverse/src/inventory.rs @@ -0,0 +1,2762 @@ +//! Native inventory models, hierarchy store, and bounded cache persistence. +//! +//! The store deliberately owns no transport behavior. Network inventory +//! managers can populate it, but all mutation, indexing, notifications, and +//! persistence remain deterministic local operations. + +#![allow(clippy::missing_errors_doc)] // Public signatures mirror the mapped C# API. +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] + +use crate::agent_manager::EventRegistry; +use crate::{GridClient, InventoryItemFlags, InventoryObjectClass, PermissionMask}; +use libremetaverse_structured_data::{OSD, OSDMap}; +use libremetaverse_types::compat::{CancellationToken, EventHandler, Object, Subscription}; +use libremetaverse_types::{ + AssetType, AttachmentPoint, Error, FolderType, InventoryType, SaleType, UUID, WearableType, +}; +use std::collections::{HashMap, HashSet}; +use std::fmt; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock, Weak}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +const CACHE_MAGIC: &[u8; 8] = b"INVCACHE"; +const CACHE_VERSION: u32 = 1; +const MAX_CACHE_BYTES: u64 = 64 * 1024 * 1024; +const MAX_CACHE_RECORDS: usize = 1_000_000; +const MAX_STRING_BYTES: usize = 1024 * 1024; +const MAX_HIERARCHY_DEPTH: usize = 512; + +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) +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Permissions { + pub base_mask: PermissionMask, + pub everyone_mask: PermissionMask, + pub group_mask: PermissionMask, + pub next_owner_mask: PermissionMask, + pub owner_mask: PermissionMask, +} + +impl Default for Permissions { + fn default() -> Self { + Self::no_permissions() + } +} + +impl Permissions { + pub fn full_permissions() -> Self { + Self { + base_mask: PermissionMask::ALL, + everyone_mask: PermissionMask::ALL, + group_mask: PermissionMask::ALL, + next_owner_mask: PermissionMask::ALL, + owner_mask: PermissionMask::ALL, + } + } + + pub const fn no_permissions() -> Self { + Self { + base_mask: PermissionMask::NONE, + everyone_mask: PermissionMask::NONE, + group_mask: PermissionMask::NONE, + next_owner_mask: PermissionMask::NONE, + owner_mask: PermissionMask::NONE, + } + } + + pub const fn new( + base_mask: u32, + everyone_mask: u32, + group_mask: u32, + next_owner_mask: u32, + owner_mask: u32, + ) -> Result { + Ok(Self { + base_mask: PermissionMask(base_mask), + everyone_mask: PermissionMask(everyone_mask), + group_mask: PermissionMask(group_mask), + next_owner_mask: PermissionMask(next_owner_mask), + owner_mask: PermissionMask(owner_mask), + }) + } + + pub fn equals_with_permissions(&self, other: Self) -> bool { + *self == other + } + + pub fn equals_with_object(&self, obj: Option) -> bool { + obj.as_ref() + .and_then(Object::downcast_ref::) + .is_some_and(|other| self == other) + } + + pub fn from_osd(llsd: OSD) -> Result { + let OSD::Map(map) = llsd else { + return Ok(Self::default()); + }; + Ok(Self { + base_mask: PermissionMask(osd_u32(&map, "base_mask")?), + everyone_mask: PermissionMask(osd_u32(&map, "everyone_mask")?), + group_mask: PermissionMask(osd_u32(&map, "group_mask")?), + next_owner_mask: PermissionMask(osd_u32(&map, "next_owner_mask")?), + owner_mask: PermissionMask(osd_u32(&map, "owner_mask")?), + }) + } + + pub fn get_hash_code(&self) -> i32 { + (self.base_mask.0 + ^ self.everyone_mask.0 + ^ self.group_mask.0 + ^ self.next_owner_mask.0 + ^ self.owner_mask.0) + .cast_signed() + } + + pub fn get_next_permissions(&self) -> Result { + let next = self.next_owner_mask.0; + Self::new( + self.base_mask.0 & next, + self.everyone_mask.0 & next, + self.group_mask.0 & next, + next, + self.owner_mask.0 & next, + ) + } + + pub fn get_osd(&self) -> Result { + Ok(OSD::Map(HashMap::from([ + ( + "base_mask".into(), + OSD::Integer(self.base_mask.0.cast_signed()), + ), + ( + "everyone_mask".into(), + OSD::Integer(self.everyone_mask.0.cast_signed()), + ), + ( + "group_mask".into(), + OSD::Integer(self.group_mask.0.cast_signed()), + ), + ( + "next_owner_mask".into(), + OSD::Integer(self.next_owner_mask.0.cast_signed()), + ), + ( + "owner_mask".into(), + OSD::Integer(self.owner_mask.0.cast_signed()), + ), + ]))) + } + + pub fn has_permissions( + perms: PermissionMask, + check_perms: PermissionMask, + ) -> Result { + Ok(perms.0 & check_perms.0 == check_perms.0) + } + + #[allow(clippy::should_implement_trait)] // C# operator mapping requires this name. + pub fn eq(lhs: Self, rhs: Self) -> bool { + lhs == rhs + } + + pub fn ne(lhs: Self, rhs: Self) -> bool { + lhs != rhs + } +} + +impl fmt::Display for Permissions { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + formatter, + "Base: {:?}, Everyone: {:?}, Group: {:?}, NextOwner: {:?}, Owner: {:?}", + self.base_mask, + self.everyone_mask, + self.group_mask, + self.next_owner_mask, + self.owner_mask + ) + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct InventoryBase { + uuid: UUID, + parent_uuid: UUID, + name: String, + owner_id: UUID, +} + +impl InventoryBase { + fn new(uuid: UUID) -> Self { + Self { + uuid, + parent_uuid: UUID::zero(), + name: String::new(), + owner_id: UUID::zero(), + } + } + + pub fn equals_with_inventory_base(&self, other: Self) -> bool { + *self == other + } + + pub fn equals_with_object(&self, obj: Option) -> bool { + obj.as_ref() + .and_then(Object::downcast_ref::) + .is_some_and(|other| self == other) + } + + pub fn get_hash_code(&self) -> i32 { + self.uuid.get_hash_code() + } + + pub fn get_osd(&self) -> Result { + Ok(OSD::Map(HashMap::from([ + ("item_id".into(), OSD::UUID(self.uuid)), + ("parent_id".into(), OSD::UUID(self.parent_uuid)), + ("name".into(), OSD::String(self.name.clone())), + ("owner_id".into(), OSD::UUID(self.owner_id)), + ]))) + } + + pub fn name(&self) -> String { + self.name.clone() + } + + pub fn set_name(&mut self, value: String) { + self.name = value; + } + + pub const fn owner_id(&self) -> UUID { + self.owner_id + } + + pub fn set_owner_id(&mut self, value: UUID) { + self.owner_id = value; + } + + pub const fn parent_uuid(&self) -> UUID { + self.parent_uuid + } + + pub fn set_parent_uuid(&mut self, value: UUID) { + self.parent_uuid = value; + } + + pub const fn uuid(&self) -> UUID { + self.uuid + } + + pub fn set_uuid(&mut self, value: UUID) { + self.uuid = value; + } +} + +#[derive(Clone, Debug)] +pub struct InventoryItem { + pub base: InventoryBase, + asset_uuid: UUID, + permissions: Permissions, + asset_type: AssetType, + inventory_type: InventoryType, + creator_id: UUID, + description: String, + group_id: UUID, + group_owned: bool, + sale_price: i32, + sale_type: SaleType, + flags: u32, + creation_date: SystemTime, + transaction_id: UUID, + last_owner_id: UUID, +} + +impl PartialEq for InventoryItem { + fn eq(&self, other: &Self) -> bool { + self.base == other.base + && self.asset_type == other.asset_type + && self.asset_uuid == other.asset_uuid + && self.creation_date == other.creation_date + && self.description == other.description + && self.flags == other.flags + && self.group_id == other.group_id + && self.group_owned == other.group_owned + && self.inventory_type == other.inventory_type + && self.permissions == other.permissions + && self.sale_price == other.sale_price + && self.sale_type == other.sale_type + && self.last_owner_id == other.last_owner_id + } +} + +impl Eq for InventoryItem {} + +impl InventoryItem { + fn with_type(inventory_type: InventoryType, uuid: UUID) -> Self { + Self { + base: InventoryBase::new(uuid), + asset_uuid: UUID::zero(), + permissions: Permissions::default(), + asset_type: AssetType::Unknown, + inventory_type, + creator_id: UUID::zero(), + description: String::new(), + group_id: UUID::zero(), + group_owned: false, + sale_price: 0, + sale_type: SaleType::Not, + flags: 0, + creation_date: UNIX_EPOCH, + transaction_id: UUID::zero(), + last_owner_id: UUID::zero(), + } + } + + pub fn new_with_inventory_type_uuid( + type_: InventoryType, + item_id: UUID, + ) -> Result { + Ok(Self::with_type(type_, item_id)) + } + + pub fn new_with_uuid(uuid: UUID) -> Result { + Ok(Self::with_type(InventoryType::UNKNOWN, uuid)) + } + + pub fn equals_with_inventory_base(&self, other: InventoryBase) -> bool { + self.base == other + } + + pub fn equals_with_inventory_item(&self, other: Self) -> bool { + *self == other + } + + pub fn equals_with_object(&self, obj: Option) -> bool { + obj.as_ref() + .and_then(Object::downcast_ref::) + .is_some_and(|other| self == other) + } + + pub fn from_osd(data: OSD) -> Result { + let OSD::Map(map) = data else { + return Err(Error::Argument); + }; + let inventory_type = parse_inventory_type(map.get("inv_type"))?; + let asset_type = parse_asset_type(map.get("type"))?; + let corrected_type = if inventory_type == InventoryType::TEXTURE + && matches!(asset_type, AssetType::Object | AssetType::Mesh) + { + InventoryType::ATTACHMENT + } else { + inventory_type + }; + let mut item = Self::with_type(corrected_type, osd_uuid(&map, "item_id")?); + item.apply_map(&map)?; + Ok(item) + } + + pub fn get_hash_code(&self) -> i32 { + self.base.get_hash_code() + } + + pub fn get_osd(&self) -> Result { + let OSD::Map(mut permissions) = self.permissions.get_osd()? else { + unreachable!("Permissions always serializes as a map"); + }; + permissions.insert("creator_id".into(), OSD::UUID(self.creator_id)); + permissions.insert("last_owner_id".into(), OSD::UUID(self.last_owner_id)); + permissions.insert("is_owner_group".into(), OSD::Boolean(self.group_owned)); + permissions.insert("group_id".into(), OSD::UUID(self.group_id)); + Ok(OSD::Map(HashMap::from([ + ("item_id".into(), OSD::UUID(self.base.uuid)), + ("parent_id".into(), OSD::UUID(self.base.parent_uuid)), + ( + "type".into(), + OSD::Integer(i32::from(self.asset_type as i8)), + ), + ( + "inv_type".into(), + OSD::Integer(i32::from(self.inventory_type.0)), + ), + ("flags".into(), OSD::Integer(self.flags.cast_signed())), + ("name".into(), OSD::String(self.base.name.clone())), + ("desc".into(), OSD::String(self.description.clone())), + ("asset_id".into(), OSD::UUID(self.asset_uuid)), + ("created_at".into(), OSD::Date(self.creation_date)), + ("permissions".into(), OSD::Map(permissions)), + ( + "sale_info".into(), + OSD::Map(HashMap::from([ + ("sale_price".into(), OSD::Integer(self.sale_price)), + ( + "sale_type".into(), + OSD::Integer(i32::from(self.sale_type as u8)), + ), + ])), + ), + ]))) + } + + pub fn is_link(&self) -> Result { + Ok(matches!( + self.asset_type, + AssetType::Link | AssetType::LinkFolder + )) + } + + pub fn update(&mut self, data: OSDMap) -> Result<(), Error> { + self.apply_map(&data.snapshot()) + } + + fn apply_map(&mut self, map: &HashMap) -> Result<(), Error> { + if let Some(value) = map.get("item_id") { + self.base.uuid = value.as_uuid()?; + } + if let Some(value) = map.get("parent_id") { + self.base.parent_uuid = value.as_uuid()?; + } + if let Some(value) = map.get("agent_id") { + self.base.owner_id = value.as_uuid()?; + } + if let Some(value) = map.get("name") { + self.base.name = value.as_string()?; + } + if let Some(value) = map.get("desc") { + self.description = value.as_string()?; + } + if let Some(value) = map.get("permissions") { + self.permissions = Permissions::from_osd(value.clone())?; + if let OSD::Map(perms) = value { + self.base.owner_id = + optional_uuid(perms, "owner_id")?.unwrap_or(self.base.owner_id); + self.creator_id = optional_uuid(perms, "creator_id")?.unwrap_or(self.creator_id); + self.last_owner_id = + optional_uuid(perms, "last_owner_id")?.unwrap_or(self.last_owner_id); + self.group_id = optional_uuid(perms, "group_id")?.unwrap_or(self.group_id); + if let Some(group_owned) = perms.get("is_owner_group") { + self.group_owned = group_owned.as_boolean()?; + } + } + } + if let Some(OSD::Map(sale)) = map.get("sale_info") { + self.sale_price = osd_i32(sale, "sale_price")?; + self.sale_type = sale_type_from_i32(osd_i32(sale, "sale_type")?)?; + } + if let Some(value) = map.get("shadow_id") { + self.asset_uuid = decrypt_shadow_id(value.as_uuid()?)?; + } + if let Some(value) = map.get("asset_id") { + self.asset_uuid = value.as_uuid()?; + } + if let Some(value) = map.get("linked_id") { + self.asset_uuid = value.as_uuid()?; + } + if let Some(value) = map.get("type") { + let value = parse_asset_type(Some(value))?; + if value != AssetType::Unknown { + self.asset_type = value; + } + } + if let Some(value) = map.get("inv_type") { + let value = parse_inventory_type(Some(value))?; + if value != InventoryType::UNKNOWN { + self.inventory_type = value; + } + } + if let Some(value) = map.get("flags") { + self.flags = value.as_u_integer()?; + } + if let Some(value) = map.get("created_at") { + self.creation_date = match value { + OSD::Date(value) => *value, + _ => UNIX_EPOCH + Duration::from_secs(u64::from(value.as_u_integer()?)), + }; + } + Ok(()) + } + + pub const fn asset_type(&self) -> AssetType { + self.asset_type + } + pub fn set_asset_type(&mut self, value: AssetType) { + self.asset_type = value; + } + pub const fn asset_uuid(&self) -> UUID { + self.asset_uuid + } + pub fn set_asset_uuid(&mut self, value: UUID) { + self.asset_uuid = value; + } + pub fn creation_date(&self) -> SystemTime { + self.creation_date + } + pub fn set_creation_date(&mut self, value: SystemTime) { + self.creation_date = value; + } + pub const fn creator_id(&self) -> UUID { + self.creator_id + } + pub fn set_creator_id(&mut self, value: UUID) { + self.creator_id = value; + } + pub fn description(&self) -> String { + self.description.clone() + } + pub fn set_description(&mut self, value: String) { + self.description = value; + } + pub const fn flags(&self) -> u32 { + self.flags + } + pub fn set_flags(&mut self, value: u32) { + self.flags = value; + } + pub const fn group_id(&self) -> UUID { + self.group_id + } + pub fn set_group_id(&mut self, value: UUID) { + self.group_id = value; + } + pub const fn group_owned(&self) -> bool { + self.group_owned + } + pub fn set_group_owned(&mut self, value: bool) { + self.group_owned = value; + } + pub const fn inventory_type(&self) -> InventoryType { + self.inventory_type + } + pub fn set_inventory_type(&mut self, value: InventoryType) { + self.inventory_type = value; + } + pub const fn last_owner_id(&self) -> UUID { + self.last_owner_id + } + pub fn set_last_owner_id(&mut self, value: UUID) { + self.last_owner_id = value; + } + pub const fn permissions(&self) -> Permissions { + self.permissions + } + pub fn set_permissions(&mut self, value: Permissions) { + self.permissions = value; + } + pub fn resolved_asset_id(&self) -> UUID { + if self.is_link().unwrap_or(false) { + UUID::zero() + } else { + self.asset_uuid + } + } + pub fn resolved_item_id(&self) -> UUID { + if self.is_link().unwrap_or(false) { + self.asset_uuid + } else { + self.base.uuid + } + } + pub const fn sale_price(&self) -> i32 { + self.sale_price + } + pub fn set_sale_price(&mut self, value: i32) { + self.sale_price = value; + } + pub const fn sale_type(&self) -> SaleType { + self.sale_type + } + pub fn set_sale_type(&mut self, value: SaleType) { + self.sale_type = value; + } + pub const fn transaction_id(&self) -> UUID { + self.transaction_id + } + pub fn set_transaction_id(&mut self, value: UUID) { + self.transaction_id = value; + } +} + +impl fmt::Display for InventoryItem { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + formatter, + "{:?} {} ({} {}) '{}'/'{}' {}", + self.asset_type, + self.asset_uuid, + inventory_type_name(self.inventory_type), + self.base.uuid, + self.base.name, + self.description, + self.permissions + ) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InventoryFolder { + pub base: InventoryBase, + descendent_count: i32, + preferred_type: FolderType, + version: i32, +} + +impl InventoryFolder { + pub const VERSION_UNKNOWN: i32 = -1; + + pub fn new(uuid: UUID) -> Result { + Ok(Self { + base: InventoryBase::new(uuid), + descendent_count: 0, + preferred_type: FolderType::None, + version: 1, + }) + } + + pub fn equals_with_inventory_base(&self, other: InventoryBase) -> bool { + self.base == other + } + pub fn equals_with_inventory_folder(&self, other: Self) -> bool { + *self == other + } + pub fn equals_with_object(&self, obj: Option) -> bool { + obj.as_ref() + .and_then(Object::downcast_ref::) + .is_some_and(|other| self == other) + } + pub fn from_osd(data: OSD) -> Result { + let OSD::Map(map) = data else { + return Err(Error::Argument); + }; + let id = if map.contains_key("category_id") { + osd_uuid(&map, "category_id")? + } else { + osd_uuid(&map, "folder_id")? + }; + let mut folder = Self::new(id)?; + folder.version = Self::VERSION_UNKNOWN; + folder.apply_map(&map)?; + Ok(folder) + } + pub fn get_hash_code(&self) -> i32 { + self.base.get_hash_code() + } + pub fn get_osd(&self) -> Result { + Ok(OSD::Map(HashMap::from([ + ("item_id".into(), OSD::UUID(self.base.uuid)), + ("version".into(), OSD::Integer(self.version)), + ("parent_id".into(), OSD::UUID(self.base.parent_uuid)), + ( + "type".into(), + OSD::Integer(i32::from(self.preferred_type as i8)), + ), + ("name".into(), OSD::String(self.base.name.clone())), + ]))) + } + pub fn update(&mut self, data: OSDMap) -> Result<(), Error> { + self.apply_map(&data.snapshot()) + } + fn apply_map(&mut self, map: &HashMap) -> Result<(), Error> { + if let Some(value) = map.get("category_id") { + self.base.uuid = value.as_uuid()?; + } + if let Some(value) = map.get("folder_id") { + self.base.uuid = value.as_uuid()?; + } + if let Some(value) = map.get("version") { + self.version = value.as_integer()?; + } + if let Some(value) = map.get("parent_id") { + self.base.parent_uuid = value.as_uuid()?; + } + if let Some(value) = map.get("type_default").or_else(|| map.get("type")) { + self.preferred_type = folder_type_from_i32(value.as_integer()?)?; + } + if let Some(value) = map.get("descendents") { + self.descendent_count = value.as_integer()?; + } + if let Some(value) = map.get("owner_id").or_else(|| map.get("agent_id")) { + self.base.owner_id = value.as_uuid()?; + } + if let Some(value) = map.get("name") { + self.base.name = value.as_string()?; + } + Ok(()) + } + pub const fn descendent_count(&self) -> i32 { + self.descendent_count + } + pub fn set_descendent_count(&mut self, value: i32) { + self.descendent_count = value; + } + pub const fn preferred_type(&self) -> FolderType { + self.preferred_type + } + pub fn set_preferred_type(&mut self, value: FolderType) { + self.preferred_type = value; + } + pub const fn version(&self) -> i32 { + self.version + } + pub fn set_version(&mut self, value: i32) { + self.version = value; + } +} + +impl fmt::Display for InventoryFolder { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(&self.base.name) + } +} + +macro_rules! simple_inventory_item { + ($name:ident, $inventory_type:expr) => { + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct $name { + pub base: InventoryItem, + } + impl $name { + pub fn new(uuid: UUID) -> Result { + Ok(Self { + base: InventoryItem::with_type($inventory_type, uuid), + }) + } + } + }; +} + +simple_inventory_item!(InventoryAnimation, InventoryType::ANIMATION); +simple_inventory_item!(InventoryCallingCard, InventoryType::CALLING_CARD); +simple_inventory_item!(InventoryCategory, InventoryType::CATEGORY); +simple_inventory_item!(InventoryGesture, InventoryType::GESTURE); +simple_inventory_item!(InventoryLSL, InventoryType::LSL); +simple_inventory_item!(InventoryMaterial, InventoryType::MATERIAL); +simple_inventory_item!(InventoryNotecard, InventoryType::NOTECARD); +simple_inventory_item!(InventorySettings, InventoryType::SETTINGS); +simple_inventory_item!(InventorySnapshot, InventoryType::SNAPSHOT); +simple_inventory_item!(InventorySound, InventoryType::SOUND); +simple_inventory_item!(InventoryTexture, InventoryType::TEXTURE); + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InventoryLandmark { + pub base: InventoryItem, +} +impl InventoryLandmark { + pub fn new(uuid: UUID) -> Result { + Ok(Self { + base: InventoryItem::with_type(InventoryType::LANDMARK, uuid), + }) + } + pub const fn landmark_visited(&self) -> bool { + self.base.flags & 1 != 0 + } + pub fn set_landmark_visited(&mut self, value: bool) { + if value { + self.base.flags |= 1; + } else { + self.base.flags &= !1; + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InventoryObject { + pub base: InventoryItem, +} +impl InventoryObject { + pub fn new(uuid: UUID) -> Result { + Ok(Self { + base: InventoryItem::with_type(InventoryType::OBJECT, uuid), + }) + } + pub const fn attach_point(&self) -> AttachmentPoint { + attachment_point_from_u8(self.base.flags.to_le_bytes()[0]) + } + pub fn set_attach_point(&mut self, value: AttachmentPoint) { + self.base.flags = u32::from(value as u8) | (self.base.flags & 0xffff_ff00); + } + pub const fn item_flags(&self) -> InventoryItemFlags { + InventoryItemFlags(self.base.flags & !0xff) + } + pub fn set_item_flags(&mut self, value: InventoryItemFlags) { + self.base.flags = value.0 | (self.base.flags & 0xff); + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InventoryAttachment { + pub base: InventoryItem, +} +impl InventoryAttachment { + pub fn new(uuid: UUID) -> Result { + Ok(Self { + base: InventoryItem::with_type(InventoryType::ATTACHMENT, uuid), + }) + } + pub const fn attachment_point(&self) -> AttachmentPoint { + attachment_point_from_u8(self.base.flags.to_le_bytes()[0]) + } + pub fn set_attachment_point(&mut self, value: AttachmentPoint) { + self.base.flags = u32::from(value as u8); + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InventoryWearable { + pub base: InventoryItem, +} +impl InventoryWearable { + pub fn new(uuid: UUID) -> Result { + Ok(Self { + base: InventoryItem::with_type(InventoryType::WEARABLE, uuid), + }) + } + pub const fn wearable_type(&self) -> WearableType { + wearable_type_from_u8(self.base.flags.to_le_bytes()[0]) + } + pub fn set_wearable_type(&mut self, value: WearableType) { + self.base.flags = u32::from(value as u8); + } +} + +#[derive(Clone, Debug)] +enum InventoryValue { + Base(InventoryBase), + Folder(InventoryFolder), + Item(InventoryItem), + Animation(InventoryAnimation), + Attachment(InventoryAttachment), + CallingCard(InventoryCallingCard), + Category(InventoryCategory), + Gesture(InventoryGesture), + Lsl(InventoryLSL), + Landmark(InventoryLandmark), + Material(InventoryMaterial), + Notecard(InventoryNotecard), + Object(InventoryObject), + Settings(InventorySettings), + Snapshot(InventorySnapshot), + Sound(InventorySound), + Texture(InventoryTexture), + Wearable(InventoryWearable), +} + +impl InventoryValue { + fn from_class(value: &dyn InventoryObjectClass) -> Option { + macro_rules! downcast { + ($type:ty, $variant:ident) => { + if let Some(value) = value.as_any().downcast_ref::<$type>() { + return Some(Self::$variant(value.clone())); + } + }; + } + downcast!(InventoryBase, Base); + downcast!(InventoryFolder, Folder); + downcast!(InventoryItem, Item); + downcast!(InventoryAnimation, Animation); + downcast!(InventoryAttachment, Attachment); + downcast!(InventoryCallingCard, CallingCard); + downcast!(InventoryCategory, Category); + downcast!(InventoryGesture, Gesture); + downcast!(InventoryLSL, Lsl); + downcast!(InventoryLandmark, Landmark); + downcast!(InventoryMaterial, Material); + downcast!(InventoryNotecard, Notecard); + downcast!(InventoryObject, Object); + downcast!(InventorySettings, Settings); + downcast!(InventorySnapshot, Snapshot); + downcast!(InventorySound, Sound); + downcast!(InventoryTexture, Texture); + downcast!(InventoryWearable, Wearable); + None + } + + fn base(&self) -> &InventoryBase { + match self { + Self::Base(value) => value, + Self::Folder(value) => &value.base, + Self::Item(value) => &value.base, + Self::Animation(value) => &value.base.base, + Self::Attachment(value) => &value.base.base, + Self::CallingCard(value) => &value.base.base, + Self::Category(value) => &value.base.base, + Self::Gesture(value) => &value.base.base, + Self::Lsl(value) => &value.base.base, + Self::Landmark(value) => &value.base.base, + Self::Material(value) => &value.base.base, + Self::Notecard(value) => &value.base.base, + Self::Object(value) => &value.base.base, + Self::Settings(value) => &value.base.base, + Self::Snapshot(value) => &value.base.base, + Self::Sound(value) => &value.base.base, + Self::Texture(value) => &value.base.base, + Self::Wearable(value) => &value.base.base, + } + } + + fn item(&self) -> Option<&InventoryItem> { + match self { + Self::Item(value) => Some(value), + Self::Animation(value) => Some(&value.base), + Self::Attachment(value) => Some(&value.base), + Self::CallingCard(value) => Some(&value.base), + Self::Category(value) => Some(&value.base), + Self::Gesture(value) => Some(&value.base), + Self::Lsl(value) => Some(&value.base), + Self::Landmark(value) => Some(&value.base), + Self::Material(value) => Some(&value.base), + Self::Notecard(value) => Some(&value.base), + Self::Object(value) => Some(&value.base), + Self::Settings(value) => Some(&value.base), + Self::Snapshot(value) => Some(&value.base), + Self::Sound(value) => Some(&value.base), + Self::Texture(value) => Some(&value.base), + Self::Wearable(value) => Some(&value.base), + Self::Base(_) | Self::Folder(_) => None, + } + } + + fn folder(&self) -> Option<&InventoryFolder> { + match self { + Self::Folder(value) => Some(value), + _ => None, + } + } + + fn folder_mut(&mut self) -> Option<&mut InventoryFolder> { + match self { + Self::Folder(value) => Some(value), + _ => None, + } + } + + fn boxed(&self) -> Box { + match self { + Self::Base(value) => Box::new(value.clone()), + Self::Folder(value) => Box::new(value.clone()), + Self::Item(value) => Box::new(value.clone()), + Self::Animation(value) => Box::new(value.clone()), + Self::Attachment(value) => Box::new(value.clone()), + Self::CallingCard(value) => Box::new(value.clone()), + Self::Category(value) => Box::new(value.clone()), + Self::Gesture(value) => Box::new(value.clone()), + Self::Lsl(value) => Box::new(value.clone()), + Self::Landmark(value) => Box::new(value.clone()), + Self::Material(value) => Box::new(value.clone()), + Self::Notecard(value) => Box::new(value.clone()), + Self::Object(value) => Box::new(value.clone()), + Self::Settings(value) => Box::new(value.clone()), + Self::Snapshot(value) => Box::new(value.clone()), + Self::Sound(value) => Box::new(value.clone()), + Self::Texture(value) => Box::new(value.clone()), + Self::Wearable(value) => Box::new(value.clone()), + } + } + + fn clone_as(&self) -> Option { + self.boxed().as_any().downcast_ref::().cloned() + } + + fn tag(&self) -> u8 { + match self { + Self::Base(_) => 0, + Self::Folder(_) => 1, + Self::Item(_) => 2, + Self::Animation(_) => 3, + Self::Attachment(_) => 4, + Self::CallingCard(_) => 5, + Self::Category(_) => 6, + Self::Gesture(_) => 7, + Self::Lsl(_) => 8, + Self::Landmark(_) => 9, + Self::Material(_) => 10, + Self::Notecard(_) => 11, + Self::Object(_) => 12, + Self::Settings(_) => 13, + Self::Snapshot(_) => 14, + Self::Sound(_) => 15, + Self::Texture(_) => 16, + Self::Wearable(_) => 17, + } + } +} + +#[derive(Clone)] +pub struct InventoryNode { + inner: Arc>, +} + +#[derive(Debug)] +struct InventoryNodeInner { + data: Option, + parent: Option>>, + children: HashMap, + needs_update: bool, +} + +impl fmt::Debug for InventoryNode { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let inner = read(&self.inner); + formatter + .debug_struct("InventoryNode") + .field("uuid", &inner.data.as_ref().map(|value| value.base().uuid)) + .field("children", &inner.children.len()) + .field("needs_update", &inner.needs_update) + .finish() + } +} + +impl InventoryNode { + fn empty() -> Self { + Self { + inner: Arc::new(RwLock::new(InventoryNodeInner { + data: None, + parent: None, + children: HashMap::new(), + needs_update: true, + })), + } + } + + fn from_value(data: InventoryValue) -> Self { + let node = Self::empty(); + write(&node.inner).data = Some(data); + node + } + + pub fn new_with_constructor() -> Result { + Ok(Self::empty()) + } + + pub fn new_with_inventory_base(data: &dyn InventoryObjectClass) -> Result { + Ok(Self::from_value( + InventoryValue::from_class(data).ok_or(Error::Argument)?, + )) + } + + pub fn new_with_inventory_base_inventory_node( + data: InventoryBase, + parent: Self, + ) -> Result { + let mut node = Self::from_value(InventoryValue::Base(data)); + node.set_parent(Some(parent.clone())); + let uuid = node.uuid().ok_or(Error::Argument)?; + write(&parent.inner).children.insert(uuid, node.clone()); + Ok(node) + } + + fn uuid(&self) -> Option { + read(&self.inner) + .data + .as_ref() + .map(|value| value.base().uuid) + } + + fn value(&self) -> Option { + read(&self.inner).data.clone() + } + + fn set_value(&self, value: InventoryValue) { + write(&self.inner).data = Some(value); + } + + pub fn data(&self) -> Option> { + self.value().map(|value| value.boxed()) + } + + pub fn set_data(&mut self, value: Option>) { + write(&self.inner).data = value.as_deref().and_then(InventoryValue::from_class); + } + + pub fn modify_time(&self) -> SystemTime { + let inner = read(&self.inner); + let Some(data) = inner.data.as_ref() else { + return UNIX_EPOCH; + }; + if let Some(item) = data.item() { + return item.creation_date; + } + if data.folder().is_some() { + let children: Vec<_> = inner.children.values().cloned().collect(); + drop(inner); + return children + .into_iter() + .filter_map(|child| child.value()) + .filter_map(|value| value.item().map(InventoryItem::creation_date)) + .max() + .unwrap_or(UNIX_EPOCH); + } + UNIX_EPOCH + } + + pub fn needs_update(&self) -> bool { + read(&self.inner).needs_update + } + pub fn set_needs_update(&mut self, value: bool) { + write(&self.inner).needs_update = value; + } + pub fn nodes(&self) -> InventoryNodeDictionary { + InventoryNodeDictionary { + parent: self.clone(), + } + } + pub fn set_nodes(&mut self, value: InventoryNodeDictionary) { + let children = read(&value.parent.inner).children.clone(); + write(&self.inner).children = children; + self.reparent_children(); + } + pub fn parent(&self) -> Option { + read(&self.inner) + .parent + .as_ref() + .and_then(Weak::upgrade) + .map(|inner| Self { inner }) + } + pub fn set_parent(&mut self, value: Option) { + write(&self.inner).parent = value.as_ref().map(|parent| Arc::downgrade(&parent.inner)); + } + fn reparent_children(&self) { + let children: Vec<_> = read(&self.inner).children.values().cloned().collect(); + for mut child in children { + child.set_parent(Some(self.clone())); + } + } +} + +impl fmt::Display for InventoryNode { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let Some(value) = self.value() else { + return formatter.write_str("[Empty Node]"); + }; + if let Some(folder) = value.folder() { + write!(formatter, "{folder}") + } else if let Some(item) = value.item() { + write!(formatter, "{item}") + } else { + formatter.write_str(&value.base().name()) + } + } +} + +#[derive(Clone, Debug)] +pub struct InventoryNodeDictionary { + parent: InventoryNode, +} +impl InventoryNodeDictionary { + pub fn new(parent: InventoryNode) -> Result { + Ok(Self { parent }) + } + pub fn add(&self, key: UUID, mut value: InventoryNode) -> Result<(), Error> { + value.set_parent(Some(self.parent.clone())); + write(&self.parent.inner).children.insert(key, value); + Ok(()) + } + pub fn contains(&self, key: UUID) -> Result { + Ok(read(&self.parent.inner).children.contains_key(&key)) + } + pub fn remove(&self, key: UUID) -> Result<(), Error> { + if let Some(mut removed) = write(&self.parent.inner).children.remove(&key) { + removed.set_parent(None); + } + Ok(()) + } + pub fn count(&self) -> i32 { + i32::try_from(read(&self.parent.inner).children.len()).unwrap_or(i32::MAX) + } + pub fn item(&self, key: UUID) -> InventoryNode { + read(&self.parent.inner).children[&key].clone() + } + pub fn set_item(&mut self, key: UUID, value: InventoryNode) { + let _ = self.add(key, value); + } + pub fn keys(&self) -> Vec { + read(&self.parent.inner).children.keys().copied().collect() + } + pub fn parent(&self) -> InventoryNode { + self.parent.clone() + } + pub fn set_parent(&mut self, value: InventoryNode) { + self.parent = value; + self.parent.reparent_children(); + } + pub fn sync_root(&self) -> Object { + Object::opaque(self.clone()) + } + pub fn values(&self) -> Vec { + read(&self.parent.inner) + .children + .values() + .cloned() + .collect() + } +} + +#[derive(Clone, Debug)] +pub struct InventoryObjectAddedEventArgs { + value: InventoryValue, +} +impl InventoryObjectAddedEventArgs { + pub fn new(obj: InventoryBase) -> Result { + Ok(Self { + value: InventoryValue::Base(obj), + }) + } + fn from_value(value: InventoryValue) -> Self { + Self { value } + } + pub fn obj(&self) -> InventoryBase { + self.value.base().clone() + } + pub fn object(&self) -> Box { + self.value.boxed() + } +} + +#[derive(Clone, Debug)] +pub struct InventoryObjectRemovedEventArgs { + value: InventoryValue, +} +impl InventoryObjectRemovedEventArgs { + pub fn new(obj: InventoryBase) -> Result { + Ok(Self { + value: InventoryValue::Base(obj), + }) + } + fn from_value(value: InventoryValue) -> Self { + Self { value } + } + pub fn obj(&self) -> InventoryBase { + self.value.base().clone() + } + pub fn object(&self) -> Box { + self.value.boxed() + } +} + +#[derive(Clone, Debug)] +pub struct InventoryObjectUpdatedEventArgs { + old: InventoryValue, + new: InventoryValue, +} +impl InventoryObjectUpdatedEventArgs { + pub fn new(old_object: InventoryBase, new_object: InventoryBase) -> Result { + Ok(Self { + old: InventoryValue::Base(old_object), + new: InventoryValue::Base(new_object), + }) + } + fn from_values(old: InventoryValue, new: InventoryValue) -> Self { + Self { old, new } + } + pub fn old_object(&self) -> InventoryBase { + self.old.base().clone() + } + pub fn new_object(&self) -> InventoryBase { + self.new.base().clone() + } + pub fn old_value(&self) -> Box { + self.old.boxed() + } + pub fn new_value(&self) -> Box { + self.new.boxed() + } +} + +#[derive(Default)] +struct InventoryState { + items: HashMap, + links: HashMap>, + root: Option, + library_root: Option, + root_node: Option, + library_root_node: Option, +} + +struct InventoryInner { + owner: UUID, + _client: Weak, + state: RwLock, + added: EventRegistry, + removed: EventRegistry, + updated: EventRegistry, +} + +#[derive(Clone)] +pub struct Inventory { + inner: Arc, +} + +impl fmt::Debug for Inventory { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("Inventory") + .field("owner", &self.owner()) + .field("count", &self.count()) + .finish() + } +} + +impl Inventory { + pub fn subscribe_inventory_object_added( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.added.subscribe(handler) + } + pub fn subscribe_inventory_object_removed( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.removed.subscribe(handler) + } + pub fn subscribe_inventory_object_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.updated.subscribe(handler) + } + + pub fn new_with_grid_client(client: Arc) -> Result { + let owner = client.network().native_agent_id(); + Self::new_with_grid_client_uuid(client, owner) + } + + pub fn new_with_grid_client_uuid(client: Arc, owner: UUID) -> Result { + Ok(Self { + inner: Arc::new(InventoryInner { + owner, + _client: Arc::downgrade(&client), + state: RwLock::new(InventoryState::default()), + added: EventRegistry::default(), + removed: EventRegistry::default(), + updated: EventRegistry::default(), + }), + }) + } + + pub fn clear(&self) -> Result<(), Error> { + let mut state = write(&self.inner.state); + state.items.clear(); + state.links.clear(); + Ok(()) + } + pub fn contains_with_inventory_base( + &self, + obj: &dyn InventoryObjectClass, + ) -> Result { + self.contains_with_uuid(obj.inventory_base().uuid) + } + pub fn contains_with_uuid(&self, uuid: UUID) -> Result { + Ok(read(&self.inner.state).items.contains_key(&uuid)) + } + + pub fn find_all_links(&self, asset_id: UUID) -> Result, Error> { + if asset_id == UUID::zero() { + return Ok(Vec::new()); + } + let state = read(&self.inner.state); + Ok(state + .links + .get(&asset_id) + .into_iter() + .flatten() + .filter_map(|uuid| state.items.get(uuid).cloned()) + .collect()) + } + + pub fn get_contents_with_inventory_folder( + &self, + folder: &InventoryFolder, + ) -> Result>, Error> { + self.get_contents_with_uuid(folder.base.uuid) + } + pub fn get_contents_with_uuid( + &self, + folder: UUID, + ) -> Result>, Error> { + let state = read(&self.inner.state); + let node = state.items.get(&folder).ok_or(Error::InvalidOperation)?; + if node + .value() + .and_then(|value| value.folder().cloned()) + .is_none() + { + return Err(Error::InvalidOperation); + } + Ok(read(&node.inner) + .children + .values() + .filter_map(InventoryNode::value) + .map(|value| value.boxed()) + .collect()) + } + + pub fn get_contents_sorted_with_uuid( + &self, + folder: UUID, + order: crate::InventorySortOrder, + ) -> Result>, Error> { + let mut values = self.get_contents_with_uuid(folder)?; + values.sort_by(|left, right| compare_inventory(left.as_ref(), right.as_ref(), order)); + Ok(values) + } + + pub fn find_folder_for_type(&self, folder_type: FolderType) -> Option { + let state = read(&self.inner.state); + let root = state.root?; + state + .items + .values() + .filter_map(InventoryNode::value) + .filter_map(|value| value.folder().cloned()) + .find(|folder| folder.base.parent_uuid == root && folder.preferred_type == folder_type) + .map(|folder| folder.base.uuid) + } + + pub fn get_node_for(&self, uuid: UUID) -> Result { + read(&self.inner.state) + .items + .get(&uuid) + .cloned() + .ok_or(Error::InvalidOperation) + } + pub fn get_node_or_default(&self, uuid: UUID) -> Result, Error> { + Ok(read(&self.inner.state).items.get(&uuid).cloned()) + } + pub fn try_get_node_for(&self, uuid: UUID, node: &mut Option) -> bool { + *node = read(&self.inner.state).items.get(&uuid).cloned(); + node.is_some() + } + + pub fn get_value_or_default_with_uuid( + &self, + uuid: UUID, + ) -> Result, Error> { + Ok(read(&self.inner.state) + .items + .get(&uuid) + .and_then(InventoryNode::value) + .map(|value| value.base().clone())) + } + pub fn get_value_or_default_with_uuid_a7c63fbe( + &self, + uuid: UUID, + ) -> Result, Error> { + Ok(read(&self.inner.state) + .items + .get(&uuid) + .and_then(InventoryNode::value) + .and_then(|value| value.clone_as::())) + } + + pub fn try_get_value_with_uuid_inventory_base( + &self, + uuid: UUID, + item: &mut Option>, + ) -> Result { + *item = read(&self.inner.state) + .items + .get(&uuid) + .and_then(InventoryNode::value) + .map(|value| value.boxed()); + Ok(item.is_some()) + } + pub fn try_get_value_with_uuid_t( + &self, + uuid: UUID, + item: &mut Option, + ) -> bool { + *item = read(&self.inner.state) + .items + .get(&uuid) + .and_then(InventoryNode::value) + .and_then(|value| value.clone_as::()); + item.is_some() + } + + pub fn update_node_for(&self, item: &dyn InventoryObjectClass) -> Result<(), Error> { + let value = InventoryValue::from_class(item).ok_or(Error::Argument)?; + let uuid = value.base().uuid; + let parent_uuid = value.base().parent_uuid; + let event = { + let mut state = write(&self.inner.state); + if parent_uuid != UUID::zero() && !state.items.contains_key(&parent_uuid) { + let mut fake = InventoryFolder::new(parent_uuid)?; + fake.version = InventoryFolder::VERSION_UNKNOWN; + state.items.insert( + parent_uuid, + InventoryNode::from_value(InventoryValue::Folder(fake)), + ); + } + let event = if let Some(node) = state.items.get(&uuid) { + let old = node.value(); + node.set_value(value.clone()); + old.map(|old| InventoryObjectUpdatedEventArgs::from_values(old, value.clone())) + } else { + state + .items + .insert(uuid, InventoryNode::from_value(value.clone())); + None + }; + rebuild_indexes_and_counts(&mut state); + event + }; + if let Some(event) = event { + self.inner.updated.emit(event); + } else { + self.inner + .added + .emit(InventoryObjectAddedEventArgs::from_value(value)); + } + Ok(()) + } + + pub fn remove_node_for(&self, item: &dyn InventoryObjectClass) -> Result<(), Error> { + let uuid = item.inventory_base().uuid; + let removed = { + let mut state = write(&self.inner.state); + let Some(root) = state.items.get(&uuid).and_then(InventoryNode::value) else { + return Ok(()); + }; + let mut pending = vec![uuid]; + let mut ids = HashSet::new(); + while let Some(current) = pending.pop() { + if ids.len() >= MAX_CACHE_RECORDS || !ids.insert(current) { + continue; + } + if let Some(node) = state.items.get(¤t) { + pending.extend(read(&node.inner).children.keys().copied()); + } + } + for id in ids { + state.items.remove(&id); + } + if state.root == Some(uuid) { + state.root = None; + state.root_node = None; + } + if state.library_root == Some(uuid) { + state.library_root = None; + state.library_root_node = None; + } + rebuild_indexes_and_counts(&mut state); + root + }; + self.inner + .removed + .emit(InventoryObjectRemovedEventArgs::from_value(removed)); + Ok(()) + } + + pub fn count(&self) -> i32 { + i32::try_from(read(&self.inner.state).items.len()).unwrap_or(i32::MAX) + } + /// Returns the item with the requested identifier. + /// + /// # Panics + /// + /// Panics when the identifier is not present, matching the indexed C# + /// inventory accessor. + pub fn item(&self, uuid: UUID) -> InventoryBase { + self.get_value_or_default_with_uuid(uuid) + .ok() + .flatten() + .unwrap_or_else(|| panic!("unknown inventory item: {uuid}")) + } + pub fn set_item(&mut self, _uuid: UUID, value: InventoryBase) { + let _ = self.update_node_for(&value); + } + pub fn owner(&self) -> UUID { + self.inner.owner + } + + pub fn root_folder(&self) -> Option { + read(&self.inner.state) + .root_node + .as_ref() + .and_then(InventoryNode::value) + .and_then(|value| value.folder().cloned()) + } + pub fn set_root_folder(&mut self, value: Option) { + let Some(value) = value else { + return; + }; + let uuid = value.base.uuid; + if self.update_node_for(&value).is_ok() { + let mut state = write(&self.inner.state); + state.root = Some(uuid); + state.root_node = state.items.get(&uuid).cloned(); + } + } + pub fn root_node(&self) -> InventoryNode { + read(&self.inner.state) + .root_node + .clone() + .unwrap_or_else(InventoryNode::empty) + } + pub fn set_root_node(&mut self, value: InventoryNode) { + if let Some(uuid) = value.uuid() { + let mut state = write(&self.inner.state); + state.items.insert(uuid, value.clone()); + state.root = Some(uuid); + state.root_node = Some(value); + rebuild_indexes_and_counts(&mut state); + } + } + + pub fn library_folder(&self) -> Option { + read(&self.inner.state) + .library_root_node + .as_ref() + .and_then(InventoryNode::value) + .and_then(|value| value.folder().cloned()) + } + pub fn set_library_folder(&mut self, value: Option) { + let Some(value) = value else { + return; + }; + let uuid = value.base.uuid; + if self.update_node_for(&value).is_ok() { + let mut state = write(&self.inner.state); + state.library_root = Some(uuid); + state.library_root_node = state.items.get(&uuid).cloned(); + } + } + pub fn library_root_node(&self) -> InventoryNode { + read(&self.inner.state) + .library_root_node + .clone() + .unwrap_or_else(InventoryNode::empty) + } + pub fn set_library_root_node(&mut self, value: InventoryNode) { + if let Some(uuid) = value.uuid() { + let mut state = write(&self.inner.state); + state.items.insert(uuid, value.clone()); + state.library_root = Some(uuid); + state.library_root_node = Some(value); + rebuild_indexes_and_counts(&mut state); + } + } +} + +fn rebuild_indexes_and_counts(state: &mut InventoryState) { + state.links.clear(); + let nodes: Vec<_> = state.items.values().cloned().collect(); + for node in &nodes { + let mut inner = write(&node.inner); + inner.children.clear(); + inner.parent = None; + if let Some(folder) = inner.data.as_mut().and_then(InventoryValue::folder_mut) { + folder.descendent_count = 0; + } + } + for node in &nodes { + let Some(value) = node.value() else { + continue; + }; + if let Some(item) = value.item() + && item.is_link().unwrap_or(false) + && item.asset_uuid != UUID::zero() + { + state + .links + .entry(item.asset_uuid) + .or_default() + .insert(item.base.uuid); + } + let parent_uuid = value.base().parent_uuid; + if parent_uuid == UUID::zero() + || parent_uuid == value.base().uuid + || would_create_cycle(state, value.base().uuid, parent_uuid) + { + continue; + } + if let Some(parent) = state.items.get(&parent_uuid) { + write(&parent.inner) + .children + .insert(value.base().uuid, node.clone()); + write(&node.inner).parent = Some(Arc::downgrade(&parent.inner)); + } + } + for node in &nodes { + let Some(value) = node.value() else { + continue; + }; + if value.item().is_none() { + continue; + } + let mut parent = value.base().parent_uuid; + let mut visited = HashSet::new(); + for _ in 0..MAX_HIERARCHY_DEPTH { + if parent == UUID::zero() || !visited.insert(parent) { + break; + } + let Some(parent_node) = state.items.get(&parent) else { + break; + }; + let next = { + let mut inner = write(&parent_node.inner); + let Some(parent_value) = inner.data.as_mut() else { + break; + }; + if let Some(folder) = parent_value.folder_mut() { + folder.descendent_count = folder.descendent_count.saturating_add(1); + } + parent_value.base().parent_uuid + }; + parent = next; + } + } +} + +fn would_create_cycle(state: &InventoryState, child: UUID, mut parent: UUID) -> bool { + let mut visited = HashSet::new(); + for _ in 0..MAX_HIERARCHY_DEPTH { + if parent == child { + return true; + } + if parent == UUID::zero() || !visited.insert(parent) { + return false; + } + let Some(value) = state.items.get(&parent).and_then(InventoryNode::value) else { + return false; + }; + parent = value.base().parent_uuid; + } + true +} + +fn compare_inventory( + left: &dyn InventoryObjectClass, + right: &dyn InventoryObjectClass, + order: crate::InventorySortOrder, +) -> std::cmp::Ordering { + let left_folder = left.as_any().downcast_ref::(); + let right_folder = right.as_any().downcast_ref::(); + let system_top = order.0 & crate::InventorySortOrder::SYSTEM_FOLDERS_TO_TOP.0 != 0; + if system_top { + let left_system = + left_folder.is_some_and(|folder| folder.preferred_type != FolderType::None); + let right_system = + right_folder.is_some_and(|folder| folder.preferred_type != FolderType::None); + if left_system != right_system { + return right_system.cmp(&left_system); + } + } + if left_folder.is_some() != right_folder.is_some() { + return right_folder.is_some().cmp(&left_folder.is_some()); + } + let by_date = order.0 & crate::InventorySortOrder::BY_DATE.0 != 0 + && !(order.0 & crate::InventorySortOrder::FOLDERS_BY_NAME.0 != 0 && left_folder.is_some()); + if by_date { + let date = |value: &dyn InventoryObjectClass| { + InventoryValue::from_class(value) + .and_then(|value| value.item().map(InventoryItem::creation_date)) + .unwrap_or(UNIX_EPOCH) + }; + date(right).cmp(&date(left)).then_with(|| { + left.inventory_base() + .name + .to_lowercase() + .cmp(&right.inventory_base().name.to_lowercase()) + }) + } else { + left.inventory_base() + .name + .to_lowercase() + .cmp(&right.inventory_base().name.to_lowercase()) + } +} + +impl Inventory { + pub fn save_to_disk_with_string(&self, filename: String) -> Result<(), Error> { + let snapshot = self.cache_snapshot()?; + save_cache_atomic(Path::new(&filename), &snapshot) + } + + pub async fn save_to_disk_with_string_cancellation_token( + &self, + filename: String, + cancellation_token: Option, + ) -> Result<(), Error> { + check_cancelled(cancellation_token.as_ref())?; + let snapshot = self.cache_snapshot()?; + check_cancelled(cancellation_token.as_ref())?; + run_blocking(move || save_cache_atomic(Path::new(&filename), &snapshot)).await?; + check_cancelled(cancellation_token.as_ref()) + } + + pub fn restore_from_disk_with_string(&self, filename: String) -> Result { + match load_cache(Path::new(&filename)) { + Ok(snapshot) => self.install_snapshot(snapshot), + Err(_) => Ok(-1), + } + } + + pub async fn restore_from_disk_with_string_cancellation_token( + &self, + filename: String, + cancellation_token: Option, + ) -> Result { + check_cancelled(cancellation_token.as_ref())?; + let snapshot = run_blocking(move || load_cache(Path::new(&filename))).await?; + check_cancelled(cancellation_token.as_ref())?; + self.install_snapshot(snapshot) + } + + fn cache_snapshot(&self) -> Result { + let state = read(&self.inner.state); + if state.items.len() > MAX_CACHE_RECORDS { + return Err(Error::InvalidOperation); + } + let mut values: Vec<_> = state + .items + .values() + .filter_map(InventoryNode::value) + .collect(); + values.sort_unstable_by_key(|value| value.base().uuid); + Ok(CacheSnapshot { + owner: self.inner.owner, + root: state.root, + library_root: state.library_root, + values, + }) + } + + fn install_snapshot(&self, snapshot: CacheSnapshot) -> Result { + if snapshot.owner != UUID::zero() + && self.inner.owner != UUID::zero() + && snapshot.owner != self.inner.owner + { + return Err(Error::InvalidOperation); + } + let restored_items = snapshot + .values + .iter() + .filter(|value| value.item().is_some()) + .count(); + let mut state = InventoryState::default(); + for value in snapshot.values { + let uuid = value.base().uuid; + if uuid == UUID::zero() + || state + .items + .insert(uuid, InventoryNode::from_value(value)) + .is_some() + { + return Err(Error::Parse { + position: 0, + context: "duplicate or zero inventory UUID", + }); + } + } + state.root = snapshot.root.filter(|uuid| state.items.contains_key(uuid)); + state.library_root = snapshot + .library_root + .filter(|uuid| state.items.contains_key(uuid)); + state.root_node = state.root.and_then(|uuid| state.items.get(&uuid).cloned()); + state.library_root_node = state + .library_root + .and_then(|uuid| state.items.get(&uuid).cloned()); + rebuild_indexes_and_counts(&mut state); + *write(&self.inner.state) = state; + i32::try_from(restored_items).map_err(|_| Error::InvalidOperation) + } +} + +struct CacheSnapshot { + owner: UUID, + root: Option, + library_root: Option, + values: Vec, +} + +async fn run_blocking(operation: F) -> Result +where + T: Send + 'static, + F: FnOnce() -> Result + Send + 'static, +{ + let (sender, receiver) = futures_channel::oneshot::channel(); + std::thread::Builder::new() + .name("libremetaverse-inventory-io".into()) + .spawn(move || { + let _ = sender.send(operation()); + }) + .map_err(|_| Error::InvalidOperation)?; + receiver.await.map_err(|_| Error::InvalidOperation)? +} + +fn check_cancelled(token: Option<&CancellationToken>) -> Result<(), Error> { + token.map_or(Ok(()), CancellationToken::throw_if_cancellation_requested) +} + +fn save_cache_atomic(path: &Path, snapshot: &CacheSnapshot) -> Result<(), Error> { + let bytes = encode_cache(snapshot)?; + if bytes.len() as u64 > MAX_CACHE_BYTES { + return Err(Error::InvalidOperation); + } + let parent = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| Path::new(".")); + let name = path + .file_name() + .and_then(|name| name.to_str()) + .ok_or(Error::Argument)?; + let nonce = UUID::random()?.to_string(); + let temporary = parent.join(format!(".{name}.{nonce}.tmp")); + let result = (|| { + let mut file = OpenOptions::new() + .create_new(true) + .write(true) + .open(&temporary) + .map_err(|_| Error::InvalidOperation)?; + file.write_all(&bytes) + .map_err(|_| Error::InvalidOperation)?; + file.sync_all().map_err(|_| Error::InvalidOperation)?; + drop(file); + replace_file(&temporary, path)?; + if let Ok(directory) = File::open(parent) { + let _ = directory.sync_all(); + } + Ok(()) + })(); + if result.is_err() { + let _ = fs::remove_file(&temporary); + } + result +} + +fn replace_file(temporary: &Path, destination: &Path) -> Result<(), Error> { + match fs::rename(temporary, destination) { + Ok(()) => Ok(()), + Err(_) if destination.exists() => { + let backup = backup_path(destination)?; + fs::rename(destination, &backup).map_err(|_| Error::InvalidOperation)?; + if fs::rename(temporary, destination).is_ok() { + let _ = fs::remove_file(backup); + Ok(()) + } else { + let _ = fs::rename(backup, destination); + Err(Error::InvalidOperation) + } + } + Err(_) => Err(Error::InvalidOperation), + } +} + +fn backup_path(destination: &Path) -> Result { + let parent = destination.parent().unwrap_or_else(|| Path::new(".")); + let name = destination + .file_name() + .and_then(|name| name.to_str()) + .ok_or(Error::Argument)?; + Ok(parent.join(format!(".{name}.{}.bak", UUID::random()?.to_string()))) +} + +fn load_cache(path: &Path) -> Result { + let metadata = fs::metadata(path).map_err(|_| Error::InvalidOperation)?; + if metadata.len() > MAX_CACHE_BYTES { + return Err(Error::InvalidOperation); + } + let capacity = usize::try_from(metadata.len()).map_err(|_| Error::InvalidOperation)?; + let mut bytes = Vec::with_capacity(capacity); + File::open(path) + .map(|file| file.take(MAX_CACHE_BYTES + 1)) + .and_then(|mut file| file.read_to_end(&mut bytes)) + .map_err(|_| Error::InvalidOperation)?; + if bytes.len() as u64 > MAX_CACHE_BYTES { + return Err(Error::InvalidOperation); + } + decode_cache(&bytes) +} + +fn encode_cache(snapshot: &CacheSnapshot) -> Result, Error> { + let mut output = Vec::new(); + cache_extend(&mut output, CACHE_MAGIC)?; + cache_extend(&mut output, &CACHE_VERSION.to_le_bytes())?; + write_uuid(&mut output, snapshot.owner)?; + write_optional_uuid(&mut output, snapshot.root)?; + write_optional_uuid(&mut output, snapshot.library_root)?; + cache_extend( + &mut output, + &u32::try_from(snapshot.values.len()) + .map_err(|_| Error::InvalidOperation)? + .to_le_bytes(), + )?; + for value in &snapshot.values { + encode_value(&mut output, value)?; + } + Ok(output) +} + +fn encode_value(output: &mut Vec, value: &InventoryValue) -> Result<(), Error> { + cache_push(output, value.tag())?; + let base = value.base(); + write_uuid(output, base.uuid)?; + write_uuid(output, base.parent_uuid)?; + write_uuid(output, base.owner_id)?; + write_string(output, &base.name)?; + if let Some(folder) = value.folder() { + cache_push(output, (folder.preferred_type as i8).cast_unsigned())?; + cache_extend(output, &folder.version.to_le_bytes())?; + cache_extend(output, &folder.descendent_count.to_le_bytes())?; + } else if let Some(item) = value.item() { + write_uuid(output, item.asset_uuid)?; + for mask in [ + item.permissions.base_mask, + item.permissions.everyone_mask, + item.permissions.group_mask, + item.permissions.next_owner_mask, + item.permissions.owner_mask, + ] { + cache_extend(output, &mask.0.to_le_bytes())?; + } + cache_push(output, (item.asset_type as i8).cast_unsigned())?; + cache_push(output, item.inventory_type.0.cast_unsigned())?; + write_uuid(output, item.creator_id)?; + write_string(output, &item.description)?; + write_uuid(output, item.group_id)?; + cache_push(output, u8::from(item.group_owned))?; + cache_extend(output, &item.sale_price.to_le_bytes())?; + cache_push(output, item.sale_type as u8)?; + cache_extend(output, &item.flags.to_le_bytes())?; + let (seconds, nanos) = system_time_parts(item.creation_date)?; + cache_extend(output, &seconds.to_le_bytes())?; + cache_extend(output, &nanos.to_le_bytes())?; + write_uuid(output, item.transaction_id)?; + write_uuid(output, item.last_owner_id)?; + } + Ok(()) +} + +fn decode_cache(bytes: &[u8]) -> Result { + let mut input = CacheReader { bytes, position: 0 }; + if input.take(CACHE_MAGIC.len())? != CACHE_MAGIC { + return input.error("invalid inventory cache magic"); + } + if input.u32()? != CACHE_VERSION { + return input.error("unsupported inventory cache version"); + } + let owner = input.uuid()?; + let root = input.optional_uuid()?; + let library_root = input.optional_uuid()?; + let count = usize::try_from(input.u32()?).map_err(|_| Error::InvalidOperation)?; + if count > MAX_CACHE_RECORDS { + return input.error("inventory cache record limit exceeded"); + } + let mut values = Vec::with_capacity(count); + for _ in 0..count { + values.push(decode_value(&mut input)?); + } + if input.position != bytes.len() { + return input.error("trailing inventory cache bytes"); + } + Ok(CacheSnapshot { + owner, + root, + library_root, + values, + }) +} + +fn decode_value(input: &mut CacheReader<'_>) -> Result { + let tag = input.u8()?; + let base = InventoryBase { + uuid: input.uuid()?, + parent_uuid: input.uuid()?, + owner_id: input.uuid()?, + name: input.string()?, + }; + if tag == 0 { + return Ok(InventoryValue::Base(base)); + } + if tag == 1 { + return Ok(InventoryValue::Folder(InventoryFolder { + base, + preferred_type: folder_type_from_i32(i32::from(input.i8()?))?, + version: input.i32()?, + descendent_count: input.i32()?, + })); + } + let item = InventoryItem { + base, + asset_uuid: input.uuid()?, + permissions: Permissions { + base_mask: PermissionMask(input.u32()?), + everyone_mask: PermissionMask(input.u32()?), + group_mask: PermissionMask(input.u32()?), + next_owner_mask: PermissionMask(input.u32()?), + owner_mask: PermissionMask(input.u32()?), + }, + asset_type: asset_type_from_i32(i32::from(input.i8()?))?, + inventory_type: InventoryType(input.i8()?), + creator_id: input.uuid()?, + description: input.string()?, + group_id: input.uuid()?, + group_owned: input.u8()? != 0, + sale_price: input.i32()?, + sale_type: sale_type_from_i32(i32::from(input.u8()?))?, + flags: input.u32()?, + creation_date: system_time_from_parts(input.i64()?, input.u32()?)?, + transaction_id: input.uuid()?, + last_owner_id: input.uuid()?, + }; + Ok(match tag { + 2 => InventoryValue::Item(item), + 3 => InventoryValue::Animation(InventoryAnimation { base: item }), + 4 => InventoryValue::Attachment(InventoryAttachment { base: item }), + 5 => InventoryValue::CallingCard(InventoryCallingCard { base: item }), + 6 => InventoryValue::Category(InventoryCategory { base: item }), + 7 => InventoryValue::Gesture(InventoryGesture { base: item }), + 8 => InventoryValue::Lsl(InventoryLSL { base: item }), + 9 => InventoryValue::Landmark(InventoryLandmark { base: item }), + 10 => InventoryValue::Material(InventoryMaterial { base: item }), + 11 => InventoryValue::Notecard(InventoryNotecard { base: item }), + 12 => InventoryValue::Object(InventoryObject { base: item }), + 13 => InventoryValue::Settings(InventorySettings { base: item }), + 14 => InventoryValue::Snapshot(InventorySnapshot { base: item }), + 15 => InventoryValue::Sound(InventorySound { base: item }), + 16 => InventoryValue::Texture(InventoryTexture { base: item }), + 17 => InventoryValue::Wearable(InventoryWearable { base: item }), + _ => return input.error("unknown inventory cache record tag"), + }) +} + +struct CacheReader<'a> { + bytes: &'a [u8], + position: usize, +} +impl<'a> CacheReader<'a> { + fn error(&self, context: &'static str) -> Result { + Err(Error::Parse { + position: self.position, + context, + }) + } + fn take(&mut self, count: usize) -> Result<&'a [u8], Error> { + let end = self + .position + .checked_add(count) + .ok_or(Error::InvalidOperation)?; + let result = self.bytes.get(self.position..end).ok_or(Error::Parse { + position: self.position, + context: "truncated inventory cache", + })?; + self.position = end; + Ok(result) + } + fn u8(&mut self) -> Result { + Ok(self.take(1)?[0]) + } + fn i8(&mut self) -> Result { + Ok(self.u8()?.cast_signed()) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes( + self.take(4)? + .try_into() + .map_err(|_| Error::InvalidOperation)?, + )) + } + fn i32(&mut self) -> Result { + Ok(i32::from_le_bytes( + self.take(4)? + .try_into() + .map_err(|_| Error::InvalidOperation)?, + )) + } + fn i64(&mut self) -> Result { + Ok(i64::from_le_bytes( + self.take(8)? + .try_into() + .map_err(|_| Error::InvalidOperation)?, + )) + } + fn uuid(&mut self) -> Result { + UUID::new_with_bytes_int32(self.take(16)?.to_vec(), 0) + } + fn optional_uuid(&mut self) -> Result, Error> { + Ok(if self.u8()? == 0 { + None + } else { + Some(self.uuid()?) + }) + } + fn string(&mut self) -> Result { + let count = usize::try_from(self.u32()?).map_err(|_| Error::InvalidOperation)?; + if count > MAX_STRING_BYTES { + return self.error("inventory cache string limit exceeded"); + } + String::from_utf8(self.take(count)?.to_vec()).map_err(|_| Error::Parse { + position: self.position, + context: "invalid inventory cache UTF-8", + }) + } +} + +fn write_uuid(output: &mut Vec, value: UUID) -> Result<(), Error> { + cache_extend(output, &value.get_bytes()?) +} +fn write_optional_uuid(output: &mut Vec, value: Option) -> Result<(), Error> { + cache_push(output, u8::from(value.is_some()))?; + if let Some(value) = value { + write_uuid(output, value)?; + } + Ok(()) +} +fn write_string(output: &mut Vec, value: &str) -> Result<(), Error> { + if value.len() > MAX_STRING_BYTES { + return Err(Error::InvalidOperation); + } + cache_extend( + output, + &u32::try_from(value.len()) + .map_err(|_| Error::InvalidOperation)? + .to_le_bytes(), + )?; + cache_extend(output, value.as_bytes()) +} + +fn cache_push(output: &mut Vec, value: u8) -> Result<(), Error> { + cache_extend(output, &[value]) +} + +fn cache_extend(output: &mut Vec, value: &[u8]) -> Result<(), Error> { + let length = output + .len() + .checked_add(value.len()) + .ok_or(Error::InvalidOperation)?; + if length > usize::try_from(MAX_CACHE_BYTES).map_err(|_| Error::InvalidOperation)? { + return Err(Error::InvalidOperation); + } + output.extend_from_slice(value); + Ok(()) +} + +fn system_time_parts(value: SystemTime) -> Result<(i64, u32), Error> { + match value.duration_since(UNIX_EPOCH) { + Ok(duration) => Ok(( + i64::try_from(duration.as_secs()).map_err(|_| Error::InvalidOperation)?, + duration.subsec_nanos(), + )), + Err(error) => { + let duration = error.duration(); + let seconds = i64::try_from(duration.as_secs()).map_err(|_| Error::InvalidOperation)?; + if duration.subsec_nanos() == 0 { + Ok((-seconds, 0)) + } else { + Ok(( + seconds + .checked_neg() + .and_then(|value| value.checked_sub(1)) + .ok_or(Error::InvalidOperation)?, + 1_000_000_000 - duration.subsec_nanos(), + )) + } + } + } +} +fn system_time_from_parts(seconds: i64, nanos: u32) -> Result { + if nanos >= 1_000_000_000 { + return Err(Error::Argument); + } + if seconds >= 0 { + Ok(UNIX_EPOCH + Duration::new(u64::try_from(seconds).map_err(|_| Error::Argument)?, nanos)) + } else { + UNIX_EPOCH + .checked_sub(Duration::from_secs(seconds.unsigned_abs())) + .and_then(|value| value.checked_add(Duration::from_nanos(u64::from(nanos)))) + .ok_or(Error::Argument) + } +} + +fn osd_i32(map: &HashMap, key: &str) -> Result { + map.get(key).map_or(Ok(0), OSD::as_integer) +} +fn osd_u32(map: &HashMap, key: &str) -> Result { + map.get(key).map_or(Ok(0), OSD::as_u_integer) +} +fn osd_uuid(map: &HashMap, key: &str) -> Result { + map.get(key).ok_or(Error::Argument)?.as_uuid() +} +fn optional_uuid(map: &HashMap, key: &str) -> Result, Error> { + map.get(key).map(OSD::as_uuid).transpose() +} + +fn parse_asset_type(value: Option<&OSD>) -> Result { + match value { + None => Ok(AssetType::Unknown), + Some(OSD::String(value)) => Ok(match value.to_ascii_lowercase().as_str() { + "texture" => AssetType::Texture, + "sound" => AssetType::Sound, + "callcard" | "callingcard" => AssetType::CallingCard, + "landmark" => AssetType::Landmark, + "script" => AssetType::Script, + "clothing" => AssetType::Clothing, + "object" => AssetType::Object, + "notecard" => AssetType::Notecard, + "category" | "folder" => AssetType::Folder, + "lsltext" => AssetType::LSLText, + "lslbyte" | "lslbytecode" => AssetType::LSLBytecode, + "txtr_tga" => AssetType::TextureTGA, + "bodypart" => AssetType::Bodypart, + "snd_wav" => AssetType::SoundWAV, + "img_tga" => AssetType::ImageTGA, + "jpeg" | "img_jpeg" => AssetType::ImageJPEG, + "animatn" | "animation" => AssetType::Animation, + "gesture" => AssetType::Gesture, + "simstate" => AssetType::Simstate, + "link" => AssetType::Link, + "link_f" | "linkfolder" => AssetType::LinkFolder, + "mesh" => AssetType::Mesh, + "settings" => AssetType::Settings, + "material" => AssetType::Material, + _ => AssetType::Unknown, + }), + Some(value) => asset_type_from_i32(value.as_integer()?), + } +} + +fn parse_inventory_type(value: Option<&OSD>) -> Result { + match value { + None => Ok(InventoryType::UNKNOWN), + Some(OSD::String(value)) => Ok(match value.to_ascii_lowercase().as_str() { + "texture" => InventoryType::TEXTURE, + "sound" => InventoryType::SOUND, + "callcard" | "callingcard" => InventoryType::CALLING_CARD, + "landmark" => InventoryType::LANDMARK, + "object" => InventoryType::OBJECT, + "notecard" => InventoryType::NOTECARD, + "category" | "folder" => InventoryType::CATEGORY, + "root" | "rootcategory" => InventoryType::ROOT_CATEGORY, + "lsl" | "script" => InventoryType::LSL, + "snapshot" => InventoryType::SNAPSHOT, + "attach" | "attachment" => InventoryType::ATTACHMENT, + "wearable" => InventoryType::WEARABLE, + "animation" => InventoryType::ANIMATION, + "gesture" => InventoryType::GESTURE, + "mesh" => InventoryType::MESH, + "settings" => InventoryType::SETTINGS, + "material" => InventoryType::MATERIAL, + _ => InventoryType::UNKNOWN, + }), + Some(value) => Ok(InventoryType( + i8::try_from(value.as_integer()?).map_err(|_| Error::Argument)?, + )), + } +} + +fn inventory_type_name(value: InventoryType) -> String { + match value { + InventoryType::UNKNOWN => "Unknown".into(), + InventoryType::TEXTURE => "Texture".into(), + InventoryType::SOUND => "Sound".into(), + InventoryType::CALLING_CARD => "CallingCard".into(), + InventoryType::LANDMARK => "Landmark".into(), + InventoryType::OBJECT => "Object".into(), + InventoryType::NOTECARD => "Notecard".into(), + InventoryType::CATEGORY => "Category".into(), + InventoryType::ROOT_CATEGORY => "RootCategory".into(), + InventoryType::LSL => "LSL".into(), + InventoryType::SNAPSHOT => "Snapshot".into(), + InventoryType::ATTACHMENT => "Attachment".into(), + InventoryType::WEARABLE => "Wearable".into(), + InventoryType::ANIMATION => "Animation".into(), + InventoryType::GESTURE => "Gesture".into(), + InventoryType::MESH => "Mesh".into(), + InventoryType::SETTINGS => "Settings".into(), + InventoryType::MATERIAL => "Material".into(), + InventoryType(other) => other.to_string(), + } +} + +fn decrypt_shadow_id(shadow_id: UUID) -> Result { + let magic = UUID::new_with_string("3c115e51-04f4-523c-9fa6-98aff1034730".into())?; + Ok(UUID::bitxor(shadow_id, magic)) +} + +fn asset_type_from_i32(value: i32) -> Result { + Ok(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, + _ => return Err(Error::Argument), + }) +} + +fn folder_type_from_i32(value: i32) -> Result { + Ok(match value { + -1 => FolderType::None, + 0 => FolderType::Texture, + 1 => FolderType::Sound, + 2 => FolderType::CallingCard, + 3 => FolderType::Landmark, + 5 => FolderType::Clothing, + 6 => FolderType::Object, + 7 => FolderType::Notecard, + 8 => FolderType::Root, + 9 => FolderType::OldRoot, + 10 => FolderType::LSLText, + 13 => FolderType::BodyPart, + 14 => FolderType::Trash, + 15 => FolderType::Snapshot, + 16 => FolderType::LostAndFound, + 20 => FolderType::Animation, + 21 => FolderType::Gesture, + 23 => FolderType::Favorites, + 26 => FolderType::EnsembleStart, + 45 => FolderType::EnsembleEnd, + 46 => FolderType::CurrentOutfit, + 47 => FolderType::Outfit, + 48 => FolderType::MyOutfits, + 49 => FolderType::Mesh, + 50 => FolderType::Inbox, + 51 => FolderType::Outbox, + 52 => FolderType::BasicRoot, + 53 => FolderType::MarketplaceListings, + 54 => FolderType::MarketplaceStock, + 55 => FolderType::MarketplaceVersion, + 56 => FolderType::Settings, + 57 => FolderType::Material, + 100 => FolderType::Suitcase, + _ => return Err(Error::Argument), + }) +} + +fn sale_type_from_i32(value: i32) -> Result { + Ok(match value { + 0 => SaleType::Not, + 1 => SaleType::Original, + 2 => SaleType::Copy, + 3 => SaleType::Contents, + _ => return Err(Error::Argument), + }) +} + +const fn attachment_point_from_u8(value: u8) -> AttachmentPoint { + match value { + 1 => AttachmentPoint::Chest, + 2 => AttachmentPoint::Skull, + 3 => AttachmentPoint::LeftShoulder, + 4 => AttachmentPoint::RightShoulder, + 5 => AttachmentPoint::LeftHand, + 6 => AttachmentPoint::RightHand, + 7 => AttachmentPoint::LeftFoot, + 8 => AttachmentPoint::RightFoot, + 9 => AttachmentPoint::Spine, + 10 => AttachmentPoint::Pelvis, + 11 => AttachmentPoint::Mouth, + 12 => AttachmentPoint::Chin, + 13 => AttachmentPoint::LeftEar, + 14 => AttachmentPoint::RightEar, + 15 => AttachmentPoint::LeftEyeball, + 16 => AttachmentPoint::RightEyeball, + 17 => AttachmentPoint::Nose, + 18 => AttachmentPoint::RightUpperArm, + 19 => AttachmentPoint::RightForearm, + 20 => AttachmentPoint::LeftUpperArm, + 21 => AttachmentPoint::LeftForearm, + 22 => AttachmentPoint::RightHip, + 23 => AttachmentPoint::RightUpperLeg, + 24 => AttachmentPoint::RightLowerLeg, + 25 => AttachmentPoint::LeftHip, + 26 => AttachmentPoint::LeftUpperLeg, + 27 => AttachmentPoint::LeftLowerLeg, + 28 => AttachmentPoint::Stomach, + 29 => AttachmentPoint::LeftPec, + 30 => AttachmentPoint::RightPec, + 31 => AttachmentPoint::HUDCenter2, + 32 => AttachmentPoint::HUDTopRight, + 33 => AttachmentPoint::HUDTop, + 34 => AttachmentPoint::HUDTopLeft, + 35 => AttachmentPoint::HUDCenter, + 36 => AttachmentPoint::HUDBottomLeft, + 37 => AttachmentPoint::HUDBottom, + 38 => AttachmentPoint::HUDBottomRight, + 39 => AttachmentPoint::Neck, + 40 => AttachmentPoint::Root, + 41 => AttachmentPoint::LeftHandRing, + 42 => AttachmentPoint::RightHandRing, + 43 => AttachmentPoint::TailBase, + 44 => AttachmentPoint::TailTip, + 45 => AttachmentPoint::LeftWing, + 46 => AttachmentPoint::RightWing, + 47 => AttachmentPoint::Jaw, + 48 => AttachmentPoint::AltLeftEar, + 49 => AttachmentPoint::AltRightEar, + 50 => AttachmentPoint::AltLeftEye, + 51 => AttachmentPoint::AltRightEye, + 52 => AttachmentPoint::Tongue, + 53 => AttachmentPoint::Groin, + 54 => AttachmentPoint::LeftHindFoot, + 55 => AttachmentPoint::RightHindFoot, + _ => AttachmentPoint::Default, + } +} + +const fn wearable_type_from_u8(value: u8) -> WearableType { + match value { + 0 => WearableType::Shape, + 1 => WearableType::Skin, + 2 => WearableType::Hair, + 3 => WearableType::Eyes, + 4 => WearableType::Shirt, + 5 => WearableType::Pants, + 6 => WearableType::Shoes, + 7 => WearableType::Socks, + 8 => WearableType::Jacket, + 9 => WearableType::Gloves, + 10 => WearableType::Undershirt, + 11 => WearableType::Underpants, + 12 => WearableType::Skirt, + 13 => WearableType::Alpha, + 14 => WearableType::Tattoo, + 15 => WearableType::Physics, + 16 => WearableType::Universal, + _ => WearableType::Invalid, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + + fn fixture() -> (Arc, Inventory) { + let client = Arc::new(GridClient::new().expect("client")); + let inventory = + Inventory::new_with_grid_client_uuid(Arc::clone(&client), UUID::random().unwrap()) + .unwrap(); + (client, inventory) + } + + fn temporary_file(name: &str) -> PathBuf { + std::env::temp_dir().join(format!( + "metacrate-{name}-{}", + UUID::random().unwrap().to_string() + )) + } + + #[test] + fn cache_round_trip_preserves_roots_concrete_type_and_permissions() { + let (_client, mut inventory) = fixture(); + let mut root = InventoryFolder::new(UUID::random().unwrap()).unwrap(); + root.base.set_name("My Inventory".into()); + root.set_preferred_type(FolderType::Root); + let root_id = root.base.uuid(); + inventory.set_root_folder(Some(root)); + + let mut library = InventoryFolder::new(UUID::random().unwrap()).unwrap(); + library.base.set_name("Library".into()); + library.set_preferred_type(FolderType::BasicRoot); + let library_id = library.base.uuid(); + inventory.set_library_folder(Some(library)); + + let mut wearable = InventoryWearable::new(UUID::random().unwrap()).unwrap(); + wearable.base.base.set_parent_uuid(root_id); + wearable.base.base.set_name("Shirt".into()); + wearable.base.set_asset_type(AssetType::Clothing); + wearable.base.set_asset_uuid(UUID::random().unwrap()); + wearable + .base + .set_permissions(Permissions::new(1, 2, 3, 4, 5).unwrap()); + wearable + .base + .set_creation_date(UNIX_EPOCH - Duration::from_millis(500)); + wearable.set_wearable_type(WearableType::Shirt); + let wearable_id = wearable.base.base.uuid(); + inventory.update_node_for(&wearable).unwrap(); + + let path = temporary_file("inventory-roundtrip"); + inventory + .save_to_disk_with_string(path.to_string_lossy().into_owned()) + .unwrap(); + inventory.clear().unwrap(); + assert_eq!(inventory.count(), 0); + assert_eq!(inventory.root_folder().unwrap().base.uuid(), root_id); + assert_eq!(inventory.library_folder().unwrap().base.uuid(), library_id); + assert_eq!( + inventory + .restore_from_disk_with_string(path.to_string_lossy().into_owned()) + .unwrap(), + 1 + ); + let restored = inventory + .get_value_or_default_with_uuid_a7c63fbe::(wearable_id) + .unwrap() + .unwrap(); + assert_eq!(restored.wearable_type(), WearableType::Shirt); + assert_eq!( + restored.base.creation_date(), + UNIX_EPOCH - Duration::from_millis(500) + ); + assert_eq!( + restored.base.permissions(), + Permissions::new(1, 2, 3, 4, 5).unwrap() + ); + assert_eq!(inventory.root_folder().unwrap().base.uuid(), root_id); + assert_eq!(inventory.library_folder().unwrap().base.uuid(), library_id); + let _ = fs::remove_file(path); + } + + #[test] + fn models_preserve_csharp_equality_shadow_and_indexer_rules() { + let item_id = UUID::random().unwrap(); + let mut left = InventoryItem::new_with_uuid(item_id).unwrap(); + let mut right = left.clone(); + left.set_creator_id(UUID::random().unwrap()); + right.set_creator_id(UUID::random().unwrap()); + left.set_transaction_id(UUID::random().unwrap()); + right.set_transaction_id(UUID::random().unwrap()); + assert_eq!(left, right, "C# equality excludes transient IDs"); + + let asset_id = UUID::random().unwrap(); + let shadow_id = UUID::bitxor( + asset_id, + UUID::new_with_string("3c115e51-04f4-523c-9fa6-98aff1034730".into()).unwrap(), + ); + left.update( + OSDMap::new_with_dictionary(HashMap::from([( + "shadow_id".into(), + OSD::UUID(shadow_id), + )])) + .unwrap(), + ) + .unwrap(); + assert_eq!(left.asset_uuid(), asset_id); + + let (_client, mut inventory) = fixture(); + let folder = InventoryFolder::new(UUID::random().unwrap()).unwrap(); + let folder_id = folder.base.uuid(); + let mismatched_key = UUID::random().unwrap(); + inventory.set_item(mismatched_key, folder.base); + assert!(inventory.contains_with_uuid(folder_id).unwrap()); + assert!(!inventory.contains_with_uuid(mismatched_key).unwrap()); + } + + #[tokio::test] + async fn cache_rejects_corruption_and_unknown_versions() { + let (_client, inventory) = fixture(); + let path = temporary_file("inventory-corrupt"); + fs::write(&path, b"not-an-inventory-cache").unwrap(); + assert_eq!( + inventory + .restore_from_disk_with_string(path.to_string_lossy().into_owned()) + .unwrap(), + -1 + ); + assert!( + inventory + .restore_from_disk_with_string_cancellation_token( + path.to_string_lossy().into_owned(), + None + ) + .await + .is_err() + ); + + let snapshot = inventory.cache_snapshot().unwrap(); + let mut bytes = encode_cache(&snapshot).unwrap(); + bytes[8..12].copy_from_slice(&99_u32.to_le_bytes()); + fs::write(&path, bytes).unwrap(); + assert_eq!( + inventory + .restore_from_disk_with_string(path.to_string_lossy().into_owned()) + .unwrap(), + -1 + ); + assert!( + inventory + .restore_from_disk_with_string_cancellation_token( + path.to_string_lossy().into_owned(), + None + ) + .await + .is_err() + ); + let _ = fs::remove_file(path); + } + + #[test] + fn callbacks_run_after_store_lock_is_released() { + let (_client, inventory) = fixture(); + let reentrant = inventory.clone(); + let calls = Arc::new(AtomicUsize::new(0)); + let observed = Arc::clone(&calls); + let _subscription = inventory.subscribe_inventory_object_added(Arc::new(move |event| { + assert!(reentrant.contains_with_uuid(event.obj().uuid()).unwrap()); + assert!(reentrant.count() > 0); + observed.fetch_add(1, Ordering::SeqCst); + })); + let folder = InventoryFolder::new(UUID::random().unwrap()).unwrap(); + inventory.update_node_for(&folder).unwrap(); + assert_eq!(calls.load(Ordering::SeqCst), 1); + } + + #[test] + fn system_folders_sort_first_and_are_discoverable() { + let (_client, mut inventory) = fixture(); + let root = InventoryFolder::new(UUID::random().unwrap()).unwrap(); + let root_id = root.base.uuid(); + inventory.set_root_folder(Some(root)); + let mut ordinary = InventoryFolder::new(UUID::random().unwrap()).unwrap(); + ordinary.base.set_parent_uuid(root_id); + ordinary.base.set_name("A ordinary".into()); + inventory.update_node_for(&ordinary).unwrap(); + let mut trash = InventoryFolder::new(UUID::random().unwrap()).unwrap(); + trash.base.set_parent_uuid(root_id); + trash.base.set_name("Z trash".into()); + trash.set_preferred_type(FolderType::Trash); + let trash_id = trash.base.uuid(); + inventory.update_node_for(&trash).unwrap(); + let sorted = inventory + .get_contents_sorted_with_uuid( + root_id, + crate::InventorySortOrder(crate::InventorySortOrder::SYSTEM_FOLDERS_TO_TOP.0), + ) + .unwrap(); + assert_eq!(sorted[0].inventory_base().uuid(), trash_id); + assert_eq!( + inventory.find_folder_for_type(FolderType::Trash), + Some(trash_id) + ); + } +} diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index e355a92..c193efd 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -14,6 +14,7 @@ mod client_core; mod download_manager; mod event_queue; mod gesture; +mod inventory; #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. mod foliage_catalog; mod generated; diff --git a/docs/inventory.md b/docs/inventory.md new file mode 100644 index 0000000..b16e349 --- /dev/null +++ b/docs/inventory.md @@ -0,0 +1,38 @@ +# Inventory models, hierarchy, and cache + +The native inventory layer mirrors the value and hierarchy behavior of +LibreMetaverse's `InventoryBase`, `InventoryItem`, `InventoryFolder`, +`InventoryNode`, and `Inventory` types without depending on a network +transport. Concrete item subclasses remain concrete when inserted, queried, +notified, or restored from disk; link detection is based on `AssetType`, not on +the Rust wrapper type. This preserves the C# misclassified-link behavior. + +Store mutation is serialized by one inventory state lock. Parent/child and link +indexes and recursive item counts are rebuilt as one transaction, with cycle +and depth bounds. Missing parents become placeholder folders until their real +folder record arrives. Self-loops and longer parent cycles remain queryable but +are not linked into a cyclic ownership graph. Removing a folder removes its +bounded descendant subtree. Root and library roots are tracked independently, +and direct system folders can be found by `FolderType`. Sorting supports the +`ByDate`, `FoldersByName`, and `SystemFoldersToTop` flags. + +Added, updated, and removed notifications clone their arguments and subscriber +list before invoking observers. No inventory, node, or subscriber lock is held +during a callback, so observers may safely query or mutate the store. A panic +in one observer is isolated by the shared event registry. + +Cache files start with `INVCACHE`, a little-endian format version, owner and +root identities, and tagged concrete records. Reads are limited to 64 MiB, +one million records, one MiB per string, and a 512-level hierarchy. Invalid +magic, versions, UTF-8, enums, duplicate IDs, zero IDs, truncated records, and +trailing data are rejected before the live store is replaced. Synchronous +restore returns `-1` for compatibility; asynchronous restore returns the typed +error. Saves snapshot under the state lock, release it before I/O, write and +sync a same-directory temporary file, then replace the destination. The +replacement includes a rollback path on platforms that cannot rename over an +existing file. + +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. diff --git a/tests/api-compile/Cargo.lock b/tests/api-compile/Cargo.lock index 9ec3c1d..e73b609 100644 --- a/tests/api-compile/Cargo.lock +++ b/tests/api-compile/Cargo.lock @@ -716,6 +716,7 @@ version = "0.0.1" dependencies = [ "bcdec_rs", "flate2", + "futures-channel", "futures-util", "libremetaverse-imaging", "libremetaverse-structured-data", diff --git a/tests/api-compile/src/lib.rs b/tests/api-compile/src/lib.rs index 418b75a..29d2aa9 100644 --- a/tests/api-compile/src/lib.rs +++ b/tests/api-compile/src/lib.rs @@ -39120,7 +39120,7 @@ fn member_06311(value: &mut libremetaverse::Inventory) { } // C# member: `M:LibreMetaverse.Inventory.GetValueOrDefault``1(LibreMetaverse.UUID)`. -fn member_06312(value: &mut libremetaverse::Inventory) { +fn member_06312(value: &mut libremetaverse::Inventory) { let _ = value.get_value_or_default_with_uuid_a7c63fbe::(argument()); } @@ -39160,7 +39160,7 @@ fn member_06319(value: &mut libremetaverse::Inventory) { } // C# member: `M:LibreMetaverse.Inventory.TryGetValue``1(LibreMetaverse.UUID,``0@)`. -fn member_06320(value: &mut libremetaverse::Inventory) { +fn member_06320(value: &mut libremetaverse::Inventory) { let _ = value.try_get_value_with_uuid_t::(argument(), argument()); } diff --git a/tests/compat/tests/wire_shims.rs b/tests/compat/tests/wire_shims.rs index 7166a5f..5ab0a87 100644 --- a/tests/compat/tests/wire_shims.rs +++ b/tests/compat/tests/wire_shims.rs @@ -19,9 +19,11 @@ fn each_wire_data_domain_is_callable_and_failure_only() { let packet = UseCircuitCodePacket::new_with_constructor() .expect("generated packet constructor is native"); assert_eq!(packet.circuit_code.code, 0); - assert_eq!( - member_id(AgentDropGroupMessage::new()), - "M:LibreMetaverse.Messages.Linden.AgentDropGroupMessage.#ctor" + assert!( + AgentDropGroupMessage::new() + .expect("native generated message constructor") + .agent_data_block + .is_empty() ); assert_eq!( member_id(AssetTexture::new_with_constructor()), @@ -47,13 +49,12 @@ fn each_wire_data_domain_is_callable_and_failure_only() { member_id(OarFile::package_archive(String::new(), String::new())), "M:LibreMetaverse.Assets.OarFile.PackageArchive(System.String,System.String)" ); + let bit_pack = BitPack::new(Vec::new(), 0).expect("native bit pack constructor"); + assert_eq!(bit_pack.byte_pos(), 0); + assert_eq!(bit_pack.bit_pos(), 0); assert_eq!( - member_id(BitPack::new(Vec::new(), 0)), - "M:LibreMetaverse.BitPack.#ctor(System.Byte[],System.Int32)" - ); - assert_eq!( - member_id(Permissions::new(0, 0, 0, 0, 0)), - "M:LibreMetaverse.Permissions.#ctor(System.UInt32,System.UInt32,System.UInt32,System.UInt32,System.UInt32)" + Permissions::new(0, 0, 0, 0, 0).expect("native permissions constructor"), + Permissions::no_permissions() ); } diff --git a/tools/check_milestone_09_issue_61.py b/tools/check_milestone_09_issue_61.py new file mode 100644 index 0000000..4abbed1 --- /dev/null +++ b/tools/check_milestone_09_issue_61.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Audit issue 61's native inventory model, store, and cache 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.rs" +DOC = ROOT / "docs" / "inventory.md" +STORE_TESTS = ROOT / "tests" / "compat" / "tests" / "inventory_store_semantics.rs" +LINK_TESTS = ROOT / "tests" / "compat" / "tests" / "misclassified_link_semantics.rs" +STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(") + +REQUIRED_TYPES = { + "T:LibreMetaverse.Inventory", + "T:LibreMetaverse.InventoryAnimation", + "T:LibreMetaverse.InventoryAttachment", + "T:LibreMetaverse.InventoryBase", + "T:LibreMetaverse.InventoryCallingCard", + "T:LibreMetaverse.InventoryCategory", + "T:LibreMetaverse.InventoryFolder", + "T:LibreMetaverse.InventoryGesture", + "T:LibreMetaverse.InventoryItem", + "T:LibreMetaverse.InventoryLSL", + "T:LibreMetaverse.InventoryLandmark", + "T:LibreMetaverse.InventoryMaterial", + "T:LibreMetaverse.InventoryNode", + "T:LibreMetaverse.InventoryNodeDictionary", + "T:LibreMetaverse.InventoryNotecard", + "T:LibreMetaverse.InventoryObject", + "T:LibreMetaverse.InventoryObjectAddedEventArgs", + "T:LibreMetaverse.InventoryObjectRemovedEventArgs", + "T:LibreMetaverse.InventoryObjectUpdatedEventArgs", + "T:LibreMetaverse.InventorySettings", + "T:LibreMetaverse.InventorySnapshot", + "T:LibreMetaverse.InventorySound", + "T:LibreMetaverse.InventoryTexture", + "T:LibreMetaverse.InventoryWearable", + "T:LibreMetaverse.Permissions", +} + + +def require_markers(path: Path, markers: tuple[str, ...]) -> None: + text = path.read_text() + missing = [marker for marker in markers if marker not in text] + if missing: + raise SystemExit(f"{path.name}: audit evidence missing: " + ", ".join(missing)) + + +def main() -> None: + missing = sorted(REQUIRED_TYPES - generate_api_shims.NATIVE_TYPES.keys()) + if missing: + raise SystemExit("issue 61 native types missing: " + ", ".join(missing)) + if STUB_RE.search(SOURCE.read_text()): + raise SystemExit("issue 61 owned Rust stubs remain in inventory.rs") + require_markers(SOURCE, ( + "MAX_CACHE_BYTES", "MAX_CACHE_RECORDS", "MAX_HIERARCHY_DEPTH", + "rebuild_indexes_and_counts", "replace_file", "would_create_cycle", + "callbacks_run_after_store_lock_is_released", + "cache_rejects_corruption_and_unknown_versions", + "cache_round_trip_preserves_roots_concrete_type_and_permissions", + "system_folders_sort_first_and_are_discoverable", + )) + require_markers(DOC, ("misclassified-link", "64 MiB", "rollback", "No inventory")) + require_markers(STORE_TESTS, ("concurrent_add_move_remove_stress", "link_index_updates_on_asset_change")) + require_markers(LINK_TESTS, ("inventory_store_indexer_returns_wearable", "resolved_item_id_on_misclassified_texture_link")) + print("issue 61 audit: native models, concrete hierarchy storage, bounded cache, lock-free callbacks, and compatibility regressions are present") + + +if __name__ == "__main__": + main() diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index 3bc5337..f6d8b8d 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -63,6 +63,31 @@ NATIVE_TYPES = { "T:LibreMetaverse.HttpCapsClient.ProgressReport": "crate::caps_http::HttpCapsClientProgressReport", "T:LibreMetaverse.InstantMessage": "crate::agent_manager::InstantMessage", "T:LibreMetaverse.InstantMessageEventArgs": "crate::agent_manager::InstantMessageEventArgs", + "T:LibreMetaverse.Inventory": "crate::inventory::Inventory", + "T:LibreMetaverse.InventoryAnimation": "crate::inventory::InventoryAnimation", + "T:LibreMetaverse.InventoryAttachment": "crate::inventory::InventoryAttachment", + "T:LibreMetaverse.InventoryBase": "crate::inventory::InventoryBase", + "T:LibreMetaverse.InventoryCallingCard": "crate::inventory::InventoryCallingCard", + "T:LibreMetaverse.InventoryCategory": "crate::inventory::InventoryCategory", + "T:LibreMetaverse.InventoryFolder": "crate::inventory::InventoryFolder", + "T:LibreMetaverse.InventoryGesture": "crate::inventory::InventoryGesture", + "T:LibreMetaverse.InventoryItem": "crate::inventory::InventoryItem", + "T:LibreMetaverse.InventoryLSL": "crate::inventory::InventoryLSL", + "T:LibreMetaverse.InventoryLandmark": "crate::inventory::InventoryLandmark", + "T:LibreMetaverse.InventoryMaterial": "crate::inventory::InventoryMaterial", + "T:LibreMetaverse.InventoryNode": "crate::inventory::InventoryNode", + "T:LibreMetaverse.InventoryNodeDictionary": "crate::inventory::InventoryNodeDictionary", + "T:LibreMetaverse.InventoryNotecard": "crate::inventory::InventoryNotecard", + "T:LibreMetaverse.InventoryObject": "crate::inventory::InventoryObject", + "T:LibreMetaverse.InventoryObjectAddedEventArgs": "crate::inventory::InventoryObjectAddedEventArgs", + "T:LibreMetaverse.InventoryObjectRemovedEventArgs": "crate::inventory::InventoryObjectRemovedEventArgs", + "T:LibreMetaverse.InventoryObjectUpdatedEventArgs": "crate::inventory::InventoryObjectUpdatedEventArgs", + "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.ChatEventArgs": "crate::agent_messages::ChatEventArgs", "T:LibreMetaverse.ChatSessionMember": "crate::agent_messages::ChatSessionMember", "T:LibreMetaverse.ChatSessionMemberAddedEventArgs": "crate::agent_messages::ChatSessionMemberAddedEventArgs", @@ -1560,6 +1585,21 @@ VALUE_DERIVES = { "T:LibreMetaverse.Vector3d": "Clone, Copy, Debug", "T:LibreMetaverse.WorldSettings": "Clone, Debug, Eq, PartialEq", } + +# Native implementations occasionally need Rust-only bounds that have no +# direct C# metadata equivalent. C#'s `where T : class` permits returning a +# reference to the stored object; the Rust inventory store returns an owned +# value, so its generic compatibility accessors clone the concrete item. +NATIVE_MEMBER_BOUNDS = { + "M:LibreMetaverse.Inventory.GetValueOrDefault``1(LibreMetaverse.UUID)": { + "T": "T: Clone + 'static", + }, + "M:LibreMetaverse.Inventory.TryGetValue``1(LibreMetaverse.UUID,``0@)": { + "T": "T: Clone + 'static", + }, +} + + def generic_names(item: dict) -> list[str]: return [parameter["name"] for parameter in item.get("generic_parameters", [])] @@ -1649,6 +1689,11 @@ def render_compile_fixture( *(owner_bounds.get(name, name) for name in owner_names), *generic_declarations(member, mapper, owner_names), ] + member_bounds = NATIVE_MEMBER_BOUNDS.get(member["doc_id"], {}) + parameters = [ + member_bounds.get(parameter.split(":", 1)[0], parameter) + for parameter in parameters + ] if member["doc_id"] == "M:LibreMetaverse.Utils.EnumTryParse``1(System.String,``0@)": parameters = [ *owner_names, diff --git a/tools/generate_rust_mapping.py b/tools/generate_rust_mapping.py index c61b31b..ba08bda 100644 --- a/tools/generate_rust_mapping.py +++ b/tools/generate_rust_mapping.py @@ -993,6 +993,7 @@ def validate_generated_shims() -> None: "crate::genepool_catalog::", "crate::gesture::", "crate::json_codec::", + "crate::inventory::", "crate::message_codec::", "crate::message_decoder::", "crate::notation::",