* More parameters to HTTPBase and Capabilities for making special requests * Renamed InventoryManager callbacks to match the rest of libsecondlife * Several new InventoryManager functions, not complete yet! * Fix for null buddy list on login * OnSimConnecting returns a bool to allow canceling sim connections * NetworkManager.Connect() properly returns null on a failure git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1393 52acb1d6-8a22-11de-b505-999d5b087335
1828 lines
75 KiB
C#
1828 lines
75 KiB
C#
/*
|
|
* Copyright (c) 2006-2007, Second Life Reverse Engineering Team
|
|
* All rights reserved.
|
|
*
|
|
* - Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* - Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
* - Neither the name of the Second Life Reverse Engineering Team nor the names
|
|
* of its contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Text;
|
|
using libsecondlife;
|
|
using libsecondlife.Packets;
|
|
|
|
namespace libsecondlife
|
|
{
|
|
#region Enums
|
|
|
|
public enum InventoryType : sbyte
|
|
{
|
|
Unknown = -1,
|
|
Texture = 0,
|
|
Sound = 1,
|
|
CallingCard = 2,
|
|
Landmark = 3,
|
|
[Obsolete] Script = 4,
|
|
[Obsolete] Clothing = 5,
|
|
Object = 6,
|
|
Notecard = 7,
|
|
Category = 8,
|
|
Folder = 8,
|
|
RootCategory = 0,
|
|
LSL = 10,
|
|
[Obsolete] LSLBytecode = 11,
|
|
[Obsolete] TextureTGA = 12,
|
|
[Obsolete] Bodypart = 13,
|
|
[Obsolete] Trash = 14,
|
|
Snapshot = 15,
|
|
[Obsolete] LostAndFound = 16,
|
|
Attachment = 17,
|
|
Wearable = 18,
|
|
Animation = 19,
|
|
Gesture = 20
|
|
}
|
|
|
|
[Flags]
|
|
public enum InventorySortOrder : int
|
|
{
|
|
/// <summary>Sort by name</summary>
|
|
ByName = 0,
|
|
/// <summary>Sort by date</summary>
|
|
ByDate = 1,
|
|
/// <summary>Sort folders by name, regardless of whether items are
|
|
/// sorted by name or date</summary>
|
|
FoldersByName = 2,
|
|
/// <summary>Place system folders at the top</summary>
|
|
SystemFoldersToTop = 4
|
|
}
|
|
|
|
#endregion Enums
|
|
|
|
#region Inventory Object Classes
|
|
|
|
public abstract class InventoryBase
|
|
{
|
|
public readonly LLUUID UUID;
|
|
public LLUUID ParentUUID;
|
|
public string Name;
|
|
public LLUUID OwnerID;
|
|
|
|
public InventoryBase(LLUUID itemID)
|
|
{
|
|
if (itemID == LLUUID.Zero)
|
|
throw new ArgumentException("Inventory item ID cannot be NULL_KEY (LLUUID.Zero)");
|
|
UUID = itemID;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return UUID.GetHashCode() ^ ParentUUID.GetHashCode() ^ Name.GetHashCode() ^ OwnerID.GetHashCode();
|
|
}
|
|
|
|
public override bool Equals(object o)
|
|
{
|
|
InventoryBase inv = o as InventoryBase;
|
|
return inv != null && Equals(inv);
|
|
}
|
|
|
|
public virtual bool Equals(InventoryBase o)
|
|
{
|
|
return o.UUID == UUID
|
|
&& o.ParentUUID == ParentUUID
|
|
&& o.Name == Name
|
|
&& o.OwnerID == OwnerID;
|
|
}
|
|
}
|
|
|
|
public class InventoryItem : InventoryBase
|
|
{
|
|
public LLUUID AssetUUID;
|
|
public Permissions Permissions;
|
|
public AssetType AssetType;
|
|
public InventoryType InventoryType;
|
|
public LLUUID CreatorID;
|
|
public string Description;
|
|
public LLUUID GroupID;
|
|
public bool GroupOwned;
|
|
public int SalePrice;
|
|
public SaleType SaleType;
|
|
public uint Flags;
|
|
/// <summary>Time and date this inventory item was created, stored as
|
|
/// UTC (Coordinated Universal Time)</summary>
|
|
public DateTime CreationDate;
|
|
|
|
public InventoryItem(LLUUID itemID)
|
|
: base(itemID) { }
|
|
|
|
public InventoryItem(InventoryType type, LLUUID itemID) : base(itemID) { InventoryType = type; }
|
|
|
|
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();
|
|
}
|
|
|
|
public override bool Equals(object o)
|
|
{
|
|
InventoryItem item = o as InventoryItem;
|
|
return item != null && Equals(item);
|
|
}
|
|
|
|
public override bool Equals(InventoryBase o)
|
|
{
|
|
InventoryItem item = o as InventoryItem;
|
|
return item != null && Equals(item);
|
|
}
|
|
|
|
public bool Equals(InventoryItem o)
|
|
{
|
|
return base.Equals(o as InventoryBase)
|
|
&& o.AssetType == AssetType
|
|
&& o.AssetUUID == AssetUUID
|
|
&& o.CreationDate == CreationDate
|
|
&& o.Description == Description
|
|
&& o.Flags == Flags
|
|
&& o.GroupID == GroupID
|
|
&& o.GroupOwned == GroupOwned
|
|
&& o.InventoryType == InventoryType
|
|
&& o.Permissions.Equals(Permissions)
|
|
&& o.SalePrice == SalePrice
|
|
&& o.SaleType == SaleType;
|
|
}
|
|
}
|
|
|
|
public class InventoryTexture : InventoryItem { public InventoryTexture(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Texture; } }
|
|
public class InventorySound : InventoryItem { public InventorySound(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Sound; } }
|
|
public class InventoryCallingCard : InventoryItem { public InventoryCallingCard(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.CallingCard; } }
|
|
public class InventoryLandmark : InventoryItem { public InventoryLandmark(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Landmark; } }
|
|
public class InventoryObject : InventoryItem { public InventoryObject(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Object; } }
|
|
public class InventoryNotecard : InventoryItem { public InventoryNotecard(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Notecard; } }
|
|
public class InventoryCategory : InventoryItem { public InventoryCategory(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Category; } }
|
|
public class InventoryLSL : InventoryItem { public InventoryLSL(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.LSL; } }
|
|
public class InventorySnapshot : InventoryItem { public InventorySnapshot(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Snapshot; } }
|
|
public class InventoryAttachment : InventoryItem { public InventoryAttachment(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Attachment; } }
|
|
|
|
public class InventoryWearable : InventoryItem
|
|
{
|
|
public InventoryWearable(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Wearable; }
|
|
|
|
public WearableType WearableType
|
|
{
|
|
get { return (WearableType)Flags; }
|
|
set { Flags = (uint)value; }
|
|
}
|
|
}
|
|
|
|
public class InventoryAnimation : InventoryItem { public InventoryAnimation(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Animation; } }
|
|
public class InventoryGesture : InventoryItem { public InventoryGesture(LLUUID itemID) : base(itemID) { InventoryType = InventoryType.Gesture; } }
|
|
|
|
public class InventoryFolder : InventoryBase
|
|
{
|
|
public AssetType PreferredType;
|
|
public int Version;
|
|
public int DescendentCount;
|
|
|
|
public InventoryFolder(LLUUID itemID)
|
|
: base(itemID) { }
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return PreferredType.GetHashCode() ^ Version.GetHashCode() ^ DescendentCount.GetHashCode();
|
|
}
|
|
|
|
public override bool Equals(object o)
|
|
{
|
|
InventoryFolder folder = o as InventoryFolder;
|
|
return folder != null && Equals(folder);
|
|
}
|
|
|
|
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)
|
|
&& o.DescendentCount == DescendentCount
|
|
&& o.PreferredType == PreferredType
|
|
&& o.Version == Version;
|
|
}
|
|
}
|
|
|
|
#endregion Inventory Object Classes
|
|
|
|
public class InventoryManager
|
|
{
|
|
/// <summary>
|
|
/// Callback for inventory item creation finishing
|
|
/// </summary>
|
|
/// <param name="success">Whether the request to create an inventory
|
|
/// item succeeded or not</param>
|
|
/// <param name="item">Inventory item being created. If success is
|
|
/// false this will be null</param>
|
|
public delegate void ItemCreatedCallback(bool success, InventoryItem item);
|
|
/// <summary>
|
|
/// Callback for an inventory folder updating
|
|
/// </summary>
|
|
/// <param name="folderID">UUID of the folder that was updated</param>
|
|
public delegate void FolderUpdatedCallback(LLUUID folderID);
|
|
/// <summary>
|
|
/// Callback when an inventory object is received from another avatar
|
|
/// or a primitive
|
|
/// </summary>
|
|
/// <param name="fromAgentID"></param>
|
|
/// <param name="fromAgentName"></param>
|
|
/// <param name="parentEstateID"></param>
|
|
/// <param name="regionID"></param>
|
|
/// <param name="position"></param>
|
|
/// <param name="timestamp"></param>
|
|
/// <param name="type"></param>
|
|
/// <param name="objectID"></param>
|
|
/// <param name="fromTask"></param>
|
|
/// <returns>True to accept the inventory offer, false to reject it</returns>
|
|
public delegate bool ObjectReceivedCallback(LLUUID fromAgentID, string fromAgentName, uint parentEstateID,
|
|
LLUUID regionID, LLVector3 position, DateTime timestamp, AssetType type, LLUUID objectID, bool fromTask);
|
|
|
|
public event FolderUpdatedCallback OnInventoryFolderUpdated;
|
|
public event ObjectReceivedCallback OnInventoryObjectReceived;
|
|
|
|
private SecondLife _Client;
|
|
private Inventory _Store;
|
|
private Dictionary<LLUUID, List<DescendantsResult>> _FolderRequests = new Dictionary<LLUUID, List<DescendantsResult>>();
|
|
private Dictionary<uint, ItemCreatedCallback> _ItemCreatedCallbacks = new Dictionary<uint, ItemCreatedCallback>();
|
|
private uint _ItemCreatedCallbackPos = 0;
|
|
|
|
/// <summary>Partial mapping of AssetTypes to folder names</summary>
|
|
private static readonly string[] _NewFolderNames = new string[]
|
|
{
|
|
"Textures",
|
|
"Sounds",
|
|
"Calling Cards",
|
|
"Landmarks",
|
|
"Scripts",
|
|
"Clothing",
|
|
"Objects",
|
|
"Notecards",
|
|
"New Folder",
|
|
"Inventory",
|
|
"Scripts",
|
|
"Scripts",
|
|
"Uncompressed Images",
|
|
"Body Parts",
|
|
"Trash",
|
|
"Photo Album",
|
|
"Lost And Found",
|
|
"Uncompressed Sounds",
|
|
"Uncompressed Images",
|
|
"Uncompressed Images",
|
|
"Animations",
|
|
"Gestures"
|
|
};
|
|
|
|
private static readonly string[] _AssetTypeNames = new string[]
|
|
{
|
|
"texture",
|
|
"sound",
|
|
"callcard",
|
|
"landmark",
|
|
"script",
|
|
"clothing",
|
|
"object",
|
|
"notecard",
|
|
"category",
|
|
"root",
|
|
"lsltext",
|
|
"lslbyte",
|
|
"txtr_tga",
|
|
"bodypart",
|
|
"trash",
|
|
"snapshot",
|
|
"lstndfnd",
|
|
"snd_wav",
|
|
"img_tga",
|
|
"jpeg",
|
|
"animatn",
|
|
"gesture",
|
|
"simstate"
|
|
};
|
|
|
|
private static readonly string[] _InventoryTypeNames = new string[]
|
|
{
|
|
"texture",
|
|
"sound",
|
|
"callcard",
|
|
"landmark",
|
|
String.Empty,
|
|
String.Empty,
|
|
"object",
|
|
"notecard",
|
|
"category",
|
|
"root",
|
|
"script",
|
|
String.Empty,
|
|
String.Empty,
|
|
String.Empty,
|
|
String.Empty,
|
|
"snapshot",
|
|
String.Empty,
|
|
"attach",
|
|
"wearable",
|
|
"animation",
|
|
"gesture",
|
|
};
|
|
|
|
#region Properties
|
|
|
|
public Inventory Store { get { return _Store; } }
|
|
|
|
#endregion Properties
|
|
|
|
public InventoryManager(SecondLife client)
|
|
{
|
|
_Client = client;
|
|
|
|
_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));
|
|
// Watch for inventory given to us through instant message
|
|
_Client.Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage);
|
|
}
|
|
|
|
#region File & Folder Public Methods
|
|
|
|
/// <summary>
|
|
/// If you have a list of inventory item IDs (from a cached inventory, perhaps)
|
|
/// you can use this function to request an update from the server for those items.
|
|
/// </summary>
|
|
/// <param name="itemIDs">A list of LLUUIDs of the items to request.</param>
|
|
public void FetchInventory(List<LLUUID> itemIDs)
|
|
{
|
|
FetchInventoryPacket fetch = new FetchInventoryPacket();
|
|
fetch.AgentData = new FetchInventoryPacket.AgentDataBlock();
|
|
fetch.AgentData.AgentID = _Client.Network.AgentID;
|
|
fetch.AgentData.SessionID = _Client.Network.SessionID;
|
|
|
|
fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[itemIDs.Count];
|
|
// TODO: Make sure the packet doesnt overflow.
|
|
for (int i = 0; i < itemIDs.Count; ++i)
|
|
{
|
|
fetch.InventoryData[i] = new FetchInventoryPacket.InventoryDataBlock();
|
|
fetch.InventoryData[i].ItemID = itemIDs[i];
|
|
fetch.InventoryData[i].OwnerID = _Client.Network.AgentID;
|
|
}
|
|
|
|
_Client.Network.SendPacket(fetch);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="itemID"></param>
|
|
public void FetchInventory(LLUUID itemID)
|
|
{
|
|
List<LLUUID> list = new List<LLUUID>(1);
|
|
list.Add(itemID);
|
|
FetchInventory(list);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="obj"></param>
|
|
public void Remove(InventoryBase obj)
|
|
{
|
|
List<InventoryBase> temp = new List<InventoryBase>(1);
|
|
temp.Add(obj);
|
|
Remove(temp);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="objects"></param>
|
|
public void Remove(List<InventoryBase> objects)
|
|
{
|
|
List<LLUUID> items = new List<LLUUID>(objects.Count);
|
|
List<LLUUID> folders = new List<LLUUID>(objects.Count);
|
|
foreach (InventoryBase obj in objects)
|
|
{
|
|
if (obj is InventoryFolder)
|
|
{
|
|
folders.Add(obj.UUID);
|
|
}
|
|
else
|
|
{
|
|
items.Add(obj.UUID);
|
|
}
|
|
}
|
|
Remove(items, folders);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
/// <param name="folders"></param>
|
|
public void Remove(List<LLUUID> items, List<LLUUID> folders)
|
|
{
|
|
if ((items == null && items.Count == 0) && (folders == null && folders.Count == 0))
|
|
return;
|
|
|
|
RemoveInventoryObjectsPacket rem = new RemoveInventoryObjectsPacket();
|
|
rem.AgentData.AgentID = _Client.Network.AgentID;
|
|
rem.AgentData.SessionID = _Client.Network.SessionID;
|
|
|
|
if (items == null || items.Count == 0)
|
|
{
|
|
// To indicate that we want no items removed:
|
|
rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[1];
|
|
rem.ItemData[0] = new RemoveInventoryObjectsPacket.ItemDataBlock();
|
|
rem.ItemData[0].ItemID = LLUUID.Zero;
|
|
}
|
|
else
|
|
{
|
|
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
|
|
_Store.RemoveNodeFor(_Store[items[i]]);
|
|
}
|
|
}
|
|
|
|
if (folders == null || folders.Count == 0)
|
|
{
|
|
// To indicate we want no folders removed:
|
|
rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[1];
|
|
rem.FolderData[0] = new RemoveInventoryObjectsPacket.FolderDataBlock();
|
|
rem.FolderData[0].FolderID = LLUUID.Zero;
|
|
}
|
|
else
|
|
{
|
|
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:
|
|
_Store.RemoveNodeFor(_Store[folders[i]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion File & Folder Public Methods
|
|
|
|
#region Searching
|
|
private Dictionary<IAsyncResult, FindResult> FindDescendantsMap = new Dictionary<IAsyncResult, FindResult>();
|
|
|
|
/// <summary>
|
|
/// Starts a search for any items whose names match the regex within
|
|
/// the spacified folder.
|
|
/// </summary>
|
|
/// <remarks>Use the AsyncWaitHandle of the returned value to run the search synchronously.</remarks>
|
|
/// <param name="baseFolder">The UUID of the folder to look in.</param>
|
|
/// <param name="regex">The regex that results match.</param>
|
|
/// <param name="recurse">Recurse into and search inside subfolders of baseFolder.</param>
|
|
/// <param name="refresh">Re-download the contents of baseFolder (and its subdirectories, if recursing)</param>
|
|
/// <param name="callback">The AsyncCallback to call when the search is complete.</param>
|
|
/// <param name="asyncState">An object that will be passed back to the caller.</param>
|
|
/// <returns>An IAsyncResult that represents this find operation, and can be passed to EndFindObjects.</returns>
|
|
public IAsyncResult BeginFindObjects(LLUUID baseFolder, string regex, bool recurse, bool refresh, AsyncCallback callback, object asyncState)
|
|
{
|
|
return BeginFindObjects(baseFolder, new Regex(regex), recurse, refresh, false, callback, asyncState);
|
|
}
|
|
|
|
public IAsyncResult BeginFindObjects(LLUUID baseFolder, string regex, bool recurse, bool refresh, bool firstOnly, AsyncCallback callback, object asyncState)
|
|
{
|
|
return BeginFindObjects(baseFolder, new Regex(regex), recurse, refresh, firstOnly, callback, asyncState);
|
|
}
|
|
|
|
public IAsyncResult BeginFindObjects(LLUUID baseFolder, Regex regexp, bool recurse, bool refresh, bool firstOnly, AsyncCallback callback, object asyncState)
|
|
{
|
|
FindResult result = new FindResult(regexp, recurse, callback);
|
|
result.FirstOnly = firstOnly;
|
|
result.AsyncState = asyncState;
|
|
result.FoldersWaiting = 1;
|
|
if (refresh)
|
|
{
|
|
lock (FindDescendantsMap)
|
|
{
|
|
IAsyncResult descendReq = BeginRequestFolderContents(baseFolder, _Client.Network.AgentID, true, true, recurse && !firstOnly, InventorySortOrder.ByName, new AsyncCallback(SearchDescendantsCallback), baseFolder);
|
|
FindDescendantsMap.Add(descendReq, result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.Result = LocalFind(baseFolder, regexp, recurse, firstOnly);
|
|
result.CompletedSynchronously = true;
|
|
result.IsCompleted = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public List<InventoryBase> EndFindObjects(IAsyncResult result)
|
|
{
|
|
if (result is FindResult)
|
|
{
|
|
FindResult fr = result as FindResult;
|
|
if (!fr.IsCompleted) fr.AsyncWaitHandle.WaitOne();
|
|
return fr.Result;
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("EndFindObjects must be passed the return value of BeginFindObjects.");
|
|
}
|
|
}
|
|
|
|
public void SearchDescendantsCallback(IAsyncResult result)
|
|
{
|
|
EndRequestFolderContents(result);
|
|
LLUUID updatedFolder = (LLUUID)result.AsyncState;
|
|
FindResult find = null;
|
|
lock (FindDescendantsMap)
|
|
{
|
|
if (FindDescendantsMap.TryGetValue(result, out find))
|
|
FindDescendantsMap.Remove(result);
|
|
else
|
|
return;
|
|
}
|
|
Interlocked.Decrement(ref find.FoldersWaiting);
|
|
List<InventoryBase> folderContents = _Store.GetContents(updatedFolder);
|
|
foreach (InventoryBase obj in folderContents)
|
|
{
|
|
if (find.Regex.IsMatch(obj.Name))
|
|
{
|
|
find.Result.Add(obj);
|
|
if (find.FirstOnly)
|
|
{
|
|
find.IsCompleted = true;
|
|
return;
|
|
}
|
|
}
|
|
if (find.Recurse && obj is InventoryFolder)
|
|
{
|
|
Interlocked.Increment(ref find.FoldersWaiting);
|
|
lock (FindDescendantsMap)
|
|
{
|
|
IAsyncResult descendReq = BeginRequestFolderContents(
|
|
obj.UUID,
|
|
_Client.Network.AgentID,
|
|
true,
|
|
true,
|
|
true,
|
|
InventorySortOrder.ByName,
|
|
new AsyncCallback(SearchDescendantsCallback),
|
|
obj.UUID);
|
|
FindDescendantsMap.Add(descendReq, find);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Interlocked.Equals(find.FoldersWaiting, 0))
|
|
{
|
|
find.IsCompleted = true;
|
|
}
|
|
}
|
|
|
|
|
|
private List<InventoryBase> LocalFind(LLUUID baseFolder, Regex regexp, bool recurse, bool firstOnly)
|
|
{
|
|
List<InventoryBase> objects = new List<InventoryBase>();
|
|
List<InventoryFolder> folders = new List<InventoryFolder>();
|
|
|
|
List<InventoryBase> contents = _Store.GetContents(baseFolder);
|
|
foreach (InventoryBase inv in contents)
|
|
{
|
|
if (regexp.IsMatch(inv.Name))
|
|
{
|
|
objects.Add(inv);
|
|
if (firstOnly)
|
|
return objects;
|
|
}
|
|
if (inv is InventoryFolder)
|
|
{
|
|
folders.Add(inv as InventoryFolder);
|
|
}
|
|
}
|
|
// Recurse outside of the loop because subsequent calls to FindObjects may
|
|
// modify the baseNode.Nodes collection.
|
|
// FIXME: I'm pretty sure this is not necessary
|
|
if (recurse)
|
|
{
|
|
foreach (InventoryFolder folder in folders)
|
|
{
|
|
objects.AddRange(LocalFind(folder.UUID, regexp, true, firstOnly));
|
|
}
|
|
}
|
|
return objects;
|
|
}
|
|
|
|
private class FindObjectsByPathState
|
|
{
|
|
public FindResult Result;
|
|
public LLUUID Folder;
|
|
public int Level;
|
|
|
|
public FindObjectsByPathState(FindResult result, LLUUID folder, int level)
|
|
{
|
|
Result = result;
|
|
Folder = folder;
|
|
Level = level;
|
|
}
|
|
}
|
|
|
|
public List<InventoryBase> FindObjectsByPath(LLUUID baseFolder, string[] path, bool refresh, bool firstOnly)
|
|
{
|
|
IAsyncResult r = BeginFindObjectsByPath(baseFolder, path, refresh, firstOnly, null, null);
|
|
return EndFindObjects(r);
|
|
}
|
|
|
|
public IAsyncResult BeginFindObjectsByPath(LLUUID baseFolder, string[] path, bool refresh, bool firstOnly, AsyncCallback callback, object asyncState)
|
|
{
|
|
if (path.Length == 0)
|
|
throw new ArgumentException("Empty path is not supported");
|
|
|
|
FindResult result = new FindResult(path, callback);
|
|
result.FirstOnly = firstOnly;
|
|
result.AsyncState = asyncState;
|
|
|
|
if (refresh)
|
|
{
|
|
result.FoldersWaiting = 1;
|
|
BeginRequestFolderContents(
|
|
baseFolder,
|
|
_Client.Network.AgentID,
|
|
true,
|
|
true,
|
|
false,
|
|
InventorySortOrder.ByName,
|
|
new AsyncCallback(FindObjectsByPathCallback),
|
|
new FindObjectsByPathState(result, baseFolder, 0));
|
|
}
|
|
else
|
|
{
|
|
result.Result = LocalFind(baseFolder, path, 0, firstOnly);
|
|
result.CompletedSynchronously = true;
|
|
result.IsCompleted = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void FindObjectsByPathCallback(IAsyncResult result)
|
|
{
|
|
EndRequestFolderContents(result);
|
|
FindObjectsByPathState state = (FindObjectsByPathState)result.AsyncState;
|
|
|
|
Interlocked.Decrement(ref state.Result.FoldersWaiting);
|
|
List<InventoryBase> folderContents = _Store.GetContents(state.Folder);
|
|
|
|
foreach (InventoryBase obj in folderContents)
|
|
{
|
|
if (obj.Name.CompareTo(state.Result.Path[state.Level]) == 0)
|
|
{
|
|
if (state.Level == state.Result.Path.Length - 1)
|
|
{
|
|
state.Result.Result.Add(obj);
|
|
|
|
if (state.Result.FirstOnly)
|
|
{
|
|
state.Result.IsCompleted = true;
|
|
return;
|
|
}
|
|
}
|
|
else if (obj is InventoryFolder)
|
|
{
|
|
Interlocked.Increment(ref state.Result.FoldersWaiting);
|
|
BeginRequestFolderContents(
|
|
obj.UUID,
|
|
_Client.Network.AgentID,
|
|
true,
|
|
true,
|
|
false,
|
|
InventorySortOrder.ByName,
|
|
new AsyncCallback(FindObjectsByPathCallback),
|
|
new FindObjectsByPathState(state.Result, obj.UUID, state.Level + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Interlocked.Equals(state.Result.FoldersWaiting, 0))
|
|
state.Result.IsCompleted = true;
|
|
}
|
|
|
|
private List<InventoryBase> LocalFind(LLUUID baseFolder, string[] path, int level, bool firstOnly)
|
|
{
|
|
List<InventoryBase> objects = new List<InventoryBase>();
|
|
List<InventoryFolder> folders = new List<InventoryFolder>();
|
|
List<InventoryBase> contents = _Store.GetContents(baseFolder);
|
|
|
|
foreach (InventoryBase inv in contents)
|
|
{
|
|
if (inv.Name.CompareTo(path[level]) == 0)
|
|
{
|
|
if (level == path.Length - 1)
|
|
{
|
|
objects.Add(inv);
|
|
if (firstOnly) return objects;
|
|
}
|
|
else if (inv is InventoryFolder)
|
|
objects.AddRange(LocalFind(inv.UUID, path, level + 1, firstOnly));
|
|
}
|
|
}
|
|
|
|
return objects;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Folder Actions
|
|
|
|
public void RequestFolderContents(LLUUID folder, LLUUID owner, bool folders, bool items, bool recurse,
|
|
InventorySortOrder order)
|
|
{
|
|
EndRequestFolderContents(BeginRequestFolderContents(folder, owner, folders, items, recurse, order, null, null));
|
|
}
|
|
|
|
public IAsyncResult BeginRequestFolderContents(LLUUID folder, LLUUID owner, bool folders, bool items, bool recurse, InventorySortOrder order, AsyncCallback callback, object asyncState)
|
|
{
|
|
DescendantsResult result = new DescendantsResult(callback);
|
|
result.AsyncState = asyncState;
|
|
result.Folders = folders;
|
|
result.Items = items;
|
|
result.Recurse = recurse;
|
|
result.SortOrder = order;
|
|
return InternalFolderContentsRequest(folder, owner, result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the UUID of the folder (category) that defaults to
|
|
/// containing 'type'. The folder is not necessarily only for that
|
|
/// type
|
|
/// </summary>
|
|
/// <remarks>This will create a new inventory folder on the fly if
|
|
/// one does not exist</remarks>
|
|
/// <param name="type"></param>
|
|
/// <returns>The UUID of the desired or newly created folder, or
|
|
/// LLUUID.Zero on failure</returns>
|
|
public LLUUID FindFolderForType(AssetType type)
|
|
{
|
|
if (_Store == null)
|
|
{
|
|
_Client.Log("Inventory is null, FindFolderForType() lookup cannot continue",
|
|
Helpers.LogLevel.Error);
|
|
return LLUUID.Zero;
|
|
}
|
|
|
|
// Folders go in the root
|
|
if (type == AssetType.Folder)
|
|
return _Store.RootFolder.UUID;
|
|
|
|
// Loop through each top-level directory and check if PreferredType
|
|
// matches the requested type
|
|
List<InventoryBase> contents = _Store.GetContents(_Store.RootFolder.UUID);
|
|
foreach (InventoryBase inv in contents)
|
|
{
|
|
if (inv is InventoryFolder)
|
|
{
|
|
InventoryFolder folder = inv as InventoryFolder;
|
|
|
|
if (folder.PreferredType == type)
|
|
return folder.UUID;
|
|
}
|
|
}
|
|
|
|
// No match found, create one
|
|
return CreateFolder(_Store.RootFolder.UUID, type, String.Empty);
|
|
}
|
|
|
|
public LLUUID CreateFolder(LLUUID parentID, AssetType preferredType, string name)
|
|
{
|
|
LLUUID id = LLUUID.Random();
|
|
|
|
// Assign a folder name if one is not already set
|
|
if (String.IsNullOrEmpty(name))
|
|
{
|
|
if (preferredType >= AssetType.Texture && preferredType <= AssetType.Gesture)
|
|
{
|
|
name = _NewFolderNames[(int)preferredType];
|
|
}
|
|
else
|
|
{
|
|
name = "New Folder";
|
|
}
|
|
}
|
|
|
|
// 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.Network.AgentID;
|
|
|
|
try
|
|
{
|
|
_Store[newFolder.UUID] = newFolder;
|
|
}
|
|
catch (InventoryException ie)
|
|
{
|
|
_Client.Log(ie.Message, Helpers.LogLevel.Warning);
|
|
}
|
|
|
|
// Create the create folder packet and send it
|
|
CreateInventoryFolderPacket create = new CreateInventoryFolderPacket();
|
|
create.AgentData.AgentID = _Client.Network.AgentID;
|
|
create.AgentData.SessionID = _Client.Network.SessionID;
|
|
create.FolderData.FolderID = id;
|
|
create.FolderData.ParentID = parentID;
|
|
create.FolderData.Type = (sbyte)preferredType;
|
|
create.FolderData.Name = Helpers.StringToField(name);
|
|
|
|
_Client.Network.SendPacket(create);
|
|
|
|
return id;
|
|
}
|
|
|
|
public void RemoveDescendants(LLUUID folder)
|
|
{
|
|
PurgeInventoryDescendentsPacket purge = new PurgeInventoryDescendentsPacket();
|
|
purge.AgentData.AgentID = _Client.Network.AgentID;
|
|
purge.AgentData.SessionID = _Client.Network.SessionID;
|
|
purge.InventoryData.FolderID = folder;
|
|
_Client.Network.SendPacket(purge);
|
|
|
|
// Update our local copy:
|
|
if (_Store.Contains(folder))
|
|
{
|
|
List<InventoryBase> contents = _Store.GetContents(folder);
|
|
foreach (InventoryBase obj in contents) {
|
|
_Store.RemoveNodeFor(obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveFolder(LLUUID folder)
|
|
{
|
|
List<LLUUID> folders = new List<LLUUID>(1);
|
|
folders.Add(folder);
|
|
Remove(null, folders);
|
|
}
|
|
|
|
#endregion Folder Actions
|
|
|
|
#region Item Actions
|
|
|
|
public void BeginCreateItem(LLUUID 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
|
|
BeginCreateItem(parentFolder, name, description, type, invType, (WearableType)0, nextOwnerMask, callback);
|
|
}
|
|
|
|
public void BeginCreateItem(LLUUID parentFolder, string name, string description, AssetType type, InventoryType invType,
|
|
WearableType wearableType, PermissionMask nextOwnerMask, ItemCreatedCallback callback)
|
|
{
|
|
CreateInventoryItemPacket create = new CreateInventoryItemPacket();
|
|
create.AgentData.AgentID = _Client.Network.AgentID;
|
|
create.AgentData.SessionID = _Client.Network.SessionID;
|
|
|
|
create.InventoryBlock.CallbackID = RegisterInventoryCallback(callback);
|
|
create.InventoryBlock.FolderID = parentFolder;
|
|
create.InventoryBlock.TransactionID = LLUUID.Random();
|
|
create.InventoryBlock.NextOwnerMask = (uint)nextOwnerMask;
|
|
create.InventoryBlock.Type = (sbyte)type;
|
|
create.InventoryBlock.InvType = (sbyte)invType;
|
|
create.InventoryBlock.WearableType = (byte)wearableType;
|
|
create.InventoryBlock.Name = Helpers.StringToField(name);
|
|
create.InventoryBlock.Description = Helpers.StringToField(description);
|
|
|
|
_Client.Network.SendPacket(create);
|
|
}
|
|
|
|
public void BeginCreateItemFromAsset(byte[] data, string name, string description, AssetType assetType,
|
|
InventoryType invType, LLUUID folderID, ItemCreatedCallback callback)
|
|
{
|
|
string url = _Client.Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory");
|
|
|
|
if (url != String.Empty)
|
|
{
|
|
Hashtable query = new Hashtable();
|
|
query.Add("folder_id", folderID);
|
|
query.Add("asset_type", AssetTypeToString(assetType));
|
|
query.Add("inventory_type", InventoryTypeToString(invType));
|
|
query.Add("name", name);
|
|
query.Add("description", description);
|
|
|
|
byte[] postData = LLSD.LLSDSerialize(query);
|
|
|
|
// Make the request
|
|
CapsRequest request = new CapsRequest(url, _Client.Network.CurrentSim);
|
|
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CreateItemFromAssetResponse);
|
|
request.MakeRequest(postData, "application/xml", _Client.Network.CurrentSim.udpPort,
|
|
new KeyValuePair<ItemCreatedCallback, byte[]>(callback, data));
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("NewFileAgentInventory capability is not currently available");
|
|
}
|
|
}
|
|
|
|
private void CreateItemFromAssetResponse(object response, HttpRequestState state)
|
|
{
|
|
Hashtable contents = (Hashtable)response;
|
|
KeyValuePair<ItemCreatedCallback, byte[]> kvp = (KeyValuePair<ItemCreatedCallback, byte[]>)state.State;
|
|
ItemCreatedCallback callback = kvp.Key;
|
|
byte[] itemData = (byte[])kvp.Value;
|
|
|
|
string status = (string)contents["state"];
|
|
|
|
if (status == "upload")
|
|
{
|
|
string uploadURL = (string)contents["uploader"];
|
|
|
|
// This makes the assumption that all uploads go to CurrentSim, to avoid
|
|
// the problem of HttpRequestState not knowing anything about simulators
|
|
CapsRequest upload = new CapsRequest(uploadURL, _Client.Network.CurrentSim);
|
|
upload.OnCapsResponse += new CapsRequest.CapsResponseCallback(CreateItemFromAssetResponse);
|
|
upload.MakeRequest(itemData, "application/octet-stream", _Client.Network.CurrentSim.udpPort, kvp);
|
|
}
|
|
else if (status == "complete")
|
|
{
|
|
//FIXME: Callback successfully
|
|
callback(true, null);
|
|
}
|
|
else
|
|
{
|
|
// Failure
|
|
try { callback(false, null); }
|
|
catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
|
}
|
|
}
|
|
|
|
public void CopyItem(LLUUID currentOwner, LLUUID itemID, LLUUID parentID, string newName)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void CopyItemFromNotecard(LLUUID objectID, LLUUID notecardID, LLUUID folderID, LLUUID itemID)
|
|
{
|
|
CopyInventoryFromNotecardPacket copy = new CopyInventoryFromNotecardPacket();
|
|
copy.AgentData.AgentID = _Client.Network.AgentID;
|
|
copy.AgentData.SessionID = _Client.Network.SessionID;
|
|
|
|
copy.NotecardData.ObjectID = objectID;
|
|
copy.NotecardData.NotecardItemID = notecardID;
|
|
|
|
copy.InventoryData = new CopyInventoryFromNotecardPacket.InventoryDataBlock[1];
|
|
copy.InventoryData[0] = new CopyInventoryFromNotecardPacket.InventoryDataBlock();
|
|
copy.InventoryData[0].FolderID = folderID;
|
|
copy.InventoryData[0].ItemID = itemID;
|
|
|
|
_Client.Network.SendPacket(copy);
|
|
}
|
|
|
|
public void MoveItem(LLUUID itemID, LLUUID parentID, string newName)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void RemoveItem(LLUUID item)
|
|
{
|
|
List<LLUUID> items = new List<LLUUID>(1);
|
|
items.Add(item);
|
|
Remove(items, null);
|
|
}
|
|
|
|
public void GiveItem(LLUUID itemID, string itemName, AssetType assetType, LLUUID recipient, bool doEffect)
|
|
{
|
|
byte[] bucket = new byte[17];
|
|
bucket[0] = (byte)assetType;
|
|
Buffer.BlockCopy(itemID.GetBytes(), 0, bucket, 1, 16);
|
|
|
|
_Client.Self.InstantMessage(
|
|
_Client.Self.Name,
|
|
recipient,
|
|
itemName,
|
|
LLUUID.Random(),
|
|
InstantMessageDialog.InventoryOffered,
|
|
InstantMessageOnline.Online,
|
|
_Client.Self.Position,
|
|
_Client.Network.CurrentSim.ID,
|
|
bucket);
|
|
|
|
if (doEffect)
|
|
{
|
|
_Client.Self.BeamEffect(_Client.Network.AgentID, recipient, LLVector3d.Zero,
|
|
_Client.Settings.DEFAULT_EFFECT_COLOR, 1f, LLUUID.Random());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rez an object from inventory
|
|
/// </summary>
|
|
/// <param name="simulator">Simulator to place object in</param>
|
|
/// <param name="rotation">Rotation of the object when rezzed</param>
|
|
/// <param name="position">Vector of where to place object</param>
|
|
/// <param name="item">InventoryObject object containing item details</param>
|
|
public LLUUID RezFromInventory(Simulator simulator, LLQuaternion rotation, LLVector3 position, InventoryObject item)
|
|
{
|
|
return RezFromInventory(simulator, rotation, position, item, _Client.Self.ActiveGroup, LLUUID.Random());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rez an object from inventory
|
|
/// </summary>
|
|
/// <param name="simulator">Simulator to place object in</param>
|
|
/// <param name="rotation">Rotation of the object when rezzed</param>
|
|
/// <param name="position">Vector of where to place object</param>
|
|
/// <param name="item">InventoryObject object containing item details</param>
|
|
/// <param name="groupOwner">LLUUID of group to own the object</param>
|
|
public LLUUID RezFromInventory(Simulator simulator, LLQuaternion rotation, LLVector3 position, InventoryObject item,
|
|
LLUUID groupOwner)
|
|
{
|
|
return RezFromInventory(simulator, rotation, position, item, groupOwner, LLUUID.Random());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rez an object from inventory
|
|
/// </summary>
|
|
/// <param name="simulator">Simulator to place object in</param>
|
|
/// <param name="rotation">Rotation of the object when rezzed</param>
|
|
/// <param name="position">Vector of where to place object</param>
|
|
/// <param name="item">InventoryObject object containing item details</param>
|
|
/// <param name="groupOwner">LLUUID of group to own the object.</param>
|
|
/// <param name="queryID">User defined queryID to correlate replies.</param>
|
|
public LLUUID RezFromInventory(Simulator simulator, LLQuaternion rotation, LLVector3 position, InventoryObject item,
|
|
LLUUID groupOwner, LLUUID queryID)
|
|
{
|
|
RezObjectPacket add = new RezObjectPacket();
|
|
|
|
add.AgentData.AgentID = _Client.Network.AgentID;
|
|
add.AgentData.SessionID = _Client.Network.SessionID;
|
|
add.AgentData.GroupID = groupOwner;
|
|
|
|
add.RezData.FromTaskID = LLUUID.Zero;
|
|
add.RezData.BypassRaycast = 1;
|
|
add.RezData.RayStart = position;
|
|
add.RezData.RayEnd = position;
|
|
add.RezData.RayTargetID = LLUUID.Zero;
|
|
add.RezData.RayEndIsIntersection = false;
|
|
add.RezData.RezSelected = false;
|
|
add.RezData.RemoveItem = false;
|
|
add.RezData.ItemFlags = item.Flags;
|
|
add.RezData.GroupMask = (uint)item.Permissions.GroupMask;
|
|
add.RezData.EveryoneMask = (uint)item.Permissions.EveryoneMask;
|
|
add.RezData.NextOwnerMask = (uint)item.Permissions.NextOwnerMask;
|
|
|
|
add.InventoryData.ItemID = item.UUID;
|
|
add.InventoryData.FolderID = item.ParentUUID;
|
|
add.InventoryData.CreatorID = item.CreatorID;
|
|
add.InventoryData.OwnerID = item.OwnerID;
|
|
add.InventoryData.GroupID = item.GroupID;
|
|
add.InventoryData.BaseMask = (uint)item.Permissions.BaseMask;
|
|
add.InventoryData.OwnerMask = (uint)item.Permissions.OwnerMask;
|
|
add.InventoryData.GroupMask = (uint)item.Permissions.GroupMask;
|
|
add.InventoryData.EveryoneMask = (uint)item.Permissions.EveryoneMask;
|
|
add.InventoryData.NextOwnerMask = (uint)item.Permissions.NextOwnerMask;
|
|
add.InventoryData.GroupOwned = item.GroupOwned;
|
|
add.InventoryData.TransactionID = queryID;
|
|
add.InventoryData.Type = (sbyte)item.InventoryType;
|
|
add.InventoryData.InvType = (sbyte)item.InventoryType;
|
|
add.InventoryData.Flags = item.Flags;
|
|
add.InventoryData.SaleType = (byte)item.SaleType;
|
|
add.InventoryData.SalePrice = item.SalePrice;
|
|
add.InventoryData.Name = Helpers.StringToField(item.Name);
|
|
add.InventoryData.Description = Helpers.StringToField(item.Description);
|
|
add.InventoryData.CreationDate = (int)Helpers.DateTimeToUnixTime(item.CreationDate);
|
|
|
|
_Client.Network.SendPacket(add, simulator);
|
|
return queryID;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
public string AssetTypeToString(AssetType type)
|
|
{
|
|
return _AssetTypeNames[(int)type];
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
public string InventoryTypeToString(InventoryType type)
|
|
{
|
|
return _InventoryTypeNames[(int)type];
|
|
}
|
|
|
|
internal void InitializeRootNode(LLUUID rootFolderID)
|
|
{
|
|
InventoryFolder rootFolder = new InventoryFolder(rootFolderID);
|
|
rootFolder.Name = String.Empty;
|
|
rootFolder.ParentUUID = LLUUID.Zero;
|
|
|
|
_Store = new Inventory(_Client, this, rootFolder);
|
|
}
|
|
|
|
#region Private Helper Functions
|
|
|
|
private uint RegisterInventoryCallback(ItemCreatedCallback callback)
|
|
{
|
|
if (_ItemCreatedCallbackPos == UInt32.MaxValue)
|
|
_ItemCreatedCallbackPos = 0;
|
|
|
|
_ItemCreatedCallbackPos++;
|
|
|
|
if (_ItemCreatedCallbacks.ContainsKey(_ItemCreatedCallbackPos))
|
|
_Client.Log("Overwriting an existing ItemCreatedCallback", Helpers.LogLevel.Warning);
|
|
|
|
_ItemCreatedCallbacks[_ItemCreatedCallbackPos] = callback;
|
|
|
|
return _ItemCreatedCallbackPos;
|
|
}
|
|
|
|
private InventoryItem CreateInventoryItem(InventoryType type, LLUUID 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 void EndRequestFolderContents(IAsyncResult result)
|
|
{
|
|
result.AsyncWaitHandle.WaitOne();
|
|
}
|
|
|
|
private DescendantsResult InternalFolderContentsRequest(LLUUID folder, LLUUID owner, DescendantsResult parameters)
|
|
{
|
|
lock (_FolderRequests)
|
|
{
|
|
List<DescendantsResult> requestsForFolder;
|
|
if (!_FolderRequests.TryGetValue(folder, out requestsForFolder))
|
|
{
|
|
requestsForFolder = new List<DescendantsResult>();
|
|
_FolderRequests.Add(folder, requestsForFolder);
|
|
}
|
|
lock (requestsForFolder)
|
|
requestsForFolder.Add(parameters);
|
|
}
|
|
|
|
FetchInventoryDescendentsPacket fetch = new FetchInventoryDescendentsPacket();
|
|
fetch.AgentData.AgentID = _Client.Network.AgentID;
|
|
fetch.AgentData.SessionID = _Client.Network.SessionID;
|
|
|
|
fetch.InventoryData.FetchFolders = parameters.Folders;
|
|
fetch.InventoryData.FetchItems = parameters.Items;
|
|
fetch.InventoryData.FolderID = folder;
|
|
fetch.InventoryData.OwnerID = owner;
|
|
fetch.InventoryData.SortOrder = (int)parameters.SortOrder;
|
|
|
|
_Client.Network.SendPacket(fetch);
|
|
return parameters;
|
|
}
|
|
|
|
private void HandleDescendantsRetrieved(LLUUID uuid)
|
|
{
|
|
List<DescendantsResult> satisfiedResults = null;
|
|
lock (_FolderRequests)
|
|
{
|
|
if (_FolderRequests.TryGetValue(uuid, out satisfiedResults))
|
|
_FolderRequests.Remove(uuid);
|
|
}
|
|
if (satisfiedResults == null)
|
|
return;
|
|
lock (satisfiedResults)
|
|
{
|
|
List<InventoryBase> contents = _Store.GetContents(uuid);
|
|
foreach (DescendantsResult result in satisfiedResults)
|
|
{
|
|
if (result.Recurse)
|
|
{
|
|
bool done = true;
|
|
|
|
foreach (InventoryBase obj in contents)
|
|
{
|
|
if (obj is InventoryFolder)
|
|
{
|
|
done = false;
|
|
DescendantsResult child = new DescendantsResult(null);
|
|
child.Folders = result.Folders;
|
|
child.Items = result.Items;
|
|
child.Recurse = result.Recurse;
|
|
child.SortOrder = result.SortOrder;
|
|
child.Parent = result;
|
|
result.AddChild(child);
|
|
InternalFolderContentsRequest(obj.UUID, obj.OwnerID, child);
|
|
}
|
|
}
|
|
if (done)
|
|
result.IsCompleted = true;
|
|
}
|
|
else
|
|
{
|
|
result.IsCompleted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Private Helper Functions
|
|
|
|
#region Callbacks
|
|
|
|
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;
|
|
}
|
|
|
|
private void InventoryDescendentsHandler(Packet packet, Simulator simulator)
|
|
{
|
|
InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet;
|
|
InventoryFolder parentFolder = null;
|
|
|
|
if (_Store.Contains(reply.AgentData.FolderID) &&
|
|
_Store[reply.AgentData.FolderID] is InventoryFolder)
|
|
{
|
|
parentFolder = _Store[reply.AgentData.FolderID] as InventoryFolder;
|
|
}
|
|
else
|
|
{
|
|
_Client.Log("Don't have a reference to FolderID " + reply.AgentData.FolderID.ToStringHyphenated() +
|
|
" or it is not a folder", Helpers.LogLevel.Error);
|
|
return;
|
|
}
|
|
|
|
if (reply.AgentData.Version < parentFolder.Version)
|
|
{
|
|
_Client.Log("Got an outdated InventoryDescendents packet for folder " + parentFolder.Name +
|
|
", this version = " + reply.AgentData.Version + ", latest version = " + parentFolder.Version,
|
|
Helpers.LogLevel.Warning);
|
|
return;
|
|
}
|
|
|
|
if (reply.AgentData.Descendents > 0)
|
|
{
|
|
// InventoryDescendantsReply sends a null folder if the parent doesnt contain any folders.
|
|
if (reply.FolderData[0].FolderID != LLUUID.Zero)
|
|
{
|
|
// Iterate folders in this packet
|
|
for (int i = 0; i < reply.FolderData.Length; i++)
|
|
{
|
|
InventoryFolder folder = new InventoryFolder(reply.FolderData[i].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;
|
|
}
|
|
}
|
|
|
|
// InventoryDescendantsReply sends a null item if the parent doesnt contain any items.
|
|
if (reply.ItemData[0].ItemID != LLUUID.Zero)
|
|
{
|
|
// Iterate items in this packet
|
|
for (int i = 0; i < reply.ItemData.Length; i++)
|
|
{
|
|
if (reply.ItemData[i].ItemID != LLUUID.Zero)
|
|
{
|
|
InventoryItem 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;
|
|
item.AssetUUID = reply.ItemData[i].AssetID;
|
|
item.CreationDate = Helpers.UnixTimeToDateTime((uint)reply.ItemData[i].CreationDate);
|
|
item.Description = Helpers.FieldToUTF8String(reply.ItemData[i].Description);
|
|
item.Flags = reply.ItemData[i].Flags;
|
|
item.Name = Helpers.FieldToUTF8String(reply.ItemData[i].Name);
|
|
item.GroupID = reply.ItemData[i].GroupID;
|
|
item.GroupOwned = reply.ItemData[i].GroupOwned;
|
|
item.Permissions = new Permissions(
|
|
reply.ItemData[i].BaseMask,
|
|
reply.ItemData[i].EveryoneMask,
|
|
reply.ItemData[i].GroupMask,
|
|
reply.ItemData[i].NextOwnerMask,
|
|
reply.ItemData[i].OwnerMask);
|
|
item.SalePrice = reply.ItemData[i].SalePrice;
|
|
item.SaleType = (SaleType)reply.ItemData[i].SaleType;
|
|
item.OwnerID = reply.AgentData.OwnerID;
|
|
|
|
_Store[item.UUID] = item;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
parentFolder.Version = reply.AgentData.Version;
|
|
parentFolder.DescendentCount = reply.AgentData.Descendents;
|
|
|
|
if (OnInventoryFolderUpdated != null)
|
|
{
|
|
try { OnInventoryFolderUpdated(parentFolder.UUID); }
|
|
catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
|
}
|
|
|
|
// For RequestFolderContents - only call the handler if we've retrieved all the descendants.
|
|
if (_FolderRequests.ContainsKey(parentFolder.UUID) && parentFolder.DescendentCount == _Store.GetContents(parentFolder.UUID).Count)
|
|
HandleDescendantsRetrieved(parentFolder.UUID);
|
|
}
|
|
|
|
/// <summary>
|
|
/// UpdateCreateInventoryItem packets are received when a new inventory item
|
|
/// is created. This may occur when an object that's rezzed in world is
|
|
/// taken into inventory, when an item is created using the CreateInventoryItem
|
|
/// packet, or when an object is purchased.
|
|
/// </summary>
|
|
private void UpdateCreateInventoryItemHandler(Packet packet, Simulator simulator)
|
|
{
|
|
UpdateCreateInventoryItemPacket reply = packet as UpdateCreateInventoryItemPacket;
|
|
|
|
foreach (UpdateCreateInventoryItemPacket.InventoryDataBlock dataBlock in reply.InventoryData)
|
|
{
|
|
if (dataBlock.InvType == (sbyte)InventoryType.Folder) {
|
|
_Client.Log("Received InventoryFolder in an UpdateCreateInventoryItem packet.", Helpers.LogLevel.Error);
|
|
continue;
|
|
}
|
|
|
|
InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID);
|
|
item.AssetType = (AssetType)dataBlock.Type;
|
|
item.AssetUUID = dataBlock.AssetID;
|
|
item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate);
|
|
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;
|
|
|
|
ItemCreatedCallback callback;
|
|
if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out callback))
|
|
{
|
|
_ItemCreatedCallbacks.Remove(dataBlock.CallbackID);
|
|
|
|
try { callback(true, item); }
|
|
catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
_Client.DebugLog(String.Format("MoveInventoryItemHandler: Item {0} is moving to Folder {1} with new name \"{2}\"",
|
|
move.InventoryData[i].ItemID.ToStringHyphenated(), move.InventoryData[i].FolderID.ToStringHyphenated(),
|
|
newName));
|
|
}
|
|
}
|
|
|
|
private void BulkUpdateInventoryHandler(Packet packet, Simulator simulator)
|
|
{
|
|
BulkUpdateInventoryPacket update = packet as BulkUpdateInventoryPacket;
|
|
|
|
if (update.FolderData.Length > 0 && update.FolderData[0].FolderID != LLUUID.Zero)
|
|
{
|
|
foreach (BulkUpdateInventoryPacket.FolderDataBlock dataBlock in update.FolderData)
|
|
{
|
|
|
|
if (_Store.Contains(dataBlock.FolderID))
|
|
_Client.Log("Received BulkUpdate for unknown folder: " + dataBlock.FolderID, Helpers.LogLevel.Warning);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (update.ItemData.Length > 0 && update.ItemData[0].ItemID != LLUUID.Zero)
|
|
{
|
|
foreach (BulkUpdateInventoryPacket.ItemDataBlock dataBlock in update.ItemData)
|
|
{
|
|
if (!_Store.Contains(dataBlock.ItemID))
|
|
_Client.Log("Received BulkUpdate for unknown item: " + dataBlock.ItemID, Helpers.LogLevel.Warning);
|
|
|
|
// FIXME: Write a helper function that will either fetch an item out of the store or create it
|
|
// and use that here instead, to prevent overwriting already fetched AssetIDs on an update
|
|
InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType, dataBlock.ItemID);
|
|
|
|
item.AssetType = (AssetType)dataBlock.Type;
|
|
if (dataBlock.AssetID != LLUUID.Zero) item.AssetUUID = dataBlock.AssetID;
|
|
item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate);
|
|
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;
|
|
|
|
ItemCreatedCallback callback;
|
|
if (_ItemCreatedCallbacks.TryGetValue(dataBlock.CallbackID, out callback))
|
|
{
|
|
_ItemCreatedCallbacks.Remove(dataBlock.CallbackID);
|
|
|
|
try { callback(true, item); }
|
|
catch (Exception e) { _Client.Log(e.ToString(), Helpers.LogLevel.Error); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void FetchInventoryReplyHandler(Packet packet, Simulator simulator)
|
|
{
|
|
FetchInventoryReplyPacket reply = packet as FetchInventoryReplyPacket;
|
|
|
|
foreach (FetchInventoryReplyPacket.InventoryDataBlock dataBlock in reply.InventoryData)
|
|
{
|
|
if (dataBlock.InvType == (sbyte)InventoryType.Folder)
|
|
{
|
|
_Client.Log("Received FetchInventoryReply for inventory folder!", Helpers.LogLevel.Error);
|
|
continue;
|
|
}
|
|
|
|
InventoryItem item = CreateInventoryItem((InventoryType)dataBlock.InvType,dataBlock.ItemID);
|
|
item.AssetType = (AssetType)dataBlock.Type;
|
|
item.AssetUUID = dataBlock.AssetID;
|
|
item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate);
|
|
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;
|
|
}
|
|
}
|
|
private void Self_OnInstantMessage(InstantMessage im, Simulator simulator)
|
|
{
|
|
// TODO: MainAvatar.InstantMessageDialog.GroupNotice can also be an inventory offer, should we
|
|
// handle it here?
|
|
|
|
if (OnInventoryObjectReceived != null &&
|
|
(im.Dialog == InstantMessageDialog.InventoryOffered || im.Dialog == InstantMessageDialog.TaskInventoryOffered))
|
|
{
|
|
AssetType type = AssetType.Unknown;
|
|
LLUUID objectID = LLUUID.Zero;
|
|
bool fromTask = false;
|
|
|
|
if (im.Dialog == InstantMessageDialog.InventoryOffered)
|
|
{
|
|
if (im.BinaryBucket.Length == 17)
|
|
{
|
|
type = (AssetType)im.BinaryBucket[0];
|
|
objectID = new LLUUID(im.BinaryBucket, 1);
|
|
fromTask = false;
|
|
}
|
|
else
|
|
{
|
|
_Client.Log("Malformed inventory offer from agent", Helpers.LogLevel.Warning);
|
|
return;
|
|
}
|
|
}
|
|
else if (im.Dialog == InstantMessageDialog.TaskInventoryOffered)
|
|
{
|
|
if (im.BinaryBucket.Length == 1)
|
|
{
|
|
type = (AssetType)im.BinaryBucket[0];
|
|
fromTask = true;
|
|
}
|
|
else
|
|
{
|
|
_Client.Log("Malformed inventory offer from object", Helpers.LogLevel.Warning);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Find the folder where this is going to go
|
|
LLUUID destinationFolderID = FindFolderForType(type);
|
|
|
|
// Fire the callback
|
|
try
|
|
{
|
|
ImprovedInstantMessagePacket imp = new ImprovedInstantMessagePacket();
|
|
imp.AgentData.AgentID = _Client.Network.AgentID;
|
|
imp.AgentData.SessionID = _Client.Network.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.Message = new byte[0];
|
|
imp.MessageBlock.ParentEstateID = 0;
|
|
imp.MessageBlock.RegionID = LLUUID.Zero;
|
|
imp.MessageBlock.Position = _Client.Self.Position;
|
|
|
|
if (OnInventoryObjectReceived(im.FromAgentID, im.FromAgentName, im.ParentEstateID, im.RegionID, im.Position,
|
|
im.Timestamp, type, objectID, fromTask))
|
|
{
|
|
// Accept the inventory offer
|
|
switch (im.Dialog)
|
|
{
|
|
case InstantMessageDialog.InventoryOffered:
|
|
imp.MessageBlock.Dialog = (byte)InstantMessageDialog.InventoryAccepted;
|
|
break;
|
|
case InstantMessageDialog.TaskInventoryOffered:
|
|
imp.MessageBlock.Dialog = (byte)InstantMessageDialog.TaskInventoryOffered;
|
|
break;
|
|
case InstantMessageDialog.GroupNotice:
|
|
imp.MessageBlock.Dialog = (byte)InstantMessageDialog.GroupNoticeInventoryAccepted;
|
|
break;
|
|
}
|
|
|
|
imp.MessageBlock.BinaryBucket = destinationFolderID.GetBytes();
|
|
}
|
|
else
|
|
{
|
|
// Decline the inventory offer
|
|
switch (im.Dialog)
|
|
{
|
|
case InstantMessageDialog.InventoryOffered:
|
|
imp.MessageBlock.Dialog = (byte)InstantMessageDialog.InventoryDeclined;
|
|
break;
|
|
case InstantMessageDialog.TaskInventoryOffered:
|
|
imp.MessageBlock.Dialog = (byte)InstantMessageDialog.TaskInventoryDeclined;
|
|
break;
|
|
case InstantMessageDialog.GroupNotice:
|
|
imp.MessageBlock.Dialog = (byte)InstantMessageDialog.GroupNoticeInventoryDeclined;
|
|
break;
|
|
}
|
|
|
|
imp.MessageBlock.BinaryBucket = new byte[0];
|
|
}
|
|
|
|
_Client.Network.SendPacket(imp, simulator);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_Client.Log(e.ToString(), Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Callbacks
|
|
}
|
|
|
|
class FindResult : IAsyncResult
|
|
{
|
|
public List<InventoryBase> Result;
|
|
public int FoldersWaiting;
|
|
public bool FirstOnly;
|
|
|
|
private AsyncCallback callback;
|
|
private Regex regex;
|
|
private string[] path;
|
|
private bool recurse;
|
|
private AutoResetEvent waitHandle;
|
|
private bool complete;
|
|
private bool sync;
|
|
private object asyncstate;
|
|
|
|
#region Properties
|
|
|
|
public bool Recurse { get { return recurse; } }
|
|
public Regex Regex { get { return regex; } }
|
|
public string[] Path { get { return path; } }
|
|
public AsyncCallback Callback { get { return callback; } }
|
|
|
|
#region IAsyncResult Members
|
|
|
|
public object AsyncState
|
|
{
|
|
get { return asyncstate; }
|
|
set { asyncstate = value; }
|
|
}
|
|
|
|
public WaitHandle AsyncWaitHandle
|
|
{
|
|
get { return waitHandle; }
|
|
}
|
|
|
|
public bool CompletedSynchronously
|
|
{
|
|
get { return sync; }
|
|
set { sync = value; }
|
|
}
|
|
|
|
public bool IsCompleted
|
|
{
|
|
get { return complete; }
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
waitHandle.Set();
|
|
if (callback != null)
|
|
{
|
|
callback(this);
|
|
}
|
|
}
|
|
complete = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion Properties
|
|
|
|
public FindResult(Regex regex, bool recurse, AsyncCallback callback)
|
|
{
|
|
this.waitHandle = new AutoResetEvent(false);
|
|
this.callback = callback;
|
|
this.recurse = recurse;
|
|
this.regex = regex;
|
|
this.Result = new List<InventoryBase>();
|
|
}
|
|
|
|
public FindResult(string[] path, AsyncCallback callback)
|
|
{
|
|
this.waitHandle = new AutoResetEvent(false);
|
|
this.callback = callback;
|
|
this.path = path;
|
|
this.Result = new List<InventoryBase>();
|
|
}
|
|
}
|
|
|
|
class DescendantsResult : IAsyncResult
|
|
{
|
|
public bool Folders = true;
|
|
public bool Items = true;
|
|
public bool Recurse = false;
|
|
public InventorySortOrder SortOrder = InventorySortOrder.ByName;
|
|
public DescendantsResult Parent;
|
|
|
|
private AsyncCallback _Callback;
|
|
private ManualResetEvent _AsyncWaitHandle;
|
|
private object _AsyncState;
|
|
private bool _IsCompleted;
|
|
private List<DescendantsResult> _ChildrenWaiting = new List<DescendantsResult>();
|
|
|
|
#region Properties
|
|
|
|
#region IAsyncResult Members
|
|
|
|
public object AsyncState
|
|
{
|
|
get { return _AsyncState; }
|
|
set { _AsyncState = value; }
|
|
}
|
|
|
|
public WaitHandle AsyncWaitHandle
|
|
{
|
|
get { return _AsyncWaitHandle; }
|
|
}
|
|
|
|
public bool CompletedSynchronously
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
public bool IsCompleted
|
|
{
|
|
get { return _IsCompleted; }
|
|
set
|
|
{
|
|
_IsCompleted = value;
|
|
if (value)
|
|
{
|
|
if (_ChildrenWaiting.Count == 0)
|
|
{
|
|
if (Parent != null)
|
|
{
|
|
Parent.ChildComplete(this);
|
|
}
|
|
else
|
|
{
|
|
_AsyncWaitHandle.Set();
|
|
if (_Callback != null)
|
|
_Callback(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion Properties
|
|
|
|
public DescendantsResult(AsyncCallback callback)
|
|
{
|
|
_Callback = callback;
|
|
_AsyncWaitHandle = new ManualResetEvent(false);
|
|
}
|
|
|
|
public void AddChild(DescendantsResult child)
|
|
{
|
|
lock (_ChildrenWaiting)
|
|
{
|
|
if (!child.IsCompleted)
|
|
_ChildrenWaiting.Add(child);
|
|
}
|
|
}
|
|
|
|
public void ChildComplete(DescendantsResult child)
|
|
{
|
|
lock (_ChildrenWaiting)
|
|
{
|
|
_ChildrenWaiting.Remove(child);
|
|
if (_ChildrenWaiting.Count == 0)
|
|
IsCompleted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|