aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region
diff options
context:
space:
mode:
authorHomer Horwitz2008-11-01 22:09:48 +0000
committerHomer Horwitz2008-11-01 22:09:48 +0000
commit38e8853e5761d09a7e8f580dd277d9b99b834696 (patch)
tree653fe4c9075a03c05a4b5782f7309afa83062e5c /OpenSim/Region
parent* minor: Remove mono compiler warning (diff)
downloadopensim-SC-38e8853e5761d09a7e8f580dd277d9b99b834696.zip
opensim-SC-38e8853e5761d09a7e8f580dd277d9b99b834696.tar.gz
opensim-SC-38e8853e5761d09a7e8f580dd277d9b99b834696.tar.bz2
opensim-SC-38e8853e5761d09a7e8f580dd277d9b99b834696.tar.xz
Megapatch that fixes/adds: friend offer/deny/accept, friendship termination,
on-/offline updates, calling cards for friends. This adds methods in the DB layer and changes the MessagingServer, so a full update (incl. UGAIM) is necessary to get it working. Older regions shouldn't break, nor should older UGAIM break newer regions, but friends/presence will only work with all concerned parts (UGAIM, source region and destination region) at this revision (or later). I added the DB code for MSSQL, too, but couldn't test that. BEWARE: May contain bugs.
Diffstat (limited to 'OpenSim/Region')
-rw-r--r--OpenSim/Region/Application/OpenSimBase.cs3
-rw-r--r--OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs23
-rw-r--r--OpenSim/Region/Communications/Local/CommunicationsLocal.cs4
-rw-r--r--OpenSim/Region/Communications/Local/LocalBackEndServices.cs24
-rw-r--r--OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs4
-rw-r--r--OpenSim/Region/Communications/OGS1/OGS1GridServices.cs80
-rw-r--r--OpenSim/Region/Communications/OGS1/OGS1UserServices.cs60
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs1137
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs10
-rw-r--r--OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs4
-rw-r--r--OpenSim/Region/Environment/Scenes/Scene.cs14
-rw-r--r--OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs4
12 files changed, 935 insertions, 432 deletions
diff --git a/OpenSim/Region/Application/OpenSimBase.cs b/OpenSim/Region/Application/OpenSimBase.cs
index 4f1eed8..2bb4b57 100644
--- a/OpenSim/Region/Application/OpenSimBase.cs
+++ b/OpenSim/Region/Application/OpenSimBase.cs
@@ -364,7 +364,8 @@ namespace OpenSim
364 m_commsManager 364 m_commsManager
365 = new CommunicationsLocal( 365 = new CommunicationsLocal(
366 m_networkServersInfo, m_httpServer, m_assetCache, userService, userService, 366 m_networkServersInfo, m_httpServer, m_assetCache, userService, userService,
367 inventoryService, backendService, backendService, libraryRootFolder, m_dumpAssetsToFile); 367 inventoryService, backendService, backendService, userService,
368 libraryRootFolder, m_dumpAssetsToFile);
368 369
369 // set up XMLRPC handler for client's initial login request message 370 // set up XMLRPC handler for client's initial login request message
370 m_httpServer.AddXmlRPCHandler("login_to_simulator", loginService.XmlRpcLoginMethod); 371 m_httpServer.AddXmlRPCHandler("login_to_simulator", loginService.XmlRpcLoginMethod);
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
index 5b3bc98..363b09c 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
@@ -4129,7 +4129,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP
4129 handlerApproveFriendRequest(this, agentID, transactionID, callingCardFolders); 4129 handlerApproveFriendRequest(this, agentID, transactionID, callingCardFolders);
4130 } 4130 }
4131 break; 4131 break;
4132 4132
4133 case PacketType.DeclineFriendship:
4134 DeclineFriendshipPacket dfriendpack = (DeclineFriendshipPacket)Pack;
4135
4136 if (OnDenyFriendRequest != null)
4137 {
4138 OnDenyFriendRequest(this,
4139 dfriendpack.AgentData.AgentID,
4140 dfriendpack.TransactionBlock.TransactionID,
4141 null);
4142 }
4143 break;
4144
4133 case PacketType.TerminateFriendship: 4145 case PacketType.TerminateFriendship:
4134 TerminateFriendshipPacket tfriendpack = (TerminateFriendshipPacket)Pack; 4146 TerminateFriendshipPacket tfriendpack = (TerminateFriendshipPacket)Pack;
4135 UUID listOwnerAgentID = tfriendpack.AgentData.AgentID; 4147 UUID listOwnerAgentID = tfriendpack.AgentData.AgentID;
@@ -7648,6 +7660,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP
7648 OutPacket(p, ThrottleOutPacketType.Task); 7660 OutPacket(p, ThrottleOutPacketType.Task);
7649 } 7661 }
7650 7662
7663 public void SendTerminateFriend(UUID exFriendID)
7664 {
7665 TerminateFriendshipPacket p = (TerminateFriendshipPacket)PacketPool.Instance.GetPacket(PacketType.TerminateFriendship);
7666 p.AgentData.AgentID = AgentId;
7667 p.AgentData.SessionID = SessionId;
7668 p.ExBlock.OtherID = exFriendID;
7669 OutPacket(p, ThrottleOutPacketType.Task);
7670 }
7671
7651 public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) 7672 public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data)
7652 { 7673 {
7653 int i; 7674 int i;
diff --git a/OpenSim/Region/Communications/Local/CommunicationsLocal.cs b/OpenSim/Region/Communications/Local/CommunicationsLocal.cs
index 71c44e5..48a635a 100644
--- a/OpenSim/Region/Communications/Local/CommunicationsLocal.cs
+++ b/OpenSim/Region/Communications/Local/CommunicationsLocal.cs
@@ -42,7 +42,8 @@ namespace OpenSim.Region.Communications.Local
42 IUserServiceAdmin userServiceAdmin, 42 IUserServiceAdmin userServiceAdmin,
43 LocalInventoryService inventoryService, 43 LocalInventoryService inventoryService,
44 IInterRegionCommunications interRegionService, 44 IInterRegionCommunications interRegionService,
45 IGridServices gridService, LibraryRootFolder libraryRootFolder, bool dumpAssetsToFile) 45 IGridServices gridService, IMessagingService messageService,
46 LibraryRootFolder libraryRootFolder, bool dumpAssetsToFile)
46 : base(serversInfo, httpServer, assetCache, dumpAssetsToFile, libraryRootFolder) 47 : base(serversInfo, httpServer, assetCache, dumpAssetsToFile, libraryRootFolder)
47 { 48 {
48 AddInventoryService(inventoryService); 49 AddInventoryService(inventoryService);
@@ -53,6 +54,7 @@ namespace OpenSim.Region.Communications.Local
53 m_avatarService = (IAvatarService)userService; 54 m_avatarService = (IAvatarService)userService;
54 m_gridService = gridService; 55 m_gridService = gridService;
55 m_interRegion = interRegionService; 56 m_interRegion = interRegionService;
57 m_messageService = messageService;
56 } 58 }
57 } 59 }
58} 60}
diff --git a/OpenSim/Region/Communications/Local/LocalBackEndServices.cs b/OpenSim/Region/Communications/Local/LocalBackEndServices.cs
index 9034e49..4980378 100644
--- a/OpenSim/Region/Communications/Local/LocalBackEndServices.cs
+++ b/OpenSim/Region/Communications/Local/LocalBackEndServices.cs
@@ -39,7 +39,7 @@ namespace OpenSim.Region.Communications.Local
39 public class LocalBackEndServices : IGridServices, IInterRegionCommunications 39 public class LocalBackEndServices : IGridServices, IInterRegionCommunications
40 { 40 {
41 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 41 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
42 42
43 protected Dictionary<ulong, RegionInfo> m_regions = new Dictionary<ulong, RegionInfo>(); 43 protected Dictionary<ulong, RegionInfo> m_regions = new Dictionary<ulong, RegionInfo>();
44 44
45 protected Dictionary<ulong, RegionCommsListener> m_regionListeners = 45 protected Dictionary<ulong, RegionCommsListener> m_regionListeners =
@@ -50,7 +50,7 @@ namespace OpenSim.Region.Communications.Local
50 private Dictionary<string, string> m_queuedGridSettings = new Dictionary<string, string>(); 50 private Dictionary<string, string> m_queuedGridSettings = new Dictionary<string, string>();
51 51
52 public string _gdebugRegionName = String.Empty; 52 public string _gdebugRegionName = String.Empty;
53 53
54 public bool RegionLoginsEnabled 54 public bool RegionLoginsEnabled
55 { 55 {
56 get { return m_regionLoginsEnabled; } 56 get { return m_regionLoginsEnabled; }
@@ -523,11 +523,27 @@ namespace OpenSim.Region.Communications.Local
523 if (info.RegionName.StartsWith(name)) 523 if (info.RegionName.StartsWith(name))
524 { 524 {
525 regions.Add(info); 525 regions.Add(info);
526 if (regions.Count >= maxNumber) break; 526 if (regions.Count >= maxNumber) break;
527 } 527 }
528 } 528 }
529 529
530 return regions; 530 return regions;
531 } 531 }
532
533 public List<UUID> InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List<UUID> friends, bool online)
534 {
535 // if we get to here, something is wrong: We are in standalone mode, but have users that are not on our server?
536 m_log.WarnFormat("[INTERREGION STANDALONE] Did find {0} users on a region not on our server: {1} ???",
537 friends.Count, destRegionHandle);
538 return new List<UUID>();
539 }
540
541 public bool TriggerTerminateFriend (ulong regionHandle, UUID agentID, UUID exFriendID)
542 {
543 // if we get to here, something is wrong: We are in standalone mode, but have users that are not on our server?
544 m_log.WarnFormat("[INTERREGION STANDALONE] Did find user {0} on a region not on our server: {1} ???",
545 agentID, regionHandle);
546 return true;
547 }
532 } 548 }
533} 549}
diff --git a/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs b/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs
index d76f076..7f6fbc8 100644
--- a/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs
+++ b/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs
@@ -56,7 +56,9 @@ namespace OpenSim.Region.Communications.OGS1
56 m_defaultInventoryHost = invService.Host; 56 m_defaultInventoryHost = invService.Host;
57 } 57 }
58 58
59 m_userService = new OGS1UserServices(this); 59 OGS1UserServices userServices = new OGS1UserServices(this);
60 m_userService = userServices;
61 m_messageService = userServices;
60 m_avatarService = (IAvatarService)m_userService; 62 m_avatarService = (IAvatarService)m_userService;
61 } 63 }
62 64
diff --git a/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs b/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs
index 32628f8..d9e0370 100644
--- a/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs
+++ b/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs
@@ -1785,5 +1785,83 @@ namespace OpenSim.Region.Communications.OGS1
1785 return null; 1785 return null;
1786 } 1786 }
1787 } 1787 }
1788
1789 public List<UUID> InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List<UUID> friends, bool online)
1790 {
1791 List<UUID> tpdAway = new List<UUID>();
1792
1793 // destRegionHandle is a region on another server
1794 RegionInfo info = RequestNeighbourInfo(destRegionHandle);
1795 if (info != null)
1796 {
1797 string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk";
1798
1799 Hashtable reqParams = new Hashtable();
1800 reqParams["agentID"] = agentId.ToString();
1801 reqParams["agentOnline"] = online;
1802 int count = 0;
1803 foreach (UUID uuid in friends)
1804 {
1805 reqParams["friendID_" + count++] = uuid.ToString();
1806 }
1807 reqParams["friendCount"] = count;
1808
1809 IList parameters = new ArrayList();
1810 parameters.Add(reqParams);
1811 try
1812 {
1813 XmlRpcRequest request = new XmlRpcRequest("presence_update_bulk", parameters);
1814 XmlRpcResponse response = request.Send(httpServer, 5000);
1815 Hashtable respData = (Hashtable)response.Value;
1816
1817 count = (int)respData["friendCount"];
1818 for (int i = 0; i < count; ++i)
1819 {
1820 UUID uuid;
1821 if(UUID.TryParse((string)respData["friendID_" + i], out uuid)) tpdAway.Add(uuid);
1822 }
1823 }
1824 catch(Exception e)
1825 {
1826 m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e);
1827 }
1828 }
1829 else m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}???", destRegionHandle);
1830
1831 return tpdAway;
1832 }
1833
1834 public bool TriggerTerminateFriend(ulong destRegionHandle, UUID agentID, UUID exFriendID)
1835 {
1836 // destRegionHandle is a region on another server
1837 RegionInfo info = RequestNeighbourInfo(destRegionHandle);
1838 if (info == null)
1839 {
1840 m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}", destRegionHandle);
1841 return false; // region not found???
1842 }
1843
1844 string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk";
1845
1846 Hashtable reqParams = new Hashtable();
1847 reqParams["agentID"] = agentID.ToString();
1848 reqParams["friendID"] = exFriendID.ToString();
1849
1850 IList parameters = new ArrayList();
1851 parameters.Add(reqParams);
1852 try
1853 {
1854 XmlRpcRequest request = new XmlRpcRequest("terminate_friend", parameters);
1855 XmlRpcResponse response = request.Send(httpServer, 5000);
1856 Hashtable respData = (Hashtable)response.Value;
1857
1858 return (bool)respData["success"];
1859 }
1860 catch(Exception e)
1861 {
1862 m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e);
1863 return false;
1864 }
1865 }
1788 } 1866 }
1789} \ No newline at end of file 1867}
diff --git a/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs b/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs
index 28177d0..595c4a9 100644
--- a/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs
+++ b/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs
@@ -39,7 +39,7 @@ using OpenSim.Framework.Communications;
39 39
40namespace OpenSim.Region.Communications.OGS1 40namespace OpenSim.Region.Communications.OGS1
41{ 41{
42 public class OGS1UserServices : IUserService, IAvatarService 42 public class OGS1UserServices : IUserService, IAvatarService, IMessagingService
43 { 43 {
44 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 44 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
45 45
@@ -722,6 +722,64 @@ namespace OpenSim.Region.Communications.OGS1
722 return buddylist; 722 return buddylist;
723 } 723 }
724 724
725 public Dictionary<UUID, FriendRegionInfo> GetFriendRegionInfos (List<UUID> uuids)
726 {
727 Dictionary<UUID, FriendRegionInfo> result = new Dictionary<UUID, FriendRegionInfo>();
728
729 // ask MessageServer about the current on-/offline status and regions the friends are in
730 ArrayList parameters = new ArrayList();
731 Hashtable map = new Hashtable();
732
733 ArrayList list = new ArrayList();
734 foreach (UUID uuid in uuids)
735 {
736 list.Add(uuid.ToString());
737 list.Add(uuid.ToString());
738 }
739 map["uuids"] = list;
740
741 map["recv_key"] = m_parent.NetworkServersInfo.UserRecvKey;
742 map["send_key"] = m_parent.NetworkServersInfo.UserRecvKey;
743
744 parameters.Add(map);
745
746 try {
747 XmlRpcRequest req = new XmlRpcRequest("get_presence_info_bulk", parameters);
748 XmlRpcResponse resp = req.Send(m_parent.NetworkServersInfo.MessagingURL, 8000);
749 Hashtable respData = (Hashtable) resp.Value;
750
751 if (respData.ContainsKey("faultMessage"))
752 {
753 m_log.WarnFormat("[OGS1 USER SERVICES]: Contacting MessageServer about user-regions resulted in error: {0}",
754 respData["faultMessage"]);
755 }
756 else
757 {
758 int count = (int)respData["count"];
759 m_log.DebugFormat("[OGS1 USER SERVICES]: Request returned {0} results.", count);
760 for (int i = 0; i < count; ++i)
761 {
762 UUID uuid;
763 if (UUID.TryParse((string)respData["uuid_" + i], out uuid))
764 {
765 FriendRegionInfo info = new FriendRegionInfo();
766 info.isOnline = (bool)respData["isOnline_" + i];
767 if (info.isOnline) info.regionHandle = Convert.ToUInt64(respData["regionHandle_" + i]);
768
769 result.Add(uuid, info);
770 }
771 }
772 }
773 }
774 catch (WebException e)
775 {
776 m_log.ErrorFormat("[OGS1 USER SERVICES]: Network problems when trying to fetch friend infos: {0}", e.Message);
777 }
778
779 m_log.DebugFormat("[OGS1 USER SERVICES]: Returning {0} entries", result.Count);
780 return result;
781 }
782
725 #endregion 783 #endregion
726 784
727 /// Appearance 785 /// Appearance
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs
index a380700..e2cb2e0 100644
--- a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs
+++ b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs
@@ -34,43 +34,100 @@ using log4net;
34using Nini.Config; 34using Nini.Config;
35using Nwc.XmlRpc; 35using Nwc.XmlRpc;
36using OpenSim.Framework; 36using OpenSim.Framework;
37using OpenSim.Framework.Communications.Cache;
38using OpenSim.Framework.Servers;
37using OpenSim.Region.Environment.Interfaces; 39using OpenSim.Region.Environment.Interfaces;
38using OpenSim.Region.Environment.Scenes; 40using OpenSim.Region.Environment.Scenes;
39 41
40namespace OpenSim.Region.Environment.Modules.Avatar.Friends 42namespace OpenSim.Region.Environment.Modules.Avatar.Friends
41{ 43{
44 /*
45 This module handles adding/removing friends, and the the presence
46 notification process for login/logoff of friends.
47
48 The presence notification works as follows:
49 - After the user initially connects to a region (so we now have a UDP
50 connection to work with), this module fetches the friends of user
51 (those are cached), their on-/offline status, and info about the
52 region they are in from the MessageServer.
53 - (*) It then informs the user about the on-/offline status of her friends.
54 - It then informs all online friends currently on this region-server about
55 user's new online status (this will save some network traffic, as local
56 messages don't have to be transferred inter-region, and it will be all
57 that has to be done in Standalone Mode).
58 - For the rest of the online friends (those not on this region-server),
59 this module uses the provided region-information to map users to
60 regions, and sends one notification to every region containing the
61 friends to inform on that server.
62 - The region-server will handle that in the following way:
63 - If it finds the friend, it informs her about the user being online.
64 - If it doesn't find the friend (maybe she TPed away in the meantime),
65 it stores that information.
66 - After it processed all friends, it returns the list of friends it
67 couldn't find.
68 - If this list isn't empty, the FriendsModule re-requests information
69 about those online friends that have been missed and starts at (*)
70 again until all friends have been found, or until it tried 3 times
71 (to prevent endless loops due to some uncaught error).
72
73 NOTE: Online/Offline notifications don't need to be sent on region change.
74
75 We implement two XMLRpc handlers here, handling all the inter-region things
76 we have to handle:
77 - On-/Offline-Notifications (bulk)
78 - Terminate Friendship messages (single)
79 */
80
42 public class FriendsModule : IRegionModule 81 public class FriendsModule : IRegionModule
43 { 82 {
83 private class Transaction
84 {
85 public UUID agentID;
86 public string agentName;
87 public uint count;
88
89 public Transaction(UUID agentID, string agentName)
90 {
91 this.agentID = agentID;
92 this.agentName = agentName;
93 this.count = 1;
94 }
95 }
96
44 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 97 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
45 98
46 private Dictionary<UUID, List<FriendListItem>> FriendLists = new Dictionary<UUID, List<FriendListItem>>(); 99 private Cache m_friendLists = new Cache(CacheFlags.AllowUpdate);
47 private Dictionary<UUID, UUID> m_pendingFriendRequests = new Dictionary<UUID, UUID>(); 100
48 private Dictionary<UUID, ulong> m_rootAgents = new Dictionary<UUID, ulong>(); 101 private Dictionary<UUID, ulong> m_rootAgents = new Dictionary<UUID, ulong>();
49 private Dictionary<UUID, List<StoredFriendListUpdate>> StoredFriendListUpdates = new Dictionary<UUID, List<StoredFriendListUpdate>>();
50 102
103 private Dictionary<UUID, Transaction> m_pendingFriendRequests = new Dictionary<UUID, Transaction>();
51 private Dictionary<UUID, UUID> m_pendingCallingcardRequests = new Dictionary<UUID,UUID>(); 104 private Dictionary<UUID, UUID> m_pendingCallingcardRequests = new Dictionary<UUID,UUID>();
52 105
53 private List<Scene> m_scene = new List<Scene>(); 106 private Scene m_initialScene; // saves a lookup if we don't have a specific scene
107 private Dictionary<ulong, Scene> m_scenes = new Dictionary<ulong,Scene>();
54 108
55 #region IRegionModule Members 109 #region IRegionModule Members
56 110
57 public void Initialise(Scene scene, IConfigSource config) 111 public void Initialise(Scene scene, IConfigSource config)
58 { 112 {
59 lock (m_scene) 113 lock (m_scenes)
60 { 114 {
61 if (m_scene.Count == 0) 115 if (m_scenes.Count == 0)
62 { 116 {
63 scene.AddXmlRPCHandler("presence_update", processPresenceUpdate); 117 scene.AddXmlRPCHandler("presence_update_bulk", processPresenceUpdateBulk);
118 scene.AddXmlRPCHandler("terminate_friend", processTerminateFriend);
119 m_friendLists.DefaultTTL = new TimeSpan(1, 0, 0); // store entries for one hour max
120 m_initialScene = scene;
64 } 121 }
65 122
66 if (!m_scene.Contains(scene)) 123 if (!m_scenes.ContainsKey(scene.RegionInfo.RegionHandle))
67 m_scene.Add(scene); 124 m_scenes[scene.RegionInfo.RegionHandle] = scene;
68 } 125 }
69 scene.EventManager.OnNewClient += OnNewClient; 126 scene.EventManager.OnNewClient += OnNewClient;
70 scene.EventManager.OnGridInstantMessage += OnGridInstantMessage; 127 scene.EventManager.OnGridInstantMessage += OnGridInstantMessage;
71 scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; 128 scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel;
72 scene.EventManager.OnMakeChildAgent += MakeChildAgent; 129 scene.EventManager.OnMakeChildAgent += MakeChildAgent;
73 scene.EventManager.OnClientClosed += ClientLoggedOut; 130 scene.EventManager.OnClientClosed += ClientClosed;
74 } 131 }
75 132
76 public void PostInitialise() 133 public void PostInitialise()
@@ -93,82 +150,104 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
93 150
94 #endregion 151 #endregion
95 152
96 public XmlRpcResponse processPresenceUpdate(XmlRpcRequest req) 153 public XmlRpcResponse processPresenceUpdateBulk(XmlRpcRequest req)
97 { 154 {
98 //m_log.Info("[FRIENDS]: Got Notification about a user! OMG");
99 Hashtable requestData = (Hashtable)req.Params[0]; 155 Hashtable requestData = (Hashtable)req.Params[0];
100 156
101 if (requestData.ContainsKey("agent_id") && requestData.ContainsKey("notify_id") && requestData.ContainsKey("status")) 157 List<UUID> friendsNotHere = new List<UUID>();
102 {
103 UUID notifyAgentId = UUID.Zero;
104 UUID notifyAboutAgentId = UUID.Zero;
105 bool notifyOnlineStatus = false;
106
107 if ((string)requestData["status"] == "TRUE")
108 notifyOnlineStatus = true;
109 158
110 UUID.TryParse((string)requestData["notify_id"], out notifyAgentId); 159 // this is called with the expectation that all the friends in the request are on this region-server.
111 160 // But as some time passed since we checked (on the other region-server, via the MessagingServer),
112 UUID.TryParse((string)requestData["agent_id"], out notifyAboutAgentId); 161 // some of the friends might have teleported away.
113 m_log.InfoFormat("[PRESENCE]: Got presence update for {0}, and we're telling {1}, with a status {2}", notifyAboutAgentId.ToString(), notifyAgentId.ToString(), notifyOnlineStatus.ToString()); 162 // Actually, even now, between this line and the sending below, some people could TP away. So,
114 ScenePresence avatar = GetRootPresenceFromAgentID(notifyAgentId); 163 // we'll have to lock the m_rootAgents list for the duration to prevent/delay that.
115 if (avatar != null) 164 lock(m_rootAgents)
165 {
166 List<ScenePresence> friendsHere = new List<ScenePresence>();
167 try
116 { 168 {
117 if (avatar.IsChildAgent) 169 UUID agentID = new UUID((string)requestData["agentID"]);
170 bool agentOnline = (bool)requestData["agentOnline"];
171 int count = (int)requestData["friendCount"];
172 for (int i = 0; i < count; ++i)
118 { 173 {
119 StoredFriendListUpdate sob = new StoredFriendListUpdate(); 174 UUID uuid;
120 sob.OnlineYN = notifyOnlineStatus; 175 if (UUID.TryParse((string)requestData["friendID_" + i], out uuid))
121 sob.storedAbout = notifyAboutAgentId;
122 sob.storedFor = notifyAgentId;
123 lock (StoredFriendListUpdates)
124 { 176 {
125 if (StoredFriendListUpdates.ContainsKey(notifyAgentId)) 177 if (m_rootAgents.ContainsKey(uuid)) friendsHere.Add(GetRootPresenceFromAgentID(uuid));
126 { 178 else friendsNotHere.Add(uuid);
127 StoredFriendListUpdates[notifyAgentId].Add(sob);
128 }
129 else
130 {
131 List<StoredFriendListUpdate> newitem = new List<StoredFriendListUpdate>();
132 newitem.Add(sob);
133 StoredFriendListUpdates.Add(notifyAgentId, newitem);
134 }
135 } 179 }
136 } 180 }
137 else 181
138 { 182 // now send, as long as they are still here...
139 if (notifyOnlineStatus) 183 UUID[] agentUUID = new UUID[] { agentID };
140 doFriendListUpdateOnline(notifyAboutAgentId); 184 if (agentOnline)
141 else
142 ClientLoggedOut(notifyAboutAgentId);
143 }
144 }
145 else
146 {
147 StoredFriendListUpdate sob = new StoredFriendListUpdate();
148 sob.OnlineYN = notifyOnlineStatus;
149 sob.storedAbout = notifyAboutAgentId;
150 sob.storedFor = notifyAgentId;
151 lock (StoredFriendListUpdates)
152 { 185 {
153 if (StoredFriendListUpdates.ContainsKey(notifyAgentId)) 186 foreach (ScenePresence agent in friendsHere)
154 { 187 {
155 StoredFriendListUpdates[notifyAgentId].Add(sob); 188 agent.ControllingClient.SendAgentOnline(agentUUID);
156 } 189 }
157 else 190 }
191 else
192 {
193 foreach (ScenePresence agent in friendsHere)
158 { 194 {
159 List<StoredFriendListUpdate> newitem = new List<StoredFriendListUpdate>(); 195 agent.ControllingClient.SendAgentOffline(agentUUID);
160 newitem.Add(sob);
161 StoredFriendListUpdates.Add(notifyAgentId, newitem);
162 } 196 }
163 } 197 }
164 } 198 }
199 catch(Exception e)
200 {
201 m_log.Warn("[FRIENDS]: Got exception while parsing presence_update_bulk request:", e);
202 }
203 }
165 204
205 // no need to lock anymore; if TPs happen now, worst case is that we have an additional agent in this region,
206 // which should be caught on the next iteration...
207 Hashtable result = new Hashtable();
208 int idx = 0;
209 foreach (UUID uuid in friendsNotHere)
210 {
211 result["friendID_" + idx++] = uuid.ToString();
166 } 212 }
167 else 213 result["friendCount"] = idx;
214
215 XmlRpcResponse response = new XmlRpcResponse();
216 response.Value = result;
217
218 return response;
219 }
220
221 public XmlRpcResponse processTerminateFriend(XmlRpcRequest req)
222 {
223 Hashtable requestData = (Hashtable)req.Params[0];
224
225 bool success = false;
226
227 UUID agentID;
228 UUID friendID;
229 if (requestData.ContainsKey("agentID") && UUID.TryParse((string)requestData["agentID"], out agentID) &&
230 requestData.ContainsKey("friendID") && UUID.TryParse((string)requestData["friendID"], out friendID))
168 { 231 {
169 m_log.Warn("[PRESENCE]: Malformed XMLRPC Presence request"); 232 // try to find it and if it is there, prevent it to vanish before we sent the message
233 lock(m_rootAgents)
234 {
235 if (m_rootAgents.ContainsKey(agentID))
236 {
237 m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", friendID, agentID);
238 GetRootPresenceFromAgentID(agentID).ControllingClient.SendTerminateFriend(friendID);
239 success = true;
240 }
241 }
170 } 242 }
171 return new XmlRpcResponse(); 243
244 // return whether we were successful
245 Hashtable result = new Hashtable();
246 result["success"] = success;
247
248 XmlRpcResponse response = new XmlRpcResponse();
249 response.Value = result;
250 return response;
172 } 251 }
173 252
174 private void OnNewClient(IClientAPI client) 253 private void OnNewClient(IClientAPI client)
@@ -176,245 +255,52 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
176 // All friends establishment protocol goes over instant message 255 // All friends establishment protocol goes over instant message
177 // There's no way to send a message from the sim 256 // There's no way to send a message from the sim
178 // to a user to 'add a friend' without causing dialog box spam 257 // to a user to 'add a friend' without causing dialog box spam
179 //
180 // The base set of friends are added when the user signs on in their XMLRPC response
181 // Generated by LoginService. The friends are retreived from the database by the UserManager
182 258
183 // Subscribe to instant messages 259 // Subscribe to instant messages
184
185 client.OnInstantMessage += OnInstantMessage; 260 client.OnInstantMessage += OnInstantMessage;
186 client.OnApproveFriendRequest += OnApprovedFriendRequest; 261
262 // Friend list management
263 client.OnApproveFriendRequest += OnApproveFriendRequest;
187 client.OnDenyFriendRequest += OnDenyFriendRequest; 264 client.OnDenyFriendRequest += OnDenyFriendRequest;
188 client.OnTerminateFriendship += OnTerminateFriendship; 265 client.OnTerminateFriendship += OnTerminateFriendship;
266
267 // ... calling card handling...
189 client.OnOfferCallingCard += OnOfferCallingCard; 268 client.OnOfferCallingCard += OnOfferCallingCard;
190 client.OnAcceptCallingCard += OnAcceptCallingCard; 269 client.OnAcceptCallingCard += OnAcceptCallingCard;
191 client.OnDeclineCallingCard += OnDeclineCallingCard; 270 client.OnDeclineCallingCard += OnDeclineCallingCard;
192 271
193 doFriendListUpdateOnline(client.AgentId); 272 // we need this one exactly once per agent session (see comments in the handler below)
194 273 client.OnEconomyDataRequest += OnEconomyDataRequest;
195 }
196
197 private void doFriendListUpdateOnline(UUID AgentId)
198 {
199 List<FriendListItem> fl = new List<FriendListItem>();
200
201 //bool addFLback = false;
202
203 lock (FriendLists)
204 {
205 if (FriendLists.ContainsKey(AgentId))
206 {
207 fl = FriendLists[AgentId];
208 }
209 else
210 {
211 fl = m_scene[0].GetFriendList(AgentId);
212
213 //lock (FriendLists)
214 //{
215 if (!FriendLists.ContainsKey(AgentId))
216 FriendLists.Add(AgentId, fl);
217 //}
218 }
219 }
220
221 List<UUID> UpdateUsers = new List<UUID>();
222
223 foreach (FriendListItem f in fl)
224 {
225 if (m_rootAgents.ContainsKey(f.Friend))
226 {
227 if (f.onlinestatus == false)
228 {
229 UpdateUsers.Add(f.Friend);
230 f.onlinestatus = true;
231 }
232 }
233 }
234 foreach (UUID user in UpdateUsers)
235 {
236 ScenePresence av = GetRootPresenceFromAgentID(user);
237 if (av != null)
238 {
239 List<FriendListItem> usrfl = new List<FriendListItem>();
240
241 lock (FriendLists)
242 {
243 usrfl = FriendLists[user];
244 }
245
246 lock (usrfl)
247 {
248 foreach (FriendListItem fli in usrfl)
249 {
250 if (fli.Friend == AgentId)
251 {
252 fli.onlinestatus = true;
253 UUID[] Agents = new UUID[1];
254 Agents[0] = AgentId;
255 av.ControllingClient.SendAgentOnline(Agents);
256
257 }
258 }
259 }
260 }
261 }
262
263 if (UpdateUsers.Count > 0)
264 {
265 ScenePresence avatar = GetRootPresenceFromAgentID(AgentId);
266 if (avatar != null)
267 {
268 avatar.ControllingClient.SendAgentOnline(UpdateUsers.ToArray());
269 }
270 274
271 } 275 // if it leaves, we want to know, too
276 client.OnLogout += OnLogout;
272 } 277 }
273 278
274 private void ClientLoggedOut(UUID AgentId) 279 private void ClientClosed(UUID AgentId)
275 { 280 {
281 // agent's client was closed. As we handle logout in OnLogout, this here has only to handle
282 // TPing away (root agent is closed) or TPing/crossing in a region far enough away (client
283 // agent is closed).
284 // NOTE: In general, this doesn't mean that the agent logged out, just that it isn't around
285 // in one of the regions here anymore.
276 lock (m_rootAgents) 286 lock (m_rootAgents)
277 { 287 {
278 if (m_rootAgents.ContainsKey(AgentId)) 288 if (m_rootAgents.ContainsKey(AgentId))
279 { 289 {
280 m_rootAgents.Remove(AgentId); 290 m_rootAgents.Remove(AgentId);
281 m_log.Info("[FRIEND]: Removing " + AgentId + ". Agent logged out."); 291 m_log.Info("[FRIEND]: Removing " + AgentId + ". Agent was closed.");
282 }
283 }
284 List<FriendListItem> lfli = new List<FriendListItem>();
285 lock (FriendLists)
286 {
287 if (FriendLists.ContainsKey(AgentId))
288 {
289 lfli = FriendLists[AgentId];
290 }
291 }
292 List<UUID> updateUsers = new List<UUID>();
293 foreach (FriendListItem fli in lfli)
294 {
295 if (fli.onlinestatus == true)
296 {
297 updateUsers.Add(fli.Friend);
298 }
299 }
300 lock (updateUsers)
301 {
302 for (int i = 0; i < updateUsers.Count; i++)
303 {
304 List<FriendListItem> flfli = new List<FriendListItem>();
305 try
306 {
307 lock (FriendLists)
308 {
309 if (FriendLists.ContainsKey(updateUsers[i]))
310 flfli = FriendLists[updateUsers[i]];
311 }
312 }
313 catch (IndexOutOfRangeException)
314 {
315 // Ignore the index out of range exception.
316 // This causes friend lists to get out of sync slightly.. however
317 // prevents a sim crash.
318 m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off");
319 }
320 catch (ArgumentOutOfRangeException)
321 {
322 // Ignore the index out of range exception.
323 // This causes friend lists to get out of sync slightly.. however
324 // prevents a sim crash.
325 m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off");
326 }
327
328 for (int j = 0; j < flfli.Count; j++)
329 {
330 try
331 {
332 if (flfli[i].Friend == AgentId)
333 {
334 flfli[i].onlinestatus = false;
335 }
336 }
337
338 catch (IndexOutOfRangeException)
339 {
340 // Ignore the index out of range exception.
341 // This causes friend lists to get out of sync slightly.. however
342 // prevents a sim crash.
343 m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off");
344 }
345 catch (ArgumentOutOfRangeException)
346 {
347 // Ignore the index out of range exception.
348 // This causes friend lists to get out of sync slightly.. however
349 // prevents a sim crash.
350 m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off");
351 }
352 }
353 }
354
355 for (int i = 0; i < updateUsers.Count; i++)
356 {
357 ScenePresence av = GetRootPresenceFromAgentID(updateUsers[i]);
358 if (av != null)
359 {
360 UUID[] agents = new UUID[1];
361 agents[0] = AgentId;
362 av.ControllingClient.SendAgentOffline(agents);
363 }
364 } 292 }
365 } 293 }
366 lock (FriendLists)
367 {
368 FriendLists.Remove(AgentId);
369 }
370 } 294 }
371 295
372 private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) 296 private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID)
373 { 297 {
374 lock (m_rootAgents) 298 lock (m_rootAgents)
375 { 299 {
376 if (m_rootAgents.ContainsKey(avatar.UUID)) 300 m_rootAgents[avatar.UUID] = avatar.RegionHandle;
377 { 301 m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + ".");
378 if (avatar.RegionHandle != m_rootAgents[avatar.UUID]) 302 // Claim User! my user! Mine mine mine!
379 {
380 m_rootAgents[avatar.UUID] = avatar.RegionHandle;
381 m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + ".");
382 if (avatar.JID.Length > 0)
383 {
384 // JId avatarID = new JId(avatar.JID);
385 // REST Post XMPP Stanzas!
386 }
387 // Claim User! my user! Mine mine mine!
388 }
389 }
390 else
391 {
392 m_rootAgents.Add(avatar.UUID, avatar.RegionHandle);
393 m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + ".");
394
395 List<StoredFriendListUpdate> updateme = new List<StoredFriendListUpdate>();
396 lock (StoredFriendListUpdates)
397 {
398 if (StoredFriendListUpdates.ContainsKey(avatar.UUID))
399 {
400 updateme = StoredFriendListUpdates[avatar.UUID];
401 StoredFriendListUpdates.Remove(avatar.UUID);
402 }
403 }
404
405 if (updateme.Count > 0)
406 {
407 foreach (StoredFriendListUpdate u in updateme)
408 {
409 if (u.OnlineYN)
410 doFriendListUpdateOnline(u.storedAbout);
411 else
412 ClientLoggedOut(u.storedAbout);
413 }
414 }
415 }
416 } 303 }
417 //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString());
418 } 304 }
419 305
420 private void MakeChildAgent(ScenePresence avatar) 306 private void MakeChildAgent(ScenePresence avatar)
@@ -423,6 +309,8 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
423 { 309 {
424 if (m_rootAgents.ContainsKey(avatar.UUID)) 310 if (m_rootAgents.ContainsKey(avatar.UUID))
425 { 311 {
312 // only delete if the region matches. As this is a shared module, the avatar could be
313 // root agent in another region on this server.
426 if (m_rootAgents[avatar.UUID] == avatar.RegionHandle) 314 if (m_rootAgents[avatar.UUID] == avatar.RegionHandle)
427 { 315 {
428 m_rootAgents.Remove(avatar.UUID); 316 m_rootAgents.Remove(avatar.UUID);
@@ -435,12 +323,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
435 private ScenePresence GetRootPresenceFromAgentID(UUID AgentID) 323 private ScenePresence GetRootPresenceFromAgentID(UUID AgentID)
436 { 324 {
437 ScenePresence returnAgent = null; 325 ScenePresence returnAgent = null;
438 lock (m_scene) 326 lock (m_scenes)
439 { 327 {
440 ScenePresence queryagent = null; 328 ScenePresence queryagent = null;
441 for (int i = 0; i < m_scene.Count; i++) 329 foreach (Scene scene in m_scenes.Values)
442 { 330 {
443 queryagent = m_scene[i].GetScenePresence(AgentID); 331 queryagent = scene.GetScenePresence(AgentID);
444 if (queryagent != null) 332 if (queryagent != null)
445 { 333 {
446 if (!queryagent.IsChildAgent) 334 if (!queryagent.IsChildAgent)
@@ -457,12 +345,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
457 private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID) 345 private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID)
458 { 346 {
459 ScenePresence returnAgent = null; 347 ScenePresence returnAgent = null;
460 lock (m_scene) 348 lock (m_scenes)
461 { 349 {
462 ScenePresence queryagent = null; 350 ScenePresence queryagent = null;
463 for (int i = 0; i < m_scene.Count; i++) 351 foreach (Scene scene in m_scenes.Values)
464 { 352 {
465 queryagent = m_scene[i].GetScenePresence(AgentID); 353 queryagent = scene.GetScenePresence(AgentID);
466 if (queryagent != null) 354 if (queryagent != null)
467 { 355 {
468 returnAgent = queryagent; 356 returnAgent = queryagent;
@@ -485,141 +373,160 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
485 // Friend Requests go by Instant Message.. using the dialog param 373 // Friend Requests go by Instant Message.. using the dialog param
486 // https://wiki.secondlife.com/wiki/ImprovedInstantMessage 374 // https://wiki.secondlife.com/wiki/ImprovedInstantMessage
487 375
488 // 38 == Offer friendship 376 if (dialog == (byte)InstantMessageDialog.FriendshipOffered) // 38
489 if (dialog == (byte) 38)
490 { 377 {
491 UUID friendTransactionID = UUID.Random(); 378 // this is triggered by the initiating agent and has two parts:
379 // A local agent offers friendship to some possibly remote friend.
380 // A IM is triggered, processed here (1), sent to the destination region,
381 // and processed there in this part of the code again (2).
382 // (1) has fromAgentSession != UUID.Zero,
383 // (2) has fromAgentSession == UUID.Zero (don't leak agent sessions to other agents)
384 // For (1), build the IM to send to the other region (and trigger sending it)
385 // FOr (2), just store the transaction; we will wait for Approval or Decline
386
387 // some properties are misused here:
388 // fromAgentName is the *destination* name (the friend we offer friendship to)
389
390 if (fromAgentSession != UUID.Zero)
391 {
392 // (1)
393 // send the friendship-offer to the target
394 m_log.InfoFormat("[FRIEND]: Offer(38) - From: {0}, FromName: {1} To: {2}, Session: {3}, Message: {4}, Offline {5}",
395 fromAgentID, fromAgentName, toAgentID, imSessionID, message, offline);
396
397 UUID transactionID = UUID.Random();
398
399 GridInstantMessage msg = new GridInstantMessage();
400 msg.fromAgentID = fromAgentID.Guid;
401 msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session
402 msg.toAgentID = toAgentID.Guid;
403 msg.imSessionID = transactionID.Guid; // Start new transaction
404 m_log.DebugFormat("[FRIEND]: new transactionID: {0}", msg.imSessionID);
405 msg.timestamp = timestamp;
406 if (client != null)
407 {
408 msg.fromAgentName = client.Name; // fromAgentName;
409 }
410 else
411 {
412 msg.fromAgentName = "(hippos)"; // Added for posterity. This means that we can't figure out who sent it
413 }
414 msg.message = message;
415 msg.dialog = dialog;
416 msg.fromGroup = fromGroup;
417 msg.offline = offline;
418 msg.ParentEstateID = ParentEstateID;
419 msg.Position = Position;
420 msg.RegionID = RegionID.Guid;
421 msg.binaryBucket = binaryBucket;
422
423 m_log.DebugFormat("[FRIEND]: storing transactionID {0} on sender side", transactionID);
424 lock (m_pendingFriendRequests)
425 {
426 m_pendingFriendRequests.Add(transactionID, new Transaction(fromAgentID, fromAgentName));
427 outPending();
428 }
492 429
493 m_pendingFriendRequests.Add(friendTransactionID, fromAgentID); 430 // we don't want to get that new IM into here if we aren't local, as only on the destination
431 // should receive it. If we *are* local, *we* are the destination, so we have to receive it.
432 // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here.
433 InstantMessageReceiver recv = InstantMessageReceiver.IMModule;
434 if(GetAnyPresenceFromAgentID(toAgentID) != null) recv |= InstantMessageReceiver.FriendsModule;
494 435
495 m_log.Info("[FRIEND]: 38 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + 436 // We don't really care which local scene we pipe it through.
496 message); 437 m_initialScene.TriggerGridInstantMessage(msg, recv);
497 GridInstantMessage msg = new GridInstantMessage();
498 msg.fromAgentID = fromAgentID.Guid;
499 msg.fromAgentSession = fromAgentSession.Guid;
500 msg.toAgentID = toAgentID.Guid;
501 msg.imSessionID = friendTransactionID.Guid; // This is the item we're mucking with here
502 m_log.Info("[FRIEND]: Filling Session: " + msg.imSessionID.ToString());
503 msg.timestamp = timestamp;
504 if (client != null)
505 {
506 msg.fromAgentName = client.Name; // fromAgentName;
507 } 438 }
508 else 439 else
509 { 440 {
510 msg.fromAgentName = "(hippos)"; // Added for posterity. This means that we can't figure out who sent it 441 // (2)
442 // we are on the receiving end here; just add the transactionID to the stored transactions for later lookup
443 m_log.DebugFormat("[FRIEND]: storing transactionID {0} on receiver side", imSessionID);
444 lock (m_pendingFriendRequests)
445 {
446 // if both are on the same region-server, the transaction is stored already, but we have to update the name
447 if (m_pendingFriendRequests.ContainsKey(imSessionID))
448 {
449 m_pendingFriendRequests[imSessionID].agentName = fromAgentName;
450 m_pendingFriendRequests[imSessionID].count++;
451 }
452 else m_pendingFriendRequests.Add(imSessionID, new Transaction(fromAgentID, fromAgentName));
453 outPending();
454 }
511 } 455 }
512 msg.message = message;
513 msg.dialog = dialog;
514 msg.fromGroup = fromGroup;
515 msg.offline = offline;
516 msg.ParentEstateID = ParentEstateID;
517 msg.Position = Position;
518 msg.RegionID = RegionID.Guid;
519 msg.binaryBucket = binaryBucket;
520 // We don't really care which scene we pipe it through.
521 m_scene[0].TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule);
522 } 456 }
523 457 else if (dialog == (byte)InstantMessageDialog.FriendshipAccepted) // 39
524 // 39 == Accept Friendship
525 if (dialog == (byte) 39)
526 { 458 {
527 m_log.Info("[FRIEND]: 39 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + 459 // accepting the friendship offer causes a type 39 IM being sent to the (possibly remote) initiator
528 message); 460 // toAgentID is initiator, fromAgentID is new friend (which just approved)
529 } 461 m_log.DebugFormat("[FRIEND]: 39 - from client {0}, agentSession {1}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})",
462 client != null ? client.AgentId.ToString() : "<null>", fromAgentSession,
463 fromAgentID, fromAgentName, imSessionID, toAgentID, message, dialog);
464 lock (m_pendingFriendRequests)
465 {
466 if (!m_pendingFriendRequests.ContainsKey(imSessionID))
467 {
468 m_log.DebugFormat("[FRIEND]: Got friendship approval from {0} to {1} without matching transaction {2}",
469 fromAgentID, toAgentID, imSessionID);
470 return; // unknown transaction
471 }
472 // else found pending friend request with that transaction => remove it if we handled all
473 if (--m_pendingFriendRequests[imSessionID].count <= 0) m_pendingFriendRequests.Remove(imSessionID);
474 outPending();
475 }
530 476
531 // 40 == Decline Friendship 477 // a new friend was added in the initiator's and friend's data, so the cache entries are wrong now.
532 if (dialog == (byte) 40) 478 lock (m_friendLists)
533 { 479 {
534 m_log.Info("[FRIEND]: 40 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + 480 m_friendLists.Invalidate(toAgentID);
535 message); 481 m_friendLists.Invalidate(fromAgentID);
536 } 482 }
537 }
538 483
539 private void OnApprovedFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List<UUID> callingCardFolders) 484 // now send presence update and add a calling card for the new friend
540 {
541 if (m_pendingFriendRequests.ContainsKey(transactionID))
542 {
543 // Found Pending Friend Request with that Transaction..
544 Scene SceneAgentIn = m_scene[0];
545 485
546 // Found Pending Friend Request with that Transaction.. 486 ScenePresence initiator = GetAnyPresenceFromAgentID(toAgentID);
547 ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID); 487 if (initiator == null)
548 if (agentpresence != null)
549 { 488 {
550 SceneAgentIn = agentpresence.Scene; 489 // quite wrong. Shouldn't happen.
490 m_log.WarnFormat("[FRIEND]: Coudn't find initiator of friend request {0}", toAgentID);
491 return;
551 } 492 }
552 493
553 // Compose response to other agent. 494 // tell initiator that friend is online
554 GridInstantMessage msg = new GridInstantMessage(); 495 initiator.ControllingClient.SendAgentOnline(new UUID[] { fromAgentID });
555 msg.toAgentID = m_pendingFriendRequests[transactionID].Guid;
556 msg.fromAgentID = agentID.Guid;
557 msg.fromAgentName = client.Name;
558 msg.fromAgentSession = client.SessionId.Guid;
559 msg.fromGroup = false;
560 msg.imSessionID = transactionID.Guid;
561 msg.message = agentID.Guid.ToString();
562 msg.ParentEstateID = 0;
563 msg.timestamp = (uint) Util.UnixTimeSinceEpoch();
564 msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid;
565 msg.dialog = (byte) 39; // Approved friend request
566 msg.Position = Vector3.Zero;
567 msg.offline = (byte) 0;
568 msg.binaryBucket = new byte[0];
569 // We don't really care which scene we pipe it through, it goes to the shared IM Module and/or the database
570
571 SceneAgentIn.TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule);
572 SceneAgentIn.StoreAddFriendship(m_pendingFriendRequests[transactionID], agentID, (uint) 1);
573
574
575 //UUID[] Agents = new UUID[1];
576 //Agents[0] = msg.toAgentID;
577 //av.ControllingClient.SendAgentOnline(Agents);
578
579 m_pendingFriendRequests.Remove(transactionID);
580 // TODO: Inform agent that the friend is online
581 }
582 }
583 496
584 private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List<UUID> callingCardFolders) 497 // find the folder for the friend...
585 { 498 InventoryFolderImpl folder =
586 if (m_pendingFriendRequests.ContainsKey(transactionID)) 499 initiator.Scene.CommsManager.UserProfileCacheService.GetUserDetails(toAgentID).FindFolderForType((int)InventoryType.CallingCard);
500 if (folder != null)
501 {
502 // ... and add the calling card
503 CreateCallingCard(initiator.ControllingClient, fromAgentID, folder.ID, fromAgentName);
504 }
505 }
506 else if (dialog == (byte)InstantMessageDialog.FriendshipDeclined) // 40
587 { 507 {
588 Scene SceneAgentIn = m_scene[0]; 508 // declining the friendship offer causes a type 40 IM being sent to the (possibly remote) initiator
589 509 // toAgentID is initiator, fromAgentID declined friendship
590 // Found Pending Friend Request with that Transaction.. 510 m_log.DebugFormat("[FRIEND]: 40 - from client {0}, agentSession {1}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})",
591 ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID); 511 client != null ? client.AgentId.ToString() : "<null>", fromAgentSession,
592 if (agentpresence != null) 512 fromAgentID, fromAgentName, imSessionID, toAgentID, message, dialog);
513
514 // not much to do, just clean up the transaction...
515 lock (m_pendingFriendRequests)
593 { 516 {
594 SceneAgentIn = agentpresence.Scene; 517 if (!m_pendingFriendRequests.ContainsKey(imSessionID))
518 {
519 m_log.DebugFormat("[FRIEND]: Got friendship denial from {0} to {1} without matching transaction {2}",
520 fromAgentID, toAgentID, imSessionID);
521 return; // unknown transaction
522 }
523 // else found pending friend request with that transaction => remove it if we handled all
524 if (--m_pendingFriendRequests[imSessionID].count <= 0) m_pendingFriendRequests.Remove(imSessionID);
525 outPending();
595 } 526 }
596 // Compose response to other agent.
597 GridInstantMessage msg = new GridInstantMessage();
598 msg.toAgentID = m_pendingFriendRequests[transactionID].Guid;
599 msg.fromAgentID = agentID.Guid;
600 msg.fromAgentName = client.Name;
601 msg.fromAgentSession = client.SessionId.Guid;
602 msg.fromGroup = false;
603 msg.imSessionID = transactionID.Guid;
604 msg.message = agentID.Guid.ToString();
605 msg.ParentEstateID = 0;
606 msg.timestamp = (uint) Util.UnixTimeSinceEpoch();
607 msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid;
608 msg.dialog = (byte) 40; // Deny friend request
609 msg.Position = Vector3.Zero;
610 msg.offline = (byte) 0;
611 msg.binaryBucket = new byte[0];
612 SceneAgentIn.TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule);
613 m_pendingFriendRequests.Remove(transactionID);
614 } 527 }
615 } 528 }
616 529
617 private void OnTerminateFriendship(IClientAPI client, UUID agent, UUID exfriendID)
618 {
619 m_scene[0].StoreRemoveFriendship(agent, exfriendID);
620 // TODO: Inform the client that the ExFriend is offline
621 }
622
623 private void OnGridInstantMessage(GridInstantMessage msg, InstantMessageReceiver whichModule) 530 private void OnGridInstantMessage(GridInstantMessage msg, InstantMessageReceiver whichModule)
624 { 531 {
625 if ((whichModule & InstantMessageReceiver.FriendsModule) == 0) 532 if ((whichModule & InstantMessageReceiver.FriendsModule) == 0)
@@ -633,6 +540,182 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
633 msg.binaryBucket); 540 msg.binaryBucket);
634 } 541 }
635 542
543 private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List<UUID> callingCardFolders)
544 {
545 m_log.DebugFormat("[FRIEND]: Got approve friendship from {0} {1}, agentID {2}, tid {3}",
546 client.Name, client.AgentId, agentID, transactionID);
547 Transaction transaction;
548 lock (m_pendingFriendRequests)
549 {
550 if (!m_pendingFriendRequests.TryGetValue(transactionID, out transaction))
551 {
552 m_log.DebugFormat("[FRIEND]: Got friendship approval {0} from {1} ({2}) without matching transaction {3}",
553 agentID, client.AgentId, client.Name, transactionID);
554 return; // unknown transaction
555 }
556 // else found pending friend request with that transaction => remove if done with all
557 if(--m_pendingFriendRequests[transactionID].count <= 0) m_pendingFriendRequests.Remove(transactionID);
558 outPending();
559 }
560
561 UUID friendID = transaction.agentID;
562 m_log.DebugFormat("[FRIEND]: {0} ({1}) approved friendship request from {2}",
563 client.Name, client.AgentId, friendID);
564
565 Scene SceneAgentIn = m_initialScene;
566 // we need any presence to send the packets to, not necessarily the root agent...
567 ScenePresence agentpresence = GetAnyPresenceFromAgentID(agentID);
568 if (agentpresence != null)
569 {
570 SceneAgentIn = agentpresence.Scene;
571 }
572
573 // store the new friend persistently for both avatars
574 SceneAgentIn.StoreAddFriendship(friendID, agentID, (uint) FriendRights.CanSeeOnline);
575
576 // The cache entries aren't valid anymore either, as we just added a friend to both sides.
577 lock (m_friendLists)
578 {
579 m_friendLists.Invalidate(agentID);
580 m_friendLists.Invalidate(friendID);
581 }
582
583 // create calling card
584 CreateCallingCard(client, friendID, callingCardFolders[0], transaction.agentName);
585
586 // Compose response to other agent.
587 GridInstantMessage msg = new GridInstantMessage();
588 msg.toAgentID = friendID.Guid;
589 msg.fromAgentID = agentID.Guid;
590 msg.fromAgentName = client.Name;
591 msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session
592 msg.fromGroup = false;
593 msg.imSessionID = transactionID.Guid;
594 msg.message = agentID.Guid.ToString();
595 msg.ParentEstateID = 0;
596 msg.timestamp = (uint) Util.UnixTimeSinceEpoch();
597 msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid;
598 msg.dialog = (byte) InstantMessageDialog.FriendshipAccepted;
599 msg.Position = Vector3.Zero;
600 msg.offline = (byte) 0;
601 msg.binaryBucket = new byte[0];
602
603 // we don't want to get that new IM into here if we aren't local, as only on the destination
604 // should receive it. If we *are* local, *we* are the destination, so we have to receive it.
605 // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here.
606 InstantMessageReceiver recv = InstantMessageReceiver.IMModule;
607 if(GetAnyPresenceFromAgentID(friendID) != null) recv |= InstantMessageReceiver.FriendsModule;
608
609 // now we have to inform the agent about the friend. For the opposite direction, this happens in the handler
610 // of the type 39 IM
611 SceneAgentIn.TriggerGridInstantMessage(msg, recv);
612
613 // tell client that new friend is online
614 client.SendAgentOnline(new UUID[] { friendID });
615 }
616
617 private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List<UUID> callingCardFolders)
618 {
619 m_log.DebugFormat("[FRIEND]: Got deny friendship from {0} {1}, agentID {2}, tid {3}",
620 client.Name, client.AgentId, agentID, transactionID);
621 Transaction transaction;
622 lock (m_pendingFriendRequests)
623 {
624 if(!m_pendingFriendRequests.TryGetValue(transactionID, out transaction))
625 {
626 m_log.DebugFormat("[FRIEND]: Got friendship denial {0} from {1} ({2}) without matching transaction {3}",
627 agentID, client.AgentId, client.Name, transactionID);
628 return;
629 }
630 // else found pending friend request with that transaction.
631 if(--m_pendingFriendRequests[transactionID].count <= 0) m_pendingFriendRequests.Remove(transactionID);
632 outPending();
633 }
634 UUID friendID = transaction.agentID;
635
636 Scene SceneAgentIn = m_initialScene;
637 ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID);
638 if (agentpresence != null)
639 {
640 SceneAgentIn = agentpresence.Scene;
641 }
642
643 // Compose response to other agent.
644 GridInstantMessage msg = new GridInstantMessage();
645 msg.toAgentID = friendID.Guid;
646 msg.fromAgentID = agentID.Guid;
647 msg.fromAgentName = client.Name;
648 msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session
649 msg.fromGroup = false;
650 msg.imSessionID = transactionID.Guid;
651 msg.message = agentID.Guid.ToString();
652 msg.ParentEstateID = 0;
653 msg.timestamp = (uint) Util.UnixTimeSinceEpoch();
654 msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid;
655 msg.dialog = (byte) InstantMessageDialog.FriendshipDeclined;
656 msg.Position = Vector3.Zero;
657 msg.offline = (byte) 0;
658 msg.binaryBucket = new byte[0];
659
660 // we don't want to get that new IM into here if we aren't local, as only on the destination
661 // should receive it. If we *are* local, *we* are the destination, so we have to receive it.
662 // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here.
663 InstantMessageReceiver recv = InstantMessageReceiver.IMModule;
664 if(GetAnyPresenceFromAgentID(friendID) != null) recv |= InstantMessageReceiver.FriendsModule;
665
666 // now we have to inform the agent about the friend. For the opposite direction, this happens in the handler
667 // of the type 39 IM
668 SceneAgentIn.TriggerGridInstantMessage(msg, recv);
669 }
670
671 private void OnTerminateFriendship(IClientAPI client, UUID agentID, UUID exfriendID)
672 {
673 // client.AgentId == agentID!
674
675 // this removes the friends from the stored friendlists. After the next login, they will be gone...
676 m_initialScene.StoreRemoveFriendship(agentID, exfriendID);
677
678 // ... now tell the two involved clients that they aren't friends anymore.
679
680 // I don't know why we have to tell <agent>, as this was caused by her, but that's how it works in SL...
681 client.SendTerminateFriend(exfriendID);
682
683 // now send the friend, if online
684 ScenePresence presence = GetAnyPresenceFromAgentID(exfriendID);
685 if (presence != null)
686 {
687 m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", agentID, exfriendID);
688 presence.ControllingClient.SendTerminateFriend(agentID);
689 }
690 else
691 {
692 // retry 3 times, in case the agent TPed from the last known region...
693 for(int retry = 0; retry < 3; ++retry)
694 {
695 // wasn't sent, so ex-friend wasn't around on this region-server. Fetch info and try to send
696 UserAgentData data = m_initialScene.CommsManager.UserService.GetAgentByUUID(exfriendID);
697 if (!data.AgentOnline)
698 {
699 m_log.DebugFormat("[FRIEND]: {0} is offline, so not sending TerminateFriend", exfriendID);
700 break; // if ex-friend isn't online, we don't need to send
701 }
702
703 m_log.DebugFormat("[FRIEND]: Sending remote terminate friend {0} to agent {1}@{2}",
704 agentID, exfriendID, data.Handle);
705
706 // try to send to foreign region, retry if it fails (friend TPed away, for example)
707 if (m_initialScene.TriggerTerminateFriend(data.Handle, exfriendID, agentID)) break;
708 }
709 }
710
711 // clean up cache: FriendList is wrong now...
712 lock (m_friendLists)
713 {
714 m_friendLists.Invalidate(agentID);
715 m_friendLists.Invalidate(exfriendID);
716 }
717 }
718
636 #endregion 719 #endregion
637 720
638 #region CallingCards 721 #region CallingCards
@@ -650,7 +733,10 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
650 return; 733 return;
651 } 734 }
652 735
653 m_pendingCallingcardRequests[transactionID] = client.AgentId; 736 lock (m_pendingCallingcardRequests)
737 {
738 m_pendingCallingcardRequests[transactionID] = client.AgentId;
739 }
654 // inform the destination agent about the offer 740 // inform the destination agent about the offer
655 destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID); 741 destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID);
656 } 742 }
@@ -687,19 +773,25 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
687 client.FirstName, client.LastName, 773 client.FirstName, client.LastName,
688 transactionID, folderID); 774 transactionID, folderID);
689 UUID destID; 775 UUID destID;
690 if (m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) 776 lock (m_pendingCallingcardRequests)
691 { 777 {
778 if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID))
779 {
780 m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.",
781 client.Name);
782 return;
783 }
784 // else found pending calling card request with that transaction.
692 m_pendingCallingcardRequests.Remove(transactionID); 785 m_pendingCallingcardRequests.Remove(transactionID);
786 }
693 787
694 ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
695 // inform sender of the card that destination declined the offer
696 if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID);
697 788
698 // put a calling card into the inventory of receiver 789 ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
699 CreateCallingCard(client, destID, folderID, destAgent.Name); 790 // inform sender of the card that destination declined the offer
700 } 791 if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID);
701 else m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} {1} without an offer before.", 792
702 client.FirstName, client.LastName); 793 // put a calling card into the inventory of receiver
794 CreateCallingCard(client, destID, folderID, destAgent.Name);
703 } 795 }
704 796
705 private void OnDeclineCallingCard(IClientAPI client, UUID transactionID) 797 private void OnDeclineCallingCard(IClientAPI client, UUID transactionID)
@@ -707,25 +799,230 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
707 m_log.DebugFormat("[CALLING CARD]: User {0} declined card, tid {2}", 799 m_log.DebugFormat("[CALLING CARD]: User {0} declined card, tid {2}",
708 client.AgentId, transactionID); 800 client.AgentId, transactionID);
709 UUID destID; 801 UUID destID;
710 if (m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) 802 lock (m_pendingCallingcardRequests)
711 { 803 {
804 if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID))
805 {
806 m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.",
807 client.Name);
808 return;
809 }
810 // else found pending calling card request with that transaction.
712 m_pendingCallingcardRequests.Remove(transactionID); 811 m_pendingCallingcardRequests.Remove(transactionID);
812 }
813
814 ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
815 // inform sender of the card that destination declined the offer
816 if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID);
817 }
818
819 private void SendPresenceState(IClientAPI client, List<FriendListItem> friendList, bool iAmOnline)
820 {
821 m_log.DebugFormat("[FRIEND]: {0} logged {1}; sending presence updates", client.Name, iAmOnline ? "in" : "out");
822
823 if (friendList == null || friendList.Count == 0)
824 {
825 m_log.DebugFormat("[FRIEND]: {0} doesn't have friends.", client.Name);
826 return; // nothing we can do if she doesn't have friends...
827 }
828
829 // collect sets of friendIDs; to send to (online and offline), and to receive from
830 // TODO: If we ever switch to .NET >= 3, replace those Lists with HashSets.
831 // I can't believe that we have Dictionaries, but no Sets, considering Java introduced them years ago...
832 List<UUID> friendIDsToSendTo = new List<UUID>();
833 List<UUID> friendIDsToReceiveFromOffline = new List<UUID>();
834 List<UUID> friendIDsToReceiveFromOnline = new List<UUID>();
835 foreach (FriendListItem item in friendList)
836 {
837 if (((item.FriendListOwnerPerms | item.FriendPerms) & (uint)FriendRights.CanSeeOnline) != 0)
838 {
839 // friend is allowed to see my presence => add
840 if ((item.FriendListOwnerPerms & (uint)FriendRights.CanSeeOnline) != 0) friendIDsToSendTo.Add(item.Friend);
841
842 // I'm allowed to see friend's presence => add as offline, we might reconsider in a momnet...
843 if ((item.FriendPerms & (uint)FriendRights.CanSeeOnline) != 0) friendIDsToReceiveFromOffline.Add(item.Friend);
844 }
845 }
846
847
848
849 // we now have a list of "interesting" friends (which we have to find out on-/offline state for),
850 // friends we want to send our online state to (if *they* are online, too), and
851 // friends we want to receive online state for (currently unknown whether online or not)
852
853 // as this processing might take some time and friends might TP away, we try up to three times to
854 // reach them. Most of the time, we *will* reach them, and this loop won't loop
855 int retry = 0;
856 do
857 {
858 // build a list of friends to look up region-information and on-/offline-state for
859 List<UUID> friendIDsToLookup = new List<UUID>(friendIDsToSendTo);
860 foreach (UUID uuid in friendIDsToReceiveFromOffline)
861 {
862 if(!friendIDsToLookup.Contains(uuid)) friendIDsToLookup.Add(uuid);
863 }
864
865 m_log.DebugFormat("[FRIEND]: {0} to lookup, {1} to send to, {2} to receive from for agent {3}",
866 friendIDsToLookup.Count, friendIDsToSendTo.Count, friendIDsToReceiveFromOffline.Count, client.Name);
867
868 // we have to fetch FriendRegionInfos, as the (cached) FriendListItems don't
869 // necessarily contain the correct online state...
870 Dictionary<UUID, FriendRegionInfo> friendRegions = m_initialScene.GetFriendRegionInfos(friendIDsToLookup);
871 m_log.DebugFormat("[FRIEND]: Found {0} regionInfos for {1} friends of {2}",
872 friendRegions.Count, friendIDsToLookup.Count, client.Name);
873
874 // argument for SendAgentOn/Offline; we shouldn't generate that repeatedly within loops.
875 UUID[] agentArr = new UUID[] { client.AgentId };
876
877 // first, send to friend presence state to me, if I'm online...
878 if (iAmOnline)
879 {
880 for (int i = friendIDsToReceiveFromOffline.Count - 1; i >= 0; --i)
881 {
882 UUID uuid = friendIDsToReceiveFromOffline[i];
883 FriendRegionInfo info;
884 if (friendRegions.TryGetValue(uuid, out info) && info.isOnline)
885 {
886 friendIDsToReceiveFromOffline.RemoveAt(i);
887 friendIDsToReceiveFromOnline.Add(uuid);
888 }
889 }
890 m_log.DebugFormat("[FRIEND]: Sending {0} offline and {1} online friends to {2}",
891 friendIDsToReceiveFromOffline.Count, friendIDsToReceiveFromOnline.Count, client.Name);
892 if (friendIDsToReceiveFromOffline.Count > 0) client.SendAgentOffline(friendIDsToReceiveFromOffline.ToArray());
893 if (friendIDsToReceiveFromOnline.Count > 0) client.SendAgentOnline(friendIDsToReceiveFromOnline.ToArray());
894
895 // clear them for a possible second iteration; we don't have to repeat this
896 friendIDsToReceiveFromOffline.Clear();
897 friendIDsToReceiveFromOnline.Clear();
898 }
899
900 // now, send my presence state to my friends
901 for (int i = friendIDsToSendTo.Count - 1; i >= 0; --i)
902 {
903 UUID uuid = friendIDsToSendTo[i];
904 FriendRegionInfo info;
905 if (friendRegions.TryGetValue(uuid, out info) && info.isOnline)
906 {
907 // any client is good enough, root or child...
908 ScenePresence agent = GetAnyPresenceFromAgentID(uuid);
909 if(agent != null)
910 {
911 m_log.DebugFormat("[FRIEND]: Found local agent {0}", agent.Name);
912
913 // friend is online and on this server...
914 if(iAmOnline) agent.ControllingClient.SendAgentOnline(agentArr);
915 else agent.ControllingClient.SendAgentOffline(agentArr);
916
917 // done, remove it
918 friendIDsToSendTo.RemoveAt(i);
919 }
920 }
921 else
922 {
923 m_log.DebugFormat("[FRIEND]: Friend {0} ({1}) is offline; not sending.", uuid, i);
713 924
714 ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); 925 // friend is offline => no need to try sending
715 // inform sender of the card that destination declined the offer 926 friendIDsToSendTo.RemoveAt(i);
716 if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID); 927 }
928 }
929
930 m_log.DebugFormat("[FRIEND]: Have {0} friends to contact via inter-region comms.", friendIDsToSendTo.Count);
931
932 // we now have all the friends left that are online (we think), but not on this region-server
933 if (friendIDsToSendTo.Count > 0)
934 {
935 // sort them into regions
936 Dictionary<ulong, List<UUID>> friendsInRegion = new Dictionary<ulong,List<UUID>>();
937 foreach (UUID uuid in friendIDsToSendTo)
938 {
939 ulong handle = friendRegions[uuid].regionHandle; // this can't fail as we filtered above already
940 List<UUID> friends;
941 if (!friendsInRegion.TryGetValue(handle, out friends))
942 {
943 friends = new List<UUID>();
944 friendsInRegion[handle] = friends;
945 }
946 friends.Add(uuid);
947 }
948 m_log.DebugFormat("[FRIEND]: Found {0} regions to send to.", friendRegions.Count);
949
950 // clear uuids list and collect missed friends in it for the next retry
951 friendIDsToSendTo.Clear();
952
953 // send bulk updates to the region
954 foreach (KeyValuePair<ulong, List<UUID>> pair in friendsInRegion)
955 {
956 m_log.DebugFormat("[FRIEND]: Inform {0} friends in region {1} that user {2} is {3}line",
957 pair.Value.Count, pair.Key, client.Name, iAmOnline ? "on" : "off");
958
959 friendIDsToSendTo.AddRange(m_initialScene.InformFriendsInOtherRegion(client.AgentId, pair.Key, pair.Value, iAmOnline));
960 }
961 }
962 // now we have in friendIDsToSendTo only the agents left that TPed away while we tried to contact them.
963 // In most cases, it will be empty, and it won't loop here. But sometimes, we have to work harder and try again...
717 } 964 }
718 else m_log.WarnFormat("[CALLING CARD]: Got a DeclineCallingCard from {0} {1} without an offer before.", 965 while(++retry < 3 && friendIDsToSendTo.Count > 0);
719 client.FirstName, client.LastName);
720 } 966 }
721 }
722 967
723 #endregion 968 private void OnEconomyDataRequest(UUID agentID)
969 {
970 // KLUDGE: This is the only way I found to get a message (only) after login was completed and the
971 // client is connected enough to receive UDP packets).
972 // This packet seems to be sent only once, just after connection was established to the first
973 // region after login.
974 // We use it here to trigger a presence update; the old update-on-login was never be heard by
975 // the freshly logged in viewer, as it wasn't connected to the region at that time.
976 // TODO: Feel free to replace this by a better solution if you find one.
977
978 // get the agent. This should work every time, as we just got a packet from it
979 //ScenePresence agent = GetRootPresenceFromAgentID(agentID);
980 // KLUDGE 2: As this is sent quite early, the avatar isn't here as root agent yet. So, we have to cheat a bit
981 ScenePresence agent = GetAnyPresenceFromAgentID(agentID);
982
983 // just to be paranoid...
984 if (agent == null)
985 {
986 m_log.ErrorFormat("[FRIEND]: Got a packet from agent {0} who can't be found anymore!?", agentID);
987 return;
988 }
724 989
725 public struct StoredFriendListUpdate 990 List<FriendListItem> fl;
726 { 991 lock (m_friendLists)
727 public UUID storedFor; 992 {
728 public UUID storedAbout; 993 fl = (List<FriendListItem>)m_friendLists.Get(agent.ControllingClient.AgentId,
729 public bool OnlineYN; 994 m_initialScene.GetFriendList);
995 }
996
997 // tell everyone that we are online
998 SendPresenceState(agent.ControllingClient, fl, true);
999 }
1000
1001 private void OnLogout(IClientAPI remoteClient)
1002 {
1003 m_log.ErrorFormat("[FRIEND]: Client {0} logged out", remoteClient.Name);
1004
1005 List<FriendListItem> fl;
1006 lock (m_friendLists)
1007 {
1008 fl = (List<FriendListItem>)m_friendLists.Get(remoteClient.AgentId,
1009 m_initialScene.GetFriendList);
1010 }
1011
1012 // tell everyone that we are offline
1013 SendPresenceState(remoteClient, fl, false);
1014 }
1015
1016 private void outPending()
1017 {
1018 m_log.DebugFormat("[FRIEND]: got {0} requests pending", m_pendingFriendRequests.Count);
1019 foreach (KeyValuePair<UUID, Transaction> pair in m_pendingFriendRequests)
1020 {
1021 m_log.DebugFormat("[FRIEND]: tid={0}, agent={1}, name={2}, count={3}",
1022 pair.Key, pair.Value.agentID, pair.Value.agentName, pair.Value.count);
1023 }
1024 }
730 } 1025 }
1026
1027 #endregion
731} 1028}
diff --git a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs
index adf1103..5597381 100644
--- a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs
+++ b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs
@@ -137,8 +137,14 @@ namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage
137 // IM dialogs need to be pre-processed and have their sessionID filled by the server 137 // IM dialogs need to be pre-processed and have their sessionID filled by the server
138 // so the sim can match the transaction on the return packet. 138 // so the sim can match the transaction on the return packet.
139 139
140 // Don't send a Friend Dialog IM with a UUID.Zero session. 140 // Don't process IMs that are handled elsewhere (e.g. friend dialog
141 if (!dialogHandledElsewhere) 141 // IMs) with a non-UUID.Zero agent session, as those have been send
142 // by a client (either directly or from another region via
143 // inter-region communication) and will be processed in another
144 // module (e.g. the friends-module).
145 // IMs with fromAgentSession == UUID.Zero come from the server, and
146 // have to be passed to the matching viewer
147 if (!dialogHandledElsewhere || fromAgentSession == UUID.Zero)
142 { 148 {
143 // Try root avatar only first 149 // Try root avatar only first
144 foreach (Scene scene in m_scenes) 150 foreach (Scene scene in m_scenes)
diff --git a/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs b/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs
index da7a10c..369b56c 100644
--- a/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs
+++ b/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs
@@ -963,5 +963,9 @@ namespace OpenSim.Region.Environment.Modules.World.NPC
963 public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) 963 public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data)
964 { 964 {
965 } 965 }
966
967 public void SendTerminateFriend(UUID exFriendID)
968 {
969 }
966 } 970 }
967} 971}
diff --git a/OpenSim/Region/Environment/Scenes/Scene.cs b/OpenSim/Region/Environment/Scenes/Scene.cs
index f411a7f..647682e 100644
--- a/OpenSim/Region/Environment/Scenes/Scene.cs
+++ b/OpenSim/Region/Environment/Scenes/Scene.cs
@@ -3200,6 +3200,20 @@ namespace OpenSim.Region.Environment.Scenes
3200 return CommsManager.GetUserFriendList(avatarID); 3200 return CommsManager.GetUserFriendList(avatarID);
3201 } 3201 }
3202 3202
3203 public Dictionary<UUID, FriendRegionInfo> GetFriendRegionInfos(List<UUID> uuids)
3204 {
3205 return CommsManager.GetFriendRegionInfos(uuids);
3206 }
3207
3208 public List<UUID> InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List<UUID> friends, bool online)
3209 {
3210 return CommsManager.InformFriendsInOtherRegion(agentId, destRegionHandle, friends, online);
3211 }
3212
3213 public bool TriggerTerminateFriend(ulong regionHandle, UUID agentID, UUID exFriendID)
3214 {
3215 return CommsManager.TriggerTerminateFriend(regionHandle, agentID, exFriendID);
3216 }
3203 3217
3204 #endregion 3218 #endregion
3205 3219
diff --git a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs
index b55e5b6..0131109 100644
--- a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs
+++ b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs
@@ -962,5 +962,9 @@ namespace OpenSim.Region.Examples.SimpleModule
962 public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) 962 public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data)
963 { 963 {
964 } 964 }
965
966 public void SendTerminateFriend(UUID exFriendID)
967 {
968 }
965 } 969 }
966} 970}