aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs')
-rw-r--r--OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs1344
1 files changed, 456 insertions, 888 deletions
diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs
index 086d4fe..555fb00 100644
--- a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs
+++ b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs
@@ -28,7 +28,6 @@
28using System; 28using System;
29using System.Collections; 29using System.Collections;
30using System.Collections.Generic; 30using System.Collections.Generic;
31using System.Net;
32using System.Reflection; 31using System.Reflection;
33using log4net; 32using log4net;
34using Nini.Config; 33using Nini.Config;
@@ -36,1119 +35,688 @@ using Nwc.XmlRpc;
36using OpenMetaverse; 35using OpenMetaverse;
37using OpenSim.Framework; 36using OpenSim.Framework;
38using OpenSim.Framework.Communications; 37using OpenSim.Framework.Communications;
39using OpenSim.Framework.Communications.Cache;
40using OpenSim.Region.Framework.Interfaces; 38using OpenSim.Region.Framework.Interfaces;
41using OpenSim.Region.Framework.Scenes; 39using OpenSim.Region.Framework.Scenes;
42using OpenSim.Services.Interfaces; 40using OpenSim.Services.Interfaces;
41using OpenSim.Services.Connectors.Friends;
42using OpenSim.Server.Base;
43using OpenSim.Framework.Servers.HttpServer;
44using FriendInfo = OpenSim.Services.Interfaces.FriendInfo;
45using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo;
43using GridRegion = OpenSim.Services.Interfaces.GridRegion; 46using GridRegion = OpenSim.Services.Interfaces.GridRegion;
44 47
45namespace OpenSim.Region.CoreModules.Avatar.Friends 48namespace OpenSim.Region.CoreModules.Avatar.Friends
46{ 49{
47 /* 50 public class FriendsModule : ISharedRegionModule, IFriendsModule
48 This module handles adding/removing friends, and the the presence
49 notification process for login/logoff of friends.
50
51 The presence notification works as follows:
52 - After the user initially connects to a region (so we now have a UDP
53 connection to work with), this module fetches the friends of user
54 (those are cached), their on-/offline status, and info about the
55 region they are in from the MessageServer.
56 - (*) It then informs the user about the on-/offline status of her friends.
57 - It then informs all online friends currently on this region-server about
58 user's new online status (this will save some network traffic, as local
59 messages don't have to be transferred inter-region, and it will be all
60 that has to be done in Standalone Mode).
61 - For the rest of the online friends (those not on this region-server),
62 this module uses the provided region-information to map users to
63 regions, and sends one notification to every region containing the
64 friends to inform on that server.
65 - The region-server will handle that in the following way:
66 - If it finds the friend, it informs her about the user being online.
67 - If it doesn't find the friend (maybe she TPed away in the meantime),
68 it stores that information.
69 - After it processed all friends, it returns the list of friends it
70 couldn't find.
71 - If this list isn't empty, the FriendsModule re-requests information
72 about those online friends that have been missed and starts at (*)
73 again until all friends have been found, or until it tried 3 times
74 (to prevent endless loops due to some uncaught error).
75
76 NOTE: Online/Offline notifications don't need to be sent on region change.
77
78 We implement two XMLRpc handlers here, handling all the inter-region things
79 we have to handle:
80 - On-/Offline-Notifications (bulk)
81 - Terminate Friendship messages (single)
82 */
83
84 public class FriendsModule : IRegionModule, IFriendsModule
85 { 51 {
86 private class Transaction 52 protected class UserFriendData
87 { 53 {
88 public UUID agentID; 54 public UUID PrincipalID;
89 public string agentName; 55 public FriendInfo[] Friends;
90 public uint count; 56 public int Refcount;
57 public UUID RegionID;
91 58
92 public Transaction(UUID agentID, string agentName) 59 public bool IsFriend(string friend)
93 { 60 {
94 this.agentID = agentID; 61 foreach (FriendInfo fi in Friends)
95 this.agentName = agentName; 62 {
96 this.count = 1; 63 if (fi.Friend == friend)
64 return true;
65 }
66
67 return false;
97 } 68 }
98 } 69 }
99 70
100 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 71 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
101 72
102 private Cache m_friendLists = new Cache(CacheFlags.AllowUpdate); 73 protected List<Scene> m_Scenes = new List<Scene>();
103
104 private Dictionary<UUID, ulong> m_rootAgents = new Dictionary<UUID, ulong>();
105
106 private Dictionary<UUID, UUID> m_pendingCallingcardRequests = new Dictionary<UUID,UUID>();
107 74
108 private Scene m_initialScene; // saves a lookup if we don't have a specific scene 75 protected IPresenceService m_PresenceService = null;
109 private Dictionary<ulong, Scene> m_scenes = new Dictionary<ulong,Scene>(); 76 protected IFriendsService m_FriendsService = null;
110 private IMessageTransferModule m_TransferModule = null; 77 protected FriendsSimConnector m_FriendsSimConnector;
111 78
112 private IGridService m_gridServices = null; 79 protected Dictionary<UUID, UserFriendData> m_Friends =
80 new Dictionary<UUID, UserFriendData>();
113 81
114 #region IRegionModule Members 82 protected List<UUID> m_NeedsListOfFriends = new List<UUID>();
115 83
116 public void Initialise(Scene scene, IConfigSource config) 84 protected IPresenceService PresenceService
117 { 85 {
118 lock (m_scenes) 86 get
119 { 87 {
120 if (m_scenes.Count == 0) 88 if (m_PresenceService == null)
121 { 89 {
122 MainServer.Instance.AddXmlRPCHandler("presence_update_bulk", processPresenceUpdateBulk); 90 if (m_Scenes.Count > 0)
123 MainServer.Instance.AddXmlRPCHandler("terminate_friend", processTerminateFriend); 91 m_PresenceService = m_Scenes[0].RequestModuleInterface<IPresenceService>();
124 m_friendLists.DefaultTTL = new TimeSpan(1, 0, 0); // store entries for one hour max
125 m_initialScene = scene;
126 } 92 }
127 93
128 if (!m_scenes.ContainsKey(scene.RegionInfo.RegionHandle)) 94 return m_PresenceService;
129 m_scenes[scene.RegionInfo.RegionHandle] = scene;
130 } 95 }
131
132 scene.RegisterModuleInterface<IFriendsModule>(this);
133
134 scene.EventManager.OnNewClient += OnNewClient;
135 scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
136 scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel;
137 scene.EventManager.OnMakeChildAgent += MakeChildAgent;
138 scene.EventManager.OnClientClosed += ClientClosed;
139 } 96 }
140 97
141 public void PostInitialise() 98 protected IFriendsService FriendsService
142 { 99 {
143 if (m_scenes.Count > 0) 100 get
144 { 101 {
145 m_TransferModule = m_initialScene.RequestModuleInterface<IMessageTransferModule>(); 102 if (m_FriendsService == null)
146 m_gridServices = m_initialScene.GridService; 103 {
147 } 104 if (m_Scenes.Count > 0)
148 if (m_TransferModule == null) 105 m_FriendsService = m_Scenes[0].RequestModuleInterface<IFriendsService>();
149 m_log.Error("[FRIENDS]: Unable to find a message transfer module, friendship offers will not work"); 106 }
150 }
151
152 public void Close()
153 {
154 }
155 107
156 public string Name 108 return m_FriendsService;
157 { 109 }
158 get { return "FriendsModule"; }
159 } 110 }
160 111
161 public bool IsSharedModule 112 protected IGridService GridService
162 { 113 {
163 get { return true; } 114 get
115 {
116 return m_Scenes[0].GridService;
117 }
164 } 118 }
165 119
166 #endregion 120 public IScene Scene
167
168 #region IInterregionFriendsComms
169
170 public List<UUID> InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List<UUID> friends, bool online)
171 { 121 {
172 List<UUID> tpdAway = new List<UUID>(); 122 get
173
174 // destRegionHandle is a region on another server
175 uint x = 0, y = 0;
176 Utils.LongToUInts(destRegionHandle, out x, out y);
177 GridRegion info = m_gridServices.GetRegionByPosition(m_initialScene.RegionInfo.ScopeID, (int)x, (int)y);
178 if (info != null)
179 { 123 {
180 string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk"; 124 if (m_Scenes.Count > 0)
181 125 return m_Scenes[0];
182 Hashtable reqParams = new Hashtable(); 126 else
183 reqParams["agentID"] = agentId.ToString(); 127 return null;
184 reqParams["agentOnline"] = online;
185 int count = 0;
186 foreach (UUID uuid in friends)
187 {
188 reqParams["friendID_" + count++] = uuid.ToString();
189 }
190 reqParams["friendCount"] = count;
191
192 IList parameters = new ArrayList();
193 parameters.Add(reqParams);
194 try
195 {
196 XmlRpcRequest request = new XmlRpcRequest("presence_update_bulk", parameters);
197 XmlRpcResponse response = request.Send(httpServer, 5000);
198 Hashtable respData = (Hashtable)response.Value;
199
200 count = (int)respData["friendCount"];
201 for (int i = 0; i < count; ++i)
202 {
203 UUID uuid;
204 if (UUID.TryParse((string)respData["friendID_" + i], out uuid)) tpdAway.Add(uuid);
205 }
206 }
207 catch (WebException e)
208 {
209 // Ignore connect failures, simulators come and go
210 //
211 if (!e.Message.Contains("ConnectFailure"))
212 {
213 m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e);
214 }
215 }
216 catch (Exception e)
217 {
218 m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e);
219 }
220 } 128 }
221 else m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}???", destRegionHandle);
222
223 return tpdAway;
224 } 129 }
225 130
226 public bool TriggerTerminateFriend(ulong destRegionHandle, UUID agentID, UUID exFriendID) 131 public void Initialise(IConfigSource config)
227 { 132 {
228 // destRegionHandle is a region on another server 133 IConfig friendsConfig = config.Configs["Friends"];
229 uint x = 0, y = 0; 134 if (friendsConfig != null)
230 Utils.LongToUInts(destRegionHandle, out x, out y);
231 GridRegion info = m_gridServices.GetRegionByPosition(m_initialScene.RegionInfo.ScopeID, (int)x, (int)y);
232 if (info == null)
233 { 135 {
234 m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}", destRegionHandle); 136 int mPort = friendsConfig.GetInt("Port", 0);
235 return false; // region not found???
236 }
237 137
238 string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk"; 138 string connector = friendsConfig.GetString("Connector", String.Empty);
139 Object[] args = new Object[] { config };
239 140
240 Hashtable reqParams = new Hashtable(); 141 m_FriendsService = ServerUtils.LoadPlugin<IFriendsService>(connector, args);
241 reqParams["agentID"] = agentID.ToString(); 142 m_FriendsSimConnector = new FriendsSimConnector();
242 reqParams["friendID"] = exFriendID.ToString();
243 143
244 IList parameters = new ArrayList(); 144 // Instantiate the request handler
245 parameters.Add(reqParams); 145 IHttpServer server = MainServer.GetHttpServer((uint)mPort);
246 try 146 server.AddStreamHandler(new FriendsRequestHandler(this));
247 {
248 XmlRpcRequest request = new XmlRpcRequest("terminate_friend", parameters);
249 XmlRpcResponse response = request.Send(httpServer, 5000);
250 Hashtable respData = (Hashtable)response.Value;
251 147
252 return (bool)respData["success"];
253 } 148 }
254 catch (Exception e) 149
150 if (m_FriendsService == null)
255 { 151 {
256 m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e); 152 m_log.Error("[FRIENDS]: No Connector defined in section Friends, or filed to load, cannot continue");
257 return false; 153 throw new Exception("Connector load error");
258 } 154 }
155
259 } 156 }
260 157
261 #endregion 158 public void PostInitialise()
159 {
160 }
262 161
263 #region Incoming XMLRPC messages 162 public void Close()
264 /// <summary>
265 /// Receive presence information changes about clients in other regions.
266 /// </summary>
267 /// <param name="req"></param>
268 /// <returns></returns>
269 public XmlRpcResponse processPresenceUpdateBulk(XmlRpcRequest req, IPEndPoint remoteClient)
270 { 163 {
271 Hashtable requestData = (Hashtable)req.Params[0]; 164 }
272 165
273 List<UUID> friendsNotHere = new List<UUID>(); 166 public void AddRegion(Scene scene)
167 {
168 m_Scenes.Add(scene);
169 scene.RegisterModuleInterface<IFriendsModule>(this);
274 170
275 // this is called with the expectation that all the friends in the request are on this region-server. 171 scene.EventManager.OnNewClient += OnNewClient;
276 // But as some time passed since we checked (on the other region-server, via the MessagingServer), 172 scene.EventManager.OnClientClosed += OnClientClosed;
277 // some of the friends might have teleported away. 173 scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
278 // Actually, even now, between this line and the sending below, some people could TP away. So, 174 scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
279 // we'll have to lock the m_rootAgents list for the duration to prevent/delay that. 175 scene.EventManager.OnClientLogin += OnClientLogin;
280 lock (m_rootAgents) 176 }
281 {
282 List<ScenePresence> friendsHere = new List<ScenePresence>();
283
284 try
285 {
286 UUID agentID = new UUID((string)requestData["agentID"]);
287 bool agentOnline = (bool)requestData["agentOnline"];
288 int count = (int)requestData["friendCount"];
289 for (int i = 0; i < count; ++i)
290 {
291 UUID uuid;
292 if (UUID.TryParse((string)requestData["friendID_" + i], out uuid))
293 {
294 if (m_rootAgents.ContainsKey(uuid)) friendsHere.Add(GetRootPresenceFromAgentID(uuid));
295 else friendsNotHere.Add(uuid);
296 }
297 }
298 177
299 // now send, as long as they are still here... 178 public void RegionLoaded(Scene scene)
300 UUID[] agentUUID = new UUID[] { agentID }; 179 {
301 if (agentOnline) 180 }
302 {
303 foreach (ScenePresence agent in friendsHere)
304 {
305 agent.ControllingClient.SendAgentOnline(agentUUID);
306 }
307 }
308 else
309 {
310 foreach (ScenePresence agent in friendsHere)
311 {
312 agent.ControllingClient.SendAgentOffline(agentUUID);
313 }
314 }
315 }
316 catch(Exception e)
317 {
318 m_log.Warn("[FRIENDS]: Got exception while parsing presence_update_bulk request:", e);
319 }
320 }
321 181
322 // no need to lock anymore; if TPs happen now, worst case is that we have an additional agent in this region, 182 public void RemoveRegion(Scene scene)
323 // which should be caught on the next iteration... 183 {
324 Hashtable result = new Hashtable(); 184 m_Scenes.Remove(scene);
325 int idx = 0; 185 }
326 foreach (UUID uuid in friendsNotHere)
327 {
328 result["friendID_" + idx++] = uuid.ToString();
329 }
330 result["friendCount"] = idx;
331 186
332 XmlRpcResponse response = new XmlRpcResponse(); 187 public string Name
333 response.Value = result; 188 {
189 get { return "FriendsModule"; }
190 }
334 191
335 return response; 192 public Type ReplaceableInterface
193 {
194 get { return null; }
336 } 195 }
337 196
338 public XmlRpcResponse processTerminateFriend(XmlRpcRequest req, IPEndPoint remoteClient) 197 public uint GetFriendPerms(UUID principalID, UUID friendID)
339 { 198 {
340 Hashtable requestData = (Hashtable)req.Params[0]; 199 if (!m_Friends.ContainsKey(principalID))
200 return 0;
341 201
342 bool success = false; 202 UserFriendData data = m_Friends[principalID];
343 203
344 UUID agentID; 204 foreach (FriendInfo fi in data.Friends)
345 UUID friendID;
346 if (requestData.ContainsKey("agentID") && UUID.TryParse((string)requestData["agentID"], out agentID) &&
347 requestData.ContainsKey("friendID") && UUID.TryParse((string)requestData["friendID"], out friendID))
348 { 205 {
349 // try to find it and if it is there, prevent it to vanish before we sent the message 206 if (fi.Friend == friendID.ToString())
350 lock (m_rootAgents) 207 return (uint)fi.TheirFlags;
351 {
352 if (m_rootAgents.ContainsKey(agentID))
353 {
354 m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", friendID, agentID);
355 GetRootPresenceFromAgentID(agentID).ControllingClient.SendTerminateFriend(friendID);
356 success = true;
357 }
358 }
359 } 208 }
360 209 return 0;
361 // return whether we were successful
362 Hashtable result = new Hashtable();
363 result["success"] = success;
364
365 XmlRpcResponse response = new XmlRpcResponse();
366 response.Value = result;
367 return response;
368 } 210 }
369 211
370 #endregion
371
372 #region Scene events
373
374 private void OnNewClient(IClientAPI client) 212 private void OnNewClient(IClientAPI client)
375 { 213 {
376 // All friends establishment protocol goes over instant message
377 // There's no way to send a message from the sim
378 // to a user to 'add a friend' without causing dialog box spam
379
380 // Subscribe to instant messages
381 client.OnInstantMessage += OnInstantMessage; 214 client.OnInstantMessage += OnInstantMessage;
382
383 // Friend list management
384 client.OnApproveFriendRequest += OnApproveFriendRequest; 215 client.OnApproveFriendRequest += OnApproveFriendRequest;
385 client.OnDenyFriendRequest += OnDenyFriendRequest; 216 client.OnDenyFriendRequest += OnDenyFriendRequest;
386 client.OnTerminateFriendship += OnTerminateFriendship; 217 client.OnTerminateFriendship += OnTerminateFriendship;
387 218
388 // ... calling card handling... 219 client.OnGrantUserRights += OnGrantUserRights;
389 client.OnOfferCallingCard += OnOfferCallingCard;
390 client.OnAcceptCallingCard += OnAcceptCallingCard;
391 client.OnDeclineCallingCard += OnDeclineCallingCard;
392 220
393 // we need this one exactly once per agent session (see comments in the handler below)
394 client.OnEconomyDataRequest += OnEconomyDataRequest;
395
396 // if it leaves, we want to know, too
397 client.OnLogout += OnLogout; 221 client.OnLogout += OnLogout;
398
399 client.OnGrantUserRights += GrantUserFriendRights;
400 client.OnTrackAgent += FindAgent;
401 client.OnFindAgent += FindAgent;
402 222
223 if (m_Friends.ContainsKey(client.AgentId))
224 {
225 m_Friends[client.AgentId].Refcount++;
226 return;
227 }
228
229 UserFriendData newFriends = new UserFriendData();
230
231 newFriends.PrincipalID = client.AgentId;
232 newFriends.Friends = m_FriendsService.GetFriends(client.AgentId);
233 newFriends.Refcount = 1;
234 newFriends.RegionID = UUID.Zero;
235
236 m_Friends.Add(client.AgentId, newFriends);
237
238 //StatusChange(client.AgentId, true);
403 } 239 }
404 240
405 private void ClientClosed(UUID AgentId, Scene scene) 241 private void OnClientClosed(UUID agentID, Scene scene)
406 { 242 {
407 // agent's client was closed. As we handle logout in OnLogout, this here has only to handle 243 if (m_Friends.ContainsKey(agentID))
408 // TPing away (root agent is closed) or TPing/crossing in a region far enough away (client
409 // agent is closed).
410 // NOTE: In general, this doesn't mean that the agent logged out, just that it isn't around
411 // in one of the regions here anymore.
412 lock (m_rootAgents)
413 { 244 {
414 if (m_rootAgents.ContainsKey(AgentId)) 245 if (m_Friends[agentID].Refcount == 1)
415 { 246 m_Friends.Remove(agentID);
416 m_rootAgents.Remove(AgentId); 247 else
417 } 248 m_Friends[agentID].Refcount--;
418 } 249 }
419 } 250 }
420 251
421 private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) 252 private void OnLogout(IClientAPI client)
422 { 253 {
423 lock (m_rootAgents) 254 StatusChange(client.AgentId, false);
424 { 255 m_Friends.Remove(client.AgentId);
425 m_rootAgents[avatar.UUID] = avatar.RegionHandle;
426 // Claim User! my user! Mine mine mine!
427 }
428 } 256 }
429 257
430 private void MakeChildAgent(ScenePresence avatar) 258 private void OnMakeRootAgent(ScenePresence sp)
431 { 259 {
432 lock (m_rootAgents) 260 UUID agentID = sp.ControllingClient.AgentId;
261
262 if (m_Friends.ContainsKey(agentID))
433 { 263 {
434 if (m_rootAgents.ContainsKey(avatar.UUID)) 264 if (m_Friends[agentID].RegionID == UUID.Zero && m_Friends[agentID].Friends == null)
435 { 265 {
436 // only delete if the region matches. As this is a shared module, the avatar could be 266 m_Friends[agentID].Friends =
437 // root agent in another region on this server. 267 m_FriendsService.GetFriends(agentID);
438 if (m_rootAgents[avatar.UUID] == avatar.RegionHandle)
439 {
440 m_rootAgents.Remove(avatar.UUID);
441// m_log.Debug("[FRIEND]: Removing " + avatar.Firstname + " " + avatar.Lastname + " as a root agent");
442 }
443 } 268 }
269 m_Friends[agentID].RegionID =
270 sp.ControllingClient.Scene.RegionInfo.RegionID;
444 } 271 }
445 } 272 }
446 #endregion
447 273
448 private ScenePresence GetRootPresenceFromAgentID(UUID AgentID) 274
275 private void OnMakeChildAgent(ScenePresence sp)
449 { 276 {
450 ScenePresence returnAgent = null; 277 UUID agentID = sp.ControllingClient.AgentId;
451 lock (m_scenes) 278
279 if (m_Friends.ContainsKey(agentID))
452 { 280 {
453 ScenePresence queryagent = null; 281 if (m_Friends[agentID].RegionID == sp.ControllingClient.Scene.RegionInfo.RegionID)
454 foreach (Scene scene in m_scenes.Values) 282 m_Friends[agentID].RegionID = UUID.Zero;
455 {
456 queryagent = scene.GetScenePresence(AgentID);
457 if (queryagent != null)
458 {
459 if (!queryagent.IsChildAgent)
460 {
461 returnAgent = queryagent;
462 break;
463 }
464 }
465 }
466 } 283 }
467 return returnAgent;
468 } 284 }
469 285
470 private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID) 286 private void OnClientLogin(IClientAPI client)
471 { 287 {
472 ScenePresence returnAgent = null; 288 UUID agentID = client.AgentId;
473 lock (m_scenes) 289
474 { 290 // Inform the friends that this user is online
475 ScenePresence queryagent = null; 291 StatusChange(agentID, true);
476 foreach (Scene scene in m_scenes.Values) 292
293 // Register that we need to send the list of online friends to this user
294 lock (m_NeedsListOfFriends)
295 if (!m_NeedsListOfFriends.Contains(agentID))
477 { 296 {
478 queryagent = scene.GetScenePresence(AgentID); 297 m_NeedsListOfFriends.Add(agentID);
479 if (queryagent != null)
480 {
481 returnAgent = queryagent;
482 break;
483 }
484 } 298 }
485 }
486 return returnAgent;
487 } 299 }
488 300
489 public void OfferFriendship(UUID fromUserId, IClientAPI toUserClient, string offerMessage) 301 public void SendFriendsOnlineIfNeeded(IClientAPI client)
490 { 302 {
491 CachedUserInfo userInfo = m_initialScene.CommsManager.UserProfileCacheService.GetUserDetails(fromUserId); 303 UUID agentID = client.AgentId;
492 304 if (m_NeedsListOfFriends.Contains(agentID))
493 if (userInfo != null)
494 {
495 GridInstantMessage msg = new GridInstantMessage(
496 toUserClient.Scene, fromUserId, userInfo.UserProfile.Name, toUserClient.AgentId,
497 (byte)InstantMessageDialog.FriendshipOffered, offerMessage, false, Vector3.Zero);
498
499 FriendshipOffered(msg);
500 }
501 else
502 { 305 {
503 m_log.ErrorFormat("[FRIENDS]: No user found for id {0} in OfferFriendship()", fromUserId); 306 if (!m_Friends.ContainsKey(agentID))
307 {
308 m_log.DebugFormat("[FRIENDS MODULE]: agent {0} not found in local cache", agentID);
309 return;
310 }
311
312 client = LocateClientObject(agentID);
313 if (client == null)
314 {
315 m_log.DebugFormat("[FRIENDS MODULE]: agent's client {0} not found in local scene", agentID);
316 return;
317 }
318
319 List<UUID> online = GetOnlineFriends(agentID);
320
321 if (online.Count > 0)
322 {
323 m_log.DebugFormat("[FRIENDS MODULE]: User {0} in region {1} has {2} friends online", client.AgentId, client.Scene.RegionInfo.RegionName, online.Count);
324 client.SendAgentOnline(online.ToArray());
325 }
326
327 lock (m_NeedsListOfFriends)
328 m_NeedsListOfFriends.Remove(agentID);
504 } 329 }
505 } 330 }
506 331
507 #region FriendRequestHandling 332 List<UUID> GetOnlineFriends(UUID userID)
508
509 private void OnInstantMessage(IClientAPI client, GridInstantMessage im)
510 { 333 {
511 // Friend Requests go by Instant Message.. using the dialog param 334 List<string> friendList = new List<string>();
512 // https://wiki.secondlife.com/wiki/ImprovedInstantMessage
513 335
514 if (im.dialog == (byte)InstantMessageDialog.FriendshipOffered) // 38 336 foreach (FriendInfo fi in m_Friends[userID].Friends)
515 { 337 {
516 // fromAgentName is the *destination* name (the friend we offer friendship to) 338 if (((fi.TheirFlags & 1) != 0) && (fi.TheirFlags != -1))
517 ScenePresence initiator = GetAnyPresenceFromAgentID(new UUID(im.fromAgentID)); 339 friendList.Add(fi.Friend);
518 im.fromAgentName = initiator != null ? initiator.Name : "(hippo)";
519
520 FriendshipOffered(im);
521 } 340 }
522 else if (im.dialog == (byte)InstantMessageDialog.FriendshipAccepted) // 39 341
523 { 342 PresenceInfo[] presence = PresenceService.GetAgents(friendList.ToArray());
524 FriendshipAccepted(client, im); 343
525 } 344 List<UUID> online = new List<UUID>();
526 else if (im.dialog == (byte)InstantMessageDialog.FriendshipDeclined) // 40 345
346 foreach (PresenceInfo pi in presence)
527 { 347 {
528 FriendshipDeclined(client, im); 348 if (pi.Online)
349 {
350 online.Add(new UUID(pi.UserID));
351 //m_log.DebugFormat("[XXX] {0} friend online {1}", userID, pi.UserID);
352 }
529 } 353 }
354
355 return online;
530 } 356 }
531
532 /// <summary>
533 /// Invoked when a user offers a friendship.
534 /// </summary>
535 ///
536 /// <param name="im"></param>
537 /// <param name="client"></param>
538 private void FriendshipOffered(GridInstantMessage im)
539 {
540 // this is triggered by the initiating agent:
541 // A local agent offers friendship to some possibly remote friend.
542 // A IM is triggered, processed here and sent to the friend (possibly in a remote region).
543 357
544 m_log.DebugFormat("[FRIEND]: Offer(38) - From: {0}, FromName: {1} To: {2}, Session: {3}, Message: {4}, Offline {5}", 358 //
545 im.fromAgentID, im.fromAgentName, im.toAgentID, im.imSessionID, im.message, im.offline); 359 // Find the client for a ID
360 //
361 public IClientAPI LocateClientObject(UUID agentID)
362 {
363 Scene scene = GetClientScene(agentID);
364 if(scene == null)
365 return null;
546 366
547 // 1.20 protocol sends an UUID in the message field, instead of the friendship offer text. 367 ScenePresence presence = scene.GetScenePresence(agentID);
548 // For interoperability, we have to clear that 368 if(presence == null)
549 if (Util.isUUID(im.message)) im.message = ""; 369 return null;
550 370
551 // be sneeky and use the initiator-UUID as transactionID. This means we can be stateless. 371 return presence.ControllingClient;
552 // we have to look up the agent name on friendship-approval, though. 372 }
553 im.imSessionID = im.fromAgentID;
554 373
555 if (m_TransferModule != null) 374 //
375 // Find the scene for an agent
376 //
377 private Scene GetClientScene(UUID agentId)
378 {
379 lock (m_Scenes)
556 { 380 {
557 // Send it to whoever is the destination. 381 foreach (Scene scene in m_Scenes)
558 // If new friend is local, it will send an IM to the viewer. 382 {
559 // If new friend is remote, it will cause a OnGridInstantMessage on the remote server 383 ScenePresence presence = scene.GetScenePresence(agentId);
560 m_TransferModule.SendInstantMessage( 384 if (presence != null)
561 im,
562 delegate(bool success)
563 { 385 {
564 m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success); 386 if (!presence.IsChildAgent)
387 return scene;
565 } 388 }
566 ); 389 }
567 } 390 }
391 return null;
568 } 392 }
569 393
570 /// <summary>
571 /// Invoked when a user accepts a friendship offer.
572 /// </summary>
573 /// <param name="im"></param>
574 /// <param name="client"></param>
575 private void FriendshipAccepted(IClientAPI client, GridInstantMessage im)
576 {
577 m_log.DebugFormat("[FRIEND]: 39 - from client {0}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})",
578 client.AgentId, im.fromAgentID, im.fromAgentName, im.imSessionID, im.toAgentID, im.message, im.dialog);
579 }
580
581 /// <summary> 394 /// <summary>
582 /// Invoked when a user declines a friendship offer. 395 /// Caller beware! Call this only for root agents.
583 /// </summary> 396 /// </summary>
584 /// May not currently be used - see OnDenyFriendRequest() instead 397 /// <param name="agentID"></param>
585 /// <param name="im"></param> 398 /// <param name="online"></param>
586 /// <param name="client"></param> 399 private void StatusChange(UUID agentID, bool online)
587 private void FriendshipDeclined(IClientAPI client, GridInstantMessage im)
588 { 400 {
589 UUID fromAgentID = new UUID(im.fromAgentID); 401 if (m_Friends.ContainsKey(agentID))
590 UUID toAgentID = new UUID(im.toAgentID); 402 {
591 403 List<FriendInfo> friendList = new List<FriendInfo>();
592 // declining the friendship offer causes a type 40 IM being sent to the (possibly remote) initiator 404 foreach (FriendInfo fi in m_Friends[agentID].Friends)
593 // toAgentID is initiator, fromAgentID declined friendship 405 {
594 m_log.DebugFormat("[FRIEND]: 40 - from client {0}, agent {1} {2}, imsession {3} to {4}: {5} (dialog {6})", 406 if (((fi.MyFlags & 1) != 0) && (fi.TheirFlags != -1))
595 client != null ? client.AgentId.ToString() : "<null>", 407 friendList.Add(fi);
596 fromAgentID, im.fromAgentName, im.imSessionID, im.toAgentID, im.message, im.dialog);
597
598 // Send the decline to whoever is the destination.
599 GridInstantMessage msg
600 = new GridInstantMessage(
601 client.Scene, fromAgentID, client.Name, toAgentID,
602 im.dialog, im.message, im.offline != 0, im.Position);
603
604 // If new friend is local, it will send an IM to the viewer.
605 // If new friend is remote, it will cause a OnGridInstantMessage on the remote server
606 m_TransferModule.SendInstantMessage(msg,
607 delegate(bool success) {
608 m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
609 } 408 }
610 ); 409 foreach (FriendInfo fi in friendList)
410 {
411 // Notify about this user status
412 StatusNotify(fi, agentID, online);
413 }
414 }
611 } 415 }
612 416
613 private void OnGridInstantMessage(GridInstantMessage msg) 417 private void StatusNotify(FriendInfo friend, UUID userID, bool online)
614 { 418 {
615 // This event won't be raised unless we have that agent, 419 UUID friendID = UUID.Zero;
616 // so we can depend on the above not trying to send
617 // via grid again
618 //m_log.DebugFormat("[FRIEND]: Got GridIM from {0}, to {1}, imSession {2}, message {3}, dialog {4}",
619 // msg.fromAgentID, msg.toAgentID, msg.imSessionID, msg.message, msg.dialog);
620
621 if (msg.dialog == (byte)InstantMessageDialog.FriendshipOffered ||
622 msg.dialog == (byte)InstantMessageDialog.FriendshipAccepted ||
623 msg.dialog == (byte)InstantMessageDialog.FriendshipDeclined)
624 {
625 // this should succeed as we *know* the root agent is here.
626 m_TransferModule.SendInstantMessage(msg,
627 delegate(bool success) {
628 //m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
629 }
630 );
631 }
632 420
633 if (msg.dialog == (byte)InstantMessageDialog.FriendshipAccepted) 421 if (UUID.TryParse(friend.Friend, out friendID))
634 { 422 {
635 // for accept friendship, we have to do a bit more 423 // Try local
636 ApproveFriendship(new UUID(msg.fromAgentID), new UUID(msg.toAgentID), msg.fromAgentName); 424 if (LocalStatusNotification(userID, friendID, online))
425 return;
426
427 // The friend is not here [as root]. Let's forward.
428 PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() });
429 PresenceInfo friendSession = PresenceInfo.GetOnlinePresence(friendSessions);
430 if (friendSession != null)
431 {
432 GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
433 m_FriendsSimConnector.StatusNotify(region, userID, friendID, online);
434 }
435
436 // Friend is not online. Ignore.
637 } 437 }
638 } 438 }
639 439
640 private void ApproveFriendship(UUID fromAgentID, UUID toAgentID, string fromName) 440 private void OnInstantMessage(IClientAPI client, GridInstantMessage im)
641 { 441 {
642 m_log.DebugFormat("[FRIEND]: Approve friendship from {0} (ID: {1}) to {2}", 442 if (im.dialog == (byte)OpenMetaverse.InstantMessageDialog.FriendshipOffered)
643 fromAgentID, fromName, toAgentID); 443 {
444 // we got a friendship offer
445 UUID principalID = new UUID(im.fromAgentID);
446 UUID friendID = new UUID(im.toAgentID);
644 447
645 // a new friend was added in the initiator's and friend's data, so the cache entries are wrong now. 448 m_log.DebugFormat("[FRIENDS]: {0} offered friendship to {1}", principalID, friendID);
646 lock (m_friendLists) 449
647 { 450 // This user wants to be friends with the other user.
648 m_friendLists.Invalidate(fromAgentID.ToString()); 451 // Let's add both relations to the DB, but one of them is inactive (-1)
649 m_friendLists.Invalidate(toAgentID.ToString()); 452 FriendsService.StoreFriend(friendID, principalID.ToString(), 0);
453
454 // Now let's ask the other user to be friends with this user
455 ForwardFriendshipOffer(principalID, friendID, im);
650 } 456 }
457 }
651 458
652 // now send presence update and add a calling card for the new friend 459 private void ForwardFriendshipOffer(UUID agentID, UUID friendID, GridInstantMessage im)
460 {
461 // !!!!!!!! This is a hack so that we don't have to keep state (transactionID/imSessionID)
462 // We stick this agent's ID as imSession, so that it's directly available on the receiving end
463 im.imSessionID = im.fromAgentID;
653 464
654 ScenePresence initiator = GetAnyPresenceFromAgentID(toAgentID); 465 // Try the local sim
655 if (initiator == null) 466 if (LocalFriendshipOffered(friendID, im))
656 {
657 // quite wrong. Shouldn't happen.
658 m_log.WarnFormat("[FRIEND]: Coudn't find initiator of friend request {0}", toAgentID);
659 return; 467 return;
660 }
661 468
662 m_log.DebugFormat("[FRIEND]: Tell {0} that {1} is online", 469 // The prospective friend is not here [as root]. Let's forward.
663 initiator.Name, fromName); 470 PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() });
664 // tell initiator that friend is online 471 PresenceInfo friendSession = PresenceInfo.GetOnlinePresence(friendSessions);
665 initiator.ControllingClient.SendAgentOnline(new UUID[] { fromAgentID }); 472 if (friendSession != null)
666
667 // find the folder for the friend...
668 //InventoryFolderImpl folder =
669 // initiator.Scene.CommsManager.UserProfileCacheService.GetUserDetails(toAgentID).FindFolderForType((int)InventoryType.CallingCard);
670 IInventoryService invService = initiator.Scene.InventoryService;
671 InventoryFolderBase folder = invService.GetFolderForType(toAgentID, AssetType.CallingCard);
672 if (folder != null)
673 { 473 {
674 // ... and add the calling card 474 GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
675 CreateCallingCard(initiator.ControllingClient, fromAgentID, folder.ID, fromName); 475 m_FriendsSimConnector.FriendshipOffered(region, agentID, friendID, im.message);
676 } 476 }
477
478 // If the prospective friend is not online, he'll get the message upon login.
677 } 479 }
678 480
679 private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List<UUID> callingCardFolders) 481 private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List<UUID> callingCardFolders)
680 { 482 {
681 m_log.DebugFormat("[FRIEND]: Got approve friendship from {0} {1}, agentID {2}, tid {3}", 483 FriendsService.StoreFriend(agentID, friendID.ToString(), 1);
682 client.Name, client.AgentId, agentID, friendID); 484 FriendsService.StoreFriend(friendID, agentID.ToString(), 1);
485 // update the local cache
486 m_Friends[agentID].Friends = FriendsService.GetFriends(agentID);
683 487
684 // store the new friend persistently for both avatars 488 m_log.DebugFormat("[FRIENDS]: {0} accepted friendship from {1}", agentID, friendID);
685 m_initialScene.StoreAddFriendship(friendID, agentID, (uint) FriendRights.CanSeeOnline);
686 489
687 // The cache entries aren't valid anymore either, as we just added a friend to both sides. 490 //
688 lock (m_friendLists) 491 // Notify the friend
689 { 492 //
690 m_friendLists.Invalidate(agentID.ToString());
691 m_friendLists.Invalidate(friendID.ToString());
692 }
693
694 // if it's a local friend, we don't have to do the lookup
695 ScenePresence friendPresence = GetAnyPresenceFromAgentID(friendID);
696 493
697 if (friendPresence != null) 494 // Try Local
495 if (LocalFriendshipApproved(agentID, client.Name, friendID))
698 { 496 {
699 m_log.Debug("[FRIEND]: Local agent detected."); 497 client.SendAgentOnline(new UUID[] { friendID });
700 498 return;
701 // create calling card
702 CreateCallingCard(client, friendID, callingCardFolders[0], friendPresence.Name);
703
704 // local message means OnGridInstantMessage won't be triggered, so do the work here.
705 friendPresence.ControllingClient.SendInstantMessage(
706 new GridInstantMessage(client.Scene, agentID,
707 client.Name, friendID,
708 (byte)InstantMessageDialog.FriendshipAccepted,
709 agentID.ToString(), false, Vector3.Zero));
710 ApproveFriendship(agentID, friendID, client.Name);
711 } 499 }
712 else
713 {
714 m_log.Debug("[FRIEND]: Remote agent detected.");
715 500
716 // fetch the friend's name for the calling card. 501 // The friend is not here
717 CachedUserInfo info = m_initialScene.CommsManager.UserProfileCacheService.GetUserDetails(friendID); 502 PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() });
718 503 PresenceInfo friendSession = PresenceInfo.GetOnlinePresence(friendSessions);
719 // create calling card 504 if (friendSession != null)
720 CreateCallingCard(client, friendID, callingCardFolders[0], 505 {
721 info.UserProfile.FirstName + " " + info.UserProfile.SurName); 506 GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
722 507 m_FriendsSimConnector.FriendshipApproved(region, agentID, client.Name, friendID);
723 // Compose (remote) response to friend. 508 client.SendAgentOnline(new UUID[] { friendID });
724 GridInstantMessage msg = new GridInstantMessage(client.Scene, agentID, client.Name, friendID,
725 (byte)InstantMessageDialog.FriendshipAccepted,
726 agentID.ToString(), false, Vector3.Zero);
727 if (m_TransferModule != null)
728 {
729 m_TransferModule.SendInstantMessage(msg,
730 delegate(bool success) {
731 m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
732 }
733 );
734 }
735 } 509 }
736 510
737 // tell client that new friend is online
738 client.SendAgentOnline(new UUID[] { friendID });
739 } 511 }
740 512
741 private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List<UUID> callingCardFolders) 513 private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List<UUID> callingCardFolders)
742 { 514 {
743 m_log.DebugFormat("[FRIEND]: Got deny friendship from {0} {1}, agentID {2}, tid {3}", 515 m_log.DebugFormat("[FRIENDS]: {0} denied friendship to {1}", agentID, friendID);
744 client.Name, client.AgentId, agentID, friendID); 516
745 517 FriendsService.Delete(agentID, friendID.ToString());
746 // Compose response to other agent. 518 FriendsService.Delete(friendID, agentID.ToString());
747 GridInstantMessage msg = new GridInstantMessage(client.Scene, agentID, client.Name, friendID, 519
748 (byte)InstantMessageDialog.FriendshipDeclined, 520 //
749 agentID.ToString(), false, Vector3.Zero); 521 // Notify the friend
750 // send decline to initiator 522 //
751 if (m_TransferModule != null) 523
524 // Try local
525 if (LocalFriendshipDenied(agentID, client.Name, friendID))
526 return;
527
528 PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() });
529 PresenceInfo friendSession = PresenceInfo.GetOnlinePresence(friendSessions);
530 if (friendSession != null)
752 { 531 {
753 m_TransferModule.SendInstantMessage(msg, 532 GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
754 delegate(bool success) { 533 m_FriendsSimConnector.FriendshipDenied(region, agentID, client.Name, friendID);
755 m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
756 }
757 );
758 } 534 }
759 } 535 }
760 536
761 private void OnTerminateFriendship(IClientAPI client, UUID agentID, UUID exfriendID) 537 private void OnTerminateFriendship(IClientAPI client, UUID agentID, UUID exfriendID)
762 { 538 {
763 // client.AgentId == agentID! 539 FriendsService.Delete(agentID, exfriendID.ToString());
764 540 FriendsService.Delete(exfriendID, agentID.ToString());
765 // this removes the friends from the stored friendlists. After the next login, they will be gone...
766 m_initialScene.StoreRemoveFriendship(agentID, exfriendID);
767 541
768 // ... now tell the two involved clients that they aren't friends anymore. 542 // Update local cache
543 m_Friends[agentID].Friends = FriendsService.GetFriends(agentID);
769 544
770 // I don't know why we have to tell <agent>, as this was caused by her, but that's how it works in SL...
771 client.SendTerminateFriend(exfriendID); 545 client.SendTerminateFriend(exfriendID);
772 546
773 // now send the friend, if online 547 //
774 ScenePresence presence = GetAnyPresenceFromAgentID(exfriendID); 548 // Notify the friend
775 if (presence != null) 549 //
776 {
777 m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", agentID, exfriendID);
778 presence.ControllingClient.SendTerminateFriend(agentID);
779 }
780 else
781 {
782 // retry 3 times, in case the agent TPed from the last known region...
783 for (int retry = 0; retry < 3; ++retry)
784 {
785 // wasn't sent, so ex-friend wasn't around on this region-server. Fetch info and try to send
786 UserAgentData data = m_initialScene.CommsManager.UserService.GetAgentByUUID(exfriendID);
787
788 if (null == data)
789 break;
790
791 if (!data.AgentOnline)
792 {
793 m_log.DebugFormat("[FRIEND]: {0} is offline, so not sending TerminateFriend", exfriendID);
794 break; // if ex-friend isn't online, we don't need to send
795 }
796
797 m_log.DebugFormat("[FRIEND]: Sending remote terminate friend {0} to agent {1}@{2}",
798 agentID, exfriendID, data.Handle);
799 550
800 // try to send to foreign region, retry if it fails (friend TPed away, for example) 551 // Try local
801 if (TriggerTerminateFriend(data.Handle, exfriendID, agentID)) break; 552 if (LocalFriendshipTerminated(exfriendID))
802 } 553 return;
803 }
804 554
805 // clean up cache: FriendList is wrong now... 555 PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { exfriendID.ToString() });
806 lock (m_friendLists) 556 PresenceInfo friendSession = PresenceInfo.GetOnlinePresence(friendSessions);
557 if (friendSession != null)
807 { 558 {
808 m_friendLists.Invalidate(agentID.ToString()); 559 GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
809 m_friendLists.Invalidate(exfriendID.ToString()); 560 m_FriendsSimConnector.FriendshipTerminated(region, agentID, exfriendID);
810 } 561 }
811 } 562 }
812 563
813 #endregion 564 private void OnGrantUserRights(IClientAPI remoteClient, UUID requester, UUID target, int rights)
814
815 #region CallingCards
816
817 private void OnOfferCallingCard(IClientAPI client, UUID destID, UUID transactionID)
818 { 565 {
819 m_log.DebugFormat("[CALLING CARD]: got offer from {0} for {1}, transaction {2}", 566 if (!m_Friends.ContainsKey(remoteClient.AgentId))
820 client.AgentId, destID, transactionID);
821 // This might be slightly wrong. On a multi-region server, we might get the child-agent instead of the root-agent
822 // (or the root instead of the child)
823 ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
824 if (destAgent == null)
825 {
826 client.SendAlertMessage("The person you have offered a card to can't be found anymore.");
827 return; 567 return;
828 }
829
830 lock (m_pendingCallingcardRequests)
831 {
832 m_pendingCallingcardRequests[transactionID] = client.AgentId;
833 }
834 // inform the destination agent about the offer
835 destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID);
836 }
837 568
838 private void CreateCallingCard(IClientAPI client, UUID creator, UUID folder, string name) 569 m_log.DebugFormat("[FRIENDS MODULE]: User {0} changing rights to {1} for friend {2}", requester, rights, target);
839 { 570 // Let's find the friend in this user's friend list
840 InventoryItemBase item = new InventoryItemBase(); 571 UserFriendData fd = m_Friends[remoteClient.AgentId];
841 item.AssetID = UUID.Zero; 572 FriendInfo friend = null;
842 item.AssetType = (int)AssetType.CallingCard; 573 foreach (FriendInfo fi in fd.Friends)
843 item.BasePermissions = (uint)PermissionMask.Copy; 574 if (fi.Friend == target.ToString())
844 item.CreationDate = Util.UnixTimeSinceEpoch(); 575 friend = fi;
845 item.CreatorId = creator.ToString();
846 item.CurrentPermissions = item.BasePermissions;
847 item.Description = "";
848 item.EveryOnePermissions = (uint)PermissionMask.None;
849 item.Flags = 0;
850 item.Folder = folder;
851 item.GroupID = UUID.Zero;
852 item.GroupOwned = false;
853 item.ID = UUID.Random();
854 item.InvType = (int)InventoryType.CallingCard;
855 item.Name = name;
856 item.NextPermissions = item.EveryOnePermissions;
857 item.Owner = client.AgentId;
858 item.SalePrice = 10;
859 item.SaleType = (byte)SaleType.Not;
860 ((Scene)client.Scene).AddInventoryItem(client, item);
861 }
862 576
863 private void OnAcceptCallingCard(IClientAPI client, UUID transactionID, UUID folderID) 577 if (friend != null) // Found it
864 {
865 m_log.DebugFormat("[CALLING CARD]: User {0} ({1} {2}) accepted tid {3}, folder {4}",
866 client.AgentId,
867 client.FirstName, client.LastName,
868 transactionID, folderID);
869 UUID destID;
870 lock (m_pendingCallingcardRequests)
871 { 578 {
872 if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) 579 // Store it on the DB
873 { 580 FriendsService.StoreFriend(requester, target.ToString(), rights);
874 m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.",
875 client.Name);
876 return;
877 }
878 // else found pending calling card request with that transaction.
879 m_pendingCallingcardRequests.Remove(transactionID);
880 }
881 581
582 // Store it in the local cache
583 int myFlags = friend.MyFlags;
584 friend.MyFlags = rights;
882 585
883 ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); 586 // Always send this back to the original client
884 // inform sender of the card that destination declined the offer 587 remoteClient.SendChangeUserRights(requester, target, rights);
885 if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID);
886 588
887 // put a calling card into the inventory of receiver 589 //
888 CreateCallingCard(client, destID, folderID, destAgent.Name); 590 // Notify the friend
889 } 591 //
890 592
891 private void OnDeclineCallingCard(IClientAPI client, UUID transactionID) 593 // Try local
892 { 594 if (LocalGrantRights(requester, target, myFlags, rights))
893 m_log.DebugFormat("[CALLING CARD]: User {0} (ID:{1}) declined card, tid {2}",
894 client.Name, client.AgentId, transactionID);
895 UUID destID;
896 lock (m_pendingCallingcardRequests)
897 {
898 if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID))
899 {
900 m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.",
901 client.Name);
902 return; 595 return;
596
597 PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { target.ToString() });
598 PresenceInfo friendSession = PresenceInfo.GetOnlinePresence(friendSessions);
599 if (friendSession != null)
600 {
601 GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
602 // TODO: You might want to send the delta to save the lookup
603 // on the other end!!
604 m_FriendsSimConnector.GrantRights(region, requester, target, myFlags, rights);
903 } 605 }
904 // else found pending calling card request with that transaction.
905 m_pendingCallingcardRequests.Remove(transactionID);
906 } 606 }
907
908 ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
909 // inform sender of the card that destination declined the offer
910 if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID);
911 } 607 }
912 608
913 /// <summary> 609 #region Local
914 /// Send presence information about a client to other clients in both this region and others.
915 /// </summary>
916 /// <param name="client"></param>
917 /// <param name="friendList"></param>
918 /// <param name="iAmOnline"></param>
919 private void SendPresenceState(IClientAPI client, List<FriendListItem> friendList, bool iAmOnline)
920 {
921 //m_log.DebugFormat("[FRIEND]: {0} logged {1}; sending presence updates", client.Name, iAmOnline ? "in" : "out");
922 610
923 if (friendList == null || friendList.Count == 0) 611 public bool LocalFriendshipOffered(UUID toID, GridInstantMessage im)
612 {
613 IClientAPI friendClient = LocateClientObject(toID);
614 if (friendClient != null)
924 { 615 {
925 //m_log.DebugFormat("[FRIEND]: {0} doesn't have friends.", client.Name); 616 // the prospective friend in this sim as root agent
926 return; // nothing we can do if she doesn't have friends... 617 friendClient.SendInstantMessage(im);
618 // we're done
619 return true;
927 } 620 }
621 return false;
622 }
928 623
929 // collect sets of friendIDs; to send to (online and offline), and to receive from 624 public bool LocalFriendshipApproved(UUID userID, string userName, UUID friendID)
930 // TODO: If we ever switch to .NET >= 3, replace those Lists with HashSets. 625 {
931 // I can't believe that we have Dictionaries, but no Sets, considering Java introduced them years ago... 626 IClientAPI friendClient = LocateClientObject(friendID);
932 List<UUID> friendIDsToSendTo = new List<UUID>(); 627 if (friendClient != null)
933 List<UUID> candidateFriendIDsToReceive = new List<UUID>();
934
935 foreach (FriendListItem item in friendList)
936 { 628 {
937 if (((item.FriendListOwnerPerms | item.FriendPerms) & (uint)FriendRights.CanSeeOnline) != 0) 629 // the prospective friend in this sim as root agent
938 { 630 GridInstantMessage im = new GridInstantMessage(Scene, userID, userName, friendID,
939 // friend is allowed to see my presence => add 631 (byte)OpenMetaverse.InstantMessageDialog.FriendshipAccepted, userID.ToString(), false, Vector3.Zero);
940 if ((item.FriendListOwnerPerms & (uint)FriendRights.CanSeeOnline) != 0) 632 friendClient.SendInstantMessage(im);
941 friendIDsToSendTo.Add(item.Friend); 633 // update the local cache
942 634 m_Friends[friendID].Friends = FriendsService.GetFriends(friendID);
943 if ((item.FriendPerms & (uint)FriendRights.CanSeeOnline) != 0) 635 // we're done
944 candidateFriendIDsToReceive.Add(item.Friend); 636 return true;
945 }
946 } 637 }
947 638
948 // we now have a list of "interesting" friends (which we have to find out on-/offline state for), 639 return false;
949 // friends we want to send our online state to (if *they* are online, too), and
950 // friends we want to receive online state for (currently unknown whether online or not)
951
952 // as this processing might take some time and friends might TP away, we try up to three times to
953 // reach them. Most of the time, we *will* reach them, and this loop won't loop
954 int retry = 0;
955 do
956 {
957 // build a list of friends to look up region-information and on-/offline-state for
958 List<UUID> friendIDsToLookup = new List<UUID>(friendIDsToSendTo);
959 foreach (UUID uuid in candidateFriendIDsToReceive)
960 {
961 if (!friendIDsToLookup.Contains(uuid)) friendIDsToLookup.Add(uuid);
962 }
963
964 m_log.DebugFormat(
965 "[FRIEND]: {0} to lookup, {1} to send to, {2} candidates to receive from for agent {3}",
966 friendIDsToLookup.Count, friendIDsToSendTo.Count, candidateFriendIDsToReceive.Count, client.Name);
967
968 // we have to fetch FriendRegionInfos, as the (cached) FriendListItems don't
969 // necessarily contain the correct online state...
970 Dictionary<UUID, FriendRegionInfo> friendRegions = m_initialScene.GetFriendRegionInfos(friendIDsToLookup);
971 m_log.DebugFormat(
972 "[FRIEND]: Found {0} regionInfos for {1} friends of {2}",
973 friendRegions.Count, friendIDsToLookup.Count, client.Name);
974
975 // argument for SendAgentOn/Offline; we shouldn't generate that repeatedly within loops.
976 UUID[] agentArr = new UUID[] { client.AgentId };
977
978 // first, send to friend presence state to me, if I'm online...
979 if (iAmOnline)
980 {
981 List<UUID> friendIDsToReceive = new List<UUID>();
982
983 for (int i = candidateFriendIDsToReceive.Count - 1; i >= 0; --i)
984 {
985 UUID uuid = candidateFriendIDsToReceive[i];
986 FriendRegionInfo info;
987 if (friendRegions.TryGetValue(uuid, out info) && info != null && info.isOnline)
988 {
989 friendIDsToReceive.Add(uuid);
990 }
991 }
992
993 m_log.DebugFormat(
994 "[FRIEND]: Sending {0} online friends to {1}", friendIDsToReceive.Count, client.Name);
995
996 if (friendIDsToReceive.Count > 0)
997 client.SendAgentOnline(friendIDsToReceive.ToArray());
998
999 // clear them for a possible second iteration; we don't have to repeat this
1000 candidateFriendIDsToReceive.Clear();
1001 }
1002
1003 // now, send my presence state to my friends
1004 for (int i = friendIDsToSendTo.Count - 1; i >= 0; --i)
1005 {
1006 UUID uuid = friendIDsToSendTo[i];
1007 FriendRegionInfo info;
1008 if (friendRegions.TryGetValue(uuid, out info) && info != null && info.isOnline)
1009 {
1010 // any client is good enough, root or child...
1011 ScenePresence agent = GetAnyPresenceFromAgentID(uuid);
1012 if (agent != null)
1013 {
1014 //m_log.DebugFormat("[FRIEND]: Found local agent {0}", agent.Name);
1015
1016 // friend is online and on this server...
1017 if (iAmOnline) agent.ControllingClient.SendAgentOnline(agentArr);
1018 else agent.ControllingClient.SendAgentOffline(agentArr);
1019
1020 // done, remove it
1021 friendIDsToSendTo.RemoveAt(i);
1022 }
1023 }
1024 else
1025 {
1026 //m_log.DebugFormat("[FRIEND]: Friend {0} ({1}) is offline; not sending.", uuid, i);
1027
1028 // friend is offline => no need to try sending
1029 friendIDsToSendTo.RemoveAt(i);
1030 }
1031 }
1032
1033 m_log.DebugFormat("[FRIEND]: Have {0} friends to contact via inter-region comms.", friendIDsToSendTo.Count);
1034
1035 // we now have all the friends left that are online (we think), but not on this region-server
1036 if (friendIDsToSendTo.Count > 0)
1037 {
1038 // sort them into regions
1039 Dictionary<ulong, List<UUID>> friendsInRegion = new Dictionary<ulong,List<UUID>>();
1040 foreach (UUID uuid in friendIDsToSendTo)
1041 {
1042 ulong handle = friendRegions[uuid].regionHandle; // this can't fail as we filtered above already
1043 List<UUID> friends;
1044 if (!friendsInRegion.TryGetValue(handle, out friends))
1045 {
1046 friends = new List<UUID>();
1047 friendsInRegion[handle] = friends;
1048 }
1049 friends.Add(uuid);
1050 }
1051 m_log.DebugFormat("[FRIEND]: Found {0} regions to send to.", friendRegions.Count);
1052
1053 // clear uuids list and collect missed friends in it for the next retry
1054 friendIDsToSendTo.Clear();
1055
1056 // send bulk updates to the region
1057 foreach (KeyValuePair<ulong, List<UUID>> pair in friendsInRegion)
1058 {
1059 //m_log.DebugFormat("[FRIEND]: Inform {0} friends in region {1} that user {2} is {3}line",
1060 // pair.Value.Count, pair.Key, client.Name, iAmOnline ? "on" : "off");
1061
1062 friendIDsToSendTo.AddRange(InformFriendsInOtherRegion(client.AgentId, pair.Key, pair.Value, iAmOnline));
1063 }
1064 }
1065 // now we have in friendIDsToSendTo only the agents left that TPed away while we tried to contact them.
1066 // In most cases, it will be empty, and it won't loop here. But sometimes, we have to work harder and try again...
1067 }
1068 while (++retry < 3 && friendIDsToSendTo.Count > 0);
1069 } 640 }
1070 641
1071 private void OnEconomyDataRequest(UUID agentID) 642 public bool LocalFriendshipDenied(UUID userID, string userName, UUID friendID)
1072 { 643 {
1073 // KLUDGE: This is the only way I found to get a message (only) after login was completed and the 644 IClientAPI friendClient = LocateClientObject(friendID);
1074 // client is connected enough to receive UDP packets). 645 if (friendClient != null)
1075 // This packet seems to be sent only once, just after connection was established to the first
1076 // region after login.
1077 // We use it here to trigger a presence update; the old update-on-login was never be heard by
1078 // the freshly logged in viewer, as it wasn't connected to the region at that time.
1079 // TODO: Feel free to replace this by a better solution if you find one.
1080
1081 // get the agent. This should work every time, as we just got a packet from it
1082 //ScenePresence agent = GetRootPresenceFromAgentID(agentID);
1083 // KLUDGE 2: As this is sent quite early, the avatar isn't here as root agent yet. So, we have to cheat a bit
1084 ScenePresence agent = GetAnyPresenceFromAgentID(agentID);
1085
1086 // just to be paranoid...
1087 if (agent == null)
1088 { 646 {
1089 m_log.ErrorFormat("[FRIEND]: Got a packet from agent {0} who can't be found anymore!?", agentID); 647 // the prospective friend in this sim as root agent
1090 return;
1091 }
1092 648
1093 List<FriendListItem> fl; 649 GridInstantMessage im = new GridInstantMessage(Scene, userID, userName, friendID,
1094 lock (m_friendLists) 650 (byte)OpenMetaverse.InstantMessageDialog.FriendshipDeclined, userID.ToString(), false, Vector3.Zero);
1095 { 651 friendClient.SendInstantMessage(im);
1096 fl = (List<FriendListItem>)m_friendLists.Get(agent.ControllingClient.AgentId.ToString(), 652 // we're done
1097 m_initialScene.GetFriendList); 653 return true;
1098 } 654 }
1099 655
1100 // tell everyone that we are online 656 return false;
1101 SendPresenceState(agent.ControllingClient, fl, true);
1102 } 657 }
1103 658
1104 private void OnLogout(IClientAPI remoteClient) 659 public bool LocalFriendshipTerminated(UUID exfriendID)
1105 { 660 {
1106 List<FriendListItem> fl; 661 IClientAPI friendClient = LocateClientObject(exfriendID);
1107 lock (m_friendLists) 662 if (friendClient != null)
1108 { 663 {
1109 fl = (List<FriendListItem>)m_friendLists.Get(remoteClient.AgentId.ToString(), 664 // the friend in this sim as root agent
1110 m_initialScene.GetFriendList); 665 friendClient.SendTerminateFriend(exfriendID);
666 // update local cache
667 m_Friends[exfriendID].Friends = FriendsService.GetFriends(exfriendID);
668 // we're done
669 return true;
1111 } 670 }
1112 671
1113 // tell everyone that we are offline 672 return false;
1114 SendPresenceState(remoteClient, fl, false);
1115 } 673 }
1116 private void GrantUserFriendRights(IClientAPI remoteClient, UUID requester, UUID target, int rights) 674
1117 { 675 public bool LocalGrantRights(UUID userID, UUID friendID, int userFlags, int rights)
1118 ((Scene)remoteClient.Scene).CommsManager.UpdateUserFriendPerms(requester, target, (uint)rights);
1119 }
1120 public void FindAgent(IClientAPI remoteClient, UUID hunter, UUID target)
1121 { 676 {
1122 List<FriendListItem> friendList = GetUserFriends(hunter); 677 IClientAPI friendClient = LocateClientObject(friendID);
1123 foreach (FriendListItem item in friendList) 678 if (friendClient != null)
1124 { 679 {
1125 if (item.onlinestatus == true) 680 bool onlineBitChanged = ((rights ^ userFlags) & (int)FriendRights.CanSeeOnline) != 0;
681 if (onlineBitChanged)
1126 { 682 {
1127 if (item.Friend == target && (item.FriendPerms & (uint)FriendRights.CanSeeOnMap) != 0) 683 if ((rights & (int)FriendRights.CanSeeOnline) == 1)
1128 { 684 friendClient.SendAgentOnline(new UUID[] { new UUID(userID) });
1129 ScenePresence SPTarget = ((Scene)remoteClient.Scene).GetScenePresence(target); 685 else
1130 string regionname = SPTarget.Scene.RegionInfo.RegionName; 686 friendClient.SendAgentOffline(new UUID[] { new UUID(userID) });
1131 remoteClient.SendScriptTeleportRequest("FindAgent", regionname,new Vector3(SPTarget.AbsolutePosition),new Vector3(SPTarget.Lookat));
1132 }
1133 } 687 }
1134 else 688 else
1135 { 689 {
1136 remoteClient.SendAgentAlertMessage("The agent you are looking for is not online.", false); 690 bool canEditObjectsChanged = ((rights ^ userFlags) & (int)FriendRights.CanModifyObjects) != 0;
691 if (canEditObjectsChanged)
692 friendClient.SendChangeUserRights(userID, friendID, rights);
1137 } 693 }
694
695 return true;
1138 } 696 }
697
698 return false;
699
1139 } 700 }
1140 701
1141 public List<FriendListItem> GetUserFriends(UUID agentID) 702 public bool LocalStatusNotification(UUID userID, UUID friendID, bool online)
1142 { 703 {
1143 List<FriendListItem> fl; 704 IClientAPI friendClient = LocateClientObject(friendID);
1144 lock (m_friendLists) 705 if (friendClient != null)
1145 { 706 {
1146 fl = (List<FriendListItem>)m_friendLists.Get(agentID.ToString(), 707 //m_log.DebugFormat("[FRIENDS]: Notify {0} that user {1} is {2}", friend.Friend, userID, online);
1147 m_initialScene.GetFriendList); 708 // the friend in this sim as root agent
709 if (online)
710 friendClient.SendAgentOnline(new UUID[] { userID });
711 else
712 friendClient.SendAgentOffline(new UUID[] { userID });
713 // we're done
714 return true;
1148 } 715 }
1149 716
1150 return fl; 717 return false;
1151 } 718 }
719 #endregion
720
1152 } 721 }
1153 #endregion
1154} 722}