/* * Copyright (c) 2006, Second Life Reverse Engineering Team * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the Second Life Reverse Engineering Team nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ //#define DEBUG_PACKETS using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.Threading; using libsecondlife; using libsecondlife.AssetSystem; using libsecondlife.Packets; namespace libsecondlife.InventorySystem { /// /// Summary description for Inventory. /// public class InventoryManager { // Reference to the SLClient Library private SecondLife slClient; // private ManualResetEvent InventoryManagerInitialized = new ManualResetEvent(false); // Reference to the Asset Manager internal libsecondlife.AssetManager AssetManager { get { return slClient.Assets; } } // Packet assembly helper public InventoryPacketHelper InvPacketHelper = null; // Setup a dictionary to easily lookup folders by UUID private Dictionary FoldersByUUID = new Dictionary(); // Setup a dictionary to track download progress // protected Dictionary FolderDownloadStatus = new Dictionary(); protected List FolderRequests = new List(); protected bool CurrentlyDownloadingAFolder = false; protected DownloadRequest_Folder CurrentlyDownloadingRequest = null; private Mutex CurrentlyDownloadingMutex = new Mutex(); protected Dictionary FolderByType = new Dictionary(); // Used to track current item being created private InventoryItem iiCreationInProgress; public ManualResetEvent ItemCreationCompleted; // Used to track to see if a download has timed out or not // private int LastPacketRecievedAtTick; public enum InventoryType : sbyte { Unknown = -1, Texture = 0, Sound = 1, CallingCard = 2, Landmark = 3, [Obsolete] Script = 4, [Obsolete] Clothing = 5, Object = 6, Notecard = 7, Category = 8, Folder = 8, RootCategory = 0, LSL = 10, [Obsolete] LSLBytecode = 11, [Obsolete] TextureTGA = 12, [Obsolete] Bodypart = 13, [Obsolete] Trash = 14, Snapshot = 15, [Obsolete] LostAndFound = 16, Attachment = 17, Wearable = 18, Animation = 19, Gesture = 20 } /// /// Used to turn on debug logging of descendant downloading. /// public bool LogDescendantQueue = false; /// /// Download event singalling that folder contents have been downloaded. /// /// The Inventory Folder that was updated /// public delegate void On_RequestDownloadContents_Finished(object iFolder, EventArgs e); public event On_RequestDownloadContents_Finished OnRequestDownloadFinishedEvent; public delegate void On_InventoryItemReceived(LLUUID fromAgentID, string fromAgentName, uint parentEstateID, LLUUID regionID, LLVector3 position, DateTime timestamp, InventoryItem item); public event On_InventoryItemReceived OnInventoryItemReceived; public delegate void On_InventoryFolderReceived(LLUUID fromAgentID, string fromAgentName, uint parentEstateID, LLUUID regionID, LLVector3 position, DateTime timestamp, InventoryFolder folder); public event On_InventoryFolderReceived OnInventoryFolderReceived; /// /// Primary constructor /// /// public InventoryManager(SecondLife client) { slClient = client; InvPacketHelper = new InventoryPacketHelper(slClient); // Need to know what when we're connected/disconnected slClient.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnected); slClient.Network.OnDisconnected += new NetworkManager.DisconnectedCallback(Network_OnDisconnected); // Setup the callback for Inventory Downloads slClient.Network.RegisterCallback(PacketType.InventoryDescendents, new NetworkManager.PacketCallback(InventoryDescendentsHandler)); // Setup the callback for Inventory Creation Update slClient.Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, new NetworkManager.PacketCallback(UpdateCreateInventoryItemHandler)); // Lets listen for inventory being given to us slClient.Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage); } #region State Management /// /// Inventory Management state should be cleared on connect/disconnect. /// /// /// void Network_OnDisconnected(NetworkManager.DisconnectType reason, string message) { // Clear out current state ClearState(); } /// /// Inventory Management state should be cleared on connect/disconnect. /// /// void Network_OnConnected(object sender) { // Clear out current state ClearState(); } /// /// Reset the current state of the InventorySystem /// private void ClearState() { FoldersByUUID.Clear(); lock (FolderRequests) { FolderRequests.Clear(); } if (slClient.Self.InventoryRootFolderUUID != null) { // Init folder structure with root InventoryFolder ifRootFolder = new InventoryFolder(this, "My Inventory", slClient.Self.InventoryRootFolderUUID, null); FoldersByUUID[slClient.Self.InventoryRootFolderUUID] = ifRootFolder; } } #endregion #region Folder Navigation /// /// Get the root folder of a client's inventory /// /// public InventoryFolder GetRootFolder() { return FoldersByUUID[slClient.Self.InventoryRootFolderUUID]; } /// /// Get a specific folder by FolderID from the local cached inventory information /// /// /// Returns null if the folder doesn't exist in cached inventory public InventoryFolder getFolder(LLUUID folderID) { if (FoldersByUUID.ContainsKey(folderID)) { return FoldersByUUID[folderID]; } else { return null; } } /// /// Get a specific folder by Name from the local cached inventory information /// /// /// public InventoryFolder getFolder(String sFolderPath) { string sSecretConst = "+@#%$#$%^%^%$^$%SV$#%FR$G"; sFolderPath = sFolderPath.Replace("//", sSecretConst); if (sFolderPath.StartsWith("/")) { sFolderPath = sFolderPath.Remove(0, 1); } if (sFolderPath.Length == 0) { return GetRootFolder(); } char[] seperators = { '/' }; string[] sFolderPathParts = sFolderPath.Split(seperators); for (int i = 0; i < sFolderPathParts.Length; i++) { sFolderPathParts[i] = sFolderPathParts[i].Replace(sSecretConst, "/"); } Queue pathParts = new Queue(sFolderPathParts); return getFolder(pathParts); } /// /// Recursive helper function for public InventoryFolder getFolder(String sFolderPath) /// /// Queue /// private InventoryFolder getFolder(Queue qFolderPath) { return getFolder(qFolderPath, GetRootFolder()); } /// /// Recursive helper function for public InventoryFolder getFolder(String sFolderPath) /// /// /// /// private InventoryFolder getFolder(Queue qFolderPath, InventoryFolder ifRoot) { string sCurFolder = qFolderPath.Dequeue(); foreach (InventoryBase ibFolder in ifRoot._Contents) { if (ibFolder is libsecondlife.InventorySystem.InventoryFolder) { if (((InventoryFolder)ibFolder).Name.Equals(sCurFolder)) { if (qFolderPath.Count == 0) { return (InventoryFolder)ibFolder; } else { return getFolder(qFolderPath, (InventoryFolder)ibFolder); } } } } // Try updating the current level's child folders, then look again if (ifRoot.RequestDownloadContents(false, true, false).RequestComplete.WaitOne(1000, false)) { foreach (InventoryBase ibFolder in ifRoot._Contents) { if (ibFolder is libsecondlife.InventorySystem.InventoryFolder) { if (((InventoryFolder)ibFolder).Name.Equals(sCurFolder)) { // NOTE: We only found it because we did a folder download, // perhaps we should initiate a recursive download at this point if (qFolderPath.Count == 0) { return (InventoryFolder)ibFolder; } else { return getFolder(qFolderPath, (InventoryFolder)ibFolder); } } } } } return null; } #endregion #region Inventory Creation Functions /// /// Request that a folder be created /// /// /// internal LLUUID FolderCreate(String name, LLUUID parentid) { LLUUID requestedFolderID = LLUUID.Random(); InventoryFolder ifolder = new InventoryFolder(this, name, requestedFolderID, parentid); ifolder._Type = -1; if (FoldersByUUID.ContainsKey(ifolder.ParentID)) { if (((InventoryFolder)FoldersByUUID[ifolder.ParentID])._Contents.Contains(ifolder) == false) { // Add new folder to the contents of the parent folder. ((InventoryFolder)FoldersByUUID[ifolder.ParentID])._Contents.Add(ifolder); } } else { throw new Exception("Parent Folder " + ifolder.ParentID + " does not exist in this Inventory Manager."); } if (FoldersByUUID.ContainsKey(ifolder.FolderID) == false) { FoldersByUUID[ifolder.FolderID] = ifolder; } Packet packet = InvPacketHelper.CreateInventoryFolder(ifolder.Name, ifolder.ParentID, ifolder.Type, ifolder.FolderID); slClient.Network.SendPacket(packet); return requestedFolderID; } /// /// Create a new notecard /// /// /// /// /// /// internal InventoryNotecard NewNotecard(string Name, string Description, string Body, LLUUID FolderID) { InventoryNotecard iNotecard = new InventoryNotecard(this, Name, Description, FolderID, slClient.Network.AgentID); // Create this notecard on the server. ItemCreate(iNotecard); if ((Body != null) && (Body.Equals("") != true)) { iNotecard.Body = Body; } return iNotecard; } internal InventoryLandmark NewLandmark(string Name, string Description, LLUUID FolderID) { InventoryLandmark iLandmark = new InventoryLandmark(this, Name, Description, FolderID, slClient.Network.AgentID); // Create this notecard on the server. ItemCreate(iLandmark); return iLandmark; } /// /// Create a new image /// /// /// /// /// /// internal InventoryImage NewImage(string Name, string Description, byte[] j2cdata, LLUUID FolderID) { InventoryImage iImage = new InventoryImage(this, Name, Description, FolderID, slClient.Network.AgentID); // Create this image on the server. ItemCreate(iImage); if ((j2cdata != null) && (j2cdata.Length != 0)) { iImage.J2CData = j2cdata; } return iImage; } #endregion #region Folder Management /// /// Flushes the local cache of this folder's contents /// /// /// Clear Folders /// Clear Items public void FolderClearContents(InventoryFolder iFolder, bool Folders, bool Items) { // Need to recursively do this... while( iFolder._Contents.Count > 0 ) { InventoryBase ib = iFolder._Contents[0]; if ((ib is InventoryFolder) && Folders) { InventoryFolder ChildFolder = (InventoryFolder)ib; FolderClearContents(ChildFolder, Folders, Items); if (FoldersByUUID.ContainsKey(ChildFolder.FolderID)) { FoldersByUUID.Remove(ChildFolder.FolderID); } iFolder._Contents.Remove(ib); } else if (Items) { iFolder._Contents.Remove(ib); } } } /// /// Delete/Remove a folder /// /// internal void FolderRemove(InventoryFolder ifolder) { // Need to recursively remove children foreach (InventoryBase ib in ifolder.GetContents()) { if (ib is InventoryFolder) { InventoryFolder ifChild = (InventoryFolder)ib; FolderRemove(ifChild); } } // Remove from parent if (FoldersByUUID.ContainsKey(ifolder.ParentID)) { InventoryFolder ifParent = FoldersByUUID[ifolder.ParentID]; if (ifParent._Contents.Contains(ifolder)) { ifParent._Contents.Remove(ifolder); } } // Remove from lookup cache if (FoldersByUUID.ContainsKey(ifolder.FolderID)) { FoldersByUUID.Remove(ifolder.FolderID); } Packet packet = InvPacketHelper.RemoveInventoryFolder(ifolder.FolderID); slClient.Network.SendPacket(packet); } /// /// Delete/Remove a folder /// /// internal void FolderRemove(LLUUID folderID) { if (FoldersByUUID.ContainsKey(folderID)) { FolderRemove(FoldersByUUID[folderID]); } } /// /// Move a folder /// /// /// internal void FolderMove(InventoryFolder iFolder, LLUUID newParentID) { //Remove this folder from the old parent if (FoldersByUUID.ContainsKey(iFolder.ParentID)) { InventoryFolder ParentFolder = FoldersByUUID[iFolder.ParentID]; if (ParentFolder._Contents.Contains(iFolder)) { ParentFolder._Contents.Remove(iFolder); } } // Set Parent ID iFolder._ParentID = newParentID; // Add to Parent's contents if (FoldersByUUID.ContainsKey(iFolder.ParentID)) { InventoryFolder ParentFolder = FoldersByUUID[iFolder.ParentID]; if (!ParentFolder._Contents.Contains(iFolder)) { ParentFolder._Contents.Add(iFolder); } } Packet packet = InvPacketHelper.MoveInventoryFolder(newParentID, iFolder.FolderID); slClient.Network.SendPacket(packet); } /// /// Rename a folder /// /// internal void FolderRename(InventoryFolder ifolder) { Packet packet = InvPacketHelper.UpdateInventoryFolder(ifolder.Name, ifolder.ParentID, ifolder.Type, ifolder.FolderID); slClient.Network.SendPacket(packet); } #endregion #region Item Management /// /// Create a new inventory item /// /// internal void ItemCreate(InventoryItem iitem) { if( iiCreationInProgress != null ) { throw new Exception("Can only create one item at a time, and an item creation is already in progress."); } try { ItemCreationCompleted = new ManualResetEvent(false); iiCreationInProgress = iitem; Packet packet = InvPacketHelper.CreateInventoryItem(iitem); int i = 0; do { if (i++ > 10) throw new Exception("Could not create " + iitem.Name); slClient.Network.SendPacket(packet); #if DEBUG_PACKETS slClient.DebugLog(packet); #endif } while (!ItemCreationCompleted.WaitOne(5000, false)); } finally { iiCreationInProgress = null; } } /// /// Update an existing item /// /// internal void ItemUpdate(InventoryItem iitem) { Packet packet = InvPacketHelper.UpdateInventoryItem(iitem); slClient.Network.SendPacket(packet); #if DEBUG_PACKETS slClient.DebugLog(packet); #endif } /// /// Copy an item /// /// /// internal void ItemCopy(LLUUID ItemID, LLUUID TargetFolderID) { Packet packet = InvPacketHelper.CopyInventoryItem(ItemID, TargetFolderID); slClient.Network.SendPacket(packet); #if DEBUG_PACKETS slClient.DebugLog(packet); #endif } internal void MoveItem(LLUUID itemID, LLUUID targetFolderID) { Packet packet = InvPacketHelper.MoveInventoryItem(itemID, targetFolderID); slClient.Network.SendPacket(packet); #if DEBUG_PACKETS slClient.Log(packet.ToString(), Helpers.LogLevel.Info); #endif } /// /// Give an item to someone /// /// /// internal void ItemGiveTo(InventoryItem iitem, LLUUID ToAgentID) { LLUUID MessageID = LLUUID.Random(); Packet packet = InvPacketHelper.GiveItemViaImprovedInstantMessage( MessageID , ToAgentID , slClient.Self.FirstName + " " + slClient.Self.LastName , slClient.Self.Position , iitem ); slClient.Network.SendPacket(packet); #if DEBUG_PACKETS slClient.DebugLog(packet); #endif } /// /// Remove/Delete an item /// /// internal void ItemRemove(InventoryItem iitem) { InventoryFolder ifolder = getFolder(iitem.FolderID); ifolder._Contents.Remove(iitem); Packet packet = InvPacketHelper.RemoveInventoryItem(iitem.ItemID); slClient.Network.SendPacket(packet); #if DEBUG_PACKETS slClient.DebugLog(packet); #endif } #endregion #region Misc /// /// Rez the given item into the given sim. /// /// /// You can specify null to use the current sim /// Position is in Region coordinates internal void ItemRezObject(InventoryItem item, Simulator TargetSim, LLVector3 TargetPos) { Packet packet = InvPacketHelper.RezObject(item, TargetPos); if (TargetSim == null) { slClient.Network.SendPacket(packet); } else { slClient.Network.SendPacket(packet, TargetSim); } } /// /// Attempt to rez and attach an inventory item /// /// /// internal void ItemRezAttach(InventoryItem Item, ObjectManager.AttachmentPoint AttachmentPt) { Packet p = InvPacketHelper.RezSingleAttachmentFromInv(Item, AttachmentPt); slClient.Network.SendPacket(p); } /// /// Attempt to detach and return an item to your inventory /// /// internal void ItemDetach(InventoryItem Item) { Packet p = InvPacketHelper.DetachAttachmentIntoInv(Item.ItemID); slClient.Network.SendPacket(p); } #endregion #region Folder Downloading protected void LogDescendantQueueEvent(string msg) { if (LogDescendantQueue) { StringBuilder sb = new StringBuilder(); sb.AppendLine("=============================="); sb.AppendLine(msg); if (CurrentlyDownloadingRequest == null) { sb.AppendLine("CurrentlyDownloadingRequest: NULL"); } else { sb.AppendLine("CurrentlyDownloadingRequest: " + CurrentlyDownloadingRequest.ToString()); } sb.AppendLine("Current queue status:"); lock (FolderRequests) { if (FolderRequests.Count == 0) { sb.AppendLine(" *** Download Queue Empty ***"); } else { foreach (DownloadRequest_Folder dr in FolderRequests) { sb.AppendLine(" * " + dr.ToString()); } } } slClient.Log(sb.ToString(), Helpers.LogLevel.Info); } } /// /// Append a request to the end of the queue. /// internal DownloadRequest_Folder FolderRequestAppend(LLUUID folderID, bool recurse, bool fetchFolders, bool fetchItems, string requestName) { DownloadRequest_Folder dr = new DownloadRequest_Folder(folderID, recurse, fetchFolders, fetchItems, requestName); // Add new request to the tail of the queue lock (FolderRequests) { if (FolderRequests.Contains(dr)) { foreach (DownloadRequest_Folder existing in FolderRequests) { if (dr.Equals(existing)) { dr = existing; break; } } LogDescendantQueueEvent("Append(returned existing): " + dr.ToString()); } else { FolderRequests.Add(dr); LogDescendantQueueEvent("Append: " + dr.ToString()); } } FolderRequestBegin(); return dr; } protected DownloadRequest_Folder FolderRequestPrepend(LLUUID folderID, bool recurse, bool fetchFolders, bool fetchItems, string requestName) { DownloadRequest_Folder dr = new DownloadRequest_Folder(folderID, recurse, fetchFolders, fetchItems, requestName); // Prepend the request at the head of the queue lock (FolderRequests) { if (FolderRequests.Contains(dr)) { foreach (DownloadRequest_Folder existing in FolderRequests) { if (dr.Equals(existing)) { dr = existing; break; } } LogDescendantQueueEvent("Append(returned existing): " + dr.ToString()); } else { FolderRequests.Insert(0, dr); LogDescendantQueueEvent("Prepend: " + dr.ToString()); } } return dr; } /// /// If not currently downloading a request, dequeue the next request and start it. /// protected void FolderRequestBegin() { // Wait until it's safe to be modifying what is currently downloading. CurrentlyDownloadingMutex.WaitOne(); // If we not already downloading stuff, then lets start if (CurrentlyDownloadingAFolder == false) { // Start downloading the first thing at the head of the queue lock (FolderRequests) { while ((FolderRequests.Count > 0) && (FolderRequests[0].IsCompleted)) { LogDescendantQueueEvent("Head request completed, notify recurse completed: " + FolderRequests[0]); FolderRequests.RemoveAt(0); } if (FolderRequests.Count > 0) { CurrentlyDownloadingRequest = FolderRequests[0]; LogDescendantQueueEvent("Starting download of head of queue: " + FolderRequests[0].ToString()); } else { // Nothing to do // Release so that we can let other things look at and modify what is currently downloading. CurrentlyDownloadingMutex.ReleaseMutex(); return; } } // Mark that we're currently downloading CurrentlyDownloadingAFolder = true; // Download! Packet packet = InvPacketHelper.FetchInventoryDescendents( CurrentlyDownloadingRequest.FolderID , CurrentlyDownloadingRequest.FetchFolders , CurrentlyDownloadingRequest.FetchItems); slClient.Network.SendPacket(packet); } // Release so that we can let other things look at and modify what is currently downloading. CurrentlyDownloadingMutex.ReleaseMutex(); } /// /// Issue a RequestDownload Finished event. Happens after each download request completes. /// /// /// protected void FireRequestDownloadFinishedEvent(object o, EventArgs e) { if (OnRequestDownloadFinishedEvent != null) { OnRequestDownloadFinishedEvent(o, e); } } #endregion #region libsecondlife callback handlers /// /// Used to track when inventory is dropped onto/into agent /// /// /// /// /// /// /// /// /// /// /// /// /// /// void Self_OnInstantMessage(LLUUID fromAgentID, string fromAgentName, LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, MainAvatar.InstantMessageDialog dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message, MainAvatar.InstantMessageOnline offline, byte[] binaryBucket) { if ((dialog == MainAvatar.InstantMessageDialog.InventoryOffered) && ((OnInventoryItemReceived != null) || (OnInventoryFolderReceived !=null))) { sbyte IncomingItemType = (sbyte)binaryBucket[0]; LLUUID IncomingUUID = new LLUUID(binaryBucket, 1); // Update root folders InventoryFolder root = GetRootFolder(); if (root.GetContents().Count == 0) { root.RequestDownloadContents(false, true, false).RequestComplete.WaitOne(3000, false); } // Handle the case of the incoming inventory folder if (IncomingItemType == (sbyte)InventoryManager.InventoryType.Folder) { if (OnInventoryFolderReceived == null) { // Short-circuit early exit, we're not interested... return; } InventoryFolder iFolder = null; int numAttempts = 6; int timeBetweenAttempts = 500; while( numAttempts-- > 0 ) { foreach( InventoryBase ib in root.GetContents() ) { if (ib is InventoryFolder) { InventoryFolder tiFolder = (InventoryFolder)ib; if (tiFolder.FolderID == IncomingUUID) { iFolder = tiFolder; break; } } } if ( iFolder != null) { try { OnInventoryFolderReceived(fromAgentID, fromAgentName, parentEstateID, regionID, position, timestamp, iFolder); } catch (Exception e) { slClient.Log(e.ToString(), Helpers.LogLevel.Error); } return; } else { Thread.Sleep(timeBetweenAttempts); timeBetweenAttempts *= 2; root.RequestDownloadContents(false, true, false).RequestComplete.WaitOne(3000, false); } } slClient.Log("Incoming folder [" + IncomingUUID.ToStringHyphenated() + "] not found in inventory.", Helpers.LogLevel.Error); return; } if (OnInventoryItemReceived == null) { // Short-circuit, early exit, we're not interested return; } // Make sure we have a folder lookup by type table ready. lock (FolderByType) { if (FolderByType.Count == 0) { foreach (InventoryBase ib in root.GetContents()) { if (ib is InventoryFolder) { InventoryFolder iFolder = (InventoryFolder)ib; FolderByType[iFolder.Type] = iFolder; } } } } // Get a reference to the incoming/receiving folder if (!FolderByType.ContainsKey(IncomingItemType)) { slClient.Log("Incoming item specifies type (" + IncomingItemType + ") with no matching inventory folder found.", Helpers.LogLevel.Error); } InventoryFolder incomingFolder = FolderByType[IncomingItemType]; InventoryItem incomingItem = null; // lock just incase another item comes into the same directory while processing this one. lock (incomingFolder) { // Refresh contents of receiving folder incomingFolder.RequestDownloadContents(false, false, true).RequestComplete.WaitOne(3000, false); int numAttempts = 2; while( numAttempts-- > 0 ) { // Search folder for incoming item foreach (InventoryBase ib2 in incomingFolder.GetContents()) { if (ib2 is InventoryItem) { InventoryItem tiItem = (InventoryItem)ib2; if (tiItem.ItemID == IncomingUUID) { incomingItem = tiItem; break; } } } // If found, send out notification if (incomingItem != null) { try { OnInventoryItemReceived(fromAgentID, fromAgentName, parentEstateID, regionID, position, timestamp, incomingItem); } catch (Exception e) { slClient.Log(e.ToString(), Helpers.LogLevel.Error); } return; } else { Thread.Sleep(500); incomingFolder.RequestDownloadContents(false, false, true).RequestComplete.WaitOne(3000, false); } } } slClient.Log("Incoming item/folder [" + IncomingUUID.ToStringHyphenated() + "] not found in inventory.", Helpers.LogLevel.Error); } } /// /// This is called in response to an item creation request /// /// /// public void UpdateCreateInventoryItemHandler(Packet packet, Simulator simulator) { #if DEBUG_PACKETS slClient.DebugLog(packet); #endif if (iiCreationInProgress != null) { UpdateCreateInventoryItemPacket reply = (UpdateCreateInventoryItemPacket)packet; // Use internal variable references, so we don't fire off any update code by using the public accessors iiCreationInProgress._ItemID = reply.InventoryData[0].ItemID; iiCreationInProgress._GroupOwned = reply.InventoryData[0].GroupOwned; iiCreationInProgress._SaleType = reply.InventoryData[0].SaleType; iiCreationInProgress._CreationDate = reply.InventoryData[0].CreationDate; iiCreationInProgress._BaseMask = reply.InventoryData[0].BaseMask; iiCreationInProgress._Name = Helpers.FieldToUTF8String(reply.InventoryData[0].Name); iiCreationInProgress._InvType = reply.InventoryData[0].InvType; iiCreationInProgress._Type = reply.InventoryData[0].Type; iiCreationInProgress._AssetID = reply.InventoryData[0].AssetID; iiCreationInProgress._GroupID = reply.InventoryData[0].GroupID; iiCreationInProgress._SalePrice = reply.InventoryData[0].SalePrice; iiCreationInProgress._OwnerID = reply.InventoryData[0].OwnerID; iiCreationInProgress._CreatorID = reply.InventoryData[0].CreatorID; iiCreationInProgress._ItemID = reply.InventoryData[0].ItemID; iiCreationInProgress._FolderID = reply.InventoryData[0].FolderID; iiCreationInProgress._EveryoneMask = reply.InventoryData[0].EveryoneMask; iiCreationInProgress._Description = Helpers.FieldToUTF8String(reply.InventoryData[0].Description); iiCreationInProgress._NextOwnerMask = reply.InventoryData[0].NextOwnerMask; iiCreationInProgress._GroupMask = reply.InventoryData[0].GroupMask; iiCreationInProgress._OwnerMask = reply.InventoryData[0].OwnerMask; // NOT USED YET: iiCreationInProgress._CallbackID = reply.InventoryData[0].CallbackID; ItemCreationCompleted.Set(); } else { slClient.DebugLog(packet.ToString()); // TODO: Looks like this packet may be sent in response to a Buy. // Should probably use it to update local cached inventory, to show the bought item(s) // // throw new Exception("Received a packet for item creation, but no such response was expected. This is probably a bad thing..."); } } /// /// Returned in response to a FetchInventoryDescendents request. Contains information about the /// contents of a folder. /// /// /// public void InventoryDescendentsHandler(Packet packet, Simulator simulator) { InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet; // The UUID of this folder. LLUUID uuidFolderID = reply.AgentData.FolderID; // Wait until it's safe to be looking at what is currently downloading. CurrentlyDownloadingMutex.WaitOne(); // Make sure this request matches the one we believe is the currently downloading request if (((CurrentlyDownloadingRequest != null) && (CurrentlyDownloadingRequest.FolderID != uuidFolderID)) || (CurrentlyDownloadingRequest == null)) { // Release so that we can let other things look at and modify what is currently downloading. CurrentlyDownloadingMutex.ReleaseMutex(); // Log problem LogDescendantQueueEvent("Unexpected descendent packet for folder: " + uuidFolderID.ToStringHyphenated()); // Just discard this packet... return; } // Get the Inventory folder that we'll be updating InventoryFolder InvFolderUpdating = (InventoryFolder)FoldersByUUID[uuidFolderID]; // Update Inventory Manager's last tick point, used for timeouts and such // LastPacketRecievedAtTick = Environment.TickCount; // Used to count the number of descendants received to see if we're finished or not. int iDescendentsExpected = reply.AgentData.Descendents; int iDescendentsReceivedThisBlock = 0; #region Handle Child Items foreach (InventoryDescendentsPacket.ItemDataBlock itemBlock in reply.ItemData) { // There is always an item block, even if there isn't any items // the "filler" block will not have a name if (itemBlock.Name.Length != 0) { iDescendentsReceivedThisBlock++; if (itemBlock.ItemID == LLUUID.Zero) { // this shouldn't ever happen, unless you've uploaded an invalid item // to yourself while developping inventory code :-( } else { InventoryItem TempInvItem = new InventoryItem(this, itemBlock); if (InvFolderUpdating._Contents.Contains(TempInvItem) == false) { #region Create an instance of the appriopriate Inventory class if ((TempInvItem.InvType == 7) && (TempInvItem.Type == (sbyte)Asset.AssetType.Notecard)) { InventoryItem temp = new InventoryNotecard(this, TempInvItem); TempInvItem = temp; } if ((TempInvItem.InvType == 3) && (TempInvItem.Type == (sbyte)Asset.AssetType.Landmark)) { InventoryItem temp = new InventoryLandmark(this, TempInvItem); TempInvItem = temp; } if ((TempInvItem.InvType == 0) && (TempInvItem.Type == (sbyte)Asset.AssetType.Texture)) { InventoryItem temp = new InventoryImage(this, TempInvItem); TempInvItem = temp; } if ((TempInvItem.InvType == 10) && (TempInvItem.Type == (sbyte)Asset.AssetType.LSLText)) { InventoryItem temp = new InventoryScript(this, TempInvItem); TempInvItem = temp; } if ((TempInvItem.InvType == 18) && ( (TempInvItem.Type == (sbyte)Asset.AssetType.Bodypart) || (TempInvItem.Type == (sbyte)Asset.AssetType.Clothing) ) ) { InventoryItem temp = new InventoryWearable(this, TempInvItem); TempInvItem = temp; } #endregion InvFolderUpdating._Contents.Add(TempInvItem); } } } } #endregion #region Handle Child Folders foreach (InventoryDescendentsPacket.FolderDataBlock folderBlock in reply.FolderData) { String IncomingName = System.Text.Encoding.UTF8.GetString(folderBlock.Name).Trim().Replace("\0", ""); LLUUID IncomingFolderID = folderBlock.FolderID; LLUUID IncomingParentID = folderBlock.ParentID; sbyte IncomingType = folderBlock.Type; // There is always an folder block, even if there isn't any folders // the "filler" block will not have a name if (folderBlock.Name.Length != 0) { iDescendentsReceivedThisBlock++; // See if the Incoming Folder already exists locally if (FoldersByUUID.ContainsKey(IncomingFolderID)) { InventoryFolder existingFolder = FoldersByUUID[IncomingFolderID]; existingFolder._Name = IncomingName; existingFolder._Type = IncomingType; // Check if parent of existing is the same as the incoming if (!existingFolder.ParentID.Equals(IncomingParentID)) { // Remove existing from old parent if (FoldersByUUID.ContainsKey(existingFolder.ParentID)) { InventoryFolder ExistingParent = FoldersByUUID[existingFolder.ParentID]; if (ExistingParent._Contents.Contains(existingFolder)) { ExistingParent._Contents.Remove(existingFolder); } } // Set existings parent to new existingFolder._ParentID = IncomingParentID; // Connect existing folder to parent specified in new if (FoldersByUUID.ContainsKey(IncomingParentID)) { InventoryFolder ExistingParent = FoldersByUUID[IncomingParentID]; if (!ExistingParent._Contents.Contains(existingFolder)) { ExistingParent._Contents.Add(existingFolder); } } } } else { InventoryFolder TempInvFolder = new InventoryFolder(this, IncomingName, IncomingFolderID, IncomingParentID, IncomingType); // Add folder to Parent if (InvFolderUpdating._Contents.Contains(TempInvFolder) == false) { InvFolderUpdating._Contents.Add(TempInvFolder); } // Add folder to local cache lookup FoldersByUUID[TempInvFolder.FolderID] = TempInvFolder; } // Do we recurse? if (CurrentlyDownloadingRequest.Recurse) { // It's not the root, should be safe to "recurse" if (!IncomingFolderID.Equals(slClient.Self.InventoryRootFolderUUID)) { FolderRequestPrepend(IncomingFolderID, CurrentlyDownloadingRequest.Recurse, CurrentlyDownloadingRequest.FetchFolders, CurrentlyDownloadingRequest.FetchItems, CurrentlyDownloadingRequest.Name + "/" + IncomingName); } } } } #endregion // Update total number of descendants expected , and update the total downloaded CurrentlyDownloadingRequest.Expected = iDescendentsExpected; CurrentlyDownloadingRequest.Received += iDescendentsReceivedThisBlock; CurrentlyDownloadingRequest.LastReceivedAtTick = Environment.TickCount; if ((iDescendentsExpected > 1) && (iDescendentsReceivedThisBlock == 0)) { slClient.Log("Received an InventoryDescendant packet where it indicated that there should be at least 1 descendant, but none were present... [" + CurrentlyDownloadingRequest.Name + "]", Helpers.LogLevel.Warning); CurrentlyDownloadingRequest.Expected = 0; } if (LogDescendantQueue) { slClient.Log("Received packet for: " + CurrentlyDownloadingRequest.ToString(), Helpers.LogLevel.Info); } // Check if we're finished if (CurrentlyDownloadingRequest.Received >= CurrentlyDownloadingRequest.Expected) { LogDescendantQueueEvent("Done downloading request: " + CurrentlyDownloadingRequest); // Singal anyone that was waiting for this request to finish CurrentlyDownloadingRequest.RequestComplete.Set(); // Raise an event for anyone that cares to listen for downloaded folder events if (OnRequestDownloadFinishedEvent != null) { DownloadRequest_EventArgs e = new DownloadRequest_EventArgs(); e.DownloadRequest = CurrentlyDownloadingRequest; FireRequestDownloadFinishedEvent(InvFolderUpdating, e); } // Set Inventory Manager state to reflect that we're done with the current download CurrentlyDownloadingAFolder = false; CurrentlyDownloadingRequest = null; } // Release so that we can let other things look at and modify what is currently downloading. CurrentlyDownloadingMutex.ReleaseMutex(); // If there's any more download requests queued, grab one, and go FolderRequestBegin(); } #endregion } }