/*
* 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
{
// Reference to the SLClient Library
private SecondLife slClient;
// Reference to the Asset Manager
private static AssetManager slAssetManager;
internal AssetManager AssetManager
{
get{ return slAssetManager; }
}
// 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;
private int iLastPacketRecieved;
private class DescendentRequest
{
public LLUUID FolderID;
public int Expected = int.MaxValue;
public int Received = 0;
public int LastReceived = 0;
public bool FetchFolders = true;
public bool FetchItems = true;
public DescendentRequest( LLUUID folderID )
{
FolderID = folderID;
LastReceived = InventoryManager.getUnixtime();
}
public DescendentRequest( LLUUID folderID, bool fetchFolders, bool fetchItems )
{
FolderID = folderID;
FetchFolders = fetchFolders;
FetchItems = fetchItems;
LastReceived = InventoryManager.getUnixtime();
}
}
// Each InventorySystem needs to be initialized with a client (for network access to SL)
// and root folder. The root folder can be the root folder of an object OR an agent.
public InventoryManager( SecondLife client, LLUUID rootFolder )
{
slClient = client;
if( slAssetManager == null )
{
slAssetManager = new AssetManager( slClient );
}
uuidRootFolder = rootFolder;
resetFoldersByUUID();
// Setup the callback
PacketCallback InventoryDescendentsCallback = new PacketCallback(InventoryDescendentsHandler);
slClient.Network.RegisterCallback("InventoryDescendents", InventoryDescendentsCallback);
}
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);
char[] seperators = new char[1];
seperators[0] = '/';
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( (getUnixtime() - iLastPacketRecieved) > 10 )
{
Console.WriteLine("Time-out while waiting for packets (" + (getUnixtime() - iLastPacketRecieved) + " 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();
Console.WriteLine( htFolderDownloadStatus[0].GetType() );
foreach( DescendentRequest dr in htFolderDownloadStatus )
{
Console.WriteLine( dr.FolderID + " " + dr.Expected + " / " + dr.Received + " / " + dr.LastReceived );
alRestartList.Add( dr );
}
iLastPacketRecieved = getUnixtime();
foreach( DescendentRequest dr in alRestartList )
{
RequestFolder( dr );
}
}
slClient.Tick();
}
}
/*
Low 00333 - InventoryDescendents - Untrusted - Unencoded
1044 ItemData (Variable)
0047 GroupOwned (BOOL / 1)
0149 CRC (U32 / 1)
0159 CreationDate (S32 / 1)
0345 SaleType (U8 / 1)
0395 BaseMask (U32 / 1)
0506 Name (Variable / 1)
0562 InvType (S8 / 1)
0630 Type (S8 / 1)
0680 AssetID (LLUUID / 1)
0699 GroupID (LLUUID / 1)
0716 SalePrice (S32 / 1)
0719 OwnerID (LLUUID / 1)
0736 CreatorID (LLUUID / 1)
0968 ItemID (LLUUID / 1)
1025 FolderID (LLUUID / 1)
1084 EveryoneMask (U32 / 1)
1101 Description (Variable / 1)
1189 Flags (U32 / 1)
1348 NextOwnerMask (U32 / 1)
1452 GroupMask (U32 / 1)
1505 OwnerMask (U32 / 1)
1297 AgentData (01)
0219 AgentID (LLUUID / 1)
0366 Descendents (S32 / 1)
0418 Version (S32 / 1)
0719 OwnerID (LLUUID / 1)
1025 FolderID (LLUUID / 1)
1298 FolderData (Variable)
0506 Name (Variable / 1)
0558 ParentID (LLUUID / 1)
0630 Type (S8 / 1)
1025 FolderID (LLUUID / 1)
*/
public void InventoryDescendentsHandler(Packet packet, Simulator simulator)
{
// Console.WriteLine("Status|Queue :: " + htFolderDownloadStatus.Count + "/" + qFolderRequestQueue.Count);
iLastPacketRecieved = getUnixtime();
ArrayList blocks = packet.Blocks();
InventoryItem invItem;
InventoryFolder invFolder;
LLUUID uuidFolderID = new LLUUID();
int iDescendentsExpected = int.MaxValue;
int iDescendentsReceivedThisBlock = 0;
foreach (Block block in blocks)
{
if( block.Layout.Name.Equals("ItemData") )
{
invItem = new InventoryItem(this);
foreach (Field field in block.Fields )
{
switch( field.Layout.Name )
{
case "Name":
invItem._Name = System.Text.Encoding.UTF8.GetString( (byte[])field.Data).Trim();
invItem._Name = invItem.Name.Substring(0,invItem.Name.Length-1);
break;
case "Description":
invItem._Description = System.Text.Encoding.UTF8.GetString( (byte[])field.Data).Trim();
invItem._Description = invItem.Description.Substring(0,invItem.Description.Length-1);
break;
case "InvType":
invItem._InvType = sbyte.Parse(field.Data.ToString());
break;
case "Type":
invItem._Type = sbyte.Parse(field.Data.ToString());
break;
case "SaleType":
invItem._SaleType = byte.Parse(field.Data.ToString());
break;
case "GroupOwned":
invItem._GroupOwned = bool.Parse(field.Data.ToString());
break;
case "FolderID":
invItem._FolderID = new LLUUID(field.Data.ToString());
break;
case "ItemID":
invItem._ItemID = new LLUUID(field.Data.ToString());
break;
case "AssetID":
invItem._AssetID = new LLUUID(field.Data.ToString());
break;
case "GroupID":
invItem._GroupID = new LLUUID(field.Data.ToString());
break;
case "OwnerID":
invItem._OwnerID = new LLUUID(field.Data.ToString());
break;
case "CreatorID":
invItem._CreatorID = new LLUUID(field.Data.ToString());
break;
case "CRC":
invItem._CRC = uint.Parse(field.Data.ToString());
break;
case "Flags":
invItem._Flags = uint.Parse(field.Data.ToString());
break;
case "BaseMask":
invItem._BaseMask = uint.Parse(field.Data.ToString());
break;
case "EveryoneMask":
invItem._EveryoneMask = uint.Parse(field.Data.ToString());
break;
case "NextOwnerMask":
invItem._NextOwnerMask = uint.Parse(field.Data.ToString());
break;
case "GroupMask":
invItem._GroupMask = uint.Parse(field.Data.ToString());
break;
case "OwnerMask":
invItem._OwnerMask = uint.Parse(field.Data.ToString());
break;
case "CreationDate":
invItem._CreationDate = int.Parse(field.Data.ToString());
break;
case "SalePrice":
invItem._SalePrice = int.Parse(field.Data.ToString());
break;
default:
break;
}
}
// There is always an item block, even if there isn't any items
// the "filler" block will not have a name
if( (invItem.Name != null) && !invItem.Name.Equals("") )
{
iDescendentsReceivedThisBlock++;
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);
}
}
}
// Count number of folder descendents received
if( block.Layout.Name.Equals("FolderData") )
{
String name = "";
LLUUID folderid = new LLUUID();
LLUUID parentid = new LLUUID();
sbyte type = 0;
foreach (Field field in block.Fields )
{
switch( field.Layout.Name )
{
case "Name":
name = System.Text.Encoding.UTF8.GetString( (byte[])field.Data).Trim();
name = name.Substring(0,name.Length-1);
break;
case "FolderID":
folderid = new LLUUID(field.Data.ToString());
break;
case "ParentID":
parentid = new LLUUID(field.Data.ToString());
break;
case "Type":
type = sbyte.Parse(field.Data.ToString());
break;
default:
break;
}
}
invFolder = new InventoryFolder(this, name, folderid, parentid);
// There is always an folder block, even if there isn't any folders
// the "filler" block will not have a name
if( (invFolder.Name != null) && !invFolder.Name.Equals("") )
{
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
if( block.Layout.Name.Equals("AgentData") )
{
foreach (Field field in block.Fields )
{
if( field.Layout.Name.Equals("Descendents") )
{
iDescendentsExpected = int.Parse(field.Data.ToString());
}
if( field.Layout.Name.Equals("FolderID") )
{
uuidFolderID = field.Data.ToString();
// Console.WriteLine("Recieved a packet for : " + uuidFolderID);
}
}
}
}
// 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 = 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." );
}
}
}
public static int getUnixtime()
{
TimeSpan ts = (DateTime.UtcNow - new DateTime(1970,1,1,0,0,0));
return (int)ts.TotalSeconds;
}
}
}