Implement native AIS inventory reconciliation (#63)
This commit is contained in:
@@ -17,9 +17,12 @@ on:
|
||||
- "tools/check_milestone_09_issue_60.py"
|
||||
- "tools/check_milestone_09_issue_61.py"
|
||||
- "tools/check_milestone_09_issue_62.py"
|
||||
- "tools/check_milestone_09_issue_63.py"
|
||||
- "crates/libremetaverse/src/agent_movement.rs"
|
||||
- "crates/libremetaverse/src/inventory.rs"
|
||||
- "crates/libremetaverse/src/inventory_manager.rs"
|
||||
- "crates/libremetaverse/src/inventory_ais.rs"
|
||||
- "crates/libremetaverse/src/inventory_ais_internal_semantics.rs"
|
||||
- "docs/agent-movement.md"
|
||||
- "docs/inventory.md"
|
||||
- "crates/libremetaverse/Cargo.toml"
|
||||
@@ -35,6 +38,7 @@ on:
|
||||
- "tests/compat/tests/appearance_visual_semantics.rs"
|
||||
- "tests/compat/tests/inventory_store_semantics.rs"
|
||||
- "tests/compat/tests/inventory_manager_semantics.rs"
|
||||
- "tests/compat/tests/inventory_ais_semantics.rs"
|
||||
- "tests/compat/tests/task_inventory_semantics.rs"
|
||||
- "tests/compat/tests/misclassified_link_semantics.rs"
|
||||
- "tests/upstream-tests.json"
|
||||
@@ -54,9 +58,12 @@ on:
|
||||
- "tools/check_milestone_09_issue_60.py"
|
||||
- "tools/check_milestone_09_issue_61.py"
|
||||
- "tools/check_milestone_09_issue_62.py"
|
||||
- "tools/check_milestone_09_issue_63.py"
|
||||
- "crates/libremetaverse/src/agent_movement.rs"
|
||||
- "crates/libremetaverse/src/inventory.rs"
|
||||
- "crates/libremetaverse/src/inventory_manager.rs"
|
||||
- "crates/libremetaverse/src/inventory_ais.rs"
|
||||
- "crates/libremetaverse/src/inventory_ais_internal_semantics.rs"
|
||||
- "docs/agent-movement.md"
|
||||
- "docs/inventory.md"
|
||||
- "crates/libremetaverse/Cargo.toml"
|
||||
@@ -72,6 +79,7 @@ on:
|
||||
- "tests/compat/tests/appearance_visual_semantics.rs"
|
||||
- "tests/compat/tests/inventory_store_semantics.rs"
|
||||
- "tests/compat/tests/inventory_manager_semantics.rs"
|
||||
- "tests/compat/tests/inventory_ais_semantics.rs"
|
||||
- "tests/compat/tests/task_inventory_semantics.rs"
|
||||
- "tests/compat/tests/misclassified_link_semantics.rs"
|
||||
- "tests/upstream-tests.json"
|
||||
@@ -99,6 +107,7 @@ jobs:
|
||||
python3 tools/check_milestone_09_issue_60.py
|
||||
python3 tools/check_milestone_09_issue_61.py
|
||||
python3 tools/check_milestone_09_issue_62.py
|
||||
python3 tools/check_milestone_09_issue_63.py
|
||||
cargo check --manifest-path tests/api-compile/Cargo.toml --locked
|
||||
- name: Test generated wire codecs and translated parity cases
|
||||
run: |
|
||||
@@ -108,6 +117,8 @@ jobs:
|
||||
cargo test -p libremetaverse-compat-tests --test wire_semantics
|
||||
cargo test -p libremetaverse-compat-tests --test inventory_store_semantics
|
||||
cargo test -p libremetaverse-compat-tests --test inventory_manager_semantics
|
||||
cargo test -p libremetaverse-compat-tests --test inventory_ais_semantics
|
||||
cargo test -p libremetaverse --lib inventory_ais_internal_semantics
|
||||
cargo test -p libremetaverse-compat-tests --test task_inventory_semantics
|
||||
cargo test -p libremetaverse-compat-tests --test misclassified_link_semantics
|
||||
- name: Test generated visual and foliage catalogs
|
||||
|
||||
@@ -31,5 +31,6 @@ jobs:
|
||||
python3 tools/check_milestone_09_issue_60.py
|
||||
python3 tools/check_milestone_09_issue_61.py
|
||||
python3 tools/check_milestone_09_issue_62.py
|
||||
python3 tools/check_milestone_09_issue_63.py
|
||||
- name: Compile every workspace target with bounded memory
|
||||
run: cargo check --workspace --all-targets --locked -j 1
|
||||
|
||||
@@ -367,3 +367,11 @@ before store reconciliation, and legacy task inventory uses a 16 MiB-bounded
|
||||
`ReplyTaskInventory`/`RequestXfer` state machine. Focused translated and native
|
||||
fake-packet tests cover correlation, stale replies, offers, bulk completion,
|
||||
capability task inventory, and malformed transfer sizes.
|
||||
|
||||
The client-owned `InventoryAISClient` implements Inventory/Library API v3
|
||||
category, child, link, item, outfit, orphan, and trash resources with exact
|
||||
LLSD XML request shapes and `COPY` destination headers. Parsed objects,
|
||||
side-effect removals, and folder versions are validated and reconciled through
|
||||
one atomic store transaction; cancellation and malformed responses cannot
|
||||
partially mutate the cache. Focused compatibility and injected-HTTP tests cover
|
||||
link correction, endpoint recording, error isolation, and metadata updates.
|
||||
|
||||
@@ -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: 164 types / 14,877 members; remaining surface is callable failure-only shims |
|
||||
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 166 types / 14,923 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 |
|
||||
|
||||
@@ -699,6 +699,31 @@ impl HttpCapsClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sends a capability request whose verb or headers are not covered by the
|
||||
/// fixed public compatibility overloads (`AISv3` uses `COPY` and
|
||||
/// `Destination`). The request still passes through all normal limits,
|
||||
/// cancellation, rate limiting, and injected-handler recording.
|
||||
pub(crate) async fn send_custom(
|
||||
&self,
|
||||
method: &str,
|
||||
uri: Uri,
|
||||
headers: std::collections::BTreeMap<String, String>,
|
||||
content_type: Option<String>,
|
||||
payload: Vec<u8>,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Result<(HttpResponse, Vec<u8>), Error> {
|
||||
self.send_with_headers(
|
||||
method,
|
||||
uri,
|
||||
headers,
|
||||
content_type,
|
||||
payload,
|
||||
cancellation_token,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_request(
|
||||
&self,
|
||||
uri: Uri,
|
||||
@@ -1026,6 +1051,28 @@ impl HttpCapsClient {
|
||||
payload: Vec<u8>,
|
||||
cancellation_token: CancellationToken,
|
||||
progress: Option<Box<dyn IProgress<HttpCapsClientProgressReport>>>,
|
||||
) -> Result<(HttpResponse, Vec<u8>), Error> {
|
||||
self.send_with_headers(
|
||||
method,
|
||||
uri,
|
||||
std::collections::BTreeMap::new(),
|
||||
content_type,
|
||||
payload,
|
||||
cancellation_token,
|
||||
progress,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_with_headers(
|
||||
&self,
|
||||
method: &str,
|
||||
uri: Uri,
|
||||
headers: std::collections::BTreeMap<String, String>,
|
||||
content_type: Option<String>,
|
||||
payload: Vec<u8>,
|
||||
cancellation_token: CancellationToken,
|
||||
progress: Option<Box<dyn IProgress<HttpCapsClientProgressReport>>>,
|
||||
) -> Result<(HttpResponse, Vec<u8>), Error> {
|
||||
if self.0.disposed.load(Ordering::Acquire) {
|
||||
return Err(Error::InvalidOperation);
|
||||
@@ -1057,6 +1104,7 @@ impl HttpCapsClient {
|
||||
handler,
|
||||
method,
|
||||
uri,
|
||||
headers,
|
||||
content_type,
|
||||
payload,
|
||||
cancellation_token,
|
||||
@@ -1069,6 +1117,7 @@ impl HttpCapsClient {
|
||||
client,
|
||||
method,
|
||||
parsed,
|
||||
headers,
|
||||
content_type,
|
||||
payload,
|
||||
cancellation_token,
|
||||
@@ -1086,6 +1135,7 @@ impl HttpCapsClient {
|
||||
handler: &HttpMessageHandler,
|
||||
method: &str,
|
||||
uri: Uri,
|
||||
headers: std::collections::BTreeMap<String, String>,
|
||||
content_type: Option<String>,
|
||||
payload: Vec<u8>,
|
||||
cancellation_token: CancellationToken,
|
||||
@@ -1095,7 +1145,7 @@ impl HttpCapsClient {
|
||||
let request = HttpRequest {
|
||||
method: method.to_owned(),
|
||||
uri,
|
||||
headers: std::collections::BTreeMap::new(),
|
||||
headers,
|
||||
content_type,
|
||||
body: payload,
|
||||
};
|
||||
@@ -1112,6 +1162,7 @@ impl HttpCapsClient {
|
||||
client: &reqwest::Client,
|
||||
method: &str,
|
||||
uri: reqwest::Url,
|
||||
headers: std::collections::BTreeMap<String, String>,
|
||||
content_type: Option<String>,
|
||||
payload: Vec<u8>,
|
||||
cancellation_token: CancellationToken,
|
||||
@@ -1119,6 +1170,9 @@ impl HttpCapsClient {
|
||||
) -> Result<(HttpResponse, Vec<u8>), Error> {
|
||||
let method = reqwest::Method::from_bytes(method.as_bytes()).map_err(|_| Error::Argument)?;
|
||||
let mut request = client.request(method, uri);
|
||||
for (name, value) in headers {
|
||||
request = request.header(name, value);
|
||||
}
|
||||
if let Some(content_type) = content_type {
|
||||
request = request.header(reqwest::header::CONTENT_TYPE, content_type);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ struct ClientRuntime {
|
||||
network_manager: Mutex<std::sync::Weak<crate::network_manager::NetworkManagerInner>>,
|
||||
agent_manager: Mutex<std::sync::Weak<crate::agent_manager::AgentManagerInner>>,
|
||||
inventory_manager: Mutex<Option<Arc<crate::inventory_manager::InventoryManagerInner>>>,
|
||||
inventory_ais_client: Mutex<Option<crate::inventory_ais::InventoryAISClient>>,
|
||||
shutdown_complete: Condvar,
|
||||
shutdown_wait: Mutex<()>,
|
||||
}
|
||||
@@ -148,6 +149,7 @@ impl ClientRuntime {
|
||||
network_manager: Mutex::new(std::sync::Weak::new()),
|
||||
agent_manager: Mutex::new(std::sync::Weak::new()),
|
||||
inventory_manager: Mutex::new(None),
|
||||
inventory_ais_client: Mutex::new(None),
|
||||
shutdown_complete: Condvar::new(),
|
||||
shutdown_wait: Mutex::new(()),
|
||||
}
|
||||
@@ -224,6 +226,10 @@ impl ClientRuntime {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.inventory_ais_client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
self.http_caps_client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
@@ -441,6 +447,30 @@ impl GridClient {
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
pub(crate) fn native_ais_client(&self) -> Result<crate::InventoryAISClient, crate::Error> {
|
||||
let mut cached = self
|
||||
.runtime
|
||||
.inventory_ais_client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(client) = cached.as_ref() {
|
||||
return Ok(client.clone());
|
||||
}
|
||||
let client =
|
||||
crate::inventory_ais::InventoryAISClient::native_new(Some(Arc::new(self.clone())))?;
|
||||
*cached = Some(client.clone());
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn native_set_ais_client(&mut self, value: crate::InventoryAISClient) {
|
||||
*self
|
||||
.runtime
|
||||
.inventory_ais_client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value);
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // The mapped C# property setter owns its value.
|
||||
pub(crate) fn native_set_inventory(&mut self, value: crate::InventoryManager) {
|
||||
*self
|
||||
|
||||
@@ -6,52 +6,14 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
/// C# type: `T:LibreMetaverse.AISResponseMeta`.
|
||||
pub struct AISResponseMeta;
|
||||
impl AISResponseMeta {
|
||||
/// C# member: `F:LibreMetaverse.AISResponseMeta.Empty`.
|
||||
pub fn empty() -> libremetaverse::AISResponseMeta {
|
||||
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.AISResponseMeta.Empty")
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.AISResponseMeta.#ctor(System.Collections.Generic.IReadOnlyList{LibreMetaverse.UUID},System.Collections.Generic.IReadOnlyList{LibreMetaverse.UUID},System.Collections.Generic.IReadOnlyList{LibreMetaverse.UUID},System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.Int32})`.
|
||||
pub fn new(
|
||||
broken_links: Vec<libremetaverse_types::UUID>,
|
||||
items: Vec<libremetaverse_types::UUID>,
|
||||
categories: Vec<libremetaverse_types::UUID>,
|
||||
versions: std::collections::HashMap<libremetaverse_types::UUID, i32>,
|
||||
) -> Result<Self, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.AISResponseMeta.#ctor(System.Collections.Generic.IReadOnlyList{LibreMetaverse.UUID},System.Collections.Generic.IReadOnlyList{LibreMetaverse.UUID},System.Collections.Generic.IReadOnlyList{LibreMetaverse.UUID},System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.Int32})",
|
||||
)
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.AISResponseMeta.BrokenLinksRemoved`.
|
||||
pub fn broken_links_removed(&self) -> Vec<libremetaverse_types::UUID> {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"P:LibreMetaverse.AISResponseMeta.BrokenLinksRemoved"
|
||||
)
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.AISResponseMeta.CategoriesRemoved`.
|
||||
pub fn categories_removed(&self) -> Vec<libremetaverse_types::UUID> {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"P:LibreMetaverse.AISResponseMeta.CategoriesRemoved"
|
||||
)
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.AISResponseMeta.CategoryVersionUpdates`.
|
||||
pub fn category_version_updates(
|
||||
&self,
|
||||
) -> std::collections::HashMap<libremetaverse_types::UUID, i32> {
|
||||
libremetaverse_types::unimplemented_api!(
|
||||
"P:LibreMetaverse.AISResponseMeta.CategoryVersionUpdates"
|
||||
)
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.AISResponseMeta.HasAnyData`.
|
||||
pub fn has_any_data(&self) -> bool {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AISResponseMeta.HasAnyData")
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.AISResponseMeta.ItemsRemoved`.
|
||||
pub fn items_removed(&self) -> Vec<libremetaverse_types::UUID> {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AISResponseMeta.ItemsRemoved")
|
||||
}
|
||||
}
|
||||
pub use crate::inventory_ais::AISResponseMeta;
|
||||
|
||||
/// C# type: `T:LibreMetaverse.AccessDeniedReason`.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -9265,11 +9227,14 @@ impl GridClient {
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.GridClient.AisClient`.
|
||||
pub fn ais_client(&self) -> libremetaverse::InventoryAISClient {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.AisClient")
|
||||
/* native client-core implementation */
|
||||
self.native_ais_client()
|
||||
.unwrap_or_else(|_| panic!("failed to construct InventoryAISClient"))
|
||||
}
|
||||
/// Setter for C# member: `P:LibreMetaverse.GridClient.AisClient`.
|
||||
pub fn set_ais_client(&mut self, value: libremetaverse::InventoryAISClient) {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.AisClient")
|
||||
/* native client-core implementation */
|
||||
self.native_set_ais_client(value)
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.GridClient.Animesh`.
|
||||
pub fn animesh(&self) -> libremetaverse::animesh::AnimeshManager {
|
||||
@@ -11794,7 +11759,7 @@ impl InterestListManager {
|
||||
pub use crate::inventory::Inventory;
|
||||
|
||||
/// C# type: `T:LibreMetaverse.InventoryAISClient`.
|
||||
pub struct InventoryAISClient;
|
||||
pub use crate::inventory_ais::InventoryAISClient;
|
||||
impl InventoryAISClient {
|
||||
/// C# member: `F:LibreMetaverse.InventoryAISClient.INVENTORY_CAP_NAME`.
|
||||
pub const INVENTORY_CAP_NAME: &'static str = "InventoryAPIv3";
|
||||
@@ -11804,9 +11769,7 @@ impl InventoryAISClient {
|
||||
pub fn new(
|
||||
client: Option<std::sync::Arc<libremetaverse::GridClient>>,
|
||||
) -> Result<Self, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.#ctor(LibreMetaverse.GridClient)",
|
||||
)
|
||||
crate::inventory_ais::InventoryAISClient::native_new(client)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.CopyCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)`.
|
||||
pub async fn copy_category(
|
||||
@@ -11816,9 +11779,8 @@ impl InventoryAISClient {
|
||||
simulate: Option<bool>,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_copy_category(source_uuid, dest_uuid, simulate, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.CopyCategoryChildrenAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn copy_category_children(
|
||||
@@ -11827,9 +11789,8 @@ impl InventoryAISClient {
|
||||
dest_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyCategoryChildrenAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_copy_category_children(category, dest_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.CopyCategoryLinksAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn copy_category_links(
|
||||
@@ -11838,9 +11799,8 @@ impl InventoryAISClient {
|
||||
dest_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyCategoryLinksAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_copy_category_links(category, dest_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn copy_item(
|
||||
@@ -11849,9 +11809,8 @@ impl InventoryAISClient {
|
||||
dest_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_copy_item(item_uuid, dest_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.CopyLibraryCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)`.
|
||||
pub async fn copy_library_category(
|
||||
@@ -11861,9 +11820,13 @@ impl InventoryAISClient {
|
||||
copy_subfolders: bool,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyLibraryCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)",
|
||||
self.native_copy_library_category(
|
||||
source_uuid,
|
||||
dest_uuid,
|
||||
copy_subfolders,
|
||||
cancellation_token,
|
||||
)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.CreateInventoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Boolean,System.Threading.CancellationToken)`.
|
||||
pub async fn create_inventory(
|
||||
@@ -11873,9 +11836,8 @@ impl InventoryAISClient {
|
||||
create_link: bool,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<(bool, Option<libremetaverse::InventoryItem>), crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.CreateInventoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Boolean,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_create_inventory(parent_uuid, new_inventory, create_link, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.DeleteCategoryChildrenAsync(System.String,System.Threading.CancellationToken)`.
|
||||
pub async fn delete_category_children(
|
||||
@@ -11883,9 +11845,8 @@ impl InventoryAISClient {
|
||||
category: String,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.DeleteCategoryChildrenAsync(System.String,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_delete_category_children(category, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.DeleteCategoryLinksAsync(System.String,System.Threading.CancellationToken)`.
|
||||
pub async fn delete_category_links(
|
||||
@@ -11893,27 +11854,22 @@ impl InventoryAISClient {
|
||||
category: String,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.DeleteCategoryLinksAsync(System.String,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_delete_category_links(category, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.EmptyTrashAsync(System.Threading.CancellationToken)`.
|
||||
pub async fn empty_trash(
|
||||
&self,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.EmptyTrashAsync(System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_empty_trash(cancellation_token).await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.FetchCOFAsync(System.Threading.CancellationToken)`.
|
||||
pub async fn fetch_cof(
|
||||
&self,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchCOFAsync(System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_fetch_cof(cancellation_token).await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.FetchCategoryCategoriesAsync(LibreMetaverse.UUID,System.Boolean,System.Boolean,System.Int32,System.Threading.CancellationToken)`.
|
||||
pub async fn fetch_category_categories(
|
||||
@@ -11932,9 +11888,14 @@ impl InventoryAISClient {
|
||||
),
|
||||
crate::Error,
|
||||
> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchCategoryCategoriesAsync(LibreMetaverse.UUID,System.Boolean,System.Boolean,System.Int32,System.Threading.CancellationToken)",
|
||||
self.native_fetch_category_categories(
|
||||
cat_id,
|
||||
use_inventory_cap,
|
||||
recursive,
|
||||
depth,
|
||||
cancellation_token,
|
||||
)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.FetchCategorySubsetAsync(LibreMetaverse.UUID,System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Boolean,System.Boolean,System.Int32,System.Threading.CancellationToken)`.
|
||||
pub async fn fetch_category_subset(
|
||||
@@ -11954,9 +11915,15 @@ impl InventoryAISClient {
|
||||
),
|
||||
crate::Error,
|
||||
> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchCategorySubsetAsync(LibreMetaverse.UUID,System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Boolean,System.Boolean,System.Int32,System.Threading.CancellationToken)",
|
||||
self.native_fetch_category_subset(
|
||||
cat_id,
|
||||
specific_children,
|
||||
use_inventory_cap,
|
||||
recursive,
|
||||
depth,
|
||||
cancellation_token,
|
||||
)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.FetchItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn fetch_item(
|
||||
@@ -11972,18 +11939,14 @@ impl InventoryAISClient {
|
||||
),
|
||||
crate::Error,
|
||||
> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_fetch_item(item_uuid, cancellation_token).await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.FetchOrphansAsync(System.Threading.CancellationToken)`.
|
||||
pub async fn fetch_orphans(
|
||||
&self,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchOrphansAsync(System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_fetch_orphans(cancellation_token).await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.GetCategoryAsync(System.String,System.Threading.CancellationToken)`.
|
||||
pub async fn get_category(
|
||||
@@ -11999,9 +11962,7 @@ impl InventoryAISClient {
|
||||
),
|
||||
crate::Error,
|
||||
> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.GetCategoryAsync(System.String,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_get_category(category, cancellation_token).await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.GetCategoryChildrenAsync(System.String,System.Int32,System.Boolean,System.Threading.CancellationToken)`.
|
||||
pub async fn get_category_children(
|
||||
@@ -12019,9 +11980,8 @@ impl InventoryAISClient {
|
||||
),
|
||||
crate::Error,
|
||||
> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.GetCategoryChildrenAsync(System.String,System.Int32,System.Boolean,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_get_category_children(category, depth, recursive, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.GetCategoryLinksAsync(System.String,System.Threading.CancellationToken)`.
|
||||
pub async fn get_category_links(
|
||||
@@ -12037,9 +11997,8 @@ impl InventoryAISClient {
|
||||
),
|
||||
crate::Error,
|
||||
> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.GetCategoryLinksAsync(System.String,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_get_category_links(category, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.MoveCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn move_category(
|
||||
@@ -12048,9 +12007,8 @@ impl InventoryAISClient {
|
||||
dest_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.MoveCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_move_category(source_uuid, dest_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.MoveCategoryChildrenAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn move_category_children(
|
||||
@@ -12059,9 +12017,8 @@ impl InventoryAISClient {
|
||||
dest_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.MoveCategoryChildrenAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_move_category_children(category, dest_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.MoveCategoryLinksAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn move_category_links(
|
||||
@@ -12070,9 +12027,8 @@ impl InventoryAISClient {
|
||||
dest_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.MoveCategoryLinksAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_move_category_links(category, dest_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn move_item(
|
||||
@@ -12081,17 +12037,14 @@ impl InventoryAISClient {
|
||||
dest_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_move_item(item_uuid, dest_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.ParseAISResponseMeta(LibreMetaverse.StructuredData.OSDMap)`.
|
||||
pub fn parse_ais_response_meta(
|
||||
response: Option<libremetaverse_structured_data::OSDMap>,
|
||||
) -> Result<libremetaverse::AISResponseMeta, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseAISResponseMeta(LibreMetaverse.StructuredData.OSDMap)",
|
||||
)
|
||||
crate::inventory_ais::InventoryAISClient::native_parse_ais_response_meta(response)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.ParseEmbedded(LibreMetaverse.StructuredData.OSDMap,System.Collections.Generic.List{LibreMetaverse.InventoryFolder}@,System.Collections.Generic.List{LibreMetaverse.InventoryItem}@,System.Collections.Generic.List{LibreMetaverse.InventoryItem}@)`.
|
||||
pub fn parse_embedded(
|
||||
@@ -12101,36 +12054,28 @@ impl InventoryAISClient {
|
||||
items: &mut Option<Vec<libremetaverse::InventoryItem>>,
|
||||
links: &mut Option<Vec<libremetaverse::InventoryItem>>,
|
||||
) -> Result<(), crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseEmbedded(LibreMetaverse.StructuredData.OSDMap,System.Collections.Generic.List{LibreMetaverse.InventoryFolder}@,System.Collections.Generic.List{LibreMetaverse.InventoryItem}@,System.Collections.Generic.List{LibreMetaverse.InventoryItem}@)",
|
||||
)
|
||||
self.native_parse_embedded(response, folders, items, links)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.ParseFoldersFromEmbedded(LibreMetaverse.StructuredData.OSDMap)`.
|
||||
pub fn parse_folders_from_embedded(
|
||||
&self,
|
||||
response: libremetaverse_structured_data::OSDMap,
|
||||
) -> Result<Vec<libremetaverse::InventoryFolder>, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseFoldersFromEmbedded(LibreMetaverse.StructuredData.OSDMap)",
|
||||
)
|
||||
self.native_parse_folders_from_embedded(response)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.ParseItemsFromEmbedded(LibreMetaverse.StructuredData.OSDMap)`.
|
||||
pub fn parse_items_from_embedded(
|
||||
&self,
|
||||
response: Option<libremetaverse_structured_data::OSD>,
|
||||
) -> Result<Vec<libremetaverse::InventoryItem>, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseItemsFromEmbedded(LibreMetaverse.StructuredData.OSDMap)",
|
||||
)
|
||||
self.native_parse_items_from_embedded(response)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.ParseLinksFromEmbedded(LibreMetaverse.StructuredData.OSDMap)`.
|
||||
pub fn parse_links_from_embedded(
|
||||
&self,
|
||||
response: Option<libremetaverse_structured_data::OSD>,
|
||||
) -> Result<Vec<libremetaverse::InventoryItem>, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseLinksFromEmbedded(LibreMetaverse.StructuredData.OSDMap)",
|
||||
)
|
||||
self.native_parse_links_from_embedded(response)
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.PurgeDescendentsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn purge_descendents(
|
||||
@@ -12138,9 +12083,8 @@ impl InventoryAISClient {
|
||||
category_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.PurgeDescendentsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_purge_descendents(category_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.PutCategoryChildrenAsync(System.String,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)`.
|
||||
pub async fn put_category_children(
|
||||
@@ -12149,9 +12093,8 @@ impl InventoryAISClient {
|
||||
children_payload: libremetaverse_structured_data::OSD,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.PutCategoryChildrenAsync(System.String,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_put_category_children(category, children_payload, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.PutCategoryLinksAsync(System.String,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)`.
|
||||
pub async fn put_category_links(
|
||||
@@ -12160,9 +12103,8 @@ impl InventoryAISClient {
|
||||
links_payload: libremetaverse_structured_data::OSD,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.PutCategoryLinksAsync(System.String,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_put_category_links(category, links_payload, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.RemoveCategoryAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn remove_category(
|
||||
@@ -12170,9 +12112,8 @@ impl InventoryAISClient {
|
||||
category_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.RemoveCategoryAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_remove_category(category_uuid, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.RemoveItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`.
|
||||
pub async fn remove_item(
|
||||
@@ -12180,9 +12121,7 @@ impl InventoryAISClient {
|
||||
item_uuid: libremetaverse_types::UUID,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.RemoveItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_remove_item(item_uuid, cancellation_token).await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.SlamFolderAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)`.
|
||||
pub async fn slam_folder(
|
||||
@@ -12191,9 +12130,8 @@ impl InventoryAISClient {
|
||||
new_inventory: libremetaverse_structured_data::OSD,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.SlamFolderAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_slam_folder(folder_uuid, new_inventory, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.UpdateCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)`.
|
||||
pub async fn update_category(
|
||||
@@ -12202,9 +12140,8 @@ impl InventoryAISClient {
|
||||
updates: libremetaverse_structured_data::OSD,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.UpdateCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_update_category(category_uuid, updates, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `M:LibreMetaverse.InventoryAISClient.UpdateItemAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)`.
|
||||
pub async fn update_item(
|
||||
@@ -12213,13 +12150,12 @@ impl InventoryAISClient {
|
||||
updates: libremetaverse_structured_data::OSD,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<bool, crate::Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.UpdateItemAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_update_item(item_uuid, updates, cancellation_token)
|
||||
.await
|
||||
}
|
||||
/// C# member: `P:LibreMetaverse.InventoryAISClient.IsAvailable`.
|
||||
pub fn is_available(&self) -> bool {
|
||||
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InventoryAISClient.IsAvailable")
|
||||
self.native_is_available()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -855,6 +855,35 @@ enum InventoryValue {
|
||||
}
|
||||
|
||||
impl InventoryValue {
|
||||
fn from_item_typed(item: InventoryItem) -> Self {
|
||||
macro_rules! typed {
|
||||
($kind:ty, $variant:ident) => {{
|
||||
let mut value = <$kind>::new(item.base.uuid)
|
||||
.expect("known inventory item constructor cannot fail");
|
||||
value.base = item;
|
||||
Self::$variant(value)
|
||||
}};
|
||||
}
|
||||
match item.inventory_type {
|
||||
InventoryType::TEXTURE => typed!(InventoryTexture, Texture),
|
||||
InventoryType::SOUND => typed!(InventorySound, Sound),
|
||||
InventoryType::CALLING_CARD => typed!(InventoryCallingCard, CallingCard),
|
||||
InventoryType::LANDMARK => typed!(InventoryLandmark, Landmark),
|
||||
InventoryType::OBJECT => typed!(InventoryObject, Object),
|
||||
InventoryType::NOTECARD => typed!(InventoryNotecard, Notecard),
|
||||
InventoryType::CATEGORY => typed!(InventoryCategory, Category),
|
||||
InventoryType::LSL => typed!(InventoryLSL, Lsl),
|
||||
InventoryType::SNAPSHOT => typed!(InventorySnapshot, Snapshot),
|
||||
InventoryType::ATTACHMENT => typed!(InventoryAttachment, Attachment),
|
||||
InventoryType::WEARABLE => typed!(InventoryWearable, Wearable),
|
||||
InventoryType::ANIMATION => typed!(InventoryAnimation, Animation),
|
||||
InventoryType::GESTURE => typed!(InventoryGesture, Gesture),
|
||||
InventoryType::SETTINGS => typed!(InventorySettings, Settings),
|
||||
InventoryType::MATERIAL => typed!(InventoryMaterial, Material),
|
||||
_ => Self::Item(item),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_class(value: &dyn InventoryObjectClass) -> Option<Self> {
|
||||
macro_rules! downcast {
|
||||
($type:ty, $variant:ident) => {
|
||||
@@ -1472,6 +1501,161 @@ impl Inventory {
|
||||
.and_then(|value| value.item().cloned())
|
||||
}
|
||||
|
||||
/// Atomically applies one fully parsed AIS response. A detached candidate
|
||||
/// graph is built first, so validation or rebuilding failures cannot expose
|
||||
/// a partially reconciled inventory to readers.
|
||||
#[allow(clippy::too_many_lines)] // One lock-free candidate build followed by one state swap.
|
||||
pub(crate) fn native_apply_ais_batch(
|
||||
&self,
|
||||
folders: Vec<InventoryFolder>,
|
||||
items: Vec<InventoryItem>,
|
||||
removed_items: &[UUID],
|
||||
removed_categories: &[UUID],
|
||||
broken_links: &[UUID],
|
||||
versions: &HashMap<UUID, i32>,
|
||||
) -> Result<(), Error> {
|
||||
let mut incoming = HashSet::new();
|
||||
for uuid in folders
|
||||
.iter()
|
||||
.map(|folder| folder.base.uuid)
|
||||
.chain(items.iter().map(|item| item.base.uuid))
|
||||
{
|
||||
if uuid == UUID::zero() || !incoming.insert(uuid) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
}
|
||||
if incoming.len() > MAX_CACHE_RECORDS || versions.keys().any(|uuid| *uuid == UUID::zero()) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let mut touched = incoming.clone();
|
||||
touched.extend(versions.keys().copied());
|
||||
|
||||
let (mut candidate, old_values) = {
|
||||
let state = read(&self.inner.state);
|
||||
let old_values: HashMap<_, _> = state
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|(uuid, node)| node.value().map(|value| (*uuid, value)))
|
||||
.collect();
|
||||
let mut candidate = InventoryState {
|
||||
root: state.root,
|
||||
library_root: state.library_root,
|
||||
..InventoryState::default()
|
||||
};
|
||||
for (uuid, value) in &old_values {
|
||||
candidate
|
||||
.items
|
||||
.insert(*uuid, InventoryNode::from_value(value.clone()));
|
||||
}
|
||||
(candidate, old_values)
|
||||
};
|
||||
|
||||
for folder in folders {
|
||||
candidate.items.insert(
|
||||
folder.base.uuid,
|
||||
InventoryNode::from_value(InventoryValue::Folder(folder)),
|
||||
);
|
||||
}
|
||||
for item in items {
|
||||
let uuid = item.base.uuid;
|
||||
let value = InventoryValue::from_item_typed(item);
|
||||
candidate
|
||||
.items
|
||||
.insert(uuid, InventoryNode::from_value(value));
|
||||
}
|
||||
for (uuid, version) in versions {
|
||||
if let Some(node) = candidate.items.get(uuid) {
|
||||
let Some(mut value) = node.value() else {
|
||||
return Err(Error::InvalidOperation);
|
||||
};
|
||||
let Some(folder) = value.folder_mut() else {
|
||||
return Err(Error::InvalidOperation);
|
||||
};
|
||||
folder.version = *version;
|
||||
node.set_value(value);
|
||||
}
|
||||
}
|
||||
|
||||
let direct_removals: HashSet<_> = removed_items
|
||||
.iter()
|
||||
.chain(broken_links)
|
||||
.copied()
|
||||
.filter(|uuid| *uuid != UUID::zero())
|
||||
.collect();
|
||||
for uuid in direct_removals {
|
||||
candidate.items.remove(&uuid);
|
||||
}
|
||||
rebuild_indexes_and_counts(&mut candidate);
|
||||
for category in removed_categories
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|uuid| *uuid != UUID::zero())
|
||||
{
|
||||
let mut pending = vec![category];
|
||||
let mut descendants = HashSet::new();
|
||||
while let Some(uuid) = pending.pop() {
|
||||
if descendants.len() >= MAX_CACHE_RECORDS || !descendants.insert(uuid) {
|
||||
continue;
|
||||
}
|
||||
if let Some(node) = candidate.items.get(&uuid) {
|
||||
pending.extend(read(&node.inner).children.keys().copied());
|
||||
}
|
||||
}
|
||||
for uuid in descendants {
|
||||
candidate.items.remove(&uuid);
|
||||
}
|
||||
if candidate.root == Some(category) {
|
||||
candidate.root = None;
|
||||
}
|
||||
if candidate.library_root == Some(category) {
|
||||
candidate.library_root = None;
|
||||
}
|
||||
}
|
||||
if candidate.items.len() > MAX_CACHE_RECORDS {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
rebuild_indexes_and_counts(&mut candidate);
|
||||
candidate.root_node = candidate
|
||||
.root
|
||||
.and_then(|uuid| candidate.items.get(&uuid).cloned());
|
||||
candidate.library_root_node = candidate
|
||||
.library_root
|
||||
.and_then(|uuid| candidate.items.get(&uuid).cloned());
|
||||
|
||||
let new_values: HashMap<_, _> = candidate
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|(uuid, node)| node.value().map(|value| (*uuid, value)))
|
||||
.collect();
|
||||
*write(&self.inner.state) = candidate;
|
||||
|
||||
for (uuid, old) in &old_values {
|
||||
match new_values.get(uuid) {
|
||||
None => self
|
||||
.inner
|
||||
.removed
|
||||
.emit(InventoryObjectRemovedEventArgs::from_value(old.clone())),
|
||||
Some(new) if touched.contains(uuid) => {
|
||||
self.inner
|
||||
.updated
|
||||
.emit(InventoryObjectUpdatedEventArgs::from_values(
|
||||
old.clone(),
|
||||
new.clone(),
|
||||
));
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
for (uuid, new) in new_values {
|
||||
if !old_values.contains_key(&uuid) {
|
||||
self.inner
|
||||
.added
|
||||
.emit(InventoryObjectAddedEventArgs::from_value(new));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn try_get_value_with_uuid_inventory_base(
|
||||
&self,
|
||||
uuid: UUID,
|
||||
|
||||
1060
crates/libremetaverse/src/inventory_ais.rs
Normal file
1060
crates/libremetaverse/src/inventory_ais.rs
Normal file
File diff suppressed because it is too large
Load Diff
215
crates/libremetaverse/src/inventory_ais_internal_semantics.rs
Normal file
215
crates/libremetaverse/src/inventory_ais_internal_semantics.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use super::*;
|
||||
use libremetaverse_structured_data::{OSD, OSDParser};
|
||||
use libremetaverse_types::compat::{
|
||||
CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
|
||||
};
|
||||
use libremetaverse_types::{InventoryType, UUID};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
const CAP: &str = "http://inventory.test/ais";
|
||||
const LIBRARY_CAP: &str = "http://library.test/ais";
|
||||
const SEED: &str = "http://inventory.test/seed";
|
||||
|
||||
fn block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
use std::task::{Context, Poll, Wake, Waker};
|
||||
struct ThreadWake(std::thread::Thread);
|
||||
impl Wake for ThreadWake {
|
||||
fn wake(self: Arc<Self>) {
|
||||
self.0.unpark();
|
||||
}
|
||||
}
|
||||
let waker = Waker::from(Arc::new(ThreadWake(std::thread::current())));
|
||||
let mut context = Context::from_waker(&waker);
|
||||
let mut future = std::pin::pin!(future);
|
||||
loop {
|
||||
match future.as_mut().poll(&mut context) {
|
||||
Poll::Ready(output) => return output,
|
||||
Poll::Pending => std::thread::park(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn client_with_response(body: Vec<u8>) -> (GridClient, Arc<Mutex<Vec<HttpRequest>>>) {
|
||||
let requests = Arc::new(Mutex::new(Vec::new()));
|
||||
let recorded = Arc::clone(&requests);
|
||||
let seed = format!(
|
||||
"<llsd><map><key>InventoryAPIv3</key><uri>{CAP}</uri><key>LibraryAPIv3</key><uri>{LIBRARY_CAP}</uri></map></llsd>"
|
||||
);
|
||||
let handler = HttpMessageHandler::new(move |request, _| {
|
||||
let response = if request.uri.0 == SEED {
|
||||
HttpResponse {
|
||||
status_code: 200,
|
||||
headers: BTreeMap::new(),
|
||||
content_type: Some("application/llsd+xml".into()),
|
||||
body: seed.as_bytes().to_vec(),
|
||||
}
|
||||
} else {
|
||||
recorded.lock().unwrap().push(request);
|
||||
HttpResponse {
|
||||
status_code: 200,
|
||||
headers: BTreeMap::new(),
|
||||
content_type: Some("application/llsd+xml".into()),
|
||||
body: body.clone(),
|
||||
}
|
||||
};
|
||||
async move { response }
|
||||
});
|
||||
let mut client = GridClient::new().unwrap();
|
||||
client.set_http_caps_client(HttpCapsClient::new(handler.clone()).unwrap());
|
||||
let mut simulator_client = GridClient::new().unwrap();
|
||||
simulator_client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
|
||||
let simulator = Simulator::new(
|
||||
simulator_client,
|
||||
"127.0.0.1:0".parse().unwrap(),
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
simulator
|
||||
.set_seed_caps(Some(Uri(SEED.into())), Some(true))
|
||||
.unwrap();
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
|
||||
while simulator
|
||||
.native_caps()
|
||||
.and_then(|caps| caps.capability_uri("InventoryAPIv3".into()).ok().flatten())
|
||||
.is_none()
|
||||
&& std::time::Instant::now() < deadline
|
||||
{
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
let mut network = client.network();
|
||||
network.set_current_sim(Some(simulator));
|
||||
client.set_network(network);
|
||||
(client, requests)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn records_exact_ais_verbs_paths_headers_and_llsd_payloads() {
|
||||
let (client, requests) = client_with_response(Vec::new());
|
||||
let ais = client.ais_client();
|
||||
assert!(ais.is_available(), "seed capability should be visible");
|
||||
let item = UUID::random().unwrap();
|
||||
let destination = UUID::random().unwrap();
|
||||
let payload = OSD::Map(HashMap::from([(
|
||||
"name".into(),
|
||||
OSD::String("renamed".into()),
|
||||
)]));
|
||||
|
||||
assert!(block_on(ais.update_item(item, payload.clone(), None)).unwrap());
|
||||
assert!(block_on(ais.copy_item(item, destination, None)).unwrap());
|
||||
assert!(
|
||||
block_on(ais.get_category_children("current".into(), 0, true, None))
|
||||
.unwrap()
|
||||
.0
|
||||
);
|
||||
|
||||
let requests = requests.lock().unwrap();
|
||||
assert_eq!(requests.len(), 3);
|
||||
assert_eq!(requests[0].method, "PATCH");
|
||||
assert_eq!(requests[0].uri.0, format!("{CAP}/item/{item}"));
|
||||
assert_eq!(
|
||||
requests[0].content_type.as_deref(),
|
||||
Some("application/llsd+xml")
|
||||
);
|
||||
assert_eq!(
|
||||
OSDParser::deserialize_with_bytes(requests[0].body.clone()).unwrap(),
|
||||
payload
|
||||
);
|
||||
assert_eq!(requests[1].method, "COPY");
|
||||
assert!(
|
||||
requests[1]
|
||||
.uri
|
||||
.0
|
||||
.starts_with(&format!("{CAP}/item/{item}?tid="))
|
||||
);
|
||||
assert_eq!(
|
||||
requests[1].headers.get("Destination"),
|
||||
Some(&destination.to_string())
|
||||
);
|
||||
assert!(requests[1].body.is_empty());
|
||||
assert_eq!(requests[2].method, "GET");
|
||||
assert_eq!(
|
||||
requests[2].uri.0,
|
||||
format!("{CAP}/category/current/children?depth=50")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_and_cancelled_responses_leave_store_unchanged() {
|
||||
let (client, requests) = client_with_response(b"not llsd".to_vec());
|
||||
let owner = UUID::random().unwrap();
|
||||
let item_id = UUID::random().unwrap();
|
||||
let store = Inventory::new_with_grid_client_uuid(Arc::new(client.clone()), owner).unwrap();
|
||||
let item = InventoryItem::new_with_inventory_type_uuid(InventoryType::OBJECT, item_id).unwrap();
|
||||
store.update_node_for(&item).unwrap();
|
||||
client.inventory().native_set_store_for_tests(store.clone());
|
||||
|
||||
let result = block_on(client.ais_client().fetch_item(item_id, None)).unwrap();
|
||||
assert!(!result.0);
|
||||
assert!(store.contains_with_uuid(item_id).unwrap());
|
||||
assert_eq!(store.count(), 1);
|
||||
|
||||
let cancellation = CancellationTokenSource::new();
|
||||
cancellation.cancel();
|
||||
let result = block_on(
|
||||
client
|
||||
.ais_client()
|
||||
.fetch_item(item_id, Some(cancellation.token())),
|
||||
);
|
||||
assert!(matches!(result, Err(Error::Cancelled)));
|
||||
assert_eq!(requests.lock().unwrap().len(), 1);
|
||||
assert_eq!(store.count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partially_valid_response_is_rejected_before_any_reconciliation() {
|
||||
let existing = UUID::random().unwrap();
|
||||
let new_folder = UUID::random().unwrap();
|
||||
let malformed_item = UUID::random().unwrap();
|
||||
let zero = UUID::zero();
|
||||
let body = format!(
|
||||
"<llsd><map><key>_removed_items</key><array><uuid>{existing}</uuid></array><key>_embedded</key><map><key>categories</key><map><key>{new_folder}</key><map><key>category_id</key><uuid>{new_folder}</uuid><key>parent_id</key><uuid>{zero}</uuid><key>name</key><string>valid folder</string></map></map><key>items</key><map><key>{malformed_item}</key><map><key>item_id</key><string>not-a-uuid</string></map></map></map></map></llsd>"
|
||||
);
|
||||
let (client, _) = client_with_response(body.into_bytes());
|
||||
let store =
|
||||
Inventory::new_with_grid_client_uuid(Arc::new(client.clone()), UUID::random().unwrap())
|
||||
.unwrap();
|
||||
let item =
|
||||
InventoryItem::new_with_inventory_type_uuid(InventoryType::OBJECT, existing).unwrap();
|
||||
store.update_node_for(&item).unwrap();
|
||||
client.inventory().native_set_store_for_tests(store.clone());
|
||||
|
||||
let result = block_on(client.ais_client().get_category("current".into(), None)).unwrap();
|
||||
assert!(!result.0);
|
||||
assert!(store.contains_with_uuid(existing).unwrap());
|
||||
assert!(!store.contains_with_uuid(new_folder).unwrap());
|
||||
assert_eq!(store.count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_metadata_reconciles_removals_and_folder_versions_together() {
|
||||
let folder_id = UUID::random().unwrap();
|
||||
let item_id = UUID::random().unwrap();
|
||||
let body = format!(
|
||||
"<llsd><map><key>_removed_items</key><array><uuid>{item_id}</uuid></array><key>_updated_category_versions</key><map><key>{folder_id}</key><integer>17</integer></map></map></llsd>"
|
||||
);
|
||||
let (client, _) = client_with_response(body.into_bytes());
|
||||
let owner = UUID::random().unwrap();
|
||||
let store = Inventory::new_with_grid_client_uuid(Arc::new(client.clone()), owner).unwrap();
|
||||
let folder = InventoryFolder::new(folder_id).unwrap();
|
||||
let mut item =
|
||||
InventoryItem::new_with_inventory_type_uuid(InventoryType::OBJECT, item_id).unwrap();
|
||||
item.base.set_parent_uuid(folder_id);
|
||||
store.update_node_for(&folder).unwrap();
|
||||
store.update_node_for(&item).unwrap();
|
||||
client.inventory().native_set_store_for_tests(store.clone());
|
||||
|
||||
assert!(block_on(client.ais_client().remove_item(item_id, None)).unwrap());
|
||||
assert!(!store.contains_with_uuid(item_id).unwrap());
|
||||
let updated: Option<InventoryFolder> = store
|
||||
.get_value_or_default_with_uuid_a7c63fbe(folder_id)
|
||||
.unwrap();
|
||||
assert_eq!(updated.unwrap().version(), 17);
|
||||
}
|
||||
@@ -1215,6 +1215,30 @@ impl InventoryManager {
|
||||
read(&self.inner.store).clone()
|
||||
}
|
||||
|
||||
pub(crate) fn native_reconcile_ais(
|
||||
&self,
|
||||
folders: Vec<InventoryFolder>,
|
||||
items: Vec<InventoryItem>,
|
||||
meta: &crate::inventory_ais::AISResponseMeta,
|
||||
) -> Result<(), Error> {
|
||||
let Some(store) = self.store_clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
store.native_apply_ais_batch(
|
||||
folders,
|
||||
items,
|
||||
&meta.items_removed(),
|
||||
&meta.categories_removed(),
|
||||
&meta.broken_links_removed(),
|
||||
&meta.category_version_updates(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn native_set_store_for_tests(&self, store: Inventory) {
|
||||
*write(&self.inner.store) = Some(store);
|
||||
}
|
||||
|
||||
fn cache_server_item(&self, item: &InventoryItem) -> Result<(), Error> {
|
||||
if item.base.uuid() == UUID::zero() || item.inventory_type() == InventoryType::FOLDER {
|
||||
return Err(Error::Argument);
|
||||
|
||||
@@ -15,6 +15,7 @@ mod download_manager;
|
||||
mod event_queue;
|
||||
mod gesture;
|
||||
mod inventory;
|
||||
mod inventory_ais;
|
||||
mod inventory_manager;
|
||||
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
|
||||
mod foliage_catalog;
|
||||
@@ -47,6 +48,8 @@ mod avatar_animesh_semantics;
|
||||
#[cfg(test)]
|
||||
mod batch_link_internal_semantics;
|
||||
#[cfg(test)]
|
||||
mod inventory_ais_internal_semantics;
|
||||
#[cfg(test)]
|
||||
mod world_internal_semantics;
|
||||
|
||||
type Terrain = Vec<Vec<f32>>;
|
||||
@@ -146,17 +149,16 @@ impl appearance::CurrentOutfitFolder {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async, dead_code)]
|
||||
#[allow(dead_code)] // Internal C# surface consumed by InventoryManager workflows.
|
||||
impl InventoryAISClient {
|
||||
async fn create_inventory_links(
|
||||
&self,
|
||||
_parent_uuid: libremetaverse_types::UUID,
|
||||
_new_inventory: libremetaverse_structured_data::OSD,
|
||||
_cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
parent_uuid: libremetaverse_types::UUID,
|
||||
new_inventory: libremetaverse_structured_data::OSD,
|
||||
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> Result<Vec<InventoryItem>, Error> {
|
||||
libremetaverse_types::not_implemented(
|
||||
"M:LibreMetaverse.InventoryAISClient.CreateInventoryLinksAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)",
|
||||
)
|
||||
self.native_create_inventory_links(parent_uuid, new_inventory, cancellation_token)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,3 +69,27 @@ snapshot subscribers before invoking them. Focused coverage lives in
|
||||
`inventory_manager_semantics`, `task_inventory_semantics`, and the native
|
||||
`inventory_manager::tests` fake-packet cases for callback isolation, bulk-copy
|
||||
completion, stale folder versions, offers, capability events, and bounded Xfer.
|
||||
|
||||
## Inventory API v3
|
||||
|
||||
`InventoryAISClient` implements the `InventoryAPIv3` and `LibraryAPIv3`
|
||||
capability resources for categories, children, links, individual items, the
|
||||
current outfit folder, orphans, and trash. Requests preserve the AIS verbs and
|
||||
resource shapes, including `COPY` with its `Destination` header, transaction
|
||||
IDs, depth/subset queries, and LLSD XML request content type. The client uses
|
||||
the injected capability HTTP transport, so tests and applications can record
|
||||
requests deterministically without replacing inventory logic.
|
||||
|
||||
Successful response bodies are fully decoded and validated before any local
|
||||
state changes. Embedded folders, concrete items, and links are parsed together;
|
||||
object or mesh links with the legacy texture inventory type are corrected to
|
||||
attachments. Side-effect removals and category version updates are combined
|
||||
with returned objects and installed atomically through a detached candidate
|
||||
inventory graph. Malformed, oversized, cancelled, or timed-out responses leave
|
||||
the live store unchanged, and no store lock crosses an await or callback.
|
||||
|
||||
AIS remains capability-optional. Missing capabilities and HTTP failures return
|
||||
the fixed false/empty compatibility results, while cancellation is propagated.
|
||||
The existing `InventoryManager` UDP fallback remains the contract-correct path
|
||||
for operations whose capability is absent; AIS reconciliation shares that
|
||||
manager's transport-independent store and observer ordering.
|
||||
|
||||
104
tools/check_milestone_09_issue_63.py
Normal file
104
tools/check_milestone_09_issue_63.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Audit issue 63's native AISv3 capability and reconciliation 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_ais.rs"
|
||||
HTTP = ROOT / "crates" / "libremetaverse" / "src" / "caps_http.rs"
|
||||
INVENTORY = ROOT / "crates" / "libremetaverse" / "src" / "inventory.rs"
|
||||
NATIVE_TESTS = ROOT / "crates" / "libremetaverse" / "src" / "inventory_ais_internal_semantics.rs"
|
||||
COMPAT_TESTS = ROOT / "tests" / "compat" / "tests" / "inventory_ais_semantics.rs"
|
||||
DOC = ROOT / "docs" / "inventory.md"
|
||||
MAPPING = ROOT / "api" / "RUST-MAPPING.tsv"
|
||||
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")
|
||||
|
||||
|
||||
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:
|
||||
native_types = generate_api_shims.NATIVE_TYPES | generate_api_shims.NATIVE_DECLARATIONS
|
||||
for type_id in ("T:LibreMetaverse.AISResponseMeta", "T:LibreMetaverse.InventoryAISClient"):
|
||||
if type_id not in native_types:
|
||||
raise SystemExit(f"issue 63 native type missing: {type_id}")
|
||||
|
||||
mapped = generate_api_shims.NATIVE_MEMBER_BODIES
|
||||
required = set()
|
||||
for row in MAPPING.read_text().splitlines()[1:]:
|
||||
doc_id = row.split("\t", 1)[0]
|
||||
if doc_id.startswith(("M:LibreMetaverse.InventoryAISClient.", "P:LibreMetaverse.InventoryAISClient.")):
|
||||
required.add(doc_id)
|
||||
missing = sorted(required - mapped.keys())
|
||||
if missing:
|
||||
raise SystemExit("issue 63 native members missing: " + ", ".join(missing))
|
||||
for member in (
|
||||
"P:LibreMetaverse.GridClient.AisClient",
|
||||
"P:LibreMetaverse.GridClient.AisClient#set",
|
||||
):
|
||||
if member not in mapped:
|
||||
raise SystemExit(f"issue 63 GridClient member missing: {member}")
|
||||
if STUB_RE.search(SOURCE.read_text()):
|
||||
raise SystemExit("issue 63 owned Rust stubs remain in inventory_ais.rs")
|
||||
|
||||
require_markers(
|
||||
SOURCE,
|
||||
(
|
||||
'"COPY"',
|
||||
'"Destination"',
|
||||
'"application/llsd+xml"',
|
||||
"MAX_FOLDER_DEPTH_REQUEST",
|
||||
"MAX_AIS_OBJECTS",
|
||||
"Error::Cancelled",
|
||||
"parse_meta_map",
|
||||
"native_reconcile_ais",
|
||||
),
|
||||
)
|
||||
require_markers(HTTP, ("send_custom", "send_with_headers"))
|
||||
require_markers(INVENTORY, ("native_apply_ais_batch", "detached candidate"))
|
||||
require_markers(
|
||||
NATIVE_TESTS,
|
||||
(
|
||||
"records_exact_ais_verbs_paths_headers_and_llsd_payloads",
|
||||
"malformed_and_cancelled_responses_leave_store_unchanged",
|
||||
"partially_valid_response_is_rejected_before_any_reconciliation",
|
||||
"response_metadata_reconciles_removals_and_folder_versions_together",
|
||||
),
|
||||
)
|
||||
require_markers(
|
||||
COMPAT_TESTS,
|
||||
(
|
||||
"parse_links_object_asset_results_in_attachment_type_and_parses_fields",
|
||||
"parse_embedded_combines_items_and_links",
|
||||
"move_category_links_returns_completed_future",
|
||||
),
|
||||
)
|
||||
require_markers(
|
||||
DOC,
|
||||
(
|
||||
"Inventory API v3",
|
||||
"InventoryAPIv3",
|
||||
"LibraryAPIv3",
|
||||
"Destination",
|
||||
"atomically",
|
||||
"UDP fallback",
|
||||
),
|
||||
)
|
||||
print(
|
||||
"issue 63 audit: AIS endpoints, LLSD parsing, COPY headers, bounded atomic "
|
||||
"reconciliation, documentation, and focused tests are present"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -38,6 +38,7 @@ TARGETS = {
|
||||
# implementations. The generated module keeps catalog markers and re-exports
|
||||
# the hand-written type so coverage remains deterministic.
|
||||
NATIVE_TYPES = {
|
||||
"T:LibreMetaverse.AISResponseMeta": "crate::inventory_ais::AISResponseMeta",
|
||||
"T:LibreMetaverse.AccountLevelBenefits": "crate::login::AccountLevelBenefits",
|
||||
"T:LibreMetaverse.AgentDisplayName": "crate::message_decoder::AgentDisplayName",
|
||||
"T:LibreMetaverse.AgentDataReplyEventArgs": "crate::agent_messages::AgentDataReplyEventArgs",
|
||||
@@ -262,6 +263,7 @@ NATIVE_DECLARATIONS = {
|
||||
"T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager",
|
||||
"T:LibreMetaverse.GridClient": "crate::client_core::GridClient",
|
||||
"T:LibreMetaverse.InventoryManager": "crate::inventory_manager::InventoryManager",
|
||||
"T:LibreMetaverse.InventoryAISClient": "crate::inventory_ais::InventoryAISClient",
|
||||
"T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager",
|
||||
"T:LibreMetaverse.Settings": "crate::client_core::Settings",
|
||||
"T:LibreMetaverse.Simulator": "crate::network_manager::Simulator",
|
||||
@@ -337,6 +339,10 @@ NATIVE_MEMBER_BODIES = {
|
||||
"self.native_inventory().unwrap_or_else(|_| panic!(\"failed to construct InventoryManager\"))",
|
||||
"P:LibreMetaverse.GridClient.Inventory#set":
|
||||
"self.native_set_inventory(value)",
|
||||
"P:LibreMetaverse.GridClient.AisClient":
|
||||
"self.native_ais_client().unwrap_or_else(|_| panic!(\"failed to construct InventoryAISClient\"))",
|
||||
"P:LibreMetaverse.GridClient.AisClient#set":
|
||||
"self.native_set_ais_client(value)",
|
||||
"P:LibreMetaverse.GridClient.Network":
|
||||
"self.native_network().unwrap_or_else(|_| panic!(\"failed to construct NetworkManager\"))",
|
||||
"P:LibreMetaverse.GridClient.Network#set":
|
||||
@@ -363,6 +369,81 @@ NATIVE_MEMBER_BODIES = {
|
||||
"crate::inventory_manager::InventoryManager::native_magic_id()",
|
||||
"M:LibreMetaverse.InventoryManager.#ctor(LibreMetaverse.GridClient)":
|
||||
"crate::inventory_manager::InventoryManager::native_new(client)",
|
||||
"F:LibreMetaverse.InventoryAISClient.INVENTORY_CAP_NAME":
|
||||
"crate::inventory_ais::InventoryAISClient::INVENTORY_CAP_NAME",
|
||||
"F:LibreMetaverse.InventoryAISClient.LIBRARY_CAP_NAME":
|
||||
"crate::inventory_ais::InventoryAISClient::LIBRARY_CAP_NAME",
|
||||
"M:LibreMetaverse.InventoryAISClient.#ctor(LibreMetaverse.GridClient)":
|
||||
"crate::inventory_ais::InventoryAISClient::native_new(client)",
|
||||
"P:LibreMetaverse.InventoryAISClient.IsAvailable": "self.native_is_available()",
|
||||
"M:LibreMetaverse.InventoryAISClient.CreateInventoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Boolean,System.Threading.CancellationToken)":
|
||||
"self.native_create_inventory(parent_uuid, new_inventory, create_link, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.SlamFolderAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
|
||||
"self.native_slam_folder(folder_uuid, new_inventory, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.RemoveCategoryAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_remove_category(category_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.RemoveItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_remove_item(item_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyLibraryCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)":
|
||||
"self.native_copy_library_category(source_uuid, dest_uuid, copy_subfolders, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.PurgeDescendentsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_purge_descendents(category_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.UpdateCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
|
||||
"self.native_update_category(category_uuid, updates, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.UpdateItemAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
|
||||
"self.native_update_item(item_uuid, updates, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)":
|
||||
"self.native_copy_category(source_uuid, dest_uuid, simulate, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.MoveCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_move_category(source_uuid, dest_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.PutCategoryChildrenAsync(System.String,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
|
||||
"self.native_put_category_children(category, children_payload, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyCategoryChildrenAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_copy_category_children(category, dest_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.MoveCategoryChildrenAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_move_category_children(category, dest_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.DeleteCategoryChildrenAsync(System.String,System.Threading.CancellationToken)":
|
||||
"self.native_delete_category_children(category, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.PutCategoryLinksAsync(System.String,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
|
||||
"self.native_put_category_links(category, links_payload, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyCategoryLinksAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_copy_category_links(category, dest_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.MoveCategoryLinksAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_move_category_links(category, dest_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.DeleteCategoryLinksAsync(System.String,System.Threading.CancellationToken)":
|
||||
"self.native_delete_category_links(category, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_copy_item(item_uuid, dest_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_move_item(item_uuid, dest_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.GetCategoryAsync(System.String,System.Threading.CancellationToken)":
|
||||
"self.native_get_category(category, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.GetCategoryChildrenAsync(System.String,System.Int32,System.Boolean,System.Threading.CancellationToken)":
|
||||
"self.native_get_category_children(category, depth, recursive, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchCategoryCategoriesAsync(LibreMetaverse.UUID,System.Boolean,System.Boolean,System.Int32,System.Threading.CancellationToken)":
|
||||
"self.native_fetch_category_categories(cat_id, use_inventory_cap, recursive, depth, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchCategorySubsetAsync(LibreMetaverse.UUID,System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Boolean,System.Boolean,System.Int32,System.Threading.CancellationToken)":
|
||||
"self.native_fetch_category_subset(cat_id, specific_children, use_inventory_cap, recursive, depth, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.GetCategoryLinksAsync(System.String,System.Threading.CancellationToken)":
|
||||
"self.native_get_category_links(category, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
"self.native_fetch_item(item_uuid, cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchCOFAsync(System.Threading.CancellationToken)":
|
||||
"self.native_fetch_cof(cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.FetchOrphansAsync(System.Threading.CancellationToken)":
|
||||
"self.native_fetch_orphans(cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.EmptyTrashAsync(System.Threading.CancellationToken)":
|
||||
"self.native_empty_trash(cancellation_token).await",
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseItemsFromEmbedded(LibreMetaverse.StructuredData.OSDMap)":
|
||||
"self.native_parse_items_from_embedded(response)",
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseFoldersFromEmbedded(LibreMetaverse.StructuredData.OSDMap)":
|
||||
"self.native_parse_folders_from_embedded(response)",
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseLinksFromEmbedded(LibreMetaverse.StructuredData.OSDMap)":
|
||||
"self.native_parse_links_from_embedded(response)",
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseEmbedded(LibreMetaverse.StructuredData.OSDMap,System.Collections.Generic.List{LibreMetaverse.InventoryFolder}@,System.Collections.Generic.List{LibreMetaverse.InventoryItem}@,System.Collections.Generic.List{LibreMetaverse.InventoryItem}@)":
|
||||
"self.native_parse_embedded(response, folders, items, links)",
|
||||
"M:LibreMetaverse.InventoryAISClient.ParseAISResponseMeta(LibreMetaverse.StructuredData.OSDMap)":
|
||||
"crate::inventory_ais::InventoryAISClient::native_parse_ais_response_meta(response)",
|
||||
"M:LibreMetaverse.InventoryManager.CreateInventoryItem(LibreMetaverse.InventoryType,LibreMetaverse.UUID)":
|
||||
"crate::inventory_manager::InventoryManager::native_create_inventory_item(type_, id)",
|
||||
"M:LibreMetaverse.InventoryManager.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
|
||||
|
||||
@@ -994,6 +994,7 @@ def validate_generated_shims() -> None:
|
||||
"crate::gesture::",
|
||||
"crate::json_codec::",
|
||||
"crate::inventory::",
|
||||
"crate::inventory_ais::",
|
||||
"crate::inventory_manager::",
|
||||
"crate::message_codec::",
|
||||
"crate::message_decoder::",
|
||||
|
||||
Reference in New Issue
Block a user