diff --git a/OpenMetaverse/AppearanceManager.cs b/OpenMetaverse/AppearanceManager.cs index 741e0728..e6e30584 100644 --- a/OpenMetaverse/AppearanceManager.cs +++ b/OpenMetaverse/AppearanceManager.cs @@ -88,8 +88,13 @@ namespace OpenMetaverse public class WearableData { - public InventoryWearable Item; + public ItemData Item; public AssetWearable Asset; + public WearableType WearableType + { + get { return (WearableType)Item.Flags; } + set { Item.Flags = (uint)value; } + } } /// @@ -278,7 +283,7 @@ namespace OpenMetaverse /// Replace the current outfit with a list of wearables and set appearance /// /// List of wearables that define the new outfit - public void WearOutfit(List ibs) + public void WearOutfit(List ibs) { WearOutfit(ibs, true); } @@ -288,7 +293,7 @@ namespace OpenMetaverse /// /// List of wearables that define the new outfit /// Whether to bake textures for the avatar or not - public void WearOutfit(List ibs, bool bake) + public void WearOutfit(List ibs, bool bake) { _wearParams = new WearParams(ibs, bake); Thread appearanceThread = new Thread(new ThreadStart(StartWearOutfit)); @@ -298,15 +303,15 @@ namespace OpenMetaverse private WearParams _wearParams; private void StartWearOutfit() { - List ibs = (List)_wearParams.Param; - List wearables = new List(); - List attachments = new List(); + List ibs = (List)_wearParams.Param; + List wearables = new List(); + List attachments = new List(); - foreach (InventoryBase ib in ibs) + foreach (ItemData ib in ibs) { - if (ib is InventoryWearable) - wearables.Add((InventoryWearable)ib); - else if (ib is InventoryAttachment || ib is InventoryObject) + if (ib.InventoryType == InventoryType.Wearable) + wearables.Add(ib); + else if (ib.InventoryType == InventoryType.Attachment || ib.InventoryType == InventoryType.Object) attachments.Add(ib); } @@ -362,8 +367,8 @@ namespace OpenMetaverse private void StartWearOutfitFolder() { SendAgentWearablesRequest(); // request current wearables async - List wearables; - List attachments; + List wearables; + List attachments; if (!GetFolderWearables(_wearOutfitParams.Param, out wearables, out attachments)) // get wearables in outfit folder return; // TODO: this error condition should be passed back to the client somehow @@ -374,7 +379,7 @@ namespace OpenMetaverse AddAttachments(attachments, true); } - private bool GetFolderWearables(object _folder, out List wearables, out List attachments) + private bool GetFolderWearables(object _folder, out List wearables, out List attachments) { UUID folder; wearables = null; @@ -385,7 +390,7 @@ namespace OpenMetaverse string[] path = (string[])_folder; folder = Client.Inventory.FindObjectByPath( - Client.Inventory.Store.RootFolder.UUID, Client.Self.AgentID, String.Join("/", path), 1000 * 20); + Client.Inventory.InventorySkeleton.RootUUID, Client.Self.AgentID, String.Join("/", path), TimeSpan.FromMilliseconds(1000 * 20)); if (folder == UUID.Zero) { @@ -396,26 +401,29 @@ namespace OpenMetaverse else folder = (UUID)_folder; - wearables = new List(); - attachments = new List(); - List objects = Client.Inventory.FolderContents(folder, Client.Self.AgentID, - false, true, InventorySortOrder.ByName, 1000 * 20); + wearables = new List(); + attachments = new List(); - if (objects != null) + List folders; + List items; + Client.Inventory.FolderContents(folder, Client.Self.AgentID, + false, true, InventorySortOrder.ByName, TimeSpan.FromMilliseconds(1000 * 20), out items, out folders); + + if (items != null) { - foreach (InventoryBase ib in objects) + foreach (ItemData ib in items) { - if (ib is InventoryWearable) + if (ib.InventoryType == InventoryType.Wearable) { Logger.DebugLog("Adding wearable " + ib.Name, Client); - wearables.Add((InventoryWearable)ib); + wearables.Add(ib); } - else if (ib is InventoryAttachment) + else if (ib.InventoryType == InventoryType.Attachment) { Logger.DebugLog("Adding attachment (attachment) " + ib.Name, Client); attachments.Add(ib); } - else if (ib is InventoryObject) + else if (ib.InventoryType == InventoryType.Object) { Logger.DebugLog("Adding attachment (object) " + ib.Name, Client); attachments.Add(ib); @@ -437,7 +445,7 @@ namespace OpenMetaverse } // this method will download the assets for all inventory items in iws - private void ReplaceOutfitWearables(List iws) + private void ReplaceOutfitWearables(List iws) { lock (Wearables.Dictionary) { @@ -451,11 +459,11 @@ namespace OpenMetaverse Wearables.Dictionary = preserve; - foreach (InventoryWearable iw in iws) + foreach (ItemData iw in iws) { WearableData wd = new WearableData(); wd.Item = iw; - Wearables.Dictionary[wd.Item.WearableType] = wd; + Wearables.Dictionary[wd.WearableType] = wd; } } } @@ -466,7 +474,7 @@ namespace OpenMetaverse /// A List containing the attachments to add /// If true, tells simulator to remove existing attachment /// first - public void AddAttachments(List attachments, bool removeExistingFirst) + public void AddAttachments(List attachments, bool removeExistingFirst) { // FIXME: Obey this const int OBJECTS_PER_PACKET = 4; @@ -483,10 +491,9 @@ namespace OpenMetaverse attachmentsPacket.ObjectData = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock[attachments.Count]; for (int i = 0; i < attachments.Count; i++) { - if (attachments[i] is InventoryAttachment) + if (attachments[i].InventoryType == InventoryType.Attachment) { - InventoryAttachment attachment = (InventoryAttachment)attachments[i]; - + ItemData attachment = attachments[i]; attachmentsPacket.ObjectData[i] = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock(); attachmentsPacket.ObjectData[i].AttachmentPt = 0; attachmentsPacket.ObjectData[i].EveryoneMask = (uint)attachment.Permissions.EveryoneMask; @@ -498,10 +505,9 @@ namespace OpenMetaverse attachmentsPacket.ObjectData[i].NextOwnerMask = (uint)attachment.Permissions.NextOwnerMask; attachmentsPacket.ObjectData[i].OwnerID = attachment.OwnerID; } - else if (attachments[i] is InventoryObject) + else if (attachments[i].InventoryType == InventoryType.Object) { - InventoryObject attachment = (InventoryObject)attachments[i]; - + ItemData attachment = attachments[i]; attachmentsPacket.ObjectData[i] = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock(); attachmentsPacket.ObjectData[i].AttachmentPt = 0; attachmentsPacket.ObjectData[i].EveryoneMask = (uint)attachment.Permissions.EveryoneMask; @@ -529,7 +535,7 @@ namespace OpenMetaverse /// A to attach /// the on the avatar /// to attach the item to - public void Attach(InventoryItem item, AttachmentPoint attachPoint) + public void Attach(ItemData item, AttachmentPoint attachPoint) { Attach(item.UUID, item.OwnerID, item.Name, item.Description, item.Permissions, item.Flags, attachPoint); @@ -574,7 +580,7 @@ namespace OpenMetaverse /// Detach an item from avatar using an object /// /// An object - public void Detach(InventoryItem item) + public void Detach(ItemData item) { Detach(item.UUID); } @@ -796,10 +802,11 @@ namespace OpenMetaverse { WearableType type = (WearableType)update.WearableData[i].WearableType; WearableData data = new WearableData(); - data.Item = new InventoryWearable(update.WearableData[i].ItemID); - data.Item.WearableType = type; - data.Item.AssetType = WearableTypeToAssetType(type); - data.Item.AssetUUID = update.WearableData[i].AssetID; + ItemData itemData = new ItemData(update.WearableData[i].ItemID, InventoryType.Wearable); + itemData.AssetType = WearableTypeToAssetType(type); + itemData.AssetUUID = update.WearableData[i].AssetID; + data.Item = itemData; + data.WearableType = type; // Add this wearable to our collection lock (Wearables.Dictionary) Wearables.Dictionary[type] = data; @@ -1012,7 +1019,7 @@ namespace OpenMetaverse { foreach (KeyValuePair kvp in Wearables.Dictionary) { - Logger.DebugLog("Requesting asset for wearable item " + kvp.Value.Item.WearableType + " (" + kvp.Value.Item.AssetUUID + ")", Client); + Logger.DebugLog("Requesting asset for wearable item " + kvp.Value.WearableType + " (" + kvp.Value.Item.AssetUUID + ")", Client); AssetDownloads.Enqueue(new PendingAssetDownload(kvp.Value.Item.AssetUUID, kvp.Value.Item.AssetType)); } diff --git a/OpenMetaverse/AssetManager.cs b/OpenMetaverse/AssetManager.cs index cc215fce..8bdaa6ab 100644 --- a/OpenMetaverse/AssetManager.cs +++ b/OpenMetaverse/AssetManager.cs @@ -96,6 +96,44 @@ namespace OpenMetaverse Simstate = 22, } + public static class AssetTypeParser + { + private static readonly ReversableDictionary AssetTypeMap = new ReversableDictionary(); + static AssetTypeParser() + { + AssetTypeMap.Add("animatn", AssetType.Animation); + AssetTypeMap.Add("clothing", AssetType.Clothing); + AssetTypeMap.Add("callcard", AssetType.CallingCard); + AssetTypeMap.Add("object", AssetType.Object); + AssetTypeMap.Add("texture", AssetType.Texture); + AssetTypeMap.Add("sound", AssetType.Sound); + AssetTypeMap.Add("bodypart", AssetType.Bodypart); + AssetTypeMap.Add("gesture", AssetType.Gesture); + AssetTypeMap.Add("lsltext", AssetType.LSLText); + AssetTypeMap.Add("landmark", AssetType.Landmark); + AssetTypeMap.Add("notecard", AssetType.Notecard); + AssetTypeMap.Add("category", AssetType.Folder); + } + + public static AssetType Parse(string str) + { + AssetType t; + if (AssetTypeMap.TryGetValue(str, out t)) + return t; + else + return AssetType.Unknown; + } + + public static string StringValueOf(AssetType type) + { + string str; + if (AssetTypeMap.TryGetKey(type, out str)) + return str; + else + return "unknown"; + } + } + /// /// /// @@ -520,7 +558,7 @@ namespace OpenMetaverse return transfer.ID; } - public UUID RequestInventoryAsset(InventoryItem item, bool priority) + public UUID RequestInventoryAsset(ItemData item, bool priority) { return RequestInventoryAsset(item.AssetUUID, item.UUID, UUID.Zero, item.OwnerID, item.AssetType, priority); } diff --git a/OpenMetaverse/Inventory.cs b/OpenMetaverse/Inventory.cs index 4d18b2b6..882548de 100644 --- a/OpenMetaverse/Inventory.cs +++ b/OpenMetaverse/Inventory.cs @@ -26,359 +26,998 @@ using System; using System.Collections.Generic; - +using System.Collections; +using System.Text; +using System.Threading; namespace OpenMetaverse { /// - /// Exception class to identify inventory exceptions + /// 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 InventoryException : Exception - { - public InventoryException(string message) - : base(message) { } - } - - /// - /// Responsible for maintaining inventory structure. Inventory constructs nodes - /// and manages node children as is necessary to maintain a coherant hirarchy. - /// Other classes should not manipulate or create InventoryNodes explicitly. When - /// A node's parent changes (when a folder is moved, for example) simply pass - /// Inventory the updated InventoryFolder and it will make the appropriate changes - /// to its internal representation. - /// - public class Inventory + public class Inventory : IEnumerable { /// - /// Delegate to use for the OnInventoryObjectUpdated event. + /// Delegate for . /// - /// The state of the InventoryObject before the update occured. - /// The state of the InventoryObject after the update occured. - public delegate void InventoryObjectUpdated(InventoryBase oldObject, InventoryBase newObject); - /// - /// Delegate to use for the OnInventoryObjectRemoved event. - /// - /// The InventoryObject that was removed. - public delegate void InventoryObjectRemoved(InventoryBase obj); - /// - /// Delegate to use for the OnInventoryObjectUpdated event. - /// - /// The InventoryObject that has been stored. - public delegate void InventoryObjectAdded(InventoryBase obj); + /// Inventory that was updated. + public delegate void InventoryUpdate(InventoryBase inventory); /// - /// Called when an InventoryObject's state is changed. + /// Triggered when a managed inventory item or folder is updated. /// - public event InventoryObjectUpdated OnInventoryObjectUpdated; - /// - /// Called when an item or folder is removed from inventory. - /// - public event InventoryObjectRemoved OnInventoryObjectRemoved; - /// - /// Called when an item is first added to the local inventory store. - /// This will occur most frequently when we're initially downloading - /// the inventory from the server. - /// - /// This will also fire when another avatar or object offers us inventory - /// - public event InventoryObjectAdded OnInventoryObjectAdded; + public event InventoryUpdate OnInventoryUpdate; /// - /// The root folder of this avatars inventory + /// Retrieves a managed InventoryBase from the Inventory. + /// Returns null if the UUID isn't managed by this Inventory. /// - public InventoryFolder RootFolder + /// The UUID of the InventoryBase to retrieve. + /// A managed InventoryBase. + public InventoryBase this[UUID uuid] { - get { return RootNode.Data as InventoryFolder; } - set + get { - UpdateNodeFor(value); - _RootNode = Items[value.UUID]; + InventoryBase item; + if (Items.TryGetValue(uuid, out item)) + return item; + return null; } } - /// - /// The default shared library folder - /// - public InventoryFolder LibraryFolder - { - get { return LibraryRootNode.Data as InventoryFolder; } - set - { - UpdateNodeFor(value); - _LibraryRootNode = Items[value.UUID]; - } - } - - private InventoryNode _LibraryRootNode; - private InventoryNode _RootNode; - - /// - /// The root node of the avatars inventory - /// - public InventoryNode RootNode - { - get - { - if (_RootNode == null) - throw new InventoryException("Root node unknown. Are you completely logged in?"); - return _RootNode; - } - } - - /// - /// The root node of the default shared library - /// - public InventoryNode LibraryRootNode - { - get - { - if (_LibraryRootNode == null) - throw new InventoryException("Library Root node unknown. Are you completely logged in?"); - return _LibraryRootNode; - } - } - - public UUID Owner { - get { return _Owner; } - } - + protected Dictionary Items; + private InventoryManager _Manager; private UUID _Owner; - private GridClient Client; - private InventoryManager Manager; - private Dictionary Items = new Dictionary(); - - public Inventory(GridClient client, InventoryManager manager) - : this(client, manager, client.Self.AgentID) { } - - public Inventory(GridClient client, InventoryManager manager, UUID owner) - { - Client = client; - Manager = manager; - _Owner = owner; - if (owner == UUID.Zero) - Logger.Log("Inventory owned by nobody!", Helpers.LogLevel.Warning, Client); - Items = new Dictionary(); + /// + /// 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; } } - public List GetContents(InventoryFolder folder) + private UUID _RootUUID; + public UUID RootUUID { - return GetContents(folder.UUID); + get { return _RootUUID; } + private set { _RootUUID = value; } + } + + public InventoryFolder RootFolder + { + get { return this[RootUUID] as InventoryFolder; } } /// - /// Returns the contents of the specified folder + /// 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. /// - /// A folder's UUID - /// The contents of the folder corresponding to folder - /// When folder does not exist in the inventory - public List GetContents(UUID folder) + /// Manager for remote updates. + /// Skeleton of folders, inventory owner. + public Inventory(InventoryManager manager, InventorySkeleton skeleton) + : this (manager, skeleton.Owner, skeleton.RootUUID) { - InventoryNode folderNode; - if (!Items.TryGetValue(folder, out folderNode)) - throw new InventoryException("Unknown folder: " + folder); - lock (folderNode.Nodes.SyncRoot) + Items = new Dictionary(skeleton.Folders.Length); + foreach (FolderData folder in skeleton.Folders) { - List contents = new List(folderNode.Nodes.Count); - foreach (InventoryNode node in folderNode.Nodes.Values) - { - contents.Add(node.Data); - } - return contents; + Manage(folder); } } /// - /// Updates the state of the InventoryNode and inventory data structure that - /// is responsible for the InventoryObject. If the item was previously not added to inventory, - /// it adds the item, and updates structure accordingly. If it was, it updates the - /// InventoryNode, changing the parent node if item.parentUUID does - /// not match node.Parent.Data.UUID. - /// - /// You can not set the inventory root folder using this method + /// Creates a new inventory. Remote updates are sent via the manager + /// passed to this constructor. This creates an empty inventory, with no managed items. /// - /// The InventoryObject to store - public void UpdateNodeFor(InventoryBase item) + /// Manager for remote updates. + /// Owner of this inventory. + public Inventory(InventoryManager manager, UUID owner, UUID root) { - lock (Items) - { - InventoryNode itemParent = null; - if (item.ParentUUID != UUID.Zero && !Items.TryGetValue(item.ParentUUID, out itemParent)) - { - // OK, we have no data on the parent, let's create a fake one. - InventoryFolder fakeParent = new InventoryFolder(item.ParentUUID); - fakeParent.DescendentCount = 1; // Dear god, please forgive me. - itemParent = new InventoryNode(fakeParent); - Items[item.ParentUUID] = itemParent; - // Unfortunately, this breaks the nice unified tree - // while we're waiting for the parent's data to come in. - // As soon as we get the parent, the tree repairs itself. - Logger.DebugLog("Attempting to update inventory child of " + - item.ParentUUID.ToString() + " when we have no local reference to that folder", Client); - - if (Client.Settings.FETCH_MISSING_INVENTORY) - { - // Fetch the parent - List fetchreq = new List(1); - fetchreq.Add(item.ParentUUID); - //Manager.FetchInventory(fetchreq); // we cant fetch folder data! :-O - } - } - - InventoryNode itemNode; - if (Items.TryGetValue(item.UUID, out itemNode)) // We're updating. - { - InventoryNode oldParent = itemNode.Parent; - // Handle parent change - if (oldParent == null || itemParent == null || itemParent.Data.UUID != oldParent.Data.UUID) - { - if (oldParent != null) - { - lock (oldParent.Nodes.SyncRoot) - oldParent.Nodes.Remove(item.UUID); - } - if (itemParent != null) - { - lock (itemParent.Nodes.SyncRoot) - itemParent.Nodes[item.UUID] = itemNode; - } - } - - itemNode.Parent = itemParent; - - if (item != itemNode.Data) - FireOnInventoryObjectUpdated(itemNode.Data, item); - - itemNode.Data = item; - } - else // We're adding. - { - itemNode = new InventoryNode(item, itemParent); - Items.Add(item.UUID, itemNode); - FireOnInventoryObjectAdded(item); - } - } - } - - public InventoryNode GetNodeFor(UUID uuid) - { - return Items[uuid]; + _Manager = manager; + Owner = owner; + _RootUUID = root; + if (Items == null) + Items = new Dictionary(); + RegisterInventoryCallbacks(); } /// - /// Removes the InventoryObject and all related node data from Inventory. + /// Registers InventoryManager callbacks for inventory updates. /// - /// The InventoryObject to remove. - public void RemoveNodeFor(InventoryBase item) + protected virtual void RegisterInventoryCallbacks() { - lock (Items) - { - InventoryNode node; - if (Items.TryGetValue(item.UUID, out node)) - { - if (node.Parent != null) - lock (node.Parent.Nodes.SyncRoot) - node.Parent.Nodes.Remove(item.UUID); - Items.Remove(item.UUID); - FireOnInventoryObjectRemoved(item); - } + _Manager.OnFolderUpdate += new InventoryManager.FolderUpdate(manager_OnFolderUpdate); + _Manager.OnItemUpdate += new InventoryManager.ItemUpdate(manager_OnItemUpdate); + _Manager.OnAssetUpdate += new InventoryManager.AssetUpdate(manager_OnAssetUpdate); + } - // In case there's a new parent: - InventoryNode newParent; - if (Items.TryGetValue(item.ParentUUID, out newParent)) + /// + /// 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) { - lock (newParent.Nodes.SyncRoot) - newParent.Nodes.Remove(item.UUID); + (b as InventoryItem).Data.AssetUUID = newAssetID; } } } /// - /// Used to find out if Inventory contains the InventoryObject - /// specified by uuid. + /// 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 UUID to check. - /// true if inventory contains uuid, false otherwise - public bool Contains(UUID uuid) + /// The updated ItemData. + protected void manager_OnItemUpdate(ItemData itemData) { - return Items.ContainsKey(uuid); + 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); + } } - public bool Contains(InventoryBase obj) - { - return Contains(obj.UUID); - } - - #region Operators - /// - /// By using the bracket operator on this class, the program can get the - /// InventoryObject designated by the specified uuid. If the value for the corresponding - /// UUID is null, the call is equivelant to a call to RemoveNodeFor(this[uuid]). - /// If the value is non-null, it is equivelant to a call to UpdateNodeFor(value), - /// the uuid parameter is ignored. + /// 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 UUID of the InventoryObject to get or set, ignored if set to non-null value. - /// The InventoryObject corresponding to uuid. - public InventoryBase this[UUID uuid] + /// The updated FolderData. + protected void manager_OnFolderUpdate(FolderData folderData) { - get + InventoryBase folder; + if (Items.TryGetValue(folderData.UUID, out folder)) { - InventoryNode node = Items[uuid]; - return node.Data; - } - set - { - if (value != null) + if (folder is InventoryFolder) { - // Log a warning if there is a UUID mismatch, this will cause problems - if (value.UUID != uuid) - Logger.Log("Inventory[uuid]: uuid " + uuid.ToString() + " is not equal to value.UUID " + - value.UUID.ToString(), Helpers.LogLevel.Warning, Client); + Update(folder as InventoryFolder, folderData); + } + } + else + { + // Check if it's a child of a managed folder. + if (Items.ContainsKey(folderData.ParentUUID)) + Manage(folderData); + } + } - UpdateNodeFor(value); + /// + /// 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 { - InventoryNode node; - if (Items.TryGetValue(uuid, out node)) - { - RemoveNodeFor(node.Data); - } + return null; } } } - #endregion Operators - - #region Event Firing - - protected void FireOnInventoryObjectUpdated(InventoryBase oldObject, InventoryBase newObject) + /// + /// 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 InventoryItem Manage(ItemData item) { - if (OnInventoryObjectUpdated != null) + if (item.OwnerID == Owner) { - try { OnInventoryObjectUpdated(oldObject, newObject); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } + InventoryBase b; + if (Items.TryGetValue(item.UUID, out b)) + { + Update(b as InventoryItem, item); + return b as InventoryItem; + } + else + { + InventoryItem wrapper = WrapItemData(item); + Items.Add(item.UUID, wrapper); + return wrapper; + } + } + else + { + return null; } } - protected void FireOnInventoryObjectRemoved(InventoryBase obj) + + /// + /// 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 InventoryFolder Manage(FolderData folder) { - if (OnInventoryObjectRemoved != null) + if (folder.OwnerID == Owner) { - try { OnInventoryObjectRemoved(obj); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } + InventoryBase b; + if (Items.TryGetValue(folder.UUID, out b)) + { + Update(b as InventoryFolder, folder); + return b as InventoryFolder; + } + else + { + InventoryFolder wrapper = WrapFolderData(folder); + Items.Add(folder.UUID, wrapper); + lock (Items) + { + // Folder is now managed, update its contents with known children. + foreach (InventoryBase item in Items.Values) + { + if (item.ParentUUID == folder.UUID) + { + wrapper.AddChild(item); + } + } + } + return wrapper; + } + } + else + { + return null; } } - protected void FireOnInventoryObjectAdded(InventoryBase obj) + /// + /// Unmanages an inventory item or folder. The item or folder will no + /// longer be automatically updated. + /// + /// + public void Unmanage(InventoryBase item) { - if (OnInventoryObjectAdded != null) + Items.Remove(item.UUID); + } + + protected void Update(InventoryItem item, ItemData update) + { + if (item.Data != update) { - try { OnInventoryObjectAdded(obj); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } + // 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); } + } + } + + public List InventoryFromPath(string[] path) + { + return InventoryFromPath(path, RootFolder); + } + + + public List InventoryFromPath(string[] path, InventoryFolder baseFolder) + { + if (path == null || path.Length == 0) + { + List one = new List(1); + one.Add(baseFolder); + return one; + } + + Dictionary goal = new Dictionary(); + List results = new List(); + Stack agenda = new Stack(); + goal.Add(baseFolder, 0); + agenda.Push(baseFolder); + while (agenda.Count > 0) + { + InventoryFolder currentFolder = agenda.Pop() as InventoryFolder; + int currentLevel = goal[currentFolder]; + foreach (InventoryBase child in currentFolder) + { + if (child.Name == path[currentLevel]) + { + if (currentLevel == path.Length - 1) + { + results.Add(child); + } + else + { + if (child is InventoryFolder) + { + agenda.Push(child); + goal.Add(child, currentLevel + 1); + } + } + } + } + } + return results; + } + + + + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + foreach (KeyValuePair kvp in Items) + { + yield return kvp.Value; + } + } + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } + + public abstract class InventoryBase + { + /// + /// InventoryBase's item UUID as obtained from ItemData or FolderData. + /// + public abstract UUID UUID + { + get; + } + + /// + /// InventoryBase parent's item UUID, as obtained from ItemData or FolderData. + /// Setting this will not modify the remote inventory, it will only modify the local + /// ItemData or FolderData struct. + /// + public abstract UUID ParentUUID + { + get; + set; + } + + /// + /// Inventory base's name, as obtained from ItemData or FolderData. + /// Setting this will not modify the remote inventory, it will only modify + /// the local ItemData or FolderData struct. + /// + public abstract string Name + { + get; + set; + } + + /// + /// Inventory base's owner ID, as obtained from ItemData or FolderData. + /// + public abstract UUID OwnerUUID + { + get; + } + + /// + /// Gets the parent InventoryFolder referenced by ParentUUID. Returns null + /// if parent is not managed by the Inventory. + /// + public InventoryFolder Parent + { + get + { + return Inventory[ParentUUID] as InventoryFolder; + } + } + + private Inventory _Inventory; + protected Inventory Inventory + { + get { return _Inventory; } + private set { _Inventory = value; } + } + + private InventoryManager _Manager; + protected InventoryManager Manager + { + get { return _Manager; } + private set { _Manager = value; } + } + + public InventoryBase(InventoryManager manager, Inventory inventory) + { + Inventory = inventory; + Manager = manager; + } + + /// + /// Moves this InventoryBase to a new folder. Updates local and + /// remote inventory. + /// + /// The folder to move this InventoryBase to. + public virtual void Move(InventoryFolder destination) + { + if (destination.UUID != ParentUUID) + { + if (Parent != null) + { + Parent.RemoveChild(this); + } + ParentUUID = destination.UUID; + destination.AddChild(this); + } + // Subclass will call the InventoryManager method. + } + + /// + /// Removes this InventoryBase from the local Inventory and its parent's + /// Contents. + /// + protected internal virtual void LocalRemove() + { + if (Parent != null) + Parent.RemoveChild(this); + Inventory.Unmanage(this); + } + + /// + /// Changes the parent of this InventoryBase. Removing it from old parent's contents + /// and adding it to new parent's contents. + /// + /// The UUID of the new parent. + /// The InventoryFolder of the new parent. (may be null, if unmanaged) + protected internal virtual void LocalMove(UUID newParentUUID, InventoryFolder newParent) + { + if (ParentUUID != newParentUUID) + { + if (Parent != null) + { + Parent.RemoveChild(this); + } + if (newParent != null) + { + newParent.AddChild(this); + } + ParentUUID = newParentUUID; + } + } + + /// + /// Removes this InventoryBase from the remote and local inventory. + /// + public virtual void Remove() + { + LocalRemove(); + // Subclass will call the InventoryManager method. + } + + /// + /// Renames this InventoryBase, without moving it. + /// + /// The InventoryBase's new name. + public virtual void Rename(string newName) + { + Name = newName; + // Subclass will call InventoryManager method. + } + + public abstract void Give(UUID recipiant, bool particleEffect); + + public void Give(UUID recipiant) + { + Give(recipiant, false); + } + + public override bool Equals(object obj) + { + return (obj is InventoryBase) ? (obj as InventoryBase).UUID == UUID : false; + } + + public override int GetHashCode() + { + return UUID.GetHashCode(); + } + } + + public class InventoryItem : InventoryBase + { + public ItemData Data; + private Asset _Asset; + public virtual Asset Asset + { + get { return _Asset; } + protected set { _Asset = value; } + } + + public InventoryItem(InventoryManager manager, Inventory inv, UUID uuid, InventoryType type) + : this(manager, inv, new ItemData(uuid, type)) { } + + public InventoryItem(InventoryManager manager, Inventory inv, ItemData data) + : base(manager, inv) + { + Data = data; + if (Parent != null) + Parent.AddChild(this); + } + + #region InventoryBase Members + + public override UUID UUID + { + get { return Data.UUID; } + } + + public override UUID ParentUUID + { + get { return Data.ParentUUID; } + set { Data.ParentUUID = value; } + } + + public override string Name + { + get { return Data.Name; } + set { Data.Name = value; } + } + + public override UUID OwnerUUID + { + get { return Data.OwnerID; } + } + + /// + /// Requests that a copy of this item be made and placed in the + /// folder. This method is not synchronous, and returns immediately. The callback is called + /// with the new item's inventory data. + /// + /// The InventoryFolder to copy this item to. + /// The callback to call when the copy is complete. + public void Copy(InventoryFolder destination, InventoryManager.ItemCopiedCallback callback) + { + Manager.RequestCopyItem(UUID, destination.UUID, Data.Name, callback); + } + + /// + /// Synchronously requests a copy of this item be made and placed in the + /// folder. The copy is automatically managed. + /// + /// Location for the new copy. + /// Amount of time to wait for a server response. + /// A managed InventoryItem if copy successful, null if not. + public InventoryItem Copy(InventoryFolder destination, TimeSpan timeout) + { + ItemData copy; + if (Manager.CopyItem(UUID, destination.UUID, Name, timeout, out copy)) + return Inventory.Manage(copy) as InventoryItem; + else + return null; + } + + public override void Move(InventoryFolder destination) + { + base.Move(destination); + Manager.MoveItem(UUID, destination.UUID); + } + + public override void Remove() + { + base.Remove(); + Manager.RemoveItem(UUID); + } + + public override void Rename(string newName) + { + base.Rename(newName); + Manager.RenameItem(UUID, ParentUUID, newName); + } + + public override void Give(UUID recipiant, bool particleEffect) + { + Manager.GiveItem(UUID, Data.Name, Data.AssetType, recipiant, particleEffect); + } + + #endregion + + /// + /// Updates the remote inventory item with the local inventory + /// ItemData. + /// + public void Update() + { + Manager.RequestUpdateItem(Data); + } + } + + /// + /// + /// + public class InventoryFolder : InventoryBase, IEnumerable + { + /// + /// Delegate for InventoryFolder.OnContentsRetrieved + /// + /// The folder whose contents were retrieved. + public delegate void ContentsRetrieved(InventoryFolder folder); + + /// + /// Triggered when the InventoryFolder.Contents dictionary + /// is updated from the remote inventory. + /// + public event ContentsRetrieved OnContentsRetrieved; + + public FolderData Data; + + /// + /// The local contents of this InventoryFolder. This returns a copy of the + /// internal collection, so incurs a memory and CPU penalty. Consider enumerating + /// directly over the InventoryFolder. + /// + protected Dictionary _Contents; + public List Contents + { + get + { + lock (_Contents) + { + return new List(_Contents.Values); + } + } + } + + /// + /// true if all the folder's contents have been downloaded and managed. + /// false otherwise. + /// + public bool IsStale + { + get { return Data.DescendentCount == 0 || _Contents.Count != Data.DescendentCount; } + } + + public InventoryFolder(InventoryManager manager, Inventory inv, UUID uuid) + : this(manager, inv, new FolderData(uuid)) { } + + public InventoryFolder(InventoryManager manager, Inventory inv, FolderData data) + : base(manager, inv) + { + Data = data; + if (data.DescendentCount > 0) + _Contents = new Dictionary(data.DescendentCount); + else + _Contents = new Dictionary(); + + if (Parent != null) + Parent.AddChild(this); + } + + protected internal void AddChild(InventoryBase child) + { + lock (_Contents) + { + _Contents[child.UUID] = child; + } + } + + protected internal void RemoveChild(InventoryBase child) + { + lock (_Contents) + { + _Contents.Remove(child.UUID); + } + } + + #region InventoryBase Members + + public override UUID UUID + { + get { return Data.UUID; } + } + + public override UUID ParentUUID + { + get { return Data.ParentUUID; } + set { Data.ParentUUID = value; } + } + + public override string Name + { + get { return Data.Name; } + set { Data.Name = value; } + } + + public override UUID OwnerUUID + { + get { return Data.OwnerID; } + } + + public override void Move(InventoryFolder destination) + { + base.Move(destination); + Manager.MoveFolder(UUID, destination.UUID); + } + + public override void Remove() + { + base.Remove(); + Manager.RemoveFolder(UUID); + } + + protected internal override void LocalRemove() + { + // Recursively remove all children. + + // First we need to copy the children into our own list, because + // calling LocalRemove on the child causes the child to modify + // our Contents dictionary (through our RemoveChild method) + // and C# doesn't like iterating through a modified collection. + List children = new List(_Contents.Count); + lock (_Contents) + { + foreach (KeyValuePair child in _Contents) + { + children.Add(child.Value); + } + } + // Now actually do the removal: + foreach (InventoryBase child in children) + { + child.LocalRemove(); + } + base.LocalRemove(); + } + + public override void Rename(string newName) + { + base.Rename(newName); + Manager.RenameFolder(UUID, ParentUUID, newName); + } + + public override void Give(UUID recipiant, bool particleEffect) + { + // Attempt to use local copy of contents, so we dont block waiting to + // download contents. + if (!IsStale) + { + List itemContents = new List(_Contents.Count); + foreach (InventoryBase ib in this) + { + if (ib is InventoryItem) + { + itemContents.Add((ib as InventoryItem).Data); + } + } + //FIXME: Will we ever want to pass anything other then AssetType.Folder? + Manager.GiveFolder(UUID, Name, AssetType.Folder, recipiant, particleEffect, itemContents); + } + else + { + Manager.GiveFolder(UUID, Name, AssetType.Folder, recipiant, particleEffect); + } + } + + #endregion + + /// + /// Empties the folder, remotely and locally removing all items + /// in the folder RECURSIVELY. Be careful with this! + /// + public void Empty() + { + Manager.RemoveDescendants(UUID); + + + List children = null; + lock (_Contents) + { + // We need to copy the collection before removing, see comment + // in LocalRemove method. + children = new List(_Contents.Values); + } + + foreach (InventoryBase child in children) + { + child.LocalRemove(); + } + } + + /// + /// Retrieves the contents of this folder from the remote inventory. + /// This method is synchronous, and blocks until the contents are retrieved or + /// the timeout has expired. The contents are written to the + /// InventoryFolder.Contents dictionary. If the method times out, + /// InventoryFolder.Contents is left unchanged. + /// The contents retrieved (if successful) are automatically managed. + /// + /// TimeSpan to wait for a reply. + /// true if the contents were retrieved, false if timed out. + public bool DownloadContents(TimeSpan timeout) + { + List items; + List folders; + bool success = Manager.FolderContents(UUID, Data.OwnerID, true, true, InventorySortOrder.ByName, + timeout, out items, out folders); + if (success) + { + ContentsFromData(items, folders); + Data.DescendentCount = Contents.Count; + } + return success; + } + + /// + /// Asynchronously requests the folder's contents from the remote inventory. + /// The InventoryFolder.OnContentsRetrieved event + /// is raised when he new contents are written to the + /// InventoryFolder.Contents Dictionary. + /// The contents retrieved are automatically managed. + /// + public void RequestContents() + { + InventoryManager.FolderContentsCallback callback = + delegate(UUID folder, List items, List folders) + { + if (folder != UUID) + return; + + ContentsFromData(items, folders); + Data.DescendentCount = Contents.Count; + if (OnContentsRetrieved != null) + { + try { OnContentsRetrieved(this); } + catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, e); } + } + }; + + Manager.RequestFolderContents(UUID, Data.OwnerID, true, true, InventorySortOrder.ByName, + callback); + } + + private void ContentsFromData(List items, List folders) + { + Dictionary contents = new Dictionary(items.Count + folders.Count); + foreach (ItemData item in items) + { + contents.Add(item.UUID, Inventory.Manage(item)); + } + foreach (FolderData folder in folders) + { + contents.Add(folder.UUID, Inventory.Manage(folder)); + } + + lock (_Contents) + { + _Contents = contents; + } + } + + #region IEnumerable Members + + /// + /// Enumerates over the local contents of this folder. + /// Consider calling GetContents or RequestContents before enumerating + /// to synchronize the local folder contents with the remote folder contents. + /// + /// An enumerator for this InventoryFolder. + public IEnumerator GetEnumerator() + { + foreach (KeyValuePair child in _Contents) + { + yield return child.Value; + } + } + + #endregion + + #region IEnumerable Members + + /// + /// + /// + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion } } diff --git a/OpenMetaverse/InventoryManager.cs b/OpenMetaverse/InventoryManager.cs index e572c091..8b4cb048 100644 --- a/OpenMetaverse/InventoryManager.cs +++ b/OpenMetaverse/InventoryManager.cs @@ -1,26 +1,26 @@ /* - * Copyright (c) 2006-2008, openmetaverse.org + * Copyright (c) 2007-2008, openmetaverse.org * All rights reserved. * - * - Redistribution and use in source and binary forms, with or without + * - 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 + * - 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 + * 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. */ @@ -28,10 +28,12 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; +using System.Globalization; using System.Text; using OpenMetaverse.Capabilities; using OpenMetaverse.StructuredData; using OpenMetaverse.Packets; +using System.IO; namespace OpenMetaverse { @@ -52,9 +54,11 @@ namespace OpenMetaverse /// Landmark Landmark = 3, /// Script - //[Obsolete("See LSL")] Script = 4, + [Obsolete("See LSL")] + Script = 4, /// Clothing - //[Obsolete("See Wearable")] Clothing = 5, + [Obsolete("See Wearable")] + Clothing = 5, /// Object, both single and coalesced Object = 6, /// Notecard @@ -68,17 +72,22 @@ namespace OpenMetaverse /// an LSL Script LSL = 10, /// - //[Obsolete("See LSL")] LSLBytecode = 11, + [Obsolete("See LSL")] + LSLBytecode = 11, /// - //[Obsolete("See Texture")] TextureTGA = 12, + [Obsolete("See Texture")] + TextureTGA = 12, /// - //[Obsolete] Bodypart = 13, + [Obsolete] + Bodypart = 13, /// - //[Obsolete] Trash = 14, + [Obsolete] + Trash = 14, /// Snapshot = 15, /// - //[Obsolete] LostAndFound = 16, + [Obsolete] + LostAndFound = 16, /// Attachment = 17, /// @@ -89,6 +98,45 @@ namespace OpenMetaverse Gesture = 20 } + public static class InventoryTypeParser + { + private static readonly ReversableDictionary InventoryTypeMap = new ReversableDictionary(); + + static InventoryTypeParser() + { + InventoryTypeMap.Add("sound", InventoryType.Sound); + InventoryTypeMap.Add("wearable", InventoryType.Wearable); + InventoryTypeMap.Add("gesture", InventoryType.Gesture); + InventoryTypeMap.Add("script", InventoryType.LSL); + InventoryTypeMap.Add("texture", InventoryType.Texture); + InventoryTypeMap.Add("landmark", InventoryType.Landmark); + InventoryTypeMap.Add("notecard", InventoryType.Notecard); + InventoryTypeMap.Add("object", InventoryType.Object); + InventoryTypeMap.Add("animation", InventoryType.Animation); + InventoryTypeMap.Add("snapshot", InventoryType.Snapshot); + InventoryTypeMap.Add("attach", InventoryType.Attachment); + InventoryTypeMap.Add("callcard", InventoryType.CallingCard); + } + + public static InventoryType Parse(string str) + { + InventoryType t; + if (InventoryTypeMap.TryGetValue(str, out t)) + return t; + else + return InventoryType.Unknown; + } + + public static string StringValueOf(InventoryType type) + { + string str; + if (InventoryTypeMap.TryGetKey(type, out str)) + return str; + else + return "unknown"; + } + } + /// /// Item Sale Status /// @@ -104,6 +152,35 @@ namespace OpenMetaverse Contents = 3 } + public static class SaleTypeParser + { + private static readonly ReversableDictionary SaleTypeMap = new ReversableDictionary(); + + static SaleTypeParser() + { + SaleTypeMap.Add("not", SaleType.Not); + SaleTypeMap.Add("cntn", SaleType.Contents); + SaleTypeMap.Add("copy", SaleType.Copy); + SaleTypeMap.Add("orig", SaleType.Original); + } + public static SaleType Parse(string str) + { + SaleType t; + if (SaleTypeMap.TryGetValue(str, out t)) + return t; + else + return SaleType.Not; + } + public static string StringValueOf(SaleType type) + { + string str; + if (SaleTypeMap.TryGetKey(type, out str)) + return str; + else + return "not"; + } + } + [Flags] public enum InventorySortOrder : int { @@ -133,72 +210,19 @@ namespace OpenMetaverse #endregion Enums - #region Inventory Object Classes /// - /// Base Class for Inventory Items + /// Struct containing entire inventory state for an item. /// - public abstract class InventoryBase + public struct ItemData { /// of item/folder - public readonly UUID UUID; + public UUID UUID; /// of parent folder public UUID ParentUUID; /// Name of item/folder public string Name; /// Item/Folder Owners public UUID OwnerID; - - /// - /// Constructor, takes an itemID as a parameter - /// - /// The of the item - public InventoryBase(UUID itemID) - { - if (itemID == UUID.Zero) - Logger.Log("Initializing an InventoryBase with UUID.Zero", Helpers.LogLevel.Warning); - UUID = itemID; - } - - /// - /// Generates a number corresponding to the value of the object to support the use of a hash table, - /// suitable for use in hashing algorithms and data structures such as a hash table - /// - /// A Hashcode of all the combined InventoryBase fields - public override int GetHashCode() - { - return UUID.GetHashCode() ^ ParentUUID.GetHashCode() ^ Name.GetHashCode() ^ OwnerID.GetHashCode(); - } - - /// - /// Determine whether the specified object is equal to the current object - /// - /// InventoryBase object to compare against - /// true if objects are the same - public override bool Equals(object o) - { - InventoryBase inv = o as InventoryBase; - return inv != null && Equals(inv); - } - - /// - /// Determine whether the specified object is equal to the current object - /// - /// InventoryBase object to compare against - /// true if objects are the same - public virtual bool Equals(InventoryBase o) - { - return o.UUID == UUID - && o.ParentUUID == ParentUUID - && o.Name == Name - && o.OwnerID == OwnerID; - } - } - - /// - /// An Item in Inventory - /// - public class InventoryItem : InventoryBase - { /// The of this item public UUID AssetUUID; /// The combined of this item @@ -225,63 +249,45 @@ namespace OpenMetaverse /// UTC (Coordinated Universal Time) public DateTime CreationDate; - /// - /// Construct a new InventoryItem object - /// - /// The of the item - public InventoryItem(UUID itemID) - : base(itemID) { } + public ItemData(InventoryType type) + : this(UUID.Zero, type) { } + public ItemData(UUID uuid) + : this(uuid, InventoryType.Unknown) { } + public ItemData(UUID uuid, InventoryType type) + { + UUID = uuid; + InventoryType = type; + ParentUUID = UUID.Zero; + Name = String.Empty; + OwnerID = UUID.Zero; + AssetUUID = UUID.Zero; + Permissions = new Permissions(); + AssetType = AssetType.Unknown; + CreatorID = UUID.Zero; + Description = String.Empty; + GroupID = UUID.Zero; + GroupOwned = false; + SalePrice = 0; + SaleType = SaleType.Not; + Flags = 0; + CreationDate = DateTime.Now; + } - /// - /// Construct a new InventoryItem object of a specific Type - /// - /// The type of item from - /// of the item - public InventoryItem(InventoryType type, UUID itemID) : base(itemID) { InventoryType = type; } - - /// - /// Generates a number corresponding to the value of the object to support the use of a hash table. - /// Suitable for use in hashing algorithms and data structures such as a hash table - /// - /// A Hashcode of all the combined InventoryItem fields public override int GetHashCode() { - return AssetUUID.GetHashCode() ^ Permissions.GetHashCode() ^ AssetType.GetHashCode() ^ - InventoryType.GetHashCode() ^ Description.GetHashCode() ^ GroupID.GetHashCode() ^ - GroupOwned.GetHashCode() ^ SalePrice.GetHashCode() ^ SaleType.GetHashCode() ^ - Flags.GetHashCode() ^ CreationDate.GetHashCode(); + return UUID.GetHashCode(); } - /// - /// Compares an object - /// - /// The object to compare - /// true if comparison object matches - public override bool Equals(object o) + public override bool Equals(object obj) { - InventoryItem item = o as InventoryItem; - return item != null && Equals(item); - } + if (!(obj is ItemData)) + return false; - /// - /// Determine whether the specified object is equal to the current object - /// - /// The object to compare against - /// true if objects are the same - public override bool Equals(InventoryBase o) - { - InventoryItem item = o as InventoryItem; - return item != null && Equals(item); - } - - /// - /// Determine whether the specified object is equal to the current object - /// - /// The object to compare against - /// true if objects are the same - public bool Equals(InventoryItem o) - { - return base.Equals(o as InventoryBase) + ItemData o = (ItemData)obj; + return o.UUID == UUID + && o.ParentUUID == ParentUUID + && o.Name == Name + && o.OwnerID == OwnerID && o.AssetType == AssetType && o.AssetUUID == AssetUUID && o.CreationDate == CreationDate @@ -294,260 +300,179 @@ namespace OpenMetaverse && o.SalePrice == SalePrice && o.SaleType == SaleType; } - } - - /// - /// InventoryTexture Class representing a graphical image - /// - /// - public class InventoryTexture : InventoryItem - { - /// - /// Construct an InventoryTexture object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryTexture(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Texture; - } - } - - /// - /// InventorySound Class representing a playable sound - /// - public class InventorySound : InventoryItem - { - /// - /// Construct an InventorySound object - /// - /// A which becomes the - /// objects AssetUUID - public InventorySound(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Sound; - } - } - - /// - /// InventoryCallingCard Class, contains information on another avatar - /// - public class InventoryCallingCard : InventoryItem - { - /// - /// Construct an InventoryCallingCard object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryCallingCard(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.CallingCard; - } - } - - /// - /// InventoryLandmark Class, contains details on a specific location - /// - public class InventoryLandmark : InventoryItem - { - /// - /// Construct an InventoryLandmark object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryLandmark(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Landmark; - } - - /// - /// Landmarks use the ObjectType struct and will have a flag of 1 set if they have been visited - /// - public ObjectType LandmarkType + public static bool operator ==(ItemData lhs, ItemData rhs) { - get { return (ObjectType)Flags; } - set { Flags = (uint)value; } - } - } - - /// - /// InventoryObject Class contains details on a primitive or coalesced set of primitives - /// - public class InventoryObject : InventoryItem - { - /// - /// Construct an InventoryObject object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryObject(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Object; + return lhs.Equals(rhs); } - /// - /// Get the Objects permission override settings - /// - /// These will indicate the which permissions that - /// will be overwritten when the object is rezzed in-world - /// - public ObjectType ObjectType + public static bool operator !=(ItemData lhs, ItemData rhs) { - get { return (ObjectType)Flags; } - set { Flags = (uint)value; } - } - } - - /// - /// InventoryNotecard Class, contains details on an encoded text document - /// - public class InventoryNotecard : InventoryItem - { - /// - /// Construct an InventoryNotecard object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryNotecard(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Notecard; - } - } - - /// - /// InventoryCategory Class - /// - /// TODO: Is this even used for anything? - public class InventoryCategory : InventoryItem - { - /// - /// Construct an InventoryCategory object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryCategory(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Category; - } - } - - /// - /// InventoryLSL Class, represents a Linden Scripting Language object - /// - public class InventoryLSL : InventoryItem - { - /// - /// Construct an InventoryLSL object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryLSL(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.LSL; - } - } - - /// - /// InventorySnapshot Class, an image taken with the viewer - /// - public class InventorySnapshot : InventoryItem - { - /// - /// Construct an InventorySnapshot object - /// - /// A which becomes the - /// objects AssetUUID - public InventorySnapshot(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Snapshot; - } - } - - /// - /// InventoryAttachment Class, contains details on an attachable object - /// - public class InventoryAttachment : InventoryItem - { - /// - /// Construct an InventoryAttachment object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryAttachment(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Attachment; + return !(lhs == rhs); } /// - /// Get the last AttachmentPoint this object was attached to + /// Returns the ItemData in the hierarchical bracket format + /// used in the Second Life client's notecards and inventory cache. /// - public AttachmentPoint AttachmentPoint + /// a string representation of this ItemData + public override string ToString() { - get { return (AttachmentPoint)Flags; } - set { Flags = (uint)value; } + StringWriter writer = new StringWriter(); + ToString(writer); + return writer.ToString(); } - } - - /// - /// InventoryWearable Class, details on a clothing item or body part - /// - public class InventoryWearable : InventoryItem - { - /// - /// Construct an InventoryWearable object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryWearable(UUID itemID) : base(itemID) { InventoryType = InventoryType.Wearable; } /// - /// The , Skin, Shape, Skirt, Etc + /// Writes the inventory item to the TextWriter in the hierarchical bracket format + /// used in the Second Life client's notecards and inventory cache. /// - public WearableType WearableType + /// Writer to write to. + public void ToString(TextWriter writer) { - get { return (WearableType)Flags; } - set { Flags = (uint)value; } + writer.WriteLine("inv_item\t0"); + writer.WriteLine('{'); + writer.WriteLine("\titem_id\t{0}", UUID.ToString()); + writer.WriteLine("\tparent_id\t{0}", ParentUUID.ToString()); + // Permissions: + writer.WriteLine("permissions\t0"); + writer.WriteLine('{'); + writer.WriteLine("\tbase_mask\t{0}", String.Format("{0:x}", (uint)Permissions.BaseMask).PadLeft(8, '0')); + writer.WriteLine("\towner_mask\t{0}", String.Format("{0:x}", (uint)Permissions.OwnerMask).PadLeft(8, '0')); + writer.WriteLine("\tgroup_mask\t{0}", String.Format("{0:x}", (uint)Permissions.GroupMask).PadLeft(8, '0')); + writer.WriteLine("\teveryone_mask\t{0}", String.Format("{0:x}", (uint)Permissions.EveryoneMask).PadLeft(8, '0')); + writer.WriteLine("\tnext_owner_mask\t{0}", String.Format("{0:x}", (uint)Permissions.NextOwnerMask).PadLeft(8, '0')); + writer.WriteLine("\tcreator_id\t{0}", CreatorID.ToString()); + writer.WriteLine("\towner_id\t{0}", OwnerID.ToString()); + writer.WriteLine("\tlast_owner_id\t{0}", UUID.Zero); // FIXME? + writer.WriteLine("\tgroup_id\t{0}", GroupID.ToString()); + writer.WriteLine('}'); + + writer.WriteLine("\tasset_id\t{0}", AssetUUID.ToString()); + writer.WriteLine("\ttype\t{0}", AssetTypeParser.StringValueOf(AssetType)); + writer.WriteLine("\tinv_type\t{0}", InventoryTypeParser.StringValueOf(InventoryType)); + writer.WriteLine("\tflags\t{0}", string.Format("{0:x}", Flags).PadLeft(8, '0')); + + // Sale info: + writer.WriteLine("sale_info\t0"); + writer.WriteLine('{'); + writer.WriteLine("\tsale_type\t{0}", SaleTypeParser.StringValueOf(SaleType)); + writer.WriteLine("\tsale_price\t{0}", SalePrice); + writer.WriteLine('}'); + + writer.WriteLine("\tname\t{0}|", Name); + writer.WriteLine("\tdesc\t{0}|", Description); + writer.WriteLine("\tcreation_date\t{0}", Helpers.DateTimeToUnixTime(CreationDate)); + writer.WriteLine('}'); + } + + /// + /// Reads the ItemData from a string source. The string is wrapped + /// in a and passed to the + /// other method. + /// + /// String to parse ItemData from. + /// Parsed ItemData + public static ItemData Parse(string src) + { + return Parse(new StringReader(src)); + } + + /// + /// Reads an ItemData from a TextReader source. The format of the text + /// should be the same as the one used by Second Life Notecards. The TextReader should + /// be placed ideally on the line containing "inv_item" but parsing will succeed as long + /// as it is before the opening bracket immediately following the inv_item line. + /// The TextReader will be placed on the line following the inv_item's closing bracket. + /// + /// text source + /// Parsed item. + public static ItemData Parse(TextReader reader) + { + ItemData item = new ItemData(); + #region Parsing + TextData invItem = TextHierarchyParser.Parse(reader); + Console.WriteLine(invItem); + //if (invItem.Name == "inv_item") // YAY + item.UUID = new UUID(invItem.Nested["item_id"].Value); + item.ParentUUID = new UUID(invItem.Nested["parent_id"].Value); + item.AssetUUID = new UUID(invItem.Nested["asset_id"].Value); + item.AssetType = AssetTypeParser.Parse(invItem.Nested["type"].Value); + item.InventoryType = InventoryTypeParser.Parse(invItem.Nested["inv_type"].Value); + item.Flags = uint.Parse(invItem.Nested["flags"].Value, NumberStyles.HexNumber); + string rawName = invItem.Nested["name"].Value; + item.Name = rawName.Substring(0, rawName.LastIndexOf('|')); + string rawDesc = invItem.Nested["desc"].Value; + item.Description = rawDesc.Substring(0, rawDesc.LastIndexOf('|')); + item.CreationDate = Helpers.UnixTimeToDateTime(uint.Parse(invItem.Nested["creation_date"].Value)); + + // Sale info: + TextData saleInfo = invItem.Nested["sale_info"]; + item.SalePrice = int.Parse(saleInfo.Nested["sale_price"].Value); + item.SaleType = SaleTypeParser.Parse(saleInfo.Nested["sale_type"].Value); + + TextData permissions = invItem.Nested["permissions"]; + item.Permissions = new Permissions(); + item.Permissions.BaseMask = (PermissionMask)uint.Parse(permissions.Nested["base_mask"].Value, NumberStyles.HexNumber); + item.Permissions.EveryoneMask = (PermissionMask)uint.Parse(permissions.Nested["everyone_mask"].Value, NumberStyles.HexNumber); + item.Permissions.GroupMask = (PermissionMask)uint.Parse(permissions.Nested["group_mask"].Value, NumberStyles.HexNumber); + item.Permissions.OwnerMask = (PermissionMask)uint.Parse(permissions.Nested["owner_mask"].Value, NumberStyles.HexNumber); + item.Permissions.NextOwnerMask = (PermissionMask)uint.Parse(permissions.Nested["next_owner_mask"].Value, NumberStyles.HexNumber); + item.CreatorID = new UUID(permissions.Nested["creator_id"].Value); + item.OwnerID = new UUID(permissions.Nested["owner_id"].Value); + item.GroupID = new UUID(permissions.Nested["group_id"].Value); + // permissions.Nested["last_owner_id"] // FIXME? + #endregion + return item; + } + + /// + /// + /// + /// String to parse from. + /// Parsed ItemData. + /// true if successful, false otherwise. + public static bool TryParse(string str, out ItemData item) + { + return TryParse(new StringReader(str), out item); + } + + + /// + /// + /// + /// Text source. + /// Parsed ItemData. + /// true if successful false otherwise. + public static bool TryParse(TextReader reader, out ItemData item) + { + try + { + item = Parse(reader); + } + catch (Exception e) + { + item = new ItemData(); + Logger.Log(e.Message, Helpers.LogLevel.Error, e); + return false; + } + + return true; } } /// - /// InventoryAnimation Class, A bvh encoded object which animates an avatar + /// Struct containing all inventory state for a folder. /// - public class InventoryAnimation : InventoryItem - { - /// - /// Construct an InventoryAnimation object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryAnimation(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Animation; - } - } - - /// - /// InventoryGesture Class, details on a series of animations, sounds, and actions - /// - public class InventoryGesture : InventoryItem - { - /// - /// Construct an InventoryGesture object - /// - /// A which becomes the - /// objects AssetUUID - public InventoryGesture(UUID itemID) : base(itemID) - { - InventoryType = InventoryType.Gesture; - } - } - - /// - /// A folder contains s and has certain attributes specific - /// to itself - /// - public class InventoryFolder : InventoryBase + public struct FolderData { + /// The of this item + public UUID UUID; + /// of parent folder + public UUID ParentUUID; + /// Name of item/folder + public string Name; + /// Item/Folder Owners + public UUID OwnerID; /// The Preferred for a folder. public AssetType PreferredType; /// The Version of this folder @@ -555,80 +480,201 @@ namespace OpenMetaverse /// Number of child items this folder contains. public int DescendentCount; - /// - /// Constructor - /// - /// UUID of the folder - public InventoryFolder(UUID itemID) - : base(itemID) { } - - /// - /// - /// - /// - public override string ToString() + public FolderData(UUID uuid) { - return Name; + UUID = uuid; + ParentUUID = UUID.Zero; + Name = String.Empty; + OwnerID = UUID.Zero; + PreferredType = AssetType.Unknown; + Version = 0; + DescendentCount = 0; } - /// - /// - /// - /// public override int GetHashCode() { - return PreferredType.GetHashCode() ^ Version.GetHashCode() ^ DescendentCount.GetHashCode(); + return ParentUUID.GetHashCode() ^ Name.GetHashCode() ^ OwnerID.GetHashCode() ^ + PreferredType.GetHashCode() ^ Version.GetHashCode() ^ DescendentCount.GetHashCode(); } - /// - /// - /// - /// - /// - public override bool Equals(object o) + public override bool Equals(object obj) { - InventoryFolder folder = o as InventoryFolder; - return folder != null && Equals(folder); - } + if (!(obj is FolderData)) + return false; - /// - /// - /// - /// - /// - public override bool Equals(InventoryBase o) - { - InventoryFolder folder = o as InventoryFolder; - return folder != null && Equals(folder); - } - - /// - /// - /// - /// - /// - public bool Equals(InventoryFolder o) - { - return base.Equals(o as InventoryBase) + FolderData o = (FolderData)obj; + return o.UUID == UUID + && o.ParentUUID == ParentUUID + && o.Name == Name + && o.OwnerID == OwnerID && o.DescendentCount == DescendentCount && o.PreferredType == PreferredType && o.Version == Version; } + + public static bool operator ==(FolderData lhs, FolderData rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(FolderData lhs, FolderData rhs) + { + return !(lhs == rhs); + } + + /// + /// Returns the FolderData in the hierarchical bracket format + /// used in the Second Life client's notecards and inventory cache. + /// + /// a string representation of this FolderData + public override string ToString() + { + StringWriter writer = new StringWriter(); + ToString(writer); + return writer.ToString(); + } + + /// + /// Writes the FolderData to the TextWriter in the hierarchical bracket format + /// used in the Second Life client's notecards and inventory cache. + /// + /// Writer to write to. + public void ToString(TextWriter writer) + { + writer.WriteLine("inv_category\t0"); + writer.WriteLine('{'); + writer.WriteLine("\tcat_id\t{0}", UUID.ToString()); + writer.WriteLine("\tparent_id\t{0}", ParentUUID.ToString()); + writer.WriteLine("\ttype\tcategory"); + // TODO: Some folders have "-1" as their perf_type, investigate this. + writer.WriteLine("\tpref_type\t{0}", AssetTypeParser.StringValueOf(PreferredType)); + writer.WriteLine("\tname\t{0}|", Name); + writer.WriteLine("\towner_id\t{0}", OwnerID.ToString()); + writer.WriteLine("\tversion\t{0}", Version); + writer.WriteLine('}'); + } + + /// + /// Reads the FolderData from a string source. The string is wrapped + /// in a and passed to the + /// other method. + /// + /// String to parse FolderData from. + /// Parsed FolderData + public static FolderData Parse(string src) + { + return Parse(new StringReader(src)); + } + + /// + /// Reads an InventoryItem from a TextReader source. The format of the text + /// should be the same as the one used by Second Life Notecards. The TextReader should + /// be placed ideally on the line containing "inv_category" but parsing will succeed as long + /// as it is before the opening bracket immediately following the inv_category line. + /// The TextReader will be placed on the line following the inv_category's closing bracket. + /// + /// text source + /// Parsed item. + public static FolderData Parse(TextReader reader) + { + FolderData folder = new FolderData(); + #region Parsing + TextData invCategory = TextHierarchyParser.Parse(reader); + + //if (invCategory.Name == "inv_category") // YAY + folder.UUID = new UUID(invCategory.Nested["cat_id"].Value); + string rawName = invCategory.Nested["name"].Value; + folder.Name = rawName.Substring(0, rawName.LastIndexOf('|')); + folder.OwnerID = new UUID(invCategory.Nested["owner_id"].Value); + folder.ParentUUID = new UUID(invCategory.Nested["parent_id"].Value); + folder.PreferredType = AssetTypeParser.Parse(invCategory.Nested["pref_type"].Value); + folder.Version = int.Parse(invCategory.Nested["version"].Value); + // TODO: Investigate invCategory.Nested["type"] + #endregion + return folder; + } + + public static bool TryParse(string str, out FolderData folder) + { + return TryParse(new StringReader(str), out folder); + } + + public static bool TryParse(TextReader reader, out FolderData folder) + { + try + { + folder = Parse(reader); + } + catch (Exception e) + { + folder = new FolderData(); + Logger.Log(e.Message, Helpers.LogLevel.Error, e); + return false; + } + return true; + } } - #endregion Inventory Object Classes + public class InventorySkeleton + { + public UUID RootUUID; + public UUID Owner; + public FolderData[] Folders; + public InventorySkeleton(UUID rootFolder, UUID owner) + { + RootUUID = rootFolder; + Owner = owner; + Folders = new FolderData[0]; + } + } /// /// Tools for dealing with agents inventory /// public class InventoryManager { - protected struct InventorySearch + protected struct DescendentsRequest { public UUID Folder; - public UUID Owner; - public string[] Path; - public int Level; + public bool ReceivedResponse; + public FolderContentsCallback Callback; + public int Descendents; + public List FolderContents; + public List ItemContents; + public DescendentsRequest(UUID folder, FolderContentsCallback callback) + { + Folder = folder; + Callback = callback; + ReceivedResponse = false; + Descendents = 0; + FolderContents = new List(); + ItemContents = new List(); + } + } + + protected struct FetchRequest + { + public int ItemsFetched; + public Dictionary RequestedItems; + public FetchItemsCallback Callback; + + public FetchRequest(FetchItemsCallback callback, ICollection requestedItems) + { + ItemsFetched = 0; + Callback = callback; + RequestedItems = new Dictionary(requestedItems.Count); + foreach (UUID uuid in requestedItems) + RequestedItems.Add(uuid, null); + } + + public void StoreFetchedItem(ItemData item) + { + if (RequestedItems.ContainsKey(item.UUID) && RequestedItems[item.UUID] == null) + { + ++ItemsFetched; + RequestedItems[item.UUID] = item; + } + } } #region Delegates @@ -640,7 +686,7 @@ namespace OpenMetaverse /// item succeeded or not /// Inventory item being created. If success is /// false this will be null - public delegate void ItemCreatedCallback(bool success, InventoryItem item); + public delegate void ItemCreatedCallback(bool success, ItemData item); /// /// Callback for an inventory item being create from an uploaded asset @@ -655,19 +701,33 @@ namespace OpenMetaverse /// /// /// - public delegate void ItemCopiedCallback(InventoryBase item); + public delegate void ItemCopiedCallback(ItemData itemData); /// - /// + /// Use this delegate to create a callback for RequestFolderContents. /// - /// - public delegate void ItemReceivedCallback(InventoryItem item); + /// The folder whose contents were received. + /// The items in + /// The folders in + /// + public delegate void FolderContentsCallback(UUID folder, List Items, List Folders); /// - /// Callback for an inventory folder updating + /// Use this delegate to create a callback for RequestFetchItems. /// - /// UUID of the folder that was updated - public delegate void FolderUpdatedCallback(UUID folderID); + /// The items retrieved. + /// + public delegate void FetchItemsCallback(List items); + + /// + /// The updated item data. + public delegate void ItemUpdate(ItemData itemData); + + /// + /// The updated folder data. + public delegate void FolderUpdate(FolderData folderData); + + public delegate void AssetUpdate(UUID itemID, UUID newAssetID); /// /// Callback for when an inventory item is offered to us by another avatar or an object @@ -677,8 +737,8 @@ namespace OpenMetaverse /// The AssetType being offered /// Will be null if item is offered from an object /// will be true of item is offered from an object - /// Return true to accept the offer, or false to decline it - public delegate bool ObjectOfferedCallback(InstantMessage offerDetails, AssetType type, UUID objectID, bool fromTask); + /// Return UUID of destination folder to accept offer, UUID.Zero to decline it. + public delegate UUID ObjectOfferedCallback(InstantMessage offerDetails, AssetType type, UUID objectID, bool fromTask); /// /// Callback when an inventory object is accepted and received from a @@ -690,14 +750,16 @@ namespace OpenMetaverse /// /// /// - public delegate void TaskItemReceivedCallback(UUID itemID, UUID folderID, UUID creatorID, + public delegate void TaskItemReceivedCallback(UUID itemID, UUID folderID, UUID creatorID, UUID assetID, InventoryType type); /// - /// + /// Delegate for use with the and + /// methods. Raised when the path + /// is resolved to a UUID. /// - /// - /// + /// A string representing the path to the UUID, with '/' seperators. + /// The item's UUID. public delegate void FindObjectByPathCallback(string path, UUID inventoryObjectID); /// @@ -721,29 +783,23 @@ namespace OpenMetaverse #endregion Delegates #region Events + public event AssetUpdate OnAssetUpdate; /// - /// Fired when a reply to a RequestFetchInventory() is received + /// Fired when a BulkUpdateInventory packet is received containing item data. /// /// - public event ItemReceivedCallback OnItemReceived; + public event ItemUpdate OnItemUpdate; /// - /// Fired when a response to a RequestFolderContents() is received + /// Fired when a BulkUpdateInventory packet is received containing folder data. /// - /// - public event FolderUpdatedCallback OnFolderUpdated; + public event FolderUpdate OnFolderUpdate; /// /// Fired when an object or another avatar offers us an inventory item /// public event ObjectOfferedCallback OnObjectOffered; - - /// - /// Fired when a response to FindObjectByPath() is received - /// - /// - public event FindObjectByPathCallback OnFindObjectByPath; /// /// Fired when a task inventory item is received @@ -764,13 +820,17 @@ namespace OpenMetaverse #endregion Events private GridClient _Client; - private Inventory _Store; + private NetworkManager _Network; + private AgentManager _Agents; + private InventorySkeleton _InventorySkeleton; + private InventorySkeleton _LibrarySkeleton; private Random _RandNumbers = new Random(); private object _CallbacksLock = new object(); private uint _CallbackPos; private Dictionary _ItemCreatedCallbacks = new Dictionary(); - private Dictionary _ItemCopiedCallbacks = new Dictionary(); - private List _Searches = new List(); + private Dictionary _ItemCopiedCallbacks = new Dictionary(); + private List _DescendentsRequests = new List(); + private List _FetchRequests = new List(); #region String Arrays @@ -865,34 +925,44 @@ namespace OpenMetaverse #region Properties - /// - /// Get this agents Inventory data - /// - public Inventory Store { get { return _Store; } } + public InventorySkeleton LibrarySkeleton + { + get { return _LibrarySkeleton; } + set { _LibrarySkeleton = value; } + } + public InventorySkeleton InventorySkeleton + { + get { return _InventorySkeleton; } + set { _InventorySkeleton = value; } + } #endregion Properties /// /// Default constructor /// - /// Reference to the GridClient object + /// Reference to the SecondLife client + /// public InventoryManager(GridClient client) + : this(client, client.Network, client.Self) { } + + public InventoryManager(GridClient client, NetworkManager network, AgentManager agents) { _Client = client; + _Network = network; + _Agents = agents; + _Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, new NetworkManager.PacketCallback(UpdateCreateInventoryItemHandler)); + _Network.RegisterCallback(PacketType.SaveAssetIntoInventory, new NetworkManager.PacketCallback(SaveAssetIntoInventoryHandler)); + _Network.RegisterCallback(PacketType.BulkUpdateInventory, new NetworkManager.PacketCallback(BulkUpdateInventoryHandler)); + _Network.RegisterCallback(PacketType.InventoryDescendents, new NetworkManager.PacketCallback(InventoryDescendentsHandler)); + _Network.RegisterCallback(PacketType.FetchInventoryReply, new NetworkManager.PacketCallback(FetchInventoryReplyHandler)); + _Network.RegisterCallback(PacketType.ReplyTaskInventory, new NetworkManager.PacketCallback(ReplyTaskInventoryHandler)); - _Client.Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, new NetworkManager.PacketCallback(UpdateCreateInventoryItemHandler)); - _Client.Network.RegisterCallback(PacketType.SaveAssetIntoInventory, new NetworkManager.PacketCallback(SaveAssetIntoInventoryHandler)); - _Client.Network.RegisterCallback(PacketType.BulkUpdateInventory, new NetworkManager.PacketCallback(BulkUpdateInventoryHandler)); - _Client.Network.RegisterCallback(PacketType.MoveInventoryItem, new NetworkManager.PacketCallback(MoveInventoryItemHandler)); - _Client.Network.RegisterCallback(PacketType.InventoryDescendents, new NetworkManager.PacketCallback(InventoryDescendentsHandler)); - _Client.Network.RegisterCallback(PacketType.FetchInventoryReply, new NetworkManager.PacketCallback(FetchInventoryReplyHandler)); - _Client.Network.RegisterCallback(PacketType.ReplyTaskInventory, new NetworkManager.PacketCallback(ReplyTaskInventoryHandler)); - // Watch for inventory given to us through instant message - _Client.Self.OnInstantMessage += new AgentManager.InstantMessageCallback(Self_OnInstantMessage); + _Agents.OnInstantMessage += new AgentManager.InstantMessageCallback(Self_OnInstantMessage); // Register extra parameters with login and parse the inventory data that comes back - _Client.Network.RegisterLoginResponseCallback( + _Network.RegisterLoginResponseCallback( new NetworkManager.LoginResponseCallback(Network_OnLoginResponse), new string[] { "inventory-root", "inventory-skeleton", "inventory-lib-root", @@ -901,57 +971,51 @@ namespace OpenMetaverse #region Fetch + /// + /// Fetch a single inventory item. + /// + /// The item's + /// The item owner's + /// The amount of time to wait for results. + /// The item retrieved. + /// true if successful, false if not. + public bool FetchItem(UUID itemID, UUID ownerID, TimeSpan timeout, out ItemData item) + { + List items = FetchItems(new UUID[] { itemID }, ownerID, timeout); + if (items == null || items.Count == 0) + { + item = new ItemData(); + return false; + } + else + { + item = items[0]; + return true; + } + } + /// /// Fetch an inventory item from the dataserver /// /// The items /// The item Owners - /// a integer representing the number of milliseconds to wait for results + /// a TimeSpan representing the amount of time to wait for results /// An object on success, or null if no item was found /// Items will also be sent to the event - public InventoryItem FetchItem(UUID itemID, UUID ownerID, int timeoutMS) + public List FetchItems(ICollection itemIDs, UUID ownerID, TimeSpan timeout) { AutoResetEvent fetchEvent = new AutoResetEvent(false); - InventoryItem fetchedItem = null; - ItemReceivedCallback callback = - delegate(InventoryItem item) + List items = null; + FetchItemsCallback callback = + delegate(List fetchedItems) { - if (item.UUID == itemID) - { - fetchedItem = item; - fetchEvent.Set(); - } + items = fetchedItems; + fetchEvent.Set(); }; - - OnItemReceived += callback; - RequestFetchInventory(itemID, ownerID); - - fetchEvent.WaitOne(timeoutMS, false); - OnItemReceived -= callback; - - return fetchedItem; - } - - /// - /// Request A single inventory item - /// - /// The items - /// The item Owners - /// - public void RequestFetchInventory(UUID itemID, UUID ownerID) - { - FetchInventoryPacket fetch = new FetchInventoryPacket(); - fetch.AgentData = new FetchInventoryPacket.AgentDataBlock(); - fetch.AgentData.AgentID = _Client.Self.AgentID; - fetch.AgentData.SessionID = _Client.Self.SessionID; - - fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[1]; - fetch.InventoryData[0] = new FetchInventoryPacket.InventoryDataBlock(); - fetch.InventoryData[0].ItemID = itemID; - fetch.InventoryData[0].OwnerID = ownerID; - - _Client.Network.SendPacket(fetch); + RequestFetchItems(itemIDs, ownerID, callback); + fetchEvent.WaitOne(timeout, false); + return items; } /// @@ -960,25 +1024,29 @@ namespace OpenMetaverse /// Inventory items to request /// Owners of the inventory items /// - public void RequestFetchInventory(List itemIDs, List ownerIDs) + public void RequestFetchItems(ICollection itemIDs, UUID ownerID, FetchItemsCallback callback) { - if (itemIDs.Count != ownerIDs.Count) - throw new ArgumentException("itemIDs and ownerIDs must contain the same number of entries"); + FetchRequest request = new FetchRequest(callback, itemIDs); + lock (_FetchRequests) + _FetchRequests.Add(request); + // Send the packet: FetchInventoryPacket fetch = new FetchInventoryPacket(); fetch.AgentData = new FetchInventoryPacket.AgentDataBlock(); - fetch.AgentData.AgentID = _Client.Self.AgentID; - fetch.AgentData.SessionID = _Client.Self.SessionID; + fetch.AgentData.AgentID = _Agents.AgentID; + fetch.AgentData.SessionID = _Agents.SessionID; fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[itemIDs.Count]; - for (int i = 0; i < itemIDs.Count; i++) + int i = 0; + foreach (UUID item in itemIDs) { fetch.InventoryData[i] = new FetchInventoryPacket.InventoryDataBlock(); - fetch.InventoryData[i].ItemID = itemIDs[i]; - fetch.InventoryData[i].OwnerID = ownerIDs[i]; + fetch.InventoryData[i].ItemID = item; + fetch.InventoryData[i].OwnerID = ownerID; + ++i; } - _Client.Network.SendPacket(fetch); + _Network.SendPacket(fetch); } /// @@ -989,45 +1057,33 @@ namespace OpenMetaverse /// true to retrieve folders /// true to retrieve items /// sort order to return results in - /// a integer representing the number of milliseconds to wait for results - /// A list of inventory items matching search criteria within folder + /// a TimeSpan representing the amount of time to wait for results + /// A list of FolderData representing the folders contained in the parent folder. + /// A list of ItemData representing the items contained in the parent folder. + /// true if successful, false if timed out /// - /// InventoryFolder.DescendentCount will only be accurate if both folders and items are - /// requested - public List FolderContents(UUID folder, UUID owner, bool folders, bool items, - InventorySortOrder order, int timeoutMS) + public bool FolderContents(UUID folder, UUID owner, bool folders, bool items, + InventorySortOrder order, TimeSpan timeout, out List itemContents, out List folderContents) { - List objects = null; - AutoResetEvent fetchEvent = new AutoResetEvent(false); - - FolderUpdatedCallback callback = - delegate(UUID folderID) + AutoResetEvent lockEvent = new AutoResetEvent(false); + List _folders = null; + List _items = null; + FolderContentsCallback callback = new FolderContentsCallback( + delegate(UUID folderID, List __items, List __folders) { - if (folderID == folder - && _Store[folder] is InventoryFolder) + if (folderID == folder) { - // InventoryDescendentsHandler only stores DescendendCount if both folders and items are fetched. - if (_Store.GetContents(folder).Count >= ((InventoryFolder)_Store[folder]).DescendentCount) - { - - fetchEvent.Set(); - } + _folders = __folders; + _items = __items; + lockEvent.Set(); } - else - { - fetchEvent.Set(); - } - }; + }); + RequestFolderContents(folder, owner, folders, items, order, callback); - OnFolderUpdated += callback; - - RequestFolderContents(folder, owner, folders, items, order); - if (fetchEvent.WaitOne(timeoutMS, false)) - objects = _Store.GetContents(folder); - - OnFolderUpdated -= callback; - - return objects; + bool success = lockEvent.WaitOne(timeout, false); + itemContents = _items; + folderContents = _folders; + return success; } /// @@ -1038,13 +1094,18 @@ namespace OpenMetaverse /// true to return s contained in folder /// true to return s containd in folder /// the sort order to return items in + /// The callback to fire when the contents are received. /// - public void RequestFolderContents(UUID folder, UUID owner, bool folders, bool items, - InventorySortOrder order) + public void RequestFolderContents(UUID folder, UUID owner, bool folders, bool items, InventorySortOrder order, FolderContentsCallback callback) { + DescendentsRequest request = new DescendentsRequest(folder, callback); + lock (_DescendentsRequests) + _DescendentsRequests.Add(request); + + // Send the packet: FetchInventoryDescendentsPacket fetch = new FetchInventoryDescendentsPacket(); - fetch.AgentData.AgentID = _Client.Self.AgentID; - fetch.AgentData.SessionID = _Client.Self.SessionID; + fetch.AgentData.AgentID = _Agents.AgentID; + fetch.AgentData.SessionID = _Agents.SessionID; fetch.InventoryData.FetchFolders = folders; fetch.InventoryData.FetchItems = items; @@ -1052,7 +1113,7 @@ namespace OpenMetaverse fetch.InventoryData.OwnerID = owner; fetch.InventoryData.SortOrder = (int)order; - _Client.Network.SendPacket(fetch); + _Network.SendPacket(fetch); } #endregion Fetch @@ -1070,33 +1131,27 @@ namespace OpenMetaverse /// if not found, or UUID.Zero on failure public UUID FindFolderForType(AssetType type) { - if (_Store == null) + if (_InventorySkeleton == null) { - Logger.Log("Inventory is null, FindFolderForType() lookup cannot continue", + Logger.Log("Inventory skeleton is null, FindFolderForType() lookup cannot continue", Helpers.LogLevel.Error, _Client); return UUID.Zero; } // Folders go in the root if (type == AssetType.Folder) - return _Store.RootFolder.UUID; + return _InventorySkeleton.RootUUID; // Loop through each top-level directory and check if PreferredType // matches the requested type - List contents = _Store.GetContents(_Store.RootFolder.UUID); - foreach (InventoryBase inv in contents) + foreach (FolderData folder in _InventorySkeleton.Folders) { - if (inv is InventoryFolder) - { - InventoryFolder folder = inv as InventoryFolder; - - if (folder.PreferredType == type) - return folder.UUID; - } + if (folder.PreferredType == type) + return folder.UUID; } // No match found, return Root Folder ID - return _Store.RootFolder.UUID; + return _InventorySkeleton.RootUUID; } /// @@ -1105,10 +1160,10 @@ namespace OpenMetaverse /// The folder to begin the search in /// The object owners /// A string path to search - /// milliseconds to wait for a reply + /// Time to wait for a reply /// Found items or if /// timeout occurs or item is not found - public UUID FindObjectByPath(UUID baseFolder, UUID inventoryOwner, string path, int timeoutMS) + public UUID FindObjectByPath(UUID baseFolder, UUID inventoryOwner, string path, TimeSpan timeout) { AutoResetEvent findEvent = new AutoResetEvent(false); UUID foundItem = UUID.Zero; @@ -1123,12 +1178,8 @@ namespace OpenMetaverse } }; - OnFindObjectByPath += callback; - - RequestFindObjectByPath(baseFolder, inventoryOwner, path); - findEvent.WaitOne(timeoutMS, false); - - OnFindObjectByPath -= callback; + RequestFindObjectByPath(baseFolder, inventoryOwner, path, callback); + findEvent.WaitOne(timeout, false); return foundItem; } @@ -1139,191 +1190,166 @@ namespace OpenMetaverse /// The folder to begin the search in /// The object owners /// A string path to search, folders/objects separated by a '/' - /// Results are sent to the event - public void RequestFindObjectByPath(UUID baseFolder, UUID inventoryOwner, string path) + /// The callback to fire when the path has been found. + public void RequestFindObjectByPath(UUID baseFolder, UUID inventoryOwner, string path, FindObjectByPathCallback callback) { if (path == null || path.Length == 0) throw new ArgumentException("Empty path is not supported"); - - // Store this search - InventorySearch search; - search.Folder = baseFolder; - search.Owner = inventoryOwner; - search.Path = path.Split('/'); - search.Level = 0; - lock (_Searches) _Searches.Add(search); - - // Start the search - RequestFolderContents(baseFolder, inventoryOwner, true, true, InventorySortOrder.ByName); + string[] pathArray = path.Split('/'); + RequestFindObjectByPath(baseFolder, inventoryOwner, pathArray, callback); } /// - /// Search inventory Store object for an item or folder + /// Find inventory items by path. /// - /// The folder to begin the search in - /// An array which creates a path to search - /// Number of levels below baseFolder to conduct searches - /// if True, will stop searching after first match is found - /// A list of inventory items found - public List LocalFind(UUID baseFolder, string[] path, int level, bool firstOnly) + /// The folder to begin the search in. + /// The object owner's + /// A string array representing a path already split into individual folder names. + /// The callback to fire when the path has been found. + public void RequestFindObjectByPath(UUID baseFolder, UUID inventoryOwner, string[] pathArray, FindObjectByPathCallback callback) { - List objects = new List(); - //List folders = new List(); - List contents = _Store.GetContents(baseFolder); + // Create the RequestFolderContents callback: + FolderContentsCallback contentsCallback = ConstructFindContentsHandler(Helpers.Implode(pathArray, "/"), pathArray, 0, callback); + // Start the search + RequestFolderContents(baseFolder, inventoryOwner, true, true, InventorySortOrder.ByName, contentsCallback); + } - foreach (InventoryBase inv in contents) - { - if (inv.Name.CompareTo(path[level]) == 0) + /// + /// This constructs the callback that RequestFindObjectByPath needs to call RequestFolderContents. + /// We need to put it in its own method because the callback will need to create another callback for + /// recursing into child folders. + /// Used for display purposes only. + /// + private FolderContentsCallback ConstructFindContentsHandler(string path, string[] pathArray, int level, FindObjectByPathCallback callback) + { + return new FolderContentsCallback( + delegate(UUID folder, List items, List folders) { - if (level == path.Length - 1) + foreach (FolderData folderData in folders) { - objects.Add(inv); - if (firstOnly) return objects; - } - else if (inv is InventoryFolder) - objects.AddRange(LocalFind(inv.UUID, path, level + 1, firstOnly)); - } - } + if (folderData.Name == pathArray[level]) + { + if (level == pathArray.Length - 1) + { + Logger.DebugLog("Finished path search of " + path, _Client); - return objects; + // This is the last node in the path, fire the callback and clean up + callback(path, folderData.UUID); + } + else + { + // Construct the callback that will be called to recurse into the child folder. + FolderContentsCallback contentsCallback = ConstructFindContentsHandler(path, pathArray, level + 1, callback); + RequestFolderContents(folderData.UUID, folderData.OwnerID, true, true, InventorySortOrder.ByName, contentsCallback); + } + } + } + foreach (ItemData item in items) + { + if (item.Name == pathArray[level]) + { + if (level == pathArray.Length - 1) + { + Logger.DebugLog("Finished path search of " + path, _Client); + + // This is the last node in the path, fire the callback and clean up + callback(path, item.UUID); + } + else + { + Logger.Log("Path search attempted to request the contents of an item.", Helpers.LogLevel.Warning, _Client); + callback(path, UUID.Zero); + } + } + } + }); } #endregion Find #region Move/Rename - - /// - /// Move an inventory item or folder to a new location - /// - /// The item or folder to move - /// The to move item or folder to - public void Move(InventoryBase item, InventoryFolder newParent) - { - if (item is InventoryFolder) - MoveFolder(item.UUID, newParent.UUID); - else - MoveItem(item.UUID, newParent.UUID); - } + /// - /// Move an inventory item or folder to a new location and change its name + /// Rename a folder. /// - /// The item or folder to move - /// The to move item or folder to - /// The name to change the item or folder to - public void Move(InventoryBase item, InventoryFolder newParent, string newName) + /// The folder's + /// The folder's parent + /// The new name of the folder. + public void RenameFolder(UUID folderID, UUID parentID, string newName) { - if (item is InventoryFolder) - MoveFolder(item.UUID, newParent.UUID, newName); - else - MoveItem(item.UUID, newParent.UUID, newName); - } - - /// - /// Move and rename a folder - /// - /// The source folders - /// The destination folders - /// The name to change the folder to - public void MoveFolder(UUID folderID, UUID newparentID, string newName) - { - lock (Store) - { - if (_Store.Contains(folderID)) - { - InventoryBase inv = Store[folderID]; - inv.Name = newName; - _Store.UpdateNodeFor(inv); - } - } - UpdateInventoryFolderPacket move = new UpdateInventoryFolderPacket(); - move.AgentData.AgentID = _Client.Self.AgentID; - move.AgentData.SessionID = _Client.Self.SessionID; + move.AgentData.AgentID = _Agents.AgentID; + move.AgentData.SessionID = _Agents.SessionID; move.FolderData = new UpdateInventoryFolderPacket.FolderDataBlock[1]; move.FolderData[0] = new UpdateInventoryFolderPacket.FolderDataBlock(); move.FolderData[0].FolderID = folderID; - move.FolderData[0].ParentID = newparentID; + move.FolderData[0].ParentID = parentID; move.FolderData[0].Name = Helpers.StringToField(newName); move.FolderData[0].Type = -1; - _Client.Network.SendPacket(move); + _Network.SendPacket(move); } /// /// Move a folder /// - /// The source folders - /// The destination folders + /// The source folder's + /// The destination folder's public void MoveFolder(UUID folderID, UUID newParentID) { - lock (Store) - { - if (_Store.Contains(folderID)) - { - InventoryBase inv = Store[folderID]; - inv.ParentUUID = newParentID; - _Store.UpdateNodeFor(inv); - } - } - - MoveInventoryFolderPacket move = new MoveInventoryFolderPacket(); - move.AgentData.AgentID = _Client.Self.AgentID; - move.AgentData.SessionID = _Client.Self.SessionID; - move.AgentData.Stamp = false; //FIXME: ?? - - move.InventoryData = new MoveInventoryFolderPacket.InventoryDataBlock[1]; - move.InventoryData[0] = new MoveInventoryFolderPacket.InventoryDataBlock(); - move.InventoryData[0].FolderID = folderID; - move.InventoryData[0].ParentID = newParentID; - - _Client.Network.SendPacket(move); + MoveFolders(new UUID[] { folderID }, newParentID); } - + /// - /// Move multiple folders, the keys in the Dictionary parameter, - /// to a new parents, the value of that folder's key. + /// Move multiple folders. /// - /// A Dictionary containing the - /// of the source as the key, and the - /// of the destination as the value - public void MoveFolders(Dictionary foldersNewParents) + /// The parent to move the folders to. + /// The folders to move. + public void MoveFolders(ICollection folders, UUID newParent) { - // FIXME: Use two List to stay consistent - - lock (Store) - { - foreach (KeyValuePair entry in foldersNewParents) - { - if (_Store.Contains(entry.Key)) - { - InventoryBase inv = _Store[entry.Key]; - inv.ParentUUID = entry.Value; - _Store.UpdateNodeFor(inv); - } - } - } - //TODO: Test if this truly supports multiple-folder move MoveInventoryFolderPacket move = new MoveInventoryFolderPacket(); - move.AgentData.AgentID = _Client.Self.AgentID; - move.AgentData.SessionID = _Client.Self.SessionID; + move.AgentData.AgentID = _Agents.AgentID; + move.AgentData.SessionID = _Agents.SessionID; move.AgentData.Stamp = false; //FIXME: ?? - move.InventoryData = new MoveInventoryFolderPacket.InventoryDataBlock[foldersNewParents.Count]; + move.InventoryData = new MoveInventoryFolderPacket.InventoryDataBlock[folders.Count]; - int index = 0; - foreach (KeyValuePair folder in foldersNewParents) + int i = 0; + foreach (UUID folder in folders) { MoveInventoryFolderPacket.InventoryDataBlock block = new MoveInventoryFolderPacket.InventoryDataBlock(); - block.FolderID = folder.Key; - block.ParentID = folder.Value; - move.InventoryData[index++] = block; + block.FolderID = folder; + block.ParentID = newParent; + move.InventoryData[i] = block; + ++i; } - _Client.Network.SendPacket(move); + _Network.SendPacket(move); } + /// + /// Rename an inventory item + /// + /// The of the source item to move + /// The of the item's parent. + /// The name to change the folder to + public void RenameItem(UUID itemID, UUID parentID, string newName) + { + MoveInventoryItemPacket move = new MoveInventoryItemPacket(); + move.AgentData.AgentID = _Agents.AgentID; + move.AgentData.SessionID = _Agents.SessionID; + move.AgentData.Stamp = false; //FIXME: ?? + + move.InventoryData = new MoveInventoryItemPacket.InventoryDataBlock[1]; + move.InventoryData[0] = new MoveInventoryItemPacket.InventoryDataBlock(); + move.InventoryData[0].ItemID = itemID; + move.InventoryData[0].FolderID = parentID; + move.InventoryData[0].NewName = Helpers.StringToField(newName); + + _Network.SendPacket(move); + } /// /// Move an inventory item to a new folder @@ -1332,80 +1358,35 @@ namespace OpenMetaverse /// The of the destination folder public void MoveItem(UUID itemID, UUID folderID) { - MoveItem(itemID, folderID, String.Empty); + MoveItems(new UUID[] { itemID }, folderID); } /// - /// Move and rename an inventory item + /// Move multiple inventory items. /// - /// The of the source item to move - /// The of the destination folder - /// The name to change the folder to - public void MoveItem(UUID itemID, UUID folderID, string newName) + /// The of the new parent. + /// The s of the items. + public void MoveItems(ICollection items, UUID newParentID) { - lock (_Store) - { - if (_Store.Contains(itemID)) - { - InventoryBase inv = _Store[itemID]; - inv.ParentUUID = folderID; - _Store.UpdateNodeFor(inv); - } - } - MoveInventoryItemPacket move = new MoveInventoryItemPacket(); - move.AgentData.AgentID = _Client.Self.AgentID; - move.AgentData.SessionID = _Client.Self.SessionID; + move.AgentData.AgentID = _Agents.AgentID; + move.AgentData.SessionID = _Agents.SessionID; move.AgentData.Stamp = false; //FIXME: ?? - move.InventoryData = new MoveInventoryItemPacket.InventoryDataBlock[1]; - move.InventoryData[0] = new MoveInventoryItemPacket.InventoryDataBlock(); - move.InventoryData[0].ItemID = itemID; - move.InventoryData[0].FolderID = folderID; - move.InventoryData[0].NewName = Helpers.StringToField(newName); + move.InventoryData = new MoveInventoryItemPacket.InventoryDataBlock[items.Count]; - _Client.Network.SendPacket(move); - } - - /// - /// Move multiple inventory items to new locations - /// - /// A Dictionary containing the - /// of the source item as the key, and the - /// of the destination folder as the value - public void MoveItems(Dictionary itemsNewParents) - { - lock (_Store) - { - foreach (KeyValuePair entry in itemsNewParents) - { - if (_Store.Contains(entry.Key)) - { - InventoryBase inv = _Store[entry.Key]; - inv.ParentUUID = entry.Value; - _Store.UpdateNodeFor(inv); - } - } - } - - MoveInventoryItemPacket move = new MoveInventoryItemPacket(); - move.AgentData.AgentID = _Client.Self.AgentID; - move.AgentData.SessionID = _Client.Self.SessionID; - move.AgentData.Stamp = false; //FIXME: ?? - - move.InventoryData = new MoveInventoryItemPacket.InventoryDataBlock[itemsNewParents.Count]; - - int index = 0; - foreach (KeyValuePair entry in itemsNewParents) + int i = 0; + foreach (UUID item in items) { MoveInventoryItemPacket.InventoryDataBlock block = new MoveInventoryItemPacket.InventoryDataBlock(); - block.ItemID = entry.Key; - block.FolderID = entry.Value; + block.ItemID = item; + block.FolderID = newParentID; block.NewName = new byte[0]; - move.InventoryData[index++] = block; + move.InventoryData[i] = block; + ++i; } - _Client.Network.SendPacket(move); + _Network.SendPacket(move); } #endregion Move @@ -1419,23 +1400,10 @@ namespace OpenMetaverse public void RemoveDescendants(UUID folder) { PurgeInventoryDescendentsPacket purge = new PurgeInventoryDescendentsPacket(); - purge.AgentData.AgentID = _Client.Self.AgentID; - purge.AgentData.SessionID = _Client.Self.SessionID; + purge.AgentData.AgentID = _Agents.AgentID; + purge.AgentData.SessionID = _Agents.SessionID; purge.InventoryData.FolderID = folder; - _Client.Network.SendPacket(purge); - - // Update our local copy - lock (_Store) - { - if (_Store.Contains(folder)) - { - List contents = _Store.GetContents(folder); - foreach (InventoryBase obj in contents) - { - _Store.RemoveNodeFor(obj); - } - } - } + _Network.SendPacket(purge); } /// @@ -1467,14 +1435,14 @@ namespace OpenMetaverse /// /// A List containing the s of items to remove /// A List containing the s of the folders to remove - public void Remove(List items, List folders) + public void Remove(ICollection items, ICollection folders) { if ((items == null || items.Count == 0) && (folders == null || folders.Count == 0)) return; RemoveInventoryObjectsPacket rem = new RemoveInventoryObjectsPacket(); - rem.AgentData.AgentID = _Client.Self.AgentID; - rem.AgentData.SessionID = _Client.Self.SessionID; + rem.AgentData.AgentID = _Agents.AgentID; + rem.AgentData.SessionID = _Agents.SessionID; if (items == null || items.Count == 0) { @@ -1485,18 +1453,13 @@ namespace OpenMetaverse } else { - lock (_Store) + rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[items.Count]; + int i = 0; + foreach (UUID item in items) { - rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[items.Count]; - for (int i = 0; i < items.Count; i++) - { - rem.ItemData[i] = new RemoveInventoryObjectsPacket.ItemDataBlock(); - rem.ItemData[i].ItemID = items[i]; - - // Update local copy - if (_Store.Contains(items[i])) - _Store.RemoveNodeFor(Store[items[i]]); - } + rem.ItemData[i] = new RemoveInventoryObjectsPacket.ItemDataBlock(); + rem.ItemData[i].ItemID = item; + ++i; } } @@ -1509,23 +1472,18 @@ namespace OpenMetaverse } else { - lock (_Store) + rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[folders.Count]; + int i = 0; + foreach (UUID folder in folders) { - rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[folders.Count]; - for (int i = 0; i < folders.Count; i++) - { - rem.FolderData[i] = new RemoveInventoryObjectsPacket.FolderDataBlock(); - rem.FolderData[i].FolderID = folders[i]; - - // Update local copy - if (_Store.Contains(folders[i])) - _Store.RemoveNodeFor(Store[folders[i]]); - } + rem.FolderData[i] = new RemoveInventoryObjectsPacket.FolderDataBlock(); + rem.FolderData[i].FolderID = folder; + ++i; } } - _Client.Network.SendPacket(rem); + _Network.SendPacket(rem); } - + /// /// Empty the Lost and Found folder /// @@ -1544,62 +1502,67 @@ namespace OpenMetaverse private void EmptySystemFolder(AssetType folderType) { - List items = _Store.GetContents(_Store.RootFolder); - - UUID folderKey = UUID.Zero; - foreach (InventoryBase item in items) - { - if ((item as InventoryFolder) != null) - { - InventoryFolder folder = item as InventoryFolder; - if (folder.PreferredType == folderType) - { - folderKey = folder.UUID; - break; - } - } - } - items = _Store.GetContents(folderKey); - List remItems = new List(); - List remFolders = new List(); - foreach (InventoryBase item in items) - { - if ((item as InventoryFolder) != null) - { - remFolders.Add(item.UUID); - } - else - { - remItems.Add(item.UUID); - } - } - Remove(remItems, remFolders); - } + RemoveDescendants(FindFolderForType(folderType)); + } #endregion Remove #region Create + + [Obsolete("Wearables must upload an Asset before being created.", false)] + public void RequestCreateItem(UUID parentFolder, string name, string description, AssetType type, + InventoryType invType, WearableType wearableType, PermissionMask nextOwnerMask, + ItemCreatedCallback callback) + { + RequestCreateItem(parentFolder, name, description, type, UUID.Zero, invType, wearableType, nextOwnerMask, callback); + } + /// - /// + /// Creates an inventory item without needing to upload an asset. + /// In most cases, this means the AssetID of the resulting item is UUID.Zero. + /// For gestures, the server automatically creates an asset and assigns it an ID. + /// This is the method the Second Life Client (as of v1.9) uses to create scripts and notecards + /// + /// of folder to put item in. + /// Name of new item. + /// Description of new item. + /// Asset type of item. + /// Inventory type of item. + /// Permissions for the next owner. + /// Callback to trigger when item is created. + public void RequestCreateItem(UUID parentFolder, string name, string description, AssetType type, + InventoryType invType, PermissionMask nextOwnerMask, ItemCreatedCallback callback) + { + // Even though WearableType 0 is Shape, in this context it is treated as NOT_WEARABLE + RequestCreateItem(parentFolder, name, description, type, UUID.Zero, invType, (WearableType)0, nextOwnerMask, + callback); + } + + /// + /// Creates an inventory item referenceing an asset upload. This associates + /// the item with an asset. The resulting ItemData will have the AssetUUID + /// of the uploaded asset if the upload completed successfully. + /// This is the method that the Second Life Client (as of v1.9) uses to create gestures. /// /// Proper use is to upload the inventory's asset first, then provide the Asset's TransactionID here. public void RequestCreateItem(UUID parentFolder, string name, string description, AssetType type, UUID assetTransactionID, InventoryType invType, PermissionMask nextOwnerMask, ItemCreatedCallback callback) { // Even though WearableType 0 is Shape, in this context it is treated as NOT_WEARABLE - RequestCreateItem(parentFolder, name, description, type, assetTransactionID, invType, (WearableType)0, nextOwnerMask, + RequestCreateItem(parentFolder, name, description, type, assetTransactionID, invType, (WearableType)0, nextOwnerMask, callback); } /// - /// + /// Creates a wearable inventory item referencing an asset upload. + /// Second Life v1.9 uses this method to create wearable inventory items. /// /// Proper use is to upload the inventory's asset first, then provide the Asset's TransactionID here. public void RequestCreateItem(UUID parentFolder, string name, string description, AssetType type, UUID assetTransactionID, InventoryType invType, WearableType wearableType, PermissionMask nextOwnerMask, ItemCreatedCallback callback) { CreateInventoryItemPacket create = new CreateInventoryItemPacket(); - create.AgentData.AgentID = _Client.Self.AgentID; - create.AgentData.SessionID = _Client.Self.SessionID; + create.AgentData.AgentID = _Agents.AgentID; + create.AgentData.SessionID = _Agents.SessionID; create.InventoryBlock.CallbackID = RegisterItemCreatedCallback(callback); create.InventoryBlock.FolderID = parentFolder; @@ -1611,7 +1574,7 @@ namespace OpenMetaverse create.InventoryBlock.Name = Helpers.StringToField(name); create.InventoryBlock.Description = Helpers.StringToField(description); - _Client.Network.SendPacket(create); + _Network.SendPacket(create); } /// @@ -1655,30 +1618,17 @@ namespace OpenMetaverse } } - // Create the new folder locally - InventoryFolder newFolder = new InventoryFolder(id); - newFolder.Version = 1; - newFolder.DescendentCount = 0; - newFolder.ParentUUID = parentID; - newFolder.PreferredType = preferredType; - newFolder.Name = name; - newFolder.OwnerID = _Client.Self.AgentID; - - // Update the local store - try { _Store[newFolder.UUID] = newFolder; } - catch (InventoryException ie) { Logger.Log(ie.Message, Helpers.LogLevel.Warning, _Client, ie); } - // Create the create folder packet and send it CreateInventoryFolderPacket create = new CreateInventoryFolderPacket(); - create.AgentData.AgentID = _Client.Self.AgentID; - create.AgentData.SessionID = _Client.Self.SessionID; + create.AgentData.AgentID = _Agents.AgentID; + create.AgentData.SessionID = _Agents.SessionID; create.FolderData.FolderID = id; create.FolderData.ParentID = parentID; create.FolderData.Type = (sbyte)preferredType; create.FolderData.Name = Helpers.StringToField(name); - _Client.Network.SendPacket(create); + _Network.SendPacket(create); return id; } @@ -1686,10 +1636,10 @@ namespace OpenMetaverse public void RequestCreateItemFromAsset(byte[] data, string name, string description, AssetType assetType, InventoryType invType, UUID folderID, CapsClient.ProgressCallback progCallback, ItemCreatedFromAssetCallback callback) { - if (_Client.Network.CurrentSim == null || _Client.Network.CurrentSim.Caps == null) + if (_Network.CurrentSim == null || _Network.CurrentSim.Caps == null) throw new Exception("NewFileAgentInventory capability is not currently available"); - Uri url = _Client.Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory"); + Uri url = _Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory"); if (url != null) { @@ -1717,6 +1667,29 @@ namespace OpenMetaverse #region Copy + public bool CopyItem(UUID itemUUID, UUID newParent, string newName, TimeSpan timeout, out ItemData copy) + { + ManualResetEvent mre = new ManualResetEvent(false); + ItemData _copy = new ItemData(); + ItemCopiedCallback callback = + delegate(ItemData item) + { + _copy = item; + mre.Set(); + }; + RequestCopyItem(itemUUID, newParent, newName, callback); + if (mre.WaitOne(timeout, false)) + { + copy = _copy; + return true; + } + else + { + copy = _copy; + return false; + } + } + /// /// /// @@ -1726,7 +1699,7 @@ namespace OpenMetaverse /// public void RequestCopyItem(UUID item, UUID newParent, string newName, ItemCopiedCallback callback) { - RequestCopyItem(item, newParent, newName, _Client.Self.AgentID, callback); + RequestCopyItem(item, newParent, newName, _Agents.AgentID, callback); } /// @@ -1760,7 +1733,7 @@ namespace OpenMetaverse /// /// /// - public void RequestCopyItems(List items, List targetFolders, List newNames, + public void RequestCopyItems(IList items, IList targetFolders, IList newNames, UUID oldOwnerID, ItemCopiedCallback callback) { if (items.Count != targetFolders.Count || (newNames != null && items.Count != newNames.Count)) @@ -1769,8 +1742,8 @@ namespace OpenMetaverse uint callbackID = RegisterItemsCopiedCallback(callback); CopyInventoryItemPacket copy = new CopyInventoryItemPacket(); - copy.AgentData.AgentID = _Client.Self.AgentID; - copy.AgentData.SessionID = _Client.Self.SessionID; + copy.AgentData.AgentID = _Agents.AgentID; + copy.AgentData.SessionID = _Agents.SessionID; copy.InventoryData = new CopyInventoryItemPacket.InventoryDataBlock[items.Count]; for (int i = 0; i < items.Count; ++i) @@ -1787,7 +1760,7 @@ namespace OpenMetaverse copy.InventoryData[i].NewName = new byte[0]; } - _Client.Network.SendPacket(copy); + _Network.SendPacket(copy); } /// @@ -1800,8 +1773,8 @@ namespace OpenMetaverse public void RequestCopyItemFromNotecard(UUID objectID, UUID notecardID, UUID folderID, UUID itemID) { CopyInventoryFromNotecardPacket copy = new CopyInventoryFromNotecardPacket(); - copy.AgentData.AgentID = _Client.Self.AgentID; - copy.AgentData.SessionID = _Client.Self.SessionID; + copy.AgentData.AgentID = _Agents.AgentID; + copy.AgentData.SessionID = _Agents.SessionID; copy.NotecardData.ObjectID = objectID; copy.NotecardData.NotecardItemID = notecardID; @@ -1811,7 +1784,7 @@ namespace OpenMetaverse copy.InventoryData[0].FolderID = folderID; copy.InventoryData[0].ItemID = itemID; - _Client.Network.SendPacket(copy); + _Network.SendPacket(copy); } #endregion Copy @@ -1822,19 +1795,16 @@ namespace OpenMetaverse /// /// /// - public void RequestUpdateItem(InventoryItem item) + public void RequestUpdateItem(ItemData parameters) { - List items = new List(1); - items.Add(item); - - RequestUpdateItems(items, UUID.Random()); + RequestUpdateItems(new ItemData[] { parameters }, UUID.Random()); } /// /// /// /// - public void RequestUpdateItems(List items) + public void RequestUpdateItems(ICollection items) { RequestUpdateItems(items, UUID.Random()); } @@ -1844,18 +1814,17 @@ namespace OpenMetaverse /// /// /// - public void RequestUpdateItems(List items, UUID transactionID) + public void RequestUpdateItems(ICollection items, UUID transactionID) { UpdateInventoryItemPacket update = new UpdateInventoryItemPacket(); - update.AgentData.AgentID = _Client.Self.AgentID; - update.AgentData.SessionID = _Client.Self.SessionID; + update.AgentData.AgentID = _Agents.AgentID; + update.AgentData.SessionID = _Agents.SessionID; update.AgentData.TransactionID = transactionID; update.InventoryData = new UpdateInventoryItemPacket.InventoryDataBlock[items.Count]; - for (int i = 0; i < items.Count; i++) + int index = 0; + foreach (ItemData item in items) { - InventoryItem item = items[i]; - UpdateInventoryItemPacket.InventoryDataBlock block = new UpdateInventoryItemPacket.InventoryDataBlock(); block.BaseMask = (uint)item.Permissions.BaseMask; block.CRC = ItemCRC(item); @@ -1879,10 +1848,11 @@ namespace OpenMetaverse block.TransactionID = UUID.Zero; block.Type = (sbyte)item.AssetType; - update.InventoryData[i] = block; + update.InventoryData[index] = block; + ++index; } - _Client.Network.SendPacket(update); + _Network.SendPacket(update); } /// @@ -1893,10 +1863,10 @@ namespace OpenMetaverse /// public void RequestUploadNotecardAsset(byte[] data, UUID notecardID, NotecardUploadedAssetCallback callback) { - if (_Client.Network.CurrentSim == null || _Client.Network.CurrentSim.Caps == null) + if (_Network.CurrentSim == null || _Network.CurrentSim.Caps == null) throw new Exception("UpdateNotecardAgentInventory capability is not currently available"); - Uri url = _Client.Network.CurrentSim.Caps.CapabilityURI("UpdateNotecardAgentInventory"); + Uri url = _Network.CurrentSim.Caps.CapabilityURI("UpdateNotecardAgentInventory"); if (url != null) { @@ -1928,9 +1898,9 @@ namespace OpenMetaverse /// Vector of where to place object /// InventoryObject object containing item details public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, - InventoryObject item) + ItemData item) { - return RequestRezFromInventory(simulator, rotation, position, item, _Client.Self.ActiveGroup, + return RequestRezFromInventory(simulator, rotation, position, item, _Agents.ActiveGroup, UUID.Random(), false); } @@ -1943,7 +1913,7 @@ namespace OpenMetaverse /// InventoryObject object containing item details /// UUID of group to own the object public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, - InventoryObject item, UUID groupOwner) + ItemData item, UUID groupOwner) { return RequestRezFromInventory(simulator, rotation, position, item, groupOwner, UUID.Random(), false); } @@ -1960,12 +1930,12 @@ namespace OpenMetaverse /// if set to true the simulator /// will automatically send object detail packet(s) back to the client public UUID RequestRezFromInventory(Simulator simulator, Quaternion rotation, Vector3 position, - InventoryObject item, UUID groupOwner, UUID queryID, bool requestObjectDetails) + ItemData item, UUID groupOwner, UUID queryID, bool requestObjectDetails) { RezObjectPacket add = new RezObjectPacket(); - add.AgentData.AgentID = _Client.Self.AgentID; - add.AgentData.SessionID = _Client.Self.SessionID; + add.AgentData.AgentID = _Agents.AgentID; + add.AgentData.SessionID = _Agents.SessionID; add.AgentData.GroupID = groupOwner; add.RezData.FromTaskID = UUID.Zero; @@ -2002,7 +1972,7 @@ namespace OpenMetaverse add.InventoryData.Description = Helpers.StringToField(item.Description); add.InventoryData.CreationDate = (int)Helpers.DateTimeToUnixTime(item.CreationDate); - _Client.Network.SendPacket(add, simulator); + _Network.SendPacket(add, simulator); return queryID; } @@ -2013,7 +1983,7 @@ namespace OpenMetaverse /// The simulator Local ID of the object public void RequestDeRezToInventory(uint objectLocalID) { - RequestDeRezToInventory(objectLocalID, DeRezDestination.ObjectsFolder, + RequestDeRezToInventory(objectLocalID, DeRezDestination.ObjectsFolder, _Client.Inventory.FindFolderForType(AssetType.Object), UUID.Random()); } @@ -2030,8 +2000,8 @@ namespace OpenMetaverse { DeRezObjectPacket take = new DeRezObjectPacket(); - take.AgentData.AgentID = _Client.Self.AgentID; - take.AgentData.SessionID = _Client.Self.SessionID; + take.AgentData.AgentID = _Agents.AgentID; + take.AgentData.SessionID = _Agents.SessionID; take.AgentBlock = new DeRezObjectPacket.AgentBlockBlock(); take.AgentBlock.GroupID = UUID.Zero; take.AgentBlock.Destination = (byte)destType; @@ -2043,8 +2013,8 @@ namespace OpenMetaverse take.ObjectData = new DeRezObjectPacket.ObjectDataBlock[1]; take.ObjectData[0] = new DeRezObjectPacket.ObjectDataBlock(); take.ObjectData[0].ObjectLocalID = objectLocalID; - - _Client.Network.SendPacket(take); + + _Network.SendPacket(take); } @@ -2062,30 +2032,34 @@ namespace OpenMetaverse byte[] bucket; - bucket = new byte[17]; - bucket[0] = (byte)assetType; - Buffer.BlockCopy(itemID.GetBytes(), 0, bucket, 1, 16); + bucket = new byte[17]; + bucket[0] = (byte)assetType; + Buffer.BlockCopy(itemID.GetBytes(), 0, bucket, 1, 16); - _Client.Self.InstantMessage( - _Client.Self.Name, + _Agents.InstantMessage( + _Agents.Name, recipient, itemName, UUID.Random(), InstantMessageDialog.InventoryOffered, InstantMessageOnline.Online, - _Client.Self.SimPosition, - _Client.Network.CurrentSim.ID, + _Agents.SimPosition, + _Network.CurrentSim.ID, bucket); if (doEffect) { - _Client.Self.BeamEffect(_Client.Self.AgentID, recipient, Vector3d.Zero, + _Agents.BeamEffect(_Agents.AgentID, recipient, Vector3d.Zero, _Client.Settings.DEFAULT_EFFECT_COLOR, 1f, UUID.Random()); } } /// /// Give an inventory Folder with contents to another avatar + /// This calls the synchronous method which blocks until + /// the folder's contents are retrieved, so it might take a while to return. + /// For an alternative, specify the folder's contents explicitly using the other + /// method. /// /// The of the Folder to give /// The name of the folder @@ -2094,43 +2068,52 @@ namespace OpenMetaverse /// true to generate a beameffect during transfer public void GiveFolder(UUID folderID, string folderName, AssetType assetType, UUID recipient, bool doEffect) + { + List folderContents; + List placeholder; + + FolderContents(folderID, _Agents.AgentID, false, true, InventorySortOrder.ByDate, + TimeSpan.FromMilliseconds(1000 * 15), + out folderContents, out placeholder); + + GiveFolder(folderID, folderName, assetType, recipient, doEffect, folderContents); + } + + + public void GiveFolder(UUID folderID, string folderName, AssetType assetType, UUID recipient, + bool doEffect, ICollection folderContents) { byte[] bucket; - List folderContents = new List(); - _Client.Inventory.FolderContents(folderID, _Client.Self.AgentID, false, true, InventorySortOrder.ByDate, 1000 * 15).ForEach( - delegate(InventoryBase ib) - { - folderContents.Add(_Client.Inventory.FetchItem(ib.UUID, _Client.Self.AgentID, 1000 * 10)); - }); - bucket = new byte[17 * (folderContents.Count + 1)]; + bucket = new byte[17 * (folderContents.Count + 1)]; + //Add parent folder (first item in bucket) + bucket[0] = (byte)assetType; + Buffer.BlockCopy(folderID.GetBytes(), 0, bucket, 1, 16); - //Add parent folder (first item in bucket) - bucket[0] = (byte)assetType; - Buffer.BlockCopy(folderID.GetBytes(), 0, bucket, 1, 16); + //Add contents to bucket after folder + int index = 1; + foreach (ItemData item in folderContents) + { + bucket[index * 17] = (byte)item.AssetType; + Buffer.BlockCopy(item.UUID.GetBytes(), 0, bucket, index * 17 + 1, 16); + ++index; + } - //Add contents to bucket after folder - for (int i = 1; i <= folderContents.Count; ++i) - { - bucket[i * 17] = (byte)folderContents[i - 1].AssetType; - Buffer.BlockCopy(folderContents[i - 1].UUID.GetBytes(), 0, bucket, i * 17 + 1, 16); - } - - _Client.Self.InstantMessage( - _Client.Self.Name, + _Agents.InstantMessage( + _Agents.Name, recipient, folderName, UUID.Random(), InstantMessageDialog.InventoryOffered, InstantMessageOnline.Online, - _Client.Self.SimPosition, - _Client.Network.CurrentSim.ID, + _Agents.SimPosition, + _Network.CurrentSim.ID, bucket); if (doEffect) { - _Client.Self.BeamEffect(_Client.Self.AgentID, recipient, Vector3d.Zero, + _Agents.BeamEffect(_Agents.AgentID, recipient, Vector3d.Zero, _Client.Settings.DEFAULT_EFFECT_COLOR, 1f, UUID.Random()); } } @@ -2145,13 +2128,13 @@ namespace OpenMetaverse /// /// /// - public UUID UpdateTaskInventory(uint objectLocalID, InventoryItem item) + public UUID UpdateTaskInventory(uint objectLocalID, ItemData item) { UUID transactionID = UUID.Random(); UpdateTaskInventoryPacket update = new UpdateTaskInventoryPacket(); - update.AgentData.AgentID = _Client.Self.AgentID; - update.AgentData.SessionID = _Client.Self.SessionID; + update.AgentData.AgentID = _Agents.AgentID; + update.AgentData.SessionID = _Agents.SessionID; update.UpdateData.Key = 0; update.UpdateData.LocalID = objectLocalID; @@ -2177,7 +2160,7 @@ namespace OpenMetaverse update.InventoryData.CreationDate = (int)Helpers.DateTimeToUnixTime(item.CreationDate); update.InventoryData.CRC = ItemCRC(item); - _Client.Network.SendPacket(update); + _Network.SendPacket(update); return transactionID; } @@ -2187,9 +2170,9 @@ namespace OpenMetaverse /// /// The tasks /// The tasks simulator local ID - /// milliseconds to wait for reply from simulator + /// Time to wait for reply from simulator /// A List containing the inventory items inside the task - public List GetTaskInventory(UUID objectID, uint objectLocalID, int timeoutMS) + public void GetTaskInventory(UUID objectID, uint objectLocalID, TimeSpan timeout, out List items, out List folders) { string filename = null; AutoResetEvent taskReplyEvent = new AutoResetEvent(false); @@ -2208,7 +2191,7 @@ namespace OpenMetaverse RequestTaskInventory(objectLocalID); - if (taskReplyEvent.WaitOne(timeoutMS, false)) + if (taskReplyEvent.WaitOne(timeout, false)) { OnTaskInventoryReply -= callback; @@ -2233,31 +2216,38 @@ namespace OpenMetaverse // Start the actual asset xfer xferID = _Client.Assets.RequestAssetXfer(filename, true, false, UUID.Zero, AssetType.Unknown); - if (taskDownloadEvent.WaitOne(timeoutMS, false)) + if (taskDownloadEvent.WaitOne(timeout, false)) { _Client.Assets.OnXferReceived -= xferCallback; string taskList = Helpers.FieldToUTF8String(assetData); - return ParseTaskInventory(taskList); + ParseTaskInventory(this, taskList, out items, out folders); + return; } else { Logger.Log("Timed out waiting for task inventory download for " + filename, Helpers.LogLevel.Warning, _Client); _Client.Assets.OnXferReceived -= xferCallback; - return null; + items = null; + folders = null; + return; } } else { Logger.DebugLog("Task is empty for " + objectLocalID, _Client); - return null; + items = null; + folders = null; + return; } } else { Logger.Log("Timed out waiting for task inventory reply for " + objectLocalID, Helpers.LogLevel.Warning, _Client); OnTaskInventoryReply -= callback; - return null; + items = null; + folders = null; + return; } } @@ -2267,7 +2257,7 @@ namespace OpenMetaverse /// public void RequestTaskInventory(uint objectLocalID) { - RequestTaskInventory(objectLocalID, _Client.Network.CurrentSim); + RequestTaskInventory(objectLocalID, _Network.CurrentSim); } /// @@ -2278,13 +2268,13 @@ namespace OpenMetaverse public void RequestTaskInventory(uint objectLocalID, Simulator simulator) { RequestTaskInventoryPacket request = new RequestTaskInventoryPacket(); - request.AgentData.AgentID = _Client.Self.AgentID; - request.AgentData.SessionID = _Client.Self.SessionID; + request.AgentData.AgentID = _Agents.AgentID; + request.AgentData.SessionID = _Agents.SessionID; request.InventoryData.LocalID = objectLocalID; - _Client.Network.SendPacket(request, simulator); + _Network.SendPacket(request, simulator); } - + /// /// Moves an Item from an objects (Prim) Inventory to the specified folder in the avatars inventory /// @@ -2295,17 +2285,17 @@ namespace OpenMetaverse public void MoveTaskInventory(uint objectLocalID, UUID taskItemID, UUID inventoryFolderID, Simulator simulator) { MoveTaskInventoryPacket request = new MoveTaskInventoryPacket(); - request.AgentData.AgentID = _Client.Self.AgentID; - request.AgentData.SessionID = _Client.Self.SessionID; + request.AgentData.AgentID = _Agents.AgentID; + request.AgentData.SessionID = _Agents.SessionID; request.AgentData.FolderID = inventoryFolderID; request.InventoryData.ItemID = taskItemID; request.InventoryData.LocalID = objectLocalID; - _Client.Network.SendPacket(request, simulator); + _Network.SendPacket(request, simulator); } - + /// /// Remove an item from an objects (Prim) Inventory /// @@ -2315,13 +2305,13 @@ namespace OpenMetaverse public void RemoveTaskInventory(uint objectLocalID, UUID taskItemID, Simulator simulator) { RemoveTaskInventoryPacket remove = new RemoveTaskInventoryPacket(); - remove.AgentData.AgentID = _Client.Self.AgentID; - remove.AgentData.SessionID = _Client.Self.SessionID; + remove.AgentData.AgentID = _Agents.AgentID; + remove.AgentData.SessionID = _Agents.SessionID; remove.InventoryData.ItemID = taskItemID; remove.InventoryData.LocalID = objectLocalID; - _Client.Network.SendPacket(remove, simulator); + _Network.SendPacket(remove, simulator); } #endregion Task @@ -2437,7 +2427,7 @@ namespace OpenMetaverse /// /// The source InventoryItem /// A uint representing the source InventoryItem as a CRC - public static uint ItemCRC(InventoryItem iitem) + public static uint ItemCRC(ItemData iitem) { uint CRC = 0; @@ -2468,46 +2458,6 @@ namespace OpenMetaverse return CRC; } - /// - /// Wrapper for creating a new object - /// - /// The type of item from the enum - /// The of the newly created object - /// An object with the type and id passed - public static InventoryItem CreateInventoryItem(InventoryType type, UUID id) - { - switch (type) - { - case InventoryType.Texture: return new InventoryTexture(id); - case InventoryType.Sound: return new InventorySound(id); - case InventoryType.CallingCard: return new InventoryCallingCard(id); - case InventoryType.Landmark: return new InventoryLandmark(id); - case InventoryType.Object: return new InventoryObject(id); - case InventoryType.Notecard: return new InventoryNotecard(id); - case InventoryType.Category: return new InventoryCategory(id); - case InventoryType.LSL: return new InventoryLSL(id); - case InventoryType.Snapshot: return new InventorySnapshot(id); - case InventoryType.Attachment: return new InventoryAttachment(id); - case InventoryType.Wearable: return new InventoryWearable(id); - case InventoryType.Animation: return new InventoryAnimation(id); - case InventoryType.Gesture: return new InventoryGesture(id); - default: return new InventoryItem(type, id); - } - } - - private InventoryItem SafeCreateInventoryItem(InventoryType InvType, UUID ItemID) - { - InventoryItem ret = null; - - if (_Store.Contains(ItemID)) - ret = _Store[ItemID] as InventoryItem; - - if (ret == null) - ret = CreateInventoryItem(InvType, ItemID); - - return ret; - } - private static bool ParseLine(string line, out string key, out string value) { string origLine = line; @@ -2548,9 +2498,10 @@ namespace OpenMetaverse /// /// A string which contains the data from the task reply /// A List containing the items contained within the tasks inventory - public static List ParseTaskInventory(string taskData) + public static void ParseTaskInventory(InventoryManager manager, string taskData, out List items, out List folders) { - List items = new List(); + items = new List(); + folders = new List(); int lineNum = 0; string[] lines = taskData.Replace("\r\n", "\n").Split('\n'); @@ -2602,20 +2553,19 @@ namespace OpenMetaverse if (assetType == AssetType.Folder) { - InventoryFolder folder = new InventoryFolder(itemID); - folder.Name = name; - folder.ParentUUID = parentID; + FolderData folderData = new FolderData(itemID); + folderData.Name = name; + folderData.ParentUUID = parentID; - items.Add(folder); + folders.Add(folderData); } else { - InventoryItem item = new InventoryItem(itemID); - item.Name = name; - item.ParentUUID = parentID; - item.AssetType = assetType; - - items.Add(item); + ItemData itemParams = new ItemData(itemID); + itemParams.Name = name; + itemParams.ParentUUID = parentID; + itemParams.AssetType = assetType; + items.Add(itemParams); } #endregion inv_object @@ -2810,8 +2760,7 @@ namespace OpenMetaverse } } } - - InventoryItem item = CreateInventoryItem(inventoryType, itemID); + ItemData item = new ItemData(itemID, inventoryType); item.AssetUUID = assetID; item.AssetType = assetType; item.CreationDate = creationDate; @@ -2826,7 +2775,6 @@ namespace OpenMetaverse item.Permissions = perms; item.SalePrice = salePrice; item.SaleType = saleType; - items.Add(item); #endregion inv_item @@ -2838,10 +2786,8 @@ namespace OpenMetaverse } } } - - return items; } - + #endregion Helper Functions #region Callbacks @@ -2853,6 +2799,8 @@ namespace OpenMetaverse ItemCreatedFromAssetCallback callback = (ItemCreatedFromAssetCallback)args[1]; byte[] itemData = (byte[])args[2]; + LLSDMap contents = (LLSDMap)result; + if (result == null) { try { callback(false, error.Message, UUID.Zero, UUID.Zero); } @@ -2860,8 +2808,6 @@ namespace OpenMetaverse return; } - LLSDMap contents = (LLSDMap)result; - string status = contents["state"].AsString().ToLower(); if (status == "upload") @@ -2880,7 +2826,7 @@ namespace OpenMetaverse } else if (status == "complete") { - Logger.DebugLog("CreateItemFromAsset: completed"); + Logger.DebugLog("CreateItemFromAsset: completed"); if (contents.ContainsKey("new_inventory_item") && contents.ContainsKey("new_asset")) { @@ -2904,43 +2850,48 @@ namespace OpenMetaverse private void SaveAssetIntoInventoryHandler(Packet packet, Simulator simulator) { SaveAssetIntoInventoryPacket save = (SaveAssetIntoInventoryPacket)packet; - - // FIXME: Find this item in the inventory structure and mark the parent as needing an update - //save.InventoryData.ItemID; - Logger.Log("SaveAssetIntoInventory packet received, someone write this function!", Helpers.LogLevel.Error, _Client); + if (OnAssetUpdate != null) + { + try { OnAssetUpdate(save.InventoryData.ItemID, save.InventoryData.NewAssetID); } + catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } + } } private void InventoryDescendentsHandler(Packet packet, Simulator simulator) { InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet; - + ItemData[] items = null; + FolderData[] folders = null; if (reply.AgentData.Descendents > 0) { // InventoryDescendantsReply sends a null folder if the parent doesnt contain any folders if (reply.FolderData[0].FolderID != UUID.Zero) { + folders = new FolderData[reply.FolderData.Length]; // Iterate folders in this packet for (int i = 0; i < reply.FolderData.Length; i++) { - InventoryFolder folder = new InventoryFolder(reply.FolderData[i].FolderID); + UUID folderID = reply.FolderData[i].FolderID; + FolderData folder = new FolderData(folderID); folder.ParentUUID = reply.FolderData[i].ParentID; folder.Name = Helpers.FieldToUTF8String(reply.FolderData[i].Name); folder.PreferredType = (AssetType)reply.FolderData[i].Type; folder.OwnerID = reply.AgentData.OwnerID; - - _Store[folder.UUID] = folder; + folders[i] = folder; } } // InventoryDescendantsReply sends a null item if the parent doesnt contain any items. if (reply.ItemData[0].ItemID != UUID.Zero) { + items = new ItemData[reply.ItemData.Length]; // Iterate items in this packet for (int i = 0; i < reply.ItemData.Length; i++) { if (reply.ItemData[i].ItemID != UUID.Zero) { - InventoryItem item; + UUID itemID = reply.ItemData[i].ItemID; + ItemData item = new ItemData(itemID); /* * Objects that have been attached in-world prior to being stored on the * asset server are stored with the InventoryType of 0 (Texture) @@ -2949,18 +2900,16 @@ namespace OpenMetaverse * This corrects that behavior by forcing Object Asset types that have an * invalid InventoryType with the proper InventoryType of Attachment. */ - if ((AssetType)reply.ItemData[i].Type == AssetType.Object + if ((AssetType)reply.ItemData[i].Type == AssetType.Object && (InventoryType)reply.ItemData[i].InvType == InventoryType.Texture) { - item = CreateInventoryItem(InventoryType.Attachment, reply.ItemData[i].ItemID); item.InventoryType = InventoryType.Attachment; } else { - item = CreateInventoryItem((InventoryType)reply.ItemData[i].InvType, reply.ItemData[i].ItemID); item.InventoryType = (InventoryType)reply.ItemData[i].InvType; } - + item.ParentUUID = reply.ItemData[i].FolderID; item.CreatorID = reply.ItemData[i].CreatorID; item.AssetType = (AssetType)reply.ItemData[i].Type; @@ -2980,102 +2929,50 @@ namespace OpenMetaverse item.SalePrice = reply.ItemData[i].SalePrice; item.SaleType = (SaleType)reply.ItemData[i].SaleType; item.OwnerID = reply.AgentData.OwnerID; - - _Store[item.UUID] = item; + items[i] = item; } } } } - InventoryFolder parentFolder = null; - - if (_Store.Contains(reply.AgentData.FolderID) && - _Store[reply.AgentData.FolderID] is InventoryFolder) + #region FolderContents Handling + if (_DescendentsRequests.Count > 0) { - parentFolder = _Store[reply.AgentData.FolderID] as InventoryFolder; - } - else - { - Logger.Log("Don't have a reference to FolderID " + reply.AgentData.FolderID.ToString() + - " or it is not a folder", Helpers.LogLevel.Error, _Client); - return; - } - - if (reply.AgentData.Version < parentFolder.Version) - { - Logger.Log("Got an outdated InventoryDescendents packet for folder " + parentFolder.Name + - ", this version = " + reply.AgentData.Version + ", latest version = " + parentFolder.Version, - Helpers.LogLevel.Warning, _Client); - return; - } - - parentFolder.Version = reply.AgentData.Version; - // FIXME: reply.AgentData.Descendants is not parentFolder.DescendentCount if we didn't - // request items and folders - parentFolder.DescendentCount = reply.AgentData.Descendents; - - #region FindObjectsByPath Handling - - if (_Searches.Count > 0) - { - lock (_Searches) + lock (_DescendentsRequests) { - StartSearch: - - // Iterate over all of the outstanding searches - for (int i = 0; i < _Searches.Count; i++) + // Iterate backwards, ensures safe removal: + for (int i = _DescendentsRequests.Count - 1; i >= 0; --i) { - InventorySearch search = _Searches[i]; - List folderContents = _Store.GetContents(search.Folder); - - // Iterate over all of the inventory objects in the base search folder - for (int j = 0; j < folderContents.Count; j++) + DescendentsRequest request = _DescendentsRequests[i]; + if (request.Folder == reply.AgentData.FolderID) { - // Check if this inventory object matches the current path node - if (folderContents[j].Name == search.Path[search.Level]) + // Store the descendent count if we haven't received a responce yet: + if (!request.ReceivedResponse) { - if (search.Level == search.Path.Length - 1) - { - Logger.DebugLog("Finished path search of " + String.Join("/", search.Path), _Client); + request.ReceivedResponse = true; + request.Descendents = reply.AgentData.Descendents; + } - // This is the last node in the path, fire the callback and clean up - if (OnFindObjectByPath != null) - { - try { OnFindObjectByPath(String.Join("/", search.Path), folderContents[j].UUID); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } - } + // Store the items and folders: + if (folders != null) + request.FolderContents.AddRange(folders); + if (items != null) + request.ItemContents.AddRange(items); - // Remove this entry and restart the loop since we are changing the collection size - _Searches.RemoveAt(i); - goto StartSearch; - } - else - { - // We found a match but it is not the end of the path, request the next level - Logger.DebugLog(String.Format("Matched level {0}/{1} in a path search of {2}", - search.Level, search.Path.Length - 1, String.Join("/", search.Path)), _Client); + _DescendentsRequests[i] = request; - search.Folder = folderContents[j].UUID; - search.Level++; - _Searches[i] = search; - - RequestFolderContents(search.Folder, search.Owner, true, true, - InventorySortOrder.ByName); - } + // Check if we're done: + if (request.FolderContents.Count + request.ItemContents.Count >= request.Descendents) + { + // Fire the callback: + request.Callback(reply.AgentData.FolderID, request.ItemContents, request.FolderContents); + _DescendentsRequests.RemoveAt(i); } } } } } - - #endregion FindObjectsByPath Handling - - // Callback for inventory folder contents being updated - if (OnFolderUpdated != null) - { - try { OnFolderUpdated(parentFolder.UUID); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } - } + #endregion FolderContents Handling } /// @@ -3097,7 +2994,7 @@ namespace OpenMetaverse continue; } - InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID); + ItemData item = new ItemData(dataBlock.ItemID, (InventoryType)dataBlock.InvType); item.AssetType = (AssetType)dataBlock.Type; item.AssetUUID = dataBlock.AssetID; item.CreationDate = Helpers.UnixTimeToDateTime(dataBlock.CreationDate); @@ -3118,9 +3015,6 @@ namespace OpenMetaverse item.SalePrice = dataBlock.SalePrice; item.SaleType = (SaleType)dataBlock.SaleType; - // Update the local copy - _Store[item.UUID] = item; - // Look for an "item created" callback ItemCreatedCallback createdCallback; if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out createdCallback)) @@ -3131,43 +3025,19 @@ namespace OpenMetaverse catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } - // TODO: Is this callback even triggered when items are copied? - // Look for an "item copied" callback - ItemCopiedCallback copyCallback; - if (_ItemCopiedCallbacks.TryGetValue(dataBlock.CallbackID, out copyCallback)) - { - _ItemCopiedCallbacks.Remove(dataBlock.CallbackID); - - try { copyCallback(item); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } - } - //This is triggered when an item is received from a task if (OnTaskItemReceived != null) { - try { OnTaskItemReceived(item.UUID, dataBlock.FolderID, item.CreatorID, item.AssetUUID, - item.InventoryType); } + try + { + OnTaskItemReceived(dataBlock.ItemID, dataBlock.FolderID, item.CreatorID, item.AssetUUID, + item.InventoryType); + } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } } } - private void MoveInventoryItemHandler(Packet packet, Simulator simulator) - { - MoveInventoryItemPacket move = (MoveInventoryItemPacket)packet; - - for (int i = 0; i < move.InventoryData.Length; i++) - { - // FIXME: Do something here - string newName = Helpers.FieldToUTF8String(move.InventoryData[i].NewName); - - Logger.Log(String.Format( - "MoveInventoryItemHandler: Item {0} is moving to Folder {1} with new name \"{2}\". Someone write this function!", - move.InventoryData[i].ItemID.ToString(), move.InventoryData[i].FolderID.ToString(), - newName), Helpers.LogLevel.Warning, _Client); - } - } - private void BulkUpdateInventoryHandler(Packet packet, Simulator simulator) { BulkUpdateInventoryPacket update = packet as BulkUpdateInventoryPacket; @@ -3176,14 +3046,16 @@ namespace OpenMetaverse { foreach (BulkUpdateInventoryPacket.FolderDataBlock dataBlock in update.FolderData) { - if (!_Store.Contains(dataBlock.FolderID)) - Logger.Log("Received BulkUpdate for unknown folder: " + dataBlock.FolderID, Helpers.LogLevel.Warning, _Client); + if (OnFolderUpdate != null) + { + FolderData folderParams = new FolderData(); + folderParams.Name = Helpers.FieldToUTF8String(dataBlock.Name); + folderParams.OwnerID = update.AgentData.AgentID; + folderParams.ParentUUID = dataBlock.ParentID; - InventoryFolder folder = new InventoryFolder(dataBlock.FolderID); - folder.Name = Helpers.FieldToUTF8String(dataBlock.Name); - folder.OwnerID = update.AgentData.AgentID; - folder.ParentUUID = dataBlock.ParentID; - _Store[folder.UUID] = folder; + try { OnFolderUpdate(folderParams); } + catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } + } } } @@ -3193,13 +3065,7 @@ namespace OpenMetaverse { BulkUpdateInventoryPacket.ItemDataBlock dataBlock = update.ItemData[i]; - // If we are given a folder of items, the item information might arrive before the folder - // (parent) is in the store - if (!_Store.Contains(dataBlock.ItemID)) - Logger.Log("Received BulkUpdate for unknown item: " + dataBlock.ItemID, Helpers.LogLevel.Warning, _Client); - - InventoryItem item = SafeCreateInventoryItem((InventoryType)dataBlock.InvType, dataBlock.ItemID); - + ItemData item = new ItemData(dataBlock.ItemID, (InventoryType)dataBlock.InvType); item.AssetType = (AssetType)dataBlock.Type; if (dataBlock.AssetID != UUID.Zero) item.AssetUUID = dataBlock.AssetID; item.CreationDate = Helpers.UnixTimeToDateTime(dataBlock.CreationDate); @@ -3220,8 +3086,6 @@ namespace OpenMetaverse item.SalePrice = dataBlock.SalePrice; item.SaleType = (SaleType)dataBlock.SaleType; - _Store[item.UUID] = item; - // Look for an "item created" callback ItemCreatedCallback callback; if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out callback)) @@ -3241,51 +3105,75 @@ namespace OpenMetaverse try { copyCallback(item); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } } + + if (OnItemUpdate != null) + { + try { OnItemUpdate(item); } + catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } + } } } } private void FetchInventoryReplyHandler(Packet packet, Simulator simulator) { - FetchInventoryReplyPacket reply = packet as FetchInventoryReplyPacket; - - foreach (FetchInventoryReplyPacket.InventoryDataBlock dataBlock in reply.InventoryData) + lock (_FetchRequests) { - if (dataBlock.InvType == (sbyte)InventoryType.Folder) + FetchInventoryReplyPacket reply = packet as FetchInventoryReplyPacket; + foreach (FetchInventoryReplyPacket.InventoryDataBlock dataBlock in reply.InventoryData) { - Logger.Log("Received FetchInventoryReply for an inventory folder, this should not happen!", - Helpers.LogLevel.Error, _Client); - continue; - } + if (dataBlock.InvType == (sbyte)InventoryType.Folder) + { + Logger.Log("Received FetchInventoryReply for an inventory folder, this should not happen!", + Helpers.LogLevel.Error, _Client); + continue; + } - InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID); - item.AssetType = (AssetType)dataBlock.Type; - item.AssetUUID = dataBlock.AssetID; - item.CreationDate = Helpers.UnixTimeToDateTime(dataBlock.CreationDate); - item.CreatorID = dataBlock.CreatorID; - item.Description = Helpers.FieldToUTF8String(dataBlock.Description); - item.Flags = dataBlock.Flags; - item.GroupID = dataBlock.GroupID; - item.GroupOwned = dataBlock.GroupOwned; - item.Name = Helpers.FieldToUTF8String(dataBlock.Name); - item.OwnerID = dataBlock.OwnerID; - item.ParentUUID = dataBlock.FolderID; - item.Permissions = new Permissions( - dataBlock.BaseMask, - dataBlock.EveryoneMask, - dataBlock.GroupMask, - dataBlock.NextOwnerMask, - dataBlock.OwnerMask); - item.SalePrice = dataBlock.SalePrice; - item.SaleType = (SaleType)dataBlock.SaleType; + ItemData item = new ItemData(dataBlock.ItemID); + item.InventoryType = (InventoryType)dataBlock.InvType; + item.AssetType = (AssetType)dataBlock.Type; + item.AssetUUID = dataBlock.AssetID; + item.CreationDate = Helpers.UnixTimeToDateTime(dataBlock.CreationDate); + item.CreatorID = dataBlock.CreatorID; + item.Description = Helpers.FieldToUTF8String(dataBlock.Description); + item.Flags = dataBlock.Flags; + item.GroupID = dataBlock.GroupID; + item.GroupOwned = dataBlock.GroupOwned; + item.Name = Helpers.FieldToUTF8String(dataBlock.Name); + item.OwnerID = dataBlock.OwnerID; + item.ParentUUID = dataBlock.FolderID; + item.Permissions = new Permissions( + dataBlock.BaseMask, + dataBlock.EveryoneMask, + dataBlock.GroupMask, + dataBlock.NextOwnerMask, + dataBlock.OwnerMask); + item.SalePrice = dataBlock.SalePrice; + item.SaleType = (SaleType)dataBlock.SaleType; - _Store[item.UUID] = item; + #region FetchItems Handling + // Iterate backwards through fetch requests, ensures safe removal: + for (int i = _FetchRequests.Count - 1; i >= 0; --i) + { + FetchRequest request = _FetchRequests[i]; + if (request.RequestedItems.ContainsKey(item.UUID)) + { + request.StoreFetchedItem(item); + if (request.ItemsFetched == request.RequestedItems.Count) + { + // We're done, create the list that the callback needs: + List items = new List(request.ItemsFetched); + foreach (KeyValuePair pair in request.RequestedItems) + items.Add(pair.Value.Value); - // Fire the callback for an item being fetched - if (OnItemReceived != null) - { - try { OnItemReceived(item); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, _Client, e); } + // Fire the callback: + request.Callback(items); + _FetchRequests.RemoveAt(i); + } + _FetchRequests[i] = request; + } + } + #endregion FetchItems Handling } } } @@ -3310,8 +3198,8 @@ namespace OpenMetaverse // TODO: MainAvatar.InstantMessageDialog.GroupNotice can also be an inventory offer, should we // handle it here? - if (OnObjectOffered != null && - (im.Dialog == InstantMessageDialog.InventoryOffered + if (OnObjectOffered != null && + (im.Dialog == InstantMessageDialog.InventoryOffered || im.Dialog == InstantMessageDialog.TaskInventoryOffered)) { AssetType type = AssetType.Unknown; @@ -3346,27 +3234,25 @@ namespace OpenMetaverse } } - // Find the folder where this is going to go - UUID destinationFolderID = FindFolderForType(type); - // Fire the callback try { ImprovedInstantMessagePacket imp = new ImprovedInstantMessagePacket(); - imp.AgentData.AgentID = _Client.Self.AgentID; - imp.AgentData.SessionID = _Client.Self.SessionID; + imp.AgentData.AgentID = _Agents.AgentID; + imp.AgentData.SessionID = _Agents.SessionID; imp.MessageBlock.FromGroup = false; imp.MessageBlock.ToAgentID = im.FromAgentID; imp.MessageBlock.Offline = 0; imp.MessageBlock.ID = im.IMSessionID; imp.MessageBlock.Timestamp = 0; - imp.MessageBlock.FromAgentName = Helpers.StringToField(_Client.Self.Name); + imp.MessageBlock.FromAgentName = Helpers.StringToField(_Agents.Name); imp.MessageBlock.Message = new byte[0]; imp.MessageBlock.ParentEstateID = 0; imp.MessageBlock.RegionID = UUID.Zero; - imp.MessageBlock.Position = _Client.Self.SimPosition; + imp.MessageBlock.Position = _Agents.SimPosition; - if (OnObjectOffered(im, type, objectID, fromTask)) + UUID destinationFolderID = OnObjectOffered(im, type, objectID, fromTask); + if (destinationFolderID != UUID.Zero) { // Accept the inventory offer switch (im.Dialog) @@ -3403,7 +3289,7 @@ namespace OpenMetaverse imp.MessageBlock.BinaryBucket = new byte[0]; } - _Client.Network.SendPacket(imp, simulator); + _Network.SendPacket(imp, simulator); } catch (Exception e) { @@ -3411,29 +3297,15 @@ namespace OpenMetaverse } } } - + private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason, LoginResponseData replyData) { if (loginSuccess) { - // Initialize the store here so we know who owns it: - _Store = new Inventory(_Client, this, _Client.Self.AgentID); - Logger.DebugLog("Setting InventoryRoot to " + replyData.InventoryRoot.ToString(), _Client); - InventoryFolder rootFolder = new InventoryFolder(replyData.InventoryRoot); - rootFolder.Name = String.Empty; - rootFolder.ParentUUID = UUID.Zero; - _Store.RootFolder = rootFolder; - - for (int i = 0; i < replyData.InventorySkeleton.Length; i++) - _Store.UpdateNodeFor(replyData.InventorySkeleton[i]); - - InventoryFolder libraryRootFolder = new InventoryFolder(replyData.LibraryRoot); - libraryRootFolder.Name = String.Empty; - libraryRootFolder.ParentUUID = UUID.Zero; - _Store.LibraryFolder = libraryRootFolder; - - for(int i = 0; i < replyData.LibrarySkeleton.Length; i++) - _Store.UpdateNodeFor(replyData.LibrarySkeleton[i]); + InventorySkeleton = new InventorySkeleton(replyData.InventoryRoot, replyData.AgentID); + InventorySkeleton.Folders = replyData.InventoryFolders; + LibrarySkeleton = new InventorySkeleton(replyData.LibraryRoot, replyData.LibraryOwner); + LibrarySkeleton.Folders = replyData.LibraryFolders; } } diff --git a/OpenMetaverse/Login.cs b/OpenMetaverse/Login.cs index 98dd0195..4f38bd1e 100644 --- a/OpenMetaverse/Login.cs +++ b/OpenMetaverse/Login.cs @@ -121,8 +121,8 @@ namespace OpenMetaverse public DateTime SecondsSinceEpoch; public UUID InventoryRoot; public UUID LibraryRoot; - public InventoryFolder[] InventorySkeleton; - public InventoryFolder[] LibrarySkeleton; + public FolderData[] InventoryFolders; + public FolderData[] LibraryFolders; public UUID LibraryOwner; public void Parse(LLSDMap reply) @@ -203,10 +203,10 @@ namespace OpenMetaverse SecondsSinceEpoch = Helpers.UnixTimeToDateTime(ParseUInt("seconds_since_epoch", reply)); InventoryRoot = ParseMappedUUID("inventory-root", "folder_id", reply); - InventorySkeleton = ParseInventoryFolders("inventory-skeleton", AgentID, reply); + InventoryFolders = ParseInventoryFolders("inventory-skeleton", AgentID, reply); LibraryRoot = ParseMappedUUID("inventory-lib-root", "folder_id", reply); LibraryOwner = ParseMappedUUID("inventory-lib-owner", "agent_id", reply); - LibrarySkeleton = ParseInventoryFolders("inventory-skel-lib", LibraryOwner, reply); + LibraryFolders = ParseInventoryFolders("inventory-skel-lib", LibraryOwner, reply); } #region Parsing Helpers @@ -279,9 +279,9 @@ namespace OpenMetaverse return UUID.Zero; } - public static InventoryFolder[] ParseInventoryFolders(string key, UUID owner, LLSDMap reply) + public static FolderData[] ParseInventoryFolders(string key, UUID owner, LLSDMap reply) { - List folders = new List(); + List folders = new List(); LLSD skeleton; if (reply.TryGetValue(key, out skeleton) && skeleton.Type == LLSDType.Array) @@ -293,7 +293,7 @@ namespace OpenMetaverse if (array[i].Type == LLSDType.Map) { LLSDMap map = (LLSDMap)array[i]; - InventoryFolder folder = new InventoryFolder(map["folder_id"].AsUUID()); + FolderData folder = new FolderData(map["folder_id"].AsUUID()); folder.PreferredType = (AssetType)map["type_default"].AsInteger(); folder.Version = map["version"].AsInteger(); folder.OwnerID = owner; @@ -303,11 +303,9 @@ namespace OpenMetaverse folders.Add(folder); } } - - return folders.ToArray(); } - return new InventoryFolder[0]; + return folders.ToArray(); } #endregion Parsing Helpers diff --git a/OpenMetaverse/ReversableDictionary.cs b/OpenMetaverse/ReversableDictionary.cs new file mode 100644 index 00000000..808a7da4 --- /dev/null +++ b/OpenMetaverse/ReversableDictionary.cs @@ -0,0 +1,176 @@ +/* + * Copyright (c) 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.Text; + +namespace OpenMetaverse +{ + public class ReversableDictionary : IDictionary + { + private Dictionary dict; + private Dictionary reverseDict; + + public ReversableDictionary() + : this(10) { } + + public ReversableDictionary(int initialCapacity) + { + dict = new Dictionary(initialCapacity); + reverseDict = new Dictionary(initialCapacity); + } + + #region IDictionary Members + + public void Add(K key, V value) + { + dict.Add(key, value); + reverseDict.Add(value, key); + } + + public bool ContainsKey(K key) + { + return dict.ContainsKey(key); + } + + public ICollection Keys + { + get { return dict.Keys; } + } + + public bool Remove(K key) + { + V value = dict[key]; + bool success = dict.Remove(key); + reverseDict.Remove(value); + return success; + } + + public bool TryGetValue(K key, out V value) + { + return dict.TryGetValue(key, out value); + } + + public bool TryGetKey(V value, out K key) + { + return reverseDict.TryGetValue(value, out key); + } + + public ICollection Values + { + get { return dict.Values; } + } + + public V this[K key] + { + get + { + return dict[key]; + } + set + { + dict[key] = value; + } + } + + public K this[V val] + { + get + { + return reverseDict[val]; + } + set + { + reverseDict[val] = value; + } + } + + #endregion + + #region ICollection> Members + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + dict.Clear(); + reverseDict.Clear(); + } + + public bool Contains(KeyValuePair item) + { + return dict.ContainsKey(item.Key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + foreach (KeyValuePair kvp in dict) + array[arrayIndex++] = kvp; + } + + public int Count + { + get { return dict.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public bool Remove(KeyValuePair item) + { + return Remove(item.Key); + } + + #endregion + + #region IEnumerable> Members + + public IEnumerator> GetEnumerator() + { + foreach (KeyValuePair kvp in dict) + { + yield return kvp; + } + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } +} diff --git a/OpenMetaverse/TextHierarchyParser.cs b/OpenMetaverse/TextHierarchyParser.cs new file mode 100644 index 00000000..ed9457dd --- /dev/null +++ b/OpenMetaverse/TextHierarchyParser.cs @@ -0,0 +1,207 @@ +/* + * Copyright (c) 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.IO; +using System.Text; + +namespace OpenMetaverse +{ + public class IndentWriter : TextWriter + { + private string Prefix = String.Empty; + public string Indent = String.Empty; + private bool AddPrefix = false; + + private StringBuilder builder = new StringBuilder(); + + private void TryAddPrefix() + { + if (AddPrefix) + { + builder.Append(Prefix); + AddPrefix = false; + } + } + + public override void Write(string str) + { + TryAddPrefix(); + builder.Append(str); + } + + public override void Write(char c) + { + if (c == '}') + Prefix.Remove(0, Indent.Length); + + TryAddPrefix(); + + builder.Append(c); + + if (c == '{') + Prefix += Indent; + + } + + public override void Write(object o) + { + TryAddPrefix(); + builder.Append(o); + } + + public override void WriteLine(string line) + { + TryAddPrefix(); + builder.AppendLine(line); + AddPrefix = true; + } + + public override void WriteLine(char c) + { + if (c == '}') + Prefix = Prefix.Remove(0, Indent.Length); + + TryAddPrefix(); + builder.Append(c); + builder.AppendLine(); + + if (c == '{') + Prefix += Indent; + + AddPrefix = true; + } + + public override void WriteLine(object o) + { + TryAddPrefix(); + builder.Append(o); + builder.AppendLine(); + AddPrefix = true; + } + + public override Encoding Encoding + { + get { return Encoding.UTF8; } + } + + public override string ToString() + { + return builder.ToString(); + } + } + + public class TextData + { + public string Name; + public string Value; + public Dictionary Nested = new Dictionary(); + public void ParseLine(string line) + { + int firstSpace = line.IndexOfAny(TextHierarchyParser.WordSeperators); + Name = line.Substring(0, firstSpace); + Value = line.Substring(firstSpace + 1).Trim(); + } + + public override string ToString() + { + IndentWriter writer = new IndentWriter(); + writer.Indent = "\t"; + ToString(writer); + return writer.ToString(); + } + + public void ToString(TextWriter sink) + { + sink.Write(Name); + sink.Write('\t'); + sink.WriteLine(Value); + if (Nested.Count > 0) + { + sink.WriteLine('{'); + foreach (TextData child in Nested.Values) + { + child.ToString(sink); + } + sink.WriteLine('}'); + } + } + } + + public class TextHierarchyParser + { + protected internal static readonly char[] WordSeperators = new char[] { ' ', '\t' }; + + public static TextData Parse(TextReader source) + { + string prevLine = null; + string startLine = source.ReadLine().Trim(); + while (startLine[0] != '{') + { + prevLine = startLine; + startLine = source.ReadLine().Trim(); + } + + TextData startParent = new TextData(); + if (prevLine != null) + startParent.ParseLine(prevLine); + ParseNested(startParent, source); + return startParent; + } + + private static void ParseNested(TextData parent, TextReader source) + { + string line = null; + TextData current = null; + do + { + line = source.ReadLine().Trim(); + if (line.Length > 0) + { + if (line[0] == '{') + { + if (current == null) + current = new TextData(); + ParseNested(current, source); + } + else if (line[0] == '}') + { + return; + } + else + { + current = new TextData(); + current.ParseLine(line); + } + } + + if (current != null) + parent.Nested[current.Name] = current; + } while (line != null); + } + } +} diff --git a/OpenMetaverse/_Packets_.cs b/OpenMetaverse/_Packets_.cs index a973c250..c007a5da 100644 --- a/OpenMetaverse/_Packets_.cs +++ b/OpenMetaverse/_Packets_.cs @@ -93,7 +93,7 @@ namespace OpenMetaverse.Packets get { return (Data[0] & Helpers.MSG_APPENDED_ACKS) != 0; } set { if (value) { Data[0] |= (byte)Helpers.MSG_APPENDED_ACKS; } else { byte mask = (byte)Helpers.MSG_APPENDED_ACKS ^ 0xFF; Data[0] &= mask; } } } - /// Packet sequence number, three bytes long + /// Packet sequence number public uint Sequence { get { return (uint)((Data[1] << 24) + (Data[2] << 16) + (Data[3] << 8) + Data[4]); } diff --git a/Programs/GridImageUpload/frmGridImageUpload.cs b/Programs/GridImageUpload/frmGridImageUpload.cs index 5914392d..29e2d3b2 100644 --- a/Programs/GridImageUpload/frmGridImageUpload.cs +++ b/Programs/GridImageUpload/frmGridImageUpload.cs @@ -312,12 +312,11 @@ namespace GridImageUpload UpdateAssetID(); // Fix the permissions on the new upload since they are fscked by default - InventoryItem item = Client.Inventory.FetchItem(itemID, Client.Self.AgentID, 1000 * 15); Transferred = UploadData.Length; BeginInvoke((MethodInvoker)delegate() { SetProgress(); }); - - if (item != null) + ItemData item; + if (Client.Inventory.FetchItem(itemID, Client.Self.AgentID, TimeSpan.FromSeconds(15), out item)) { item.Permissions.EveryoneMask = PermissionMask.All; item.Permissions.NextOwnerMask = PermissionMask.All; diff --git a/Programs/GridProxy/Plugins/ClientAO.cs b/Programs/GridProxy/Plugins/ClientAO.cs index e4c875bc..a1d64ae5 100644 --- a/Programs/GridProxy/Plugins/ClientAO.cs +++ b/Programs/GridProxy/Plugins/ClientAO.cs @@ -182,7 +182,7 @@ public class ClientAO : ProxyPlugin // Number of directory descendents received int nbdescendantsreceived; //List of items in the current folder - Dictionary currentFolderItems; + Dictionary currentFolderItems; //Asset download request ID UUID assetdownloadID; //Downloaded bytes so far @@ -295,7 +295,7 @@ public class ClientAO : ProxyPlugin InventorySortOrder order) { //empty the dictionnary containing current folder items by name - currentFolderItems = new Dictionary(); + currentFolderItems = new Dictionary(); //reset the number of descendants received nbdescendantsreceived = 0; //build a packet to request the content @@ -388,7 +388,7 @@ public class ClientAO : ProxyPlugin //configuration notecard if (reply.ItemData[i].ItemID != UUID.Zero) { - InventoryItem item = CreateInventoryItem((InventoryType)reply.ItemData[i].InvType, reply.ItemData[i].ItemID); + ItemData item = CreateInventoryItem((InventoryType)reply.ItemData[i].InvType, reply.ItemData[i].ItemID); item.ParentUUID = reply.ItemData[i].FolderID; item.CreatorID = reply.ItemData[i].CreatorID; item.AssetType = (AssetType)reply.ItemData[i].Type; @@ -465,29 +465,13 @@ public class ClientAO : ProxyPlugin } } - public static InventoryItem CreateInventoryItem(InventoryType type, UUID id) + public static ItemData CreateInventoryItem(InventoryType type, UUID id) { - switch (type) - { - case InventoryType.Texture: return new InventoryTexture(id); - case InventoryType.Sound: return new InventorySound(id); - case InventoryType.CallingCard: return new InventoryCallingCard(id); - case InventoryType.Landmark: return new InventoryLandmark(id); - case InventoryType.Object: return new InventoryObject(id); - case InventoryType.Notecard: return new InventoryNotecard(id); - case InventoryType.Category: return new InventoryCategory(id); - case InventoryType.LSL: return new InventoryLSL(id); - case InventoryType.Snapshot: return new InventorySnapshot(id); - case InventoryType.Attachment: return new InventoryAttachment(id); - case InventoryType.Wearable: return new InventoryWearable(id); - case InventoryType.Animation: return new InventoryAnimation(id); - case InventoryType.Gesture: return new InventoryGesture(id); - default: return new InventoryItem(type, id); - } + return new ItemData(id, type); } //Ask for download of an item - public UUID RequestInventoryAsset(InventoryItem item) + public UUID RequestInventoryAsset(ItemData item) { // Build the request packet and send it TransferRequestPacket request = new TransferRequestPacket(); diff --git a/Programs/examples/TestClient/Commands/Appearance/CloneCommand.cs b/Programs/examples/TestClient/Commands/Appearance/CloneCommand.cs index 1b9acbfe..0262a03b 100644 --- a/Programs/examples/TestClient/Commands/Appearance/CloneCommand.cs +++ b/Programs/examples/TestClient/Commands/Appearance/CloneCommand.cs @@ -61,7 +61,7 @@ namespace OpenMetaverse.TestClient #endregion AvatarAppearance to AgentSetAppearance // Detach everything we are currently wearing - Client.Appearance.AddAttachments(new List(), true); + Client.Appearance.AddAttachments(new List(0), true); // Send the new appearance packet Client.Network.SendPacket(set); diff --git a/Programs/examples/TestClient/Commands/Inventory/BackupCommand.cs b/Programs/examples/TestClient/Commands/Inventory/BackupCommand.cs index f5745d93..7a9683da 100644 --- a/Programs/examples/TestClient/Commands/Inventory/BackupCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/BackupCommand.cs @@ -240,7 +240,7 @@ namespace OpenMetaverse.TestClient DirectoryInfo di = new DirectoryInfo(args[1]); // recurse on the root folder into the entire inventory - BackupFolder(Client.Inventory.Store.RootNode, di.FullName); + BackupFolder(Client.InventoryStore.RootFolder, di.FullName); } /// @@ -248,41 +248,42 @@ namespace OpenMetaverse.TestClient /// /// The current leaf in the inventory tree /// path so far, in the form @"c:\here" -- this needs to be "clean" for the current filesystem - private void BackupFolder(InventoryNode folder, string sPathSoFar) + private void BackupFolder(InventoryFolder folder, string sPathSoFar) { StringBuilder sbRequests = new StringBuilder(); - // FIXME: - //Client.Inventory.RequestFolderContents(folder.Data.UUID, Client.Self.AgentID, true, true, false, - // InventorySortOrder.ByName); + + if (folder.IsStale) + folder.DownloadContents(TimeSpan.FromSeconds(10)); // first scan this folder for text - foreach (InventoryNode iNode in folder.Nodes.Values) + + foreach (InventoryBase ib in folder) { if (BackupWorker.CancellationPending) return; - if (iNode.Data is OpenMetaverse.InventoryItem) + if (ib is InventoryItem) { - InventoryItem ii = iNode.Data as InventoryItem; - if (ii.AssetType == AssetType.LSLText || ii.AssetType == AssetType.Notecard) + InventoryItem ii = ib as InventoryItem; + if (ii.Data.AssetType == AssetType.LSLText || ii.Data.AssetType == AssetType.Notecard) { // check permissions on scripts - if (ii.AssetType == AssetType.LSLText) + if (ii.Data.AssetType == AssetType.LSLText) { - if ((ii.Permissions.OwnerMask & PermissionMask.Modify) == PermissionMask.None) + if ((ii.Data.Permissions.OwnerMask & PermissionMask.Modify) == PermissionMask.None) { // skip this one continue; } } - string sExtension = (ii.AssetType == AssetType.LSLText) ? ".lsl" : ".txt"; + string sExtension = (ii.Data.AssetType == AssetType.LSLText) ? ".lsl" : ".txt"; // make the output file string sPath = sPathSoFar + @"\" + MakeValid(ii.Name.Trim()) + sExtension; // create the new qdi - QueuedDownloadInfo qdi = new QueuedDownloadInfo(sPath, ii.AssetUUID, iNode.Data.UUID, UUID.Zero, - Client.Self.AgentID, ii.AssetType); + QueuedDownloadInfo qdi = new QueuedDownloadInfo(sPath, ii.Data.AssetUUID, ii.UUID, UUID.Zero, + Client.Self.AgentID, ii.Data.AssetType); // add it to the queue lock (PendingDownloads) @@ -295,12 +296,12 @@ namespace OpenMetaverse.TestClient } // now run any subfolders - foreach (InventoryNode i in folder.Nodes.Values) + foreach (InventoryBase ib in folder) { if (BackupWorker.CancellationPending) return; - else if (i.Data is OpenMetaverse.InventoryFolder) - BackupFolder(i, sPathSoFar + @"\" + MakeValid(i.Data.Name.Trim())); + else if (ib is InventoryFolder) + BackupFolder(ib as InventoryFolder, sPathSoFar + @"\" + MakeValid(ib.Name.Trim())); } } diff --git a/Programs/examples/TestClient/Commands/Inventory/ChangeDirectoryCommand.cs b/Programs/examples/TestClient/Commands/Inventory/ChangeDirectoryCommand.cs index e5e3f7b6..7be6bd4b 100644 --- a/Programs/examples/TestClient/Commands/Inventory/ChangeDirectoryCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/ChangeDirectoryCommand.cs @@ -19,7 +19,7 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell public override string Execute(string[] args, UUID fromAgentID) { Manager = Client.Inventory; - Inventory = Client.Inventory.Store; + Inventory = Client.InventoryStore; if (args.Length > 1) return "Usage: cd [path-to-folder]"; @@ -52,17 +52,19 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell if (nextName == ".." && currentFolder != Inventory.RootFolder) { // If we encounter .., move to the parent folder. - currentFolder = Inventory[currentFolder.ParentUUID] as InventoryFolder; + currentFolder = currentFolder.Parent; } else { - List currentContents = Inventory.GetContents(currentFolder); + if (currentFolder.IsStale) + currentFolder.DownloadContents(TimeSpan.FromSeconds(30)); // Try and find an InventoryBase with the corresponding name. bool found = false; - foreach (InventoryBase item in currentContents) + foreach (InventoryBase item in currentFolder) { + string name = item.Name; // Allow lookup by UUID as well as name: - if (item.Name == nextName || item.UUID.ToString() == nextName) + if (name == nextName || item.UUID.ToString() == nextName) { found = true; if (item is InventoryFolder) @@ -71,16 +73,16 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell } else { - return item.Name + " is not a folder."; + return name + " is not a folder."; } } } if (!found) - return nextName + " not found in " + currentFolder.Name; + return nextName + " not found in " + currentFolder.Data.Name; } } Client.CurrentDirectory = currentFolder; - return "Current folder: " + currentFolder.Name; + return "Current folder: " + currentFolder.Data.Name; } } -} \ No newline at end of file +} diff --git a/Programs/examples/TestClient/Commands/Inventory/CreateNotecardCommand.cs b/Programs/examples/TestClient/Commands/Inventory/CreateNotecardCommand.cs index 6b145f94..06a5694c 100644 --- a/Programs/examples/TestClient/Commands/Inventory/CreateNotecardCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/CreateNotecardCommand.cs @@ -45,7 +45,7 @@ namespace OpenMetaverse.TestClient Client.Inventory.RequestCreateItem(Client.Inventory.FindFolderForType(AssetType.Notecard), file, desc, AssetType.Notecard, UUID.Random(), InventoryType.Notecard, PermissionMask.All, - delegate(bool success, InventoryItem item) { + delegate(bool success, ItemData item) { if(success) // upload the asset Client.Inventory.RequestUploadNotecardAsset(CreateNotecardAsset(body), item.UUID, new InventoryManager.NotecardUploadedAssetCallback(OnNoteUpdate)); } @@ -83,4 +83,4 @@ namespace OpenMetaverse.TestClient return assetData; } } -} \ No newline at end of file +} diff --git a/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs b/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs index cb1ee5ba..fea88f4b 100644 --- a/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs @@ -34,12 +34,20 @@ namespace OpenMetaverse.TestClient try { // find the folder - found = Client.Inventory.LocalFind(Client.Inventory.Store.RootFolder.UUID, target.Split('/'), 0, true); - if (found.Count.Equals(1)) + found = Client.InventoryStore.InventoryFromPath(target.Split('/'), Client.InventoryStore.RootFolder); + if (found.Count > 0) { - // move the folder to the trash folder - Client.Inventory.MoveFolder(found[0].UUID, Client.Inventory.FindFolderForType(AssetType.TrashFolder)); - return String.Format("Moved folder {0} to Trash", found[0].Name); + InventoryBase item = found[0]; + InventoryFolder trash = Client.InventoryStore[Client.Inventory.FindFolderForType(AssetType.TrashFolder)] as InventoryFolder; + if (trash != null) + { + item.Move(trash); + return String.Format("Moved folder {0} ({1}) to Trash", item.Name, item.UUID); + } + } + else + { + return String.Format("Unable to locate {0}", target); } } catch (InvalidOutfitException ex) @@ -49,4 +57,4 @@ namespace OpenMetaverse.TestClient return string.Empty; } } -} \ No newline at end of file +} diff --git a/Programs/examples/TestClient/Commands/Inventory/GiveItemCommand.cs b/Programs/examples/TestClient/Commands/Inventory/GiveItemCommand.cs index 1a0129ee..548637ed 100644 --- a/Programs/examples/TestClient/Commands/Inventory/GiveItemCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/GiveItemCommand.cs @@ -26,29 +26,26 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell return "First argument expected agent UUID."; } Manager = Client.Inventory; - Inventory = Manager.Store; + Inventory = Client.InventoryStore; string ret = ""; string nl = "\n"; for (int i = 1; i < args.Length; ++i) { string inventoryName = args[i]; - // WARNING: Uses local copy of inventory contents, need to download them first. - List contents = Inventory.GetContents(Client.CurrentDirectory); + + if (Client.CurrentDirectory.IsStale) + { + Client.CurrentDirectory.DownloadContents(TimeSpan.FromSeconds(30)); + } + bool found = false; - foreach (InventoryBase b in contents) { - if (inventoryName == b.Name || inventoryName == b.UUID.ToString()) + foreach (InventoryBase b in Client.CurrentDirectory) { + string name = b.Name; + if (inventoryName == name || inventoryName == b.UUID.ToString()) { found = true; - if (b is InventoryItem) - { - InventoryItem item = b as InventoryItem; - Manager.GiveItem(item.UUID, item.Name, item.AssetType, dest, true); - ret += "Gave " + item.Name + nl; - } - else - { - ret += "Unable to give folder " + b.Name + nl; - } + b.Give(dest, true); + ret += "Gave " + name + nl; } } if (!found) diff --git a/Programs/examples/TestClient/Commands/Inventory/InventoryCommand.cs b/Programs/examples/TestClient/Commands/Inventory/InventoryCommand.cs index 2e564960..f46d5992 100644 --- a/Programs/examples/TestClient/Commands/Inventory/InventoryCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/InventoryCommand.cs @@ -25,27 +25,39 @@ namespace OpenMetaverse.TestClient public override string Execute(string[] args, UUID fromAgentID) { Manager = Client.Inventory; - Inventory = Manager.Store; + Inventory = Client.InventoryStore; StringBuilder result = new StringBuilder(); - + InventoryFolder rootFolder = Inventory.RootFolder; PrintFolder(rootFolder, result, 0); return result.ToString(); } - void PrintFolder(InventoryFolder f, StringBuilder result, int indent) + void PrintFolder(InventoryFolder folder, StringBuilder result, int indent) { - foreach (InventoryBase i in Manager.FolderContents(f.UUID, Client.Self.AgentID, true, true, InventorySortOrder.ByName, 3000)) + folder.DownloadContents(TimeSpan.FromSeconds(10)); + foreach (InventoryBase b in folder) { - result.AppendFormat("{0}{1} ({2})\n", new String(' ', indent * 2), i.Name, i.UUID); - if (i is InventoryFolder) + if (b is InventoryFolder) { - InventoryFolder folder = (InventoryFolder)i; - PrintFolder(folder, result, indent + 1); + result.Append(Print(b as InventoryFolder, indent)); + PrintFolder(b as InventoryFolder, result, indent + 1); + } + else if (b is InventoryItem) + { + result.Append(Print(b as InventoryItem, indent)); } } } + string Print(InventoryItem item, int indent) + { + return string.Format("{0}{1} ({2})\n", new String(' ', indent * 2), item.Data.Name, item.UUID); + } + string Print(InventoryFolder folder, int indent) + { + return string.Format("{0}{1} ({2})\n", new String(' ', indent * 2), folder.Data.Name, folder.UUID); + } } -} \ No newline at end of file +} diff --git a/Programs/examples/TestClient/Commands/Inventory/ListContentsCommand.cs b/Programs/examples/TestClient/Commands/Inventory/ListContentsCommand.cs index b066cfa3..8a403b58 100644 --- a/Programs/examples/TestClient/Commands/Inventory/ListContentsCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/ListContentsCommand.cs @@ -24,13 +24,15 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell longDisplay = true; Manager = Client.Inventory; - Inventory = Manager.Store; - // WARNING: Uses local copy of inventory contents, need to download them first. - List contents = Inventory.GetContents(Client.CurrentDirectory); + Inventory = Client.InventoryStore; + + if (Client.CurrentDirectory.IsStale) + Client.CurrentDirectory.DownloadContents(TimeSpan.FromSeconds(30)); + string displayString = ""; string nl = "\n"; // New line character // Pretty simple, just print out the contents. - foreach (InventoryBase b in contents) + foreach (InventoryBase b in Client.CurrentDirectory) { if (longDisplay) { @@ -51,16 +53,17 @@ namespace OpenMetaverse.TestClient.Commands.Inventory.Shell { InventoryItem item = b as InventoryItem; displayString += "-"; - displayString += PermMaskString(item.Permissions.OwnerMask); - displayString += PermMaskString(item.Permissions.GroupMask); - displayString += PermMaskString(item.Permissions.EveryoneMask); + displayString += PermMaskString(item.Data.Permissions.OwnerMask); + displayString += PermMaskString(item.Data.Permissions.GroupMask); + displayString += PermMaskString(item.Data.Permissions.EveryoneMask); displayString += " " + item.UUID; displayString += " " + item.Name; } } else { - displayString += b.Name; + string name = b.Name; + displayString += name; } displayString += nl; } diff --git a/Programs/examples/TestClient/Commands/Inventory/ObjectInventoryCommand.cs b/Programs/examples/TestClient/Commands/Inventory/ObjectInventoryCommand.cs index 8df7543a..d8094990 100644 --- a/Programs/examples/TestClient/Commands/Inventory/ObjectInventoryCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/ObjectInventoryCommand.cs @@ -29,24 +29,22 @@ namespace OpenMetaverse.TestClient else return "Couldn't find prim " + objectID.ToString(); - List items = Client.Inventory.GetTaskInventory(objectID, objectLocalID, 1000 * 30); + List items; + List folders; + Client.Inventory.GetTaskInventory(objectID, objectLocalID, TimeSpan.FromMilliseconds(1000 * 30), out items, out folders); if (items != null) { string result = String.Empty; - for (int i = 0; i < items.Count; i++) + foreach (ItemData item in items) { - if (items[i] is InventoryFolder) - { - result += String.Format("[Folder] Name: {0}", items[i].Name) + Environment.NewLine; - } - else - { - InventoryItem item = (InventoryItem)items[i]; - result += String.Format("[Item] Name: {0} Desc: {1} Type: {2}", item.Name, item.Description, + result += String.Format("[Item] Name: {0} Desc: {1} Type: {2}", item.Name, item.Description, item.AssetType) + Environment.NewLine; - } + } + foreach (FolderData folder in folders) + { + result += String.Format("[Folder] Name: {0}", folder.Name) + Environment.NewLine; } return result; diff --git a/Programs/examples/TestClient/Commands/Prims/ChangePermsCommand.cs b/Programs/examples/TestClient/Commands/Prims/ChangePermsCommand.cs index 756914a1..6735d6bd 100644 --- a/Programs/examples/TestClient/Commands/Prims/ChangePermsCommand.cs +++ b/Programs/examples/TestClient/Commands/Prims/ChangePermsCommand.cs @@ -131,20 +131,18 @@ namespace OpenMetaverse.TestClient { if ((prim.Flags & LLObject.ObjectFlags.InventoryEmpty) == 0) { - List items = Client.Inventory.GetTaskInventory(prim.ID, prim.LocalID, 1000 * 30); + List items; + List folders; + Client.Inventory.GetTaskInventory(prim.ID, prim.LocalID, TimeSpan.FromSeconds(30), out items, out folders); if (items != null) { - for (int i = 0; i < items.Count; i++) + foreach (ItemData item in items) { - if (!(items[i] is InventoryFolder)) - { - InventoryItem item = (InventoryItem)items[i]; - item.Permissions.NextOwnerMask = Perms; - - Client.Inventory.UpdateTaskInventory(prim.LocalID, item); - ++taskItems; - } + ItemData iitem = item; + iitem.Permissions.NextOwnerMask = Perms; + Client.Inventory.UpdateTaskInventory(prim.LocalID, iitem); + ++taskItems; } } } diff --git a/Programs/examples/TestClient/TestClient.cs b/Programs/examples/TestClient/TestClient.cs index 88de0be4..a971e772 100644 --- a/Programs/examples/TestClient/TestClient.cs +++ b/Programs/examples/TestClient/TestClient.cs @@ -28,6 +28,8 @@ namespace OpenMetaverse.TestClient private Vector3 left = new Vector3(0.9999f, 0, 0); private Vector3 up = new Vector3(0, 0, 0.9999f); private System.Timers.Timer updateTimer; + public Inventory InventoryStore; + public Inventory LibraryStore; /// /// @@ -59,7 +61,6 @@ namespace OpenMetaverse.TestClient Network.RegisterCallback(PacketType.AlertMessage, new NetworkManager.PacketCallback(AlertMessageHandler)); VoiceManager = new VoiceManager(this); - updateTimer.Start(); } @@ -72,8 +73,12 @@ namespace OpenMetaverse.TestClient { if (login == LoginStatus.Success) { - // Start in the inventory root folder. - CurrentDirectory = Inventory.Store.RootFolder; + // Create the stores: + InventoryStore = new Inventory(Inventory, Inventory.InventorySkeleton); + LibraryStore = new Inventory(Inventory, Inventory.LibrarySkeleton); + + // Start in the inventory root folder: + CurrentDirectory = InventoryStore.RootFolder; } } @@ -244,20 +249,20 @@ namespace OpenMetaverse.TestClient } - private bool Inventory_OnInventoryObjectReceived(InstantMessage offer, AssetType type, + private UUID Inventory_OnInventoryObjectReceived(InstantMessage offer, AssetType type, UUID objectID, bool fromTask) { if (MasterKey != UUID.Zero) { if (offer.FromAgentID != MasterKey) - return false; + return UUID.Zero; } else if (GroupMembers != null && !GroupMembers.ContainsKey(offer.FromAgentID)) { - return false; + return UUID.Zero; } - return true; + return Inventory.FindFolderForType(type); } } } diff --git a/Programs/importprimscript/importprimscript.cs b/Programs/importprimscript/importprimscript.cs index 94840d60..49946794 100644 --- a/Programs/importprimscript/importprimscript.cs +++ b/Programs/importprimscript/importprimscript.cs @@ -131,7 +131,7 @@ namespace importprimscript // // Create a folder to hold all of our texture uploads - UploadFolderID = Client.Inventory.CreateFolder(Client.Inventory.Store.RootFolder.UUID, scriptfilename); + UploadFolderID = Client.Inventory.CreateFolder(Client.Inventory.InventorySkeleton.RootUUID, scriptfilename); // Loop through each sculpty and do what we need to do for (int i = 0; i < sculpties.Count; i++) diff --git a/Programs/mapgenerator/template.cs b/Programs/mapgenerator/template.cs index 117c7e35..ec2ea05a 100644 --- a/Programs/mapgenerator/template.cs +++ b/Programs/mapgenerator/template.cs @@ -93,7 +93,7 @@ namespace OpenMetaverse.Packets get { return (Data[0] & Helpers.MSG_APPENDED_ACKS) != 0; } set { if (value) { Data[0] |= (byte)Helpers.MSG_APPENDED_ACKS; } else { byte mask = (byte)Helpers.MSG_APPENDED_ACKS ^ 0xFF; Data[0] &= mask; } } } - /// Packet sequence number, three bytes long + /// Packet sequence number public uint Sequence { get { return (uint)((Data[1] << 24) + (Data[2] << 16) + (Data[3] << 8) + Data[4]); }