clockwerk-opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 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 | } |
||
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 | } |