diff options
Diffstat (limited to 'OpenSim/Region/Environment/Modules/Avatar/InstantMessage')
-rw-r--r-- | OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs | 689 | ||||
-rw-r--r-- | OpenSim/Region/Environment/Modules/Avatar/InstantMessage/MessageTransferModule.cs | 626 |
2 files changed, 682 insertions, 633 deletions
diff --git a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs index 805f7cb..1b7eb97 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs | |||
@@ -36,6 +36,7 @@ using Nini.Config; | |||
36 | using Nwc.XmlRpc; | 36 | using Nwc.XmlRpc; |
37 | using OpenSim.Framework; | 37 | using OpenSim.Framework; |
38 | using OpenSim.Framework.Client; | 38 | using OpenSim.Framework.Client; |
39 | using OpenSim.Region.Interfaces; | ||
39 | using OpenSim.Region.Environment.Interfaces; | 40 | using OpenSim.Region.Environment.Interfaces; |
40 | using OpenSim.Region.Environment.Scenes; | 41 | using OpenSim.Region.Environment.Scenes; |
41 | 42 | ||
@@ -46,12 +47,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage | |||
46 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 47 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
47 | 48 | ||
48 | private readonly List<Scene> m_scenes = new List<Scene>(); | 49 | private readonly List<Scene> m_scenes = new List<Scene>(); |
49 | private Dictionary<UUID, ulong> m_userRegionMap = new Dictionary<UUID, ulong>(); | ||
50 | 50 | ||
51 | #region IRegionModule Members | 51 | #region IRegionModule Members |
52 | 52 | ||
53 | private bool gridmode = false; | 53 | private bool gridmode = false; |
54 | 54 | ||
55 | private IMessageTransferModule m_TransferModule = null; | ||
55 | 56 | ||
56 | public void Initialise(Scene scene, IConfigSource config) | 57 | public void Initialise(Scene scene, IConfigSource config) |
57 | { | 58 | { |
@@ -65,18 +66,11 @@ namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage | |||
65 | 66 | ||
66 | lock (m_scenes) | 67 | lock (m_scenes) |
67 | { | 68 | { |
68 | if (m_scenes.Count == 0) | ||
69 | { | ||
70 | //scene.AddXmlRPCHandler("avatar_location_update", processPresenceUpdate); | ||
71 | scene.AddXmlRPCHandler("grid_instant_message", processXMLRPCGridInstantMessage); | ||
72 | ReadConfig(config); | ||
73 | } | ||
74 | |||
75 | if (!m_scenes.Contains(scene)) | 69 | if (!m_scenes.Contains(scene)) |
76 | { | 70 | { |
77 | m_scenes.Add(scene); | 71 | m_scenes.Add(scene); |
78 | scene.EventManager.OnClientConnect += OnClientConnect; | 72 | scene.EventManager.OnClientConnect += OnClientConnect; |
79 | scene.EventManager.OnGridInstantMessage += OnGridInstantMessage; | 73 | scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage; |
80 | } | 74 | } |
81 | } | 75 | } |
82 | } | 76 | } |
@@ -90,17 +84,14 @@ namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage | |||
90 | } | 84 | } |
91 | } | 85 | } |
92 | 86 | ||
93 | private void ReadConfig(IConfigSource config) | ||
94 | { | ||
95 | IConfig cnf = config.Configs["Startup"]; | ||
96 | if (cnf != null) | ||
97 | { | ||
98 | gridmode = cnf.GetBoolean("gridmode", false); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | public void PostInitialise() | 87 | public void PostInitialise() |
103 | { | 88 | { |
89 | m_TransferModule = | ||
90 | m_scenes[0].RequestModuleInterface<IMessageTransferModule>(); | ||
91 | |||
92 | if (m_TransferModule == null) | ||
93 | m_log.Error("[INSTANT MESSAGE]: No message transfer module, "+ | ||
94 | "IM will not work!"); | ||
104 | } | 95 | } |
105 | 96 | ||
106 | public void Close() | 97 | public void Close() |
@@ -120,639 +111,71 @@ namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage | |||
120 | #endregion | 111 | #endregion |
121 | 112 | ||
122 | private void OnInstantMessage(IClientAPI client, UUID fromAgentID, | 113 | private void OnInstantMessage(IClientAPI client, UUID fromAgentID, |
123 | UUID fromAgentSession, UUID toAgentID, | 114 | UUID fromAgentSession, UUID toAgentID, |
124 | UUID imSessionID, uint timestamp, string fromAgentName, | 115 | UUID imSessionID, uint timestamp, string fromAgentName, |
125 | string message, byte dialog, bool fromGroup, byte offline, | 116 | string message, byte dialog, bool fromGroup, byte offline, |
126 | uint ParentEstateID, Vector3 Position, UUID RegionID, | 117 | uint ParentEstateID, Vector3 Position, UUID RegionID, |
127 | byte[] binaryBucket) | 118 | byte[] binaryBucket) |
128 | { | 119 | { |
129 | bool dialogHandledElsewhere | 120 | // This module handles exclusively private text IM from user |
130 | = ( dialog == (byte) InstantMessageDialog.FriendshipOffered | 121 | // to user. All others will be caught in other modules |
131 | || dialog == (byte) InstantMessageDialog.FriendshipAccepted | 122 | // |
132 | || dialog == (byte) InstantMessageDialog.FriendshipDeclined | 123 | if ( dialog != (byte)InstantMessageDialog.MessageFromAgent |
133 | || dialog == (byte) InstantMessageDialog.InventoryOffered | 124 | && dialog != (byte)InstantMessageDialog.StartTyping |
134 | || dialog == (byte) InstantMessageDialog.InventoryAccepted | 125 | && dialog != (byte)InstantMessageDialog.StopTyping) |
135 | || dialog == (byte) InstantMessageDialog.InventoryDeclined | ||
136 | || dialog == (byte) InstantMessageDialog.GroupNoticeInventoryAccepted | ||
137 | || dialog == (byte) InstantMessageDialog.GroupNoticeInventoryDeclined | ||
138 | || dialog == (byte) InstantMessageDialog.GroupInvitationAccept | ||
139 | || dialog == (byte) InstantMessageDialog.GroupInvitationDecline | ||
140 | || dialog == (byte) InstantMessageDialog.GroupNotice); | ||
141 | |||
142 | // IM dialogs need to be pre-processed and have their sessionID filled by the server | ||
143 | // so the sim can match the transaction on the return packet. | ||
144 | |||
145 | // Don't process IMs that are handled elsewhere (e.g. friend dialog | ||
146 | // IMs) with a non-UUID.Zero agent session, as those have been send | ||
147 | // by a client (either directly or from another region via | ||
148 | // inter-region communication) and will be processed in another | ||
149 | // module (e.g. the friends-module). | ||
150 | // IMs with fromAgentSession == UUID.Zero come from the server, and | ||
151 | // have to be passed to the matching viewer | ||
152 | if (!dialogHandledElsewhere || fromAgentSession == UUID.Zero) | ||
153 | { | 126 | { |
154 | // Try root avatar only first | ||
155 | foreach (Scene scene in m_scenes) | ||
156 | { | ||
157 | if (scene.Entities.ContainsKey(toAgentID) && scene.Entities[toAgentID] is ScenePresence) | ||
158 | { | ||
159 | // Local message | ||
160 | ScenePresence user = (ScenePresence) scene.Entities[toAgentID]; | ||
161 | if (!user.IsChildAgent) | ||
162 | { | ||
163 | user.ControllingClient.SendInstantMessage(fromAgentID, message, | ||
164 | toAgentID, fromAgentName, dialog, | ||
165 | timestamp, imSessionID, fromGroup, binaryBucket); | ||
166 | // Message sent | ||
167 | return; | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | |||
172 | // try child avatar second | ||
173 | foreach (Scene scene in m_scenes) | ||
174 | { | ||
175 | if (scene.Entities.ContainsKey(toAgentID) && scene.Entities[toAgentID] is ScenePresence) | ||
176 | { | ||
177 | // Local message | ||
178 | ScenePresence user = (ScenePresence) scene.Entities[toAgentID]; | ||
179 | |||
180 | user.ControllingClient.SendInstantMessage(fromAgentID, message, | ||
181 | toAgentID, fromAgentName, dialog, | ||
182 | timestamp, imSessionID, fromGroup, binaryBucket); | ||
183 | // Message sent | ||
184 | return; | ||
185 | } | ||
186 | } | ||
187 | if (gridmode) | ||
188 | { | ||
189 | // Still here, try send via Grid | ||
190 | |||
191 | // don't send session drop yet, as it's not reliable somehow. | ||
192 | if (dialog != (byte)InstantMessageDialog.SessionDrop) | ||
193 | { | ||
194 | SendGridInstantMessageViaXMLRPC(client, fromAgentID, | ||
195 | fromAgentSession, toAgentID, | ||
196 | imSessionID, timestamp, fromAgentName, | ||
197 | message, dialog, fromGroup, offline, | ||
198 | ParentEstateID, Position, RegionID, | ||
199 | binaryBucket, getLocalRegionHandleFromUUID(RegionID), 0); | ||
200 | } | ||
201 | } | ||
202 | else | ||
203 | { | ||
204 | if (client != null) | ||
205 | { | ||
206 | if (dialog != (byte)InstantMessageDialog.StartTyping && dialog != (byte)InstantMessageDialog.StopTyping && dialog != (byte)InstantMessageDialog.SessionDrop) | ||
207 | client.SendInstantMessage(toAgentID, "Unable to send instant message. User is not logged in.", fromAgentID, "System", (byte)InstantMessageDialog.BusyAutoResponse, (uint)Util.UnixTimeSinceEpoch());// SendAlertMessage("Unable to send instant message"); | ||
208 | } | ||
209 | } | ||
210 | } | ||
211 | |||
212 | |||
213 | } | ||
214 | |||
215 | // Trusty OSG1 called method. This method also gets called from the FriendsModule | ||
216 | // Turns out the sim has to send an instant message to the user to get it to show an accepted friend. | ||
217 | /// <summary> | ||
218 | /// | ||
219 | /// </summary> | ||
220 | /// <param name="msg"></param> | ||
221 | private void OnGridInstantMessage(GridInstantMessage msg, InstantMessageReceiver which) | ||
222 | { | ||
223 | if ((which & InstantMessageReceiver.IMModule) == 0) | ||
224 | return; | 127 | return; |
225 | |||
226 | // Trigger the above event handler | ||
227 | OnInstantMessage(null, new UUID(msg.fromAgentID), new UUID(msg.fromAgentSession), | ||
228 | new UUID(msg.toAgentID), new UUID(msg.imSessionID), msg.timestamp, msg.fromAgentName, | ||
229 | msg.message, msg.dialog, msg.fromGroup, msg.offline, msg.ParentEstateID, | ||
230 | new Vector3(msg.Position.X, msg.Position.Y, msg.Position.Z), new UUID(msg.RegionID), | ||
231 | msg.binaryBucket); | ||
232 | } | ||
233 | |||
234 | |||
235 | /// <summary> | ||
236 | /// Process a XMLRPC Grid Instant Message | ||
237 | /// </summary> | ||
238 | /// <param name="request">XMLRPC parameters from_agent_id from_agent_session to_agent_id im_session_id timestamp | ||
239 | /// from_agent_name message dialog from_group offline parent_estate_id position_x position_y position_z region_id | ||
240 | /// binary_bucket region_handle</param> | ||
241 | /// <returns>Nothing much</returns> | ||
242 | protected virtual XmlRpcResponse processXMLRPCGridInstantMessage(XmlRpcRequest request) | ||
243 | { | ||
244 | bool successful = false; | ||
245 | // various rational defaults | ||
246 | UUID fromAgentID = UUID.Zero; | ||
247 | UUID fromAgentSession = UUID.Zero; | ||
248 | UUID toAgentID = UUID.Zero; | ||
249 | UUID imSessionID = UUID.Zero; | ||
250 | uint timestamp = 0; | ||
251 | string fromAgentName = ""; | ||
252 | string message = ""; | ||
253 | byte dialog = (byte)0; | ||
254 | bool fromGroup = false; | ||
255 | byte offline = (byte)0; | ||
256 | uint ParentEstateID=0; | ||
257 | Vector3 Position = Vector3.Zero; | ||
258 | UUID RegionID = UUID.Zero ; | ||
259 | byte[] binaryBucket = new byte[0]; | ||
260 | |||
261 | float pos_x = 0; | ||
262 | float pos_y = 0; | ||
263 | float pos_z = 0; | ||
264 | //m_log.Info("Processing IM"); | ||
265 | |||
266 | |||
267 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
268 | // Check if it's got all the data | ||
269 | if (requestData.ContainsKey("from_agent_id") && requestData.ContainsKey("from_agent_session") | ||
270 | && requestData.ContainsKey("to_agent_id") && requestData.ContainsKey("im_session_id") | ||
271 | && requestData.ContainsKey("timestamp") && requestData.ContainsKey("from_agent_name") | ||
272 | && requestData.ContainsKey("message") && requestData.ContainsKey("dialog") | ||
273 | && requestData.ContainsKey("from_group") | ||
274 | && requestData.ContainsKey("offline") && requestData.ContainsKey("parent_estate_id") | ||
275 | && requestData.ContainsKey("position_x") && requestData.ContainsKey("position_y") | ||
276 | && requestData.ContainsKey("position_z") && requestData.ContainsKey("region_id") | ||
277 | && requestData.ContainsKey("binary_bucket") && requestData.ContainsKey("region_handle")) | ||
278 | { | ||
279 | // Do the easy way of validating the UUIDs | ||
280 | UUID.TryParse((string)requestData["from_agent_id"], out fromAgentID); | ||
281 | UUID.TryParse((string)requestData["from_agent_session"], out fromAgentSession); | ||
282 | UUID.TryParse((string)requestData["to_agent_id"], out toAgentID); | ||
283 | UUID.TryParse((string)requestData["im_session_id"], out imSessionID); | ||
284 | UUID.TryParse((string)requestData["region_id"], out RegionID); | ||
285 | |||
286 | # region timestamp | ||
287 | try | ||
288 | { | ||
289 | timestamp = (uint)Convert.ToInt32((string)requestData["timestamp"]); | ||
290 | } | ||
291 | catch (ArgumentException) | ||
292 | { | ||
293 | } | ||
294 | catch (FormatException) | ||
295 | { | ||
296 | } | ||
297 | catch (OverflowException) | ||
298 | { | ||
299 | } | ||
300 | # endregion | ||
301 | |||
302 | fromAgentName = (string)requestData["from_agent_name"]; | ||
303 | message = (string)requestData["message"]; | ||
304 | |||
305 | // Bytes don't transfer well over XMLRPC, so, we Base64 Encode them. | ||
306 | string requestData1 = (string)requestData["dialog"]; | ||
307 | if (string.IsNullOrEmpty(requestData1)) | ||
308 | { | ||
309 | dialog = 0; | ||
310 | } | ||
311 | else | ||
312 | { | ||
313 | byte[] dialogdata = Convert.FromBase64String(requestData1); | ||
314 | dialog = dialogdata[0]; | ||
315 | } | ||
316 | |||
317 | if ((string)requestData["from_group"] == "TRUE") | ||
318 | fromGroup = true; | ||
319 | |||
320 | string requestData2 = (string)requestData["offline"]; | ||
321 | if (String.IsNullOrEmpty(requestData2)) | ||
322 | { | ||
323 | offline = 0; | ||
324 | } | ||
325 | else | ||
326 | { | ||
327 | byte[] offlinedata = Convert.FromBase64String(requestData2); | ||
328 | offline = offlinedata[0]; | ||
329 | } | ||
330 | |||
331 | # region ParentEstateID | ||
332 | try | ||
333 | { | ||
334 | ParentEstateID = (uint)Convert.ToInt32((string)requestData["parent_estate_id"]); | ||
335 | } | ||
336 | catch (ArgumentException) | ||
337 | { | ||
338 | } | ||
339 | catch (FormatException) | ||
340 | { | ||
341 | } | ||
342 | catch (OverflowException) | ||
343 | { | ||
344 | } | ||
345 | # endregion | ||
346 | |||
347 | # region pos_x | ||
348 | try | ||
349 | { | ||
350 | pos_x = (uint)Convert.ToInt32((string)requestData["position_x"]); | ||
351 | } | ||
352 | catch (ArgumentException) | ||
353 | { | ||
354 | } | ||
355 | catch (FormatException) | ||
356 | { | ||
357 | } | ||
358 | catch (OverflowException) | ||
359 | { | ||
360 | } | ||
361 | # endregion | ||
362 | # region pos_y | ||
363 | try | ||
364 | { | ||
365 | pos_y = (uint)Convert.ToInt32((string)requestData["position_y"]); | ||
366 | } | ||
367 | catch (ArgumentException) | ||
368 | { | ||
369 | } | ||
370 | catch (FormatException) | ||
371 | { | ||
372 | } | ||
373 | catch (OverflowException) | ||
374 | { | ||
375 | } | ||
376 | # endregion | ||
377 | # region pos_z | ||
378 | try | ||
379 | { | ||
380 | pos_z = (uint)Convert.ToInt32((string)requestData["position_z"]); | ||
381 | } | ||
382 | catch (ArgumentException) | ||
383 | { | ||
384 | } | ||
385 | catch (FormatException) | ||
386 | { | ||
387 | } | ||
388 | catch (OverflowException) | ||
389 | { | ||
390 | } | ||
391 | # endregion | ||
392 | |||
393 | Position = new Vector3(pos_x, pos_y, pos_z); | ||
394 | |||
395 | string requestData3 = (string)requestData["binary_bucket"]; | ||
396 | if (string.IsNullOrEmpty(requestData3)) | ||
397 | { | ||
398 | binaryBucket = new byte[0]; | ||
399 | } | ||
400 | else | ||
401 | { | ||
402 | binaryBucket = Convert.FromBase64String(requestData3); | ||
403 | } | ||
404 | |||
405 | // Create a New GridInstantMessageObject the the data | ||
406 | GridInstantMessage gim = new GridInstantMessage(); | ||
407 | gim.fromAgentID = fromAgentID.Guid; | ||
408 | gim.fromAgentName = fromAgentName; | ||
409 | gim.fromAgentSession = fromAgentSession.Guid; | ||
410 | gim.fromGroup = fromGroup; | ||
411 | gim.imSessionID = imSessionID.Guid; | ||
412 | gim.RegionID = RegionID.Guid; | ||
413 | gim.timestamp = timestamp; | ||
414 | gim.toAgentID = toAgentID.Guid; | ||
415 | gim.message = message; | ||
416 | gim.dialog = dialog; | ||
417 | gim.offline = offline; | ||
418 | gim.ParentEstateID = ParentEstateID; | ||
419 | gim.Position = Position; | ||
420 | gim.binaryBucket = binaryBucket; | ||
421 | |||
422 | |||
423 | // Trigger the Instant message in the scene. | ||
424 | foreach (Scene scene in m_scenes) | ||
425 | { | ||
426 | if (scene.Entities.ContainsKey(toAgentID) && scene.Entities[toAgentID] is ScenePresence) | ||
427 | { | ||
428 | // Local message | ||
429 | ScenePresence user = (ScenePresence)scene.Entities[toAgentID]; | ||
430 | if (!user.IsChildAgent) | ||
431 | { | ||
432 | scene.EventManager.TriggerGridInstantMessage(gim, InstantMessageReceiver.FriendsModule | InstantMessageReceiver.GroupsModule | InstantMessageReceiver.IMModule); | ||
433 | successful = true; | ||
434 | } | ||
435 | } | ||
436 | } | ||
437 | //OnGridInstantMessage(gim); | ||
438 | |||
439 | } | 128 | } |
440 | 129 | ||
441 | //Send response back to region calling if it was successful | 130 | GridInstantMessage im = new GridInstantMessage(client.Scene, |
442 | // calling region uses this to know when to look up a user's location again. | 131 | fromAgentID, fromAgentName, fromAgentSession, toAgentID, |
443 | XmlRpcResponse resp = new XmlRpcResponse(); | 132 | dialog, fromGroup, message, imSessionID, |
444 | Hashtable respdata = new Hashtable(); | 133 | offline != 0 ? true : false, Position, |
445 | if (successful) | 134 | binaryBucket); |
446 | respdata["success"] = "TRUE"; | ||
447 | else | ||
448 | respdata["success"] = "FALSE"; | ||
449 | resp.Value = respdata; | ||
450 | |||
451 | return resp; | ||
452 | } | ||
453 | |||
454 | #region Asynchronous setup | ||
455 | /// <summary> | ||
456 | /// delegate for sending a grid instant message asynchronously | ||
457 | /// </summary> | ||
458 | /// <param name="client"></param> | ||
459 | /// <param name="fromAgentID"></param> | ||
460 | /// <param name="fromAgentSession"></param> | ||
461 | /// <param name="toAgentID"></param> | ||
462 | /// <param name="imSessionID"></param> | ||
463 | /// <param name="timestamp"></param> | ||
464 | /// <param name="fromAgentName"></param> | ||
465 | /// <param name="message"></param> | ||
466 | /// <param name="dialog"></param> | ||
467 | /// <param name="fromGroup"></param> | ||
468 | /// <param name="offline"></param> | ||
469 | /// <param name="ParentEstateID"></param> | ||
470 | /// <param name="Position"></param> | ||
471 | /// <param name="RegionID"></param> | ||
472 | /// <param name="binaryBucket"></param> | ||
473 | /// <param name="regionhandle"></param> | ||
474 | /// <param name="prevRegionHandle"></param> | ||
475 | public delegate void GridInstantMessageDelegate(IClientAPI client, UUID fromAgentID, | ||
476 | UUID fromAgentSession, UUID toAgentID, | ||
477 | UUID imSessionID, uint timestamp, string fromAgentName, | ||
478 | string message, byte dialog, bool fromGroup, byte offline, | ||
479 | uint ParentEstateID, Vector3 Position, UUID RegionID, | ||
480 | byte[] binaryBucket, ulong regionhandle, ulong prevRegionHandle); | ||
481 | |||
482 | private void GridInstantMessageCompleted(IAsyncResult iar) | ||
483 | { | ||
484 | GridInstantMessageDelegate icon = (GridInstantMessageDelegate)iar.AsyncState; | ||
485 | icon.EndInvoke(iar); | ||
486 | } | ||
487 | |||
488 | |||
489 | protected virtual void SendGridInstantMessageViaXMLRPC(IClientAPI client, UUID fromAgentID, | ||
490 | UUID fromAgentSession, UUID toAgentID, | ||
491 | UUID imSessionID, uint timestamp, string fromAgentName, | ||
492 | string message, byte dialog, bool fromGroup, byte offline, | ||
493 | uint ParentEstateID, Vector3 Position, UUID RegionID, | ||
494 | byte[] binaryBucket, ulong regionhandle, ulong prevRegionHandle) | ||
495 | { | ||
496 | GridInstantMessageDelegate d = SendGridInstantMessageViaXMLRPCAsync; | ||
497 | |||
498 | d.BeginInvoke(client,fromAgentID, | ||
499 | fromAgentSession,toAgentID, | ||
500 | imSessionID,timestamp, fromAgentName, | ||
501 | message, dialog, fromGroup, offline, | ||
502 | ParentEstateID, Position, RegionID, | ||
503 | binaryBucket, regionhandle, prevRegionHandle, | ||
504 | GridInstantMessageCompleted, | ||
505 | d); | ||
506 | } | ||
507 | |||
508 | #endregion | ||
509 | |||
510 | |||
511 | /// <summary> | ||
512 | /// Recursive SendGridInstantMessage over XMLRPC method. The prevRegionHandle contains the last regionhandle tried | ||
513 | /// if it's the same as the user's looked up region handle, then we end the recursive loop | ||
514 | /// </summary> | ||
515 | /// <param name="prevRegionHandle"></param> | ||
516 | protected virtual void SendGridInstantMessageViaXMLRPCAsync(IClientAPI client, UUID fromAgentID, | ||
517 | UUID fromAgentSession, UUID toAgentID, | ||
518 | UUID imSessionID, uint timestamp, string fromAgentName, | ||
519 | string message, byte dialog, bool fromGroup, byte offline, | ||
520 | uint ParentEstateID, Vector3 Position, UUID RegionID, | ||
521 | byte[] binaryBucket, ulong regionhandle, ulong prevRegionHandle) | ||
522 | { | ||
523 | UserAgentData upd = null; | ||
524 | |||
525 | bool lookupAgent = false; | ||
526 | |||
527 | lock (m_userRegionMap) | ||
528 | { | ||
529 | if (m_userRegionMap.ContainsKey(toAgentID) && prevRegionHandle == 0) | ||
530 | { | ||
531 | upd = new UserAgentData(); | ||
532 | upd.AgentOnline = true; | ||
533 | upd.Handle = m_userRegionMap[toAgentID]; | ||
534 | |||
535 | } | ||
536 | else | ||
537 | { | ||
538 | lookupAgent = true; | ||
539 | |||
540 | 135 | ||
541 | } | 136 | if (m_TransferModule != null) |
542 | } | ||
543 | |||
544 | // Are we needing to look-up an agent? | ||
545 | if (lookupAgent) | ||
546 | { | 137 | { |
547 | // Non-cached user agent lookup. | 138 | m_TransferModule.SendInstantMessage(im, |
548 | upd = m_scenes[0].CommsManager.UserService.GetAgentByUUID(toAgentID); | 139 | delegate(bool success) |
549 | |||
550 | if (upd != null) | ||
551 | { | ||
552 | // check if we've tried this before.. This is one way to end the recursive loop | ||
553 | if (upd.Handle == prevRegionHandle) | ||
554 | { | 140 | { |
555 | m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); | 141 | if (dialog == (uint)InstantMessageDialog.StartTyping || |
556 | if (client != null) | 142 | dialog == (uint)InstantMessageDialog.StopTyping) |
557 | { | 143 | { |
558 | if (dialog != (byte)InstantMessageDialog.StartTyping && dialog != (byte)InstantMessageDialog.StopTyping && dialog != (byte)InstantMessageDialog.SessionDrop) | 144 | return; |
559 | client.SendInstantMessage(toAgentID, "Unable to send instant message", fromAgentID, "System", (byte)InstantMessageDialog.BusyAutoResponse, (uint)Util.UnixTimeSinceEpoch()); | ||
560 | } | 145 | } |
561 | return; | ||
562 | } | ||
563 | } | ||
564 | else | ||
565 | { | ||
566 | m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); | ||
567 | if (client != null) | ||
568 | { | ||
569 | if (dialog != (byte)InstantMessageDialog.StartTyping && dialog != (byte)InstantMessageDialog.StopTyping && dialog != (byte)InstantMessageDialog.SessionDrop) | ||
570 | client.SendInstantMessage(toAgentID, "Unable to send instant message", fromAgentID, "System", (byte)InstantMessageDialog.BusyAutoResponse, (uint)Util.UnixTimeSinceEpoch()); | ||
571 | } | ||
572 | return; | ||
573 | } | ||
574 | } | ||
575 | 146 | ||
576 | if (upd != null) | 147 | if ((client != null) && !success) |
577 | { | ||
578 | if (upd.AgentOnline) | ||
579 | { | ||
580 | RegionInfo reginfo = m_scenes[0].SceneGridService.RequestNeighbouringRegionInfo(upd.Handle); | ||
581 | if (reginfo != null) | ||
582 | { | ||
583 | GridInstantMessage msg = new GridInstantMessage(); | ||
584 | msg.fromAgentID = fromAgentID.Guid; | ||
585 | msg.fromAgentSession = fromAgentSession.Guid; | ||
586 | msg.toAgentID = toAgentID.Guid; | ||
587 | msg.imSessionID = imSessionID.Guid; | ||
588 | msg.timestamp = timestamp; | ||
589 | msg.fromAgentName = fromAgentName; | ||
590 | msg.message = message; | ||
591 | msg.dialog = dialog; | ||
592 | msg.fromGroup = fromGroup; | ||
593 | msg.offline = offline; | ||
594 | msg.ParentEstateID = ParentEstateID; | ||
595 | msg.Position = Position; | ||
596 | msg.RegionID = RegionID.Guid; | ||
597 | msg.binaryBucket = binaryBucket; | ||
598 | |||
599 | Hashtable msgdata = ConvertGridInstantMessageToXMLRPC(msg); | ||
600 | msgdata["region_handle"] = getLocalRegionHandleFromUUID(RegionID); | ||
601 | bool imresult = doIMSending(reginfo, msgdata); | ||
602 | if (imresult) | ||
603 | { | 148 | { |
604 | // IM delivery successful, so store the Agent's location in our local cache. | 149 | client.SendInstantMessage(toAgentID, |
605 | lock (m_userRegionMap) | 150 | "Unable to send instant message. "+ |
606 | { | 151 | "User is not logged in.", |
607 | if (m_userRegionMap.ContainsKey(toAgentID)) | 152 | fromAgentID, "System", |
608 | { | 153 | (byte)InstantMessageDialog.BusyAutoResponse, |
609 | m_userRegionMap[toAgentID] = upd.Handle; | 154 | (uint)Util.UnixTimeSinceEpoch()); |
610 | } | ||
611 | else | ||
612 | { | ||
613 | m_userRegionMap.Add(toAgentID, upd.Handle); | ||
614 | } | ||
615 | } | ||
616 | //m_log.Info("[GRID INSTANT MESSAGE]: Successfully sent a message"); | ||
617 | } | ||
618 | else | ||
619 | { | ||
620 | // try again, but lookup user this time. | ||
621 | // Warning, this must call the Async version | ||
622 | // of this method or we'll be making thousands of threads | ||
623 | // The version within the spawned thread is SendGridInstantMessageViaXMLRPCAsync | ||
624 | // The version that spawns the thread is SendGridInstantMessageViaXMLRPC | ||
625 | |||
626 | // This is recursive!!!!! | ||
627 | SendGridInstantMessageViaXMLRPCAsync(client, fromAgentID, | ||
628 | fromAgentSession, toAgentID, | ||
629 | imSessionID, timestamp, fromAgentName, | ||
630 | message, dialog, fromGroup, offline, | ||
631 | ParentEstateID, Position, RegionID, | ||
632 | binaryBucket, regionhandle, upd.Handle); | ||
633 | } | 155 | } |
634 | |||
635 | } | ||
636 | } | ||
637 | else | ||
638 | { | ||
639 | // send Agent Offline message | ||
640 | if (client != null) | ||
641 | { | ||
642 | if (dialog != (byte)InstantMessageDialog.StartTyping && dialog != (byte)InstantMessageDialog.StopTyping && dialog != (byte)InstantMessageDialog.SessionDrop) | ||
643 | client.SendInstantMessage(toAgentID, "Unable to send instant message: Agent Offline", fromAgentID, "System", (byte)InstantMessageDialog.BusyAutoResponse, (uint)Util.UnixTimeSinceEpoch());// SendAlertMessage("Unable to send instant message"); | ||
644 | } | ||
645 | } | ||
646 | } | ||
647 | else | ||
648 | { | ||
649 | // send Agent doesn't exist message | ||
650 | if (client != null) | ||
651 | client.SendInstantMessage(toAgentID, "Unable to send instant message: Are you sure this agent exists anymore?", fromAgentID, "System", (byte)InstantMessageDialog.MessageFromObject, (uint)Util.UnixTimeSinceEpoch());// SendAlertMessage("Unable to send instant message"); | ||
652 | } | ||
653 | |||
654 | } | ||
655 | |||
656 | /// <summary> | ||
657 | /// This actually does the XMLRPC Request | ||
658 | /// </summary> | ||
659 | /// <param name="reginfo">RegionInfo we pull the data out of to send the request to</param> | ||
660 | /// <param name="xmlrpcdata">The Instant Message data Hashtable</param> | ||
661 | /// <returns>Bool if the message was successfully delivered at the other side.</returns> | ||
662 | private bool doIMSending(RegionInfo reginfo, Hashtable xmlrpcdata) | ||
663 | { | ||
664 | |||
665 | ArrayList SendParams = new ArrayList(); | ||
666 | SendParams.Add(xmlrpcdata); | ||
667 | XmlRpcRequest GridReq = new XmlRpcRequest("grid_instant_message", SendParams); | ||
668 | try | ||
669 | { | ||
670 | |||
671 | XmlRpcResponse GridResp = GridReq.Send("http://" + reginfo.ExternalHostName + ":" + reginfo.HttpPort, 3000); | ||
672 | |||
673 | Hashtable responseData = (Hashtable)GridResp.Value; | ||
674 | |||
675 | if (responseData.ContainsKey("success")) | ||
676 | { | ||
677 | if ((string)responseData["success"] == "TRUE") | ||
678 | { | ||
679 | return true; | ||
680 | } | ||
681 | else | ||
682 | { | ||
683 | return false; | ||
684 | } | ||
685 | } | ||
686 | else | ||
687 | { | ||
688 | return false; | ||
689 | } | ||
690 | } | ||
691 | catch (WebException e) | ||
692 | { | ||
693 | m_log.ErrorFormat("[GRID INSTANT MESSAGE]: Error sending message to http://{0}:{1} the host didn't respond ({2})", | ||
694 | reginfo.ExternalHostName, reginfo.HttpPort, e.Message); | ||
695 | } | ||
696 | |||
697 | return false; | ||
698 | } | ||
699 | |||
700 | /// <summary> | ||
701 | /// Get ulong region handle for region by it's Region UUID. | ||
702 | /// We use region handles over grid comms because there's all sorts of free and cool caching. | ||
703 | /// </summary> | ||
704 | /// <param name="regionID">UUID of region to get the region handle for</param> | ||
705 | /// <returns></returns> | ||
706 | private ulong getLocalRegionHandleFromUUID(UUID regionID) | ||
707 | { | ||
708 | ulong returnhandle = 0; | ||
709 | |||
710 | lock (m_scenes) | ||
711 | { | ||
712 | foreach (Scene sn in m_scenes) | ||
713 | { | ||
714 | if (sn.RegionInfo.RegionID == regionID) | ||
715 | { | ||
716 | returnhandle = sn.RegionInfo.RegionHandle; | ||
717 | break; | ||
718 | } | 156 | } |
719 | } | 157 | ); |
720 | } | 158 | } |
721 | return returnhandle; | ||
722 | } | 159 | } |
723 | 160 | ||
724 | /// <summary> | 161 | /// <summary> |
725 | /// Takes a GridInstantMessage and converts it into a Hashtable for XMLRPC | 162 | /// |
726 | /// </summary> | 163 | /// </summary> |
727 | /// <param name="msg">The GridInstantMessage object</param> | 164 | /// <param name="msg"></param> |
728 | /// <returns>Hashtable containing the XMLRPC request</returns> | 165 | private void OnGridInstantMessage(GridInstantMessage msg) |
729 | private Hashtable ConvertGridInstantMessageToXMLRPC(GridInstantMessage msg) | 166 | { |
730 | { | 167 | // Just call the Text IM handler above |
731 | Hashtable gim = new Hashtable(); | 168 | // This event won't be raised unless we have that agent, |
732 | gim["from_agent_id"] = msg.fromAgentID.ToString(); | 169 | // so we can depend on the above not trying to send |
733 | gim["from_agent_session"] = msg.fromAgentSession.ToString(); | 170 | // via grid again |
734 | gim["to_agent_id"] = msg.toAgentID.ToString(); | 171 | // |
735 | gim["im_session_id"] = msg.imSessionID.ToString(); | 172 | OnInstantMessage(null, new UUID(msg.fromAgentID), |
736 | gim["timestamp"] = msg.timestamp.ToString(); | 173 | new UUID(msg.fromAgentSession), |
737 | gim["from_agent_name"] = msg.fromAgentName; | 174 | new UUID(msg.toAgentID), new UUID(msg.imSessionID), |
738 | gim["message"] = msg.message; | 175 | msg.timestamp, msg.fromAgentName, msg.message, |
739 | byte[] dialogdata = new byte[1];dialogdata[0] = msg.dialog; | 176 | msg.dialog, msg.fromGroup, msg.offline, |
740 | gim["dialog"] = Convert.ToBase64String(dialogdata,Base64FormattingOptions.None); | 177 | msg.ParentEstateID, msg.Position, |
741 | 178 | new UUID(msg.RegionID), msg.binaryBucket); | |
742 | if (msg.fromGroup) | ||
743 | gim["from_group"] = "TRUE"; | ||
744 | else | ||
745 | gim["from_group"] = "FALSE"; | ||
746 | byte[] offlinedata = new byte[1]; offlinedata[0] = msg.offline; | ||
747 | gim["offline"] = Convert.ToBase64String(offlinedata, Base64FormattingOptions.None); | ||
748 | gim["parent_estate_id"] = msg.ParentEstateID.ToString(); | ||
749 | gim["position_x"] = msg.Position.X.ToString(); | ||
750 | gim["position_y"] = msg.Position.Y.ToString(); | ||
751 | gim["position_z"] = msg.Position.Z.ToString(); | ||
752 | gim["region_id"] = msg.RegionID.ToString(); | ||
753 | gim["binary_bucket"] = Convert.ToBase64String(msg.binaryBucket,Base64FormattingOptions.None); | ||
754 | return gim; | ||
755 | } | 179 | } |
756 | |||
757 | } | 180 | } |
758 | } | 181 | } |
diff --git a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/MessageTransferModule.cs b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/MessageTransferModule.cs new file mode 100644 index 0000000..d1543a0 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/MessageTransferModule.cs | |||
@@ -0,0 +1,626 @@ | |||
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 OpenSim 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 | using System; | ||
28 | using System.Collections; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Reflection; | ||
31 | using System.Net; | ||
32 | using System.Threading; | ||
33 | using OpenMetaverse; | ||
34 | using log4net; | ||
35 | using Nini.Config; | ||
36 | using Nwc.XmlRpc; | ||
37 | using OpenSim.Framework; | ||
38 | using OpenSim.Framework.Client; | ||
39 | using OpenSim.Region.Interfaces; | ||
40 | using OpenSim.Region.Environment.Interfaces; | ||
41 | using OpenSim.Region.Environment.Scenes; | ||
42 | |||
43 | namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage | ||
44 | { | ||
45 | public class MessageTransferModule : IRegionModule, IMessageTransferModule | ||
46 | { | ||
47 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
48 | |||
49 | private bool m_Enabled = false; | ||
50 | private bool m_Gridmode = false; | ||
51 | private List<Scene> m_Scenes = new List<Scene>(); | ||
52 | private Dictionary<UUID, ulong> m_UserRegionMap = new Dictionary<UUID, ulong>(); | ||
53 | |||
54 | public void Initialise(Scene scene, IConfigSource config) | ||
55 | { | ||
56 | if (config.Configs["Messaging"] != null) | ||
57 | { | ||
58 | IConfig cnf = config.Configs["Messaging"]; | ||
59 | if (cnf == null || cnf.GetString( | ||
60 | "MessageTransferModule", "MessageTransferModule") != | ||
61 | "MessageTransferModule") | ||
62 | return; | ||
63 | |||
64 | cnf = config.Configs["Startup"]; | ||
65 | if (cnf != null) | ||
66 | m_Gridmode = cnf.GetBoolean("m_Gridmode", false); | ||
67 | |||
68 | m_Enabled = true; | ||
69 | } | ||
70 | |||
71 | lock (m_Scenes) | ||
72 | { | ||
73 | if (m_Scenes.Count == 0) | ||
74 | { | ||
75 | scene.AddXmlRPCHandler("grid_instant_message", processXMLRPCGridInstantMessage); | ||
76 | scene.RegisterModuleInterface<IMessageTransferModule>(this); | ||
77 | } | ||
78 | |||
79 | m_Scenes.Add(scene); | ||
80 | } | ||
81 | } | ||
82 | |||
83 | public void PostInitialise() | ||
84 | { | ||
85 | } | ||
86 | |||
87 | public void Close() | ||
88 | { | ||
89 | } | ||
90 | |||
91 | public string Name | ||
92 | { | ||
93 | get { return "MessageTransferModule"; } | ||
94 | } | ||
95 | |||
96 | public bool IsSharedModule | ||
97 | { | ||
98 | get { return true; } | ||
99 | } | ||
100 | |||
101 | public void SendInstantMessage(GridInstantMessage im, MessageResultNotification result) | ||
102 | { | ||
103 | UUID toAgentID = new UUID(im.toAgentID); | ||
104 | |||
105 | m_log.DebugFormat("[INSTANT MESSAGE]: Attempting delivery of IM fromn {0} to {1}", im.fromAgentName, toAgentID.ToString()); | ||
106 | |||
107 | // Try root avatar only first | ||
108 | foreach (Scene scene in m_Scenes) | ||
109 | { | ||
110 | if (scene.Entities.ContainsKey(toAgentID) && | ||
111 | scene.Entities[toAgentID] is ScenePresence) | ||
112 | { | ||
113 | m_log.DebugFormat("[INSTANT MESSAGE]: Looking for {0} in {1}", toAgentID.ToString(), scene.RegionInfo.RegionName); | ||
114 | // Local message | ||
115 | ScenePresence user = (ScenePresence) scene.Entities[toAgentID]; | ||
116 | if (!user.IsChildAgent) | ||
117 | { | ||
118 | m_log.DebugFormat("[INSTANT MESSAGE]: Delivering to client"); | ||
119 | user.ControllingClient.SendInstantMessage( | ||
120 | new UUID(im.fromAgentID), | ||
121 | im.message, | ||
122 | new UUID(im.toAgentID), | ||
123 | im.fromAgentName, | ||
124 | im.dialog, | ||
125 | im.timestamp, | ||
126 | new UUID(im.imSessionID), | ||
127 | im.fromGroup, | ||
128 | im.binaryBucket); | ||
129 | // Message sent | ||
130 | result(true); | ||
131 | return; | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | |||
136 | // try child avatar second | ||
137 | foreach (Scene scene in m_Scenes) | ||
138 | { | ||
139 | m_log.DebugFormat("[INSTANT MESSAGE]: Looking for child of {0} in {1}", toAgentID.ToString(), scene.RegionInfo.RegionName); | ||
140 | |||
141 | if (scene.Entities.ContainsKey(toAgentID) && | ||
142 | scene.Entities[toAgentID] is ScenePresence) | ||
143 | { | ||
144 | // Local message | ||
145 | ScenePresence user = (ScenePresence) scene.Entities[toAgentID]; | ||
146 | |||
147 | m_log.DebugFormat("[INSTANT MESSAGE]: Delivering to client"); | ||
148 | user.ControllingClient.SendInstantMessage( | ||
149 | new UUID(im.fromAgentID), | ||
150 | im.message, | ||
151 | new UUID(im.toAgentID), | ||
152 | im.fromAgentName, | ||
153 | im.dialog, | ||
154 | im.timestamp, | ||
155 | new UUID(im.imSessionID), | ||
156 | im.fromGroup, | ||
157 | im.binaryBucket); | ||
158 | // Message sent | ||
159 | result(true); | ||
160 | return; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | if (m_Gridmode) | ||
165 | { | ||
166 | m_log.DebugFormat("[INSTANT MESSAGE]: Delivering via grid"); | ||
167 | // Still here, try send via Grid | ||
168 | SendGridInstantMessageViaXMLRPC(im, result); | ||
169 | return; | ||
170 | } | ||
171 | |||
172 | m_log.DebugFormat("[INSTANT MESSAGE]: Undeliverable"); | ||
173 | result(false); | ||
174 | return; | ||
175 | } | ||
176 | |||
177 | /// <summary> | ||
178 | /// Process a XMLRPC Grid Instant Message | ||
179 | /// </summary> | ||
180 | /// <param name="request">XMLRPC parameters | ||
181 | /// </param> | ||
182 | /// <returns>Nothing much</returns> | ||
183 | protected virtual XmlRpcResponse processXMLRPCGridInstantMessage(XmlRpcRequest request) | ||
184 | { | ||
185 | bool successful = false; | ||
186 | // various rational defaults | ||
187 | UUID fromAgentID = UUID.Zero; | ||
188 | UUID fromAgentSession = UUID.Zero; | ||
189 | UUID toAgentID = UUID.Zero; | ||
190 | UUID imSessionID = UUID.Zero; | ||
191 | uint timestamp = 0; | ||
192 | string fromAgentName = ""; | ||
193 | string message = ""; | ||
194 | byte dialog = (byte)0; | ||
195 | bool fromGroup = false; | ||
196 | byte offline = (byte)0; | ||
197 | uint ParentEstateID=0; | ||
198 | Vector3 Position = Vector3.Zero; | ||
199 | UUID RegionID = UUID.Zero ; | ||
200 | byte[] binaryBucket = new byte[0]; | ||
201 | |||
202 | float pos_x = 0; | ||
203 | float pos_y = 0; | ||
204 | float pos_z = 0; | ||
205 | //m_log.Info("Processing IM"); | ||
206 | |||
207 | |||
208 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
209 | // Check if it's got all the data | ||
210 | if (requestData.ContainsKey("from_agent_id") && requestData.ContainsKey("from_agent_session") | ||
211 | && requestData.ContainsKey("to_agent_id") && requestData.ContainsKey("im_session_id") | ||
212 | && requestData.ContainsKey("timestamp") && requestData.ContainsKey("from_agent_name") | ||
213 | && requestData.ContainsKey("message") && requestData.ContainsKey("dialog") | ||
214 | && requestData.ContainsKey("from_group") | ||
215 | && requestData.ContainsKey("offline") && requestData.ContainsKey("parent_estate_id") | ||
216 | && requestData.ContainsKey("position_x") && requestData.ContainsKey("position_y") | ||
217 | && requestData.ContainsKey("position_z") && requestData.ContainsKey("region_id") | ||
218 | && requestData.ContainsKey("binary_bucket")) | ||
219 | { | ||
220 | // Do the easy way of validating the UUIDs | ||
221 | UUID.TryParse((string)requestData["from_agent_id"], out fromAgentID); | ||
222 | UUID.TryParse((string)requestData["from_agent_session"], out fromAgentSession); | ||
223 | UUID.TryParse((string)requestData["to_agent_id"], out toAgentID); | ||
224 | UUID.TryParse((string)requestData["im_session_id"], out imSessionID); | ||
225 | UUID.TryParse((string)requestData["region_id"], out RegionID); | ||
226 | |||
227 | try | ||
228 | { | ||
229 | timestamp = (uint)Convert.ToInt32((string)requestData["timestamp"]); | ||
230 | } | ||
231 | catch (ArgumentException) | ||
232 | { | ||
233 | } | ||
234 | catch (FormatException) | ||
235 | { | ||
236 | } | ||
237 | catch (OverflowException) | ||
238 | { | ||
239 | } | ||
240 | |||
241 | fromAgentName = (string)requestData["from_agent_name"]; | ||
242 | message = (string)requestData["message"]; | ||
243 | |||
244 | // Bytes don't transfer well over XMLRPC, so, we Base64 Encode them. | ||
245 | string requestData1 = (string)requestData["dialog"]; | ||
246 | if (string.IsNullOrEmpty(requestData1)) | ||
247 | { | ||
248 | dialog = 0; | ||
249 | } | ||
250 | else | ||
251 | { | ||
252 | byte[] dialogdata = Convert.FromBase64String(requestData1); | ||
253 | dialog = dialogdata[0]; | ||
254 | } | ||
255 | |||
256 | if ((string)requestData["from_group"] == "TRUE") | ||
257 | fromGroup = true; | ||
258 | |||
259 | string requestData2 = (string)requestData["offline"]; | ||
260 | if (String.IsNullOrEmpty(requestData2)) | ||
261 | { | ||
262 | offline = 0; | ||
263 | } | ||
264 | else | ||
265 | { | ||
266 | byte[] offlinedata = Convert.FromBase64String(requestData2); | ||
267 | offline = offlinedata[0]; | ||
268 | } | ||
269 | |||
270 | try | ||
271 | { | ||
272 | ParentEstateID = (uint)Convert.ToInt32((string)requestData["parent_estate_id"]); | ||
273 | } | ||
274 | catch (ArgumentException) | ||
275 | { | ||
276 | } | ||
277 | catch (FormatException) | ||
278 | { | ||
279 | } | ||
280 | catch (OverflowException) | ||
281 | { | ||
282 | } | ||
283 | |||
284 | try | ||
285 | { | ||
286 | pos_x = (uint)Convert.ToInt32((string)requestData["position_x"]); | ||
287 | } | ||
288 | catch (ArgumentException) | ||
289 | { | ||
290 | } | ||
291 | catch (FormatException) | ||
292 | { | ||
293 | } | ||
294 | catch (OverflowException) | ||
295 | { | ||
296 | } | ||
297 | try | ||
298 | { | ||
299 | pos_y = (uint)Convert.ToInt32((string)requestData["position_y"]); | ||
300 | } | ||
301 | catch (ArgumentException) | ||
302 | { | ||
303 | } | ||
304 | catch (FormatException) | ||
305 | { | ||
306 | } | ||
307 | catch (OverflowException) | ||
308 | { | ||
309 | } | ||
310 | try | ||
311 | { | ||
312 | pos_z = (uint)Convert.ToInt32((string)requestData["position_z"]); | ||
313 | } | ||
314 | catch (ArgumentException) | ||
315 | { | ||
316 | } | ||
317 | catch (FormatException) | ||
318 | { | ||
319 | } | ||
320 | catch (OverflowException) | ||
321 | { | ||
322 | } | ||
323 | |||
324 | Position = new Vector3(pos_x, pos_y, pos_z); | ||
325 | |||
326 | string requestData3 = (string)requestData["binary_bucket"]; | ||
327 | if (string.IsNullOrEmpty(requestData3)) | ||
328 | { | ||
329 | binaryBucket = new byte[0]; | ||
330 | } | ||
331 | else | ||
332 | { | ||
333 | binaryBucket = Convert.FromBase64String(requestData3); | ||
334 | } | ||
335 | |||
336 | // Create a New GridInstantMessageObject the the data | ||
337 | GridInstantMessage gim = new GridInstantMessage(); | ||
338 | gim.fromAgentID = fromAgentID.Guid; | ||
339 | gim.fromAgentName = fromAgentName; | ||
340 | gim.fromAgentSession = fromAgentSession.Guid; | ||
341 | gim.fromGroup = fromGroup; | ||
342 | gim.imSessionID = imSessionID.Guid; | ||
343 | gim.RegionID = RegionID.Guid; | ||
344 | gim.timestamp = timestamp; | ||
345 | gim.toAgentID = toAgentID.Guid; | ||
346 | gim.message = message; | ||
347 | gim.dialog = dialog; | ||
348 | gim.offline = offline; | ||
349 | gim.ParentEstateID = ParentEstateID; | ||
350 | gim.Position = Position; | ||
351 | gim.binaryBucket = binaryBucket; | ||
352 | |||
353 | |||
354 | // Trigger the Instant message in the scene. | ||
355 | foreach (Scene scene in m_Scenes) | ||
356 | { | ||
357 | if (scene.Entities.ContainsKey(toAgentID) && | ||
358 | scene.Entities[toAgentID] is ScenePresence) | ||
359 | { | ||
360 | ScenePresence user = | ||
361 | (ScenePresence)scene.Entities[toAgentID]; | ||
362 | |||
363 | if (!user.IsChildAgent) | ||
364 | { | ||
365 | scene.EventManager.TriggerIncomingInstantMessage(gim); | ||
366 | successful = true; | ||
367 | } | ||
368 | } | ||
369 | } | ||
370 | if (!successful) | ||
371 | { | ||
372 | // If the message can't be delivered to an agent, it | ||
373 | // is likely to be a group IM. On a group IM, the | ||
374 | // imSessionID = toAgentID = group id. Raise the | ||
375 | // unhandled IM event to give the groups module | ||
376 | // a chance to pick it up. We raise that in a random | ||
377 | // scene, since the groups module is shared. | ||
378 | // | ||
379 | m_Scenes[0].EventManager.TriggerUnhandledInstantMessage(gim); | ||
380 | } | ||
381 | } | ||
382 | |||
383 | //Send response back to region calling if it was successful | ||
384 | // calling region uses this to know when to look up a user's location again. | ||
385 | XmlRpcResponse resp = new XmlRpcResponse(); | ||
386 | Hashtable respdata = new Hashtable(); | ||
387 | if (successful) | ||
388 | respdata["success"] = "TRUE"; | ||
389 | else | ||
390 | respdata["success"] = "FALSE"; | ||
391 | resp.Value = respdata; | ||
392 | |||
393 | return resp; | ||
394 | } | ||
395 | |||
396 | /// <summary> | ||
397 | /// delegate for sending a grid instant message asynchronously | ||
398 | /// </summary> | ||
399 | public delegate void GridInstantMessageDelegate(GridInstantMessage im, MessageResultNotification result, ulong prevRegionHandle); | ||
400 | |||
401 | private void GridInstantMessageCompleted(IAsyncResult iar) | ||
402 | { | ||
403 | GridInstantMessageDelegate icon = | ||
404 | (GridInstantMessageDelegate)iar.AsyncState; | ||
405 | icon.EndInvoke(iar); | ||
406 | } | ||
407 | |||
408 | |||
409 | protected virtual void SendGridInstantMessageViaXMLRPC(GridInstantMessage im, MessageResultNotification result) | ||
410 | { | ||
411 | GridInstantMessageDelegate d = SendGridInstantMessageViaXMLRPCAsync; | ||
412 | |||
413 | d.BeginInvoke(im, result, 0, GridInstantMessageCompleted, d); | ||
414 | } | ||
415 | |||
416 | /// <summary> | ||
417 | /// Recursive SendGridInstantMessage over XMLRPC method. | ||
418 | /// </summary> | ||
419 | /// <param name="prevRegionHandle"></param> | ||
420 | protected virtual void SendGridInstantMessageViaXMLRPCAsync(GridInstantMessage im, MessageResultNotification result, ulong prevRegionHandle) | ||
421 | { | ||
422 | UUID toAgentID = new UUID(im.toAgentID); | ||
423 | |||
424 | UserAgentData upd = null; | ||
425 | |||
426 | bool lookupAgent = false; | ||
427 | |||
428 | lock (m_UserRegionMap) | ||
429 | { | ||
430 | if (m_UserRegionMap.ContainsKey(toAgentID)) | ||
431 | { | ||
432 | upd = new UserAgentData(); | ||
433 | upd.AgentOnline = true; | ||
434 | upd.Handle = m_UserRegionMap[toAgentID]; | ||
435 | } | ||
436 | else | ||
437 | { | ||
438 | lookupAgent = true; | ||
439 | } | ||
440 | } | ||
441 | |||
442 | // Are we needing to look-up an agent? | ||
443 | if (lookupAgent) | ||
444 | { | ||
445 | // Non-cached user agent lookup. | ||
446 | upd = m_Scenes[0].CommsManager.UserService.GetAgentByUUID(toAgentID); | ||
447 | |||
448 | if (upd != null) | ||
449 | { | ||
450 | // check if we've tried this before.. | ||
451 | // This is one way to end the recursive loop | ||
452 | // | ||
453 | if (upd.Handle == prevRegionHandle) | ||
454 | { | ||
455 | m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); | ||
456 | result(false); | ||
457 | return; | ||
458 | } | ||
459 | } | ||
460 | else | ||
461 | { | ||
462 | m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); | ||
463 | result(false); | ||
464 | return; | ||
465 | } | ||
466 | } | ||
467 | |||
468 | if (upd != null) | ||
469 | { | ||
470 | if (upd.AgentOnline) | ||
471 | { | ||
472 | RegionInfo reginfo = m_Scenes[0].SceneGridService.RequestNeighbouringRegionInfo(upd.Handle); | ||
473 | if (reginfo != null) | ||
474 | { | ||
475 | Hashtable msgdata = ConvertGridInstantMessageToXMLRPC(im); | ||
476 | // Not actually used anymore, left in for compatibility | ||
477 | // Remove at next interface change | ||
478 | // | ||
479 | msgdata["region_handle"] = 0; | ||
480 | bool imresult = doIMSending(reginfo, msgdata); | ||
481 | if (imresult) | ||
482 | { | ||
483 | // IM delivery successful, so store the Agent's location in our local cache. | ||
484 | lock (m_UserRegionMap) | ||
485 | { | ||
486 | if (m_UserRegionMap.ContainsKey(toAgentID)) | ||
487 | { | ||
488 | m_UserRegionMap[toAgentID] = upd.Handle; | ||
489 | } | ||
490 | else | ||
491 | { | ||
492 | m_UserRegionMap.Add(toAgentID, upd.Handle); | ||
493 | } | ||
494 | } | ||
495 | result(true); | ||
496 | } | ||
497 | else | ||
498 | { | ||
499 | // try again, but lookup user this time. | ||
500 | // Warning, this must call the Async version | ||
501 | // of this method or we'll be making thousands of threads | ||
502 | // The version within the spawned thread is SendGridInstantMessageViaXMLRPCAsync | ||
503 | // The version that spawns the thread is SendGridInstantMessageViaXMLRPC | ||
504 | |||
505 | // This is recursive!!!!! | ||
506 | SendGridInstantMessageViaXMLRPCAsync(im, result, | ||
507 | upd.Handle); | ||
508 | } | ||
509 | |||
510 | } | ||
511 | } | ||
512 | else | ||
513 | { | ||
514 | result(false); | ||
515 | } | ||
516 | } | ||
517 | else | ||
518 | { | ||
519 | result(false); | ||
520 | } | ||
521 | |||
522 | } | ||
523 | |||
524 | /// <summary> | ||
525 | /// This actually does the XMLRPC Request | ||
526 | /// </summary> | ||
527 | /// <param name="reginfo">RegionInfo we pull the data out of to send the request to</param> | ||
528 | /// <param name="xmlrpcdata">The Instant Message data Hashtable</param> | ||
529 | /// <returns>Bool if the message was successfully delivered at the other side.</returns> | ||
530 | private bool doIMSending(RegionInfo reginfo, Hashtable xmlrpcdata) | ||
531 | { | ||
532 | |||
533 | ArrayList SendParams = new ArrayList(); | ||
534 | SendParams.Add(xmlrpcdata); | ||
535 | XmlRpcRequest GridReq = new XmlRpcRequest("grid_instant_message", SendParams); | ||
536 | try | ||
537 | { | ||
538 | |||
539 | XmlRpcResponse GridResp = GridReq.Send("http://" + reginfo.ExternalHostName + ":" + reginfo.HttpPort, 3000); | ||
540 | |||
541 | Hashtable responseData = (Hashtable)GridResp.Value; | ||
542 | |||
543 | if (responseData.ContainsKey("success")) | ||
544 | { | ||
545 | if ((string)responseData["success"] == "TRUE") | ||
546 | { | ||
547 | return true; | ||
548 | } | ||
549 | else | ||
550 | { | ||
551 | return false; | ||
552 | } | ||
553 | } | ||
554 | else | ||
555 | { | ||
556 | return false; | ||
557 | } | ||
558 | } | ||
559 | catch (WebException e) | ||
560 | { | ||
561 | m_log.ErrorFormat("[GRID INSTANT MESSAGE]: Error sending message to http://{0}:{1} the host didn't respond ({2})", | ||
562 | reginfo.ExternalHostName, reginfo.HttpPort, e.Message); | ||
563 | } | ||
564 | |||
565 | return false; | ||
566 | } | ||
567 | |||
568 | /// <summary> | ||
569 | /// Get ulong region handle for region by it's Region UUID. | ||
570 | /// We use region handles over grid comms because there's all sorts of free and cool caching. | ||
571 | /// </summary> | ||
572 | /// <param name="regionID">UUID of region to get the region handle for</param> | ||
573 | /// <returns></returns> | ||
574 | private ulong getLocalRegionHandleFromUUID(UUID regionID) | ||
575 | { | ||
576 | ulong returnhandle = 0; | ||
577 | |||
578 | lock (m_Scenes) | ||
579 | { | ||
580 | foreach (Scene sn in m_Scenes) | ||
581 | { | ||
582 | if (sn.RegionInfo.RegionID == regionID) | ||
583 | { | ||
584 | returnhandle = sn.RegionInfo.RegionHandle; | ||
585 | break; | ||
586 | } | ||
587 | } | ||
588 | } | ||
589 | return returnhandle; | ||
590 | } | ||
591 | |||
592 | /// <summary> | ||
593 | /// Takes a GridInstantMessage and converts it into a Hashtable for XMLRPC | ||
594 | /// </summary> | ||
595 | /// <param name="msg">The GridInstantMessage object</param> | ||
596 | /// <returns>Hashtable containing the XMLRPC request</returns> | ||
597 | private Hashtable ConvertGridInstantMessageToXMLRPC(GridInstantMessage msg) | ||
598 | { | ||
599 | Hashtable gim = new Hashtable(); | ||
600 | gim["from_agent_id"] = msg.fromAgentID.ToString(); | ||
601 | gim["from_agent_session"] = msg.fromAgentSession.ToString(); | ||
602 | gim["to_agent_id"] = msg.toAgentID.ToString(); | ||
603 | gim["im_session_id"] = msg.imSessionID.ToString(); | ||
604 | gim["timestamp"] = msg.timestamp.ToString(); | ||
605 | gim["from_agent_name"] = msg.fromAgentName; | ||
606 | gim["message"] = msg.message; | ||
607 | byte[] dialogdata = new byte[1];dialogdata[0] = msg.dialog; | ||
608 | gim["dialog"] = Convert.ToBase64String(dialogdata,Base64FormattingOptions.None); | ||
609 | |||
610 | if (msg.fromGroup) | ||
611 | gim["from_group"] = "TRUE"; | ||
612 | else | ||
613 | gim["from_group"] = "FALSE"; | ||
614 | byte[] offlinedata = new byte[1]; offlinedata[0] = msg.offline; | ||
615 | gim["offline"] = Convert.ToBase64String(offlinedata, Base64FormattingOptions.None); | ||
616 | gim["parent_estate_id"] = msg.ParentEstateID.ToString(); | ||
617 | gim["position_x"] = msg.Position.X.ToString(); | ||
618 | gim["position_y"] = msg.Position.Y.ToString(); | ||
619 | gim["position_z"] = msg.Position.Z.ToString(); | ||
620 | gim["region_id"] = msg.RegionID.ToString(); | ||
621 | gim["binary_bucket"] = Convert.ToBase64String(msg.binaryBucket,Base64FormattingOptions.None); | ||
622 | return gim; | ||
623 | } | ||
624 | |||
625 | } | ||
626 | } | ||