diff options
author | Justin Clark-Casey (justincc) | 2014-03-17 20:51:35 +0000 |
---|---|---|
committer | Justin Clark-Casey (justincc) | 2014-03-17 20:51:35 +0000 |
commit | f3e177814a30ee91a2fdd27f2a1aebf06a39cd15 (patch) | |
tree | c511df5ec9e48d961241341e30adce3e33eb7ad7 | |
parent | Implement osForceBreakAllLinks(). (diff) | |
download | opensim-SC-f3e177814a30ee91a2fdd27f2a1aebf06a39cd15.zip opensim-SC-f3e177814a30ee91a2fdd27f2a1aebf06a39cd15.tar.gz opensim-SC-f3e177814a30ee91a2fdd27f2a1aebf06a39cd15.tar.bz2 opensim-SC-f3e177814a30ee91a2fdd27f2a1aebf06a39cd15.tar.xz |
Add regression test for http inventory fetch.
Involved some restructuring to allow regression tests to dequeue inventory requests and perform poll responses synchronously rather than async
-rw-r--r-- | OpenSim/Capabilities/Caps.cs | 7 | ||||
-rw-r--r-- | OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs | 24 | ||||
-rw-r--r-- | OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs | 175 | ||||
-rw-r--r-- | OpenSim/Framework/Servers/Tests/OSHttpTests.cs | 320 | ||||
-rw-r--r-- | OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs | 158 | ||||
-rw-r--r-- | OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs | 43 | ||||
-rw-r--r-- | OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs | 4 | ||||
-rw-r--r-- | OpenSim/Tests/Common/Mock/TestHttpClientContext.cs | 110 | ||||
-rw-r--r-- | OpenSim/Tests/Common/Mock/TestHttpRequest.cs | 174 | ||||
-rw-r--r-- | OpenSim/Tests/Common/Mock/TestHttpResponse.cs | 171 | ||||
-rw-r--r-- | prebuild.xml | 3 |
11 files changed, 783 insertions, 406 deletions
diff --git a/OpenSim/Capabilities/Caps.cs b/OpenSim/Capabilities/Caps.cs index bbf3b27..049afab 100644 --- a/OpenSim/Capabilities/Caps.cs +++ b/OpenSim/Capabilities/Caps.cs | |||
@@ -50,8 +50,7 @@ namespace OpenSim.Framework.Capabilities | |||
50 | 50 | ||
51 | public class Caps | 51 | public class Caps |
52 | { | 52 | { |
53 | // private static readonly ILog m_log = | 53 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
54 | // LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
55 | 54 | ||
56 | private string m_httpListenerHostName; | 55 | private string m_httpListenerHostName; |
57 | private uint m_httpListenPort; | 56 | private uint m_httpListenPort; |
@@ -152,6 +151,10 @@ namespace OpenSim.Framework.Capabilities | |||
152 | 151 | ||
153 | public void RegisterPollHandler(string capName, PollServiceEventArgs pollServiceHandler) | 152 | public void RegisterPollHandler(string capName, PollServiceEventArgs pollServiceHandler) |
154 | { | 153 | { |
154 | // m_log.DebugFormat( | ||
155 | // "[CAPS]: Registering handler with name {0}, url {1} for {2}", | ||
156 | // capName, pollServiceHandler.Url, m_agentID, m_regionName); | ||
157 | |||
155 | m_pollServiceHandlers.Add(capName, pollServiceHandler); | 158 | m_pollServiceHandlers.Add(capName, pollServiceHandler); |
156 | 159 | ||
157 | m_httpListener.AddPollServiceHTTPHandler(pollServiceHandler.Url, pollServiceHandler); | 160 | m_httpListener.AddPollServiceHTTPHandler(pollServiceHandler.Url, pollServiceHandler); |
diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index e1ae74e..e243002 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs | |||
@@ -113,7 +113,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
113 | 113 | ||
114 | protected IPAddress m_listenIPAddress = IPAddress.Any; | 114 | protected IPAddress m_listenIPAddress = IPAddress.Any; |
115 | 115 | ||
116 | private PollServiceRequestManager m_PollServiceManager; | 116 | public PollServiceRequestManager PollServiceRequestManager { get; private set; } |
117 | 117 | ||
118 | public uint SSLPort | 118 | public uint SSLPort |
119 | { | 119 | { |
@@ -374,7 +374,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
374 | return true; | 374 | return true; |
375 | } | 375 | } |
376 | 376 | ||
377 | private void OnRequest(object source, RequestEventArgs args) | 377 | public void OnRequest(object source, RequestEventArgs args) |
378 | { | 378 | { |
379 | RequestNumber++; | 379 | RequestNumber++; |
380 | 380 | ||
@@ -429,7 +429,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
429 | psEvArgs.Request(psreq.RequestID, keysvals); | 429 | psEvArgs.Request(psreq.RequestID, keysvals); |
430 | } | 430 | } |
431 | 431 | ||
432 | m_PollServiceManager.Enqueue(psreq); | 432 | PollServiceRequestManager.Enqueue(psreq); |
433 | } | 433 | } |
434 | else | 434 | else |
435 | { | 435 | { |
@@ -1781,10 +1781,17 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
1781 | 1781 | ||
1782 | public void Start() | 1782 | public void Start() |
1783 | { | 1783 | { |
1784 | StartHTTP(); | 1784 | Start(true); |
1785 | } | 1785 | } |
1786 | 1786 | ||
1787 | private void StartHTTP() | 1787 | /// <summary> |
1788 | /// Start the http server | ||
1789 | /// </summary> | ||
1790 | /// <param name='processPollRequestsAsync'> | ||
1791 | /// If true then poll responses are performed asynchronsly. | ||
1792 | /// Option exists to allow regression tests to perform processing synchronously. | ||
1793 | /// </param> | ||
1794 | public void Start(bool performPollResponsesAsync) | ||
1788 | { | 1795 | { |
1789 | m_log.InfoFormat( | 1796 | m_log.InfoFormat( |
1790 | "[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port); | 1797 | "[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port); |
@@ -1822,8 +1829,9 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
1822 | m_httpListener2.Start(64); | 1829 | m_httpListener2.Start(64); |
1823 | 1830 | ||
1824 | // Long Poll Service Manager with 3 worker threads a 25 second timeout for no events | 1831 | // Long Poll Service Manager with 3 worker threads a 25 second timeout for no events |
1825 | m_PollServiceManager = new PollServiceRequestManager(this, 3, 25000); | 1832 | PollServiceRequestManager = new PollServiceRequestManager(this, performPollResponsesAsync, 3, 25000); |
1826 | m_PollServiceManager.Start(); | 1833 | PollServiceRequestManager.Start(); |
1834 | |||
1827 | HTTPDRunning = true; | 1835 | HTTPDRunning = true; |
1828 | 1836 | ||
1829 | //HttpListenerContext context; | 1837 | //HttpListenerContext context; |
@@ -1892,7 +1900,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
1892 | 1900 | ||
1893 | try | 1901 | try |
1894 | { | 1902 | { |
1895 | m_PollServiceManager.Stop(); | 1903 | PollServiceRequestManager.Stop(); |
1896 | 1904 | ||
1897 | m_httpListener2.ExceptionThrown -= httpServerException; | 1905 | m_httpListener2.ExceptionThrown -= httpServerException; |
1898 | //m_httpListener2.DisconnectHandler = null; | 1906 | //m_httpListener2.DisconnectHandler = null; |
diff --git a/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs b/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs index 6aa5907..456acb0 100644 --- a/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs +++ b/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs | |||
@@ -44,6 +44,20 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
44 | { | 44 | { |
45 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 45 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
46 | 46 | ||
47 | /// <summary> | ||
48 | /// Is the poll service request manager running? | ||
49 | /// </summary> | ||
50 | /// <remarks> | ||
51 | /// Can be running either synchronously or asynchronously | ||
52 | /// </remarks> | ||
53 | public bool IsRunning { get; private set; } | ||
54 | |||
55 | /// <summary> | ||
56 | /// Is the poll service performing responses asynchronously (with its own threads) or synchronously (via | ||
57 | /// external calls)? | ||
58 | /// </summary> | ||
59 | public bool PerformResponsesAsync { get; private set; } | ||
60 | |||
47 | private readonly BaseHttpServer m_server; | 61 | private readonly BaseHttpServer m_server; |
48 | 62 | ||
49 | private BlockingQueue<PollServiceHttpRequest> m_requests = new BlockingQueue<PollServiceHttpRequest>(); | 63 | private BlockingQueue<PollServiceHttpRequest> m_requests = new BlockingQueue<PollServiceHttpRequest>(); |
@@ -52,48 +66,53 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
52 | private uint m_WorkerThreadCount = 0; | 66 | private uint m_WorkerThreadCount = 0; |
53 | private Thread[] m_workerThreads; | 67 | private Thread[] m_workerThreads; |
54 | 68 | ||
55 | private bool m_running = true; | ||
56 | |||
57 | private SmartThreadPool m_threadPool = new SmartThreadPool(20000, 12, 2); | 69 | private SmartThreadPool m_threadPool = new SmartThreadPool(20000, 12, 2); |
58 | 70 | ||
59 | // private int m_timeout = 1000; // increase timeout 250; now use the event one | 71 | // private int m_timeout = 1000; // increase timeout 250; now use the event one |
60 | 72 | ||
61 | public PollServiceRequestManager(BaseHttpServer pSrv, uint pWorkerThreadCount, int pTimeout) | 73 | public PollServiceRequestManager( |
74 | BaseHttpServer pSrv, bool performResponsesAsync, uint pWorkerThreadCount, int pTimeout) | ||
62 | { | 75 | { |
63 | m_server = pSrv; | 76 | m_server = pSrv; |
77 | PerformResponsesAsync = performResponsesAsync; | ||
64 | m_WorkerThreadCount = pWorkerThreadCount; | 78 | m_WorkerThreadCount = pWorkerThreadCount; |
65 | m_workerThreads = new Thread[m_WorkerThreadCount]; | 79 | m_workerThreads = new Thread[m_WorkerThreadCount]; |
66 | } | 80 | } |
67 | 81 | ||
68 | public void Start() | 82 | public void Start() |
69 | { | 83 | { |
70 | //startup worker threads | 84 | IsRunning = true; |
71 | for (uint i = 0; i < m_WorkerThreadCount; i++) | 85 | |
86 | if (PerformResponsesAsync) | ||
72 | { | 87 | { |
73 | m_workerThreads[i] | 88 | //startup worker threads |
74 | = Watchdog.StartThread( | 89 | for (uint i = 0; i < m_WorkerThreadCount; i++) |
75 | PoolWorkerJob, | 90 | { |
76 | string.Format("PollServiceWorkerThread{0}:{1}", i, m_server.Port), | 91 | m_workerThreads[i] |
77 | ThreadPriority.Normal, | 92 | = Watchdog.StartThread( |
78 | false, | 93 | PoolWorkerJob, |
79 | false, | 94 | string.Format("PollServiceWorkerThread{0}:{1}", i, m_server.Port), |
80 | null, | 95 | ThreadPriority.Normal, |
81 | int.MaxValue); | 96 | false, |
82 | } | 97 | false, |
98 | null, | ||
99 | int.MaxValue); | ||
100 | } | ||
83 | 101 | ||
84 | Watchdog.StartThread( | 102 | Watchdog.StartThread( |
85 | this.CheckLongPollThreads, | 103 | this.CheckLongPollThreads, |
86 | string.Format("LongPollServiceWatcherThread:{0}", m_server.Port), | 104 | string.Format("LongPollServiceWatcherThread:{0}", m_server.Port), |
87 | ThreadPriority.Normal, | 105 | ThreadPriority.Normal, |
88 | false, | 106 | false, |
89 | true, | 107 | true, |
90 | null, | 108 | null, |
91 | 1000 * 60 * 10); | 109 | 1000 * 60 * 10); |
110 | } | ||
92 | } | 111 | } |
93 | 112 | ||
94 | private void ReQueueEvent(PollServiceHttpRequest req) | 113 | private void ReQueueEvent(PollServiceHttpRequest req) |
95 | { | 114 | { |
96 | if (m_running) | 115 | if (IsRunning) |
97 | { | 116 | { |
98 | // delay the enqueueing for 100ms. There's no need to have the event | 117 | // delay the enqueueing for 100ms. There's no need to have the event |
99 | // actively on the queue | 118 | // actively on the queue |
@@ -109,7 +128,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
109 | 128 | ||
110 | public void Enqueue(PollServiceHttpRequest req) | 129 | public void Enqueue(PollServiceHttpRequest req) |
111 | { | 130 | { |
112 | if (m_running) | 131 | if (IsRunning) |
113 | { | 132 | { |
114 | if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll) | 133 | if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll) |
115 | { | 134 | { |
@@ -129,7 +148,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
129 | // All other types of tasks (Inventory handlers, http-in, etc) don't have the long-poll nature, | 148 | // All other types of tasks (Inventory handlers, http-in, etc) don't have the long-poll nature, |
130 | // so if they aren't ready to be served by a worker thread (no events), they are placed | 149 | // so if they aren't ready to be served by a worker thread (no events), they are placed |
131 | // directly back in the "ready-to-serve" queue by the worker thread. | 150 | // directly back in the "ready-to-serve" queue by the worker thread. |
132 | while (m_running) | 151 | while (IsRunning) |
133 | { | 152 | { |
134 | Thread.Sleep(500); | 153 | Thread.Sleep(500); |
135 | Watchdog.UpdateThread(); | 154 | Watchdog.UpdateThread(); |
@@ -137,7 +156,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
137 | // List<PollServiceHttpRequest> not_ready = new List<PollServiceHttpRequest>(); | 156 | // List<PollServiceHttpRequest> not_ready = new List<PollServiceHttpRequest>(); |
138 | lock (m_longPollRequests) | 157 | lock (m_longPollRequests) |
139 | { | 158 | { |
140 | if (m_longPollRequests.Count > 0 && m_running) | 159 | if (m_longPollRequests.Count > 0 && IsRunning) |
141 | { | 160 | { |
142 | List<PollServiceHttpRequest> ready = m_longPollRequests.FindAll(req => | 161 | List<PollServiceHttpRequest> ready = m_longPollRequests.FindAll(req => |
143 | (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id) || // there are events in this EQ | 162 | (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id) || // there are events in this EQ |
@@ -158,7 +177,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
158 | 177 | ||
159 | public void Stop() | 178 | public void Stop() |
160 | { | 179 | { |
161 | m_running = false; | 180 | IsRunning = false; |
162 | // m_timeout = -10000; // cause all to expire | 181 | // m_timeout = -10000; // cause all to expire |
163 | Thread.Sleep(1000); // let the world move | 182 | Thread.Sleep(1000); // let the world move |
164 | 183 | ||
@@ -169,7 +188,7 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
169 | 188 | ||
170 | lock (m_longPollRequests) | 189 | lock (m_longPollRequests) |
171 | { | 190 | { |
172 | if (m_longPollRequests.Count > 0 && m_running) | 191 | if (m_longPollRequests.Count > 0 && IsRunning) |
173 | m_longPollRequests.ForEach(req => m_requests.Enqueue(req)); | 192 | m_longPollRequests.ForEach(req => m_requests.Enqueue(req)); |
174 | } | 193 | } |
175 | 194 | ||
@@ -194,68 +213,82 @@ namespace OpenSim.Framework.Servers.HttpServer | |||
194 | 213 | ||
195 | private void PoolWorkerJob() | 214 | private void PoolWorkerJob() |
196 | { | 215 | { |
197 | while (m_running) | 216 | while (IsRunning) |
198 | { | 217 | { |
199 | PollServiceHttpRequest req = m_requests.Dequeue(5000); | ||
200 | //m_log.WarnFormat("[YYY]: Dequeued {0}", (req == null ? "null" : req.PollServiceArgs.Type.ToString())); | ||
201 | |||
202 | Watchdog.UpdateThread(); | 218 | Watchdog.UpdateThread(); |
203 | if (req != null) | 219 | WaitPerformResponse(); |
220 | } | ||
221 | } | ||
222 | |||
223 | public void WaitPerformResponse() | ||
224 | { | ||
225 | PollServiceHttpRequest req = m_requests.Dequeue(5000); | ||
226 | // m_log.DebugFormat("[YYY]: Dequeued {0}", (req == null ? "null" : req.PollServiceArgs.Type.ToString())); | ||
227 | |||
228 | if (req != null) | ||
229 | { | ||
230 | try | ||
204 | { | 231 | { |
205 | try | 232 | if (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id)) |
206 | { | 233 | { |
207 | if (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id)) | 234 | Hashtable responsedata = req.PollServiceArgs.GetEvents(req.RequestID, req.PollServiceArgs.Id); |
208 | { | ||
209 | Hashtable responsedata = req.PollServiceArgs.GetEvents(req.RequestID, req.PollServiceArgs.Id); | ||
210 | 235 | ||
211 | if (responsedata == null) | 236 | if (responsedata == null) |
212 | continue; | 237 | return; |
213 | 238 | ||
214 | if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll) // This is the event queue | 239 | // This is the event queue. |
240 | // Even if we're not running we can still perform responses by explicit request. | ||
241 | if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll | ||
242 | || !PerformResponsesAsync) | ||
243 | { | ||
244 | try | ||
245 | { | ||
246 | req.DoHTTPGruntWork(m_server, responsedata); | ||
247 | } | ||
248 | catch (ObjectDisposedException e) // Browser aborted before we could read body, server closed the stream | ||
249 | { | ||
250 | // Ignore it, no need to reply | ||
251 | m_log.Error(e); | ||
252 | } | ||
253 | } | ||
254 | else | ||
255 | { | ||
256 | m_threadPool.QueueWorkItem(x => | ||
215 | { | 257 | { |
216 | try | 258 | try |
217 | { | 259 | { |
218 | req.DoHTTPGruntWork(m_server, responsedata); | 260 | req.DoHTTPGruntWork(m_server, responsedata); |
219 | } | 261 | } |
220 | catch (ObjectDisposedException) // Browser aborted before we could read body, server closed the stream | 262 | catch (ObjectDisposedException e) // Browser aborted before we could read body, server closed the stream |
221 | { | 263 | { |
222 | // Ignore it, no need to reply | 264 | // Ignore it, no need to reply |
265 | m_log.Error(e); | ||
223 | } | 266 | } |
224 | } | 267 | catch (Exception e) |
225 | else | ||
226 | { | ||
227 | m_threadPool.QueueWorkItem(x => | ||
228 | { | 268 | { |
229 | try | 269 | m_log.Error(e); |
230 | { | 270 | } |
231 | req.DoHTTPGruntWork(m_server, responsedata); | 271 | |
232 | } | 272 | return null; |
233 | catch (ObjectDisposedException) // Browser aborted before we could read body, server closed the stream | 273 | }, null); |
234 | { | 274 | } |
235 | // Ignore it, no need to reply | 275 | } |
236 | } | 276 | else |
237 | 277 | { | |
238 | return null; | 278 | if ((Environment.TickCount - req.RequestTime) > req.PollServiceArgs.TimeOutms) |
239 | }, null); | 279 | { |
240 | } | 280 | req.DoHTTPGruntWork( |
281 | m_server, req.PollServiceArgs.NoEvents(req.RequestID, req.PollServiceArgs.Id)); | ||
241 | } | 282 | } |
242 | else | 283 | else |
243 | { | 284 | { |
244 | if ((Environment.TickCount - req.RequestTime) > req.PollServiceArgs.TimeOutms) | 285 | ReQueueEvent(req); |
245 | { | ||
246 | req.DoHTTPGruntWork( | ||
247 | m_server, req.PollServiceArgs.NoEvents(req.RequestID, req.PollServiceArgs.Id)); | ||
248 | } | ||
249 | else | ||
250 | { | ||
251 | ReQueueEvent(req); | ||
252 | } | ||
253 | } | 286 | } |
254 | } | 287 | } |
255 | catch (Exception e) | 288 | } |
256 | { | 289 | catch (Exception e) |
257 | m_log.ErrorFormat("Exception in poll service thread: " + e.ToString()); | 290 | { |
258 | } | 291 | m_log.ErrorFormat("Exception in poll service thread: " + e.ToString()); |
259 | } | 292 | } |
260 | } | 293 | } |
261 | } | 294 | } |
diff --git a/OpenSim/Framework/Servers/Tests/OSHttpTests.cs b/OpenSim/Framework/Servers/Tests/OSHttpTests.cs index 5b912b4..5c0e0df 100644 --- a/OpenSim/Framework/Servers/Tests/OSHttpTests.cs +++ b/OpenSim/Framework/Servers/Tests/OSHttpTests.cs | |||
@@ -41,323 +41,7 @@ namespace OpenSim.Framework.Servers.Tests | |||
41 | { | 41 | { |
42 | [TestFixture] | 42 | [TestFixture] |
43 | public class OSHttpTests : OpenSimTestCase | 43 | public class OSHttpTests : OpenSimTestCase |
44 | { | 44 | { |
45 | // we need an IHttpClientContext for our tests | ||
46 | public class TestHttpClientContext: IHttpClientContext | ||
47 | { | ||
48 | private bool _secured; | ||
49 | public bool IsSecured | ||
50 | { | ||
51 | get { return _secured; } | ||
52 | } | ||
53 | public bool Secured | ||
54 | { | ||
55 | get { return _secured; } | ||
56 | } | ||
57 | |||
58 | public TestHttpClientContext(bool secured) | ||
59 | { | ||
60 | _secured = secured; | ||
61 | } | ||
62 | |||
63 | public void Disconnect(SocketError error) {} | ||
64 | public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {} | ||
65 | public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {} | ||
66 | public void Respond(string body) {} | ||
67 | public void Send(byte[] buffer) {} | ||
68 | public void Send(byte[] buffer, int offset, int size) {} | ||
69 | public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) {} | ||
70 | public void Close() { } | ||
71 | public bool EndWhenDone { get { return false;} set { return;}} | ||
72 | |||
73 | public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() | ||
74 | { | ||
75 | return new HTTPNetworkContext(); | ||
76 | } | ||
77 | |||
78 | public event EventHandler<DisconnectedEventArgs> Disconnected = delegate { }; | ||
79 | /// <summary> | ||
80 | /// A request have been received in the context. | ||
81 | /// </summary> | ||
82 | public event EventHandler<RequestEventArgs> RequestReceived = delegate { }; | ||
83 | |||
84 | } | ||
85 | |||
86 | public class TestHttpRequest: IHttpRequest | ||
87 | { | ||
88 | private string _uriPath; | ||
89 | public bool BodyIsComplete | ||
90 | { | ||
91 | get { return true; } | ||
92 | } | ||
93 | public string[] AcceptTypes | ||
94 | { | ||
95 | get {return _acceptTypes; } | ||
96 | } | ||
97 | private string[] _acceptTypes; | ||
98 | public Stream Body | ||
99 | { | ||
100 | get { return _body; } | ||
101 | set { _body = value;} | ||
102 | } | ||
103 | private Stream _body; | ||
104 | public ConnectionType Connection | ||
105 | { | ||
106 | get { return _connection; } | ||
107 | set { _connection = value; } | ||
108 | } | ||
109 | private ConnectionType _connection; | ||
110 | public int ContentLength | ||
111 | { | ||
112 | get { return _contentLength; } | ||
113 | set { _contentLength = value; } | ||
114 | } | ||
115 | private int _contentLength; | ||
116 | public NameValueCollection Headers | ||
117 | { | ||
118 | get { return _headers; } | ||
119 | } | ||
120 | private NameValueCollection _headers = new NameValueCollection(); | ||
121 | public string HttpVersion | ||
122 | { | ||
123 | get { return _httpVersion; } | ||
124 | set { _httpVersion = value; } | ||
125 | } | ||
126 | private string _httpVersion = null; | ||
127 | public string Method | ||
128 | { | ||
129 | get { return _method; } | ||
130 | set { _method = value; } | ||
131 | } | ||
132 | private string _method = null; | ||
133 | public HttpInput QueryString | ||
134 | { | ||
135 | get { return _queryString; } | ||
136 | } | ||
137 | private HttpInput _queryString = null; | ||
138 | public Uri Uri | ||
139 | { | ||
140 | get { return _uri; } | ||
141 | set { _uri = value; } | ||
142 | } | ||
143 | private Uri _uri = null; | ||
144 | public string[] UriParts | ||
145 | { | ||
146 | get { return _uri.Segments; } | ||
147 | } | ||
148 | public HttpParam Param | ||
149 | { | ||
150 | get { return null; } | ||
151 | } | ||
152 | public HttpForm Form | ||
153 | { | ||
154 | get { return null; } | ||
155 | } | ||
156 | public bool IsAjax | ||
157 | { | ||
158 | get { return false; } | ||
159 | } | ||
160 | public RequestCookies Cookies | ||
161 | { | ||
162 | get { return null; } | ||
163 | } | ||
164 | |||
165 | public TestHttpRequest() {} | ||
166 | |||
167 | public TestHttpRequest(string contentEncoding, string contentType, string userAgent, | ||
168 | string remoteAddr, string remotePort, string[] acceptTypes, | ||
169 | ConnectionType connectionType, int contentLength, Uri uri) | ||
170 | { | ||
171 | _headers["content-encoding"] = contentEncoding; | ||
172 | _headers["content-type"] = contentType; | ||
173 | _headers["user-agent"] = userAgent; | ||
174 | _headers["remote_addr"] = remoteAddr; | ||
175 | _headers["remote_port"] = remotePort; | ||
176 | |||
177 | _acceptTypes = acceptTypes; | ||
178 | _connection = connectionType; | ||
179 | _contentLength = contentLength; | ||
180 | _uri = uri; | ||
181 | } | ||
182 | |||
183 | public void DecodeBody(FormDecoderProvider providers) {} | ||
184 | public void SetCookies(RequestCookies cookies) {} | ||
185 | public void AddHeader(string name, string value) | ||
186 | { | ||
187 | _headers.Add(name, value); | ||
188 | } | ||
189 | public int AddToBody(byte[] bytes, int offset, int length) | ||
190 | { | ||
191 | return 0; | ||
192 | } | ||
193 | public void Clear() {} | ||
194 | |||
195 | public object Clone() | ||
196 | { | ||
197 | TestHttpRequest clone = new TestHttpRequest(); | ||
198 | clone._acceptTypes = _acceptTypes; | ||
199 | clone._connection = _connection; | ||
200 | clone._contentLength = _contentLength; | ||
201 | clone._uri = _uri; | ||
202 | clone._headers = new NameValueCollection(_headers); | ||
203 | |||
204 | return clone; | ||
205 | } | ||
206 | public IHttpResponse CreateResponse(IHttpClientContext context) | ||
207 | { | ||
208 | return new HttpResponse(context, this); | ||
209 | } | ||
210 | /// <summary> | ||
211 | /// Path and query (will be merged with the host header) and put in Uri | ||
212 | /// </summary> | ||
213 | /// <see cref="Uri"/> | ||
214 | public string UriPath | ||
215 | { | ||
216 | get { return _uriPath; } | ||
217 | set | ||
218 | { | ||
219 | _uriPath = value; | ||
220 | |||
221 | } | ||
222 | } | ||
223 | |||
224 | } | ||
225 | |||
226 | public class TestHttpResponse: IHttpResponse | ||
227 | { | ||
228 | public Stream Body | ||
229 | { | ||
230 | get { return _body; } | ||
231 | |||
232 | set { _body = value; } | ||
233 | } | ||
234 | private Stream _body; | ||
235 | |||
236 | public string ProtocolVersion | ||
237 | { | ||
238 | get { return _protocolVersion; } | ||
239 | set { _protocolVersion = value; } | ||
240 | } | ||
241 | private string _protocolVersion; | ||
242 | |||
243 | public bool Chunked | ||
244 | { | ||
245 | get { return _chunked; } | ||
246 | |||
247 | set { _chunked = value; } | ||
248 | } | ||
249 | private bool _chunked; | ||
250 | |||
251 | public ConnectionType Connection | ||
252 | { | ||
253 | get { return _connection; } | ||
254 | |||
255 | set { _connection = value; } | ||
256 | } | ||
257 | private ConnectionType _connection; | ||
258 | |||
259 | public Encoding Encoding | ||
260 | { | ||
261 | get { return _encoding; } | ||
262 | |||
263 | set { _encoding = value; } | ||
264 | } | ||
265 | private Encoding _encoding; | ||
266 | |||
267 | public int KeepAlive | ||
268 | { | ||
269 | get { return _keepAlive; } | ||
270 | |||
271 | set { _keepAlive = value; } | ||
272 | } | ||
273 | private int _keepAlive; | ||
274 | |||
275 | public HttpStatusCode Status | ||
276 | { | ||
277 | get { return _status; } | ||
278 | |||
279 | set { _status = value; } | ||
280 | } | ||
281 | private HttpStatusCode _status; | ||
282 | |||
283 | public string Reason | ||
284 | { | ||
285 | get { return _reason; } | ||
286 | |||
287 | set { _reason = value; } | ||
288 | } | ||
289 | private string _reason; | ||
290 | |||
291 | public long ContentLength | ||
292 | { | ||
293 | get { return _contentLength; } | ||
294 | |||
295 | set { _contentLength = value; } | ||
296 | } | ||
297 | private long _contentLength; | ||
298 | |||
299 | public string ContentType | ||
300 | { | ||
301 | get { return _contentType; } | ||
302 | |||
303 | set { _contentType = value; } | ||
304 | } | ||
305 | private string _contentType; | ||
306 | |||
307 | public bool HeadersSent | ||
308 | { | ||
309 | get { return _headersSent; } | ||
310 | } | ||
311 | private bool _headersSent; | ||
312 | |||
313 | public bool Sent | ||
314 | { | ||
315 | get { return _sent; } | ||
316 | } | ||
317 | private bool _sent; | ||
318 | |||
319 | public ResponseCookies Cookies | ||
320 | { | ||
321 | get { return _cookies; } | ||
322 | } | ||
323 | private ResponseCookies _cookies = null; | ||
324 | |||
325 | public TestHttpResponse() | ||
326 | { | ||
327 | _headersSent = false; | ||
328 | _sent = false; | ||
329 | } | ||
330 | |||
331 | public void AddHeader(string name, string value) {} | ||
332 | public void Send() | ||
333 | { | ||
334 | if (!_headersSent) SendHeaders(); | ||
335 | if (_sent) throw new InvalidOperationException("stuff already sent"); | ||
336 | _sent = true; | ||
337 | } | ||
338 | |||
339 | public void SendBody(byte[] buffer, int offset, int count) | ||
340 | { | ||
341 | if (!_headersSent) SendHeaders(); | ||
342 | _sent = true; | ||
343 | } | ||
344 | public void SendBody(byte[] buffer) | ||
345 | { | ||
346 | if (!_headersSent) SendHeaders(); | ||
347 | _sent = true; | ||
348 | } | ||
349 | |||
350 | public void SendHeaders() | ||
351 | { | ||
352 | if (_headersSent) throw new InvalidOperationException("headers already sent"); | ||
353 | _headersSent = true; | ||
354 | } | ||
355 | |||
356 | public void Redirect(Uri uri) {} | ||
357 | public void Redirect(string url) {} | ||
358 | } | ||
359 | |||
360 | |||
361 | public OSHttpRequest req0; | 45 | public OSHttpRequest req0; |
362 | public OSHttpRequest req1; | 46 | public OSHttpRequest req1; |
363 | 47 | ||
@@ -429,4 +113,4 @@ namespace OpenSim.Framework.Servers.Tests | |||
429 | Assert.That(rsp0.ContentType, Is.EqualTo("text/xml")); | 113 | Assert.That(rsp0.ContentType, Is.EqualTo("text/xml")); |
430 | } | 114 | } |
431 | } | 115 | } |
432 | } | 116 | } \ No newline at end of file |
diff --git a/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs b/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs new file mode 100644 index 0000000..edc5016 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs | |||
@@ -0,0 +1,158 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Generic; | ||
31 | using System.IO; | ||
32 | using System.Net; | ||
33 | using System.Text; | ||
34 | using HttpServer; | ||
35 | using log4net.Config; | ||
36 | using Nini.Config; | ||
37 | using NUnit.Framework; | ||
38 | using OpenMetaverse; | ||
39 | using OpenMetaverse.Packets; | ||
40 | using OpenMetaverse.StructuredData; | ||
41 | using OpenSim.Framework; | ||
42 | using OpenSim.Framework.Capabilities; | ||
43 | using OpenSim.Framework.Servers; | ||
44 | using OpenSim.Framework.Servers.HttpServer; | ||
45 | using OpenSim.Region.ClientStack.Linden; | ||
46 | using OpenSim.Region.CoreModules.Framework; | ||
47 | using OpenSim.Region.Framework.Scenes; | ||
48 | using OpenSim.Services.Interfaces; | ||
49 | using OpenSim.Tests.Common; | ||
50 | using OpenSim.Tests.Common.Mock; | ||
51 | using OSDArray = OpenMetaverse.StructuredData.OSDArray; | ||
52 | using OSDMap = OpenMetaverse.StructuredData.OSDMap; | ||
53 | |||
54 | namespace OpenSim.Region.ClientStack.Linden.Caps.Tests | ||
55 | { | ||
56 | [TestFixture] | ||
57 | public class WebFetchInvDescModuleTests : OpenSimTestCase | ||
58 | { | ||
59 | [TestFixtureSetUp] | ||
60 | public void TestFixtureSetUp() | ||
61 | { | ||
62 | // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. | ||
63 | Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; | ||
64 | } | ||
65 | |||
66 | [TestFixtureTearDown] | ||
67 | public void TestFixureTearDown() | ||
68 | { | ||
69 | // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple | ||
70 | // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression | ||
71 | // tests really shouldn't). | ||
72 | Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; | ||
73 | } | ||
74 | |||
75 | [SetUp] | ||
76 | public override void SetUp() | ||
77 | { | ||
78 | base.SetUp(); | ||
79 | |||
80 | // This is an unfortunate bit of clean up we have to do because MainServer manages things through static | ||
81 | // variables and the VM is not restarted between tests. | ||
82 | uint port = 9999; | ||
83 | MainServer.RemoveHttpServer(port); | ||
84 | |||
85 | BaseHttpServer server = new BaseHttpServer(port, false, 0, ""); | ||
86 | MainServer.AddHttpServer(server); | ||
87 | MainServer.Instance = server; | ||
88 | |||
89 | server.Start(false); | ||
90 | } | ||
91 | |||
92 | [Test] | ||
93 | public void TestInventoryDescendentsFetch() | ||
94 | { | ||
95 | TestHelpers.InMethod(); | ||
96 | TestHelpers.EnableLogging(); | ||
97 | |||
98 | BaseHttpServer httpServer = MainServer.Instance; | ||
99 | Scene scene = new SceneHelpers().SetupScene(); | ||
100 | |||
101 | CapabilitiesModule capsModule = new CapabilitiesModule(); | ||
102 | WebFetchInvDescModule wfidModule = new WebFetchInvDescModule(false); | ||
103 | |||
104 | IConfigSource config = new IniConfigSource(); | ||
105 | config.AddConfig("ClientStack.LindenCaps"); | ||
106 | config.Configs["ClientStack.LindenCaps"].Set("Cap_FetchInventoryDescendents2", "localhost"); | ||
107 | |||
108 | SceneHelpers.SetupSceneModules(scene, config, capsModule, wfidModule); | ||
109 | |||
110 | UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); | ||
111 | |||
112 | // We need a user present to have any capabilities set up | ||
113 | SceneHelpers.AddScenePresence(scene, ua.PrincipalID); | ||
114 | |||
115 | TestHttpRequest req = new TestHttpRequest(); | ||
116 | OpenSim.Framework.Capabilities.Caps userCaps = capsModule.GetCapsForUser(ua.PrincipalID); | ||
117 | PollServiceEventArgs pseArgs; | ||
118 | userCaps.TryGetPollHandler("FetchInventoryDescendents2", out pseArgs); | ||
119 | req.UriPath = pseArgs.Url; | ||
120 | req.Uri = new Uri(req.UriPath); | ||
121 | |||
122 | // Retrieve root folder details directly so that we can request | ||
123 | InventoryFolderBase folder = scene.InventoryService.GetRootFolder(ua.PrincipalID); | ||
124 | |||
125 | OSDMap osdFolder = new OSDMap(); | ||
126 | osdFolder["folder_id"] = folder.ID; | ||
127 | osdFolder["owner_id"] = ua.PrincipalID; | ||
128 | osdFolder["fetch_folders"] = true; | ||
129 | osdFolder["fetch_items"] = true; | ||
130 | osdFolder["sort_order"] = 0; | ||
131 | |||
132 | OSDArray osdFoldersArray = new OSDArray(); | ||
133 | osdFoldersArray.Add(osdFolder); | ||
134 | |||
135 | OSDMap osdReqMap = new OSDMap(); | ||
136 | osdReqMap["folders"] = osdFoldersArray; | ||
137 | |||
138 | req.Body = new MemoryStream(OSDParser.SerializeLLSDXmlBytes(osdReqMap)); | ||
139 | |||
140 | TestHttpClientContext context = new TestHttpClientContext(false); | ||
141 | MainServer.Instance.OnRequest(context, new RequestEventArgs(req)); | ||
142 | |||
143 | // Drive processing of the queued inventory request synchronously. | ||
144 | wfidModule.WaitProcessQueuedInventoryRequest(); | ||
145 | MainServer.Instance.PollServiceRequestManager.WaitPerformResponse(); | ||
146 | |||
147 | // System.Threading.Thread.Sleep(10000); | ||
148 | |||
149 | OSDMap responseOsd = (OSDMap)OSDParser.DeserializeLLSDXml(context.ResponseBody); | ||
150 | OSDArray foldersOsd = (OSDArray)responseOsd["folders"]; | ||
151 | OSDMap folderOsd = (OSDMap)foldersOsd[0]; | ||
152 | |||
153 | // A sanity check that the response has the expected number of descendents for a default inventory | ||
154 | // TODO: Need a more thorough check. | ||
155 | Assert.That((int)folderOsd["descendents"], Is.EqualTo(14)); | ||
156 | } | ||
157 | } | ||
158 | } \ No newline at end of file | ||
diff --git a/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs index 340d2e7..f0dccda 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs | |||
@@ -65,6 +65,15 @@ namespace OpenSim.Region.ClientStack.Linden | |||
65 | 65 | ||
66 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 66 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
67 | 67 | ||
68 | |||
69 | /// <summary> | ||
70 | /// Control whether requests will be processed asynchronously. | ||
71 | /// </summary> | ||
72 | /// <remarks> | ||
73 | /// Defaults to true. Can currently not be changed once a region has been added to the module. | ||
74 | /// </remarks> | ||
75 | public bool ProcessQueuedRequestsAsync { get; private set; } | ||
76 | |||
68 | private Scene m_scene; | 77 | private Scene m_scene; |
69 | 78 | ||
70 | private IInventoryService m_InventoryService; | 79 | private IInventoryService m_InventoryService; |
@@ -84,6 +93,13 @@ namespace OpenSim.Region.ClientStack.Linden | |||
84 | 93 | ||
85 | #region ISharedRegionModule Members | 94 | #region ISharedRegionModule Members |
86 | 95 | ||
96 | public WebFetchInvDescModule() : this(true) {} | ||
97 | |||
98 | public WebFetchInvDescModule(bool processQueuedResultsAsync) | ||
99 | { | ||
100 | ProcessQueuedRequestsAsync = processQueuedResultsAsync; | ||
101 | } | ||
102 | |||
87 | public void Initialise(IConfigSource source) | 103 | public void Initialise(IConfigSource source) |
88 | { | 104 | { |
89 | IConfig config = source.Configs["ClientStack.LindenCaps"]; | 105 | IConfig config = source.Configs["ClientStack.LindenCaps"]; |
@@ -114,8 +130,16 @@ namespace OpenSim.Region.ClientStack.Linden | |||
114 | 130 | ||
115 | m_scene.EventManager.OnRegisterCaps -= RegisterCaps; | 131 | m_scene.EventManager.OnRegisterCaps -= RegisterCaps; |
116 | 132 | ||
117 | foreach (Thread t in m_workerThreads) | 133 | if (ProcessQueuedRequestsAsync) |
118 | Watchdog.AbortThread(t.ManagedThreadId); | 134 | { |
135 | if (m_workerThreads != null) | ||
136 | { | ||
137 | foreach (Thread t in m_workerThreads) | ||
138 | Watchdog.AbortThread(t.ManagedThreadId); | ||
139 | |||
140 | m_workerThreads = null; | ||
141 | } | ||
142 | } | ||
119 | 143 | ||
120 | m_scene = null; | 144 | m_scene = null; |
121 | } | 145 | } |
@@ -133,7 +157,7 @@ namespace OpenSim.Region.ClientStack.Linden | |||
133 | 157 | ||
134 | m_scene.EventManager.OnRegisterCaps += RegisterCaps; | 158 | m_scene.EventManager.OnRegisterCaps += RegisterCaps; |
135 | 159 | ||
136 | if (m_workerThreads == null) | 160 | if (ProcessQueuedRequestsAsync && m_workerThreads == null) |
137 | { | 161 | { |
138 | m_workerThreads = new Thread[2]; | 162 | m_workerThreads = new Thread[2]; |
139 | 163 | ||
@@ -358,11 +382,16 @@ namespace OpenSim.Region.ClientStack.Linden | |||
358 | { | 382 | { |
359 | Watchdog.UpdateThread(); | 383 | Watchdog.UpdateThread(); |
360 | 384 | ||
361 | aPollRequest poolreq = m_queue.Dequeue(); | 385 | WaitProcessQueuedInventoryRequest(); |
362 | |||
363 | if (poolreq != null && poolreq.thepoll != null) | ||
364 | poolreq.thepoll.Process(poolreq); | ||
365 | } | 386 | } |
366 | } | 387 | } |
388 | |||
389 | public void WaitProcessQueuedInventoryRequest() | ||
390 | { | ||
391 | aPollRequest poolreq = m_queue.Dequeue(); | ||
392 | |||
393 | if (poolreq != null && poolreq.thepoll != null) | ||
394 | poolreq.thepoll.Process(poolreq); | ||
395 | } | ||
367 | } | 396 | } |
368 | } | 397 | } |
diff --git a/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs b/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs index 13cc99a..817ef85 100644 --- a/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs +++ b/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs | |||
@@ -137,6 +137,10 @@ namespace OpenSim.Region.CoreModules.Framework | |||
137 | // agentId, m_scene.RegionInfo.RegionName, oldCaps.CapsObjectPath, capsObjectPath); | 137 | // agentId, m_scene.RegionInfo.RegionName, oldCaps.CapsObjectPath, capsObjectPath); |
138 | } | 138 | } |
139 | 139 | ||
140 | // m_log.DebugFormat( | ||
141 | // "[CAPS]: Adding capabilities for agent {0} in {1} with path {2}", | ||
142 | // agentId, m_scene.RegionInfo.RegionName, capsObjectPath); | ||
143 | |||
140 | caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName, | 144 | caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName, |
141 | (MainServer.Instance == null) ? 0: MainServer.Instance.Port, | 145 | (MainServer.Instance == null) ? 0: MainServer.Instance.Port, |
142 | capsObjectPath, agentId, m_scene.RegionInfo.RegionName); | 146 | capsObjectPath, agentId, m_scene.RegionInfo.RegionName); |
diff --git a/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs b/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs new file mode 100644 index 0000000..5a55b09 --- /dev/null +++ b/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs | |||
@@ -0,0 +1,110 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Net; | ||
32 | using System.Net.Sockets; | ||
33 | using System.Text; | ||
34 | using HttpServer; | ||
35 | using OpenSim.Framework; | ||
36 | |||
37 | namespace OpenSim.Tests.Common | ||
38 | { | ||
39 | public class TestHttpClientContext: IHttpClientContext | ||
40 | { | ||
41 | /// <summary> | ||
42 | /// Bodies of responses from the server. | ||
43 | /// </summary> | ||
44 | public string ResponseBody | ||
45 | { | ||
46 | get { return Encoding.UTF8.GetString(m_responseStream.ToArray()); } | ||
47 | } | ||
48 | |||
49 | public Byte[] ResponseBodyBytes | ||
50 | { | ||
51 | get{ return m_responseStream.ToArray(); } | ||
52 | } | ||
53 | |||
54 | private MemoryStream m_responseStream = new MemoryStream(); | ||
55 | |||
56 | public bool IsSecured { get; set; } | ||
57 | |||
58 | public bool Secured | ||
59 | { | ||
60 | get { return IsSecured; } | ||
61 | set { IsSecured = value; } | ||
62 | } | ||
63 | |||
64 | public TestHttpClientContext(bool secured) | ||
65 | { | ||
66 | Secured = secured; | ||
67 | } | ||
68 | |||
69 | public void Disconnect(SocketError error) | ||
70 | { | ||
71 | // Console.WriteLine("TestHttpClientContext.Disconnect Received disconnect with status {0}", error); | ||
72 | } | ||
73 | |||
74 | public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {Console.WriteLine("x");} | ||
75 | public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {Console.WriteLine("xx");} | ||
76 | public void Respond(string body) { Console.WriteLine("xxx");} | ||
77 | |||
78 | public void Send(byte[] buffer) | ||
79 | { | ||
80 | // Getting header data here | ||
81 | // Console.WriteLine("xxxx: Got {0}", Encoding.UTF8.GetString(buffer)); | ||
82 | } | ||
83 | |||
84 | public void Send(byte[] buffer, int offset, int size) | ||
85 | { | ||
86 | // Util.PrintCallStack(); | ||
87 | // | ||
88 | // Console.WriteLine( | ||
89 | // "TestHttpClientContext.Send(byte[], int, int) got offset={0}, size={1}, buffer={2}", | ||
90 | // offset, size, Encoding.UTF8.GetString(buffer)); | ||
91 | |||
92 | m_responseStream.Write(buffer, offset, size); | ||
93 | } | ||
94 | |||
95 | public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) {Console.WriteLine("xxxxxx");} | ||
96 | public void Close() { } | ||
97 | public bool EndWhenDone { get { return false;} set { return;}} | ||
98 | |||
99 | public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() | ||
100 | { | ||
101 | return new HTTPNetworkContext(); | ||
102 | } | ||
103 | |||
104 | public event EventHandler<DisconnectedEventArgs> Disconnected = delegate { }; | ||
105 | /// <summary> | ||
106 | /// A request have been received in the context. | ||
107 | /// </summary> | ||
108 | public event EventHandler<RequestEventArgs> RequestReceived = delegate { }; | ||
109 | } | ||
110 | } \ No newline at end of file | ||
diff --git a/OpenSim/Tests/Common/Mock/TestHttpRequest.cs b/OpenSim/Tests/Common/Mock/TestHttpRequest.cs new file mode 100644 index 0000000..b868895 --- /dev/null +++ b/OpenSim/Tests/Common/Mock/TestHttpRequest.cs | |||
@@ -0,0 +1,174 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Specialized; | ||
30 | using System.IO; | ||
31 | using HttpServer; | ||
32 | using HttpServer.FormDecoders; | ||
33 | |||
34 | namespace OpenSim.Tests.Common | ||
35 | { | ||
36 | public class TestHttpRequest: IHttpRequest | ||
37 | { | ||
38 | private string _uriPath; | ||
39 | public bool BodyIsComplete | ||
40 | { | ||
41 | get { return true; } | ||
42 | } | ||
43 | public string[] AcceptTypes | ||
44 | { | ||
45 | get {return _acceptTypes; } | ||
46 | } | ||
47 | private string[] _acceptTypes; | ||
48 | public Stream Body | ||
49 | { | ||
50 | get { return _body; } | ||
51 | set { _body = value;} | ||
52 | } | ||
53 | private Stream _body; | ||
54 | public ConnectionType Connection | ||
55 | { | ||
56 | get { return _connection; } | ||
57 | set { _connection = value; } | ||
58 | } | ||
59 | private ConnectionType _connection; | ||
60 | public int ContentLength | ||
61 | { | ||
62 | get { return _contentLength; } | ||
63 | set { _contentLength = value; } | ||
64 | } | ||
65 | private int _contentLength; | ||
66 | public NameValueCollection Headers | ||
67 | { | ||
68 | get { return _headers; } | ||
69 | } | ||
70 | private NameValueCollection _headers = new NameValueCollection(); | ||
71 | |||
72 | public string HttpVersion { get; set; } | ||
73 | |||
74 | public string Method | ||
75 | { | ||
76 | get { return _method; } | ||
77 | set { _method = value; } | ||
78 | } | ||
79 | private string _method = null; | ||
80 | public HttpInput QueryString | ||
81 | { | ||
82 | get { return _queryString; } | ||
83 | } | ||
84 | private HttpInput _queryString = null; | ||
85 | public Uri Uri | ||
86 | { | ||
87 | get { return _uri; } | ||
88 | set { _uri = value; } | ||
89 | } | ||
90 | private Uri _uri = null; | ||
91 | public string[] UriParts | ||
92 | { | ||
93 | get { return _uri.Segments; } | ||
94 | } | ||
95 | public HttpParam Param | ||
96 | { | ||
97 | get { return null; } | ||
98 | } | ||
99 | public HttpForm Form | ||
100 | { | ||
101 | get { return null; } | ||
102 | } | ||
103 | public bool IsAjax | ||
104 | { | ||
105 | get { return false; } | ||
106 | } | ||
107 | public RequestCookies Cookies | ||
108 | { | ||
109 | get { return null; } | ||
110 | } | ||
111 | |||
112 | public TestHttpRequest() | ||
113 | { | ||
114 | HttpVersion = "HTTP/1.1"; | ||
115 | } | ||
116 | |||
117 | public TestHttpRequest(string contentEncoding, string contentType, string userAgent, | ||
118 | string remoteAddr, string remotePort, string[] acceptTypes, | ||
119 | ConnectionType connectionType, int contentLength, Uri uri) : base() | ||
120 | { | ||
121 | _headers["content-encoding"] = contentEncoding; | ||
122 | _headers["content-type"] = contentType; | ||
123 | _headers["user-agent"] = userAgent; | ||
124 | _headers["remote_addr"] = remoteAddr; | ||
125 | _headers["remote_port"] = remotePort; | ||
126 | |||
127 | _acceptTypes = acceptTypes; | ||
128 | _connection = connectionType; | ||
129 | _contentLength = contentLength; | ||
130 | _uri = uri; | ||
131 | } | ||
132 | |||
133 | public void DecodeBody(FormDecoderProvider providers) {} | ||
134 | public void SetCookies(RequestCookies cookies) {} | ||
135 | public void AddHeader(string name, string value) | ||
136 | { | ||
137 | _headers.Add(name, value); | ||
138 | } | ||
139 | public int AddToBody(byte[] bytes, int offset, int length) | ||
140 | { | ||
141 | return 0; | ||
142 | } | ||
143 | public void Clear() {} | ||
144 | |||
145 | public object Clone() | ||
146 | { | ||
147 | TestHttpRequest clone = new TestHttpRequest(); | ||
148 | clone._acceptTypes = _acceptTypes; | ||
149 | clone._connection = _connection; | ||
150 | clone._contentLength = _contentLength; | ||
151 | clone._uri = _uri; | ||
152 | clone._headers = new NameValueCollection(_headers); | ||
153 | |||
154 | return clone; | ||
155 | } | ||
156 | public IHttpResponse CreateResponse(IHttpClientContext context) | ||
157 | { | ||
158 | return new HttpResponse(context, this); | ||
159 | } | ||
160 | /// <summary> | ||
161 | /// Path and query (will be merged with the host header) and put in Uri | ||
162 | /// </summary> | ||
163 | /// <see cref="Uri"/> | ||
164 | public string UriPath | ||
165 | { | ||
166 | get { return _uriPath; } | ||
167 | set | ||
168 | { | ||
169 | _uriPath = value; | ||
170 | |||
171 | } | ||
172 | } | ||
173 | } | ||
174 | } \ No newline at end of file | ||
diff --git a/OpenSim/Tests/Common/Mock/TestHttpResponse.cs b/OpenSim/Tests/Common/Mock/TestHttpResponse.cs new file mode 100644 index 0000000..ff47c10 --- /dev/null +++ b/OpenSim/Tests/Common/Mock/TestHttpResponse.cs | |||
@@ -0,0 +1,171 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.IO; | ||
30 | using System.Net; | ||
31 | using System.Text; | ||
32 | using HttpServer; | ||
33 | |||
34 | namespace OpenSim.Tests.Common | ||
35 | { | ||
36 | public class TestHttpResponse: IHttpResponse | ||
37 | { | ||
38 | public Stream Body | ||
39 | { | ||
40 | get { return _body; } | ||
41 | |||
42 | set { _body = value; } | ||
43 | } | ||
44 | private Stream _body; | ||
45 | |||
46 | public string ProtocolVersion | ||
47 | { | ||
48 | get { return _protocolVersion; } | ||
49 | set { _protocolVersion = value; } | ||
50 | } | ||
51 | private string _protocolVersion; | ||
52 | |||
53 | public bool Chunked | ||
54 | { | ||
55 | get { return _chunked; } | ||
56 | |||
57 | set { _chunked = value; } | ||
58 | } | ||
59 | private bool _chunked; | ||
60 | |||
61 | public ConnectionType Connection | ||
62 | { | ||
63 | get { return _connection; } | ||
64 | |||
65 | set { _connection = value; } | ||
66 | } | ||
67 | private ConnectionType _connection; | ||
68 | |||
69 | public Encoding Encoding | ||
70 | { | ||
71 | get { return _encoding; } | ||
72 | |||
73 | set { _encoding = value; } | ||
74 | } | ||
75 | private Encoding _encoding; | ||
76 | |||
77 | public int KeepAlive | ||
78 | { | ||
79 | get { return _keepAlive; } | ||
80 | |||
81 | set { _keepAlive = value; } | ||
82 | } | ||
83 | private int _keepAlive; | ||
84 | |||
85 | public HttpStatusCode Status | ||
86 | { | ||
87 | get { return _status; } | ||
88 | |||
89 | set { _status = value; } | ||
90 | } | ||
91 | private HttpStatusCode _status; | ||
92 | |||
93 | public string Reason | ||
94 | { | ||
95 | get { return _reason; } | ||
96 | |||
97 | set { _reason = value; } | ||
98 | } | ||
99 | private string _reason; | ||
100 | |||
101 | public long ContentLength | ||
102 | { | ||
103 | get { return _contentLength; } | ||
104 | |||
105 | set { _contentLength = value; } | ||
106 | } | ||
107 | private long _contentLength; | ||
108 | |||
109 | public string ContentType | ||
110 | { | ||
111 | get { return _contentType; } | ||
112 | |||
113 | set { _contentType = value; } | ||
114 | } | ||
115 | private string _contentType; | ||
116 | |||
117 | public bool HeadersSent | ||
118 | { | ||
119 | get { return _headersSent; } | ||
120 | } | ||
121 | private bool _headersSent; | ||
122 | |||
123 | public bool Sent | ||
124 | { | ||
125 | get { return _sent; } | ||
126 | } | ||
127 | private bool _sent; | ||
128 | |||
129 | public ResponseCookies Cookies | ||
130 | { | ||
131 | get { return _cookies; } | ||
132 | } | ||
133 | private ResponseCookies _cookies = null; | ||
134 | |||
135 | public TestHttpResponse() | ||
136 | { | ||
137 | _headersSent = false; | ||
138 | _sent = false; | ||
139 | } | ||
140 | |||
141 | public void AddHeader(string name, string value) {} | ||
142 | |||
143 | public void Send() | ||
144 | { | ||
145 | if (!_headersSent) SendHeaders(); | ||
146 | if (_sent) throw new InvalidOperationException("stuff already sent"); | ||
147 | _sent = true; | ||
148 | } | ||
149 | |||
150 | public void SendBody(byte[] buffer, int offset, int count) | ||
151 | { | ||
152 | if (!_headersSent) SendHeaders(); | ||
153 | _sent = true; | ||
154 | } | ||
155 | |||
156 | public void SendBody(byte[] buffer) | ||
157 | { | ||
158 | if (!_headersSent) SendHeaders(); | ||
159 | _sent = true; | ||
160 | } | ||
161 | |||
162 | public void SendHeaders() | ||
163 | { | ||
164 | if (_headersSent) throw new InvalidOperationException("headers already sent"); | ||
165 | _headersSent = true; | ||
166 | } | ||
167 | |||
168 | public void Redirect(Uri uri) {} | ||
169 | public void Redirect(string url) {} | ||
170 | } | ||
171 | } \ No newline at end of file | ||
diff --git a/prebuild.xml b/prebuild.xml index 356110a..c16d6e6 100644 --- a/prebuild.xml +++ b/prebuild.xml | |||
@@ -3321,12 +3321,14 @@ | |||
3321 | <ReferencePath>../../../../../bin/</ReferencePath> | 3321 | <ReferencePath>../../../../../bin/</ReferencePath> |
3322 | <Reference name="System"/> | 3322 | <Reference name="System"/> |
3323 | <Reference name="System.Xml"/> | 3323 | <Reference name="System.Xml"/> |
3324 | <Reference name="HttpServer_OpenSim" path="../../../../../bin/"/> | ||
3324 | <Reference name="log4net" path="../../../../../bin/"/> | 3325 | <Reference name="log4net" path="../../../../../bin/"/> |
3325 | <Reference name="Nini" path="../../../../../bin/"/> | 3326 | <Reference name="Nini" path="../../../../../bin/"/> |
3326 | <Reference name="nunit.framework" path="../../../../../bin/"/> | 3327 | <Reference name="nunit.framework" path="../../../../../bin/"/> |
3327 | <Reference name="OpenMetaverse" path="../../../../../bin/"/> | 3328 | <Reference name="OpenMetaverse" path="../../../../../bin/"/> |
3328 | <Reference name="OpenMetaverseTypes" path="../../../../../bin/"/> | 3329 | <Reference name="OpenMetaverseTypes" path="../../../../../bin/"/> |
3329 | <Reference name="OpenMetaverse.StructuredData" path="../../../../../bin/"/> | 3330 | <Reference name="OpenMetaverse.StructuredData" path="../../../../../bin/"/> |
3331 | <Reference name="OpenSim.Capabilities"/> | ||
3330 | <Reference name="OpenSim.Framework"/> | 3332 | <Reference name="OpenSim.Framework"/> |
3331 | <Reference name="OpenSim.Framework.Communications"/> | 3333 | <Reference name="OpenSim.Framework.Communications"/> |
3332 | <Reference name="OpenSim.Framework.Monitoring"/> | 3334 | <Reference name="OpenSim.Framework.Monitoring"/> |
@@ -3341,6 +3343,7 @@ | |||
3341 | <Reference name="OpenSim.Tests.Common"/> | 3343 | <Reference name="OpenSim.Tests.Common"/> |
3342 | 3344 | ||
3343 | <Files> | 3345 | <Files> |
3346 | <Match path="Tests" pattern="*.cs" recurse="false"/> | ||
3344 | <Match path="EventQueue/Tests" pattern="*.cs" recurse="false"/> | 3347 | <Match path="EventQueue/Tests" pattern="*.cs" recurse="false"/> |
3345 | </Files> | 3348 | </Files> |
3346 | </Project> | 3349 | </Project> |