diff --git a/LibreMetaverse/Appearance/CurrentOutfitFolder.cs b/LibreMetaverse/Appearance/CurrentOutfitFolder.cs
index 625af162..fce60f31 100644
--- a/LibreMetaverse/Appearance/CurrentOutfitFolder.cs
+++ b/LibreMetaverse/Appearance/CurrentOutfitFolder.cs
@@ -500,6 +500,53 @@ await client.Inventory.CreateLinkAsync(COF.UUID, item.UUID, item.Name,
}
}
+ ///
+ /// Creates multiple COF links in a single request.
+ ///
+ /// Original items to be linked to COF
+ ///
+ /// Note: On simulators supporting AIS3 it runs in a single request. On simulators not supporting it, it falls back
+ /// to iterating and calling for each element.
+ ///
+ public async Task AddLinks(IEnumerable items, CancellationToken cancellationToken = default)
+ {
+ if (COF == null)
+ {
+ return;
+ }
+
+ if (items == null || !items.Any())
+ {
+ return;
+ }
+
+ List cofLinks = await GetCurrentOutfitLinks(cancellationToken);
+ IEnumerable newLinks = items
+ .Where(item => cofLinks.Find(itemLink => itemLink.AssetUUID == item.ResolvedAssetID) == null);
+
+ if (client.AisClient.IsAvailable)
+ {
+ await client.Inventory.CreateLinksAsync(COF.UUID, newLinks, success =>
+ {
+ client.Inventory.RequestFolderContents(
+ COF.UUID,
+ COF.OwnerID,
+ fetchFolders: true,
+ fetchItems: true,
+ order: InventorySortOrder.ByName,
+ cancellationToken: cancellationToken
+ ).ConfigureAwait(false);
+ }, cancellationToken);
+ }
+ else
+ {
+ foreach (InventoryItem item in newLinks)
+ {
+ await AddLink(item, cancellationToken);
+ }
+ }
+ }
+
protected async Task RemoveLinksToByActualId(IEnumerable actualItemIdsToRemoveLinksTo, CancellationToken cancellationToken = default)
{
var actualItemIdsSet = actualItemIdsToRemoveLinksTo.ToArray();
@@ -736,6 +783,7 @@ public async Task ReplaceOutfit(UUID newOutfitFolderId, CancellationToken
}
var trashFolderId = client.Inventory.FindFolderForType(FolderType.Trash);
+ var outfitsFolderId = client.Inventory.FindFolderForType(FolderType.Outfit);
var rootFolderId = client.Inventory.Store.RootFolder.UUID;
var newOutfit = await client.Inventory.RequestFolderContents(
@@ -965,35 +1013,39 @@ public async Task ReplaceOutfit(UUID newOutfitFolderId, CancellationToken
var toRemoveIds = linksToRemove
.Select(n => n.UUID)
.Distinct();
- await client.Inventory.RemoveItemsAsync(toRemoveIds, cancellationToken);
+ if (toRemoveIds.Any())
+ {
+ await client.Inventory.RemoveItemsAsync(toRemoveIds, cancellationToken);
+ }
// Add body parts from current outfit to new outfit if it's lacking those essential body parts
foreach (var item in bodypartsToWear)
{
itemsBeingAdded.Add(item.Value.UUID, item.Value);
}
- foreach (var item in itemsBeingAdded)
- {
- await AddLink(item.Value, cancellationToken);
- }
+ await AddLinks(itemsBeingAdded.Values, cancellationToken);
- // Add link to outfit folder we're putting on
- await client.Inventory.CreateLinkAsync(
- currentOutfitFolder.UUID,
- newOutfitFolderNode.Data.UUID,
- newOutfitFolderNode.Data.Name,
- "",
- InventoryType.Folder,
- UUID.Random(),
- (success, newItem) =>
- {
+ bool isOutfitFolder = await IsObjectDescendentOf(newOutfitFolderNode.Data, outfitsFolderId, cancellationToken);
+ // Add link to outfit folder we're putting on, but only if its a child of "Outfits"
+ if (isOutfitFolder)
+ {
+ await client.Inventory.CreateLinkAsync(
+ currentOutfitFolder.UUID,
+ newOutfitFolderNode.Data.UUID,
+ newOutfitFolderNode.Data.Name,
+ "",
+ InventoryType.Folder,
+ UUID.Random(),
+ (success, newItem) =>
+ {
if (success && newItem != null)
{
_ = client.Inventory.RequestFetchInventoryAsync(newItem.UUID, newItem.OwnerID);
}
- },
- cancellationToken
- ).ConfigureAwait(false);
+ },
+ cancellationToken
+ ).ConfigureAwait(false);
+ }
// Wear new outfit
var tcs = new TaskCompletionSource();
@@ -1214,10 +1266,7 @@ public async Task AddToOutfit(List requestedItemsToAdd, bool repl
}
// Add links to new items
- foreach (var item in itemsToAdd)
- {
- await AddLink(item, cancellationToken);
- }
+ await AddLinks(itemsToAdd, cancellationToken);
client.Appearance.AddToOutfit(itemsToAdd, replace);
_ = Task.Run(async () =>
diff --git a/LibreMetaverse/Inventory/InventoryManager.cs b/LibreMetaverse/Inventory/InventoryManager.cs
index 6a3b639d..baa4331e 100644
--- a/LibreMetaverse/Inventory/InventoryManager.cs
+++ b/LibreMetaverse/Inventory/InventoryManager.cs
@@ -1989,6 +1989,73 @@ public async Task CreateLinkAsync(UUID folderID, UUID itemID, string name, strin
}
}
+ ///
+ /// Creates multiple inventory links to another inventory item or folder at once.
+ ///
+ ///
+ ///
+ ///
+ public async Task CreateLinksAsync(
+ UUID folderID,
+ IEnumerable items,
+ Action callback,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (Client.AisClient.IsAvailable)
+ {
+ OSDArray links = new OSDArray();
+ foreach (InventoryBase baseItem in items)
+ {
+ switch (baseItem)
+ {
+ case InventoryItem item:
+ {
+ OSDMap link = new OSDMap
+ {
+ ["linked_id"] = OSD.FromUUID(item.UUID),
+ ["type"] = OSD.FromInteger((int)AssetType.Link),
+ ["inv_type"] = OSD.FromInteger((int)item.InventoryType),
+ ["name"] = OSD.FromString(item.Name),
+ ["desc"] = OSD.FromString(item.Description)
+ };
+
+ links.Add(link);
+ break;
+ }
+ case InventoryFolder folder:
+ {
+ OSDMap link = new OSDMap
+ {
+ ["linked_id"] = OSD.FromUUID(folder.UUID),
+ ["type"] = OSD.FromInteger((int)AssetType.LinkFolder),
+ ["inv_type"] = OSD.FromInteger((int)InventoryType.Folder),
+ ["name"] = OSD.FromString(folder.Name),
+ ["desc"] = OSD.FromString(string.Empty)
+ };
+
+ links.Add(link);
+ }
+ break;
+ }
+ }
+
+ OSDMap newInventory = new OSDMap { { "links", links } };
+
+ await Client.AisClient.CreateInventory(
+ folderID,
+ newInventory,
+ true,
+ (success, reply) => callback?.Invoke(success),
+ cancellationToken
+ ).ConfigureAwait(false);
+ }
+ else
+ {
+ throw new InvalidOperationException("Creating of batch links only supported on AIS3");
+ }
+ }
+
#endregion Create
#region Copy