/*
* Copyright (c) 2007-2008, openmetaverse.org
* 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 openmetaverse.org 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.Generic;
using System.Collections;
using System.Text;
using System.Threading;
namespace OpenMetaverse
{
///
/// Inventory is responsible for managing inventory items and folders.
/// It updates the InventoryFolders and InventoryItems with new FolderData
/// and ItemData received from the InventoryManager. Updates to inventory
/// folders/items that are not explicitly managed (via the Manage method)
/// are ignored.
///
/// When the FolderData and/or ItemData indicates an inventory change that is not
/// reflected locally, then the Inventory will update the local inventory state
/// accordingly. Under normal circumstances (when inventory is modified
/// by the local client via the InventoryBase, InventoryFolder and InventoryItem
/// interfaces) it doesn't have to do this.
///
public class Inventory : IEnumerable
{
///
/// Delegate for .
///
/// Inventory that was updated.
public delegate void InventoryUpdate(InventoryBase inventory);
///
/// Triggered when a managed inventory item or folder is updated.
///
public event InventoryUpdate OnInventoryUpdate;
///
/// Delegate for
///
/// The Inventory that is managing
/// The inventory item or folder that was managed.
public delegate void InventoryManaged(Inventory inventory, InventoryBase ibase);
///
/// Triggered when an inventory item is first managed.
///
public event InventoryManaged OnInventoryManaged;
///
/// Retrieves a managed InventoryBase from the Inventory.
/// Returns null if the UUID isn't managed by this Inventory.
///
/// The UUID of the InventoryBase to retrieve.
/// A managed InventoryBase.
public InventoryBase this[UUID uuid]
{
get
{
InventoryBase item;
if (Items.TryGetValue(uuid, out item))
return item;
return null;
}
}
protected Dictionary Items;
private InventoryManager _Manager;
private UUID _Owner;
///
/// The owner of this inventory. Inventorys can only manage items
/// owned by the same agent.
///
public UUID Owner
{
get { return _Owner; }
private set { _Owner = value; }
}
private UUID _RootUUID;
///
/// The UUID of the root folder.
///
public UUID RootUUID
{
get { return _RootUUID; }
private set { _RootUUID = value; }
}
///
/// Reference to the InventoryFolder representing the
/// inventory root folder, if it has been managed.
///
public InventoryFolder RootFolder
{
get { return this[RootUUID] as InventoryFolder; }
}
///
/// Initializes an empty, rootless, ownerless inventory.
/// This is used so that we can have an Inventory instance before
/// the owner and root data is known.
///
/// Manager for remote updates.
public Inventory(InventoryManager manager)
: this(manager, UUID.Zero, UUID.Zero) { }
///
/// Creates a new Inventory. Remote updates are sent via the manager
/// passed to this constructor. All folders contained within the InventorySkeleton
/// are automatically managed. The inventory takes on the owner of the skeleton.
///
/// Manager for remote updates.
/// Skeleton of folders, inventory owner.
public Inventory(InventoryManager manager, InventorySkeleton skeleton)
: this(manager, skeleton.Owner, skeleton.RootUUID)
{
ManageSkeleton(skeleton);
}
///
/// Creates a new inventory. Remote updates are sent via the manager
/// passed to this constructor. This creates an empty inventory, with no managed items.
///
/// Manager for remote updates.
/// Owner of this inventory.
///
public Inventory(InventoryManager manager, UUID owner, UUID root)
{
_Manager = manager;
Owner = owner;
_RootUUID = root;
if (Items == null)
Items = new Dictionary();
RegisterInventoryCallbacks();
}
protected internal void InitializeFromSkeleton(InventorySkeleton skeleton)
{
Owner = skeleton.Owner;
RootUUID = skeleton.RootUUID;
Items = new Dictionary(skeleton.Folders.Length);
ManageSkeleton(skeleton);
}
///
/// Manages all the folders in the skeleton, if the skeleton is owned
/// by the same agent.
///
/// The skeleton with folders to manage.
/// true if Inventory's owner is skeleton's owner and management succeeded, false otherwise.
protected bool ManageSkeleton(InventorySkeleton skeleton)
{
if (skeleton.Owner != Owner)
return false;
foreach (FolderData folder in skeleton.Folders)
{
Manage(folder);
}
return true;
}
///
/// Registers InventoryManager callbacks for inventory updates.
///
protected virtual void RegisterInventoryCallbacks()
{
_Manager.OnFolderUpdate += new InventoryManager.FolderUpdate(manager_OnFolderUpdate);
_Manager.OnItemUpdate += new InventoryManager.ItemUpdate(manager_OnItemUpdate);
_Manager.OnAssetUpdate += new InventoryManager.AssetUpdate(manager_OnAssetUpdate);
_Manager.OnItemCreated += new InventoryManager.ItemCreatedCallback(_Manager_OnItemCreated);
}
void _Manager_OnItemCreated(bool success, ItemData itemData)
{
if (Items.ContainsKey(itemData.ParentUUID))
Manage(itemData);
}
///
/// Updates the AssetUUID of a managed InventoryItem's ItemData.
/// If the InventoryItem is not managed, the update is ignored.
///
/// UUID of the item to update.
/// The item's new asset UUID.
protected void manager_OnAssetUpdate(UUID itemID, UUID newAssetID)
{
InventoryBase b;
if (Items.TryGetValue(itemID, out b))
{
if (b is InventoryItem)
{
(b as InventoryItem).Data.AssetUUID = newAssetID;
}
}
}
///
/// Updates the ItemData of a managed InventoryItem. This may
/// change local inventory state if the local inventory is not
/// consistant with the new data. (Parent change, rename, etc)
/// If the item is not managed, the update is ignored.
///
/// The updated ItemData.
protected void manager_OnItemUpdate(ItemData itemData)
{
InventoryBase item;
if (Items.TryGetValue(itemData.UUID, out item))
{
if (item is InventoryItem)
{
Update(item as InventoryItem, itemData);
}
}
else
{
// Check if it's a child of a managed folder.
if (Items.ContainsKey(itemData.ParentUUID))
Manage(itemData);
}
}
///
/// Updates the FolderData of a managed InventoryFolder. This
/// may change local inventory state if the local inventory is not
/// consistant with the new data (Parent change, rename, etc)
/// If the folder is not managed, the update is ignored.
///
/// The updated FolderData.
protected void manager_OnFolderUpdate(FolderData folderData)
{
InventoryBase folder;
if (Items.TryGetValue(folderData.UUID, out folder))
{
if (folder is InventoryFolder)
{
Update(folder as InventoryFolder, folderData);
}
}
else
{
// Check if it's a child of a managed folder.
if (Items.ContainsKey(folderData.ParentUUID))
Manage(folderData);
}
}
///
/// Wraps the ItemData in a new InventoryItem.
/// You may override this method to use your own subclass of
/// InventoryItem.
///
/// The ItemData to wrap.
/// A new InventoryItem wrapper for the ItemData.
protected virtual InventoryItem WrapItemData(ItemData data)
{
return new InventoryItem(_Manager, this, data);
}
///
/// Wraps the FolderData in a new InventoryFolder.
/// You may override this method to use your own subclass of
/// InventoryFolder.
///
/// The FolderData to wrap.
/// A new InventoryFolder wrapper for the FolderData.
protected virtual InventoryFolder WrapFolderData(FolderData data)
{
return new InventoryFolder(_Manager, this, data);
}
///
/// Attempts to fetch and manage an inventory item from its item UUID.
/// This method will block until the item's ItemData is fetched from
/// the remote inventory. If the item is already managed by the inventory
/// returns the local managed InventoryItem wrapper.
///
/// The ItemID of the inventory item to fetch.
/// Managed InventoryItem, null if fetch fails.
public InventoryItem Manage(UUID itemID)
{
InventoryBase ib;
if (Items.TryGetValue(itemID, out ib))
{
// This method shouldn't really be used for retrieving items.
return ib as InventoryItem;
}
else
{
ItemData item;
if (_Manager.FetchItem(itemID, Owner, TimeSpan.FromSeconds(30), out item))
{
return Manage(item);
}
else
{
return null;
}
}
}
///
/// Explicitly manage the inventory item given its ItemData.
/// If the item isn't managed, a new wrapper for it is created
/// and it is added to the local inventory.
/// If the item is already managed, this method returns its wrapper,
/// updating it with the ItemData passed to this method.
///
/// The ItemData of the item to manage.
/// The managed InventoryItem wrapper.
public virtual InventoryItem Manage(ItemData item)
{
if (item.OwnerID == Owner)
{
InventoryBase b;
if (Items.TryGetValue(item.UUID, out b))
{
//Logger.DebugLog(String.Format("{0}: {1} already managed, updating.", (RootFolder == null) ? RootUUID.ToString() : RootFolder.Name, item.Name));
Update(b as InventoryItem, item);
return b as InventoryItem;
}
else
{
InventoryItem wrapper = WrapItemData(item);
//Logger.DebugLog(String.Format("{0}: {1} managed, {2} total.", (RootFolder == null) ? RootUUID.ToString() : RootFolder.Name, item.Name, Items.Count));
lock (Items)
Items.Add(item.UUID, wrapper);
if (OnInventoryManaged != null)
{
try { OnInventoryManaged(this, wrapper); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, e); }
}
return wrapper;
}
}
else
{
//Logger.DebugLog(String.Format("{0}: {1} is not owned by this inventory.", (RootFolder == null) ? RootUUID.ToString() : RootFolder.Name, item.Name));
return null;
}
}
///
/// Explicitly manage the inventory folder given its FolderData.
/// If the folder isn't managed, a new wrapper for it is created,
/// it is added to the local inventory, and any known children of
/// the folder are added to the folder's Contents.
/// If the folder is already managed, this method returns the folder's
/// wrapper, updating it with the FolderData passed to this method.
///
/// The FolderData of the folder to manage.
/// The managed InventoryFolder wrapper.
public virtual InventoryFolder Manage(FolderData folder)
{
if (folder.OwnerID == Owner)
{
InventoryBase b;
if (Items.TryGetValue(folder.UUID, out b))
{
//Logger.DebugLog(String.Format("{0}: {1} already managed, updating.", (RootFolder == null) ? RootUUID.ToString() : RootFolder.Name, folder.Name));
Update(b as InventoryFolder, folder);
return b as InventoryFolder;
}
else
{
InventoryFolder wrapper = WrapFolderData(folder);
lock (Items)
Items.Add(folder.UUID, wrapper);
//Logger.DebugLog(String.Format("{0}: {1} managed, {2} total.", (RootFolder == null) ? RootUUID.ToString() : RootFolder.Name, folder.Name, Items.Count));
// Folder is now managed, update its contents with known children.
foreach (InventoryBase item in Items.Values)
{
if (item.ParentUUID == folder.UUID)
{
wrapper.AddChild(item);
}
}
if (OnInventoryManaged != null)
{
try { OnInventoryManaged(this, wrapper); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, e); }
}
return wrapper;
}
}
else
{
//Logger.DebugLog(String.Format("{0}: {1} is not owned by this inventory.", (RootFolder == null) ? RootUUID.ToString() : RootFolder.Name, folder.Name));
return null;
}
}
///
/// Unmanages an inventory item or folder. The item or folder will no
/// longer be automatically updated.
///
///
public virtual void Unmanage(InventoryBase item)
{
lock (Items)
Items.Remove(item.UUID);
//Logger.DebugLog("Unmanaging " + item.Name);
}
protected void Update(InventoryItem item, ItemData update)
{
if (item.Data != update)
{
// Check for parent change:
if (item.Data.ParentUUID != update.ParentUUID)
{
item.LocalMove(update.ParentUUID, this[update.ParentUUID] as InventoryFolder);
}
item.Data = update;
FireInventoryUpdate(item);
}
}
protected void Update(InventoryFolder folder, FolderData update)
{
if (folder.Data != update)
{
// Check for parent change:
if (folder.Data.ParentUUID != update.ParentUUID)
{
folder.LocalMove(update.ParentUUID, this[update.ParentUUID] as InventoryFolder);
}
folder.Data = update;
FireInventoryUpdate(folder);
}
}
protected void FireInventoryUpdate(InventoryBase updatedInventory)
{
if (OnInventoryUpdate != null)
{
try { OnInventoryUpdate(updatedInventory); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, e); }
}
}
#region Pathing
///
/// Fetches an inventory item or folder by path.
/// If the path starts with /, is ignored
/// and the path is located from the root.
///
/// If is true, this method may take a while to return.
/// A "/"-seperated path, UNIX style. Accepts UUIDs or folder names.
/// The directory to begin in if the path does not start at the root.
/// Whether to fetch folder contents when they're needed.
/// Multiple items if the path is ambiguous.
public List InventoryFromPath(string path, InventoryFolder currentDirectory, bool fetchStale)
{
path = path.Trim();
if (path.StartsWith("/"))
{
currentDirectory = RootFolder;
path = path.Remove(0, 1);
}
return InventoryFromPath(path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries), currentDirectory, fetchStale);
}
///
/// Fetches an inventory item or folder by path, without fetching anything
/// from the remote inventory.
///
/// If is true, this method may take a while to return.
/// Array whose elements are names or UUIDs of folders, representing the path to the desired item or folder.
/// Multiple items if the path is ambiguous.
public List InventoryFromPath(string[] path)
{
return InventoryFromPath(path, false);
}
///
/// Fetches an inventory item or folder by path, starting at the inventory's root folder.
///
/// If is true, this method may take a while to return.
/// Array whose elements are names or UUIDs of folders, representing the path to the desired item or folder.
/// If a folder is stale, fetch its contents.
/// Multiple items if the path is ambiguous.
public List InventoryFromPath(string[] path, bool fetchStale)
{
return InventoryFromPath(path, RootFolder, fetchStale);
}
///
/// Fetches an inventory item or folder by path, starting at .
///
/// If is true, this method may take a while to return.
/// Array whose elements are names or UUIDs of folders, representing the path to the desired item or folder.
/// Folder to start the path from.
/// Whether to fetch folder contents when they're needed.
/// Multiple items if the path is ambiguous.
public List InventoryFromPath(string[] path, InventoryFolder baseFolder, bool fetchStale)
{
if (path == null || path.Length == 0)
{
List one = new List(1);
one.Add(baseFolder);
return one;
}
// Agenda stores an object[] which contains an InventoryFolder and an int
// the int represents the level in the path that the children of the InventoryFolder
// should satasfy.
List results = new List();
Stack