diff options
Diffstat (limited to 'OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs')
-rw-r--r-- | OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs | 1005 |
1 files changed, 1005 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..7ab568e --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs | |||
@@ -0,0 +1,1005 @@ | |||
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 OpenSimulator 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 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Linq; | ||
32 | using System.Reflection; | ||
33 | using System.Threading; | ||
34 | using log4net; | ||
35 | using Nini.Config; | ||
36 | using Nwc.XmlRpc; | ||
37 | using OpenMetaverse; | ||
38 | using Mono.Addins; | ||
39 | using OpenSim.Framework; | ||
40 | using OpenSim.Framework.Servers.HttpServer; | ||
41 | using OpenSim.Framework.Communications; | ||
42 | using OpenSim.Framework.Servers; | ||
43 | using OpenSim.Region.Framework.Interfaces; | ||
44 | using OpenSim.Region.Framework.Scenes; | ||
45 | using OpenSim.Services.Interfaces; | ||
46 | using OpenSim.Services.Connectors.Friends; | ||
47 | using OpenSim.Server.Base; | ||
48 | using FriendInfo = OpenSim.Services.Interfaces.FriendInfo; | ||
49 | using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo; | ||
50 | using GridRegion = OpenSim.Services.Interfaces.GridRegion; | ||
51 | |||
52 | namespace OpenSim.Region.CoreModules.Avatar.Friends | ||
53 | { | ||
54 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "FriendsModule")] | ||
55 | public class FriendsModule : ISharedRegionModule, IFriendsModule | ||
56 | { | ||
57 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
58 | |||
59 | protected bool m_Enabled = false; | ||
60 | |||
61 | protected class UserFriendData | ||
62 | { | ||
63 | public UUID PrincipalID; | ||
64 | public FriendInfo[] Friends; | ||
65 | public int Refcount; | ||
66 | |||
67 | public bool IsFriend(string friend) | ||
68 | { | ||
69 | foreach (FriendInfo fi in Friends) | ||
70 | { | ||
71 | if (fi.Friend == friend) | ||
72 | return true; | ||
73 | } | ||
74 | |||
75 | return false; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | protected static readonly FriendInfo[] EMPTY_FRIENDS = new FriendInfo[0]; | ||
80 | |||
81 | protected List<Scene> m_Scenes = new List<Scene>(); | ||
82 | |||
83 | protected IPresenceService m_PresenceService = null; | ||
84 | protected IFriendsService m_FriendsService = null; | ||
85 | protected FriendsSimConnector m_FriendsSimConnector; | ||
86 | |||
87 | /// <summary> | ||
88 | /// Cache friends lists for users. | ||
89 | /// </summary> | ||
90 | /// <remarks> | ||
91 | /// This is a complex and error-prone thing to do. At the moment, we assume that the efficiency gained in | ||
92 | /// permissions checks outweighs the disadvantages of that complexity. | ||
93 | /// </remarks> | ||
94 | protected Dictionary<UUID, UserFriendData> m_Friends = new Dictionary<UUID, UserFriendData>(); | ||
95 | |||
96 | /// <summary> | ||
97 | /// Maintain a record of clients that need to notify about their online status. This only | ||
98 | /// needs to be done on login. Subsequent online/offline friend changes are sent by a different mechanism. | ||
99 | /// </summary> | ||
100 | protected HashSet<UUID> m_NeedsToNotifyStatus = new HashSet<UUID>(); | ||
101 | |||
102 | /// <summary> | ||
103 | /// Maintain a record of viewers that need to be sent notifications for friends that are online. This only | ||
104 | /// needs to be done on login. Subsequent online/offline friend changes are sent by a different mechanism. | ||
105 | /// </summary> | ||
106 | protected HashSet<UUID> m_NeedsListOfOnlineFriends = new HashSet<UUID>(); | ||
107 | |||
108 | protected IPresenceService PresenceService | ||
109 | { | ||
110 | get | ||
111 | { | ||
112 | if (m_PresenceService == null) | ||
113 | { | ||
114 | if (m_Scenes.Count > 0) | ||
115 | m_PresenceService = m_Scenes[0].RequestModuleInterface<IPresenceService>(); | ||
116 | } | ||
117 | |||
118 | return m_PresenceService; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | public IFriendsService FriendsService | ||
123 | { | ||
124 | get | ||
125 | { | ||
126 | if (m_FriendsService == null) | ||
127 | { | ||
128 | if (m_Scenes.Count > 0) | ||
129 | m_FriendsService = m_Scenes[0].RequestModuleInterface<IFriendsService>(); | ||
130 | } | ||
131 | |||
132 | return m_FriendsService; | ||
133 | } | ||
134 | } | ||
135 | |||
136 | protected IGridService GridService | ||
137 | { | ||
138 | get { return m_Scenes[0].GridService; } | ||
139 | } | ||
140 | |||
141 | public IUserAccountService UserAccountService | ||
142 | { | ||
143 | get { return m_Scenes[0].UserAccountService; } | ||
144 | } | ||
145 | |||
146 | public IScene Scene | ||
147 | { | ||
148 | get | ||
149 | { | ||
150 | if (m_Scenes.Count > 0) | ||
151 | return m_Scenes[0]; | ||
152 | else | ||
153 | return null; | ||
154 | } | ||
155 | } | ||
156 | |||
157 | #region ISharedRegionModule | ||
158 | public void Initialise(IConfigSource config) | ||
159 | { | ||
160 | IConfig moduleConfig = config.Configs["Modules"]; | ||
161 | if (moduleConfig != null) | ||
162 | { | ||
163 | string name = moduleConfig.GetString("FriendsModule", "FriendsModule"); | ||
164 | if (name == Name) | ||
165 | { | ||
166 | InitModule(config); | ||
167 | |||
168 | m_Enabled = true; | ||
169 | m_log.DebugFormat("[FRIENDS MODULE]: {0} enabled.", Name); | ||
170 | } | ||
171 | } | ||
172 | } | ||
173 | |||
174 | protected virtual void InitModule(IConfigSource config) | ||
175 | { | ||
176 | IConfig friendsConfig = config.Configs["Friends"]; | ||
177 | if (friendsConfig != null) | ||
178 | { | ||
179 | int mPort = friendsConfig.GetInt("Port", 0); | ||
180 | |||
181 | string connector = friendsConfig.GetString("Connector", String.Empty); | ||
182 | Object[] args = new Object[] { config }; | ||
183 | |||
184 | m_FriendsService = ServerUtils.LoadPlugin<IFriendsService>(connector, args); | ||
185 | m_FriendsSimConnector = new FriendsSimConnector(); | ||
186 | |||
187 | // Instantiate the request handler | ||
188 | IHttpServer server = MainServer.GetHttpServer((uint)mPort); | ||
189 | |||
190 | if (server != null) | ||
191 | server.AddStreamHandler(new FriendsRequestHandler(this)); | ||
192 | } | ||
193 | |||
194 | if (m_FriendsService == null) | ||
195 | { | ||
196 | m_log.Error("[FRIENDS]: No Connector defined in section Friends, or failed to load, cannot continue"); | ||
197 | throw new Exception("Connector load error"); | ||
198 | } | ||
199 | } | ||
200 | |||
201 | public void PostInitialise() | ||
202 | { | ||
203 | } | ||
204 | |||
205 | public void Close() | ||
206 | { | ||
207 | } | ||
208 | |||
209 | public virtual void AddRegion(Scene scene) | ||
210 | { | ||
211 | if (!m_Enabled) | ||
212 | return; | ||
213 | |||
214 | // m_log.DebugFormat("[FRIENDS MODULE]: AddRegion on {0}", Name); | ||
215 | |||
216 | m_Scenes.Add(scene); | ||
217 | scene.RegisterModuleInterface<IFriendsModule>(this); | ||
218 | |||
219 | scene.EventManager.OnNewClient += OnNewClient; | ||
220 | scene.EventManager.OnClientClosed += OnClientClosed; | ||
221 | scene.EventManager.OnMakeRootAgent += OnMakeRootAgent; | ||
222 | scene.EventManager.OnClientLogin += OnClientLogin; | ||
223 | } | ||
224 | |||
225 | public virtual void RegionLoaded(Scene scene) {} | ||
226 | |||
227 | public void RemoveRegion(Scene scene) | ||
228 | { | ||
229 | if (!m_Enabled) | ||
230 | return; | ||
231 | |||
232 | m_Scenes.Remove(scene); | ||
233 | } | ||
234 | |||
235 | public virtual string Name | ||
236 | { | ||
237 | get { return "FriendsModule"; } | ||
238 | } | ||
239 | |||
240 | public Type ReplaceableInterface | ||
241 | { | ||
242 | get { return null; } | ||
243 | } | ||
244 | |||
245 | #endregion | ||
246 | |||
247 | public virtual int GetRightsGrantedByFriend(UUID principalID, UUID friendID) | ||
248 | { | ||
249 | FriendInfo[] friends = GetFriendsFromCache(principalID); | ||
250 | FriendInfo finfo = GetFriend(friends, friendID); | ||
251 | if (finfo != null) | ||
252 | { | ||
253 | return finfo.TheirFlags; | ||
254 | } | ||
255 | |||
256 | return 0; | ||
257 | } | ||
258 | |||
259 | private void OnNewClient(IClientAPI client) | ||
260 | { | ||
261 | client.OnInstantMessage += OnInstantMessage; | ||
262 | client.OnApproveFriendRequest += OnApproveFriendRequest; | ||
263 | client.OnDenyFriendRequest += OnDenyFriendRequest; | ||
264 | client.OnTerminateFriendship += RemoveFriendship; | ||
265 | client.OnGrantUserRights += GrantRights; | ||
266 | |||
267 | // We need to cache information for child agents as well as root agents so that friend edit/move/delete | ||
268 | // permissions will work across borders where both regions are on different simulators. | ||
269 | // | ||
270 | // Do not do this asynchronously. If we do, then subsequent code can outrace CacheFriends() and | ||
271 | // return misleading results from the still empty friends cache. | ||
272 | // If we absolutely need to do this asynchronously, then a signalling mechanism is needed so that calls | ||
273 | // to GetFriends() will wait until CacheFriends() completes. Locks are insufficient. | ||
274 | CacheFriends(client); | ||
275 | } | ||
276 | |||
277 | /// <summary> | ||
278 | /// Cache the friends list or increment the refcount for the existing friends list. | ||
279 | /// </summary> | ||
280 | /// <param name="client"> | ||
281 | /// </param> | ||
282 | /// <returns> | ||
283 | /// Returns true if the list was fetched, false if it wasn't | ||
284 | /// </returns> | ||
285 | protected virtual bool CacheFriends(IClientAPI client) | ||
286 | { | ||
287 | UUID agentID = client.AgentId; | ||
288 | lock (m_Friends) | ||
289 | { | ||
290 | UserFriendData friendsData; | ||
291 | if (m_Friends.TryGetValue(agentID, out friendsData)) | ||
292 | { | ||
293 | friendsData.Refcount++; | ||
294 | return false; | ||
295 | } | ||
296 | else | ||
297 | { | ||
298 | friendsData = new UserFriendData(); | ||
299 | friendsData.PrincipalID = agentID; | ||
300 | friendsData.Friends = GetFriendsFromService(client); | ||
301 | friendsData.Refcount = 1; | ||
302 | |||
303 | m_Friends[agentID] = friendsData; | ||
304 | return true; | ||
305 | } | ||
306 | } | ||
307 | } | ||
308 | |||
309 | private void OnClientClosed(UUID agentID, Scene scene) | ||
310 | { | ||
311 | ScenePresence sp = scene.GetScenePresence(agentID); | ||
312 | if (sp != null && !sp.IsChildAgent) | ||
313 | { | ||
314 | // do this for root agents closing out | ||
315 | StatusChange(agentID, false); | ||
316 | } | ||
317 | |||
318 | lock (m_Friends) | ||
319 | { | ||
320 | UserFriendData friendsData; | ||
321 | if (m_Friends.TryGetValue(agentID, out friendsData)) | ||
322 | { | ||
323 | friendsData.Refcount--; | ||
324 | if (friendsData.Refcount <= 0) | ||
325 | m_Friends.Remove(agentID); | ||
326 | } | ||
327 | } | ||
328 | } | ||
329 | |||
330 | private void OnMakeRootAgent(ScenePresence sp) | ||
331 | { | ||
332 | RecacheFriends(sp.ControllingClient); | ||
333 | |||
334 | lock (m_NeedsToNotifyStatus) | ||
335 | { | ||
336 | if (m_NeedsToNotifyStatus.Remove(sp.UUID)) | ||
337 | { | ||
338 | // Inform the friends that this user is online. This can only be done once the client is a Root Agent. | ||
339 | StatusChange(sp.UUID, true); | ||
340 | } | ||
341 | } | ||
342 | } | ||
343 | |||
344 | private void OnClientLogin(IClientAPI client) | ||
345 | { | ||
346 | UUID agentID = client.AgentId; | ||
347 | |||
348 | //m_log.DebugFormat("[XXX]: OnClientLogin!"); | ||
349 | |||
350 | // Register that we need to send this user's status to friends. This can only be done | ||
351 | // once the client becomes a Root Agent, because as part of sending out the presence | ||
352 | // we also get back the presence of the HG friends, and we need to send that to the | ||
353 | // client, but that can only be done when the client is a Root Agent. | ||
354 | lock (m_NeedsToNotifyStatus) | ||
355 | m_NeedsToNotifyStatus.Add(agentID); | ||
356 | |||
357 | // Register that we need to send the list of online friends to this user | ||
358 | lock (m_NeedsListOfOnlineFriends) | ||
359 | m_NeedsListOfOnlineFriends.Add(agentID); | ||
360 | } | ||
361 | |||
362 | public virtual bool SendFriendsOnlineIfNeeded(IClientAPI client) | ||
363 | { | ||
364 | UUID agentID = client.AgentId; | ||
365 | |||
366 | // Check if the online friends list is needed | ||
367 | lock (m_NeedsListOfOnlineFriends) | ||
368 | { | ||
369 | if (!m_NeedsListOfOnlineFriends.Remove(agentID)) | ||
370 | return false; | ||
371 | } | ||
372 | |||
373 | // Send the friends online | ||
374 | List<UUID> online = GetOnlineFriends(agentID); | ||
375 | |||
376 | if (online.Count > 0) | ||
377 | client.SendAgentOnline(online.ToArray()); | ||
378 | |||
379 | // Send outstanding friendship offers | ||
380 | List<string> outstanding = new List<string>(); | ||
381 | FriendInfo[] friends = GetFriendsFromCache(agentID); | ||
382 | foreach (FriendInfo fi in friends) | ||
383 | { | ||
384 | if (fi.TheirFlags == -1) | ||
385 | outstanding.Add(fi.Friend); | ||
386 | } | ||
387 | |||
388 | GridInstantMessage im = new GridInstantMessage(client.Scene, UUID.Zero, String.Empty, agentID, (byte)InstantMessageDialog.FriendshipOffered, | ||
389 | "Will you be my friend?", true, Vector3.Zero); | ||
390 | |||
391 | foreach (string fid in outstanding) | ||
392 | { | ||
393 | UUID fromAgentID; | ||
394 | string firstname = "Unknown", lastname = "UserFMSFOIN"; | ||
395 | if (!GetAgentInfo(client.Scene.RegionInfo.ScopeID, fid, out fromAgentID, out firstname, out lastname)) | ||
396 | { | ||
397 | m_log.DebugFormat("[FRIENDS MODULE]: skipping malformed friend {0}", fid); | ||
398 | continue; | ||
399 | } | ||
400 | |||
401 | im.offline = 0; | ||
402 | im.fromAgentID = fromAgentID.Guid; | ||
403 | im.fromAgentName = firstname + " " + lastname; | ||
404 | im.imSessionID = im.fromAgentID; | ||
405 | im.message = FriendshipMessage(fid); | ||
406 | |||
407 | LocalFriendshipOffered(agentID, im); | ||
408 | } | ||
409 | |||
410 | return true; | ||
411 | } | ||
412 | |||
413 | protected virtual string FriendshipMessage(string friendID) | ||
414 | { | ||
415 | return "Will you be my friend?"; | ||
416 | } | ||
417 | |||
418 | protected virtual bool GetAgentInfo(UUID scopeID, string fid, out UUID agentID, out string first, out string last) | ||
419 | { | ||
420 | first = "Unknown"; last = "UserFMGAI"; | ||
421 | if (!UUID.TryParse(fid, out agentID)) | ||
422 | return false; | ||
423 | |||
424 | UserAccount account = m_Scenes[0].UserAccountService.GetUserAccount(scopeID, agentID); | ||
425 | if (account != null) | ||
426 | { | ||
427 | first = account.FirstName; | ||
428 | last = account.LastName; | ||
429 | } | ||
430 | |||
431 | return true; | ||
432 | } | ||
433 | |||
434 | List<UUID> GetOnlineFriends(UUID userID) | ||
435 | { | ||
436 | List<string> friendList = new List<string>(); | ||
437 | |||
438 | FriendInfo[] friends = GetFriendsFromCache(userID); | ||
439 | foreach (FriendInfo fi in friends) | ||
440 | { | ||
441 | if (((fi.TheirFlags & (int)FriendRights.CanSeeOnline) != 0) && (fi.TheirFlags != -1)) | ||
442 | friendList.Add(fi.Friend); | ||
443 | } | ||
444 | |||
445 | List<UUID> online = new List<UUID>(); | ||
446 | |||
447 | if (friendList.Count > 0) | ||
448 | GetOnlineFriends(userID, friendList, online); | ||
449 | |||
450 | // m_log.DebugFormat( | ||
451 | // "[FRIENDS MODULE]: User {0} has {1} friends online", userID, online.Count); | ||
452 | |||
453 | return online; | ||
454 | } | ||
455 | |||
456 | protected virtual void GetOnlineFriends(UUID userID, List<string> friendList, /*collector*/ List<UUID> online) | ||
457 | { | ||
458 | // m_log.DebugFormat( | ||
459 | // "[FRIENDS MODULE]: Looking for online presence of {0} users for {1}", friendList.Count, userID); | ||
460 | |||
461 | PresenceInfo[] presence = PresenceService.GetAgents(friendList.ToArray()); | ||
462 | foreach (PresenceInfo pi in presence) | ||
463 | { | ||
464 | UUID presenceID; | ||
465 | if (UUID.TryParse(pi.UserID, out presenceID)) | ||
466 | online.Add(presenceID); | ||
467 | } | ||
468 | } | ||
469 | |||
470 | /// <summary> | ||
471 | /// Find the client for a ID | ||
472 | /// </summary> | ||
473 | public IClientAPI LocateClientObject(UUID agentID) | ||
474 | { | ||
475 | lock (m_Scenes) | ||
476 | { | ||
477 | foreach (Scene scene in m_Scenes) | ||
478 | { | ||
479 | ScenePresence presence = scene.GetScenePresence(agentID); | ||
480 | if (presence != null && !presence.IsChildAgent) | ||
481 | return presence.ControllingClient; | ||
482 | } | ||
483 | } | ||
484 | |||
485 | return null; | ||
486 | } | ||
487 | |||
488 | /// <summary> | ||
489 | /// Caller beware! Call this only for root agents. | ||
490 | /// </summary> | ||
491 | /// <param name="agentID"></param> | ||
492 | /// <param name="online"></param> | ||
493 | private void StatusChange(UUID agentID, bool online) | ||
494 | { | ||
495 | FriendInfo[] friends = GetFriendsFromCache(agentID); | ||
496 | if (friends.Length > 0) | ||
497 | { | ||
498 | List<FriendInfo> friendList = new List<FriendInfo>(); | ||
499 | foreach (FriendInfo fi in friends) | ||
500 | { | ||
501 | if (((fi.MyFlags & (int)FriendRights.CanSeeOnline) != 0) && (fi.TheirFlags != -1)) | ||
502 | friendList.Add(fi); | ||
503 | } | ||
504 | |||
505 | Util.FireAndForget( | ||
506 | delegate | ||
507 | { | ||
508 | // m_log.DebugFormat( | ||
509 | // "[FRIENDS MODULE]: Notifying {0} friends of {1} of online status {2}", | ||
510 | // friendList.Count, agentID, online); | ||
511 | |||
512 | // Notify about this user status | ||
513 | StatusNotify(friendList, agentID, online); | ||
514 | }, null, "FriendsModule.StatusChange" | ||
515 | ); | ||
516 | } | ||
517 | } | ||
518 | |||
519 | protected virtual void StatusNotify(List<FriendInfo> friendList, UUID userID, bool online) | ||
520 | { | ||
521 | //m_log.DebugFormat("[FRIENDS]: Entering StatusNotify for {0}", userID); | ||
522 | |||
523 | List<string> friendStringIds = friendList.ConvertAll<string>(friend => friend.Friend); | ||
524 | List<string> remoteFriendStringIds = new List<string>(); | ||
525 | foreach (string friendStringId in friendStringIds) | ||
526 | { | ||
527 | UUID friendUuid; | ||
528 | if (UUID.TryParse(friendStringId, out friendUuid)) | ||
529 | { | ||
530 | if (LocalStatusNotification(userID, friendUuid, online)) | ||
531 | continue; | ||
532 | |||
533 | remoteFriendStringIds.Add(friendStringId); | ||
534 | } | ||
535 | else | ||
536 | { | ||
537 | m_log.WarnFormat("[FRIENDS]: Error parsing friend ID {0}", friendStringId); | ||
538 | } | ||
539 | } | ||
540 | |||
541 | // We do this regrouping so that we can efficiently send a single request rather than one for each | ||
542 | // friend in what may be a very large friends list. | ||
543 | PresenceInfo[] friendSessions = PresenceService.GetAgents(remoteFriendStringIds.ToArray()); | ||
544 | |||
545 | foreach (PresenceInfo friendSession in friendSessions) | ||
546 | { | ||
547 | // let's guard against sessions-gone-bad | ||
548 | if (friendSession != null && friendSession.RegionID != UUID.Zero) | ||
549 | { | ||
550 | //m_log.DebugFormat("[FRIENDS]: Get region {0}", friendSession.RegionID); | ||
551 | GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID); | ||
552 | if (region != null) | ||
553 | { | ||
554 | m_FriendsSimConnector.StatusNotify(region, userID, friendSession.UserID, online); | ||
555 | } | ||
556 | } | ||
557 | //else | ||
558 | // m_log.DebugFormat("[FRIENDS]: friend session is null or the region is UUID.Zero"); | ||
559 | } | ||
560 | } | ||
561 | |||
562 | protected virtual void OnInstantMessage(IClientAPI client, GridInstantMessage im) | ||
563 | { | ||
564 | if ((InstantMessageDialog)im.dialog == InstantMessageDialog.FriendshipOffered) | ||
565 | { | ||
566 | // we got a friendship offer | ||
567 | UUID principalID = new UUID(im.fromAgentID); | ||
568 | UUID friendID = new UUID(im.toAgentID); | ||
569 | |||
570 | m_log.DebugFormat("[FRIENDS]: {0} ({1}) offered friendship to {2} ({3})", principalID, client.FirstName + client.LastName, friendID, im.fromAgentName); | ||
571 | |||
572 | // Check that the friendship doesn't exist yet | ||
573 | FriendInfo[] finfos = GetFriendsFromCache(principalID); | ||
574 | if (finfos != null) | ||
575 | { | ||
576 | FriendInfo f = GetFriend(finfos, friendID); | ||
577 | if (f != null) | ||
578 | { | ||
579 | client.SendAgentAlertMessage("This person is already your friend. Please delete it first if you want to reestablish the friendship.", false); | ||
580 | return; | ||
581 | } | ||
582 | } | ||
583 | |||
584 | // This user wants to be friends with the other user. | ||
585 | // Let's add the relation backwards, in case the other is not online | ||
586 | StoreBackwards(friendID, principalID); | ||
587 | |||
588 | // Now let's ask the other user to be friends with this user | ||
589 | ForwardFriendshipOffer(principalID, friendID, im); | ||
590 | } | ||
591 | } | ||
592 | |||
593 | protected virtual bool ForwardFriendshipOffer(UUID agentID, UUID friendID, GridInstantMessage im) | ||
594 | { | ||
595 | // !!!!!!!! This is a hack so that we don't have to keep state (transactionID/imSessionID) | ||
596 | // We stick this agent's ID as imSession, so that it's directly available on the receiving end | ||
597 | im.imSessionID = im.fromAgentID; | ||
598 | im.fromAgentName = GetFriendshipRequesterName(agentID); | ||
599 | |||
600 | // Try the local sim | ||
601 | if (LocalFriendshipOffered(friendID, im)) | ||
602 | return true; | ||
603 | |||
604 | // The prospective friend is not here [as root]. Let's forward. | ||
605 | PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() }); | ||
606 | if (friendSessions != null && friendSessions.Length > 0) | ||
607 | { | ||
608 | PresenceInfo friendSession = friendSessions[0]; | ||
609 | if (friendSession != null) | ||
610 | { | ||
611 | GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID); | ||
612 | m_FriendsSimConnector.FriendshipOffered(region, agentID, friendID, im.message); | ||
613 | return true; | ||
614 | } | ||
615 | } | ||
616 | // If the prospective friend is not online, he'll get the message upon login. | ||
617 | return false; | ||
618 | } | ||
619 | |||
620 | protected virtual string GetFriendshipRequesterName(UUID agentID) | ||
621 | { | ||
622 | UserAccount account = UserAccountService.GetUserAccount(UUID.Zero, agentID); | ||
623 | return (account == null) ? "Unknown" : account.FirstName + " " + account.LastName; | ||
624 | } | ||
625 | |||
626 | protected virtual void OnApproveFriendRequest(IClientAPI client, UUID friendID, List<UUID> callingCardFolders) | ||
627 | { | ||
628 | m_log.DebugFormat("[FRIENDS]: {0} accepted friendship from {1}", client.AgentId, friendID); | ||
629 | |||
630 | AddFriendship(client, friendID); | ||
631 | } | ||
632 | |||
633 | public void AddFriendship(IClientAPI client, UUID friendID) | ||
634 | { | ||
635 | StoreFriendships(client.AgentId, friendID); | ||
636 | |||
637 | ICallingCardModule ccm = client.Scene.RequestModuleInterface<ICallingCardModule>(); | ||
638 | if (ccm != null) | ||
639 | { | ||
640 | ccm.CreateCallingCard(client.AgentId, friendID, UUID.Zero); | ||
641 | } | ||
642 | |||
643 | // Update the local cache. | ||
644 | RecacheFriends(client); | ||
645 | |||
646 | // | ||
647 | // Notify the friend | ||
648 | // | ||
649 | |||
650 | // Try Local | ||
651 | if (LocalFriendshipApproved(client.AgentId, client.Name, friendID)) | ||
652 | { | ||
653 | client.SendAgentOnline(new UUID[] { friendID }); | ||
654 | return; | ||
655 | } | ||
656 | |||
657 | // The friend is not here | ||
658 | PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() }); | ||
659 | if (friendSessions != null && friendSessions.Length > 0) | ||
660 | { | ||
661 | PresenceInfo friendSession = friendSessions[0]; | ||
662 | if (friendSession != null) | ||
663 | { | ||
664 | GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID); | ||
665 | m_FriendsSimConnector.FriendshipApproved(region, client.AgentId, client.Name, friendID); | ||
666 | client.SendAgentOnline(new UUID[] { friendID }); | ||
667 | } | ||
668 | } | ||
669 | } | ||
670 | |||
671 | private void OnDenyFriendRequest(IClientAPI client, UUID friendID, List<UUID> callingCardFolders) | ||
672 | { | ||
673 | m_log.DebugFormat("[FRIENDS]: {0} denied friendship to {1}", client.AgentId, friendID); | ||
674 | |||
675 | DeleteFriendship(client.AgentId, friendID); | ||
676 | |||
677 | // | ||
678 | // Notify the friend | ||
679 | // | ||
680 | |||
681 | // Try local | ||
682 | if (LocalFriendshipDenied(client.AgentId, client.Name, friendID)) | ||
683 | return; | ||
684 | |||
685 | PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() }); | ||
686 | if (friendSessions != null && friendSessions.Length > 0) | ||
687 | { | ||
688 | PresenceInfo friendSession = friendSessions[0]; | ||
689 | if (friendSession != null) | ||
690 | { | ||
691 | GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID); | ||
692 | if (region != null) | ||
693 | m_FriendsSimConnector.FriendshipDenied(region, client.AgentId, client.Name, friendID); | ||
694 | else | ||
695 | m_log.WarnFormat("[FRIENDS]: Could not find region {0} in locating {1}", friendSession.RegionID, friendID); | ||
696 | } | ||
697 | } | ||
698 | } | ||
699 | |||
700 | public void RemoveFriendship(IClientAPI client, UUID exfriendID) | ||
701 | { | ||
702 | if (!DeleteFriendship(client.AgentId, exfriendID)) | ||
703 | client.SendAlertMessage("Unable to terminate friendship on this sim."); | ||
704 | |||
705 | // Update local cache | ||
706 | RecacheFriends(client); | ||
707 | |||
708 | client.SendTerminateFriend(exfriendID); | ||
709 | |||
710 | // | ||
711 | // Notify the friend | ||
712 | // | ||
713 | |||
714 | // Try local | ||
715 | if (LocalFriendshipTerminated(client.AgentId, exfriendID)) | ||
716 | return; | ||
717 | |||
718 | PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { exfriendID.ToString() }); | ||
719 | if (friendSessions != null && friendSessions.Length > 0) | ||
720 | { | ||
721 | PresenceInfo friendSession = friendSessions[0]; | ||
722 | if (friendSession != null) | ||
723 | { | ||
724 | GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID); | ||
725 | m_FriendsSimConnector.FriendshipTerminated(region, client.AgentId, exfriendID); | ||
726 | } | ||
727 | } | ||
728 | } | ||
729 | |||
730 | public void GrantRights(IClientAPI remoteClient, UUID friendID, int rights) | ||
731 | { | ||
732 | UUID requester = remoteClient.AgentId; | ||
733 | |||
734 | m_log.DebugFormat( | ||
735 | "[FRIENDS MODULE]: User {0} changing rights to {1} for friend {2}", | ||
736 | requester, rights, friendID); | ||
737 | |||
738 | FriendInfo[] friends = GetFriendsFromCache(requester); | ||
739 | if (friends.Length == 0) | ||
740 | { | ||
741 | return; | ||
742 | } | ||
743 | |||
744 | // Let's find the friend in this user's friend list | ||
745 | FriendInfo friend = GetFriend(friends, friendID); | ||
746 | |||
747 | if (friend != null) // Found it | ||
748 | { | ||
749 | // Store it on the DB | ||
750 | if (!StoreRights(requester, friendID, rights)) | ||
751 | { | ||
752 | remoteClient.SendAlertMessage("Unable to grant rights."); | ||
753 | return; | ||
754 | } | ||
755 | |||
756 | // Store it in the local cache | ||
757 | int myFlags = friend.MyFlags; | ||
758 | friend.MyFlags = rights; | ||
759 | |||
760 | // Always send this back to the original client | ||
761 | remoteClient.SendChangeUserRights(requester, friendID, rights); | ||
762 | |||
763 | // | ||
764 | // Notify the friend | ||
765 | // | ||
766 | |||
767 | // Try local | ||
768 | if (LocalGrantRights(requester, friendID, myFlags, rights)) | ||
769 | return; | ||
770 | |||
771 | PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() }); | ||
772 | if (friendSessions != null && friendSessions.Length > 0) | ||
773 | { | ||
774 | PresenceInfo friendSession = friendSessions[0]; | ||
775 | if (friendSession != null) | ||
776 | { | ||
777 | GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID); | ||
778 | // TODO: You might want to send the delta to save the lookup | ||
779 | // on the other end!! | ||
780 | m_FriendsSimConnector.GrantRights(region, requester, friendID, myFlags, rights); | ||
781 | } | ||
782 | } | ||
783 | } | ||
784 | else | ||
785 | { | ||
786 | m_log.DebugFormat("[FRIENDS MODULE]: friend {0} not found for {1}", friendID, requester); | ||
787 | } | ||
788 | } | ||
789 | |||
790 | protected virtual FriendInfo GetFriend(FriendInfo[] friends, UUID friendID) | ||
791 | { | ||
792 | foreach (FriendInfo fi in friends) | ||
793 | { | ||
794 | if (fi.Friend == friendID.ToString()) | ||
795 | return fi; | ||
796 | } | ||
797 | return null; | ||
798 | } | ||
799 | |||
800 | #region Local | ||
801 | |||
802 | public virtual bool LocalFriendshipOffered(UUID toID, GridInstantMessage im) | ||
803 | { | ||
804 | IClientAPI friendClient = LocateClientObject(toID); | ||
805 | if (friendClient != null) | ||
806 | { | ||
807 | // the prospective friend in this sim as root agent | ||
808 | friendClient.SendInstantMessage(im); | ||
809 | // we're done | ||
810 | return true; | ||
811 | } | ||
812 | return false; | ||
813 | } | ||
814 | |||
815 | public bool LocalFriendshipApproved(UUID userID, string userName, UUID friendID) | ||
816 | { | ||
817 | IClientAPI friendClient = LocateClientObject(friendID); | ||
818 | if (friendClient != null) | ||
819 | { | ||
820 | // the prospective friend in this sim as root agent | ||
821 | GridInstantMessage im = new GridInstantMessage(Scene, userID, userName, friendID, | ||
822 | (byte)OpenMetaverse.InstantMessageDialog.FriendshipAccepted, userID.ToString(), false, Vector3.Zero); | ||
823 | friendClient.SendInstantMessage(im); | ||
824 | |||
825 | ICallingCardModule ccm = friendClient.Scene.RequestModuleInterface<ICallingCardModule>(); | ||
826 | if (ccm != null) | ||
827 | { | ||
828 | ccm.CreateCallingCard(friendID, userID, UUID.Zero); | ||
829 | } | ||
830 | |||
831 | // Update the local cache | ||
832 | RecacheFriends(friendClient); | ||
833 | |||
834 | // we're done | ||
835 | return true; | ||
836 | } | ||
837 | |||
838 | return false; | ||
839 | } | ||
840 | |||
841 | public bool LocalFriendshipDenied(UUID userID, string userName, UUID friendID) | ||
842 | { | ||
843 | IClientAPI friendClient = LocateClientObject(friendID); | ||
844 | if (friendClient != null) | ||
845 | { | ||
846 | // the prospective friend in this sim as root agent | ||
847 | GridInstantMessage im = new GridInstantMessage(Scene, userID, userName, friendID, | ||
848 | (byte)OpenMetaverse.InstantMessageDialog.FriendshipDeclined, userID.ToString(), false, Vector3.Zero); | ||
849 | friendClient.SendInstantMessage(im); | ||
850 | // we're done | ||
851 | return true; | ||
852 | } | ||
853 | |||
854 | return false; | ||
855 | } | ||
856 | |||
857 | public bool LocalFriendshipTerminated(UUID userID, UUID exfriendID) | ||
858 | { | ||
859 | IClientAPI friendClient = LocateClientObject(exfriendID); | ||
860 | if (friendClient != null) | ||
861 | { | ||
862 | // the friend in this sim as root agent | ||
863 | friendClient.SendTerminateFriend(userID); | ||
864 | // update local cache | ||
865 | RecacheFriends(friendClient); | ||
866 | // we're done | ||
867 | return true; | ||
868 | } | ||
869 | |||
870 | return false; | ||
871 | } | ||
872 | |||
873 | public bool LocalGrantRights(UUID userID, UUID friendID, int userFlags, int rights) | ||
874 | { | ||
875 | IClientAPI friendClient = LocateClientObject(friendID); | ||
876 | if (friendClient != null) | ||
877 | { | ||
878 | bool onlineBitChanged = ((rights ^ userFlags) & (int)FriendRights.CanSeeOnline) != 0; | ||
879 | if (onlineBitChanged) | ||
880 | { | ||
881 | if ((rights & (int)FriendRights.CanSeeOnline) == 1) | ||
882 | friendClient.SendAgentOnline(new UUID[] { userID }); | ||
883 | else | ||
884 | friendClient.SendAgentOffline(new UUID[] { userID }); | ||
885 | } | ||
886 | else | ||
887 | { | ||
888 | bool canEditObjectsChanged = ((rights ^ userFlags) & (int)FriendRights.CanModifyObjects) != 0; | ||
889 | if (canEditObjectsChanged) | ||
890 | friendClient.SendChangeUserRights(userID, friendID, rights); | ||
891 | } | ||
892 | |||
893 | // Update local cache | ||
894 | UpdateLocalCache(userID, friendID, rights); | ||
895 | |||
896 | return true; | ||
897 | } | ||
898 | |||
899 | return false; | ||
900 | |||
901 | } | ||
902 | |||
903 | public bool LocalStatusNotification(UUID userID, UUID friendID, bool online) | ||
904 | { | ||
905 | //m_log.DebugFormat("[FRIENDS]: Local Status Notify {0} that user {1} is {2}", friendID, userID, online); | ||
906 | IClientAPI friendClient = LocateClientObject(friendID); | ||
907 | if (friendClient != null) | ||
908 | { | ||
909 | // the friend in this sim as root agent | ||
910 | if (online) | ||
911 | friendClient.SendAgentOnline(new UUID[] { userID }); | ||
912 | else | ||
913 | friendClient.SendAgentOffline(new UUID[] { userID }); | ||
914 | // we're done | ||
915 | return true; | ||
916 | } | ||
917 | |||
918 | return false; | ||
919 | } | ||
920 | |||
921 | #endregion | ||
922 | |||
923 | #region Get / Set friends in several flavours | ||
924 | |||
925 | public FriendInfo[] GetFriendsFromCache(UUID userID) | ||
926 | { | ||
927 | UserFriendData friendsData; | ||
928 | |||
929 | lock (m_Friends) | ||
930 | { | ||
931 | if (m_Friends.TryGetValue(userID, out friendsData)) | ||
932 | return friendsData.Friends; | ||
933 | } | ||
934 | |||
935 | return EMPTY_FRIENDS; | ||
936 | } | ||
937 | |||
938 | /// <summary> | ||
939 | /// Update local cache only | ||
940 | /// </summary> | ||
941 | /// <param name="userID"></param> | ||
942 | /// <param name="friendID"></param> | ||
943 | /// <param name="rights"></param> | ||
944 | protected void UpdateLocalCache(UUID userID, UUID friendID, int rights) | ||
945 | { | ||
946 | // Update local cache | ||
947 | lock (m_Friends) | ||
948 | { | ||
949 | FriendInfo[] friends = GetFriendsFromCache(friendID); | ||
950 | FriendInfo finfo = GetFriend(friends, userID); | ||
951 | finfo.TheirFlags = rights; | ||
952 | } | ||
953 | } | ||
954 | |||
955 | public virtual FriendInfo[] GetFriendsFromService(IClientAPI client) | ||
956 | { | ||
957 | return FriendsService.GetFriends(client.AgentId); | ||
958 | } | ||
959 | |||
960 | protected void RecacheFriends(IClientAPI client) | ||
961 | { | ||
962 | // FIXME: Ideally, we want to avoid doing this here since it sits the EventManager.OnMakeRootAgent event | ||
963 | // is on the critical path for transferring an avatar from one region to another. | ||
964 | UUID agentID = client.AgentId; | ||
965 | lock (m_Friends) | ||
966 | { | ||
967 | UserFriendData friendsData; | ||
968 | if (m_Friends.TryGetValue(agentID, out friendsData)) | ||
969 | friendsData.Friends = GetFriendsFromService(client); | ||
970 | } | ||
971 | } | ||
972 | |||
973 | public bool AreFriendsCached(UUID userID) | ||
974 | { | ||
975 | lock (m_Friends) | ||
976 | return m_Friends.ContainsKey(userID); | ||
977 | } | ||
978 | |||
979 | protected virtual bool StoreRights(UUID agentID, UUID friendID, int rights) | ||
980 | { | ||
981 | FriendsService.StoreFriend(agentID.ToString(), friendID.ToString(), rights); | ||
982 | return true; | ||
983 | } | ||
984 | |||
985 | protected virtual void StoreBackwards(UUID friendID, UUID agentID) | ||
986 | { | ||
987 | FriendsService.StoreFriend(friendID.ToString(), agentID.ToString(), 0); | ||
988 | } | ||
989 | |||
990 | protected virtual void StoreFriendships(UUID agentID, UUID friendID) | ||
991 | { | ||
992 | FriendsService.StoreFriend(agentID.ToString(), friendID.ToString(), (int)FriendRights.CanSeeOnline); | ||
993 | FriendsService.StoreFriend(friendID.ToString(), agentID.ToString(), (int)FriendRights.CanSeeOnline); | ||
994 | } | ||
995 | |||
996 | protected virtual bool DeleteFriendship(UUID agentID, UUID exfriendID) | ||
997 | { | ||
998 | FriendsService.Delete(agentID, exfriendID.ToString()); | ||
999 | FriendsService.Delete(exfriendID, agentID.ToString()); | ||
1000 | return true; | ||
1001 | } | ||
1002 | |||
1003 | #endregion | ||
1004 | } | ||
1005 | } | ||