GroupsMessagingModule.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  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. using System;
  28. using System.Collections.Generic;
  29. using System.Reflection;
  30. using log4net;
  31. using Mono.Addins;
  32. using Nini.Config;
  33. using OpenMetaverse;
  34. using OpenMetaverse.StructuredData;
  35. using OpenSim.Framework;
  36. using OpenSim.Region.CoreModules.Framework.EventQueue;
  37. using OpenSim.Region.Framework.Interfaces;
  38. using OpenSim.Region.Framework.Scenes;
  39. using Caps = OpenSim.Framework.Capabilities.Caps;
  40. namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
  41. {
  42. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
  43. public class GroupsMessagingModule : ISharedRegionModule
  44. {
  45. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  46. private List<Scene> m_sceneList = new List<Scene>();
  47. private IMessageTransferModule m_msgTransferModule = null;
  48. private IGroupsModule m_groupsModule = null;
  49. // TODO: Move this off to the Groups Server
  50. public Dictionary<Guid, List<Guid>> m_agentsInGroupSession = new Dictionary<Guid, List<Guid>>();
  51. public Dictionary<Guid, List<Guid>> m_agentsDroppedSession = new Dictionary<Guid, List<Guid>>();
  52. // Config Options
  53. private bool m_groupMessagingEnabled = false;
  54. private bool m_debugEnabled = true;
  55. #region IRegionModuleBase Members
  56. public void Initialise(IConfigSource config)
  57. {
  58. IConfig groupsConfig = config.Configs["Groups"];
  59. if (groupsConfig == null)
  60. {
  61. // Do not run this module by default.
  62. return;
  63. }
  64. else
  65. {
  66. // if groups aren't enabled, we're not needed.
  67. // if we're not specified as the connector to use, then we're not wanted
  68. if ((groupsConfig.GetBoolean("Enabled", false) == false)
  69. || (groupsConfig.GetString("MessagingModule", "Default") != Name))
  70. {
  71. m_groupMessagingEnabled = false;
  72. return;
  73. }
  74. m_groupMessagingEnabled = groupsConfig.GetBoolean("MessagingEnabled", true);
  75. if (!m_groupMessagingEnabled)
  76. {
  77. return;
  78. }
  79. m_log.Info("[GROUPS-MESSAGING]: Initializing GroupsMessagingModule");
  80. m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", true);
  81. }
  82. m_log.Info("[GROUPS-MESSAGING]: GroupsMessagingModule starting up");
  83. }
  84. public void AddRegion(Scene scene)
  85. {
  86. // NoOp
  87. }
  88. public void RegionLoaded(Scene scene)
  89. {
  90. if (!m_groupMessagingEnabled)
  91. return;
  92. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  93. m_groupsModule = scene.RequestModuleInterface<IGroupsModule>();
  94. // No groups module, no groups messaging
  95. if (m_groupsModule == null)
  96. {
  97. m_log.Error("[GROUPS-MESSAGING]: Could not get IGroupsModule, GroupsMessagingModule is now disabled.");
  98. Close();
  99. m_groupMessagingEnabled = false;
  100. return;
  101. }
  102. m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
  103. // No message transfer module, no groups messaging
  104. if (m_msgTransferModule == null)
  105. {
  106. m_log.Error("[GROUPS-MESSAGING]: Could not get MessageTransferModule");
  107. Close();
  108. m_groupMessagingEnabled = false;
  109. return;
  110. }
  111. m_sceneList.Add(scene);
  112. scene.EventManager.OnNewClient += OnNewClient;
  113. scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
  114. }
  115. public void RemoveRegion(Scene scene)
  116. {
  117. if (!m_groupMessagingEnabled)
  118. return;
  119. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  120. m_sceneList.Remove(scene);
  121. }
  122. public void Close()
  123. {
  124. if (!m_groupMessagingEnabled)
  125. return;
  126. if (m_debugEnabled) m_log.Debug("[GROUPS-MESSAGING]: Shutting down GroupsMessagingModule module.");
  127. foreach (Scene scene in m_sceneList)
  128. {
  129. scene.EventManager.OnNewClient -= OnNewClient;
  130. scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage;
  131. }
  132. m_sceneList.Clear();
  133. m_groupsModule = null;
  134. m_msgTransferModule = null;
  135. }
  136. public Type ReplaceableInterface
  137. {
  138. get { return null; }
  139. }
  140. public string Name
  141. {
  142. get { return "GroupsMessagingModule"; }
  143. }
  144. #endregion
  145. #region ISharedRegionModule Members
  146. public void PostInitialise()
  147. {
  148. // NoOp
  149. }
  150. #endregion
  151. #region SimGridEventHandlers
  152. private void OnNewClient(IClientAPI client)
  153. {
  154. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: OnInstantMessage registered for {0}", client.Name);
  155. client.OnInstantMessage += OnInstantMessage;
  156. }
  157. private void OnGridInstantMessage(GridInstantMessage msg)
  158. {
  159. // The instant message module will only deliver messages of dialog types:
  160. // MessageFromAgent, StartTyping, StopTyping, MessageFromObject
  161. //
  162. // Any other message type will not be delivered to a client by the
  163. // Instant Message Module
  164. if (m_debugEnabled)
  165. {
  166. m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  167. DebugGridInstantMessage(msg);
  168. }
  169. // Incoming message from a group
  170. if ((msg.fromGroup == true) &&
  171. ((msg.dialog == (byte)InstantMessageDialog.SessionSend)
  172. || (msg.dialog == (byte)InstantMessageDialog.SessionAdd)
  173. || (msg.dialog == (byte)InstantMessageDialog.SessionDrop)))
  174. {
  175. ProcessMessageFromGroupSession(msg);
  176. }
  177. }
  178. private void ProcessMessageFromGroupSession(GridInstantMessage msg)
  179. {
  180. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Session message from {0} going to agent {1}", msg.fromAgentName, msg.toAgentID);
  181. switch (msg.dialog)
  182. {
  183. case (byte)InstantMessageDialog.SessionAdd:
  184. AddAgentToGroupSession(msg.fromAgentID, msg.imSessionID);
  185. break;
  186. case (byte)InstantMessageDialog.SessionDrop:
  187. RemoveAgentFromGroupSession(msg.fromAgentID, msg.imSessionID);
  188. break;
  189. case (byte)InstantMessageDialog.SessionSend:
  190. if (!m_agentsInGroupSession.ContainsKey(msg.toAgentID)
  191. && !m_agentsDroppedSession.ContainsKey(msg.toAgentID))
  192. {
  193. // Agent not in session and hasn't dropped from session
  194. // Add them to the session for now, and Invite them
  195. AddAgentToGroupSession(msg.toAgentID, msg.imSessionID);
  196. UUID toAgentID = new UUID(msg.toAgentID);
  197. IClientAPI activeClient = GetActiveClient(toAgentID);
  198. if (activeClient != null)
  199. {
  200. UUID groupID = new UUID(msg.fromAgentID);
  201. GroupRecord groupInfo = m_groupsModule.GetGroupRecord(groupID);
  202. if (groupInfo != null)
  203. {
  204. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Sending chatterbox invite instant message");
  205. // Force? open the group session dialog???
  206. IEventQueue eq = activeClient.Scene.RequestModuleInterface<IEventQueue>();
  207. eq.ChatterboxInvitation(
  208. groupID
  209. , groupInfo.GroupName
  210. , new UUID(msg.fromAgentID)
  211. , msg.message, new UUID(msg.toAgentID)
  212. , msg.fromAgentName
  213. , msg.dialog
  214. , msg.timestamp
  215. , msg.offline == 1
  216. , (int)msg.ParentEstateID
  217. , msg.Position
  218. , 1
  219. , new UUID(msg.imSessionID)
  220. , msg.fromGroup
  221. , Utils.StringToBytes(groupInfo.GroupName)
  222. );
  223. eq.ChatterBoxSessionAgentListUpdates(
  224. new UUID(groupID)
  225. , new UUID(msg.fromAgentID)
  226. , new UUID(msg.toAgentID)
  227. , false //canVoiceChat
  228. , false //isModerator
  229. , false //text mute
  230. );
  231. }
  232. }
  233. }
  234. else if (!m_agentsDroppedSession.ContainsKey(msg.toAgentID))
  235. {
  236. // User hasn't dropped, so they're in the session,
  237. // maybe we should deliver it.
  238. IClientAPI client = GetActiveClient(new UUID(msg.toAgentID));
  239. if (client != null)
  240. {
  241. // Deliver locally, directly
  242. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Delivering to {0} locally", client.Name);
  243. client.SendInstantMessage(msg);
  244. }
  245. else
  246. {
  247. m_log.WarnFormat("[GROUPS-MESSAGING]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID);
  248. }
  249. }
  250. break;
  251. default:
  252. m_log.WarnFormat("[GROUPS-MESSAGING]: I don't know how to proccess a {0} message.", ((InstantMessageDialog)msg.dialog).ToString());
  253. break;
  254. }
  255. }
  256. #endregion
  257. #region ClientEvents
  258. private void RemoveAgentFromGroupSession(Guid agentID, Guid sessionID)
  259. {
  260. if (m_agentsInGroupSession.ContainsKey(sessionID))
  261. {
  262. // If in session remove
  263. if (m_agentsInGroupSession[sessionID].Contains(agentID))
  264. {
  265. m_agentsInGroupSession[sessionID].Remove(agentID);
  266. }
  267. // If not in dropped list, add
  268. if (!m_agentsDroppedSession[sessionID].Contains(agentID))
  269. {
  270. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Dropped {1} from session {0}", sessionID, agentID);
  271. m_agentsDroppedSession[sessionID].Add(agentID);
  272. }
  273. }
  274. }
  275. private void AddAgentToGroupSession(Guid agentID, Guid sessionID)
  276. {
  277. // Add Session Status if it doesn't exist for this session
  278. CreateGroupSessionTracking(sessionID);
  279. // If nessesary, remove from dropped list
  280. if (m_agentsDroppedSession[sessionID].Contains(agentID))
  281. {
  282. m_agentsDroppedSession[sessionID].Remove(agentID);
  283. }
  284. // If nessesary, add to in session list
  285. if (!m_agentsInGroupSession[sessionID].Contains(agentID))
  286. {
  287. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Added {1} to session {0}", sessionID, agentID);
  288. m_agentsInGroupSession[sessionID].Add(agentID);
  289. }
  290. }
  291. private void CreateGroupSessionTracking(Guid sessionID)
  292. {
  293. if (!m_agentsInGroupSession.ContainsKey(sessionID))
  294. {
  295. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Creating session tracking for : {0}", sessionID);
  296. m_agentsInGroupSession.Add(sessionID, new List<Guid>());
  297. m_agentsDroppedSession.Add(sessionID, new List<Guid>());
  298. }
  299. }
  300. private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im)
  301. {
  302. if (m_debugEnabled)
  303. {
  304. m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  305. DebugGridInstantMessage(im);
  306. }
  307. // Start group IM session
  308. if ((im.dialog == (byte)InstantMessageDialog.SessionGroupStart))
  309. {
  310. UUID groupID = new UUID(im.toAgentID);
  311. GroupRecord groupInfo = m_groupsModule.GetGroupRecord(groupID);
  312. if (groupInfo != null)
  313. {
  314. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Start Group Session for {0}", groupInfo.GroupName);
  315. AddAgentToGroupSession(im.fromAgentID, im.imSessionID);
  316. ChatterBoxSessionStartReplyViaCaps(remoteClient, groupInfo.GroupName, groupID);
  317. IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
  318. queue.ChatterBoxSessionAgentListUpdates(
  319. new UUID(groupID)
  320. , new UUID(im.fromAgentID)
  321. , new UUID(im.toAgentID)
  322. , false //canVoiceChat
  323. , false //isModerator
  324. , false //text mute
  325. );
  326. }
  327. }
  328. // Send a message from locally connected client to a group
  329. if ((im.dialog == (byte)InstantMessageDialog.SessionSend))
  330. {
  331. UUID groupID = new UUID(im.toAgentID);
  332. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Send message to session for group {0} with session ID {1}", groupID, im.imSessionID.ToString());
  333. SendMessageToGroup(im, groupID);
  334. }
  335. }
  336. #endregion
  337. private void SendMessageToGroup(GridInstantMessage im, UUID groupID)
  338. {
  339. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  340. foreach (GroupMembersData member in m_groupsModule.GroupMembersRequest(null, groupID))
  341. {
  342. if (!m_agentsDroppedSession.ContainsKey(im.imSessionID) || m_agentsDroppedSession[im.imSessionID].Contains(member.AgentID.Guid))
  343. {
  344. // Don't deliver messages to people who have dropped this session
  345. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} has dropped session, not delivering to them", member.AgentID);
  346. continue;
  347. }
  348. // Copy Message
  349. GridInstantMessage msg = new GridInstantMessage();
  350. msg.imSessionID = im.imSessionID;
  351. msg.fromAgentName = im.fromAgentName;
  352. msg.message = im.message;
  353. msg.dialog = im.dialog;
  354. msg.offline = im.offline;
  355. msg.ParentEstateID = im.ParentEstateID;
  356. msg.Position = im.Position;
  357. msg.RegionID = im.RegionID;
  358. msg.binaryBucket = im.binaryBucket;
  359. msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
  360. // Updat Pertinate fields to make it a "group message"
  361. msg.fromAgentID = groupID.Guid;
  362. msg.fromGroup = true;
  363. msg.toAgentID = member.AgentID.Guid;
  364. IClientAPI client = GetActiveClient(member.AgentID);
  365. if (client == null)
  366. {
  367. // If they're not local, forward across the grid
  368. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Delivering to {0} via Grid", member.AgentID);
  369. m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { });
  370. }
  371. else
  372. {
  373. // Deliver locally, directly
  374. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", client.Name);
  375. ProcessMessageFromGroupSession(msg);
  376. }
  377. }
  378. }
  379. void ChatterBoxSessionStartReplyViaCaps(IClientAPI remoteClient, string groupName, UUID groupID)
  380. {
  381. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  382. OSDMap moderatedMap = new OSDMap(4);
  383. moderatedMap.Add("voice", OSD.FromBoolean(false));
  384. OSDMap sessionMap = new OSDMap(4);
  385. sessionMap.Add("moderated_mode", moderatedMap);
  386. sessionMap.Add("session_name", OSD.FromString(groupName));
  387. sessionMap.Add("type", OSD.FromInteger(0));
  388. sessionMap.Add("voice_enabled", OSD.FromBoolean(false));
  389. OSDMap bodyMap = new OSDMap(4);
  390. bodyMap.Add("session_id", OSD.FromUUID(groupID));
  391. bodyMap.Add("temp_session_id", OSD.FromUUID(groupID));
  392. bodyMap.Add("success", OSD.FromBoolean(true));
  393. bodyMap.Add("session_info", sessionMap);
  394. IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
  395. if (queue != null)
  396. {
  397. queue.Enqueue(EventQueueHelper.buildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId);
  398. }
  399. }
  400. private void DebugGridInstantMessage(GridInstantMessage im)
  401. {
  402. // Don't log any normal IMs (privacy!)
  403. if (m_debugEnabled && im.dialog != (byte)InstantMessageDialog.MessageFromAgent)
  404. {
  405. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromGroup({0})", im.fromGroup ? "True" : "False");
  406. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: Dialog({0})", ((InstantMessageDialog)im.dialog).ToString());
  407. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromAgentID({0})", im.fromAgentID.ToString());
  408. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromAgentName({0})", im.fromAgentName.ToString());
  409. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: imSessionID({0})", im.imSessionID.ToString());
  410. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: message({0})", im.message.ToString());
  411. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: offline({0})", im.offline.ToString());
  412. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: toAgentID({0})", im.toAgentID.ToString());
  413. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket"));
  414. }
  415. }
  416. #region Client Tools
  417. /// <summary>
  418. /// Try to find an active IClientAPI reference for agentID giving preference to root connections
  419. /// </summary>
  420. private IClientAPI GetActiveClient(UUID agentID)
  421. {
  422. IClientAPI child = null;
  423. // Try root avatar first
  424. foreach (Scene scene in m_sceneList)
  425. {
  426. if (scene.Entities.ContainsKey(agentID) &&
  427. scene.Entities[agentID] is ScenePresence)
  428. {
  429. ScenePresence user = (ScenePresence)scene.Entities[agentID];
  430. if (!user.IsChildAgent)
  431. {
  432. return user.ControllingClient;
  433. }
  434. else
  435. {
  436. child = user.ControllingClient;
  437. }
  438. }
  439. }
  440. // If we didn't find a root, then just return whichever child we found, or null if none
  441. return child;
  442. }
  443. #endregion
  444. }
  445. }