aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs
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/Environment/Modules/Avatar/Friends/FriendsModule.cs
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 '')
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs1137
1 files changed, 717 insertions, 420 deletions
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}