FriendsModule.cs 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.Reflection;
  31. using OpenMetaverse;
  32. using OpenMetaverse.Packets;
  33. using log4net;
  34. using Nini.Config;
  35. using Nwc.XmlRpc;
  36. using OpenSim.Framework;
  37. using OpenSim.Framework.Communications.Cache;
  38. using OpenSim.Framework.Servers;
  39. using OpenSim.Region.Interfaces;
  40. using OpenSim.Region.Environment.Interfaces;
  41. using OpenSim.Region.Environment.Scenes;
  42. namespace OpenSim.Region.Environment.Modules.Avatar.Friends
  43. {
  44. /*
  45. This module handles adding/removing friends, and the the presence
  46. notification process for login/logoff of friends.
  47. The presence notification works as follows:
  48. - After the user initially connects to a region (so we now have a UDP
  49. connection to work with), this module fetches the friends of user
  50. (those are cached), their on-/offline status, and info about the
  51. region they are in from the MessageServer.
  52. - (*) It then informs the user about the on-/offline status of her friends.
  53. - It then informs all online friends currently on this region-server about
  54. user's new online status (this will save some network traffic, as local
  55. messages don't have to be transferred inter-region, and it will be all
  56. that has to be done in Standalone Mode).
  57. - For the rest of the online friends (those not on this region-server),
  58. this module uses the provided region-information to map users to
  59. regions, and sends one notification to every region containing the
  60. friends to inform on that server.
  61. - The region-server will handle that in the following way:
  62. - If it finds the friend, it informs her about the user being online.
  63. - If it doesn't find the friend (maybe she TPed away in the meantime),
  64. it stores that information.
  65. - After it processed all friends, it returns the list of friends it
  66. couldn't find.
  67. - If this list isn't empty, the FriendsModule re-requests information
  68. about those online friends that have been missed and starts at (*)
  69. again until all friends have been found, or until it tried 3 times
  70. (to prevent endless loops due to some uncaught error).
  71. NOTE: Online/Offline notifications don't need to be sent on region change.
  72. We implement two XMLRpc handlers here, handling all the inter-region things
  73. we have to handle:
  74. - On-/Offline-Notifications (bulk)
  75. - Terminate Friendship messages (single)
  76. */
  77. public class FriendsModule : IRegionModule, IFriendsModule
  78. {
  79. private class Transaction
  80. {
  81. public UUID agentID;
  82. public string agentName;
  83. public uint count;
  84. public Transaction(UUID agentID, string agentName)
  85. {
  86. this.agentID = agentID;
  87. this.agentName = agentName;
  88. this.count = 1;
  89. }
  90. }
  91. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  92. private Cache m_friendLists = new Cache(CacheFlags.AllowUpdate);
  93. private Dictionary<UUID, ulong> m_rootAgents = new Dictionary<UUID, ulong>();
  94. private Dictionary<UUID, UUID> m_pendingCallingcardRequests = new Dictionary<UUID,UUID>();
  95. private Scene m_initialScene; // saves a lookup if we don't have a specific scene
  96. private Dictionary<ulong, Scene> m_scenes = new Dictionary<ulong,Scene>();
  97. private IMessageTransferModule m_TransferModule = null;
  98. #region IRegionModule Members
  99. public void Initialise(Scene scene, IConfigSource config)
  100. {
  101. lock (m_scenes)
  102. {
  103. if (m_scenes.Count == 0)
  104. {
  105. scene.AddXmlRPCHandler("presence_update_bulk", processPresenceUpdateBulk);
  106. scene.AddXmlRPCHandler("terminate_friend", processTerminateFriend);
  107. m_friendLists.DefaultTTL = new TimeSpan(1, 0, 0); // store entries for one hour max
  108. m_initialScene = scene;
  109. }
  110. if (!m_scenes.ContainsKey(scene.RegionInfo.RegionHandle))
  111. m_scenes[scene.RegionInfo.RegionHandle] = scene;
  112. }
  113. scene.RegisterModuleInterface<IFriendsModule>(this);
  114. scene.EventManager.OnNewClient += OnNewClient;
  115. scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
  116. scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel;
  117. scene.EventManager.OnMakeChildAgent += MakeChildAgent;
  118. scene.EventManager.OnClientClosed += ClientClosed;
  119. }
  120. public void PostInitialise()
  121. {
  122. if (m_scenes.Count > 0)
  123. {
  124. m_TransferModule = m_initialScene.RequestModuleInterface<IMessageTransferModule>();
  125. }
  126. if (m_TransferModule == null)
  127. m_log.Error("[FRIENDS]: Unable to find a message transfer module, friendship offers will not work");
  128. }
  129. public void Close()
  130. {
  131. }
  132. public string Name
  133. {
  134. get { return "FriendsModule"; }
  135. }
  136. public bool IsSharedModule
  137. {
  138. get { return true; }
  139. }
  140. #endregion
  141. /// <summary>
  142. /// Receive presence information changes about clients in other regions.
  143. /// </summary>
  144. /// <param name="req"></param>
  145. /// <returns></returns>
  146. public XmlRpcResponse processPresenceUpdateBulk(XmlRpcRequest req)
  147. {
  148. Hashtable requestData = (Hashtable)req.Params[0];
  149. List<UUID> friendsNotHere = new List<UUID>();
  150. // this is called with the expectation that all the friends in the request are on this region-server.
  151. // But as some time passed since we checked (on the other region-server, via the MessagingServer),
  152. // some of the friends might have teleported away.
  153. // Actually, even now, between this line and the sending below, some people could TP away. So,
  154. // we'll have to lock the m_rootAgents list for the duration to prevent/delay that.
  155. lock (m_rootAgents)
  156. {
  157. List<ScenePresence> friendsHere = new List<ScenePresence>();
  158. try
  159. {
  160. UUID agentID = new UUID((string)requestData["agentID"]);
  161. bool agentOnline = (bool)requestData["agentOnline"];
  162. int count = (int)requestData["friendCount"];
  163. for (int i = 0; i < count; ++i)
  164. {
  165. UUID uuid;
  166. if (UUID.TryParse((string)requestData["friendID_" + i], out uuid))
  167. {
  168. if (m_rootAgents.ContainsKey(uuid)) friendsHere.Add(GetRootPresenceFromAgentID(uuid));
  169. else friendsNotHere.Add(uuid);
  170. }
  171. }
  172. // now send, as long as they are still here...
  173. UUID[] agentUUID = new UUID[] { agentID };
  174. if (agentOnline)
  175. {
  176. foreach (ScenePresence agent in friendsHere)
  177. {
  178. agent.ControllingClient.SendAgentOnline(agentUUID);
  179. }
  180. }
  181. else
  182. {
  183. foreach (ScenePresence agent in friendsHere)
  184. {
  185. agent.ControllingClient.SendAgentOffline(agentUUID);
  186. }
  187. }
  188. }
  189. catch(Exception e)
  190. {
  191. m_log.Warn("[FRIENDS]: Got exception while parsing presence_update_bulk request:", e);
  192. }
  193. }
  194. // no need to lock anymore; if TPs happen now, worst case is that we have an additional agent in this region,
  195. // which should be caught on the next iteration...
  196. Hashtable result = new Hashtable();
  197. int idx = 0;
  198. foreach (UUID uuid in friendsNotHere)
  199. {
  200. result["friendID_" + idx++] = uuid.ToString();
  201. }
  202. result["friendCount"] = idx;
  203. XmlRpcResponse response = new XmlRpcResponse();
  204. response.Value = result;
  205. return response;
  206. }
  207. public XmlRpcResponse processTerminateFriend(XmlRpcRequest req)
  208. {
  209. Hashtable requestData = (Hashtable)req.Params[0];
  210. bool success = false;
  211. UUID agentID;
  212. UUID friendID;
  213. if (requestData.ContainsKey("agentID") && UUID.TryParse((string)requestData["agentID"], out agentID) &&
  214. requestData.ContainsKey("friendID") && UUID.TryParse((string)requestData["friendID"], out friendID))
  215. {
  216. // try to find it and if it is there, prevent it to vanish before we sent the message
  217. lock (m_rootAgents)
  218. {
  219. if (m_rootAgents.ContainsKey(agentID))
  220. {
  221. m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", friendID, agentID);
  222. GetRootPresenceFromAgentID(agentID).ControllingClient.SendTerminateFriend(friendID);
  223. success = true;
  224. }
  225. }
  226. }
  227. // return whether we were successful
  228. Hashtable result = new Hashtable();
  229. result["success"] = success;
  230. XmlRpcResponse response = new XmlRpcResponse();
  231. response.Value = result;
  232. return response;
  233. }
  234. private void OnNewClient(IClientAPI client)
  235. {
  236. // All friends establishment protocol goes over instant message
  237. // There's no way to send a message from the sim
  238. // to a user to 'add a friend' without causing dialog box spam
  239. // Subscribe to instant messages
  240. client.OnInstantMessage += OnInstantMessage;
  241. // Friend list management
  242. client.OnApproveFriendRequest += OnApproveFriendRequest;
  243. client.OnDenyFriendRequest += OnDenyFriendRequest;
  244. client.OnTerminateFriendship += OnTerminateFriendship;
  245. // ... calling card handling...
  246. client.OnOfferCallingCard += OnOfferCallingCard;
  247. client.OnAcceptCallingCard += OnAcceptCallingCard;
  248. client.OnDeclineCallingCard += OnDeclineCallingCard;
  249. // we need this one exactly once per agent session (see comments in the handler below)
  250. client.OnEconomyDataRequest += OnEconomyDataRequest;
  251. // if it leaves, we want to know, too
  252. client.OnLogout += OnLogout;
  253. }
  254. private void ClientClosed(UUID AgentId)
  255. {
  256. // agent's client was closed. As we handle logout in OnLogout, this here has only to handle
  257. // TPing away (root agent is closed) or TPing/crossing in a region far enough away (client
  258. // agent is closed).
  259. // NOTE: In general, this doesn't mean that the agent logged out, just that it isn't around
  260. // in one of the regions here anymore.
  261. lock (m_rootAgents)
  262. {
  263. if (m_rootAgents.ContainsKey(AgentId))
  264. {
  265. m_rootAgents.Remove(AgentId);
  266. m_log.Info("[FRIEND]: Removing " + AgentId + ". Agent was closed.");
  267. }
  268. }
  269. }
  270. private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID)
  271. {
  272. lock (m_rootAgents)
  273. {
  274. m_rootAgents[avatar.UUID] = avatar.RegionHandle;
  275. m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + ".");
  276. // Claim User! my user! Mine mine mine!
  277. }
  278. }
  279. private void MakeChildAgent(ScenePresence avatar)
  280. {
  281. lock (m_rootAgents)
  282. {
  283. if (m_rootAgents.ContainsKey(avatar.UUID))
  284. {
  285. // only delete if the region matches. As this is a shared module, the avatar could be
  286. // root agent in another region on this server.
  287. if (m_rootAgents[avatar.UUID] == avatar.RegionHandle)
  288. {
  289. m_rootAgents.Remove(avatar.UUID);
  290. m_log.Info("[FRIEND]: Removing " + avatar.Firstname + " " + avatar.Lastname + " as a root agent");
  291. }
  292. }
  293. }
  294. }
  295. private ScenePresence GetRootPresenceFromAgentID(UUID AgentID)
  296. {
  297. ScenePresence returnAgent = null;
  298. lock (m_scenes)
  299. {
  300. ScenePresence queryagent = null;
  301. foreach (Scene scene in m_scenes.Values)
  302. {
  303. queryagent = scene.GetScenePresence(AgentID);
  304. if (queryagent != null)
  305. {
  306. if (!queryagent.IsChildAgent)
  307. {
  308. returnAgent = queryagent;
  309. break;
  310. }
  311. }
  312. }
  313. }
  314. return returnAgent;
  315. }
  316. private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID)
  317. {
  318. ScenePresence returnAgent = null;
  319. lock (m_scenes)
  320. {
  321. ScenePresence queryagent = null;
  322. foreach (Scene scene in m_scenes.Values)
  323. {
  324. queryagent = scene.GetScenePresence(AgentID);
  325. if (queryagent != null)
  326. {
  327. returnAgent = queryagent;
  328. break;
  329. }
  330. }
  331. }
  332. return returnAgent;
  333. }
  334. public void OfferFriendship(UUID fromUserId, IClientAPI toUserClient, string offerMessage)
  335. {
  336. CachedUserInfo userInfo = m_initialScene.CommsManager.UserProfileCacheService.GetUserDetails(fromUserId);
  337. if (userInfo != null)
  338. {
  339. GridInstantMessage msg = new GridInstantMessage(
  340. toUserClient.Scene, fromUserId, userInfo.UserProfile.Name, toUserClient.AgentId,
  341. (byte)InstantMessageDialog.FriendshipOffered, offerMessage, false, Vector3.Zero);
  342. FriendshipOffered(msg);
  343. }
  344. else
  345. {
  346. m_log.ErrorFormat("[FRIENDS]: No user found for id {0} in OfferFriendship()", fromUserId);
  347. }
  348. }
  349. #region FriendRequestHandling
  350. private void OnInstantMessage(IClientAPI client, GridInstantMessage im)
  351. {
  352. // Friend Requests go by Instant Message.. using the dialog param
  353. // https://wiki.secondlife.com/wiki/ImprovedInstantMessage
  354. if (im.dialog == (byte)InstantMessageDialog.FriendshipOffered) // 38
  355. {
  356. FriendshipOffered(im);
  357. }
  358. else if (im.dialog == (byte)InstantMessageDialog.FriendshipAccepted) // 39
  359. {
  360. FriendshipAccepted(client, im);
  361. }
  362. else if (im.dialog == (byte)InstantMessageDialog.FriendshipDeclined) // 40
  363. {
  364. FriendshipDeclined(client, im);
  365. }
  366. }
  367. /// <summary>
  368. /// Invoked when a user offers a friendship.
  369. /// </summary>
  370. /// May not currently be used - see OnApproveFriendRequest() instead
  371. /// <param name="im"></param>
  372. /// <param name="client"></param>
  373. private void FriendshipOffered(GridInstantMessage im)
  374. {
  375. // this is triggered by the initiating agent:
  376. // A local agent offers friendship to some possibly remote friend.
  377. // A IM is triggered, processed here and sent to the friend (possibly in a remote region).
  378. // some properties are misused here:
  379. // fromAgentName is the *destination* name (the friend we offer friendship to)
  380. m_log.DebugFormat("[FRIEND]: Offer(38) - From: {0}, FromName: {1} To: {2}, Session: {3}, Message: {4}, Offline {5}",
  381. im.fromAgentID, im.fromAgentName, im.toAgentID, im.imSessionID, im.message, im.offline);
  382. // 1.20 protocol sends an UUID in the message field, instead of the friendship offer text.
  383. // For interoperability, we have to clear that
  384. if (Util.isUUID(im.message)) im.message = "";
  385. // be sneeky and use the initiator-UUID as transactionID. This means we can be stateless.
  386. // we have to look up the agent name on friendship-approval, though.
  387. im.imSessionID = im.fromAgentID;
  388. if (m_TransferModule != null)
  389. {
  390. // Send it to whoever is the destination.
  391. // If new friend is local, it will send an IM to the viewer.
  392. // If new friend is remote, it will cause a OnGridInstantMessage on the remote server
  393. m_TransferModule.SendInstantMessage(im,
  394. delegate(bool success)
  395. {
  396. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  397. }
  398. );
  399. }
  400. }
  401. /// <summary>
  402. /// Invoked when a user accepts a friendship offer.
  403. /// </summary>
  404. /// <param name="im"></param>
  405. /// <param name="client"></param>
  406. private void FriendshipAccepted(IClientAPI client, GridInstantMessage im)
  407. {
  408. m_log.DebugFormat("[FRIEND]: 39 - from client {0}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})",
  409. client.AgentId, im.fromAgentID, im.fromAgentName, im.imSessionID, im.toAgentID, im.message, im.dialog);
  410. }
  411. /// <summary>
  412. /// Invoked when a user declines a friendship offer.
  413. /// </summary>
  414. /// May not currently be used - see OnDenyFriendRequest() instead
  415. /// <param name="im"></param>
  416. /// <param name="client"></param>
  417. private void FriendshipDeclined(IClientAPI client, GridInstantMessage im)
  418. {
  419. UUID fromAgentID = new UUID(im.fromAgentID);
  420. UUID toAgentID = new UUID(im.toAgentID);
  421. // declining the friendship offer causes a type 40 IM being sent to the (possibly remote) initiator
  422. // toAgentID is initiator, fromAgentID declined friendship
  423. m_log.DebugFormat("[FRIEND]: 40 - from client {0}, agent {1} {2}, imsession {3} to {4}: {5} (dialog {6})",
  424. client != null ? client.AgentId.ToString() : "<null>",
  425. fromAgentID, im.fromAgentName, im.imSessionID, im.toAgentID, im.message, im.dialog);
  426. // Send the decline to whoever is the destination.
  427. GridInstantMessage msg = new GridInstantMessage(client.Scene, fromAgentID, client.Name, toAgentID,
  428. im.dialog, im.message, im.offline != 0, im.Position);
  429. // If new friend is local, it will send an IM to the viewer.
  430. // If new friend is remote, it will cause a OnGridInstantMessage on the remote server
  431. m_TransferModule.SendInstantMessage(msg,
  432. delegate(bool success) {
  433. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  434. }
  435. );
  436. }
  437. private void OnGridInstantMessage(GridInstantMessage msg)
  438. {
  439. // This event won't be raised unless we have that agent,
  440. // so we can depend on the above not trying to send
  441. // via grid again
  442. m_log.DebugFormat("[FRIEND]: Got GridIM from {0}, to {1}, imSession {2}, message {3}, dialog {4}",
  443. msg.fromAgentID, msg.toAgentID, msg.imSessionID, msg.message, msg.dialog);
  444. if (msg.dialog == (byte)InstantMessageDialog.FriendshipOffered ||
  445. msg.dialog == (byte)InstantMessageDialog.FriendshipAccepted ||
  446. msg.dialog == (byte)InstantMessageDialog.FriendshipDeclined)
  447. {
  448. // this should succeed as we *know* the root agent is here.
  449. m_TransferModule.SendInstantMessage(msg,
  450. delegate(bool success) {
  451. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  452. }
  453. );
  454. }
  455. if (msg.dialog == (byte)InstantMessageDialog.FriendshipAccepted)
  456. {
  457. // for accept friendship, we have to do a bit more
  458. ApproveFriendship(new UUID(msg.fromAgentID), new UUID(msg.toAgentID), msg.fromAgentName);
  459. }
  460. }
  461. private void ApproveFriendship(UUID fromAgentID, UUID toAgentID, string fromName)
  462. {
  463. m_log.DebugFormat("[FRIEND]: Approve friendship from {0} (ID: {1}) to {2}",
  464. fromAgentID, fromName, toAgentID);
  465. // a new friend was added in the initiator's and friend's data, so the cache entries are wrong now.
  466. lock (m_friendLists)
  467. {
  468. m_friendLists.Invalidate(fromAgentID);
  469. m_friendLists.Invalidate(toAgentID);
  470. }
  471. // now send presence update and add a calling card for the new friend
  472. ScenePresence initiator = GetAnyPresenceFromAgentID(toAgentID);
  473. if (initiator == null)
  474. {
  475. // quite wrong. Shouldn't happen.
  476. m_log.WarnFormat("[FRIEND]: Coudn't find initiator of friend request {0}", toAgentID);
  477. return;
  478. }
  479. m_log.DebugFormat("[FRIEND]: Tell {0} that {1} is online",
  480. initiator.Name, fromName);
  481. // tell initiator that friend is online
  482. initiator.ControllingClient.SendAgentOnline(new UUID[] { fromAgentID });
  483. // find the folder for the friend...
  484. InventoryFolderImpl folder =
  485. initiator.Scene.CommsManager.UserProfileCacheService.GetUserDetails(toAgentID).FindFolderForType((int)InventoryType.CallingCard);
  486. if (folder != null)
  487. {
  488. // ... and add the calling card
  489. CreateCallingCard(initiator.ControllingClient, fromAgentID, folder.ID, fromName);
  490. }
  491. }
  492. private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List<UUID> callingCardFolders)
  493. {
  494. m_log.DebugFormat("[FRIEND]: Got approve friendship from {0} {1}, agentID {2}, tid {3}",
  495. client.Name, client.AgentId, agentID, friendID);
  496. // store the new friend persistently for both avatars
  497. m_initialScene.StoreAddFriendship(friendID, agentID, (uint) FriendRights.CanSeeOnline);
  498. // The cache entries aren't valid anymore either, as we just added a friend to both sides.
  499. lock (m_friendLists)
  500. {
  501. m_friendLists.Invalidate(agentID);
  502. m_friendLists.Invalidate(friendID);
  503. }
  504. // if it's a local friend, we don't have to do the lookup
  505. ScenePresence friendPresence = GetAnyPresenceFromAgentID(friendID);
  506. if (friendPresence != null)
  507. {
  508. m_log.Debug("[FRIEND]: Local agent detected.");
  509. // create calling card
  510. CreateCallingCard(client, friendID, callingCardFolders[0], friendPresence.Name);
  511. // local message means OnGridInstantMessage won't be triggered, so do the work here.
  512. friendPresence.ControllingClient.SendInstantMessage(agentID, agentID.ToString(), friendID, client.Name,
  513. (byte)InstantMessageDialog.FriendshipAccepted,
  514. (uint)Util.UnixTimeSinceEpoch());
  515. ApproveFriendship(agentID, friendID, client.Name);
  516. }
  517. else
  518. {
  519. m_log.Debug("[FRIEND]: Remote agent detected.");
  520. // fetch the friend's name for the calling card.
  521. CachedUserInfo info = m_initialScene.CommsManager.UserProfileCacheService.GetUserDetails(friendID);
  522. // create calling card
  523. CreateCallingCard(client, friendID, callingCardFolders[0],
  524. info.UserProfile.FirstName + " " + info.UserProfile.SurName);
  525. // Compose (remote) response to friend.
  526. GridInstantMessage msg = new GridInstantMessage(client.Scene, agentID, client.Name, friendID,
  527. (byte)InstantMessageDialog.FriendshipAccepted,
  528. agentID.ToString(), false, Vector3.Zero);
  529. if (m_TransferModule != null)
  530. {
  531. m_TransferModule.SendInstantMessage(msg,
  532. delegate(bool success) {
  533. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  534. }
  535. );
  536. }
  537. }
  538. // tell client that new friend is online
  539. client.SendAgentOnline(new UUID[] { friendID });
  540. }
  541. private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List<UUID> callingCardFolders)
  542. {
  543. m_log.DebugFormat("[FRIEND]: Got deny friendship from {0} {1}, agentID {2}, tid {3}",
  544. client.Name, client.AgentId, agentID, friendID);
  545. // Compose response to other agent.
  546. GridInstantMessage msg = new GridInstantMessage(client.Scene, agentID, client.Name, friendID,
  547. (byte)InstantMessageDialog.FriendshipDeclined,
  548. agentID.ToString(), false, Vector3.Zero);
  549. // send decline to initiator
  550. if (m_TransferModule != null)
  551. {
  552. m_TransferModule.SendInstantMessage(msg,
  553. delegate(bool success) {
  554. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  555. }
  556. );
  557. }
  558. }
  559. private void OnTerminateFriendship(IClientAPI client, UUID agentID, UUID exfriendID)
  560. {
  561. // client.AgentId == agentID!
  562. // this removes the friends from the stored friendlists. After the next login, they will be gone...
  563. m_initialScene.StoreRemoveFriendship(agentID, exfriendID);
  564. // ... now tell the two involved clients that they aren't friends anymore.
  565. // I don't know why we have to tell <agent>, as this was caused by her, but that's how it works in SL...
  566. client.SendTerminateFriend(exfriendID);
  567. // now send the friend, if online
  568. ScenePresence presence = GetAnyPresenceFromAgentID(exfriendID);
  569. if (presence != null)
  570. {
  571. m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", agentID, exfriendID);
  572. presence.ControllingClient.SendTerminateFriend(agentID);
  573. }
  574. else
  575. {
  576. // retry 3 times, in case the agent TPed from the last known region...
  577. for (int retry = 0; retry < 3; ++retry)
  578. {
  579. // wasn't sent, so ex-friend wasn't around on this region-server. Fetch info and try to send
  580. UserAgentData data = m_initialScene.CommsManager.UserService.GetAgentByUUID(exfriendID);
  581. if (!data.AgentOnline)
  582. {
  583. m_log.DebugFormat("[FRIEND]: {0} is offline, so not sending TerminateFriend", exfriendID);
  584. break; // if ex-friend isn't online, we don't need to send
  585. }
  586. m_log.DebugFormat("[FRIEND]: Sending remote terminate friend {0} to agent {1}@{2}",
  587. agentID, exfriendID, data.Handle);
  588. // try to send to foreign region, retry if it fails (friend TPed away, for example)
  589. if (m_initialScene.TriggerTerminateFriend(data.Handle, exfriendID, agentID)) break;
  590. }
  591. }
  592. // clean up cache: FriendList is wrong now...
  593. lock (m_friendLists)
  594. {
  595. m_friendLists.Invalidate(agentID);
  596. m_friendLists.Invalidate(exfriendID);
  597. }
  598. }
  599. #endregion
  600. #region CallingCards
  601. private void OnOfferCallingCard(IClientAPI client, UUID destID, UUID transactionID)
  602. {
  603. m_log.DebugFormat("[CALLING CARD]: got offer from {0} for {1}, transaction {2}",
  604. client.AgentId, destID, transactionID);
  605. // This might be slightly wrong. On a multi-region server, we might get the child-agent instead of the root-agent
  606. // (or the root instead of the child)
  607. ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
  608. if (destAgent == null)
  609. {
  610. client.SendAlertMessage("The person you have offered a card to can't be found anymore.");
  611. return;
  612. }
  613. lock (m_pendingCallingcardRequests)
  614. {
  615. m_pendingCallingcardRequests[transactionID] = client.AgentId;
  616. }
  617. // inform the destination agent about the offer
  618. destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID);
  619. }
  620. private void CreateCallingCard(IClientAPI client, UUID creator, UUID folder, string name)
  621. {
  622. InventoryItemBase item = new InventoryItemBase();
  623. item.AssetID = UUID.Zero;
  624. item.AssetType = (int)AssetType.CallingCard;
  625. item.BasePermissions = (uint)PermissionMask.Copy;
  626. item.CreationDate = Util.UnixTimeSinceEpoch();
  627. item.Creator = creator;
  628. item.CurrentPermissions = item.BasePermissions;
  629. item.Description = "";
  630. item.EveryOnePermissions = (uint)PermissionMask.None;
  631. item.Flags = 0;
  632. item.Folder = folder;
  633. item.GroupID = UUID.Zero;
  634. item.GroupOwned = false;
  635. item.ID = UUID.Random();
  636. item.InvType = (int)InventoryType.CallingCard;
  637. item.Name = name;
  638. item.NextPermissions = item.EveryOnePermissions;
  639. item.Owner = client.AgentId;
  640. item.SalePrice = 10;
  641. item.SaleType = (byte)SaleType.Not;
  642. ((Scene)client.Scene).AddInventoryItem(client, item);
  643. }
  644. private void OnAcceptCallingCard(IClientAPI client, UUID transactionID, UUID folderID)
  645. {
  646. m_log.DebugFormat("[CALLING CARD]: User {0} ({1} {2}) accepted tid {3}, folder {4}",
  647. client.AgentId,
  648. client.FirstName, client.LastName,
  649. transactionID, folderID);
  650. UUID destID;
  651. lock (m_pendingCallingcardRequests)
  652. {
  653. if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID))
  654. {
  655. m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.",
  656. client.Name);
  657. return;
  658. }
  659. // else found pending calling card request with that transaction.
  660. m_pendingCallingcardRequests.Remove(transactionID);
  661. }
  662. ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
  663. // inform sender of the card that destination declined the offer
  664. if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID);
  665. // put a calling card into the inventory of receiver
  666. CreateCallingCard(client, destID, folderID, destAgent.Name);
  667. }
  668. private void OnDeclineCallingCard(IClientAPI client, UUID transactionID)
  669. {
  670. m_log.DebugFormat("[CALLING CARD]: User {0} (ID:{1}) declined card, tid {2}",
  671. client.Name, client.AgentId, transactionID);
  672. UUID destID;
  673. lock (m_pendingCallingcardRequests)
  674. {
  675. if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID))
  676. {
  677. m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.",
  678. client.Name);
  679. return;
  680. }
  681. // else found pending calling card request with that transaction.
  682. m_pendingCallingcardRequests.Remove(transactionID);
  683. }
  684. ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
  685. // inform sender of the card that destination declined the offer
  686. if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID);
  687. }
  688. /// <summary>
  689. /// Send presence information about a client to other clients in both this region and others.
  690. /// </summary>
  691. /// <param name="client"></param>
  692. /// <param name="friendList"></param>
  693. /// <param name="iAmOnline"></param>
  694. private void SendPresenceState(IClientAPI client, List<FriendListItem> friendList, bool iAmOnline)
  695. {
  696. m_log.DebugFormat("[FRIEND]: {0} logged {1}; sending presence updates", client.Name, iAmOnline ? "in" : "out");
  697. if (friendList == null || friendList.Count == 0)
  698. {
  699. m_log.DebugFormat("[FRIEND]: {0} doesn't have friends.", client.Name);
  700. return; // nothing we can do if she doesn't have friends...
  701. }
  702. // collect sets of friendIDs; to send to (online and offline), and to receive from
  703. // TODO: If we ever switch to .NET >= 3, replace those Lists with HashSets.
  704. // I can't believe that we have Dictionaries, but no Sets, considering Java introduced them years ago...
  705. List<UUID> friendIDsToSendTo = new List<UUID>();
  706. List<UUID> candidateFriendIDsToReceive = new List<UUID>();
  707. foreach (FriendListItem item in friendList)
  708. {
  709. if (((item.FriendListOwnerPerms | item.FriendPerms) & (uint)FriendRights.CanSeeOnline) != 0)
  710. {
  711. // friend is allowed to see my presence => add
  712. if ((item.FriendListOwnerPerms & (uint)FriendRights.CanSeeOnline) != 0)
  713. friendIDsToSendTo.Add(item.Friend);
  714. if ((item.FriendPerms & (uint)FriendRights.CanSeeOnline) != 0)
  715. candidateFriendIDsToReceive.Add(item.Friend);
  716. }
  717. }
  718. // we now have a list of "interesting" friends (which we have to find out on-/offline state for),
  719. // friends we want to send our online state to (if *they* are online, too), and
  720. // friends we want to receive online state for (currently unknown whether online or not)
  721. // as this processing might take some time and friends might TP away, we try up to three times to
  722. // reach them. Most of the time, we *will* reach them, and this loop won't loop
  723. int retry = 0;
  724. do
  725. {
  726. // build a list of friends to look up region-information and on-/offline-state for
  727. List<UUID> friendIDsToLookup = new List<UUID>(friendIDsToSendTo);
  728. foreach (UUID uuid in candidateFriendIDsToReceive)
  729. {
  730. if (!friendIDsToLookup.Contains(uuid)) friendIDsToLookup.Add(uuid);
  731. }
  732. m_log.DebugFormat(
  733. "[FRIEND]: {0} to lookup, {1} to send to, {2} candidates to receive from for agent {3}",
  734. friendIDsToLookup.Count, friendIDsToSendTo.Count, candidateFriendIDsToReceive.Count, client.Name);
  735. // we have to fetch FriendRegionInfos, as the (cached) FriendListItems don't
  736. // necessarily contain the correct online state...
  737. Dictionary<UUID, FriendRegionInfo> friendRegions = m_initialScene.GetFriendRegionInfos(friendIDsToLookup);
  738. m_log.DebugFormat(
  739. "[FRIEND]: Found {0} regionInfos for {1} friends of {2}",
  740. friendRegions.Count, friendIDsToLookup.Count, client.Name);
  741. // argument for SendAgentOn/Offline; we shouldn't generate that repeatedly within loops.
  742. UUID[] agentArr = new UUID[] { client.AgentId };
  743. // first, send to friend presence state to me, if I'm online...
  744. if (iAmOnline)
  745. {
  746. List<UUID> friendIDsToReceive = new List<UUID>();
  747. for (int i = candidateFriendIDsToReceive.Count - 1; i >= 0; --i)
  748. {
  749. UUID uuid = candidateFriendIDsToReceive[i];
  750. FriendRegionInfo info;
  751. if (friendRegions.TryGetValue(uuid, out info) && info != null && info.isOnline)
  752. {
  753. friendIDsToReceive.Add(uuid);
  754. }
  755. }
  756. m_log.DebugFormat(
  757. "[FRIEND]: Sending {0} online friends to {1}", friendIDsToReceive.Count, client.Name);
  758. if (friendIDsToReceive.Count > 0)
  759. client.SendAgentOnline(friendIDsToReceive.ToArray());
  760. // clear them for a possible second iteration; we don't have to repeat this
  761. candidateFriendIDsToReceive.Clear();
  762. }
  763. // now, send my presence state to my friends
  764. for (int i = friendIDsToSendTo.Count - 1; i >= 0; --i)
  765. {
  766. UUID uuid = friendIDsToSendTo[i];
  767. FriendRegionInfo info;
  768. if (friendRegions.TryGetValue(uuid, out info) && info != null && info.isOnline)
  769. {
  770. // any client is good enough, root or child...
  771. ScenePresence agent = GetAnyPresenceFromAgentID(uuid);
  772. if (agent != null)
  773. {
  774. m_log.DebugFormat("[FRIEND]: Found local agent {0}", agent.Name);
  775. // friend is online and on this server...
  776. if (iAmOnline) agent.ControllingClient.SendAgentOnline(agentArr);
  777. else agent.ControllingClient.SendAgentOffline(agentArr);
  778. // done, remove it
  779. friendIDsToSendTo.RemoveAt(i);
  780. }
  781. }
  782. else
  783. {
  784. m_log.DebugFormat("[FRIEND]: Friend {0} ({1}) is offline; not sending.", uuid, i);
  785. // friend is offline => no need to try sending
  786. friendIDsToSendTo.RemoveAt(i);
  787. }
  788. }
  789. m_log.DebugFormat("[FRIEND]: Have {0} friends to contact via inter-region comms.", friendIDsToSendTo.Count);
  790. // we now have all the friends left that are online (we think), but not on this region-server
  791. if (friendIDsToSendTo.Count > 0)
  792. {
  793. // sort them into regions
  794. Dictionary<ulong, List<UUID>> friendsInRegion = new Dictionary<ulong,List<UUID>>();
  795. foreach (UUID uuid in friendIDsToSendTo)
  796. {
  797. ulong handle = friendRegions[uuid].regionHandle; // this can't fail as we filtered above already
  798. List<UUID> friends;
  799. if (!friendsInRegion.TryGetValue(handle, out friends))
  800. {
  801. friends = new List<UUID>();
  802. friendsInRegion[handle] = friends;
  803. }
  804. friends.Add(uuid);
  805. }
  806. m_log.DebugFormat("[FRIEND]: Found {0} regions to send to.", friendRegions.Count);
  807. // clear uuids list and collect missed friends in it for the next retry
  808. friendIDsToSendTo.Clear();
  809. // send bulk updates to the region
  810. foreach (KeyValuePair<ulong, List<UUID>> pair in friendsInRegion)
  811. {
  812. m_log.DebugFormat("[FRIEND]: Inform {0} friends in region {1} that user {2} is {3}line",
  813. pair.Value.Count, pair.Key, client.Name, iAmOnline ? "on" : "off");
  814. friendIDsToSendTo.AddRange(m_initialScene.InformFriendsInOtherRegion(client.AgentId, pair.Key, pair.Value, iAmOnline));
  815. }
  816. }
  817. // now we have in friendIDsToSendTo only the agents left that TPed away while we tried to contact them.
  818. // In most cases, it will be empty, and it won't loop here. But sometimes, we have to work harder and try again...
  819. }
  820. while (++retry < 3 && friendIDsToSendTo.Count > 0);
  821. }
  822. private void OnEconomyDataRequest(UUID agentID)
  823. {
  824. // KLUDGE: This is the only way I found to get a message (only) after login was completed and the
  825. // client is connected enough to receive UDP packets).
  826. // This packet seems to be sent only once, just after connection was established to the first
  827. // region after login.
  828. // We use it here to trigger a presence update; the old update-on-login was never be heard by
  829. // the freshly logged in viewer, as it wasn't connected to the region at that time.
  830. // TODO: Feel free to replace this by a better solution if you find one.
  831. // get the agent. This should work every time, as we just got a packet from it
  832. //ScenePresence agent = GetRootPresenceFromAgentID(agentID);
  833. // KLUDGE 2: As this is sent quite early, the avatar isn't here as root agent yet. So, we have to cheat a bit
  834. ScenePresence agent = GetAnyPresenceFromAgentID(agentID);
  835. // just to be paranoid...
  836. if (agent == null)
  837. {
  838. m_log.ErrorFormat("[FRIEND]: Got a packet from agent {0} who can't be found anymore!?", agentID);
  839. return;
  840. }
  841. List<FriendListItem> fl;
  842. lock (m_friendLists)
  843. {
  844. fl = (List<FriendListItem>)m_friendLists.Get(agent.ControllingClient.AgentId,
  845. m_initialScene.GetFriendList);
  846. }
  847. // tell everyone that we are online
  848. SendPresenceState(agent.ControllingClient, fl, true);
  849. }
  850. private void OnLogout(IClientAPI remoteClient)
  851. {
  852. m_log.DebugFormat("[FRIEND]: Client {0} logged out", remoteClient.Name);
  853. List<FriendListItem> fl;
  854. lock (m_friendLists)
  855. {
  856. fl = (List<FriendListItem>)m_friendLists.Get(remoteClient.AgentId,
  857. m_initialScene.GetFriendList);
  858. }
  859. // tell everyone that we are offline
  860. SendPresenceState(remoteClient, fl, false);
  861. }
  862. }
  863. #endregion
  864. }