/* * 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. */ using System; using System.Collections; using libsecondlife; using libsecondlife.AssetSystem; using libsecondlife.Packets; namespace libsecondlife.InventorySystem { /// /// Summary description for Inventory. /// public class InventoryManager { private const bool DEBUG_PACKETS = true; // Reference to the SLClient Library private SecondLife slClient; // Reference to the Asset Manager private static AssetManager slAssetManager; internal AssetManager AssetManager { get{ return slAssetManager; } } public InventoryPacketHelper InvPacketHelper = null; // UUID of Root Inventory Folder private LLUUID uuidRootFolder; // Setup a hashtable to easily lookup folders by UUID private Hashtable htFoldersByUUID = new Hashtable(); // Setup a Hashtable to track download progress private Hashtable htFolderDownloadStatus; private ArrayList alFolderRequestQueue; // Used to track current item being created private InventoryItem iiCreationInProgress; private bool ItemCreationInProgress; private uint LastPacketRecieved; // Each InventorySystem needs to be initialized with a client and root folder. public InventoryManager( SecondLife client, LLUUID rootFolder ) { slClient = client; if( slAssetManager == null ) { slAssetManager = new AssetManager( slClient ); } InvPacketHelper = new InventoryPacketHelper(slClient.Network.AgentID, slClient.Network.SessionID); uuidRootFolder = rootFolder; resetFoldersByUUID(); // Setup the callback for Inventory Downloads PacketCallback InventoryDescendentsCallback = new PacketCallback(InventoryDescendentsHandler); slClient.Network.RegisterCallback(PacketType.InventoryDescendents, InventoryDescendentsCallback); // Setup the callback for Inventory Creation Update PacketCallback UpdateCreateInventoryItemCallback = new PacketCallback(UpdateCreateInventoryItemHandler); slClient.Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, UpdateCreateInventoryItemCallback); } // Used primarily for debugging and testing public AssetManager getAssetManager() { Console.WriteLine("It is not recommended that you access the asset manager directly"); return AssetManager; } private void resetFoldersByUUID() { // Init folder structure with root htFoldersByUUID = new Hashtable(); InventoryFolder ifRootFolder = new InventoryFolder(this, "My Inventory", uuidRootFolder, null); htFoldersByUUID[uuidRootFolder] = ifRootFolder; } public InventoryFolder getRootFolder() { return (InventoryFolder)htFoldersByUUID[uuidRootFolder]; } public InventoryFolder getFolder( LLUUID folderID ) { return (InventoryFolder)htFoldersByUUID[folderID]; } 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 0) || (alFolderRequestQueue.Count > 0) ) { if( htFolderDownloadStatus.Count == 0 ) { DescendentRequest dr = (DescendentRequest)alFolderRequestQueue[0]; alFolderRequestQueue.RemoveAt(0); RequestFolder( dr ); } if ((Helpers.GetUnixTime() - LastPacketRecieved) > 10) { Console.WriteLine("Time-out while waiting for packets (" + (Helpers.GetUnixTime() - LastPacketRecieved) + " seconds since last packet)"); Console.WriteLine("Current Status:"); // have to make a seperate list otherwise we run into modifying the original array // while still enumerating it. ArrayList alRestartList = new ArrayList(); if (htFolderDownloadStatus[0] != null) { Console.WriteLine(htFolderDownloadStatus[0].GetType()); } foreach( DescendentRequest dr in htFolderDownloadStatus.Values ) { Console.WriteLine( dr.FolderID + " " + dr.Expected + " / " + dr.Received + " / " + dr.LastReceived ); alRestartList.Add( dr ); } LastPacketRecieved = Helpers.GetUnixTime(); foreach( DescendentRequest dr in alRestartList ) { RequestFolder( dr ); } } slClient.Tick(); } } public void UpdateCreateInventoryItemHandler(Packet packet, Simulator simulator) { if (DEBUG_PACKETS) { Console.WriteLine(packet); } if (ItemCreationInProgress) { UpdateCreateInventoryItemPacket reply = (UpdateCreateInventoryItemPacket)packet; // User 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.FieldToString(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.FieldToString(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; ItemCreationInProgress = false; } else { Console.WriteLine(packet); throw new Exception("Received a packet for item creation, but no such response was expected. This is probably a bad thing..."); } } public void InventoryDescendentsHandler(Packet packet, Simulator simulator) { InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet; LastPacketRecieved = Helpers.GetUnixTime(); InventoryItem invItem; InventoryFolder invFolder; LLUUID uuidFolderID = new LLUUID(); int iDescendentsExpected = int.MaxValue; int iDescendentsReceivedThisBlock = 0; 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++; invItem = new InventoryItem(this, itemBlock); InventoryFolder ifolder = (InventoryFolder)htFoldersByUUID[invItem.FolderID]; if (ifolder.alContents.Contains(invItem) == false) { if ((invItem.InvType == 7) && (invItem.Type == Asset.ASSET_TYPE_NOTECARD)) { InventoryItem temp = new InventoryNotecard(this, invItem); invItem = temp; } if ((invItem.InvType == 0) && (invItem.Type == Asset.ASSET_TYPE_IMAGE)) { InventoryItem temp = new InventoryImage(this, invItem); invItem = temp; } ifolder.alContents.Add(invItem); } } } foreach (InventoryDescendentsPacket.FolderDataBlock folderBlock in reply.FolderData) { String name = System.Text.Encoding.UTF8.GetString(folderBlock.Name).Trim().Replace("\0", ""); LLUUID folderid = folderBlock.FolderID; LLUUID parentid = folderBlock.ParentID; sbyte type = 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) { invFolder = new InventoryFolder(this, name, folderid, parentid); iDescendentsReceivedThisBlock++; // Add folder to Parent InventoryFolder ifolder = (InventoryFolder)htFoldersByUUID[invFolder.ParentID]; if( ifolder.alContents.Contains(invFolder) == false ) { ifolder.alContents.Add(invFolder); } // Add folder to UUID Lookup htFoldersByUUID[invFolder.FolderID] = invFolder; // It's not the root, should be safe to "recurse" if( !invFolder.FolderID.Equals( uuidRootFolder ) ) { bool alreadyQueued = false; foreach( DescendentRequest dr in alFolderRequestQueue ) { if( dr.FolderID == invFolder.FolderID ) { alreadyQueued = true; break; } } if( !alreadyQueued ) { alFolderRequestQueue.Add( new DescendentRequest( invFolder.FolderID ) ); } } } } // Check how many descendents we're actually supposed to receive iDescendentsExpected = reply.AgentData.Descendents; uuidFolderID = reply.AgentData.FolderID; // Update download status for this folder if( iDescendentsReceivedThisBlock >= iDescendentsExpected ) { // We received all the descendents we're expecting for this folder // in this packet, so go ahead and remove folder from status list. htFolderDownloadStatus.Remove(uuidFolderID); } else { // This one packet didn't have all the descendents we're expecting // so update the total we're expecting, and update the total downloaded DescendentRequest dr = (DescendentRequest)htFolderDownloadStatus[uuidFolderID]; dr.Expected = iDescendentsExpected; dr.Received += iDescendentsReceivedThisBlock; dr.LastReceived = Helpers.GetUnixTime(); if( dr.Received >= dr.Expected ) { // Looks like after updating, we have all the descendents, // remove from folder status. htFolderDownloadStatus.Remove(uuidFolderID); } else { htFolderDownloadStatus[uuidFolderID] = dr; // Console.WriteLine( uuidFolderID + " is expecting " + (iDescendentsExpected - iStatus[1]) + " more packets." ); } } } private class DescendentRequest { public LLUUID FolderID; public int Expected = int.MaxValue; public int Received = 0; public uint LastReceived = 0; public bool FetchFolders = true; public bool FetchItems = true; public DescendentRequest(LLUUID folderID) { FolderID = folderID; LastReceived = Helpers.GetUnixTime(); } public DescendentRequest(LLUUID folderID, bool fetchFolders, bool fetchItems) { FolderID = folderID; FetchFolders = fetchFolders; FetchItems = fetchItems; LastReceived = Helpers.GetUnixTime(); } } } }