OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
GroupsMessagingModule.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 using System;
29 using System.Collections.Generic;
30 using System.Linq;
31 using System.Reflection;
32 using log4net;
33 using Mono.Addins;
34 using Nini.Config;
35 using OpenMetaverse;
36 using OpenMetaverse.StructuredData;
37 using OpenSim.Framework;
38 using OpenSim.Region.Framework.Interfaces;
39 using OpenSim.Region.Framework.Scenes;
40 using OpenSim.Services.Interfaces;
43 
44 namespace OpenSim.Groups
45 {
46  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsMessagingModule")]
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 
65  private bool m_messageOnlineAgentsOnly;
66 
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 
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 
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 }
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
void AgentInvitedToGroupChatSession(string agentID, UUID groupID)
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
void SendMessageToGroup(GridInstantMessage im, UUID groupID, UUID sendingAgentForGroupCalls, Func< GroupMembersData, bool > sendCondition)
Send a message to all the members of a group that fulfill a condition.
void PostInitialise()
This is called exactly once after all the shared region-modules have been instanciated and IRegionMod...
OpenMetaverse.StructuredData.OSDMap OSDMap
bool hasAgentDroppedGroupChatSession(string agentID, UUID groupID)
void Initialise(IConfigSource config)
This is called to initialize the region module. For shared modules, this is called exactly once...
Dictionary< ulong, string > ChildrenCapSeeds
Seed caps for neighbor regions that the user can see into
Circuit data for an agent. Connection information shared between regions that accept UDP connections ...
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
bool StartGroupChatSession(UUID agentID, UUID groupID)
Not really needed, but does confirm that the group exists.
OpenSim.Services.Interfaces.PresenceInfo PresenceInfo
void SendMessageToGroup(GridInstantMessage im, UUID groupID)
Send a message to each member of a group whose chat session is active.
bool hasAgentBeenInvitedToGroupChatSession(string agentID, UUID groupID)
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
OpenSim.Services.Interfaces.GridRegion GridRegion
void AgentDroppedFromGroupChatSession(string agentID, UUID groupID)
This maintains the relationship between a UUID and a user name.