diff --git a/OpenSim/Framework/Communications/Cache/CachedUserInfo.cs b/OpenSim/Framework/Communications/Cache/CachedUserInfo.cs index 4212fbcf96..b4e7bfabd5 100644 --- a/OpenSim/Framework/Communications/Cache/CachedUserInfo.cs +++ b/OpenSim/Framework/Communications/Cache/CachedUserInfo.cs @@ -556,8 +556,10 @@ namespace OpenSim.Framework.Communications.Cache } /// - /// Add an item to the user's inventory + /// Add an item to the user's inventory. /// + /// If the item has no folder set (i.e. it is UUID.Zero), then it is placed in the most appropriate folder + /// for that type. /// public void AddItem(InventoryItemBase item) { @@ -572,6 +574,7 @@ namespace OpenSim.Framework.Communications.Cache item.Folder = RootFolder.ID; } ItemReceive(item, null); + if (m_commsManager.SecureInventoryService != null) { m_commsManager.SecureInventoryService.AddItem(item, m_session_id); diff --git a/OpenSim/Framework/Communications/Cache/InventoryFolderImpl.cs b/OpenSim/Framework/Communications/Cache/InventoryFolderImpl.cs index 8e624f97cc..07042327e2 100644 --- a/OpenSim/Framework/Communications/Cache/InventoryFolderImpl.cs +++ b/OpenSim/Framework/Communications/Cache/InventoryFolderImpl.cs @@ -358,6 +358,10 @@ namespace OpenSim.Framework.Communications.Cache return folderList; } + /// + /// The total number of items in this folder and in the immediate child folders (though not from other + /// descendants). + /// public int TotalCount { get diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs index 15a89d3688..8a3c4b6c59 100644 --- a/OpenSim/Framework/IClientAPI.cs +++ b/OpenSim/Framework/IClientAPI.cs @@ -828,6 +828,13 @@ namespace OpenSim.Framework void SendTaskInventory(UUID taskID, short serial, byte[] fileName); + /// + /// Used by the server to inform the client of new inventory items. Will transfer the contents of the folder + /// (including all descendent folders) as well as the folder itself. + /// + /// + void SendBulkUpdateInventory(InventoryFolderBase folder); + /// /// Used by the server to inform the client of a new inventory item. Used when transferring items /// between avatars, possibly among other things. diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 74bf7e5d37..42f190f284 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -1144,6 +1144,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP msg.MessageBlock.Message = Utils.StringToBytes(message); msg.MessageBlock.BinaryBucket = binaryBucket; + System.Console.WriteLine("SendInstantMessage: " + msg); OutPacket(msg, ThrottleOutPacketType.Task); } } @@ -1763,6 +1764,153 @@ namespace OpenSim.Region.ClientStack.LindenUDP inventoryReply.Header.Zerocoded = true; OutPacket(inventoryReply, ThrottleOutPacketType.Asset); } + + /// IClientAPI.SendBulkUpdateInventory(InventoryFolderBase) + public void SendBulkUpdateInventory(InventoryFolderBase folderBase) + { + // XXX: Nasty temporary move that will be resolved shortly + InventoryFolderImpl folder = (InventoryFolderImpl)folderBase; + + // We will use the same transaction id for all the separate packets to be sent out in this update. + UUID transactionId = UUID.Random(); + + List folderDataBlocks + = new List(); + + SendBulkUpdateInventoryRecursive(folder, ref folderDataBlocks, transactionId); + + if (folderDataBlocks.Count > 0) + { + // We'll end up with some unsent folder blocks if there were some empty folders at the end of the list + // Send these now + BulkUpdateInventoryPacket bulkUpdate + = (BulkUpdateInventoryPacket)PacketPool.Instance.GetPacket(PacketType.BulkUpdateInventory); + bulkUpdate.Header.Zerocoded = true; + + bulkUpdate.AgentData.AgentID = AgentId; + bulkUpdate.AgentData.TransactionID = transactionId; + bulkUpdate.FolderData = folderDataBlocks.ToArray(); + + Console.WriteLine("SendBulkUpdateInventory :" + bulkUpdate); + OutPacket(bulkUpdate, ThrottleOutPacketType.Asset); + } + } + + /// + /// Recursively construct bulk update packets to send folders and items + /// + /// + /// + /// + private void SendBulkUpdateInventoryRecursive( + InventoryFolderImpl folder, ref List folderDataBlocks, + UUID transactionId) + { + folderDataBlocks.Add(GenerateBulkUpdateFolderDataBlock(folder)); + + const int MAX_ITEMS_PER_PACKET = 5; + + // If there are any items then we have to start sending them off in this packet - the next folder will have + // to be in its own bulk update packet. Also, we can only fit 5 items in a packet (at least this was the limit + // being used on the Linden grid at 20081203). + List items = folder.RequestListOfItems(); + while (items.Count > 0) + { + BulkUpdateInventoryPacket bulkUpdate + = (BulkUpdateInventoryPacket)PacketPool.Instance.GetPacket(PacketType.BulkUpdateInventory); + bulkUpdate.Header.Zerocoded = true; + + bulkUpdate.AgentData.AgentID = AgentId; + bulkUpdate.AgentData.TransactionID = transactionId; + bulkUpdate.FolderData = folderDataBlocks.ToArray(); + + int itemsToSend = (items.Count > MAX_ITEMS_PER_PACKET ? MAX_ITEMS_PER_PACKET : items.Count); + bulkUpdate.ItemData = new BulkUpdateInventoryPacket.ItemDataBlock[itemsToSend]; + + for (int i = 0; i < itemsToSend; i++) + { + // Remove from the end of the list so that we don't incur a performance penalty + bulkUpdate.ItemData[i] = GenerateBulkUpdateItemDataBlock(items[items.Count - 1]); + items.RemoveAt(items.Count - 1); + } + + Console.WriteLine("SendBulkUpdateInventoryRecursive :" + bulkUpdate); + OutPacket(bulkUpdate, ThrottleOutPacketType.Asset); + + folderDataBlocks = new List(); + + // If we're going to be sending another items packet then it needs to contain just the folder to which those + // items belong. + if (items.Count > 0) + folderDataBlocks.Add(GenerateBulkUpdateFolderDataBlock(folder)); + } + + List subFolders = folder.RequestListOfFolderImpls(); + foreach (InventoryFolderImpl subFolder in subFolders) + { + SendBulkUpdateInventoryRecursive(subFolder, ref folderDataBlocks, transactionId); + } + } + + /// + /// Generate a bulk update inventory data block for the given folder + /// + /// + /// + private BulkUpdateInventoryPacket.FolderDataBlock GenerateBulkUpdateFolderDataBlock(InventoryFolderBase folder) + { + BulkUpdateInventoryPacket.FolderDataBlock folderBlock = new BulkUpdateInventoryPacket.FolderDataBlock(); + + folderBlock.FolderID = folder.ID; + folderBlock.ParentID = folder.ParentID; + folderBlock.Type = -1; + folderBlock.Name = Utils.StringToBytes(folder.Name); + + return folderBlock; + } + + /// + /// Generate a bulk update inventory data block for the given item + /// + /// + /// + private BulkUpdateInventoryPacket.ItemDataBlock GenerateBulkUpdateItemDataBlock(InventoryItemBase item) + { + BulkUpdateInventoryPacket.ItemDataBlock itemBlock = new BulkUpdateInventoryPacket.ItemDataBlock(); + + itemBlock.ItemID = item.ID; + itemBlock.AssetID = item.AssetID; + itemBlock.CreatorID = item.Creator; + itemBlock.BaseMask = item.BasePermissions; + itemBlock.Description = Utils.StringToBytes(item.Description); + itemBlock.EveryoneMask = item.EveryOnePermissions; + itemBlock.FolderID = item.Folder; + itemBlock.InvType = (sbyte)item.InvType; + itemBlock.Name = Utils.StringToBytes(item.Name); + itemBlock.NextOwnerMask = item.NextPermissions; + itemBlock.OwnerID = item.Owner; + itemBlock.OwnerMask = item.CurrentPermissions; + itemBlock.Type = (sbyte)item.AssetType; + itemBlock.GroupID = item.GroupID; + itemBlock.GroupOwned = item.GroupOwned; + itemBlock.GroupMask = item.GroupPermissions; + itemBlock.Flags = item.Flags; + itemBlock.SalePrice = item.SalePrice; + itemBlock.SaleType = item.SaleType; + itemBlock.CreationDate = item.CreationDate; + + itemBlock.CRC = + Helpers.InventoryCRC( + 1000, 0, itemBlock.InvType, + itemBlock.Type, itemBlock.AssetID, + itemBlock.GroupID, 100, + itemBlock.OwnerID, itemBlock.CreatorID, + itemBlock.ItemID, itemBlock.FolderID, + (uint)PermissionMask.All, 1, (uint)PermissionMask.All, (uint)PermissionMask.All, + (uint)PermissionMask.All); + + return itemBlock; + } /// IClientAPI.SendBulkUpdateInventory(InventoryItemBase) public void SendBulkUpdateInventory(InventoryItemBase item) diff --git a/OpenSim/Region/Environment/Modules/Avatar/Inventory/Transfer/InventoryTransferModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Inventory/Transfer/InventoryTransferModule.cs index 73f1761664..b41c36f92a 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/Inventory/Transfer/InventoryTransferModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/Inventory/Transfer/InventoryTransferModule.cs @@ -136,31 +136,78 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Inventory.Transfer if (im.dialog == (byte) InstantMessageDialog.InventoryOffered) { - ScenePresence user = - scene.GetScenePresence(new UUID(im.toAgentID)); + m_log.DebugFormat("Asset type {0}", ((AssetType)im.binaryBucket[0])); + + ScenePresence user = scene.GetScenePresence(new UUID(im.toAgentID)); + UUID copyID; - // First byte of the array is probably the item type - // Next 16 bytes are the UUID - - UUID itemID = new UUID(im.binaryBucket, 1); - - m_log.DebugFormat("[AGENT INVENTORY]: Inserting item {0} "+ - "into agent {1}'s inventory", - itemID, new UUID(im.toAgentID)); - - InventoryItemBase itemCopy = scene.GiveInventoryItem( - new UUID(im.toAgentID), - client.AgentId, itemID); - - if (itemCopy == null) - { - client.SendAgentAlertMessage("Can't find item to give. Nothing given.", false); + // First byte is the asset type + AssetType assetType = (AssetType)im.binaryBucket[0]; + + // Temporarily disabled pending test of complex transfers (folders within folders, lots of items, + // empty folders, etc.) + if (AssetType.Folder == assetType) return; + + if (AssetType.Folder == assetType) + { + UUID folderID = new UUID(im.binaryBucket, 1); + + m_log.DebugFormat("[AGENT INVENTORY]: Inserting original folder {0} "+ + "into agent {1}'s inventory", + folderID, new UUID(im.toAgentID)); + + InventoryFolderImpl folderCopy + = scene.GiveInventoryFolder(new UUID(im.toAgentID), client.AgentId, folderID, UUID.Zero); + + if (folderCopy == null) + { + client.SendAgentAlertMessage("Can't find folder to give. Nothing given.", false); + return; + } + + // The outgoing binary bucket should contain only the byte which signals an asset folder is + // being copied and the following bytes for the copied folder's UUID + copyID = folderCopy.ID; + byte[] copyIDBytes = copyID.GetBytes(); + im.binaryBucket = new byte[1 + copyIDBytes.Length]; + im.binaryBucket[0] = (byte)AssetType.Folder; + Array.Copy(copyIDBytes, 0, im.binaryBucket, 1, copyIDBytes.Length); + + if (user != null && !user.IsChildAgent) + { + user.ControllingClient.SendBulkUpdateInventory(folderCopy); + } } + else + { + // First byte of the array is probably the item type + // Next 16 bytes are the UUID - byte[] itemCopyID = itemCopy.ID.GetBytes(); + UUID itemID = new UUID(im.binaryBucket, 1); - Array.Copy(itemCopyID, 0, im.binaryBucket, 1, 16); + m_log.DebugFormat("[AGENT INVENTORY]: Inserting item {0} "+ + "into agent {1}'s inventory", + itemID, new UUID(im.toAgentID)); + + InventoryItemBase itemCopy = scene.GiveInventoryItem( + new UUID(im.toAgentID), + client.AgentId, itemID); + + if (itemCopy == null) + { + client.SendAgentAlertMessage("Can't find item to give. Nothing given.", false); + return; + } + + copyID = itemCopy.ID; + Array.Copy(copyID.GetBytes(), 0, im.binaryBucket, 1, 16); + + if (user != null && !user.IsChildAgent) + { + user.ControllingClient.SendBulkUpdateInventory(itemCopy); + } + } // Send the IM to the recipient. The item is already // in their inventory, so it will not be lost if @@ -168,10 +215,6 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Inventory.Transfer // if (user != null && !user.IsChildAgent) { - // User is online. So, let's make the item visible - // - user.ControllingClient.SendBulkUpdateInventory(itemCopy); - // And notify. Transaction ID is the item ID. We get that // same ID back on the reply so we know what to act on // @@ -179,7 +222,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Inventory.Transfer new UUID(im.fromAgentID), im.message, new UUID(im.toAgentID), im.fromAgentName, im.dialog, im.timestamp, - itemCopy.ID, false, im.binaryBucket); + copyID, false, im.binaryBucket); return; } @@ -208,9 +251,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Inventory.Transfer } } else if (im.dialog == (byte) InstantMessageDialog.InventoryDeclined) - { - UUID itemID = new UUID(im.imSessionID); // The item, back from it's trip - + { // Here, the recipient is local and we can assume that the // inventory is loaded. Courtesy of the above bulk update, // It will have been pushed to the client, too @@ -224,27 +265,43 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Inventory.Transfer { InventoryFolderImpl trashFolder = userInfo.FindFolderForType((int)AssetType.TrashFolder); - - InventoryItemBase item = - userInfo.RootFolder.FindItem(itemID); - - if (trashFolder != null && item != null) + + UUID inventoryEntityID = new UUID(im.imSessionID); // The inventory item/folder, back from it's trip + + InventoryItemBase item = userInfo.RootFolder.FindItem(inventoryEntityID); + InventoryFolderBase folder = null; + + if (item != null && trashFolder != null) { item.Folder = trashFolder.ID; - userInfo.DeleteItem(itemID); + userInfo.DeleteItem(inventoryEntityID); scene.AddInventoryItem(client, item); } else { - string reason = ""; + folder = userInfo.RootFolder.FindFolder(inventoryEntityID); + + if (folder != null & trashFolder != null) + { + userInfo.MoveFolder(inventoryEntityID, trashFolder.ID); + } + } + + if ((null == item && null == folder) | null == trashFolder) + { + string reason = String.Empty; + if (trashFolder == null) reason += " Trash folder not found."; if (item == null) reason += " Item not found."; + if (folder == null) + reason += " Folder not found."; + client.SendAgentAlertMessage("Unable to delete "+ - "received item" + reason, false); + "received inventory" + reason, false); } } diff --git a/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs b/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs index b5a5123bc2..fb1d1ff7fc 100644 --- a/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs +++ b/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs @@ -624,6 +624,9 @@ namespace OpenSim.Region.Environment.Modules.World.NPC public virtual void SendBulkUpdateInventory(InventoryItemBase item) { } + + public virtual void SendBulkUpdateInventory(InventoryFolderBase folderBase) + {} public void SendTakeControls(int controls, bool passToAgent, bool TakeControls) { diff --git a/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs b/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs index d1e0c2491c..3d6a90580d 100644 --- a/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs @@ -400,11 +400,11 @@ namespace OpenSim.Region.Environment.Scenes } /// - /// Give an inventory item from one avatar to another + /// Give an inventory item from one user to another /// /// /// ID of the sender of the item - /// + /// public virtual void GiveInventoryItem(IClientAPI recipientClient, UUID senderId, UUID itemId) { InventoryItemBase itemCopy = GiveInventoryItem(recipientClient.AgentId, senderId, itemId); @@ -413,7 +413,33 @@ namespace OpenSim.Region.Environment.Scenes recipientClient.SendBulkUpdateInventory(itemCopy); } + /// + /// Give an inventory item from one user to another + /// + /// + /// ID of the sender of the item + /// + /// The inventory item copy given, null if the give was unsuccessful public virtual InventoryItemBase GiveInventoryItem(UUID recipient, UUID senderId, UUID itemId) + { + return GiveInventoryItem(recipient, senderId, itemId, UUID.Zero); + } + + /// + /// Give an inventory item from one user to another + /// + /// + /// ID of the sender of the item + /// + /// + /// The id of the folder in which the copy item should go. If UUID.Zero then the item is placed in the most + /// appropriate default folder. + /// + /// + /// The inventory item copy given, null if the give was unsuccessful + /// + public virtual InventoryItemBase GiveInventoryItem( + UUID recipient, UUID senderId, UUID itemId, UUID recipientFolderId) { // Retrieve the item from the sender CachedUserInfo senderUserInfo = CommsManager.UserProfileCacheService.GetUserDetails(senderId); @@ -438,7 +464,6 @@ namespace OpenSim.Region.Environment.Scenes return null; } - // TODO get recipient's root folder CachedUserInfo recipientUserInfo = CommsManager.UserProfileCacheService.GetUserDetails(recipient); @@ -457,7 +482,8 @@ namespace OpenSim.Region.Environment.Scenes itemCopy.Name = item.Name; itemCopy.AssetType = item.AssetType; itemCopy.InvType = item.InvType; - itemCopy.Folder = UUID.Zero; + itemCopy.Folder = recipientFolderId; + if (Permissions.PropagatePermissions()) { if (item.InvType == 6) @@ -529,8 +555,93 @@ namespace OpenSim.Region.Environment.Scenes m_log.Error("[AGENT INVENTORY]: Failed to find item " + itemId.ToString() + ", no root folder"); return null; } + return null; } + + /// + /// Give an entire inventory folder from one user to another. The entire contents (including all descendent + /// folders) is given. + /// + /// + /// ID of the sender of the item + /// + /// + /// The id of the receipient folder in which the send folder should be placed. If UUID.Zero then the + /// recipient folder is the root folder + /// + /// + /// The inventory folder copy given, null if the copy was unsuccessful + /// + public virtual InventoryFolderImpl GiveInventoryFolder( + UUID recipientId, UUID senderId, UUID folderId, UUID recipientParentFolderId) + { + // Retrieve the folder from the sender + CachedUserInfo senderUserInfo = CommsManager.UserProfileCacheService.GetUserDetails(senderId); + + if (null == senderUserInfo) + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Failed to find sending user {0} for folder {1}", senderId, folderId); + + return null; + } + + if (!senderUserInfo.HasReceivedInventory) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Could not give inventory folder - have not yet received inventory for {0}", + senderId); + + return null; + } + + InventoryFolderImpl folder = senderUserInfo.RootFolder.FindFolder(folderId); + + if (null == folder) + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Could not find inventory folder {0} to give", folderId); + + return null; + } + + CachedUserInfo recipientUserInfo + = CommsManager.UserProfileCacheService.GetUserDetails(recipientId); + + if (null == recipientUserInfo) + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Failed to find receiving user {0} for folder {1}", recipientId, folderId); + + return null; + } + + if (recipientParentFolderId == UUID.Zero) + recipientParentFolderId = recipientUserInfo.RootFolder.ID; + + UUID newFolderId = UUID.Random(); + recipientUserInfo.CreateFolder(folder.Name, newFolderId, (ushort)folder.Type, recipientParentFolderId); + + // XXX: Messy - we should really get this back in the CreateFolder call + InventoryFolderImpl copiedFolder = recipientUserInfo.RootFolder.FindFolder(newFolderId); + + // Give all the subfolders + List subFolders = folder.RequestListOfFolderImpls(); + foreach (InventoryFolderImpl childFolder in subFolders) + { + GiveInventoryFolder(recipientId, senderId, childFolder.ID, copiedFolder.ID); + } + + // Give all the items + List items = folder.RequestListOfItems(); + foreach (InventoryItemBase item in items) + { + GiveInventoryItem(recipientId, senderId, item.ID, copiedFolder.ID); + } + + return copiedFolder; + } public void CopyInventoryItem(IClientAPI remoteClient, uint callbackID, UUID oldAgentID, UUID oldItemID, UUID newFolderID, string newName) diff --git a/OpenSim/Region/Environment/Scenes/Tests/TestClient.cs b/OpenSim/Region/Environment/Scenes/Tests/TestClient.cs index 69e45bb032..cecb115d1e 100644 --- a/OpenSim/Region/Environment/Scenes/Tests/TestClient.cs +++ b/OpenSim/Region/Environment/Scenes/Tests/TestClient.cs @@ -538,6 +538,9 @@ namespace OpenSim.Region.Environment.Scenes.Tests public virtual void SendBulkUpdateInventory(InventoryItemBase item) { } + + public void SendBulkUpdateInventory(InventoryFolderBase folderBase) + {} public UUID GetDefaultAnimation(string name) { @@ -561,8 +564,8 @@ namespace OpenSim.Region.Environment.Scenes.Tests int PriceParcelClaim, float PriceParcelClaimFactor, int PriceParcelRent, int PricePublicObjectDecay, int PricePublicObjectDelete, int PriceRentLight, int PriceUpload, int TeleportMinPrice, float TeleportPriceExponent) { - } + public virtual void SendNameReply(UUID profileId, string firstname, string lastname) { } diff --git a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs index 89b3bf61fe..f1a61bbf93 100644 --- a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs +++ b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs @@ -537,6 +537,9 @@ namespace OpenSim.Region.Examples.SimpleModule public virtual void SendBulkUpdateInventory(InventoryItemBase item) { } + + public void SendBulkUpdateInventory(InventoryFolderBase folderBase) + {} public UUID GetDefaultAnimation(string name) {