aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules/Avatar/InstantMessage
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/CoreModules/Avatar/InstantMessage')
-rw-r--r--OpenSim/Region/CoreModules/Avatar/InstantMessage/InstantMessageModule.cs170
-rw-r--r--OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs655
-rw-r--r--OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs426
3 files changed, 1251 insertions, 0 deletions
diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/InstantMessageModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/InstantMessageModule.cs
new file mode 100644
index 0000000..3ad2c91
--- /dev/null
+++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/InstantMessageModule.cs
@@ -0,0 +1,170 @@
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 */
27using System;
28using System.Collections;
29using System.Collections.Generic;
30using System.Reflection;
31using System.Net;
32using System.Threading;
33using OpenMetaverse;
34using log4net;
35using Nini.Config;
36using Nwc.XmlRpc;
37using OpenSim.Framework;
38using OpenSim.Framework.Client;
39using OpenSim.Region.Framework.Interfaces;
40using OpenSim.Region.Framework.Scenes;
41
42namespace OpenSim.Region.CoreModules.Avatar.InstantMessage
43{
44 public class InstantMessageModule : IRegionModule
45 {
46 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
47
48 /// <value>
49 /// Is this module enabled?
50 /// </value>
51 private bool m_enabled = false;
52
53 private readonly List<Scene> m_scenes = new List<Scene>();
54
55 #region IRegionModule Members
56
57 private IMessageTransferModule m_TransferModule = null;
58
59 public void Initialise(Scene scene, IConfigSource config)
60 {
61 if (config.Configs["Messaging"] != null)
62 {
63 if (config.Configs["Messaging"].GetString(
64 "InstantMessageModule", "InstantMessageModule") !=
65 "InstantMessageModule")
66 return;
67 }
68
69 m_enabled = true;
70
71 lock (m_scenes)
72 {
73 if (!m_scenes.Contains(scene))
74 {
75 m_scenes.Add(scene);
76 scene.EventManager.OnClientConnect += OnClientConnect;
77 scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
78 }
79 }
80 }
81
82 void OnClientConnect(IClientCore client)
83 {
84 IClientIM clientIM;
85 if (client.TryGet(out clientIM))
86 {
87 clientIM.OnInstantMessage += OnInstantMessage;
88 }
89 }
90
91 public void PostInitialise()
92 {
93 if (!m_enabled)
94 return;
95
96 m_TransferModule =
97 m_scenes[0].RequestModuleInterface<IMessageTransferModule>();
98
99 if (m_TransferModule == null)
100 m_log.Error("[INSTANT MESSAGE]: No message transfer module, "+
101 "IM will not work!");
102 }
103
104 public void Close()
105 {
106 }
107
108 public string Name
109 {
110 get { return "InstantMessageModule"; }
111 }
112
113 public bool IsSharedModule
114 {
115 get { return true; }
116 }
117
118 #endregion
119
120 public void OnInstantMessage(IClientAPI client, GridInstantMessage im)
121 {
122 byte dialog = im.dialog;
123
124 if ( dialog != (byte)InstantMessageDialog.MessageFromAgent
125 && dialog != (byte)InstantMessageDialog.StartTyping
126 && dialog != (byte)InstantMessageDialog.StopTyping)
127 {
128 return;
129 }
130
131 if (m_TransferModule != null)
132 {
133 m_TransferModule.SendInstantMessage(im,
134 delegate(bool success)
135 {
136 if (dialog == (uint)InstantMessageDialog.StartTyping ||
137 dialog == (uint)InstantMessageDialog.StopTyping)
138 {
139 return;
140 }
141
142 if ((client != null) && !success)
143 {
144 client.SendInstantMessage(new UUID(im.toAgentID),
145 "Unable to send instant message. "+
146 "User is not logged in.",
147 new UUID(im.fromAgentID), "System",
148 (byte)InstantMessageDialog.BusyAutoResponse,
149 (uint)Util.UnixTimeSinceEpoch());
150 }
151 }
152 );
153 }
154 }
155
156 /// <summary>
157 ///
158 /// </summary>
159 /// <param name="msg"></param>
160 private void OnGridInstantMessage(GridInstantMessage msg)
161 {
162 // Just call the Text IM handler above
163 // This event won't be raised unless we have that agent,
164 // so we can depend on the above not trying to send
165 // via grid again
166 //
167 OnInstantMessage(null, msg);
168 }
169 }
170}
diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs
new file mode 100644
index 0000000..91c22eb
--- /dev/null
+++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs
@@ -0,0 +1,655 @@
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 */
27using System;
28using System.Collections;
29using System.Collections.Generic;
30using System.Reflection;
31using System.Net;
32using System.Threading;
33using OpenMetaverse;
34using log4net;
35using Nini.Config;
36using Nwc.XmlRpc;
37using OpenSim.Framework;
38using OpenSim.Framework.Client;
39using OpenSim.Region.Framework.Interfaces;
40using OpenSim.Region.Framework.Scenes;
41
42namespace OpenSim.Region.CoreModules.Avatar.InstantMessage
43{
44 public class MessageTransferModule : IRegionModule, IMessageTransferModule
45 {
46 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
47
48 // private bool m_Enabled = false;
49 private bool m_Gridmode = false;
50 private List<Scene> m_Scenes = new List<Scene>();
51 private Dictionary<UUID, ulong> m_UserRegionMap = new Dictionary<UUID, ulong>();
52
53 public void Initialise(Scene scene, IConfigSource config)
54 {
55 IConfig cnf = config.Configs["Messaging"];
56 if (cnf != null && cnf.GetString(
57 "MessageTransferModule", "MessageTransferModule") !=
58 "MessageTransferModule")
59 return;
60
61 cnf = config.Configs["Startup"];
62 if (cnf != null)
63 m_Gridmode = cnf.GetBoolean("gridmode", false);
64
65 // m_Enabled = true;
66
67 lock (m_Scenes)
68 {
69 if (m_Scenes.Count == 0)
70 {
71 scene.CommsManager.HttpServer.AddXmlRPCHandler(
72 "grid_instant_message", processXMLRPCGridInstantMessage);
73 }
74
75 scene.RegisterModuleInterface<IMessageTransferModule>(this);
76 m_Scenes.Add(scene);
77 }
78 }
79
80 public void PostInitialise()
81 {
82 }
83
84 public void Close()
85 {
86 }
87
88 public string Name
89 {
90 get { return "MessageTransferModule"; }
91 }
92
93 public bool IsSharedModule
94 {
95 get { return true; }
96 }
97
98 public void SendInstantMessage(GridInstantMessage im, MessageResultNotification result)
99 {
100 UUID toAgentID = new UUID(im.toAgentID);
101
102 m_log.DebugFormat("[INSTANT MESSAGE]: Attempting delivery of IM from {0} to {1}", im.fromAgentName, toAgentID.ToString());
103
104 // Try root avatar only first
105 foreach (Scene scene in m_Scenes)
106 {
107 if (scene.Entities.ContainsKey(toAgentID) &&
108 scene.Entities[toAgentID] is ScenePresence)
109 {
110 m_log.DebugFormat("[INSTANT MESSAGE]: Looking for {0} in {1}", toAgentID.ToString(), scene.RegionInfo.RegionName);
111 // Local message
112 ScenePresence user = (ScenePresence) scene.Entities[toAgentID];
113 if (!user.IsChildAgent)
114 {
115 m_log.DebugFormat("[INSTANT MESSAGE]: Delivering to client");
116 user.ControllingClient.SendInstantMessage(
117 new UUID(im.fromAgentID),
118 im.message,
119 new UUID(im.toAgentID),
120 im.fromAgentName,
121 im.dialog,
122 im.timestamp,
123 new UUID(im.imSessionID),
124 im.fromGroup,
125 im.binaryBucket);
126 // Message sent
127 result(true);
128 return;
129 }
130 }
131 }
132
133 // try child avatar second
134 foreach (Scene scene in m_Scenes)
135 {
136// m_log.DebugFormat(
137// "[INSTANT MESSAGE]: Looking for child of {0} in {1}", toAgentID, scene.RegionInfo.RegionName);
138
139 if (scene.Entities.ContainsKey(toAgentID) &&
140 scene.Entities[toAgentID] is ScenePresence)
141 {
142 // Local message
143 ScenePresence user = (ScenePresence) scene.Entities[toAgentID];
144
145 m_log.DebugFormat("[INSTANT MESSAGE]: Delivering to client");
146 user.ControllingClient.SendInstantMessage(
147 new UUID(im.fromAgentID),
148 im.message,
149 new UUID(im.toAgentID),
150 im.fromAgentName,
151 im.dialog,
152 im.timestamp,
153 new UUID(im.imSessionID),
154 im.fromGroup,
155 im.binaryBucket);
156 // Message sent
157 result(true);
158 return;
159 }
160 }
161
162 if (m_Gridmode)
163 {
164 //m_log.DebugFormat("[INSTANT MESSAGE]: Delivering via grid");
165 // Still here, try send via Grid
166 SendGridInstantMessageViaXMLRPC(im, result);
167 return;
168 }
169
170 //m_log.DebugFormat("[INSTANT MESSAGE]: Undeliverable");
171 result(false);
172 return;
173 }
174
175 /// <summary>
176 /// Process a XMLRPC Grid Instant Message
177 /// </summary>
178 /// <param name="request">XMLRPC parameters
179 /// </param>
180 /// <returns>Nothing much</returns>
181 protected virtual XmlRpcResponse processXMLRPCGridInstantMessage(XmlRpcRequest request)
182 {
183 bool successful = false;
184
185 // TODO: For now, as IMs seem to be a bit unreliable on OSGrid, catch all exception that
186 // happen here and aren't caught and log them.
187 try
188 {
189 // various rational defaults
190 UUID fromAgentID = UUID.Zero;
191 UUID toAgentID = UUID.Zero;
192 UUID imSessionID = UUID.Zero;
193 uint timestamp = 0;
194 string fromAgentName = "";
195 string message = "";
196 byte dialog = (byte)0;
197 bool fromGroup = false;
198 byte offline = (byte)0;
199 uint ParentEstateID=0;
200 Vector3 Position = Vector3.Zero;
201 UUID RegionID = UUID.Zero ;
202 byte[] binaryBucket = new byte[0];
203
204 float pos_x = 0;
205 float pos_y = 0;
206 float pos_z = 0;
207 //m_log.Info("Processing IM");
208
209
210 Hashtable requestData = (Hashtable)request.Params[0];
211 // Check if it's got all the data
212 if (requestData.ContainsKey("from_agent_id")
213 && requestData.ContainsKey("to_agent_id") && requestData.ContainsKey("im_session_id")
214 && requestData.ContainsKey("timestamp") && requestData.ContainsKey("from_agent_name")
215 && requestData.ContainsKey("message") && requestData.ContainsKey("dialog")
216 && requestData.ContainsKey("from_group")
217 && requestData.ContainsKey("offline") && requestData.ContainsKey("parent_estate_id")
218 && requestData.ContainsKey("position_x") && requestData.ContainsKey("position_y")
219 && requestData.ContainsKey("position_z") && requestData.ContainsKey("region_id")
220 && requestData.ContainsKey("binary_bucket"))
221 {
222 // Do the easy way of validating the UUIDs
223 UUID.TryParse((string)requestData["from_agent_id"], out fromAgentID);
224 UUID.TryParse((string)requestData["to_agent_id"], out toAgentID);
225 UUID.TryParse((string)requestData["im_session_id"], out imSessionID);
226 UUID.TryParse((string)requestData["region_id"], out RegionID);
227
228 try
229 {
230 timestamp = (uint)Convert.ToInt32((string)requestData["timestamp"]);
231 }
232 catch (ArgumentException)
233 {
234 }
235 catch (FormatException)
236 {
237 }
238 catch (OverflowException)
239 {
240 }
241
242 fromAgentName = (string)requestData["from_agent_name"];
243 message = (string)requestData["message"];
244
245 // Bytes don't transfer well over XMLRPC, so, we Base64 Encode them.
246 string requestData1 = (string)requestData["dialog"];
247 if (string.IsNullOrEmpty(requestData1))
248 {
249 dialog = 0;
250 }
251 else
252 {
253 byte[] dialogdata = Convert.FromBase64String(requestData1);
254 dialog = dialogdata[0];
255 }
256
257 if ((string)requestData["from_group"] == "TRUE")
258 fromGroup = true;
259
260 string requestData2 = (string)requestData["offline"];
261 if (String.IsNullOrEmpty(requestData2))
262 {
263 offline = 0;
264 }
265 else
266 {
267 byte[] offlinedata = Convert.FromBase64String(requestData2);
268 offline = offlinedata[0];
269 }
270
271 try
272 {
273 ParentEstateID = (uint)Convert.ToInt32((string)requestData["parent_estate_id"]);
274 }
275 catch (ArgumentException)
276 {
277 }
278 catch (FormatException)
279 {
280 }
281 catch (OverflowException)
282 {
283 }
284
285 try
286 {
287 pos_x = (uint)Convert.ToInt32((string)requestData["position_x"]);
288 }
289 catch (ArgumentException)
290 {
291 }
292 catch (FormatException)
293 {
294 }
295 catch (OverflowException)
296 {
297 }
298 try
299 {
300 pos_y = (uint)Convert.ToInt32((string)requestData["position_y"]);
301 }
302 catch (ArgumentException)
303 {
304 }
305 catch (FormatException)
306 {
307 }
308 catch (OverflowException)
309 {
310 }
311 try
312 {
313 pos_z = (uint)Convert.ToInt32((string)requestData["position_z"]);
314 }
315 catch (ArgumentException)
316 {
317 }
318 catch (FormatException)
319 {
320 }
321 catch (OverflowException)
322 {
323 }
324
325 Position = new Vector3(pos_x, pos_y, pos_z);
326
327 string requestData3 = (string)requestData["binary_bucket"];
328 if (string.IsNullOrEmpty(requestData3))
329 {
330 binaryBucket = new byte[0];
331 }
332 else
333 {
334 binaryBucket = Convert.FromBase64String(requestData3);
335 }
336
337 // Create a New GridInstantMessageObject the the data
338 GridInstantMessage gim = new GridInstantMessage();
339 gim.fromAgentID = fromAgentID.Guid;
340 gim.fromAgentName = fromAgentName;
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 catch (Exception e)
384 {
385 m_log.Error("[INSTANT MESSAGE]: Caught unexpected exception:", e);
386 successful = false;
387 }
388
389 //Send response back to region calling if it was successful
390 // calling region uses this to know when to look up a user's location again.
391 XmlRpcResponse resp = new XmlRpcResponse();
392 Hashtable respdata = new Hashtable();
393 if (successful)
394 respdata["success"] = "TRUE";
395 else
396 respdata["success"] = "FALSE";
397 resp.Value = respdata;
398
399 return resp;
400 }
401
402 /// <summary>
403 /// delegate for sending a grid instant message asynchronously
404 /// </summary>
405 public delegate void GridInstantMessageDelegate(GridInstantMessage im, MessageResultNotification result, ulong prevRegionHandle);
406
407 private void GridInstantMessageCompleted(IAsyncResult iar)
408 {
409 GridInstantMessageDelegate icon =
410 (GridInstantMessageDelegate)iar.AsyncState;
411 icon.EndInvoke(iar);
412 }
413
414
415 protected virtual void SendGridInstantMessageViaXMLRPC(GridInstantMessage im, MessageResultNotification result)
416 {
417 GridInstantMessageDelegate d = SendGridInstantMessageViaXMLRPCAsync;
418
419 d.BeginInvoke(im, result, 0, GridInstantMessageCompleted, d);
420 }
421
422 /// <summary>
423 /// Recursive SendGridInstantMessage over XMLRPC method.
424 /// This is called from within a dedicated thread.
425 /// The first time this is called, prevRegionHandle will be 0 Subsequent times this is called from
426 /// itself, prevRegionHandle will be the last region handle that we tried to send.
427 /// If the handles are the same, we look up the user's location using the grid.
428 /// If the handles are still the same, we end. The send failed.
429 /// </summary>
430 /// <param name="prevRegionHandle">
431 /// Pass in 0 the first time this method is called. It will be called recursively with the last
432 /// regionhandle tried
433 /// </param>
434 protected virtual void SendGridInstantMessageViaXMLRPCAsync(GridInstantMessage im, MessageResultNotification result, ulong prevRegionHandle)
435 {
436 UUID toAgentID = new UUID(im.toAgentID);
437
438 UserAgentData upd = null;
439
440 bool lookupAgent = false;
441
442 lock (m_UserRegionMap)
443 {
444 if (m_UserRegionMap.ContainsKey(toAgentID))
445 {
446 upd = new UserAgentData();
447 upd.AgentOnline = true;
448 upd.Handle = m_UserRegionMap[toAgentID];
449
450 // We need to compare the current regionhandle with the previous region handle
451 // or the recursive loop will never end because it will never try to lookup the agent again
452 if (prevRegionHandle == upd.Handle)
453 {
454 lookupAgent = true;
455 }
456 }
457 else
458 {
459 lookupAgent = true;
460 }
461 }
462
463
464 // Are we needing to look-up an agent?
465 if (lookupAgent)
466 {
467 // Non-cached user agent lookup.
468 upd = m_Scenes[0].CommsManager.UserService.GetAgentByUUID(toAgentID);
469
470 if (upd != null)
471 {
472 // check if we've tried this before..
473 // This is one way to end the recursive loop
474 //
475 if (upd.Handle == prevRegionHandle)
476 {
477 m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message");
478 result(false);
479 return;
480 }
481 }
482 else
483 {
484 m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message");
485 result(false);
486 return;
487 }
488 }
489
490 if (upd != null)
491 {
492 if (upd.AgentOnline)
493 {
494 RegionInfo reginfo = m_Scenes[0].SceneGridService.RequestNeighbouringRegionInfo(upd.Handle);
495 if (reginfo != null)
496 {
497 Hashtable msgdata = ConvertGridInstantMessageToXMLRPC(im);
498 // Not actually used anymore, left in for compatibility
499 // Remove at next interface change
500 //
501 msgdata["region_handle"] = 0;
502 bool imresult = doIMSending(reginfo, msgdata);
503 if (imresult)
504 {
505 // IM delivery successful, so store the Agent's location in our local cache.
506 lock (m_UserRegionMap)
507 {
508 if (m_UserRegionMap.ContainsKey(toAgentID))
509 {
510 m_UserRegionMap[toAgentID] = upd.Handle;
511 }
512 else
513 {
514 m_UserRegionMap.Add(toAgentID, upd.Handle);
515 }
516 }
517 result(true);
518 }
519 else
520 {
521 // try again, but lookup user this time.
522 // Warning, this must call the Async version
523 // of this method or we'll be making thousands of threads
524 // The version within the spawned thread is SendGridInstantMessageViaXMLRPCAsync
525 // The version that spawns the thread is SendGridInstantMessageViaXMLRPC
526
527 // This is recursive!!!!!
528 SendGridInstantMessageViaXMLRPCAsync(im, result,
529 upd.Handle);
530 }
531
532 }
533 else
534 {
535 m_log.WarnFormat("[GRID INSTANT MESSAGE]: Unable to find region {0}", upd.Handle);
536 result(false);
537 }
538 }
539 else
540 {
541 result(false);
542 }
543 }
544 else
545 {
546 m_log.WarnFormat("[GRID INSTANT MESSAGE]: Unable to find user {0}", toAgentID);
547 result(false);
548 }
549
550 }
551
552 /// <summary>
553 /// This actually does the XMLRPC Request
554 /// </summary>
555 /// <param name="reginfo">RegionInfo we pull the data out of to send the request to</param>
556 /// <param name="xmlrpcdata">The Instant Message data Hashtable</param>
557 /// <returns>Bool if the message was successfully delivered at the other side.</returns>
558 private bool doIMSending(RegionInfo reginfo, Hashtable xmlrpcdata)
559 {
560
561 ArrayList SendParams = new ArrayList();
562 SendParams.Add(xmlrpcdata);
563 XmlRpcRequest GridReq = new XmlRpcRequest("grid_instant_message", SendParams);
564 try
565 {
566
567 XmlRpcResponse GridResp = GridReq.Send("http://" + reginfo.ExternalHostName + ":" + reginfo.HttpPort, 3000);
568
569 Hashtable responseData = (Hashtable)GridResp.Value;
570
571 if (responseData.ContainsKey("success"))
572 {
573 if ((string)responseData["success"] == "TRUE")
574 {
575 return true;
576 }
577 else
578 {
579 return false;
580 }
581 }
582 else
583 {
584 return false;
585 }
586 }
587 catch (WebException e)
588 {
589 m_log.ErrorFormat("[GRID INSTANT MESSAGE]: Error sending message to http://{0}:{1} the host didn't respond ({2})",
590 reginfo.ExternalHostName, reginfo.HttpPort, e.Message);
591 }
592
593 return false;
594 }
595
596 /// <summary>
597 /// Get ulong region handle for region by it's Region UUID.
598 /// We use region handles over grid comms because there's all sorts of free and cool caching.
599 /// </summary>
600 /// <param name="regionID">UUID of region to get the region handle for</param>
601 /// <returns></returns>
602// private ulong getLocalRegionHandleFromUUID(UUID regionID)
603// {
604// ulong returnhandle = 0;
605//
606// lock (m_Scenes)
607// {
608// foreach (Scene sn in m_Scenes)
609// {
610// if (sn.RegionInfo.RegionID == regionID)
611// {
612// returnhandle = sn.RegionInfo.RegionHandle;
613// break;
614// }
615// }
616// }
617// return returnhandle;
618// }
619
620 /// <summary>
621 /// Takes a GridInstantMessage and converts it into a Hashtable for XMLRPC
622 /// </summary>
623 /// <param name="msg">The GridInstantMessage object</param>
624 /// <returns>Hashtable containing the XMLRPC request</returns>
625 private Hashtable ConvertGridInstantMessageToXMLRPC(GridInstantMessage msg)
626 {
627 Hashtable gim = new Hashtable();
628 gim["from_agent_id"] = msg.fromAgentID.ToString();
629 // Kept for compatibility
630 gim["from_agent_session"] = UUID.Zero.ToString();
631 gim["to_agent_id"] = msg.toAgentID.ToString();
632 gim["im_session_id"] = msg.imSessionID.ToString();
633 gim["timestamp"] = msg.timestamp.ToString();
634 gim["from_agent_name"] = msg.fromAgentName;
635 gim["message"] = msg.message;
636 byte[] dialogdata = new byte[1];dialogdata[0] = msg.dialog;
637 gim["dialog"] = Convert.ToBase64String(dialogdata,Base64FormattingOptions.None);
638
639 if (msg.fromGroup)
640 gim["from_group"] = "TRUE";
641 else
642 gim["from_group"] = "FALSE";
643 byte[] offlinedata = new byte[1]; offlinedata[0] = msg.offline;
644 gim["offline"] = Convert.ToBase64String(offlinedata, Base64FormattingOptions.None);
645 gim["parent_estate_id"] = msg.ParentEstateID.ToString();
646 gim["position_x"] = msg.Position.X.ToString();
647 gim["position_y"] = msg.Position.Y.ToString();
648 gim["position_z"] = msg.Position.Z.ToString();
649 gim["region_id"] = msg.RegionID.ToString();
650 gim["binary_bucket"] = Convert.ToBase64String(msg.binaryBucket,Base64FormattingOptions.None);
651 return gim;
652 }
653
654 }
655}
diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs
new file mode 100644
index 0000000..49fd70a
--- /dev/null
+++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs
@@ -0,0 +1,426 @@
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 */
27using System;
28using System.Collections;
29using System.Collections.Generic;
30using System.Reflection;
31using System.Net;
32using System.Threading;
33using OpenMetaverse;
34using log4net;
35using Nini.Config;
36using Nwc.XmlRpc;
37using OpenSim.Framework;
38using OpenSim.Framework.Client;
39using OpenSim.Region.Framework.Interfaces;
40using OpenSim.Region.Framework.Scenes;
41
42namespace OpenSim.Region.CoreModules.Avatar.InstantMessage
43{
44 public class PresenceModule : IRegionModule, IPresenceModule
45 {
46 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
47
48 private bool m_Enabled = false;
49 private bool m_Gridmode = false;
50
51 // some default scene for doing things that aren't connected to a specific scene. Avoids locking.
52 private Scene m_initialScene;
53
54 private List<Scene> m_Scenes = new List<Scene>();
55
56 // we currently are only interested in root-agents. If the root isn't here, we don't know the region the
57 // user is in, so we have to ask the messaging server anyway.
58 private Dictionary<UUID, Scene> m_RootAgents =
59 new Dictionary<UUID, Scene>();
60
61 public event PresenceChange OnPresenceChange;
62 public event BulkPresenceData OnBulkPresenceData;
63
64 public void Initialise(Scene scene, IConfigSource config)
65 {
66 lock (m_Scenes)
67 {
68 // This is a shared module; Initialise will be called for every region on this server.
69 // Only check config once for the first region.
70 if (m_Scenes.Count == 0)
71 {
72 IConfig cnf = config.Configs["Messaging"];
73 if (cnf != null && cnf.GetString(
74 "PresenceModule", "PresenceModule") !=
75 "PresenceModule")
76 return;
77
78 cnf = config.Configs["Startup"];
79 if (cnf != null)
80 m_Gridmode = cnf.GetBoolean("gridmode", false);
81
82 m_Enabled = true;
83
84 m_initialScene = scene;
85 }
86
87 if (m_Gridmode)
88 NotifyMessageServerOfStartup(scene);
89
90 m_Scenes.Add(scene);
91 }
92
93 scene.RegisterModuleInterface<IPresenceModule>(this);
94
95 scene.EventManager.OnNewClient += OnNewClient;
96 scene.EventManager.OnSetRootAgentScene += OnSetRootAgentScene;
97 scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
98 }
99
100 public void PostInitialise()
101 {
102 }
103
104 public void Close()
105 {
106 if (!m_Gridmode || !m_Enabled)
107 return;
108
109 if (OnPresenceChange != null)
110 {
111 lock (m_RootAgents)
112 {
113 // on shutdown, users are kicked, too
114 foreach (KeyValuePair<UUID, Scene> pair in m_RootAgents)
115 {
116 OnPresenceChange(new PresenceInfo(pair.Key, UUID.Zero));
117 }
118 }
119 }
120
121 lock (m_Scenes)
122 {
123 foreach (Scene scene in m_Scenes)
124 NotifyMessageServerOfShutdown(scene);
125 }
126 }
127
128 public string Name
129 {
130 get { return "PresenceModule"; }
131 }
132
133 public bool IsSharedModule
134 {
135 get { return true; }
136 }
137
138 public void RequestBulkPresenceData(UUID[] users)
139 {
140 if (OnBulkPresenceData != null)
141 {
142 PresenceInfo[] result = new PresenceInfo[users.Length];
143 if (m_Gridmode)
144 {
145 // first check the local information
146 List<UUID> uuids = new List<UUID>(); // the uuids to check remotely
147 List<int> indices = new List<int>(); // just for performance.
148 lock (m_RootAgents)
149 {
150 for (int i = 0; i < uuids.Count; ++i)
151 {
152 Scene scene;
153 if (m_RootAgents.TryGetValue(users[i], out scene))
154 {
155 result[i] = new PresenceInfo(users[i], scene.RegionInfo.RegionID);
156 }
157 else
158 {
159 uuids.Add(users[i]);
160 indices.Add(i);
161 }
162 }
163 }
164
165 // now we have filtered out all the local root agents. The rest we have to request info about
166 Dictionary<UUID, FriendRegionInfo> infos = m_initialScene.GetFriendRegionInfos(uuids);
167 for (int i = 0; i < uuids.Count; ++i)
168 {
169 FriendRegionInfo info;
170 if (infos.TryGetValue(uuids[i], out info) && info.isOnline)
171 {
172 UUID regionID = info.regionID;
173 if (regionID == UUID.Zero)
174 {
175 // TODO this is the old messaging-server protocol; only the regionHandle is available.
176 // Fetch region-info to get the id
177 RegionInfo regionInfo = m_initialScene.RequestNeighbouringRegionInfo(info.regionHandle);
178 regionID = regionInfo.RegionID;
179 }
180 result[indices[i]] = new PresenceInfo(uuids[i], regionID);
181 }
182 else result[indices[i]] = new PresenceInfo(uuids[i], UUID.Zero);
183 }
184 }
185 else
186 {
187 // in standalone mode, we have all the info locally available.
188 lock (m_RootAgents)
189 {
190 for (int i = 0; i < users.Length; ++i)
191 {
192 Scene scene;
193 if (m_RootAgents.TryGetValue(users[i], out scene))
194 {
195 result[i] = new PresenceInfo(users[i], scene.RegionInfo.RegionID);
196 }
197 else
198 {
199 result[i] = new PresenceInfo(users[i], UUID.Zero);
200 }
201 }
202 }
203 }
204
205 // tell everyone
206 OnBulkPresenceData(result);
207 }
208 }
209
210 // new client doesn't mean necessarily that user logged in, it just means it entered one of the
211 // the regions on this server
212 public void OnNewClient(IClientAPI client)
213 {
214 client.OnConnectionClosed += OnConnectionClosed;
215 client.OnLogout += OnLogout;
216
217 // KLUDGE: See handler for details.
218 client.OnEconomyDataRequest += OnEconomyDataRequest;
219 }
220
221 // connection closed just means *one* client connection has been closed. It doesn't mean that the
222 // user has logged off; it might have just TPed away.
223 public void OnConnectionClosed(IClientAPI client)
224 {
225 // TODO: Have to think what we have to do here...
226 // Should we just remove the root from the list (if scene matches)?
227 if (!(client.Scene is Scene))
228 return;
229 Scene scene = (Scene)client.Scene;
230
231 lock (m_RootAgents)
232 {
233 Scene rootScene;
234 if (!(m_RootAgents.TryGetValue(client.AgentId, out rootScene)) || scene != rootScene)
235 return;
236
237 m_RootAgents.Remove(client.AgentId);
238 }
239
240 // Should it have logged off, we'll do the logout part in OnLogout, even if no root is stored
241 // anymore. It logged off, after all...
242 }
243
244 // Triggered when the user logs off.
245 public void OnLogout(IClientAPI client)
246 {
247 if (!(client.Scene is Scene))
248 return;
249 Scene scene = (Scene)client.Scene;
250
251 // On logout, we really remove the client from rootAgents, even if the scene doesn't match
252 lock (m_RootAgents)
253 {
254 if (m_RootAgents.ContainsKey(client.AgentId)) m_RootAgents.Remove(client.AgentId);
255 }
256
257 // now inform the messaging server and anyone who is interested
258 NotifyMessageServerOfAgentLeaving(client.AgentId, scene.RegionInfo.RegionID, scene.RegionInfo.RegionHandle);
259 if (OnPresenceChange != null) OnPresenceChange(new PresenceInfo(client.AgentId, UUID.Zero));
260 }
261
262 public void OnSetRootAgentScene(UUID agentID, Scene scene)
263 {
264 // OnSetRootAgentScene can be called from several threads at once (with different agentID).
265 // Concurrent access to m_RootAgents is prone to failure on multi-core/-processor systems without
266 // correct locking).
267 lock (m_RootAgents)
268 {
269 Scene rootScene;
270 if (m_RootAgents.TryGetValue(agentID, out rootScene) && scene == rootScene)
271 {
272 return;
273 }
274 m_RootAgents[agentID] = scene;
275 }
276 // inform messaging server that agent changed the region
277 NotifyMessageServerOfAgentLocation(agentID, scene.RegionInfo.RegionID, scene.RegionInfo.RegionHandle);
278 }
279
280 private void OnEconomyDataRequest(UUID agentID)
281 {
282 // KLUDGE: This is the only way I found to get a message (only) after login was completed and the
283 // client is connected enough to receive UDP packets.
284 // This packet seems to be sent only once, just after connection was established to the first
285 // region after login.
286 // We use it here to trigger a presence update; the old update-on-login was never be heard by
287 // the freshly logged in viewer, as it wasn't connected to the region at that time.
288 // TODO: Feel free to replace this by a better solution if you find one.
289
290 // get the agent. This should work every time, as we just got a packet from it
291 ScenePresence agent = null;
292 lock (m_Scenes)
293 {
294 foreach (Scene scene in m_Scenes)
295 {
296 agent = scene.GetScenePresence(agentID);
297 if (agent != null) break;
298 }
299 }
300
301 // just to be paranoid...
302 if (agent == null)
303 {
304 m_log.ErrorFormat("[PRESENCE]: Got a packet from agent {0} who can't be found anymore!?", agentID);
305 return;
306 }
307
308 // we are a bit premature here, but the next packet will switch this child agent to root.
309 if (OnPresenceChange != null) OnPresenceChange(new PresenceInfo(agentID, agent.Scene.RegionInfo.RegionID));
310 }
311
312 public void OnMakeChildAgent(ScenePresence agent)
313 {
314 // OnMakeChildAgent can be called from several threads at once (with different agent).
315 // Concurrent access to m_RootAgents is prone to failure on multi-core/-processor systems without
316 // correct locking).
317 lock (m_RootAgents)
318 {
319 Scene rootScene;
320 if (m_RootAgents.TryGetValue(agent.UUID, out rootScene) && agent.Scene == rootScene)
321 {
322 m_RootAgents.Remove(agent.UUID);
323 }
324 }
325 // don't notify the messaging-server; either this agent just had been downgraded and another one will be upgraded
326 // to root momentarily (which will notify the messaging-server), or possibly it will be closed in a moment,
327 // which will update the messaging-server, too.
328 }
329
330 private void NotifyMessageServerOfStartup(Scene scene)
331 {
332 Hashtable xmlrpcdata = new Hashtable();
333 xmlrpcdata["RegionUUID"] = scene.RegionInfo.RegionID.ToString();
334 ArrayList SendParams = new ArrayList();
335 SendParams.Add(xmlrpcdata);
336 try
337 {
338 XmlRpcRequest UpRequest = new XmlRpcRequest("region_startup", SendParams);
339 XmlRpcResponse resp = UpRequest.Send(scene.CommsManager.NetworkServersInfo.MessagingURL, 5000);
340
341 Hashtable responseData = (Hashtable)resp.Value;
342 if (responseData == null || (!responseData.ContainsKey("success")) || (string)responseData["success"] != "TRUE")
343 {
344 m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of region startup for region {0}", scene.RegionInfo.RegionName);
345 }
346 }
347 catch (System.Net.WebException)
348 {
349 m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of region startup for region {0}", scene.RegionInfo.RegionName);
350 }
351 }
352
353 private void NotifyMessageServerOfShutdown(Scene scene)
354 {
355 Hashtable xmlrpcdata = new Hashtable();
356 xmlrpcdata["RegionUUID"] = scene.RegionInfo.RegionID.ToString();
357 ArrayList SendParams = new ArrayList();
358 SendParams.Add(xmlrpcdata);
359 try
360 {
361 XmlRpcRequest DownRequest = new XmlRpcRequest("region_shutdown", SendParams);
362 XmlRpcResponse resp = DownRequest.Send(scene.CommsManager.NetworkServersInfo.MessagingURL, 5000);
363
364 Hashtable responseData = (Hashtable)resp.Value;
365 if ((!responseData.ContainsKey("success")) || (string)responseData["success"] != "TRUE")
366 {
367 m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of region shutdown for region {0}", scene.RegionInfo.RegionName);
368 }
369 }
370 catch (System.Net.WebException)
371 {
372 m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of region shutdown for region {0}", scene.RegionInfo.RegionName);
373 }
374 }
375
376 private void NotifyMessageServerOfAgentLocation(UUID agentID, UUID region, ulong regionHandle)
377 {
378 Hashtable xmlrpcdata = new Hashtable();
379 xmlrpcdata["AgentID"] = agentID.ToString();
380 xmlrpcdata["RegionUUID"] = region.ToString();
381 xmlrpcdata["RegionHandle"] = regionHandle.ToString();
382 ArrayList SendParams = new ArrayList();
383 SendParams.Add(xmlrpcdata);
384 try
385 {
386 XmlRpcRequest LocationRequest = new XmlRpcRequest("agent_location", SendParams);
387 XmlRpcResponse resp = LocationRequest.Send(m_Scenes[0].CommsManager.NetworkServersInfo.MessagingURL, 5000);
388
389 Hashtable responseData = (Hashtable)resp.Value;
390 if ((!responseData.ContainsKey("success")) || (string)responseData["success"] != "TRUE")
391 {
392 m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of agent location for {0}", agentID.ToString());
393 }
394 }
395 catch (System.Net.WebException)
396 {
397 m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of agent location for {0}", agentID.ToString());
398 }
399 }
400
401 private void NotifyMessageServerOfAgentLeaving(UUID agentID, UUID region, ulong regionHandle)
402 {
403 Hashtable xmlrpcdata = new Hashtable();
404 xmlrpcdata["AgentID"] = agentID.ToString();
405 xmlrpcdata["RegionUUID"] = region.ToString();
406 xmlrpcdata["RegionHandle"] = regionHandle.ToString();
407 ArrayList SendParams = new ArrayList();
408 SendParams.Add(xmlrpcdata);
409 try
410 {
411 XmlRpcRequest LeavingRequest = new XmlRpcRequest("agent_leaving", SendParams);
412 XmlRpcResponse resp = LeavingRequest.Send(m_Scenes[0].CommsManager.NetworkServersInfo.MessagingURL, 5000);
413
414 Hashtable responseData = (Hashtable)resp.Value;
415 if ((!responseData.ContainsKey("success")) || (string)responseData["success"] != "TRUE")
416 {
417 m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of agent leaving for {0}", agentID.ToString());
418 }
419 }
420 catch (System.Net.WebException)
421 {
422 m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of agent leaving for {0}", agentID.ToString());
423 }
424 }
425 }
426}