diff --git a/VoiceTest/VoiceTest.cs b/VoiceTest/VoiceTest.cs index 67deb227..e7fe597b 100644 --- a/VoiceTest/VoiceTest.cs +++ b/VoiceTest/VoiceTest.cs @@ -88,7 +88,7 @@ namespace VoiceTest Console.WriteLine("Voice connector handle: " + connectorHandle); // Wait for the simulator capabilities to show up - client.Network.CurrentSim.Caps.CapsReceivedEvent.WaitOne(45 * 1000, false); + // FIXME: Use client.Network.OnEventQueueRunning to continue here Console.WriteLine("Asking the current simulator to create a provisional account..."); diff --git a/libsecondlife/AgentManager.cs b/libsecondlife/AgentManager.cs index 62b660a5..13153a41 100644 --- a/libsecondlife/AgentManager.cs +++ b/libsecondlife/AgentManager.cs @@ -30,6 +30,8 @@ using System.Collections.Generic; using System.Threading; using System.Text; using System.Reflection; +using libsecondlife.StructuredData; +using libsecondlife.Capabilities; using libsecondlife.Packets; namespace libsecondlife @@ -920,7 +922,7 @@ namespace libsecondlife Client.Network.RegisterCallback(PacketType.MeanCollisionAlert, new NetworkManager.PacketCallback(MeanCollisionAlertHandler)); // CAPS callbacks - Client.Network.RegisterEventCallback("EstablishAgentCommunication", new Capabilities.EventQueueCallback(EstablishAgentCommunicationEventHandler)); + Client.Network.RegisterEventCallback("EstablishAgentCommunication", new Caps.EventQueueCallback(EstablishAgentCommunicationEventHandler)); // Login Client.Network.RegisterLoginResponseCallback(new NetworkManager.LoginResponseCallback(Network_OnLoginResponse)); @@ -1784,7 +1786,9 @@ namespace libsecondlife /// Target to look at public void RequestTeleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt) { - if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.Caps != null && Client.Network.CurrentSim.Caps.IsEventQueueRunning) + if (Client.Network.CurrentSim != null && + Client.Network.CurrentSim.Caps != null && + Client.Network.CurrentSim.Caps.IsEventQueueRunning) { TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket(); teleport.AgentData.AgentID = Client.Self.AgentID; @@ -2167,7 +2171,7 @@ namespace libsecondlife } } - private void EstablishAgentCommunicationEventHandler(string message, StructuredData.LLSD llsd, CapsEventQueue caps) + private void EstablishAgentCommunicationEventHandler(string message, LLSD llsd, Simulator simulator) { StructuredData.LLSDMap body = (StructuredData.LLSDMap)llsd; diff --git a/libsecondlife/Capabilities/CapsBase.cs b/libsecondlife/Capabilities/CapsBase.cs new file mode 100644 index 00000000..4ba7ca23 --- /dev/null +++ b/libsecondlife/Capabilities/CapsBase.cs @@ -0,0 +1,63 @@ +/* + * Copyright (c) 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.Net; + +namespace libsecondlife.Capabilities +{ + public class CapsBase : WebClient + { + public int Timeout; + public int ContentLength; + + public Uri Location { get { return _Location; } } + + protected Uri _Location; + + public CapsBase(Uri location) + { + _Location = location; + } + + protected override WebRequest GetWebRequest(Uri address) + { + HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest; + + if (request == null) + throw new ArgumentException("CapsBase requires a Uri", "address"); + + // Disable keep-alive + request.KeepAlive = false; + + // Set the timeout + if (Timeout > 0) + request.Timeout = Timeout; + + return request; + } + } +} diff --git a/libsecondlife/Capabilities/CapsClient.cs b/libsecondlife/Capabilities/CapsClient.cs new file mode 100644 index 00000000..1b0f2b21 --- /dev/null +++ b/libsecondlife/Capabilities/CapsClient.cs @@ -0,0 +1,198 @@ +/* + * Copyright (c) 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.Net; +using libsecondlife.StructuredData; + +namespace libsecondlife.Capabilities +{ + public class CapsClient + { + public delegate void ProgressCallback(CapsClient client, long bytesReceived, long bytesSent, + long totalBytesToReceive, long totalBytesToSend); + public delegate void CompleteCallback(CapsClient client, LLSD result, Exception error); + + public ProgressCallback OnProgress; + public CompleteCallback OnComplete; + + public IWebProxy Proxy; + public object UserData; + + protected CapsBase _Client; + protected byte[] _PostData; + protected string _ContentType; + + public CapsClient(Uri capability) + { + _Client = new CapsBase(capability); + _Client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Client_DownloadProgressChanged); + _Client.DownloadDataCompleted += new DownloadDataCompletedEventHandler(Client_DownloadDataCompleted); + _Client.UploadProgressChanged += new UploadProgressChangedEventHandler(Client_UploadProgressChanged); + _Client.UploadDataCompleted += new UploadDataCompletedEventHandler(Client_UploadDataCompleted); + } + + public void StartRequest() + { + StartRequest(null, null); + } + + public void StartRequest(LLSD llsd) + { + byte[] postData = LLSDParser.SerializeXmlBytes(llsd); + StartRequest(postData, null); + } + + public void StartRequest(byte[] postData) + { + StartRequest(postData, null); + } + + public void StartRequest(byte[] postData, string contentType) + { + _PostData = postData; + _ContentType = contentType; + + if (_Client.IsBusy) + { + SecondLife.LogStatic("New CAPS request initiated, closing previous request", + Helpers.LogLevel.Warning); + _Client.CancelAsync(); + } + + // Proxy + if (Proxy != null) + _Client.Proxy = Proxy; + + // Content-Type + _Client.Headers.Clear(); + if (!String.IsNullOrEmpty(contentType)) + _Client.Headers.Add(HttpRequestHeader.ContentType, contentType); + else + _Client.Headers.Add(HttpRequestHeader.ContentType, "application/xml"); + + if (postData == null) + { + _Client.DownloadStringAsync(_Client.Location); + } + else + { + _Client.UploadDataAsync(_Client.Location, postData); + + //if (postData.Length == 292) + //{ + // CapsRequest request = new CapsRequest(_Client.Location.AbsoluteUri); + // request.OnCapsResponse += new CapsRequest.CapsResponseCallback(request_OnCapsResponse); + // request.MakeRequest(postData, "application/xml", null); + + // //HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(_Client.Location); + // //request.KeepAlive = false; + // //request.Method = "POST"; + // //request.ContentLength = postData.Length; + // //request.ContentType = "application/xml"; + // //System.IO.Stream stream = request.GetRequestStream(); + + // //stream = _Client.OpenWrite(_Client.Location); + // //stream.Write(postData, 0, 292); + // //stream.Close(); + + // //WebResponse response = request.GetResponse(); + // //SecondLife.LogStatic(response.ContentLength.ToString(), Helpers.LogLevel.Info); + + // //_Client.Timeout = 10000; + // //byte[] lol = _Client.UploadData(_Client.Location, postData); + // //Console.WriteLine("Done?"); + //} + } + } + + public void Cancel() + { + if (_Client.IsBusy) + _Client.CancelAsync(); + } + + #region Callback Handlers + + private void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) + { + if (OnProgress != null) + { + try { OnProgress(this, e.BytesReceived, 0, e.TotalBytesToReceive, 0); } + catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); } + } + } + + private void Client_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e) + { + if (OnComplete != null && !e.Cancelled) + { + if (e.Error == null) + { + LLSD result = LLSDParser.DeserializeXml(e.Result); + + try { OnComplete(this, result, e.Error); } + catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); } + } + else + { + if (Helpers.StringContains(e.Error.Message, "502")) + { + // These are normal, retry the request automatically + StartRequest(_PostData, _ContentType); + } + else + { + try { OnComplete(this, null, e.Error); } + catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); } + } + } + } + } + + private void Client_UploadProgressChanged(object sender, UploadProgressChangedEventArgs e) + { + if (OnProgress != null) + { + try { OnProgress(this, e.BytesReceived, e.BytesSent, e.TotalBytesToReceive, e.TotalBytesToSend); } + catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); } + } + } + + private void Client_UploadDataCompleted(object sender, UploadDataCompletedEventArgs e) + { + if (OnComplete != null && !e.Cancelled) + { + LLSD result = LLSDParser.DeserializeXml(e.Result); + + try { OnComplete(this, result, e.Error); } + catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); } + } + } + + #endregion Callback Handlers + } +} diff --git a/libsecondlife/HttpBase.cs b/libsecondlife/Capabilities/CapsEventQueue.cs similarity index 57% rename from libsecondlife/HttpBase.cs rename to libsecondlife/Capabilities/CapsEventQueue.cs index bcf29feb..a2c618b2 100644 --- a/libsecondlife/HttpBase.cs +++ b/libsecondlife/Capabilities/CapsEventQueue.cs @@ -25,16 +25,15 @@ */ using System; +using System.Collections.Generic; using System.Net; -using System.IO; using System.Text; +using System.IO; using System.Threading; +using libsecondlife.StructuredData; -namespace libsecondlife +namespace libsecondlife.Capabilities { - /// - /// Stores the current state of an HTTP request - /// public class HttpRequestState { const int BUFFER_SIZE = 1024; @@ -55,42 +54,52 @@ namespace libsecondlife } } - public abstract class HttpBase + public class CapsEventQueue { - protected abstract void RequestReply(HttpRequestState state, bool success, WebException exception); - protected abstract void RequestSent(HttpRequestState request); + public const int HTTP_TIMEOUT = 1 * 30 * 1000; + public const int BUFFER_SIZE = 1024; - /// Buffer size for reading incoming responses - protected const int BUFFER_SIZE = 1024; - /// A default timeout for HTTP connections - protected const int HTTP_TIMEOUT = 1 * 30 * 1000; + public Simulator Simulator; + + protected bool _Running = false; + protected bool _Dead = false; protected HttpRequestState _RequestState; protected string _RequestURL; protected string _ProxyURL; protected bool _Aborted = false; - public HttpBase(string requestURL) - : this(requestURL, String.Empty) - { } + public bool Running { get { return _Running; } } - public HttpBase(string requestURL, string proxyURL) + public CapsEventQueue(Simulator simulator, string eventQueueURI) + : this(simulator, eventQueueURI, String.Empty) { - _RequestURL = requestURL; - _ProxyURL = proxyURL; + } + + public CapsEventQueue(Simulator simulator, string eventQueueURI, string proxy) + { + Simulator = simulator; + _RequestURL = eventQueueURI; + _ProxyURL = proxy; } public void MakeRequest() { - MakeRequest(null, null, 0, null); + // Create an EventQueueGet request + LLSDMap request = new LLSDMap(); + request["ack"] = new LLSD(); + request["done"] = LLSD.FromBoolean(false); + + byte[] postData = LLSDParser.SerializeXmlBytes(request); + + SendRequest(postData, "application/xml", null); } - public void MakeRequest(byte[] postData, string contentType, int udpListeningPort, object state) + private void SendRequest(byte[] postData, string contentType, object state) { // Create a new HttpWebRequest HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL); _RequestState = new HttpRequestState(httpRequest); - _RequestState.State = state; if (_ProxyURL != String.Empty) { @@ -116,55 +125,15 @@ 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.WebRequest.ContentType = "application/xml"; _RequestState.RequestData = postData; IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream( - new AsyncCallback(RequestStreamCallback), _RequestState); - - // If there is a timeout, the callback fires and the request becomes aborted -#if PocketPC - Thread thread = new Thread( - delegate() - { - if (!result.AsyncWaitHandle.WaitOne(HTTP_TIMEOUT, false)) - TimeoutCallback(_RequestState, true); - } - ); - thread.Start(); -#else - ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), - _RequestState, HTTP_TIMEOUT, true); -#endif + new AsyncCallback(EventRequestStreamCallback), _RequestState); } else { - // GET request - IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetResponse( - new AsyncCallback(ResponseCallback), _RequestState); - - // If there is a timeout, the callback fires and the request becomes aborted -#if PocketPC - Thread thread = new Thread( - delegate() - { - if (!result.AsyncWaitHandle.WaitOne(HTTP_TIMEOUT, false)) - TimeoutCallback(_RequestState, true); - } - ); - thread.Start(); -#else - ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), - _RequestState, HTTP_TIMEOUT, true); -#endif - - // If we get here the request has been initialized, so fire the callback for a request being started - RequestSent(_RequestState); + throw new ArgumentException("postData cannot be null for the event queue", "postData"); } } catch (WebException e) @@ -173,14 +142,14 @@ namespace libsecondlife } catch (Exception e) { - Log("HttpBase.MakeRequest(): " + e.ToString(), Helpers.LogLevel.Warning); + SecondLife.LogStatic("CapsEventQueue.MakeRequest(): " + e.ToString(), Helpers.LogLevel.Warning); Abort(false, null); } } public void Abort() { - Abort(false, null); + Stop(true); } protected void TimeoutCallback(object state, bool timedOut) @@ -289,7 +258,7 @@ namespace libsecondlife _RequestState.ResponseStream = responseStream; // Begin reading of the contents of the response - IAsyncResult asynchronousInputRead = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, + IAsyncResult asynchronousInputRead = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallback), _RequestState); // If there is a timeout, the callback fires and the request becomes aborted @@ -313,6 +282,80 @@ namespace libsecondlife } } + public void Stop(bool immediate) + { + Simulator.Client.Log(String.Format("Event queue for {0} is {1}", Simulator, + (immediate ? "aborting" : "disconnecting")), Helpers.LogLevel.Info); + + _Dead = true; + + if (immediate) + { + _Running = false; + + // Abort the callback if it hasn't been already + _RequestState.WebRequest.Abort(); + } + } + + protected void EventRequestStreamCallback(IAsyncResult result) + { + bool raiseEvent = false; + + if (!_Dead) + { + if (!_Running) raiseEvent = true; + + // We are connected to the event queue + _Running = true; + } + + try + { + _RequestState = (HttpRequestState)result.AsyncState; + Stream reqStream = _RequestState.WebRequest.EndGetRequestStream(result); + + reqStream.Write(_RequestState.RequestData, 0, _RequestState.RequestData.Length); + reqStream.Close(); + + IAsyncResult newResult = _RequestState.WebRequest.BeginGetResponse(new AsyncCallback(EventResponseCallback), _RequestState); + } + catch (WebException e) + { + Abort(false, e); + return; + } + + if (raiseEvent) + { + Simulator.Client.DebugLog("Capabilities event queue connected for " + Simulator.ToString()); + + // The event queue is starting up for the first time + Simulator.Client.Network.RaiseConnectedEvent(Simulator); + } + } + + protected void EventResponseCallback(IAsyncResult result) + { + try + { + _RequestState = (HttpRequestState)result.AsyncState; + _RequestState.WebResponse = (HttpWebResponse)_RequestState.WebRequest.EndGetResponse(result); + + // Read the response into a Stream object + Stream responseStream = _RequestState.WebResponse.GetResponseStream(); + _RequestState.ResponseStream = responseStream; + + // Begin reading of the contents of the response + IAsyncResult asynchronousInputRead = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, + new AsyncCallback(ReadCallback), _RequestState); + } + catch (WebException e) + { + Abort(false, e); + } + } + protected void ReadCallback(IAsyncResult result) { try @@ -334,7 +377,7 @@ namespace libsecondlife _RequestState.ResponseDataPos += read; // Continue reading the response until EndRead() returns 0 - IAsyncResult asynchronousResult = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, + IAsyncResult asynchronousResult = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallback), _RequestState); return; @@ -353,5 +396,95 @@ namespace libsecondlife Abort(false, e); } } + + protected void RequestReply(HttpRequestState state, bool success, WebException exception) + { + LLSDArray events = null; + int ack = 0; + + #region Exception Handling + + if (exception != null) + { + string message = exception.Message.ToLower(); + + // Check what kind of exception happened + if (Helpers.StringContains(message, "404") || Helpers.StringContains(message, "410")) + { + Simulator.Client.Log("Closing event queue for " + Simulator.ToString() + " due to missing caps URI", + Helpers.LogLevel.Info); + + _Running = false; + _Dead = true; + } + else if (!Helpers.StringContains(message, "aborted") && !Helpers.StringContains(message, "502")) + { + Simulator.Client.Log(String.Format("Unrecognized caps exception for {0}: {1}", Simulator, exception.Message), + Helpers.LogLevel.Warning); + } + } + + #endregion Exception Handling + + #region Reply Decoding + + // Decode successful replies from the event queue + if (success) + { + LLSDMap response = (LLSDMap)LLSDParser.DeserializeXml(state.ResponseData); + + if (response != null) + { + // Parse any events returned by the event queue + events = (LLSDArray)response["events"]; + ack = response["id"].AsInteger(); + } + } + + #endregion Reply Decoding + + #region Make New Request + + if (_Running) + { + LLSDMap request = new LLSDMap(); + if (ack != 0) request["ack"] = LLSD.FromInteger(ack); + else request["ack"] = new LLSD(); + request["done"] = LLSD.FromBoolean(_Dead); + + byte[] postData = LLSDParser.SerializeXmlBytes(request); + + SendRequest(postData, "application/xml", null); + + // If the event queue is dead at this point, turn it off since + // that was the last thing we want to do + if (_Dead) + { + _Running = false; + SecondLife.DebugLogStatic("Sent event queue shutdown message"); + } + } + + #endregion Make New Request + + #region Callbacks + + if (events != null && events.Count > 0) + { + // Fire callbacks for each event received + foreach (LLSDMap evt in events) + { + string msg = evt["message"].AsString(); + LLSDMap body = (LLSDMap)evt["body"]; + + if (Simulator.Client.Settings.SYNC_PACKETCALLBACKS) + Simulator.Client.Network.CapsEvents.RaiseEvent(msg, body, Simulator); + else + Simulator.Client.Network.CapsEvents.BeginRaiseEvent(msg, body, Simulator); + } + } + + #endregion Callbacks + } } } diff --git a/libsecondlife/Capabilities/CapsListener.cs b/libsecondlife/Capabilities/CapsListener.cs new file mode 100644 index 00000000..33b824bf --- /dev/null +++ b/libsecondlife/Capabilities/CapsListener.cs @@ -0,0 +1,39 @@ +/* + * Copyright (c) 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.Net; + +#if !PocketPC + +namespace libsecondlife.Capabilities +{ + public class CapsListener + { + } +} + +#endif diff --git a/libsecondlife/Capabilities/EventQueueClient.cs b/libsecondlife/Capabilities/EventQueueClient.cs new file mode 100644 index 00000000..3204799e --- /dev/null +++ b/libsecondlife/Capabilities/EventQueueClient.cs @@ -0,0 +1,197 @@ +/* + * Copyright (c) 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.Net; +using libsecondlife.StructuredData; + +namespace libsecondlife.Capabilities +{ + public class EventQueueClient + { + /// + /// + /// + public delegate void ConnectedCallback(); + /// + /// + /// + /// + /// + public delegate void EventCallback(string eventName, LLSDMap body); + + /// + public ConnectedCallback OnConnected; + /// + public EventCallback OnEvent; + + public IWebProxy Proxy; + + public bool Running { get { return _Running && _Client.IsBusy; } } + + protected CapsBase _Client; + protected bool _Dead; + protected bool _Running; + + public EventQueueClient(Uri eventQueueLocation) + { + _Client = new CapsBase(eventQueueLocation); + _Client.OpenWriteCompleted += new OpenWriteCompletedEventHandler(Client_OpenWriteCompleted); + _Client.UploadDataCompleted += new UploadDataCompletedEventHandler(Client_UploadDataCompleted); + } + + public void Start() + { + _Dead = false; + _Client.OpenWriteAsync(_Client.Location); + } + + public void Stop(bool immediate) + { + _Dead = true; + + if (immediate) + _Running = false; + + if (_Client.IsBusy) + { + SecondLife.DebugLogStatic("Stopping a running event queue"); + _Client.CancelAsync(); + } + else + { + SecondLife.DebugLogStatic("Stopping an already dead event queue"); + } + } + + #region Callback Handlers + + private void Client_OpenWriteCompleted(object sender, OpenWriteCompletedEventArgs e) + { + bool raiseEvent = false; + + if (!_Dead) + { + if (!_Running) raiseEvent = true; + + // We are connected to the event queue + _Running = true; + } + + // Create an EventQueueGet request + LLSDMap request = new LLSDMap(); + request["ack"] = new LLSD(); + request["done"] = LLSD.FromBoolean(false); + + byte[] postData = LLSDParser.SerializeXmlBytes(request); + + _Client.UploadDataAsync(_Client.Location, postData); + + if (raiseEvent) + { + SecondLife.DebugLogStatic("Capabilities event queue connected"); + + // The event queue is starting up for the first time + if (OnConnected != null) + { + try { OnConnected(); } + catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); } + } + } + } + + private void Client_UploadDataCompleted(object sender, UploadDataCompletedEventArgs e) + { + LLSDArray events = null; + int ack = 0; + + if (e.Error != null) + { + string message = e.Error.Message.ToLower(); + + // Check what kind of exception happened + if (Helpers.StringContains(message, "404") || Helpers.StringContains(message, "410")) + { + SecondLife.LogStatic("Closing event queue due to missing caps URI", Helpers.LogLevel.Info); + + _Running = false; + _Dead = true; + } + else if (!e.Cancelled && !Helpers.StringContains(message, "502")) + { + SecondLife.LogStatic("Unrecognized caps exception: " + e.Error.Message, Helpers.LogLevel.Warning); + } + } + else if (!e.Cancelled && e.Result != null) + { + LLSD result = LLSDParser.DeserializeXml(e.Result); + if (result != null && result.Type == LLSDType.Map) + { + // Parse any events returned by the event queue + LLSDMap map = (LLSDMap)result; + + events = (LLSDArray)map["events"]; + ack = map["id"].AsInteger(); + } + } + + if (_Running) + { + LLSDMap request = new LLSDMap(); + if (ack != 0) request["ack"] = LLSD.FromInteger(ack); + else request["ack"] = new LLSD(); + request["done"] = LLSD.FromBoolean(_Dead); + + byte[] postData = LLSDParser.SerializeXmlBytes(request); + + _Client.UploadDataAsync(_Client.Location, postData); + + // If the event queue is dead at this point, turn it off since + // that was the last thing we want to do + if (_Dead) + { + _Running = false; + SecondLife.DebugLogStatic("Sent event queue shutdown message"); + } + } + + if (OnEvent != null && events != null && events.Count > 0) + { + // Fire callbacks for each event received + foreach (LLSDMap evt in events) + { + string msg = evt["message"].AsString(); + LLSDMap body = (LLSDMap)evt["body"]; + + try { OnEvent(msg, body); } + catch (Exception ex) { SecondLife.LogStatic(ex.ToString(), Helpers.LogLevel.Error); } + } + } + } + + #endregion Callback Handlers + } +} diff --git a/libsecondlife/Capabilities/EventQueueListener.cs b/libsecondlife/Capabilities/EventQueueListener.cs new file mode 100644 index 00000000..87e21920 --- /dev/null +++ b/libsecondlife/Capabilities/EventQueueListener.cs @@ -0,0 +1,39 @@ +/* + * Copyright (c) 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.Net; + +#if !PocketPC + +namespace libsecondlife.Capabilities +{ + public class EventQueueListener + { + } +} + +#endif diff --git a/libsecondlife/Capabilities/HttpBase.cs b/libsecondlife/Capabilities/HttpBase.cs deleted file mode 100644 index a90e5b81..00000000 --- a/libsecondlife/Capabilities/HttpBase.cs +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (c) 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.Net; -using System.IO; -using System.Threading; - -namespace libsecondlife.Caps -{ - /// - /// Stores the current state of an HTTP request - /// - public class HttpRequestState - { - private const int BUFFER_SIZE = 1024; - - public byte[] RequestData; - public byte[] ResponseData; - public byte[] BufferRead; - 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]; - } - } - - public abstract class HttpBase - { - protected abstract void RequestReply(HttpRequestState state, bool success, WebException exception); - protected abstract void RequestSent(HttpRequestState request); - - /// Buffer size for reading incoming responses - protected const int BUFFER_SIZE = 1024; - /// A default timeout for HTTP connections - protected const int HTTP_TIMEOUT = 1 * 30 * 1000; - - protected HttpRequestState _RequestState; - protected string _RequestURL; - protected string _ProxyURL; - protected string _ContentType; - protected byte[] _PostData; - protected object _State; - protected bool _Aborted = false; - - public HttpBase(string requestURL) - : this(requestURL, null, null, null, null) - { } - - public HttpBase(string requestURL, string proxyURL, string contentType, byte[] postData, object state) - { - if (String.IsNullOrEmpty(_RequestURL)) - throw new ArgumentException("requestURL cannot be null or emtpy"); - - _RequestURL = requestURL; - _ProxyURL = proxyURL; - _ContentType = contentType; - _PostData = postData; - _State = state; - } - - public void Start() - { - // Client mode - HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL); - IAsyncResult result; - - // Always disable keep-alive for our purposes - httpRequest.KeepAlive = false; - - // Create a state object to track this request in async callbacks - _RequestState = new HttpRequestState(httpRequest); - _RequestState.State = _State; - - if (!String.IsNullOrEmpty(_ProxyURL)) - { - // Create a proxy object - WebProxy proxy = new WebProxy(); - - // Associate a new Uri object to the _wProxy object, using the proxy address - // selected by the user - proxy.Address = new Uri(_ProxyURL); - - // Finally, initialize the Web request object proxy property with the _wProxy - // object - httpRequest.Proxy = proxy; - } - - try - { - if (_PostData != null) - { - // POST request - _RequestState.WebRequest.Method = "POST"; - _RequestState.WebRequest.ContentLength = _PostData.Length; - if (!String.IsNullOrEmpty(_ContentType)) - _RequestState.WebRequest.ContentType = _ContentType; - _RequestState.RequestData = _PostData; - - result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream( - new AsyncCallback(RequestStreamCallback), _RequestState); - } - else - { - // GET request - result = (IAsyncResult)_RequestState.WebRequest.BeginGetResponse( - new AsyncCallback(ResponseCallback), _RequestState); - } - - // If there is a timeout, the callback fires and the request becomes aborted - ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), - _RequestState, HTTP_TIMEOUT, true); - } - catch (WebException e) - { - Stop(false, e); - return; - } - - // If we get here the request has been initialized, so fire the callback for a request being started - RequestSent(_RequestState); - } - - public void Stop() - { - Stop(false, null); - } - - protected void Stop(bool fromTimeout, WebException exception) - { - if (fromTimeout) - { - SecondLife.DebugLogStatic("HttpBase.Abort(): HTTP request timed out"); - } - else - { - if (exception == null) - { - _Aborted = true; - } - else if (exception.Message.Contains("404") || exception.Message.Contains("410")) - { - _Aborted = true; - SecondLife.DebugLogStatic("HttpBase.Abort(): HTTP request target is missing"); - } - else if (exception.Message.Contains("Aborted") || exception.Message.Contains("aborted")) - { - // A callback threw an exception because the request is aborting, return to - // avoid circular problems - return; - } - else if (exception.Message.Contains("502")) - { - // Don't log anything since 502 errors are so common - } - else - { - SecondLife.LogStatic(String.Format("HttpBase.Abort(): {0} (Status: {1})", exception.Message, exception.Status), - Helpers.LogLevel.Warning); - } - } - - // Abort the callback if it hasn't been already - _RequestState.WebRequest.Abort(); - - // Fire the callback for the request completing - try { RequestReply(_RequestState, false, exception); } - catch (Exception e) { SecondLife.LogStatic(e.ToString(), Helpers.LogLevel.Error); } - } - - protected void TimeoutCallback(object state, bool timedOut) - { - if (timedOut) Stop(true, null); - } - - protected void RequestStreamCallback(IAsyncResult result) - { - try - { - _RequestState = (HttpRequestState)result.AsyncState; - Stream reqStream = _RequestState.WebRequest.EndGetRequestStream(result); - - reqStream.Write(_RequestState.RequestData, 0, _RequestState.RequestData.Length); - reqStream.Close(); - - IAsyncResult newResult = _RequestState.WebRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), _RequestState); - - // If there is a timeout, the callback fires and the request becomes aborted - ThreadPool.RegisterWaitForSingleObject(newResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), - _RequestState, HTTP_TIMEOUT, true); - } - catch (WebException e) - { - Stop(false, e); - } - } - - private void ResponseCallback(IAsyncResult result) - { - try - { - _RequestState = (HttpRequestState)result.AsyncState; - _RequestState.WebResponse = (HttpWebResponse)_RequestState.WebRequest.EndGetResponse(result); - - // Read the response into a Stream object - Stream responseStream = _RequestState.WebResponse.GetResponseStream(); - _RequestState.ResponseStream = responseStream; - - // Begin reading of the contents of the response - IAsyncResult asynchronousInputRead = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, - new AsyncCallback(ReadCallback), _RequestState); - - // If there is a timeout, the callback fires and the request becomes aborted - ThreadPool.RegisterWaitForSingleObject(asynchronousInputRead.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), - _RequestState, HTTP_TIMEOUT, true); - } - catch (WebException e) - { - Stop(false, e); - } - } - - protected void ReadCallback(IAsyncResult result) - { - try - { - _RequestState = (HttpRequestState)result.AsyncState; - Stream responseStream = _RequestState.ResponseStream; - int read = responseStream.EndRead(result); - - // Check if we have read the entire response - if (read > 0) - { - // Create the byte array if it hasn't been created yet - if (_RequestState.ResponseData == null || _RequestState.ResponseData.Length != _RequestState.WebResponse.ContentLength) - _RequestState.ResponseData = new byte[_RequestState.WebResponse.ContentLength]; - - // Copy the current buffer data in to the response variable - Buffer.BlockCopy(_RequestState.BufferRead, 0, _RequestState.ResponseData, _RequestState.ResponseDataPos, read); - // Increment our writing position in the response variable - _RequestState.ResponseDataPos += read; - - // Continue reading the response until EndRead() returns 0 - IAsyncResult asynchronousResult = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, - new AsyncCallback(ReadCallback), _RequestState); - - return; - } - else - { - // Fire the callback for receiving a response - try { RequestReply(_RequestState, true, null); } - catch (Exception e) { SecondLife.LogStatic(e.ToString(), Helpers.LogLevel.Error); } - - responseStream.Close(); - } - } - catch (WebException e) - { - Stop(false, e); - } - } - } -} diff --git a/libsecondlife/Capabilities.cs b/libsecondlife/Caps.cs similarity index 72% rename from libsecondlife/Capabilities.cs rename to libsecondlife/Caps.cs index fd70e9d4..5969c58b 100644 --- a/libsecondlife/Capabilities.cs +++ b/libsecondlife/Caps.cs @@ -29,6 +29,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; using libsecondlife.StructuredData; +using libsecondlife.Capabilities; namespace libsecondlife { @@ -37,7 +38,7 @@ namespace libsecondlife /// Second Life uses to communicate transactions such as teleporting or /// group messaging /// - public class Capabilities + public class Caps { /// /// Triggered when an event is received via the EventQueueGet @@ -45,28 +46,21 @@ namespace libsecondlife /// /// Event name /// Decoded event data - /// The CAPS system that made the call - public delegate void EventQueueCallback(string message, StructuredData.LLSD body, CapsEventQueue eventQueue); - /// - /// Triggered when an HTTP call in the queue is executed and a response - /// is received - /// - /// Decoded response - /// Original capability request - public delegate void CapsResponseCallback(StructuredData.LLSD body, HttpRequestState request); + /// The simulator that generated the event + public delegate void EventQueueCallback(string message, StructuredData.LLSD body, Simulator simulator); /// Reference to the simulator this system is connected to public Simulator Simulator; internal string _SeedCapsURI; - internal Dictionary _Caps = new Dictionary(); + internal Dictionary _Caps = new Dictionary(); - private CapsRequest _SeedRequest; + private CapsClient _SeedRequest; + //private Capabilities2.EventQueueClient _EventQueueCap = null; private CapsEventQueue _EventQueueCap = null; /// Capabilities URI this system was initialized with public string SeedCapsURI { get { return _SeedCapsURI; } } - public ManualResetEvent CapsReceivedEvent = new ManualResetEvent(false); /// Whether the capabilities event queue is connected and /// listening for incoming events @@ -88,7 +82,7 @@ namespace libsecondlife /// /// /// - internal Capabilities(Simulator simulator, string seedcaps) + internal Caps(Simulator simulator, string seedcaps) { Simulator = simulator; _SeedCapsURI = seedcaps; @@ -102,14 +96,10 @@ namespace libsecondlife (immediate ? "aborting" : "disconnecting")), Helpers.LogLevel.Info); if (_SeedRequest != null) - { - _SeedRequest.Abort(); - } + _SeedRequest.Cancel(); if (_EventQueueCap != null) - { - _EventQueueCap.Disconnect(immediate); - } + _EventQueueCap.Stop(immediate); } /// @@ -118,14 +108,14 @@ namespace libsecondlife /// Name of the capability to request /// The URI of the requested capability, or String.Empty if /// the capability does not exist - public string CapabilityURI(string capability) + public Uri CapabilityURI(string capability) { - string cap; + Uri cap; if (_Caps.TryGetValue(capability, out cap)) return cap; else - return String.Empty; + return null; } private void MakeSeedRequest() @@ -160,16 +150,16 @@ namespace libsecondlife Simulator.Client.DebugLog("Making initial capabilities connection for " + Simulator.ToString()); - _SeedRequest = new CapsRequest(_SeedCapsURI, String.Empty, null); - _SeedRequest.OnCapsResponse += new CapsRequest.CapsResponseCallback(seedRequest_OnCapsResponse); - _SeedRequest.MakeRequest(postData, "application/xml", 0, null); + _SeedRequest = new CapsClient(new Uri(_SeedCapsURI)); + _SeedRequest.OnComplete += new CapsClient.CompleteCallback(SeedRequestCompleteHandler); + _SeedRequest.StartRequest(postData); } - private void seedRequest_OnCapsResponse(LLSD response, HttpRequestState state) + private void SeedRequestCompleteHandler(CapsClient client, LLSD result, Exception error) { - if (response != null && response.Type == LLSDType.Map) + if (result != null && result.Type == LLSDType.Map) { - LLSDMap respTable = (LLSDMap)response; + LLSDMap respTable = (LLSDMap)result; StringBuilder capsList = new StringBuilder(); @@ -178,20 +168,23 @@ namespace libsecondlife capsList.Append(cap); capsList.Append(' '); - _Caps[cap] = respTable[cap].AsString(); + _Caps[cap] = respTable[cap].AsUri(); } Simulator.Client.DebugLog("Got capabilities: " + capsList.ToString()); - // Signal that we have connected to the CAPS server and received a list of capability URIs - CapsReceivedEvent.Set(); - if (_Caps.ContainsKey("EventQueueGet")) { - _EventQueueCap = new CapsEventQueue(Simulator, _Caps["EventQueueGet"]); Simulator.Client.DebugLog("Starting event queue for " + Simulator.ToString()); + _EventQueueCap = new CapsEventQueue(Simulator, _Caps["EventQueueGet"].AbsoluteUri); _EventQueueCap.MakeRequest(); + + // FIXME: Get the new event queue client working + //_EventQueueCap = new Capabilities2.EventQueueClient(_Caps["EventQueueGet"]); + //_EventQueueCap.OnConnected += new Capabilities2.EventQueueClient.ConnectedCallback(EventQueueConnectedHandler); + //_EventQueueCap.OnEvent += new Capabilities2.EventQueueClient.EventCallback(EventQueueEventHandler); + //_EventQueueCap.Start(); } } else @@ -200,5 +193,18 @@ namespace libsecondlife MakeSeedRequest(); } } + + private void EventQueueConnectedHandler() + { + Simulator.Client.Network.RaiseConnectedEvent(Simulator); + } + + private void EventQueueEventHandler(string eventName, LLSDMap body) + { + if (Simulator.Client.Settings.SYNC_PACKETCALLBACKS) + Simulator.Client.Network.CapsEvents.RaiseEvent(eventName, body, Simulator); + else + Simulator.Client.Network.CapsEvents.BeginRaiseEvent(eventName, body, Simulator); + } } } diff --git a/libsecondlife/CapsEventQueue.cs b/libsecondlife/CapsEventQueue.cs deleted file mode 100644 index b714a02c..00000000 --- a/libsecondlife/CapsEventQueue.cs +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (c) 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.Generic; -using System.Net; -using System.Text; -using System.IO; -using System.Threading; -using libsecondlife.StructuredData; - -namespace libsecondlife -{ - public class CapsEventQueue : HttpBase - { - public Simulator Simulator; - - protected bool _Running = false; - protected bool _Dead = false; - - public bool Running { get { return _Running; } } - - public CapsEventQueue(Simulator simulator, string eventQueueURI) - : this(simulator, eventQueueURI, String.Empty) - { - } - - public CapsEventQueue(Simulator simulator, string eventQueueURI, string proxy) - : base(eventQueueURI, proxy) - { - Simulator = simulator; - } - - protected override void RequestSent(HttpRequestState request) - { - ; - } - - public new void MakeRequest() - { - // Create an EventQueueGet request - LLSDMap request = new LLSDMap(); - request["ack"] = new LLSD(); - request["done"] = LLSD.FromBoolean(false); - - byte[] postData = LLSDParser.SerializeXmlBytes(request); - - // Create a new HttpWebRequest - HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL); - _RequestState = new HttpRequestState(httpRequest); - - if (_ProxyURL != String.Empty) - { - // Create a proxy object - WebProxy proxy = new WebProxy(); - - // Associate a new Uri object to the _wProxy object, using the proxy address - // selected by the user - proxy.Address = new Uri(_ProxyURL); - - // Finally, initialize the Web request object proxy property with the _wProxy - // object - httpRequest.Proxy = proxy; - } - - // Always disable keep-alive for our purposes - httpRequest.KeepAlive = false; - - // 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, string contentType, int udpListeningPort, object state) - { - // Create a new HttpWebRequest - HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL); - _RequestState = new HttpRequestState(httpRequest); - - if (_ProxyURL != String.Empty) - { - // Create a proxy object - WebProxy proxy = new WebProxy(); - - // Associate a new Uri object to the _wProxy object, using the proxy address - // selected by the user - proxy.Address = new Uri(_ProxyURL); - - // Finally, initialize the Web request object proxy property with the _wProxy - // object - httpRequest.Proxy = proxy; - } - - // Always disable keep-alive for our purposes - httpRequest.KeepAlive = false; - - try - { - if (postData != null) - { - // 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); - } - else - { - throw new ArgumentException("postData cannot be null for the event queue", "postData"); - } - } - catch (WebException e) - { - Abort(false, e); - } - catch (Exception e) - { - SecondLife.LogStatic("CapsEventQueue.MakeRequest(): " + e.ToString(), Helpers.LogLevel.Warning); - Abort(false, null); - } - } - - public new void Abort() - { - Disconnect(true); - } - - public void Disconnect(bool immediate) - { - Simulator.Client.Log(String.Format("Event queue for {0} is {1}", Simulator, - (immediate ? "aborting" : "disconnecting")), Helpers.LogLevel.Info); - - _Dead = true; - - if (immediate) - { - _Running = false; - - // Abort the callback if it hasn't been already - _RequestState.WebRequest.Abort(); - } - } - - protected void EventRequestStreamCallback(IAsyncResult result) - { - bool raiseEvent = false; - - if (!_Dead) - { - if (!_Running) raiseEvent = true; - - // We are connected to the event queue - _Running = true; - } - - try - { - _RequestState = (HttpRequestState)result.AsyncState; - Stream reqStream = _RequestState.WebRequest.EndGetRequestStream(result); - - reqStream.Write(_RequestState.RequestData, 0, _RequestState.RequestData.Length); - reqStream.Close(); - - IAsyncResult newResult = _RequestState.WebRequest.BeginGetResponse(new AsyncCallback(EventResponseCallback), _RequestState); - } - catch (WebException e) - { - Abort(false, e); - return; - } - - if (raiseEvent) - { - Simulator.Client.DebugLog("Capabilities event queue connected for " + Simulator.ToString()); - - // The event queue is starting up for the first time - Simulator.Client.Network.RaiseConnectedEvent(Simulator); - } - } - - protected void EventResponseCallback(IAsyncResult result) - { - try - { - _RequestState = (HttpRequestState)result.AsyncState; - _RequestState.WebResponse = (HttpWebResponse)_RequestState.WebRequest.EndGetResponse(result); - - // Read the response into a Stream object - Stream responseStream = _RequestState.WebResponse.GetResponseStream(); - _RequestState.ResponseStream = responseStream; - - // Begin reading of the contents of the response - IAsyncResult asynchronousInputRead = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, - new AsyncCallback(ReadCallback), _RequestState); - } - catch (WebException e) - { - Abort(false, e); - } - } - - protected override void RequestReply(HttpRequestState state, bool success, WebException exception) - { - LLSDArray events = null; - int ack = 0; - - #region Exception Handling - - if (exception != null) - { - string message = exception.Message.ToLower(); - - // Check what kind of exception happened - if (Helpers.StringContains(message, "404") || Helpers.StringContains(message, "410")) - { - Simulator.Client.Log("Closing event queue for " + Simulator.ToString() + " due to missing caps URI", - Helpers.LogLevel.Info); - - _Running = false; - _Dead = true; - } - else if (!Helpers.StringContains(message, "aborted") && !Helpers.StringContains(message, "502")) - { - Simulator.Client.Log(String.Format("Unrecognized caps exception for {0}: {1}", Simulator, exception.Message), - Helpers.LogLevel.Warning); - } - } - - #endregion Exception Handling - - #region Reply Decoding - - // Decode successful replies from the event queue - if (success) - { - LLSDMap response = (LLSDMap)LLSDParser.DeserializeXml(state.ResponseData); - - if (response != null) - { - // Parse any events returned by the event queue - events = (LLSDArray)response["events"]; - ack = response["id"].AsInteger(); - } - } - - #endregion Reply Decoding - - #region Make New Request - - if (_Running) - { - LLSDMap request = new LLSDMap(); - if (ack != 0) request["ack"] = LLSD.FromInteger(ack); - else request["ack"] = new LLSD(); - request["done"] = LLSD.FromBoolean(_Dead); - - byte[] postData = LLSDParser.SerializeXmlBytes(request); - - MakeRequest(postData, "application/xml", 0, null); - - // If the event queue is dead at this point, turn it off since - // that was the last thing we want to do - if (_Dead) - { - _Running = false; - SecondLife.DebugLogStatic("Sent event queue shutdown message"); - } - } - - #endregion Make New Request - - #region Callbacks - - if (events != null && events.Count > 0) - { - // Fire callbacks for each event received - foreach (LLSDMap evt in events) - { - string msg = evt["message"].AsString(); - LLSDMap body = (LLSDMap)evt["body"]; - - //Simulator.Client.DebugLog( - // String.Format("[{0}] Event {1}: {2}{3}", Simulator, msg, Helpers.NewLine, LLSD.LLSDDump(body, 0))); - - if (Simulator.Client.Settings.SYNC_PACKETCALLBACKS) - Simulator.Client.Network.CapsEvents.RaiseEvent(msg, body, this); - else - Simulator.Client.Network.CapsEvents.BeginRaiseEvent(msg, body, this); - } - } - - #endregion Callbacks - } - } -} diff --git a/libsecondlife/CapsRequest.cs b/libsecondlife/CapsRequest.cs deleted file mode 100644 index b7e05f0a..00000000 --- a/libsecondlife/CapsRequest.cs +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 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.Net; -using System.Text; -using libsecondlife.StructuredData; - -namespace libsecondlife -{ - public class CapsRequest : HttpBase - { - public delegate void CapsResponseCallback(LLSD response, HttpRequestState state); - - public event CapsResponseCallback OnCapsResponse; - - public Simulator Simulator; - - public CapsRequest(string capsURI) - : this(capsURI, String.Empty, null) - { - } - - public CapsRequest(string capsURI, string proxy) - : this(capsURI, proxy, null) - { - } - - public CapsRequest(string capsURI, Simulator simulator) - : this(capsURI, String.Empty, simulator) - { - } - - public CapsRequest(string capsURI, string proxy, Simulator simulator) - : base(capsURI, proxy) - { - Simulator = simulator; - } - - public new void MakeRequest() - { - base.MakeRequest(new byte[0], null, 0, null); - } - - protected override void RequestSent(HttpRequestState request) - { - ; - } - - protected override void RequestReply(HttpRequestState state, bool success, WebException exception) - { - LLSD response = null; - - if (success) - { - response = LLSDParser.DeserializeXml(state.ResponseData); - } - else if (exception != null && Helpers.StringContains(exception.Message, "502")) - { - // These are normal, retry the request automatically - MakeRequest(state.RequestData, "application/xml", 0, null); - - return; - } - - // Only fire the callback if there is response data or the call has - // not been aborted. Timeouts and 502 errors don't count as aborting, - // although 502 errors are already handled above - if (response != null || !_Aborted) - { - if (OnCapsResponse != null) - { - try { OnCapsResponse(response, state); } - catch (Exception e) { SecondLife.LogStatic(e.ToString(), Helpers.LogLevel.Error); } - } - } - } - } -} diff --git a/libsecondlife/EventDictionary.cs b/libsecondlife/EventDictionary.cs index f84747f1..d826826c 100644 --- a/libsecondlife/EventDictionary.cs +++ b/libsecondlife/EventDictionary.cs @@ -159,20 +159,20 @@ namespace libsecondlife private struct CapsCallbackWrapper { /// Callback to fire for this packet - public Capabilities.EventQueueCallback Callback; + public Caps.EventQueueCallback Callback; /// Name of the CAPS event public string CapsEvent; /// Decoded body of the CAPS event public StructuredData.LLSD Body; - /// Reference to the event queue that generated this event - public CapsEventQueue EventQueue; + /// Reference to the simulator that generated this event + public Simulator Simulator; } /// Reference to the SecondLife client public SecondLife Client; - private Dictionary _EventTable = - new Dictionary(); + private Dictionary _EventTable = + new Dictionary(); private WaitCallback _ThreadPoolCallback; /// @@ -192,7 +192,7 @@ namespace libsecondlife /// Capability event name to register the /// handler for /// Callback to fire - public void RegisterEvent(string capsEvent, Capabilities.EventQueueCallback eventHandler) + public void RegisterEvent(string capsEvent, Caps.EventQueueCallback eventHandler) { lock (_EventTable) { @@ -209,7 +209,7 @@ namespace libsecondlife /// Capability event name unregister the /// handler for /// Callback to unregister - public void UnregisterEvent(string capsEvent, Capabilities.EventQueueCallback eventHandler) + public void UnregisterEvent(string capsEvent, Caps.EventQueueCallback eventHandler) { lock (_EventTable) { @@ -223,19 +223,19 @@ namespace libsecondlife /// /// Capability name /// Decoded event body - /// Reference to the event queue that + /// Reference to the simulator that /// generated this event - internal void RaiseEvent(string capsEvent, StructuredData.LLSD body, CapsEventQueue eventQueue) + internal void RaiseEvent(string capsEvent, StructuredData.LLSD body, Simulator simulator) { bool specialHandler = false; - Capabilities.EventQueueCallback callback; + Caps.EventQueueCallback callback; // Default handler first, if one exists if (_EventTable.TryGetValue(capsEvent, out callback)) { if (callback != null) { - try { callback(capsEvent, body, eventQueue); } + try { callback(capsEvent, body, simulator); } catch (Exception ex) { Client.Log("CAPS Event Handler: " + ex.ToString(), Helpers.LogLevel.Error); } } } @@ -248,7 +248,7 @@ namespace libsecondlife if (packet != null) { NetworkManager.IncomingPacket incomingPacket; - incomingPacket.Simulator = eventQueue.Simulator; + incomingPacket.Simulator = simulator; incomingPacket.Packet = packet; Client.DebugLog("Serializing " + packet.Type.ToString() + " capability with generic handler"); @@ -261,7 +261,7 @@ namespace libsecondlife // Explicit handler next if (_EventTable.TryGetValue(capsEvent, out callback) && callback != null) { - try { callback(capsEvent, body, eventQueue); } + try { callback(capsEvent, body, simulator); } catch (Exception ex) { Client.Log("CAPS Event Handler: " + ex.ToString(), Helpers.LogLevel.Error); } specialHandler = true; @@ -276,12 +276,12 @@ namespace libsecondlife /// /// Capability name /// Decoded event body - /// Reference to the event queue that + /// Reference to the simulator that /// generated this event - internal void BeginRaiseEvent(string capsEvent, StructuredData.LLSD body, CapsEventQueue eventQueue) + internal void BeginRaiseEvent(string capsEvent, StructuredData.LLSD body, Simulator simulator) { bool specialHandler = false; - Capabilities.EventQueueCallback callback; + Caps.EventQueueCallback callback; // Default handler first, if one exists if (_EventTable.TryGetValue(String.Empty, out callback)) @@ -292,7 +292,7 @@ namespace libsecondlife wrapper.Callback = callback; wrapper.CapsEvent = capsEvent; wrapper.Body = body; - wrapper.EventQueue = eventQueue; + wrapper.Simulator = simulator; ThreadPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper); } } @@ -305,7 +305,7 @@ namespace libsecondlife if (packet != null) { NetworkManager.IncomingPacket incomingPacket; - incomingPacket.Simulator = eventQueue.Simulator; + incomingPacket.Simulator = simulator; incomingPacket.Packet = packet; Client.DebugLog("Serializing " + packet.Type.ToString() + " capability with generic handler"); @@ -322,7 +322,7 @@ namespace libsecondlife wrapper.Callback = callback; wrapper.CapsEvent = capsEvent; wrapper.Body = body; - wrapper.EventQueue = eventQueue; + wrapper.Simulator = simulator; ThreadPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper); specialHandler = true; @@ -338,7 +338,7 @@ namespace libsecondlife try { - wrapper.Callback(wrapper.CapsEvent, wrapper.Body, wrapper.EventQueue); + wrapper.Callback(wrapper.CapsEvent, wrapper.Body, wrapper.Simulator); } catch (Exception ex) { diff --git a/libsecondlife/GridManager.cs b/libsecondlife/GridManager.cs index f3d42e12..6c45975d 100644 --- a/libsecondlife/GridManager.cs +++ b/libsecondlife/GridManager.cs @@ -29,6 +29,8 @@ using System.Text; using System.Collections; using System.Collections.Generic; using System.Threading; +using libsecondlife.StructuredData; +using libsecondlife.Capabilities; using libsecondlife.Packets; namespace libsecondlife @@ -236,15 +238,16 @@ namespace libsecondlife /// public void RequestMapLayer(GridLayerType layer) { - string url = Client.Network.CurrentSim.Caps.CapabilityURI("MapLayer"); + Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("MapLayer"); - if (!String.IsNullOrEmpty(url)) + if (url != null) { - Hashtable body = new Hashtable(); - body["Flags"] = (int)layer; + LLSDMap body = new LLSDMap(); + body["Flags"] = LLSD.FromInteger((int)layer); - Client.Network.SendCapsRequest(url, body, - new CapsRequest.CapsResponseCallback(MapLayerResponseHandler)); + CapsClient request = new CapsClient(url); + request.OnComplete += new CapsClient.CompleteCallback(MapLayerResponseHandler); + request.StartRequest(body); } } @@ -417,23 +420,23 @@ namespace libsecondlife } } - private void MapLayerResponseHandler(object response, HttpRequestState state) + private void MapLayerResponseHandler(CapsClient client, LLSD result, Exception error) { - Dictionary body = (Dictionary)response; - List layerData = (List)body["LayerData"]; + LLSDMap body = (LLSDMap)result; + LLSDArray layerData = (LLSDArray)body["LayerData"]; if (OnGridLayer != null) { for (int i = 0; i < layerData.Count; i++) { - Dictionary thisLayerData = (Dictionary)layerData[i]; + LLSDMap thisLayerData = (LLSDMap)layerData[i]; GridLayer layer; - layer.Bottom = (int)thisLayerData["Bottom"]; - layer.Left = (int)thisLayerData["Left"]; - layer.Top = (int)thisLayerData["Top"]; - layer.Right = (int)thisLayerData["Right"]; - layer.ImageID = (LLUUID)thisLayerData["ImageID"]; + layer.Bottom = thisLayerData["Bottom"].AsInteger(); + layer.Left = thisLayerData["Left"].AsInteger(); + layer.Top = thisLayerData["Top"].AsInteger(); + layer.Right = thisLayerData["Right"].AsInteger(); + layer.ImageID = thisLayerData["ImageID"].AsUUID(); try { OnGridLayer(layer); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } diff --git a/libsecondlife/InventoryManager.cs b/libsecondlife/InventoryManager.cs index 39a94b7d..4a54f659 100644 --- a/libsecondlife/InventoryManager.cs +++ b/libsecondlife/InventoryManager.cs @@ -29,7 +29,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Text; -using libsecondlife; +using libsecondlife.Capabilities; using libsecondlife.StructuredData; using libsecondlife.Packets; @@ -1157,9 +1157,9 @@ namespace libsecondlife if (_Client.Network.CurrentSim == null || _Client.Network.CurrentSim.Caps == null) throw new Exception("NewFileAgentInventory capability is not currently available"); - string url = _Client.Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory"); + Uri url = _Client.Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory"); - if (url != String.Empty) + if (url != null) { LLSDMap query = new LLSDMap(); query.Add("folder_id", LLSD.FromUUID(folderID)); @@ -1171,9 +1171,10 @@ namespace libsecondlife byte[] postData = StructuredData.LLSDParser.SerializeXmlBytes(query); // Make the request - CapsRequest request = new CapsRequest(url, _Client.Network.CurrentSim); - request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CreateItemFromAssetResponse); - request.MakeRequest(postData, "application/xml", 0, new KeyValuePair(callback, data)); + CapsClient request = new CapsClient(url); + request.OnComplete += new CapsClient.CompleteCallback(CreateItemFromAssetResponse); + request.UserData = new KeyValuePair(callback, data); + request.StartRequest(postData); } else { @@ -2031,10 +2032,10 @@ namespace libsecondlife #region Callbacks - private void CreateItemFromAssetResponse(LLSD response, HttpRequestState state) + private void CreateItemFromAssetResponse(CapsClient client, LLSD result, Exception error) { - LLSDMap contents = (LLSDMap)response; - KeyValuePair kvp = (KeyValuePair)state.State; + LLSDMap contents = (LLSDMap)result; + KeyValuePair kvp = (KeyValuePair)client.UserData; ItemCreatedFromAssetCallback callback = kvp.Key; byte[] itemData = (byte[])kvp.Value; @@ -2046,9 +2047,10 @@ namespace libsecondlife // 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", 0, kvp); + CapsClient upload = new CapsClient(new Uri(uploadURL)); + upload.OnComplete += new CapsClient.CompleteCallback(CreateItemFromAssetResponse); + upload.UserData = kvp; + upload.StartRequest(itemData, "application/octet-stream"); } else if (status == "complete") { diff --git a/libsecondlife/NetworkManager.cs b/libsecondlife/NetworkManager.cs index 72157f32..759a1ed6 100644 --- a/libsecondlife/NetworkManager.cs +++ b/libsecondlife/NetworkManager.cs @@ -269,7 +269,7 @@ namespace libsecondlife /// /// Name of the CAPS event to register a handler for /// Callback to fire when a CAPS event is received - public void RegisterEventCallback(string capsEvent, Capabilities.EventQueueCallback callback) + public void RegisterEventCallback(string capsEvent, Caps.EventQueueCallback callback) { CapsEvents.RegisterEvent(capsEvent, callback); } @@ -282,7 +282,7 @@ namespace libsecondlife /// Name of the CAPS event this callback is /// registered with /// Callback to stop firing events for - public void UnregisterEventCallback(string capsEvent, Capabilities.EventQueueCallback callback) + public void UnregisterEventCallback(string capsEvent, Caps.EventQueueCallback callback) { CapsEvents.UnregisterEvent(capsEvent, callback); } @@ -333,13 +333,6 @@ namespace libsecondlife simulator.SendPacket(payload, setSequence); } - public void SendCapsRequest(string uri, Hashtable body, CapsRequest.CapsResponseCallback callback) - { - CapsRequest request = new CapsRequest(uri, Client.Network.CurrentSim); - request.OnCapsResponse += new CapsRequest.CapsResponseCallback(callback); - request.MakeRequest(); - } - /// /// Connect to a simulator /// diff --git a/libsecondlife/Simulator.cs b/libsecondlife/Simulator.cs index 2ca256c3..a7bffa38 100644 --- a/libsecondlife/Simulator.cs +++ b/libsecondlife/Simulator.cs @@ -209,7 +209,7 @@ namespace libsecondlife /// public LLUUID ID = LLUUID.Zero; /// The capabilities for this simulator - public Capabilities Caps = null; + public Caps Caps = null; /// public ulong Handle; /// @@ -496,7 +496,7 @@ namespace libsecondlife { // Connect to the new CAPS system if (!String.IsNullOrEmpty(seedcaps)) - Caps = new Capabilities(this, seedcaps); + Caps = new Caps(this, seedcaps); else Client.Log("Setting up a sim without a valid capabilities server!", Helpers.LogLevel.Error); } diff --git a/libsecondlife/examples/GUITestClient/GUITestClient.csproj b/libsecondlife/examples/GUITestClient/GUITestClient.csproj index 34a9e7e1..2866c488 100644 --- a/libsecondlife/examples/GUITestClient/GUITestClient.csproj +++ b/libsecondlife/examples/GUITestClient/GUITestClient.csproj @@ -28,7 +28,6 @@ 4 - @@ -73,6 +72,12 @@ True + + + {D9CDEDFB-8169-4B03-B57F-0DF638F044EC} + libsecondlife + + - + \ No newline at end of file diff --git a/libsecondlife/libsecondlife.Utilities/RegistrationApi.cs b/libsecondlife/libsecondlife.Utilities/RegistrationApi.cs index 81b3de87..859654af 100644 --- a/libsecondlife/libsecondlife.Utilities/RegistrationApi.cs +++ b/libsecondlife/libsecondlife.Utilities/RegistrationApi.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Text; using libsecondlife.StructuredData; +using libsecondlife.Capabilities; namespace libsecondlife { @@ -103,31 +104,29 @@ namespace libsecondlife private void GatherCaps() { - CapsRequest request = new CapsRequest(RegistrationApiCaps.AbsoluteUri, String.Empty, null); - request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherCapsResponse); - // build post data byte[] postData = Encoding.ASCII.GetBytes( String.Format("first_name={0}&last_name={1}&password={2}", _userInfo.FirstName, _userInfo.LastName, _userInfo.Password)); - // send - request.MakeRequest(postData, "application/xml", 0, null); + CapsClient request = new CapsClient(RegistrationApiCaps); + request.OnComplete += new CapsClient.CompleteCallback(GatherCapsResponse); + request.StartRequest(postData); } - private void GatherCapsResponse(object response, HttpRequestState state) + private void GatherCapsResponse(CapsClient client, LLSD response, Exception error) { - if (response is Dictionary) + if (response is LLSDMap) { - Dictionary respTable = (Dictionary)response; + LLSDMap respTable = (LLSDMap)response; // parse _caps = new RegistrationCaps(); - _caps.CreateUser = new Uri((string)respTable["create_user"]); - _caps.CheckName = new Uri((string)respTable["check_name"]); - _caps.GetLastNames = new Uri((string)respTable["get_last_names"]); - _caps.GetErrorCodes = new Uri((string)respTable["get_error_codes"]); + _caps.CreateUser = respTable["create_user"].AsUri(); + _caps.CheckName = respTable["check_name"].AsUri(); + _caps.GetLastNames = respTable["get_last_names"].AsUri(); + _caps.GetErrorCodes = respTable["get_error_codes"].AsUri(); // finalize _initializing++; @@ -141,20 +140,20 @@ 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, String.Empty, null); - request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherErrorMessagesResponse); - request.MakeRequest(); + CapsClient request = new CapsClient(_caps.GetErrorCodes); + request.OnComplete += new CapsClient.CompleteCallback(GatherErrorMessagesResponse); + request.StartRequest(); } - private void GatherErrorMessagesResponse(object response, HttpRequestState state) + private void GatherErrorMessagesResponse(CapsClient client, LLSD response, Exception error) { - if (response is Dictionary) + if (response is LLSDMap) { // parse //FIXME: wtf? - foreach (KeyValuePair error in (Dictionary)response) - { + //foreach (KeyValuePair error in (Dictionary)response) + //{ //StringBuilder sb = new StringBuilder(); //sb.Append(error[1]); @@ -164,7 +163,7 @@ namespace libsecondlife //sb.Append(error[2]); //_errors.Add((int)error[0], sb.ToString()); - } + //} // finalize _initializing++; @@ -179,32 +178,33 @@ 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, String.Empty, null); - request.OnCapsResponse += new CapsRequest.CapsResponseCallback(GatherLastNamesResponse); - request.MakeRequest(); + CapsClient request = new CapsClient(_caps.GetLastNames); + request.OnComplete += new CapsClient.CompleteCallback(GatherLastNamesResponse); + request.StartRequest(); // FIXME: Block } - private void GatherLastNamesResponse(object response, HttpRequestState state) + private void GatherLastNamesResponse(CapsClient client, LLSD response, Exception error) { - if (response is Dictionary) + if (response is LLSDMap) { - Dictionary respTable = (Dictionary)response; + LLSDMap respTable = (LLSDMap)response; - _lastNames = new List(respTable.Count); + //FIXME: + //_lastNames = new List(respTable.Count); - for (Dictionary.Enumerator it = respTable.GetEnumerator(); it.MoveNext(); ) - { - LastName ln = new LastName(); + //for (Dictionary.Enumerator it = respTable.GetEnumerator(); it.MoveNext(); ) + //{ + // LastName ln = new LastName(); - ln.ID = int.Parse(it.Current.Key.ToString()); - ln.Name = it.Current.Value.ToString(); + // ln.ID = int.Parse(it.Current.Key.ToString()); + // ln.Name = it.Current.Value.ToString(); - _lastNames.Add(ln); - } + // _lastNames.Add(ln); + //} - _lastNames.Sort(new Comparison(delegate(LastName a, LastName b) { return a.Name.CompareTo(b.Name); })); + //_lastNames.Sort(new Comparison(delegate(LastName a, LastName b) { return a.Name.CompareTo(b.Name); })); } } @@ -222,17 +222,17 @@ namespace libsecondlife query.Add("last_name_id", LLSD.FromInteger(lastName.ID)); byte[] postData = LLSDParser.SerializeXmlBytes(query); - CapsRequest request = new CapsRequest(_caps.CheckName.AbsoluteUri, String.Empty, null); - request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CheckNameResponse); - request.MakeRequest(postData, "application/xml", 0, null); + CapsClient request = new CapsClient(_caps.CheckName); + request.OnComplete += new CapsClient.CompleteCallback(CheckNameResponse); + request.StartRequest(); // FIXME: return false; } - private void CheckNameResponse(object response, HttpRequestState state) + private void CheckNameResponse(CapsClient client, LLSD response, Exception error) { - if (response is bool) + if (response.Type == LLSDType.Boolean) { // FIXME: //(bool)response; @@ -288,17 +288,17 @@ namespace libsecondlife byte[] postData = LLSDParser.SerializeXmlBytes(query); // Make the request - CapsRequest request = new CapsRequest(_caps.CreateUser.AbsoluteUri, String.Empty, null); - request.OnCapsResponse += new CapsRequest.CapsResponseCallback(CreateUserResponse); - request.MakeRequest(postData, "application/xml", 0, null); + CapsClient request = new CapsClient(_caps.CreateUser); + request.OnComplete += new CapsClient.CompleteCallback(CreateUserResponse); + request.StartRequest(); // FIXME: Block return LLUUID.Zero; } - private void CreateUserResponse(object response, HttpRequestState state) + private void CreateUserResponse(CapsClient client, LLSD response, Exception error) { - if (response is Dictionary) + if (response is LLSDMap) { // everything is okay // FIXME: @@ -307,16 +307,16 @@ namespace libsecondlife else { // an error happened - List al = (List)response; + LLSDArray al = (LLSDArray)response; StringBuilder sb = new StringBuilder(); - foreach (int ec in al) + foreach (LLSD ec in al) { if (sb.Length > 0) sb.Append("; "); - sb.Append(_errors[ec]); + sb.Append(_errors[ec.AsInteger()]); } // FIXME: diff --git a/libsecondlife/libsecondlife.Utilities/VoiceManager.cs b/libsecondlife/libsecondlife.Utilities/VoiceManager.cs index 14590b9f..2ebb6093 100644 --- a/libsecondlife/libsecondlife.Utilities/VoiceManager.cs +++ b/libsecondlife/libsecondlife.Utilities/VoiceManager.cs @@ -33,6 +33,7 @@ using System.Xml; using System.Threading; using libsecondlife; using libsecondlife.StructuredData; +using libsecondlife.Capabilities; namespace libsecondlife.Utilities { @@ -147,7 +148,7 @@ namespace libsecondlife.Utilities public VoiceManager(SecondLife client) { Client = client; - Client.Network.RegisterEventCallback("RequiredVoiceVersion", new Capabilities.EventQueueCallback(RequiredVoiceVersionEventHandler)); + Client.Network.RegisterEventCallback("RequiredVoiceVersion", new Caps.EventQueueCallback(RequiredVoiceVersionEventHandler)); // Register callback handlers for the blocking functions RegisterCallbacks(); @@ -323,13 +324,13 @@ namespace libsecondlife.Utilities { if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.Caps != null) { - string requestURI = Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest"); + Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest"); - if (requestURI != String.Empty) + if (url != null) { - CapsRequest request = new CapsRequest(requestURI, Client.Network.CurrentSim); - request.OnCapsResponse += new CapsRequest.CapsResponseCallback(ProvisionCapsResponse); - request.MakeRequest(); + CapsClient request = new CapsClient(url); + request.OnComplete += new CapsClient.CompleteCallback(ProvisionCapsResponse); + request.StartRequest(); return true; } @@ -504,7 +505,7 @@ namespace libsecondlife.Utilities #region Callbacks - private void RequiredVoiceVersionEventHandler(string message, LLSD llsd, CapsEventQueue caps) + private void RequiredVoiceVersionEventHandler(string message, LLSD llsd, Simulator simulator) { LLSDMap body = (LLSDMap)llsd; @@ -525,13 +526,17 @@ namespace libsecondlife.Utilities } } - private void ProvisionCapsResponse(object response, HttpRequestState state) + private void ProvisionCapsResponse(CapsClient client, LLSD response, Exception error) { - if (response is System.Collections.Hashtable) + if (response is LLSDMap) { - System.Collections.Hashtable respTable = (System.Collections.Hashtable)response; + LLSDMap respTable = (LLSDMap)response; - if (OnProvisionAccount != null) OnProvisionAccount((string)respTable["username"], (string)respTable["password"]); + if (OnProvisionAccount != null) + { + try { OnProvisionAccount(respTable["username"].AsString(), respTable["password"].AsString()); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } } } diff --git a/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj b/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj index 6d21c19c..d84c5ab2 100644 --- a/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj +++ b/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj @@ -40,8 +40,12 @@ - - + + Code + + + Code + diff --git a/libsecondlife/libsecondlife.csproj b/libsecondlife/libsecondlife.csproj index 0cc1420a..8af7a5ec 100644 --- a/libsecondlife/libsecondlife.csproj +++ b/libsecondlife/libsecondlife.csproj @@ -105,17 +105,21 @@ - - - + + + + + Component + + + + - -