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