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