aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Addons/Groups/GroupsMessagingModule.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Addons/Groups/GroupsMessagingModule.cs')
-rw-r--r--OpenSim/Addons/Groups/GroupsMessagingModule.cs848
1 files changed, 848 insertions, 0 deletions
diff --git a/OpenSim/Addons/Groups/GroupsMessagingModule.cs b/OpenSim/Addons/Groups/GroupsMessagingModule.cs
new file mode 100644
index 0000000..e95db41
--- /dev/null
+++ b/OpenSim/Addons/Groups/GroupsMessagingModule.cs
@@ -0,0 +1,848 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.Linq;
31using System.Reflection;
32using log4net;
33using Mono.Addins;
34using Nini.Config;
35using OpenMetaverse;
36using OpenMetaverse.StructuredData;
37using OpenSim.Framework;
38using OpenSim.Region.Framework.Interfaces;
39using OpenSim.Region.Framework.Scenes;
40using OpenSim.Services.Interfaces;
41using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo;
42using GridRegion = OpenSim.Services.Interfaces.GridRegion;
43
44namespace OpenSim.Groups
45{
46 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsMessagingModule")]
47 public class GroupsMessagingModule : ISharedRegionModule, IGroupsMessagingModule
48 {
49 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
50
51 private List<Scene> m_sceneList = new List<Scene>();
52 private IPresenceService m_presenceService;
53
54 private IMessageTransferModule m_msgTransferModule = null;
55 private IUserManagement m_UserManagement = null;
56 private IGroupsServicesConnector m_groupData = null;
57
58 // Config Options
59 private bool m_groupMessagingEnabled;
60 private bool m_debugEnabled;
61
62 /// <summary>
63 /// If enabled, module only tries to send group IMs to online users by querying cached presence information.
64 /// </summary>
65 private bool m_messageOnlineAgentsOnly;
66
67 /// <summary>
68 /// Cache for online users.
69 /// </summary>
70 /// <remarks>
71 /// Group ID is key, presence information for online members is value.
72 /// Will only be non-null if m_messageOnlineAgentsOnly = true
73 /// We cache here so that group messages don't constantly have to re-request the online user list to avoid
74 /// attempted expensive sending of messages to offline users.
75 /// The tradeoff is that a user that comes online will not receive messages consistently from all other users
76 /// until caches have updated.
77 /// Therefore, we set the cache expiry to just 20 seconds.
78 /// </remarks>
79 private ExpiringCache<UUID, PresenceInfo[]> m_usersOnlineCache;
80
81 private int m_usersOnlineCacheExpirySeconds = 20;
82
83 private Dictionary<UUID, List<string>> m_groupsAgentsDroppedFromChatSession = new Dictionary<UUID, List<string>>();
84 private Dictionary<UUID, List<string>> m_groupsAgentsInvitedToChatSession = new Dictionary<UUID, List<string>>();
85
86 #region Region Module interfaceBase Members
87
88 public void Initialise(IConfigSource config)
89 {
90 IConfig groupsConfig = config.Configs["Groups"];
91
92 if (groupsConfig == null)
93 // Do not run this module by default.
94 return;
95
96 // if groups aren't enabled, we're not needed.
97 // if we're not specified as the connector to use, then we're not wanted
98 if ((groupsConfig.GetBoolean("Enabled", false) == false)
99 || (groupsConfig.GetString("MessagingModule", "") != Name))
100 {
101 m_groupMessagingEnabled = false;
102 return;
103 }
104
105 m_groupMessagingEnabled = groupsConfig.GetBoolean("MessagingEnabled", true);
106
107 if (!m_groupMessagingEnabled)
108 return;
109
110 m_messageOnlineAgentsOnly = groupsConfig.GetBoolean("MessageOnlineUsersOnly", false);
111
112 if (m_messageOnlineAgentsOnly)
113 {
114 m_usersOnlineCache = new ExpiringCache<UUID, PresenceInfo[]>();
115 }
116 else
117 {
118 m_log.Error("[Groups.Messaging]: GroupsMessagingModule V2 requires MessageOnlineUsersOnly = true");
119 m_groupMessagingEnabled = false;
120 return;
121 }
122
123 m_debugEnabled = groupsConfig.GetBoolean("MessagingDebugEnabled", m_debugEnabled);
124
125 m_log.InfoFormat(
126 "[Groups.Messaging]: GroupsMessagingModule enabled with MessageOnlineOnly = {0}, DebugEnabled = {1}",
127 m_messageOnlineAgentsOnly, m_debugEnabled);
128 }
129
130 public void AddRegion(Scene scene)
131 {
132 if (!m_groupMessagingEnabled)
133 return;
134
135 scene.RegisterModuleInterface<IGroupsMessagingModule>(this);
136 m_sceneList.Add(scene);
137
138 scene.EventManager.OnNewClient += OnNewClient;
139 scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
140 scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
141 scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
142 scene.EventManager.OnClientLogin += OnClientLogin;
143
144 scene.AddCommand(
145 "Debug",
146 this,
147 "debug groups messaging verbose",
148 "debug groups messaging verbose <true|false>",
149 "This setting turns on very verbose groups messaging debugging",
150 HandleDebugGroupsMessagingVerbose);
151 }
152
153 public void RegionLoaded(Scene scene)
154 {
155 if (!m_groupMessagingEnabled)
156 return;
157
158 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
159
160 m_groupData = scene.RequestModuleInterface<IGroupsServicesConnector>();
161
162 // No groups module, no groups messaging
163 if (m_groupData == null)
164 {
165 m_log.Error("[Groups.Messaging]: Could not get IGroupsServicesConnector, GroupsMessagingModule is now disabled.");
166 RemoveRegion(scene);
167 return;
168 }
169
170 m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
171
172 // No message transfer module, no groups messaging
173 if (m_msgTransferModule == null)
174 {
175 m_log.Error("[Groups.Messaging]: Could not get MessageTransferModule");
176 RemoveRegion(scene);
177 return;
178 }
179
180 m_UserManagement = scene.RequestModuleInterface<IUserManagement>();
181
182 // No groups module, no groups messaging
183 if (m_UserManagement == null)
184 {
185 m_log.Error("[Groups.Messaging]: Could not get IUserManagement, GroupsMessagingModule is now disabled.");
186 RemoveRegion(scene);
187 return;
188 }
189
190 if (m_presenceService == null)
191 m_presenceService = scene.PresenceService;
192 }
193
194 public void RemoveRegion(Scene scene)
195 {
196 if (!m_groupMessagingEnabled)
197 return;
198
199 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
200
201 m_sceneList.Remove(scene);
202 scene.EventManager.OnNewClient -= OnNewClient;
203 scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage;
204 scene.EventManager.OnClientLogin -= OnClientLogin;
205 scene.UnregisterModuleInterface<IGroupsMessagingModule>(this);
206 }
207
208 public void Close()
209 {
210 if (!m_groupMessagingEnabled)
211 return;
212
213 if (m_debugEnabled) m_log.Debug("[Groups.Messaging]: Shutting down GroupsMessagingModule module.");
214
215 m_sceneList.Clear();
216
217 m_groupData = null;
218 m_msgTransferModule = null;
219 }
220
221 public Type ReplaceableInterface
222 {
223 get { return null; }
224 }
225
226 public string Name
227 {
228 get { return "Groups Messaging Module V2"; }
229 }
230
231 public void PostInitialise()
232 {
233 // NoOp
234 }
235
236 #endregion
237
238 private void HandleDebugGroupsMessagingVerbose(object modules, string[] args)
239 {
240 if (args.Length < 5)
241 {
242 MainConsole.Instance.Output("Usage: debug groups messaging verbose <true|false>");
243 return;
244 }
245
246 bool verbose = false;
247 if (!bool.TryParse(args[4], out verbose))
248 {
249 MainConsole.Instance.Output("Usage: debug groups messaging verbose <true|false>");
250 return;
251 }
252
253 m_debugEnabled = verbose;
254
255 MainConsole.Instance.OutputFormat("{0} verbose logging set to {1}", Name, m_debugEnabled);
256 }
257
258 /// <summary>
259 /// Not really needed, but does confirm that the group exists.
260 /// </summary>
261 public bool StartGroupChatSession(UUID agentID, UUID groupID)
262 {
263 if (m_debugEnabled)
264 m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
265
266 GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID.ToString(), groupID, null);
267
268 if (groupInfo != null)
269 {
270 return true;
271 }
272 else
273 {
274 return false;
275 }
276 }
277
278 public void SendMessageToGroup(GridInstantMessage im, UUID groupID)
279 {
280 SendMessageToGroup(im, groupID, UUID.Zero, null);
281 }
282
283 public void SendMessageToGroup(
284 GridInstantMessage im, UUID groupID, UUID sendingAgentForGroupCalls, Func<GroupMembersData, bool> sendCondition)
285 {
286 int requestStartTick = Environment.TickCount;
287
288 UUID fromAgentID = new UUID(im.fromAgentID);
289
290 // Unlike current XmlRpcGroups, Groups V2 can accept UUID.Zero when a perms check for the requesting agent
291 // is not necessary.
292 List<GroupMembersData> groupMembers = m_groupData.GetGroupMembers(UUID.Zero.ToString(), groupID);
293
294 int groupMembersCount = groupMembers.Count;
295 PresenceInfo[] onlineAgents = null;
296
297 // In V2 we always only send to online members.
298 // Sending to offline members is not an option.
299 string[] t1 = groupMembers.ConvertAll<string>(gmd => gmd.AgentID.ToString()).ToArray();
300
301 // We cache in order not to overwhelm the presence service on large grids with many groups. This does
302 // mean that members coming online will not see all group members until after m_usersOnlineCacheExpirySeconds has elapsed.
303 // (assuming this is the same across all grid simulators).
304 if (!m_usersOnlineCache.TryGetValue(groupID, out onlineAgents))
305 {
306 onlineAgents = m_presenceService.GetAgents(t1);
307 m_usersOnlineCache.Add(groupID, onlineAgents, m_usersOnlineCacheExpirySeconds);
308 }
309
310 HashSet<string> onlineAgentsUuidSet = new HashSet<string>();
311 Array.ForEach<PresenceInfo>(onlineAgents, pi => onlineAgentsUuidSet.Add(pi.UserID));
312
313 groupMembers = groupMembers.Where(gmd => onlineAgentsUuidSet.Contains(gmd.AgentID.ToString())).ToList();
314
315// if (m_debugEnabled)
316// m_log.DebugFormat(
317// "[Groups.Messaging]: SendMessageToGroup called for group {0} with {1} visible members, {2} online",
318// groupID, groupMembersCount, groupMembers.Count());
319
320 im.imSessionID = groupID.Guid;
321 im.fromGroup = true;
322 IClientAPI thisClient = GetActiveClient(fromAgentID);
323 if (thisClient != null)
324 {
325 im.RegionID = thisClient.Scene.RegionInfo.RegionID.Guid;
326 }
327
328 if ((im.binaryBucket == null) || (im.binaryBucket.Length == 0) || ((im.binaryBucket.Length == 1 && im.binaryBucket[0] == 0)))
329 {
330 ExtendedGroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), groupID, null);
331 if (groupInfo != null)
332 im.binaryBucket = Util.StringToBytes256(groupInfo.GroupName);
333 }
334
335 // Send to self first of all
336 im.toAgentID = im.fromAgentID;
337 im.fromGroup = true;
338 ProcessMessageFromGroupSession(im);
339
340 List<UUID> regions = new List<UUID>();
341 List<UUID> clientsAlreadySent = new List<UUID>();
342
343 // Then send to everybody else
344 foreach (GroupMembersData member in groupMembers)
345 {
346 if (member.AgentID.Guid == im.fromAgentID)
347 continue;
348
349 if (clientsAlreadySent.Contains(member.AgentID))
350 continue;
351
352 clientsAlreadySent.Add(member.AgentID);
353
354 if (sendCondition != null)
355 {
356 if (!sendCondition(member))
357 {
358 if (m_debugEnabled)
359 m_log.DebugFormat(
360 "[Groups.Messaging]: Not sending to {0} as they do not fulfill send condition",
361 member.AgentID);
362
363 continue;
364 }
365 }
366 else if (hasAgentDroppedGroupChatSession(member.AgentID.ToString(), groupID))
367 {
368 // Don't deliver messages to people who have dropped this session
369 if (m_debugEnabled)
370 m_log.DebugFormat("[Groups.Messaging]: {0} has dropped session, not delivering to them", member.AgentID);
371
372 continue;
373 }
374
375 im.toAgentID = member.AgentID.Guid;
376
377 IClientAPI client = GetActiveClient(member.AgentID);
378 if (client == null)
379 {
380 // If they're not local, forward across the grid
381 // BUT do it only once per region, please! Sim would be even better!
382 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Delivering to {0} via Grid", member.AgentID);
383
384 bool reallySend = true;
385 if (onlineAgents != null)
386 {
387 PresenceInfo presence = onlineAgents.First(p => p.UserID == member.AgentID.ToString());
388 if (regions.Contains(presence.RegionID))
389 reallySend = false;
390 else
391 regions.Add(presence.RegionID);
392 }
393
394 if (reallySend)
395 {
396 // We have to create a new IM structure because the transfer module
397 // uses async send
398 GridInstantMessage msg = new GridInstantMessage(im, true);
399 m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { });
400 }
401 }
402 else
403 {
404 // Deliver locally, directly
405 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", client.Name);
406
407 ProcessMessageFromGroupSession(im);
408 }
409
410 }
411
412 if (m_debugEnabled)
413 m_log.DebugFormat(
414 "[Groups.Messaging]: SendMessageToGroup for group {0} with {1} visible members, {2} online took {3}ms",
415 groupID, groupMembersCount, groupMembers.Count(), Environment.TickCount - requestStartTick);
416 }
417
418 #region SimGridEventHandlers
419
420 void OnClientLogin(IClientAPI client)
421 {
422 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: OnInstantMessage registered for {0}", client.Name);
423 }
424
425 private void OnNewClient(IClientAPI client)
426 {
427 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: OnInstantMessage registered for {0}", client.Name);
428
429 ResetAgentGroupChatSessions(client.AgentId.ToString());
430 }
431
432 void OnMakeRootAgent(ScenePresence sp)
433 {
434 sp.ControllingClient.OnInstantMessage += OnInstantMessage;
435 }
436
437 void OnMakeChildAgent(ScenePresence sp)
438 {
439 sp.ControllingClient.OnInstantMessage -= OnInstantMessage;
440 }
441
442
443 private void OnGridInstantMessage(GridInstantMessage msg)
444 {
445 // The instant message module will only deliver messages of dialog types:
446 // MessageFromAgent, StartTyping, StopTyping, MessageFromObject
447 //
448 // Any other message type will not be delivered to a client by the
449 // Instant Message Module
450
451 UUID regionID = new UUID(msg.RegionID);
452 if (m_debugEnabled)
453 {
454 m_log.DebugFormat("[Groups.Messaging]: {0} called, IM from region {1}",
455 System.Reflection.MethodBase.GetCurrentMethod().Name, regionID);
456
457 DebugGridInstantMessage(msg);
458 }
459
460 // Incoming message from a group
461 if ((msg.fromGroup == true) && (msg.dialog == (byte)InstantMessageDialog.SessionSend))
462 {
463 // We have to redistribute the message across all members of the group who are here
464 // on this sim
465
466 UUID GroupID = new UUID(msg.imSessionID);
467
468 Scene aScene = m_sceneList[0];
469 GridRegion regionOfOrigin = aScene.GridService.GetRegionByUUID(aScene.RegionInfo.ScopeID, regionID);
470
471 List<GroupMembersData> groupMembers = m_groupData.GetGroupMembers(UUID.Zero.ToString(), GroupID);
472
473 //if (m_debugEnabled)
474 // foreach (GroupMembersData m in groupMembers)
475 // m_log.DebugFormat("[Groups.Messaging]: member {0}", m.AgentID);
476
477 foreach (Scene s in m_sceneList)
478 {
479 s.ForEachScenePresence(sp =>
480 {
481 // If we got this via grid messaging, it's because the caller thinks
482 // that the root agent is here. We should only send the IM to root agents.
483 if (sp.IsChildAgent)
484 return;
485
486 GroupMembersData m = groupMembers.Find(gmd =>
487 {
488 return gmd.AgentID == sp.UUID;
489 });
490 if (m.AgentID == UUID.Zero)
491 {
492 if (m_debugEnabled)
493 m_log.DebugFormat("[Groups.Messaging]: skipping agent {0} because he is not a member of the group", sp.UUID);
494 return;
495 }
496
497 // Check if the user has an agent in the region where
498 // the IM came from, and if so, skip it, because the IM
499 // was already sent via that agent
500 if (regionOfOrigin != null)
501 {
502 AgentCircuitData aCircuit = s.AuthenticateHandler.GetAgentCircuitData(sp.UUID);
503 if (aCircuit != null)
504 {
505 if (aCircuit.ChildrenCapSeeds.Keys.Contains(regionOfOrigin.RegionHandle))
506 {
507 if (m_debugEnabled)
508 m_log.DebugFormat("[Groups.Messaging]: skipping agent {0} because he has an agent in region of origin", sp.UUID);
509 return;
510 }
511 else
512 {
513 if (m_debugEnabled)
514 m_log.DebugFormat("[Groups.Messaging]: not skipping agent {0}", sp.UUID);
515 }
516 }
517 }
518
519 UUID AgentID = sp.UUID;
520 msg.toAgentID = AgentID.Guid;
521
522 if (!hasAgentDroppedGroupChatSession(AgentID.ToString(), GroupID))
523 {
524 if (!hasAgentBeenInvitedToGroupChatSession(AgentID.ToString(), GroupID))
525 AddAgentToSession(AgentID, GroupID, msg);
526 else
527 {
528 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", sp.Name);
529
530 ProcessMessageFromGroupSession(msg);
531 }
532 }
533 });
534
535 }
536 }
537 }
538
539 private void ProcessMessageFromGroupSession(GridInstantMessage msg)
540 {
541 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Session message from {0} going to agent {1}", msg.fromAgentName, msg.toAgentID);
542
543 UUID AgentID = new UUID(msg.fromAgentID);
544 UUID GroupID = new UUID(msg.imSessionID);
545 UUID toAgentID = new UUID(msg.toAgentID);
546
547 switch (msg.dialog)
548 {
549 case (byte)InstantMessageDialog.SessionAdd:
550 AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
551 break;
552
553 case (byte)InstantMessageDialog.SessionDrop:
554 AgentDroppedFromGroupChatSession(AgentID.ToString(), GroupID);
555 break;
556
557 case (byte)InstantMessageDialog.SessionSend:
558 // User hasn't dropped, so they're in the session,
559 // maybe we should deliver it.
560 IClientAPI client = GetActiveClient(new UUID(msg.toAgentID));
561 if (client != null)
562 {
563 // Deliver locally, directly
564 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Delivering to {0} locally", client.Name);
565
566 if (!hasAgentDroppedGroupChatSession(toAgentID.ToString(), GroupID))
567 {
568 if (!hasAgentBeenInvitedToGroupChatSession(toAgentID.ToString(), GroupID))
569 // This actually sends the message too, so no need to resend it
570 // with client.SendInstantMessage
571 AddAgentToSession(toAgentID, GroupID, msg);
572 else
573 client.SendInstantMessage(msg);
574 }
575 }
576 else
577 {
578 m_log.WarnFormat("[Groups.Messaging]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID);
579 }
580 break;
581
582 default:
583 m_log.WarnFormat("[Groups.Messaging]: I don't know how to proccess a {0} message.", ((InstantMessageDialog)msg.dialog).ToString());
584 break;
585 }
586 }
587
588 private void AddAgentToSession(UUID AgentID, UUID GroupID, GridInstantMessage msg)
589 {
590 // Agent not in session and hasn't dropped from session
591 // Add them to the session for now, and Invite them
592 AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
593
594 IClientAPI activeClient = GetActiveClient(AgentID);
595 if (activeClient != null)
596 {
597 GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null);
598 if (groupInfo != null)
599 {
600 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Sending chatterbox invite instant message");
601
602 // Force? open the group session dialog???
603 // and simultanously deliver the message, so we don't need to do a seperate client.SendInstantMessage(msg);
604 IEventQueue eq = activeClient.Scene.RequestModuleInterface<IEventQueue>();
605 eq.ChatterboxInvitation(
606 GroupID
607 , groupInfo.GroupName
608 , new UUID(msg.fromAgentID)
609 , msg.message
610 , AgentID
611 , msg.fromAgentName
612 , msg.dialog
613 , msg.timestamp
614 , msg.offline == 1
615 , (int)msg.ParentEstateID
616 , msg.Position
617 , 1
618 , new UUID(msg.imSessionID)
619 , msg.fromGroup
620 , OpenMetaverse.Utils.StringToBytes(groupInfo.GroupName)
621 );
622
623 eq.ChatterBoxSessionAgentListUpdates(
624 new UUID(GroupID)
625 , AgentID
626 , new UUID(msg.toAgentID)
627 , false //canVoiceChat
628 , false //isModerator
629 , false //text mute
630 );
631 }
632 }
633 }
634
635 #endregion
636
637
638 #region ClientEvents
639 private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im)
640 {
641 if (m_debugEnabled)
642 {
643 m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
644
645 DebugGridInstantMessage(im);
646 }
647
648 // Start group IM session
649 if ((im.dialog == (byte)InstantMessageDialog.SessionGroupStart))
650 {
651 if (m_debugEnabled) m_log.InfoFormat("[Groups.Messaging]: imSessionID({0}) toAgentID({1})", im.imSessionID, im.toAgentID);
652
653 UUID GroupID = new UUID(im.imSessionID);
654 UUID AgentID = new UUID(im.fromAgentID);
655
656 GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero.ToString(), GroupID, null);
657
658 if (groupInfo != null)
659 {
660 AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
661
662 ChatterBoxSessionStartReplyViaCaps(remoteClient, groupInfo.GroupName, GroupID);
663
664 IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
665 queue.ChatterBoxSessionAgentListUpdates(
666 GroupID
667 , AgentID
668 , new UUID(im.toAgentID)
669 , false //canVoiceChat
670 , false //isModerator
671 , false //text mute
672 );
673 }
674 }
675
676 // Send a message from locally connected client to a group
677 if ((im.dialog == (byte)InstantMessageDialog.SessionSend))
678 {
679 UUID GroupID = new UUID(im.imSessionID);
680 UUID AgentID = new UUID(im.fromAgentID);
681
682 if (m_debugEnabled)
683 m_log.DebugFormat("[Groups.Messaging]: Send message to session for group {0} with session ID {1}", GroupID, im.imSessionID.ToString());
684
685 //If this agent is sending a message, then they want to be in the session
686 AgentInvitedToGroupChatSession(AgentID.ToString(), GroupID);
687
688 SendMessageToGroup(im, GroupID);
689 }
690 }
691
692 #endregion
693
694 void ChatterBoxSessionStartReplyViaCaps(IClientAPI remoteClient, string groupName, UUID groupID)
695 {
696 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
697
698 OSDMap moderatedMap = new OSDMap(4);
699 moderatedMap.Add("voice", OSD.FromBoolean(false));
700
701 OSDMap sessionMap = new OSDMap(4);
702 sessionMap.Add("moderated_mode", moderatedMap);
703 sessionMap.Add("session_name", OSD.FromString(groupName));
704 sessionMap.Add("type", OSD.FromInteger(0));
705 sessionMap.Add("voice_enabled", OSD.FromBoolean(false));
706
707 OSDMap bodyMap = new OSDMap(4);
708 bodyMap.Add("session_id", OSD.FromUUID(groupID));
709 bodyMap.Add("temp_session_id", OSD.FromUUID(groupID));
710 bodyMap.Add("success", OSD.FromBoolean(true));
711 bodyMap.Add("session_info", sessionMap);
712
713 IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
714
715 if (queue != null)
716 {
717 queue.Enqueue(queue.BuildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId);
718 }
719 }
720
721 private void DebugGridInstantMessage(GridInstantMessage im)
722 {
723 // Don't log any normal IMs (privacy!)
724 if (m_debugEnabled && im.dialog != (byte)InstantMessageDialog.MessageFromAgent)
725 {
726 m_log.WarnFormat("[Groups.Messaging]: IM: fromGroup({0})", im.fromGroup ? "True" : "False");
727 m_log.WarnFormat("[Groups.Messaging]: IM: Dialog({0})", ((InstantMessageDialog)im.dialog).ToString());
728 m_log.WarnFormat("[Groups.Messaging]: IM: fromAgentID({0})", im.fromAgentID.ToString());
729 m_log.WarnFormat("[Groups.Messaging]: IM: fromAgentName({0})", im.fromAgentName.ToString());
730 m_log.WarnFormat("[Groups.Messaging]: IM: imSessionID({0})", im.imSessionID.ToString());
731 m_log.WarnFormat("[Groups.Messaging]: IM: message({0})", im.message.ToString());
732 m_log.WarnFormat("[Groups.Messaging]: IM: offline({0})", im.offline.ToString());
733 m_log.WarnFormat("[Groups.Messaging]: IM: toAgentID({0})", im.toAgentID.ToString());
734 m_log.WarnFormat("[Groups.Messaging]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket"));
735 }
736 }
737
738 #region Client Tools
739
740 /// <summary>
741 /// Try to find an active IClientAPI reference for agentID giving preference to root connections
742 /// </summary>
743 private IClientAPI GetActiveClient(UUID agentID)
744 {
745 if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Looking for local client {0}", agentID);
746
747 IClientAPI child = null;
748
749 // Try root avatar first
750 foreach (Scene scene in m_sceneList)
751 {
752 ScenePresence sp = scene.GetScenePresence(agentID);
753 if (sp != null)
754 {
755 if (!sp.IsChildAgent)
756 {
757 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Found root agent for client : {0}", sp.ControllingClient.Name);
758 return sp.ControllingClient;
759 }
760 else
761 {
762 if (m_debugEnabled) m_log.DebugFormat("[Groups.Messaging]: Found child agent for client : {0}", sp.ControllingClient.Name);
763 child = sp.ControllingClient;
764 }
765 }
766 }
767
768 // If we didn't find a root, then just return whichever child we found, or null if none
769 if (child == null)
770 {
771 if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Could not find local client for agent : {0}", agentID);
772 }
773 else
774 {
775 if (m_debugEnabled) m_log.WarnFormat("[Groups.Messaging]: Returning child agent for client : {0}", child.Name);
776 }
777 return child;
778 }
779
780 #endregion
781
782 #region GroupSessionTracking
783
784 public void ResetAgentGroupChatSessions(string agentID)
785 {
786 foreach (List<string> agentList in m_groupsAgentsDroppedFromChatSession.Values)
787 agentList.Remove(agentID);
788
789 foreach (List<string> agentList in m_groupsAgentsInvitedToChatSession.Values)
790 agentList.Remove(agentID);
791 }
792
793 public bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID)
794 {
795 // If we're tracking this group, and we can find them in the tracking, then they've been invited
796 return m_groupsAgentsInvitedToChatSession.ContainsKey(groupID)
797 && m_groupsAgentsInvitedToChatSession[groupID].Contains(agentID);
798 }
799
800 public bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID)
801 {
802 // If we're tracking drops for this group,
803 // and we find them, well... then they've dropped
804 return m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID)
805 && m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID);
806 }
807
808 public void AgentDroppedFromGroupChatSession(string agentID, UUID groupID)
809 {
810 if (m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID))
811 {
812 // If not in dropped list, add
813 if (!m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID))
814 {
815 m_groupsAgentsDroppedFromChatSession[groupID].Add(agentID);
816 }
817 }
818 }
819
820 public void AgentInvitedToGroupChatSession(string agentID, UUID groupID)
821 {
822 // Add Session Status if it doesn't exist for this session
823 CreateGroupChatSessionTracking(groupID);
824
825 // If nessesary, remove from dropped list
826 if (m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID))
827 {
828 m_groupsAgentsDroppedFromChatSession[groupID].Remove(agentID);
829 }
830
831 // Add to invited
832 if (!m_groupsAgentsInvitedToChatSession[groupID].Contains(agentID))
833 m_groupsAgentsInvitedToChatSession[groupID].Add(agentID);
834 }
835
836 private void CreateGroupChatSessionTracking(UUID groupID)
837 {
838 if (!m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID))
839 {
840 m_groupsAgentsDroppedFromChatSession.Add(groupID, new List<string>());
841 m_groupsAgentsInvitedToChatSession.Add(groupID, new List<string>());
842 }
843
844 }
845 #endregion
846
847 }
848}