* Transfer timeout support for uploads. This code will be deprecated soon though as CAPS uploading is almost finished

* 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
This commit is contained in:
John Hurliman
2007-09-10 10:20:30 +00:00
parent 9dccf908da
commit 62dddabd7c
12 changed files with 986 additions and 529 deletions

View File

@@ -6,6 +6,8 @@ using libsecondlife.Packets;
namespace libsecondlife
{
#region Enums
/// <summary>
/// The different types of assets in Second Life
/// </summary>
@@ -145,17 +147,42 @@ namespace libsecondlife
Baked = 1
}
#endregion Enums
#region Transfer Classes
/// <summary>
///
/// </summary>
public class Transfer
{
public delegate void Timeout(Transfer transfer);
public event Timeout OnTimeout;
public LLUUID ID = LLUUID.Zero;
public int Size = 0;
public byte[] AssetData = new byte[0];
public int Transferred = 0;
public bool Success = false;
public AssetType AssetType = AssetType.Unknown;
internal System.Timers.Timer TransferTimer = new System.Timers.Timer(Settings.TRANSFER_TIMEOUT);
public Transfer()
{
TransferTimer.AutoReset = false;
TransferTimer.Elapsed += new System.Timers.ElapsedEventHandler(TransferTimer_Elapsed);
}
private void TransferTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (OnTimeout != null)
{
try { OnTimeout(this); }
catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); }
}
}
}
/// <summary>
@@ -199,12 +226,15 @@ namespace libsecondlife
public uint PacketNum = 0;
}
#endregion Transfer Classes
/// <summary>
///
/// </summary>
public class AssetManager
{
#region Delegates
/// <summary>
///
/// </summary>
@@ -226,6 +256,10 @@ namespace libsecondlife
/// <param name="upload"></param>
public delegate void UploadProgressCallback(AssetUpload upload);
#endregion Delegates
#region Events
/// <summary>
///
/// </summary>
@@ -243,6 +277,8 @@ namespace libsecondlife
/// </summary>
public event UploadProgressCallback OnUploadProgress;
#endregion Events
private SecondLife Client;
private Dictionary<LLUUID, Transfer> Transfers = new Dictionary<LLUUID, Transfer>();
@@ -441,6 +477,8 @@ namespace libsecondlife
upload.AssetID = assetID;
upload.Size = data.Length;
upload.XferID = 0;
upload.TransferTimer.Interval = 10 * 1000; // 10 second timeout for no upload packet confirmation
upload.OnTimeout += new Transfer.Timeout(Transfer_OnTimeout);
// Build and send the upload packet
AssetUploadRequestPacket request = new AssetUploadRequestPacket();
@@ -498,6 +536,12 @@ namespace libsecondlife
Buffer.BlockCopy(Helpers.IntToBytes(upload.Size), 0, send.DataPacket.Data, 0, 4);
Buffer.BlockCopy(upload.AssetData, 0, send.DataPacket.Data, 4, 1000);
upload.Transferred += 1000;
lock (Transfers)
{
Transfers.Remove(upload.AssetID);
Transfers[upload.ID] = upload;
}
}
else if ((send.XferID.Packet + 1) * 1000 < upload.Size)
{
@@ -520,49 +564,86 @@ namespace libsecondlife
Client.Network.SendPacket(send);
}
private void Transfer_OnTimeout(Transfer transfer)
{
if (transfer is AssetUpload)
{
AssetUpload upload = (AssetUpload)transfer;
LLUUID transferID = new LLUUID(upload.XferID);
if (Transfers.ContainsKey(transferID))
{
Client.Log(String.Format(
"Timed out waiting for an ACK during asset upload {0}, rolling back to packet number {1}",
upload.AssetID.ToStringHyphenated(), (upload.PacketNum - 1)), Helpers.LogLevel.Info);
// Resend the last block of data and reset the timeout timer
upload.PacketNum--;
upload.Transferred -= 1000;
upload.TransferTimer.Start();
SendNextUploadPacket(upload);
}
else
{
Client.Log(String.Format("Upload {0} (Type: {1}, Success: {2}) timed out but is not being tracked",
upload.ID.ToStringHyphenated(), upload.AssetType, upload.Success), Helpers.LogLevel.Warning);
}
}
else
{
if (Transfers.ContainsKey(transfer.ID))
{
// TODO: Implement something here when timeouts for downloads are turned on
}
}
}
private void TransferInfoHandler(Packet packet, Simulator simulator)
{
if (OnAssetReceived != null)
{
TransferInfoPacket info = (TransferInfoPacket)packet;
Transfer transfer;
AssetDownload download;
if (Transfers.ContainsKey(info.TransferInfo.TransferID))
if (Transfers.TryGetValue(info.TransferInfo.TransferID, out transfer))
{
AssetDownload transfer = (AssetDownload)Transfers[info.TransferInfo.TransferID];
download = (AssetDownload)transfer;
transfer.Channel = (ChannelType)info.TransferInfo.ChannelType;
transfer.Status = (StatusCode)info.TransferInfo.Status;
transfer.Target = (TargetType)info.TransferInfo.TargetType;
transfer.Size = info.TransferInfo.Size;
download.Channel = (ChannelType)info.TransferInfo.ChannelType;
download.Status = (StatusCode)info.TransferInfo.Status;
download.Target = (TargetType)info.TransferInfo.TargetType;
download.Size = info.TransferInfo.Size;
// TODO: Once we support mid-transfer status checking and aborting this
// will need to become smarter
if (transfer.Status != StatusCode.OK)
if (download.Status != StatusCode.OK)
{
Client.Log("Transfer failed with status code " + transfer.Status, Helpers.LogLevel.Warning);
Client.Log("Transfer failed with status code " + download.Status, Helpers.LogLevel.Warning);
lock (Transfers) Transfers.Remove(transfer.ID);
lock (Transfers) Transfers.Remove(download.ID);
// No data could have been received before the TransferInfo packet
transfer.AssetData = null;
download.AssetData = null;
// Fire the event with our transfer that contains Success = false;
try { OnAssetReceived(transfer, null); }
try { OnAssetReceived(download, null); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
else
{
transfer.AssetData = new byte[transfer.Size];
download.AssetData = new byte[download.Size];
if (transfer.Source == SourceType.Asset && info.TransferInfo.Params.Length == 20)
if (download.Source == SourceType.Asset && info.TransferInfo.Params.Length == 20)
{
transfer.AssetID = new LLUUID(info.TransferInfo.Params, 0);
transfer.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[16];
download.AssetID = new LLUUID(info.TransferInfo.Params, 0);
download.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[16];
//Client.DebugLog(String.Format("TransferInfo packet received. AssetID: {0} Type: {1}",
// transfer.AssetID, type));
}
else if (transfer.Source == SourceType.SimInventoryItem && info.TransferInfo.Params.Length == 100)
else if (download.Source == SourceType.SimInventoryItem && info.TransferInfo.Params.Length == 100)
{
// TODO: Can we use these?
LLUUID agentID = new LLUUID(info.TransferInfo.Params, 0);
@@ -570,8 +651,8 @@ namespace libsecondlife
LLUUID ownerID = new LLUUID(info.TransferInfo.Params, 32);
LLUUID taskID = new LLUUID(info.TransferInfo.Params, 48);
LLUUID itemID = new LLUUID(info.TransferInfo.Params, 64);
transfer.AssetID = new LLUUID(info.TransferInfo.Params, 80);
transfer.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[96];
download.AssetID = new LLUUID(info.TransferInfo.Params, 80);
download.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[96];
//Client.DebugLog(String.Format("TransferInfo packet received. AgentID: {0} SessionID: {1} " +
// "OwnerID: {2} TaskID: {3} ItemID: {4} AssetID: {5} Type: {6}", agentID, sessionID,
@@ -579,7 +660,7 @@ namespace libsecondlife
}
else
{
Client.Log("Received a TransferInfo packet with a SourceType of " + transfer.Source.ToString() +
Client.Log("Received a TransferInfo packet with a SourceType of " + download.Source.ToString() +
" and a Params field length of " + info.TransferInfo.Params.Length,
Helpers.LogLevel.Warning);
}
@@ -596,36 +677,42 @@ namespace libsecondlife
private void TransferPacketHandler(Packet packet, Simulator simulator)
{
TransferPacketPacket asset = (TransferPacketPacket)packet;
Transfer transfer;
AssetDownload download;
if (Transfers.ContainsKey(asset.TransferData.TransferID))
if (Transfers.TryGetValue(asset.TransferData.TransferID, out transfer))
{
AssetDownload transfer = (AssetDownload)Transfers[asset.TransferData.TransferID];
download = (AssetDownload)transfer;
if (transfer.Size == 0)
// Reset the transfer timer
download.TransferTimer.Stop();
download.TransferTimer.Start();
if (download.Size == 0)
{
Client.DebugLog("TransferPacket received ahead of the transfer header, blocking...");
// We haven't received the header yet, block until it's received or times out
transfer.HeaderReceivedEvent.WaitOne(1000 * 20, false);
download.HeaderReceivedEvent.WaitOne(1000 * 20, false);
if (transfer.Size == 0)
if (download.Size == 0)
{
Client.Log("Timed out while waiting for the asset header to download for " +
transfer.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
download.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
// Abort the transfer
TransferAbortPacket abort = new TransferAbortPacket();
abort.TransferInfo.ChannelType = (int)transfer.Channel;
abort.TransferInfo.TransferID = transfer.ID;
Client.Network.SendPacket(abort, transfer.Simulator);
abort.TransferInfo.ChannelType = (int)download.Channel;
abort.TransferInfo.TransferID = download.ID;
Client.Network.SendPacket(abort, download.Simulator);
transfer.Success = false;
lock (Transfers) Transfers.Remove(transfer.ID);
download.Success = false;
lock (Transfers) Transfers.Remove(download.ID);
// Fire the event with our transfer that contains Success = false
if (OnAssetReceived != null)
{
try { OnAssetReceived(transfer, null); }
try { OnAssetReceived(download, null); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
@@ -635,25 +722,25 @@ namespace libsecondlife
// This assumes that every transfer packet except the last one is exactly 1000 bytes,
// hopefully that is a safe assumption to make
Buffer.BlockCopy(asset.TransferData.Data, 0, transfer.AssetData, 1000 * asset.TransferData.Packet,
Buffer.BlockCopy(asset.TransferData.Data, 0, download.AssetData, 1000 * asset.TransferData.Packet,
asset.TransferData.Data.Length);
transfer.Transferred += asset.TransferData.Data.Length;
download.Transferred += asset.TransferData.Data.Length;
//Client.DebugLog(String.Format("Transfer packet {0}, received {1}/{2}/{3} bytes for asset {4}",
// asset.TransferData.Packet, asset.TransferData.Data.Length, transfer.Transferred, transfer.Size,
// transfer.AssetID.ToStringHyphenated()));
// Check if we downloaded the full asset
if (transfer.Transferred >= transfer.Size)
if (download.Transferred >= download.Size)
{
Client.DebugLog("Transfer for asset " + transfer.AssetID.ToStringHyphenated() + " completed");
Client.DebugLog("Transfer for asset " + download.AssetID.ToStringHyphenated() + " completed");
transfer.Success = true;
lock (Transfers) Transfers.Remove(transfer.ID);
download.Success = true;
lock (Transfers) Transfers.Remove(download.ID);
if (OnAssetReceived != null)
{
try { OnAssetReceived(transfer, WrapAsset(transfer)); }
try { OnAssetReceived(download, WrapAsset(download)); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
@@ -750,26 +837,28 @@ namespace libsecondlife
// Building a new UUID every time an ACK is received for an upload is a horrible
// thing, but this whole Xfer system is horrible
LLUUID transferID = new LLUUID(confirm.XferID.ID);
Transfer transfer;
AssetUpload upload = null;
lock (Transfers)
if (Transfers.TryGetValue(transferID, out transfer))
{
if (Transfers.ContainsKey(transferID))
upload = (AssetUpload)transfer;
//Client.DebugLog(String.Format("ACK for upload {0} of asset type {1} ({2}/{3})",
// upload.AssetID.ToStringHyphenated(), upload.Type, upload.Transferred, upload.Size));
// Reset the transfer timer
upload.TransferTimer.Stop();
upload.TransferTimer.Start();
if (OnUploadProgress != null)
{
upload = (AssetUpload)Transfers[transferID];
//Client.DebugLog(String.Format("ACK for upload {0} of asset type {1} ({2}/{3})",
// upload.AssetID.ToStringHyphenated(), upload.Type, upload.Transferred, upload.Size));
if (OnUploadProgress != null)
{
try { OnUploadProgress(upload); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
if (upload.Transferred < upload.Size)
SendNextUploadPacket((AssetUpload)Transfers[transferID]);
try { OnUploadProgress(upload); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
if (upload.Transferred < upload.Size)
SendNextUploadPacket(upload);
}
}
@@ -779,8 +868,6 @@ namespace libsecondlife
if (OnAssetUploaded != null)
{
//Client.DebugLog(complete.ToString());
bool found = false;
KeyValuePair<LLUUID, Transfer> foundTransfer = new KeyValuePair<LLUUID, Transfer>();
@@ -795,6 +882,9 @@ namespace libsecondlife
if ((upload).AssetID == complete.AssetBlock.UUID)
{
// Stop the resend timer for this transfer
upload.TransferTimer.Stop();
found = true;
foundTransfer = transfer;
upload.Success = complete.AssetBlock.Success;

View File

@@ -154,9 +154,9 @@ namespace libsecondlife
Simulator.Client.DebugLog("Making initial capabilities connection for " + Simulator.ToString());
_SeedRequest = new CapsRequest(_SeedCapsURI);
_SeedRequest = new CapsRequest(_SeedCapsURI, String.Empty, null);
_SeedRequest.OnCapsResponse += new CapsRequest.CapsResponseCallback(seedRequest_OnCapsResponse);
_SeedRequest.MakeRequest(postData);
_SeedRequest.MakeRequest(postData, "application/xml", Simulator.udpPort, null);
}
private void seedRequest_OnCapsResponse(object response, HttpRequestState state)

View File

@@ -91,13 +91,15 @@ namespace libsecondlife
// POST request
_RequestState.WebRequest.Method = "POST";
_RequestState.WebRequest.ContentLength = postData.Length;
_RequestState.WebRequest.Headers.Add("X-SecondLife-UDP-Listen-Port", Simulator.udpPort.ToString());
_RequestState.WebRequest.ContentType = "application/xml";
_RequestState.RequestData = postData;
IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream(
new AsyncCallback(EventRequestStreamCallback), _RequestState);
}
public new void MakeRequest(byte[] postData)
public new void MakeRequest(byte[] postData, string contentType, int udpListeningPort, object state)
{
// Create a new HttpWebRequest
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL);
@@ -127,6 +129,8 @@ namespace libsecondlife
// POST request
_RequestState.WebRequest.Method = "POST";
_RequestState.WebRequest.ContentLength = postData.Length;
_RequestState.WebRequest.Headers.Add("X-SecondLife-UDP-Listen-Port", Simulator.udpPort.ToString());
_RequestState.WebRequest.ContentType = "application/xml";
_RequestState.RequestData = postData;
IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream(
@@ -278,7 +282,7 @@ namespace libsecondlife
byte[] postData = LLSD.LLSDSerialize(request);
MakeRequest(postData);
MakeRequest(postData, "application/xml", Simulator.udpPort, null);
// If the event queue is dead at this point, turn it off since
// that was the last thing we want to do

View File

@@ -62,7 +62,7 @@ namespace libsecondlife
public new void MakeRequest()
{
base.MakeRequest(new byte[0]);
base.MakeRequest(new byte[0], null, Simulator.udpPort, null);
}
protected override void Log(string message, Helpers.LogLevel level)
@@ -89,7 +89,7 @@ namespace libsecondlife
else if (exception != null && exception.Message.Contains("502"))
{
// These are normal, retry the request automatically
MakeRequest(state.RequestData);
MakeRequest(state.RequestData, "application/xml", Simulator.udpPort, null);
return;
}

View File

@@ -44,17 +44,14 @@ namespace libsecondlife
public HttpWebRequest WebRequest;
public HttpWebResponse WebResponse;
public Stream ResponseStream;
public object State;
internal int ResponseDataPos = 0;
public HttpRequestState(HttpWebRequest webRequest)
{
WebRequest = webRequest;
BufferRead = new byte[BUFFER_SIZE];
RequestData = null;
ResponseData = null;
ResponseStream = null;
}
}
@@ -85,14 +82,15 @@ namespace libsecondlife
public void MakeRequest()
{
MakeRequest(null);
MakeRequest(null, null, 0, null);
}
public void MakeRequest(byte[] postData)
public void MakeRequest(byte[] postData, string contentType, int udpListeningPort, object state)
{
// Create a new HttpWebRequest
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL);
_RequestState = new HttpRequestState(httpRequest);
_RequestState.State = state;
if (_ProxyURL != String.Empty)
{
@@ -118,6 +116,12 @@ namespace libsecondlife
// POST request
_RequestState.WebRequest.Method = "POST";
_RequestState.WebRequest.ContentLength = postData.Length;
if (udpListeningPort > 0)
_RequestState.WebRequest.Headers.Add("X-SecondLife-UDP-Listen-Port", udpListeningPort.ToString());
if (String.IsNullOrEmpty(contentType))
_RequestState.WebRequest.ContentType = "application/xml";
else
_RequestState.WebRequest.ContentType = contentType;
_RequestState.RequestData = postData;
IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream(

View File

@@ -29,6 +29,15 @@ using System.Collections.Generic;
namespace libsecondlife
{
/// <summary>
/// Exception class to identify inventory exceptions
/// </summary>
public class InventoryException : Exception
{
public InventoryException(string message)
: base(message) { }
}
/// <summary>
/// Responsible for maintaining inventory structure. Inventory constructs nodes
/// and manages node children as is necessary to maintain a coherant hirarchy.
@@ -45,20 +54,16 @@ namespace libsecondlife
/// <param name="oldObject">The state of the InventoryObject before the update occured.</param>
/// <param name="newObject">The state of the InventoryObject after the update occured.</param>
public delegate void InventoryObjectUpdated(InventoryBase oldObject, InventoryBase newObject);
/// <summary>
/// Called when an InventoryObject's state is changed.
/// </summary>
public event InventoryObjectUpdated OnInventoryObjectUpdated;
/// <summary>
/// Delegate to use for the OnInventoryObjectRemoved event.
/// </summary>
/// <param name="obj">The InventoryObject that was removed.</param>
public delegate void InventoryObjectRemoved(InventoryBase obj);
/// <summary>
/// Called when an InventoryObject's state is changed.
/// </summary>
public event InventoryObjectUpdated OnInventoryObjectUpdated;
/// <summary>
/// Called when an item or folder is removed from inventory.
/// </summary>
@@ -75,42 +80,7 @@ namespace libsecondlife
private SecondLife Client;
private InventoryManager Manager;
private Dictionary<LLUUID, InventoryNode> Items;
/// <summary>
/// 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 <code>RemoveNodeFor(this[uuid])</code>.
/// If the value is non-null, it is equivelant to a call to <code>UpdateNodeFor(value)</code>,
/// the uuid parameter is ignored.
/// </summary>
/// <param name="uuid">The UUID of the InventoryObject to get or set, ignored if set to non-null value.</param>
/// <returns>The InventoryObject corresponding to <code>uuid</code>.</returns>
public InventoryBase this[LLUUID uuid]
{
get
{
InventoryNode node = Items[uuid];
return node.Data;
}
set
{
if (value != null)
{
// what if value.UUID != uuid? :-O
// should we check for this?
UpdateNodeFor(value);
}
else
{
InventoryNode node;
if (Items.TryGetValue(uuid, out node))
{
RemoveNodeFor(node.Data);
}
}
}
}
private Dictionary<LLUUID, InventoryNode> Items = new Dictionary<LLUUID, InventoryNode>();
public Inventory(SecondLife client, InventoryManager manager, InventoryFolder rootFolder)
{
@@ -118,22 +88,20 @@ namespace libsecondlife
Manager = manager;
RootFolder = rootFolder;
RootNode = new InventoryNode(rootFolder);
Items = new Dictionary<LLUUID, InventoryNode>();
Items[rootFolder.UUID] = RootNode;
}
public List<InventoryBase> GetContents(InventoryFolder folder)
{
return GetContents(folder.UUID);
}
/// <summary>
/// Returns the contents of the specified folder.
/// Returns the contents of the specified folder
/// </summary>
/// <param name="folder">A folder's UUID.</param>
/// <returns>The contents of the folder corresponding to <code>folder</code>.</returns>
/// <exception cref="InventoryException">When <code>folder</code> does not exist in the inventory.</exception>
/// <param name="folder">A folder's UUID</param>
/// <returns>The contents of the folder corresponding to <code>folder</code></returns>
/// <exception cref="InventoryException">When <code>folder</code> does not exist in the inventory</exception>
public List<InventoryBase> GetContents(LLUUID folder)
{
InventoryNode folderNode;
@@ -150,17 +118,16 @@ namespace libsecondlife
}
}
/// <summary>
/// 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 <code>item.parentUUID</code> does
/// not match <code>node.Parent.Data.UUID</code>.
/// not match <code>node.Parent.Data.UUID</code>.
///
/// You can not set the inventory root folder using this method.
/// You can not set the inventory root folder using this method
/// </summary>
/// <param name="item">The InventoryObject to store.</param>
/// <param name="item">The InventoryObject to store</param>
public void UpdateNodeFor(InventoryBase item)
{
lock (Items)
@@ -268,28 +235,68 @@ namespace libsecondlife
return Contains(obj.UUID);
}
#region Operators
/// <summary>
/// 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 <code>RemoveNodeFor(this[uuid])</code>.
/// If the value is non-null, it is equivelant to a call to <code>UpdateNodeFor(value)</code>,
/// the uuid parameter is ignored.
/// </summary>
/// <param name="uuid">The UUID of the InventoryObject to get or set, ignored if set to non-null value.</param>
/// <returns>The InventoryObject corresponding to <code>uuid</code>.</returns>
public InventoryBase this[LLUUID uuid]
{
get
{
InventoryNode node = Items[uuid];
return node.Data;
}
set
{
if (value != null)
{
// Log a warning if there is a UUID mismatch, this will cause problems
if (value.UUID != uuid)
Client.Log("Inventory[uuid]: uuid " + uuid.ToStringHyphenated() + " is not equal to value.UUID " +
value.UUID.ToStringHyphenated(), Helpers.LogLevel.Warning);
UpdateNodeFor(value);
}
else
{
InventoryNode node;
if (Items.TryGetValue(uuid, out node))
{
RemoveNodeFor(node.Data);
}
}
}
}
#endregion Operators
#region Event Firing
protected void FireOnInventoryObjectUpdated(InventoryBase oldObject, InventoryBase newObject)
{
if (OnInventoryObjectUpdated != null)
OnInventoryObjectUpdated(oldObject, newObject);
{
try { OnInventoryObjectUpdated(oldObject, newObject); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
protected void FireOnInventoryObjectRemoved(InventoryBase obj)
{
if (OnInventoryObjectRemoved != null)
OnInventoryObjectRemoved(obj);
{
try { OnInventoryObjectRemoved(obj); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
#endregion
}
/// <summary>
/// A rudimentary Exception subclass, so exceptions thrown by the Inventory class
/// can be easily identified and caught.
/// </summary>
public class InventoryException : Exception
{
public InventoryException(string message)
: base(message) { }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -183,12 +183,19 @@ namespace libsecondlife
SimIP = IPAddress.Parse(reply.sim_ip);
SeedCapability = reply.seed_capability;
BuddyList = new FriendsManager.FriendInfo[reply.buddy_list.Length];
for (int i = 0; i < BuddyList.Length; ++i)
if (reply.buddy_list != null)
{
BuddyListEntry buddy = reply.buddy_list[i];
BuddyList[i] = new FriendsManager.FriendInfo(buddy.buddy_id, (FriendsManager.RightsFlags)buddy.buddy_rights_given,
(FriendsManager.RightsFlags)buddy.buddy_rights_has);
BuddyList = new FriendsManager.FriendInfo[reply.buddy_list.Length];
for (int i = 0; i < BuddyList.Length; ++i)
{
BuddyListEntry buddy = reply.buddy_list[i];
BuddyList[i] = new FriendsManager.FriendInfo(buddy.buddy_id, (FriendsManager.RightsFlags)buddy.buddy_rights_given,
(FriendsManager.RightsFlags)buddy.buddy_rights_has);
}
}
else
{
BuddyList = new FriendsManager.FriendInfo[0];
}
InventoryRoot = LLUUID.Parse(reply.inventory_root[0].folder_id);

View File

@@ -102,7 +102,9 @@ namespace libsecondlife
/// <remarks>The connection to the new simulator won't be established
/// until this callback returns</remarks>
/// <param name="simulator">The simulator that is being connected to</param>
public delegate void SimConnectingCallback(Simulator simulator);
/// <returns>Whether to continue connecting to the simulator or abort
/// the connection</returns>
public delegate bool SimConnectingCallback(Simulator simulator);
/// <summary>
/// Triggered when a new connection to a simulator is established
/// </summary>
@@ -370,11 +372,19 @@ namespace libsecondlife
// Fire the OnSimConnecting event
if (OnSimConnecting != null)
{
try { OnSimConnecting(simulator); }
try
{
if (!OnSimConnecting(simulator))
{
// Callback is requesting that we abort this connection
lock (Simulators) Simulators.Remove(simulator);
return null;
}
}
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
// We're not connected to this simulator, attempt to establish a connection
// Attempt to establish a connection to the simulator
if (simulator.Connect(setDefault))
{
// Start a timer that checks if we've been disconnected
@@ -391,11 +401,14 @@ namespace libsecondlife
// If enabled, send an AgentThrottle packet to the server to increase our bandwidth
if (Client.Settings.SEND_AGENT_THROTTLE) Client.Throttle.Set(simulator);
return simulator;
}
else
{
// Connection failed, so remove this simulator from our list and destroy it
// Connection failed, remove this simulator from our list and destroy it
lock (Simulators) Simulators.Remove(simulator);
return null;
}
}
else if (setDefault)
@@ -409,9 +422,15 @@ namespace libsecondlife
// Send an initial AgentUpdate to complete our movement in to the sim
if (Client.Settings.SEND_AGENT_UPDATES)
Client.Self.Status.SendUpdate(true, simulator);
}
return simulator;
return simulator;
}
else
{
// Already connected to this simulator and wasn't asked to set it as the default,
// just return a reference to the existing object
return simulator;
}
}
/// <summary>

View File

@@ -128,7 +128,7 @@ namespace libsecondlife
protected abstract void PacketSent(UDPPacketBuffer buffer, int bytesSent);
// the port to listen on
protected int udpPort;
internal int udpPort;
// the UDP socket
private Socket udpSocket;

View File

@@ -48,7 +48,7 @@ namespace libsecondlife.TestClient
Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage);
Groups.OnGroupMembers += new GroupManager.GroupMembersCallback(GroupMembersHandler);
Inventory.OnInventoryObjectReceived += new InventoryManager.InventoryObjectReceived(Inventory_OnInventoryObjectReceived);
Inventory.OnInventoryObjectReceived += new InventoryManager.ObjectReceivedCallback(Inventory_OnInventoryObjectReceived);
Network.RegisterCallback(PacketType.AvatarAppearance, new NetworkManager.PacketCallback(AvatarAppearanceHandler));
Network.RegisterCallback(PacketType.AlertMessage, new NetworkManager.PacketCallback(AlertMessageHandler));

View File

@@ -103,7 +103,7 @@ namespace libsecondlife
private void GatherCaps()
{
CapsRequest request = new CapsRequest(RegistrationApiCaps.AbsoluteUri);
CapsRequest request = new CapsRequest(RegistrationApiCaps.AbsoluteUri, String.Empty, null);
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherCapsResponse);
// build post data
@@ -112,7 +112,7 @@ namespace libsecondlife
_userInfo.Password));
// send
request.MakeRequest(postData);
request.MakeRequest(postData, "application/xml", 0, null);
}
private void GatherCapsResponse(object response, HttpRequestState state)
@@ -141,7 +141,7 @@ namespace libsecondlife
if (_caps.GetErrorCodes == null)
throw new InvalidOperationException("access denied"); // this should work even for not-approved users
CapsRequest request = new CapsRequest(_caps.GetErrorCodes.AbsoluteUri);
CapsRequest request = new CapsRequest(_caps.GetErrorCodes.AbsoluteUri, String.Empty, null);
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherErrorMessagesResponse);
request.MakeRequest();
}
@@ -177,7 +177,7 @@ namespace libsecondlife
if (_caps.GetLastNames == null)
throw new InvalidOperationException("access denied: only approved developers have access to the registration api");
CapsRequest request = new CapsRequest(_caps.GetLastNames.AbsoluteUri);
CapsRequest request = new CapsRequest(_caps.GetLastNames.AbsoluteUri, String.Empty, null);
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherLastNamesResponse);
request.MakeRequest();
@@ -220,9 +220,9 @@ namespace libsecondlife
query.Add("last_name_id", lastName.ID);
byte[] postData = LLSD.LLSDSerialize(query);
CapsRequest request = new CapsRequest(_caps.CheckName.AbsoluteUri);
CapsRequest request = new CapsRequest(_caps.CheckName.AbsoluteUri, String.Empty, null);
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CheckNameResponse);
request.MakeRequest(postData);
request.MakeRequest(postData, "application/xml", 0, null);
// FIXME:
return false;
@@ -286,9 +286,9 @@ namespace libsecondlife
byte[] postData = LLSD.LLSDSerialize(query);
// Make the request
CapsRequest request = new CapsRequest(_caps.CreateUser.AbsoluteUri);
CapsRequest request = new CapsRequest(_caps.CreateUser.AbsoluteUri, String.Empty, null);
request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CreateUserResponse);
request.MakeRequest(postData);
request.MakeRequest(postData, "application/xml", 0, null);
// FIXME: Block
return LLUUID.Zero;