diff options
author | Michael Cortez | 2009-08-05 11:15:53 -0700 |
---|---|---|
committer | Michael Cortez | 2009-08-05 11:15:53 -0700 |
commit | 989517725d02d8a2d216e599856eb2625b454e25 (patch) | |
tree | 74f31eac6e685361ff5d469cdc6992158964666f /OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs | |
parent | force back the Regions directory, which because it was empty in svn never made (diff) | |
download | opensim-SC-989517725d02d8a2d216e599856eb2625b454e25.zip opensim-SC-989517725d02d8a2d216e599856eb2625b454e25.tar.gz opensim-SC-989517725d02d8a2d216e599856eb2625b454e25.tar.bz2 opensim-SC-989517725d02d8a2d216e599856eb2625b454e25.tar.xz |
Begin refactoring XmlRpcGroups to a more generic Groups module that allows for replaceable Groups Service Connectors.
Diffstat (limited to 'OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs')
-rw-r--r-- | OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs | 1335 |
1 files changed, 1335 insertions, 0 deletions
diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs new file mode 100644 index 0000000..f0e386b --- /dev/null +++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs | |||
@@ -0,0 +1,1335 @@ | |||
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.Reflection; | ||
31 | using System.Timers; | ||
32 | |||
33 | using log4net; | ||
34 | using Nini.Config; | ||
35 | |||
36 | using OpenMetaverse; | ||
37 | using OpenMetaverse.StructuredData; | ||
38 | |||
39 | using OpenSim.Framework; | ||
40 | using OpenSim.Framework.Communications; | ||
41 | using OpenSim.Region.CoreModules.Framework.EventQueue; | ||
42 | using OpenSim.Region.Framework.Interfaces; | ||
43 | using OpenSim.Region.Framework.Scenes; | ||
44 | |||
45 | using Caps = OpenSim.Framework.Capabilities.Caps; | ||
46 | using DirFindFlags = OpenMetaverse.DirectoryManager.DirFindFlags; | ||
47 | |||
48 | |||
49 | |||
50 | namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups | ||
51 | { | ||
52 | public class GroupsModule : ISharedRegionModule, IGroupsModule | ||
53 | { | ||
54 | /// <summary> | ||
55 | /// ; To use this module, you must specify the following in your OpenSim.ini | ||
56 | /// [GROUPS] | ||
57 | /// Enabled = true | ||
58 | /// Module = XmlRpcGroups | ||
59 | /// XmlRpcServiceURL = http://osflotsam.org/xmlrpc.php | ||
60 | /// XmlRpcMessagingEnabled = true | ||
61 | /// XmlRpcNoticesEnabled = true | ||
62 | /// XmlRpcDebugEnabled = true | ||
63 | /// XmlRpcServiceReadKey = 1234 | ||
64 | /// XmlRpcServiceWriteKey = 1234 | ||
65 | /// | ||
66 | /// ; Disables HTTP Keep-Alive for Groups Module HTTP Requests, work around for | ||
67 | /// ; a problem discovered on some Windows based region servers. Only disable | ||
68 | /// ; if you see a large number (dozens) of the following Exceptions: | ||
69 | /// ; System.Net.WebException: The request was aborted: The request was canceled. | ||
70 | /// | ||
71 | /// XmlRpcDisableKeepAlive = false | ||
72 | /// </summary> | ||
73 | |||
74 | private static readonly ILog m_log = | ||
75 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
76 | |||
77 | private List<Scene> m_sceneList = new List<Scene>(); | ||
78 | |||
79 | private IMessageTransferModule m_msgTransferModule = null; | ||
80 | |||
81 | private IGroupsServicesConnector m_groupData = null; | ||
82 | |||
83 | class GroupRequestIDInfo | ||
84 | { | ||
85 | public GroupRequestID RequestID = new GroupRequestID(); | ||
86 | public DateTime LastUsedTMStamp = DateTime.MinValue; | ||
87 | } | ||
88 | private Dictionary<UUID, GroupRequestIDInfo> m_clientRequestIDInfo = new Dictionary<UUID, GroupRequestIDInfo>(); | ||
89 | private const int m_clientRequestIDFlushTimeOut = 300000; // Every 5 minutes | ||
90 | private Timer m_clientRequestIDFlushTimer = new Timer(); | ||
91 | |||
92 | |||
93 | // Configuration settings | ||
94 | private bool m_groupsEnabled = false; | ||
95 | private bool m_groupNoticesEnabled = true; | ||
96 | private bool m_debugEnabled = true; | ||
97 | |||
98 | #region IRegionModuleBase Members | ||
99 | |||
100 | public void Initialise(IConfigSource config) | ||
101 | { | ||
102 | IConfig groupsConfig = config.Configs["Groups"]; | ||
103 | |||
104 | if (groupsConfig == null) | ||
105 | { | ||
106 | // Do not run this module by default. | ||
107 | return; | ||
108 | } | ||
109 | else | ||
110 | { | ||
111 | m_groupsEnabled = groupsConfig.GetBoolean("Enabled", false); | ||
112 | if (!m_groupsEnabled) | ||
113 | { | ||
114 | return; | ||
115 | } | ||
116 | |||
117 | if (groupsConfig.GetString("Module", "Default") != "XmlRpcGroups") | ||
118 | { | ||
119 | m_groupsEnabled = false; | ||
120 | |||
121 | return; | ||
122 | } | ||
123 | |||
124 | m_log.InfoFormat("[GROUPS]: Initializing {0}", this.Name); | ||
125 | |||
126 | m_groupNoticesEnabled = groupsConfig.GetBoolean("XmlRpcNoticesEnabled", true); | ||
127 | m_debugEnabled = groupsConfig.GetBoolean("XmlRpcDebugEnabled", true); | ||
128 | |||
129 | m_clientRequestIDFlushTimer.Interval = m_clientRequestIDFlushTimeOut; | ||
130 | m_clientRequestIDFlushTimer.Elapsed += FlushClientRequestIDInfoCache; | ||
131 | m_clientRequestIDFlushTimer.AutoReset = true; | ||
132 | m_clientRequestIDFlushTimer.Start(); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | void FlushClientRequestIDInfoCache(object sender, ElapsedEventArgs e) | ||
137 | { | ||
138 | lock (m_clientRequestIDInfo) | ||
139 | { | ||
140 | TimeSpan cacheTimeout = new TimeSpan(0,0, m_clientRequestIDFlushTimeOut / 1000); | ||
141 | UUID[] CurrentKeys = new UUID[m_clientRequestIDInfo.Count]; | ||
142 | foreach (UUID key in CurrentKeys) | ||
143 | { | ||
144 | if (DateTime.Now - m_clientRequestIDInfo[key].LastUsedTMStamp > cacheTimeout) | ||
145 | { | ||
146 | m_clientRequestIDInfo.Remove(key); | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | |||
152 | public void AddRegion(Scene scene) | ||
153 | { | ||
154 | if (m_groupsEnabled) | ||
155 | scene.RegisterModuleInterface<IGroupsModule>(this); | ||
156 | } | ||
157 | |||
158 | public void RegionLoaded(Scene scene) | ||
159 | { | ||
160 | if (!m_groupsEnabled) | ||
161 | return; | ||
162 | |||
163 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
164 | |||
165 | if (m_groupData == null) | ||
166 | { | ||
167 | m_groupData = scene.RequestModuleInterface<IGroupsServicesConnector>(); | ||
168 | |||
169 | // No Groups Service Connector, then nothing works... | ||
170 | if (m_groupData == null) | ||
171 | { | ||
172 | m_groupsEnabled = false; | ||
173 | m_log.Error("[GROUPS]: Could not get IGroupsServicesConnector"); | ||
174 | Close(); | ||
175 | return; | ||
176 | } | ||
177 | } | ||
178 | |||
179 | if (m_msgTransferModule == null) | ||
180 | { | ||
181 | m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>(); | ||
182 | |||
183 | // No message transfer module, no notices, group invites, rejects, ejects, etc | ||
184 | if (m_msgTransferModule == null) | ||
185 | { | ||
186 | m_groupsEnabled = false; | ||
187 | m_log.Error("[GROUPS]: Could not get MessageTransferModule"); | ||
188 | Close(); | ||
189 | return; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | lock (m_sceneList) | ||
194 | { | ||
195 | m_sceneList.Add(scene); | ||
196 | } | ||
197 | |||
198 | scene.EventManager.OnNewClient += OnNewClient; | ||
199 | scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage; | ||
200 | |||
201 | // The InstantMessageModule itself doesn't do this, | ||
202 | // so lets see if things explode if we don't do it | ||
203 | // scene.EventManager.OnClientClosed += OnClientClosed; | ||
204 | |||
205 | } | ||
206 | |||
207 | public void RemoveRegion(Scene scene) | ||
208 | { | ||
209 | if (!m_groupsEnabled) | ||
210 | return; | ||
211 | |||
212 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
213 | |||
214 | lock (m_sceneList) | ||
215 | { | ||
216 | m_sceneList.Remove(scene); | ||
217 | } | ||
218 | } | ||
219 | |||
220 | public void Close() | ||
221 | { | ||
222 | if (!m_groupsEnabled) | ||
223 | return; | ||
224 | |||
225 | if (m_debugEnabled) m_log.Debug("[GROUPS]: Shutting down XmlRpcGroups module."); | ||
226 | |||
227 | m_clientRequestIDFlushTimer.Stop(); | ||
228 | } | ||
229 | |||
230 | public Type ReplacableInterface | ||
231 | { | ||
232 | get { return null; } | ||
233 | } | ||
234 | |||
235 | public string Name | ||
236 | { | ||
237 | get { return "XmlRpcGroupsModule"; } | ||
238 | } | ||
239 | |||
240 | #endregion | ||
241 | |||
242 | #region ISharedRegionModule Members | ||
243 | |||
244 | public void PostInitialise() | ||
245 | { | ||
246 | // NoOp | ||
247 | } | ||
248 | |||
249 | #endregion | ||
250 | |||
251 | #region EventHandlers | ||
252 | private void OnNewClient(IClientAPI client) | ||
253 | { | ||
254 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
255 | |||
256 | client.OnUUIDGroupNameRequest += HandleUUIDGroupNameRequest; | ||
257 | client.OnAgentDataUpdateRequest += OnAgentDataUpdateRequest; | ||
258 | client.OnDirFindQuery += OnDirFindQuery; | ||
259 | client.OnRequestAvatarProperties += OnRequestAvatarProperties; | ||
260 | |||
261 | // Used for Notices and Group Invites/Accept/Reject | ||
262 | client.OnInstantMessage += OnInstantMessage; | ||
263 | |||
264 | lock (m_clientRequestIDInfo) | ||
265 | { | ||
266 | if (m_clientRequestIDInfo.ContainsKey(client.AgentId)) | ||
267 | { | ||
268 | // flush any old RequestID information | ||
269 | m_clientRequestIDInfo.Remove(client.AgentId); | ||
270 | } | ||
271 | } | ||
272 | SendAgentGroupDataUpdate(client, client.AgentId); | ||
273 | } | ||
274 | |||
275 | private void OnRequestAvatarProperties(IClientAPI remoteClient, UUID avatarID) | ||
276 | { | ||
277 | GroupMembershipData[] avatarGroups = m_groupData.GetAgentGroupMemberships(GetClientGroupRequestID(remoteClient), avatarID).ToArray(); | ||
278 | remoteClient.SendAvatarGroupsReply(avatarID, avatarGroups); | ||
279 | } | ||
280 | |||
281 | /* | ||
282 | * This becomes very problematic in a shared module. In a shared module you may have more then one | ||
283 | * reference to IClientAPI's, one for 0 or 1 root connections, and 0 or more child connections. | ||
284 | * The OnClientClosed event does not provide anything to indicate which one of those should be closed | ||
285 | * nor does it provide what scene it was from so that the specific reference can be looked up. | ||
286 | * The InstantMessageModule.cs does not currently worry about unregistering the handles, | ||
287 | * and it should be an issue, since it's the client that references us not the other way around | ||
288 | * , so as long as we don't keep a reference to the client laying around, the client can still be GC'ed | ||
289 | private void OnClientClosed(UUID AgentId) | ||
290 | { | ||
291 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
292 | |||
293 | lock (m_ActiveClients) | ||
294 | { | ||
295 | if (m_ActiveClients.ContainsKey(AgentId)) | ||
296 | { | ||
297 | IClientAPI client = m_ActiveClients[AgentId]; | ||
298 | client.OnUUIDGroupNameRequest -= HandleUUIDGroupNameRequest; | ||
299 | client.OnAgentDataUpdateRequest -= OnAgentDataUpdateRequest; | ||
300 | client.OnDirFindQuery -= OnDirFindQuery; | ||
301 | client.OnInstantMessage -= OnInstantMessage; | ||
302 | |||
303 | m_ActiveClients.Remove(AgentId); | ||
304 | } | ||
305 | else | ||
306 | { | ||
307 | if (m_debugEnabled) m_log.WarnFormat("[GROUPS]: Client closed that wasn't registered here."); | ||
308 | } | ||
309 | |||
310 | |||
311 | } | ||
312 | } | ||
313 | */ | ||
314 | |||
315 | |||
316 | void OnDirFindQuery(IClientAPI remoteClient, UUID queryID, string queryText, uint queryFlags, int queryStart) | ||
317 | { | ||
318 | if (((DirFindFlags)queryFlags & DirFindFlags.Groups) == DirFindFlags.Groups) | ||
319 | { | ||
320 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called with queryText({1}) queryFlags({2}) queryStart({3})", System.Reflection.MethodBase.GetCurrentMethod().Name, queryText, (DirFindFlags)queryFlags, queryStart); | ||
321 | |||
322 | // TODO: This currently ignores pretty much all the query flags including Mature and sort order | ||
323 | remoteClient.SendDirGroupsReply(queryID, m_groupData.FindGroups(GetClientGroupRequestID(remoteClient), queryText).ToArray()); | ||
324 | } | ||
325 | |||
326 | } | ||
327 | |||
328 | private void OnAgentDataUpdateRequest(IClientAPI remoteClient, UUID dataForAgentID, UUID sessionID) | ||
329 | { | ||
330 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
331 | |||
332 | UUID activeGroupID = UUID.Zero; | ||
333 | string activeGroupTitle = string.Empty; | ||
334 | string activeGroupName = string.Empty; | ||
335 | ulong activeGroupPowers = (ulong)GroupPowers.None; | ||
336 | |||
337 | GroupMembershipData membership = m_groupData.GetAgentActiveMembership(GetClientGroupRequestID(remoteClient), dataForAgentID); | ||
338 | if (membership != null) | ||
339 | { | ||
340 | activeGroupID = membership.GroupID; | ||
341 | activeGroupTitle = membership.GroupTitle; | ||
342 | activeGroupPowers = membership.GroupPowers; | ||
343 | } | ||
344 | |||
345 | SendAgentDataUpdate(remoteClient, dataForAgentID, activeGroupID, activeGroupName, activeGroupPowers, activeGroupTitle); | ||
346 | |||
347 | SendScenePresenceUpdate(dataForAgentID, activeGroupTitle); | ||
348 | } | ||
349 | |||
350 | private void HandleUUIDGroupNameRequest(UUID GroupID,IClientAPI remoteClient) | ||
351 | { | ||
352 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
353 | |||
354 | string GroupName; | ||
355 | |||
356 | GroupRecord group = m_groupData.GetGroupRecord(GetClientGroupRequestID(remoteClient), GroupID, null); | ||
357 | if (group != null) | ||
358 | { | ||
359 | GroupName = group.GroupName; | ||
360 | } | ||
361 | else | ||
362 | { | ||
363 | GroupName = "Unknown"; | ||
364 | } | ||
365 | |||
366 | remoteClient.SendGroupNameReply(GroupID, GroupName); | ||
367 | } | ||
368 | |||
369 | private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) | ||
370 | { | ||
371 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
372 | |||
373 | // Group invitations | ||
374 | if ((im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) || (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline)) | ||
375 | { | ||
376 | UUID inviteID = new UUID(im.imSessionID); | ||
377 | GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(GetClientGroupRequestID(remoteClient), inviteID); | ||
378 | |||
379 | if (inviteInfo == null) | ||
380 | { | ||
381 | if (m_debugEnabled) m_log.WarnFormat("[GROUPS]: Received an Invite IM for an invite that does not exist {0}.", inviteID); | ||
382 | return; | ||
383 | } | ||
384 | |||
385 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Invite is for Agent {0} to Group {1}.", inviteInfo.AgentID, inviteInfo.GroupID); | ||
386 | |||
387 | UUID fromAgentID = new UUID(im.fromAgentID); | ||
388 | if ((inviteInfo != null) && (fromAgentID == inviteInfo.AgentID)) | ||
389 | { | ||
390 | // Accept | ||
391 | if (im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) | ||
392 | { | ||
393 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received an accept invite notice."); | ||
394 | |||
395 | // and the sessionid is the role | ||
396 | m_groupData.AddAgentToGroup(GetClientGroupRequestID(remoteClient), inviteInfo.AgentID, inviteInfo.GroupID, inviteInfo.RoleID); | ||
397 | |||
398 | GridInstantMessage msg = new GridInstantMessage(); | ||
399 | msg.imSessionID = UUID.Zero.Guid; | ||
400 | msg.fromAgentID = UUID.Zero.Guid; | ||
401 | msg.toAgentID = inviteInfo.AgentID.Guid; | ||
402 | msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); | ||
403 | msg.fromAgentName = "Groups"; | ||
404 | msg.message = string.Format("You have been added to the group."); | ||
405 | msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageBox; | ||
406 | msg.fromGroup = false; | ||
407 | msg.offline = (byte)0; | ||
408 | msg.ParentEstateID = 0; | ||
409 | msg.Position = Vector3.Zero; | ||
410 | msg.RegionID = UUID.Zero.Guid; | ||
411 | msg.binaryBucket = new byte[0]; | ||
412 | |||
413 | OutgoingInstantMessage(msg, inviteInfo.AgentID); | ||
414 | |||
415 | UpdateAllClientsWithGroupInfo(inviteInfo.AgentID); | ||
416 | |||
417 | // TODO: If the inviter is still online, they need an agent dataupdate | ||
418 | // and maybe group membership updates for the invitee | ||
419 | |||
420 | m_groupData.RemoveAgentToGroupInvite(GetClientGroupRequestID(remoteClient), inviteID); | ||
421 | } | ||
422 | |||
423 | // Reject | ||
424 | if (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline) | ||
425 | { | ||
426 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received a reject invite notice."); | ||
427 | m_groupData.RemoveAgentToGroupInvite(GetClientGroupRequestID(remoteClient), inviteID); | ||
428 | } | ||
429 | } | ||
430 | } | ||
431 | |||
432 | // Group notices | ||
433 | if ((im.dialog == (byte)InstantMessageDialog.GroupNotice)) | ||
434 | { | ||
435 | if (!m_groupNoticesEnabled) | ||
436 | { | ||
437 | return; | ||
438 | } | ||
439 | |||
440 | UUID GroupID = new UUID(im.toAgentID); | ||
441 | if (m_groupData.GetGroupRecord(GetClientGroupRequestID(remoteClient), GroupID, null) != null) | ||
442 | { | ||
443 | UUID NoticeID = UUID.Random(); | ||
444 | string Subject = im.message.Substring(0, im.message.IndexOf('|')); | ||
445 | string Message = im.message.Substring(Subject.Length + 1); | ||
446 | |||
447 | byte[] bucket; | ||
448 | |||
449 | if ((im.binaryBucket.Length == 1) && (im.binaryBucket[0] == 0)) | ||
450 | { | ||
451 | bucket = new byte[19]; | ||
452 | bucket[0] = 0; //dunno | ||
453 | bucket[1] = 0; //dunno | ||
454 | GroupID.ToBytes(bucket, 2); | ||
455 | bucket[18] = 0; //dunno | ||
456 | } | ||
457 | else | ||
458 | { | ||
459 | string binBucket = OpenMetaverse.Utils.BytesToString(im.binaryBucket); | ||
460 | binBucket = binBucket.Remove(0, 14).Trim(); | ||
461 | if (m_debugEnabled) | ||
462 | { | ||
463 | m_log.WarnFormat("I don't understand a group notice binary bucket of: {0}", binBucket); | ||
464 | |||
465 | OSDMap binBucketOSD = (OSDMap)OSDParser.DeserializeLLSDXml(binBucket); | ||
466 | |||
467 | foreach (string key in binBucketOSD.Keys) | ||
468 | { | ||
469 | m_log.WarnFormat("{0}: {1}", key, binBucketOSD[key].ToString()); | ||
470 | } | ||
471 | } | ||
472 | |||
473 | // treat as if no attachment | ||
474 | bucket = new byte[19]; | ||
475 | bucket[0] = 0; //dunno | ||
476 | bucket[1] = 0; //dunno | ||
477 | GroupID.ToBytes(bucket, 2); | ||
478 | bucket[18] = 0; //dunno | ||
479 | } | ||
480 | |||
481 | m_groupData.AddGroupNotice(GetClientGroupRequestID(remoteClient), GroupID, NoticeID, im.fromAgentName, Subject, Message, bucket); | ||
482 | if (OnNewGroupNotice != null) | ||
483 | { | ||
484 | OnNewGroupNotice(GroupID, NoticeID); | ||
485 | } | ||
486 | |||
487 | // Send notice out to everyone that wants notices | ||
488 | foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetClientGroupRequestID(remoteClient), GroupID)) | ||
489 | { | ||
490 | if (member.AcceptNotices) | ||
491 | { | ||
492 | // Build notice IIM | ||
493 | GridInstantMessage msg = CreateGroupNoticeIM(UUID.Zero, NoticeID, (byte)OpenMetaverse.InstantMessageDialog.GroupNotice); | ||
494 | |||
495 | msg.toAgentID = member.AgentID.Guid; | ||
496 | OutgoingInstantMessage(msg, member.AgentID); | ||
497 | } | ||
498 | } | ||
499 | } | ||
500 | } | ||
501 | |||
502 | // Interop, received special 210 code for ejecting a group member | ||
503 | // this only works within the comms servers domain, and won't work hypergrid | ||
504 | // TODO:FIXME: Use a presense server of some kind to find out where the | ||
505 | // client actually is, and try contacting that region directly to notify them, | ||
506 | // or provide the notification via xmlrpc update queue | ||
507 | if ((im.dialog == 210)) | ||
508 | { | ||
509 | // This is sent from the region that the ejectee was ejected from | ||
510 | // if it's being delivered here, then the ejectee is here | ||
511 | // so we need to send local updates to the agent. | ||
512 | |||
513 | UUID ejecteeID = new UUID(im.toAgentID); | ||
514 | |||
515 | im.dialog = (byte)InstantMessageDialog.MessageFromAgent; | ||
516 | OutgoingInstantMessage(im, ejecteeID); | ||
517 | |||
518 | IClientAPI ejectee = GetActiveClient(ejecteeID); | ||
519 | if (ejectee != null) | ||
520 | { | ||
521 | UUID groupID = new UUID(im.fromAgentID); | ||
522 | ejectee.SendAgentDropGroup(groupID); | ||
523 | } | ||
524 | } | ||
525 | } | ||
526 | |||
527 | private void OnGridInstantMessage(GridInstantMessage msg) | ||
528 | { | ||
529 | if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
530 | |||
531 | // Trigger the above event handler | ||
532 | OnInstantMessage(null, msg); | ||
533 | |||
534 | // If a message from a group arrives here, it may need to be forwarded to a local client | ||
535 | if (msg.fromGroup == true) | ||
536 | { | ||
537 | switch (msg.dialog) | ||
538 | { | ||
539 | case (byte)InstantMessageDialog.GroupInvitation: | ||
540 | case (byte)InstantMessageDialog.GroupNotice: | ||
541 | UUID toAgentID = new UUID(msg.toAgentID); | ||
542 | IClientAPI localClient = GetActiveClient(toAgentID); | ||
543 | if (localClient != null) | ||
544 | { | ||
545 | localClient.SendInstantMessage(msg); | ||
546 | } | ||
547 | break; | ||
548 | } | ||
549 | } | ||
550 | } | ||
551 | |||
552 | #endregion | ||
553 | |||
554 | #region IGroupsModule Members | ||
555 | |||
556 | public event NewGroupNotice OnNewGroupNotice; | ||
557 | |||
558 | public GroupRecord GetGroupRecord(UUID GroupID) | ||
559 | { | ||
560 | return m_groupData.GetGroupRecord(null, GroupID, null); | ||
561 | } | ||
562 | |||
563 | public void ActivateGroup(IClientAPI remoteClient, UUID groupID) | ||
564 | { | ||
565 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
566 | |||
567 | m_groupData.SetAgentActiveGroup(GetClientGroupRequestID(remoteClient), remoteClient.AgentId, groupID); | ||
568 | |||
569 | // Changing active group changes title, active powers, all kinds of things | ||
570 | // anyone who is in any region that can see this client, should probably be | ||
571 | // updated with new group info. At a minimum, they should get ScenePresence | ||
572 | // updated with new title. | ||
573 | UpdateAllClientsWithGroupInfo(remoteClient.AgentId); | ||
574 | } | ||
575 | |||
576 | /// <summary> | ||
577 | /// Get the Role Titles for an Agent, for a specific group | ||
578 | /// </summary> | ||
579 | public List<GroupTitlesData> GroupTitlesRequest(IClientAPI remoteClient, UUID groupID) | ||
580 | { | ||
581 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
582 | |||
583 | GroupRequestID grID = GetClientGroupRequestID(remoteClient); | ||
584 | |||
585 | List<GroupRolesData> agentRoles = m_groupData.GetAgentGroupRoles(grID, remoteClient.AgentId, groupID); | ||
586 | GroupMembershipData agentMembership = m_groupData.GetAgentGroupMembership(grID, remoteClient.AgentId, groupID); | ||
587 | |||
588 | List<GroupTitlesData> titles = new List<GroupTitlesData>(); | ||
589 | foreach (GroupRolesData role in agentRoles) | ||
590 | { | ||
591 | GroupTitlesData title = new GroupTitlesData(); | ||
592 | title.Name = role.Name; | ||
593 | if (agentMembership != null) | ||
594 | { | ||
595 | title.Selected = agentMembership.ActiveRole == role.RoleID; | ||
596 | } | ||
597 | title.UUID = role.RoleID; | ||
598 | |||
599 | titles.Add(title); | ||
600 | } | ||
601 | |||
602 | return titles; | ||
603 | } | ||
604 | |||
605 | public List<GroupMembersData> GroupMembersRequest(IClientAPI remoteClient, UUID groupID) | ||
606 | { | ||
607 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
608 | |||
609 | List<GroupMembersData> data = m_groupData.GetGroupMembers(GetClientGroupRequestID(remoteClient), groupID); | ||
610 | if (m_debugEnabled) | ||
611 | { | ||
612 | foreach (GroupMembersData member in data) | ||
613 | { | ||
614 | m_log.DebugFormat("[GROUPS]: {0} {1}", member.AgentID, member.Title); | ||
615 | } | ||
616 | } | ||
617 | |||
618 | return data; | ||
619 | |||
620 | } | ||
621 | |||
622 | public List<GroupRolesData> GroupRoleDataRequest(IClientAPI remoteClient, UUID groupID) | ||
623 | { | ||
624 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
625 | |||
626 | List<GroupRolesData> data = m_groupData.GetGroupRoles(GetClientGroupRequestID(remoteClient), groupID); | ||
627 | |||
628 | if (m_debugEnabled) | ||
629 | { | ||
630 | foreach (GroupRolesData member in data) | ||
631 | { | ||
632 | m_log.DebugFormat("[GROUPS]: {0} {1}", member.Title, member.Members); | ||
633 | } | ||
634 | } | ||
635 | |||
636 | return data; | ||
637 | |||
638 | } | ||
639 | |||
640 | public List<GroupRoleMembersData> GroupRoleMembersRequest(IClientAPI remoteClient, UUID groupID) | ||
641 | { | ||
642 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
643 | |||
644 | List<GroupRoleMembersData> data = m_groupData.GetGroupRoleMembers(GetClientGroupRequestID(remoteClient), groupID); | ||
645 | |||
646 | if (m_debugEnabled) | ||
647 | { | ||
648 | foreach (GroupRoleMembersData member in data) | ||
649 | { | ||
650 | m_log.DebugFormat("[GROUPS]: Av: {0} Role: {1}", member.MemberID, member.RoleID); | ||
651 | } | ||
652 | } | ||
653 | |||
654 | return data; | ||
655 | |||
656 | |||
657 | } | ||
658 | |||
659 | public GroupProfileData GroupProfileRequest(IClientAPI remoteClient, UUID groupID) | ||
660 | { | ||
661 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
662 | |||
663 | GroupProfileData profile = new GroupProfileData(); | ||
664 | |||
665 | GroupRequestID grID = GetClientGroupRequestID(remoteClient); | ||
666 | |||
667 | GroupRecord groupInfo = m_groupData.GetGroupRecord(GetClientGroupRequestID(remoteClient), groupID, null); | ||
668 | if (groupInfo != null) | ||
669 | { | ||
670 | profile.AllowPublish = groupInfo.AllowPublish; | ||
671 | profile.Charter = groupInfo.Charter; | ||
672 | profile.FounderID = groupInfo.FounderID; | ||
673 | profile.GroupID = groupID; | ||
674 | profile.GroupMembershipCount = m_groupData.GetGroupMembers(grID, groupID).Count; | ||
675 | profile.GroupRolesCount = m_groupData.GetGroupRoles(grID, groupID).Count; | ||
676 | profile.InsigniaID = groupInfo.GroupPicture; | ||
677 | profile.MaturePublish = groupInfo.MaturePublish; | ||
678 | profile.MembershipFee = groupInfo.MembershipFee; | ||
679 | profile.Money = 0; // TODO: Get this from the currency server? | ||
680 | profile.Name = groupInfo.GroupName; | ||
681 | profile.OpenEnrollment = groupInfo.OpenEnrollment; | ||
682 | profile.OwnerRole = groupInfo.OwnerRoleID; | ||
683 | profile.ShowInList = groupInfo.ShowInList; | ||
684 | } | ||
685 | |||
686 | GroupMembershipData memberInfo = m_groupData.GetAgentGroupMembership(grID, remoteClient.AgentId, groupID); | ||
687 | if (memberInfo != null) | ||
688 | { | ||
689 | profile.MemberTitle = memberInfo.GroupTitle; | ||
690 | profile.PowersMask = memberInfo.GroupPowers; | ||
691 | } | ||
692 | |||
693 | return profile; | ||
694 | } | ||
695 | |||
696 | public GroupMembershipData[] GetMembershipData(UUID agentID) | ||
697 | { | ||
698 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
699 | |||
700 | return m_groupData.GetAgentGroupMemberships(null, agentID).ToArray(); | ||
701 | } | ||
702 | |||
703 | public GroupMembershipData GetMembershipData(UUID groupID, UUID agentID) | ||
704 | { | ||
705 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
706 | |||
707 | return m_groupData.GetAgentGroupMembership(null, agentID, groupID); | ||
708 | } | ||
709 | |||
710 | public void UpdateGroupInfo(IClientAPI remoteClient, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish) | ||
711 | { | ||
712 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
713 | |||
714 | // TODO: Security Check? | ||
715 | |||
716 | m_groupData.UpdateGroup(GetClientGroupRequestID(remoteClient), groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish); | ||
717 | } | ||
718 | |||
719 | public void SetGroupAcceptNotices(IClientAPI remoteClient, UUID groupID, bool acceptNotices, bool listInProfile) | ||
720 | { | ||
721 | // TODO: Security Check? | ||
722 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
723 | |||
724 | m_groupData.SetAgentGroupInfo(GetClientGroupRequestID(remoteClient), remoteClient.AgentId, groupID, acceptNotices, listInProfile); | ||
725 | } | ||
726 | |||
727 | public UUID CreateGroup(IClientAPI remoteClient, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish) | ||
728 | { | ||
729 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
730 | |||
731 | GroupRequestID grID = GetClientGroupRequestID(remoteClient); | ||
732 | |||
733 | if (m_groupData.GetGroupRecord(grID, UUID.Zero, name) != null) | ||
734 | { | ||
735 | remoteClient.SendCreateGroupReply(UUID.Zero, false, "A group with the same name already exists."); | ||
736 | return UUID.Zero; | ||
737 | } | ||
738 | // is there is a money module present ? | ||
739 | IMoneyModule money=remoteClient.Scene.RequestModuleInterface<IMoneyModule>(); | ||
740 | if (money != null) | ||
741 | { | ||
742 | // do the transaction, that is if the agent has got sufficient funds | ||
743 | if (!money.GroupCreationCovered(remoteClient)) { | ||
744 | remoteClient.SendCreateGroupReply(UUID.Zero, false, "You have got issuficient funds to create a group."); | ||
745 | return UUID.Zero; | ||
746 | } | ||
747 | money.ApplyGroupCreationCharge(remoteClient.AgentId); | ||
748 | } | ||
749 | UUID groupID = m_groupData.CreateGroup(grID, name, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish, remoteClient.AgentId); | ||
750 | |||
751 | remoteClient.SendCreateGroupReply(groupID, true, "Group created successfullly"); | ||
752 | |||
753 | // Update the founder with new group information. | ||
754 | SendAgentGroupDataUpdate(remoteClient, remoteClient.AgentId); | ||
755 | |||
756 | return groupID; | ||
757 | } | ||
758 | |||
759 | public GroupNoticeData[] GroupNoticesListRequest(IClientAPI remoteClient, UUID groupID) | ||
760 | { | ||
761 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
762 | |||
763 | // ToDo: check if agent is a member of group and is allowed to see notices? | ||
764 | |||
765 | return m_groupData.GetGroupNotices(GetClientGroupRequestID(remoteClient), groupID).ToArray(); | ||
766 | } | ||
767 | |||
768 | /// <summary> | ||
769 | /// Get the title of the agent's current role. | ||
770 | /// </summary> | ||
771 | public string GetGroupTitle(UUID avatarID) | ||
772 | { | ||
773 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
774 | |||
775 | GroupMembershipData membership = m_groupData.GetAgentActiveMembership(null, avatarID); | ||
776 | if (membership != null) | ||
777 | { | ||
778 | return membership.GroupTitle; | ||
779 | } | ||
780 | return string.Empty; | ||
781 | } | ||
782 | |||
783 | /// <summary> | ||
784 | /// Change the current Active Group Role for Agent | ||
785 | /// </summary> | ||
786 | public void GroupTitleUpdate(IClientAPI remoteClient, UUID groupID, UUID titleRoleID) | ||
787 | { | ||
788 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
789 | |||
790 | m_groupData.SetAgentActiveGroupRole(GetClientGroupRequestID(remoteClient), remoteClient.AgentId, groupID, titleRoleID); | ||
791 | |||
792 | // TODO: Not sure what all is needed here, but if the active group role change is for the group | ||
793 | // the client currently has set active, then we need to do a scene presence update too | ||
794 | // if (m_groupData.GetAgentActiveMembership(remoteClient.AgentId).GroupID == GroupID) | ||
795 | |||
796 | UpdateAllClientsWithGroupInfo(remoteClient.AgentId); | ||
797 | } | ||
798 | |||
799 | |||
800 | public void GroupRoleUpdate(IClientAPI remoteClient, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, byte updateType) | ||
801 | { | ||
802 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
803 | |||
804 | // TODO: Security Checks? | ||
805 | |||
806 | GroupRequestID grID = GetClientGroupRequestID(remoteClient); | ||
807 | |||
808 | switch ((OpenMetaverse.GroupRoleUpdate)updateType) | ||
809 | { | ||
810 | case OpenMetaverse.GroupRoleUpdate.Create: | ||
811 | m_groupData.AddGroupRole(grID, groupID, UUID.Random(), name, description, title, powers); | ||
812 | break; | ||
813 | |||
814 | case OpenMetaverse.GroupRoleUpdate.Delete: | ||
815 | m_groupData.RemoveGroupRole(grID, groupID, roleID); | ||
816 | break; | ||
817 | |||
818 | case OpenMetaverse.GroupRoleUpdate.UpdateAll: | ||
819 | case OpenMetaverse.GroupRoleUpdate.UpdateData: | ||
820 | case OpenMetaverse.GroupRoleUpdate.UpdatePowers: | ||
821 | m_groupData.UpdateGroupRole(grID, groupID, roleID, name, description, title, powers); | ||
822 | break; | ||
823 | |||
824 | case OpenMetaverse.GroupRoleUpdate.NoUpdate: | ||
825 | default: | ||
826 | // No Op | ||
827 | break; | ||
828 | |||
829 | } | ||
830 | |||
831 | // TODO: This update really should send out updates for everyone in the role that just got changed. | ||
832 | SendAgentGroupDataUpdate(remoteClient, remoteClient.AgentId); | ||
833 | } | ||
834 | |||
835 | public void GroupRoleChanges(IClientAPI remoteClient, UUID groupID, UUID roleID, UUID memberID, uint changes) | ||
836 | { | ||
837 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
838 | // Todo: Security check | ||
839 | |||
840 | GroupRequestID grID = GetClientGroupRequestID(remoteClient); | ||
841 | |||
842 | switch (changes) | ||
843 | { | ||
844 | case 0: | ||
845 | // Add | ||
846 | m_groupData.AddAgentToGroupRole(grID, memberID, groupID, roleID); | ||
847 | |||
848 | break; | ||
849 | case 1: | ||
850 | // Remove | ||
851 | m_groupData.RemoveAgentFromGroupRole(grID, memberID, groupID, roleID); | ||
852 | |||
853 | break; | ||
854 | default: | ||
855 | m_log.ErrorFormat("[GROUPS]: {0} does not understand changes == {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, changes); | ||
856 | break; | ||
857 | } | ||
858 | |||
859 | // TODO: This update really should send out updates for everyone in the role that just got changed. | ||
860 | SendAgentGroupDataUpdate(remoteClient, remoteClient.AgentId); | ||
861 | } | ||
862 | |||
863 | public void GroupNoticeRequest(IClientAPI remoteClient, UUID groupNoticeID) | ||
864 | { | ||
865 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
866 | |||
867 | GroupRequestID grID = GetClientGroupRequestID(remoteClient); | ||
868 | |||
869 | GroupNoticeInfo data = m_groupData.GetGroupNotice(grID, groupNoticeID); | ||
870 | |||
871 | if (data != null) | ||
872 | { | ||
873 | GroupRecord groupInfo = m_groupData.GetGroupRecord(grID, data.GroupID, null); | ||
874 | |||
875 | GridInstantMessage msg = new GridInstantMessage(); | ||
876 | msg.imSessionID = UUID.Zero.Guid; | ||
877 | msg.fromAgentID = data.GroupID.Guid; | ||
878 | msg.toAgentID = remoteClient.AgentId.Guid; | ||
879 | msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); | ||
880 | msg.fromAgentName = "Group Notice : " + groupInfo == null ? "Unknown" : groupInfo.GroupName; | ||
881 | msg.message = data.noticeData.Subject + "|" + data.Message; | ||
882 | msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupNoticeRequested; | ||
883 | msg.fromGroup = true; | ||
884 | msg.offline = (byte)0; | ||
885 | msg.ParentEstateID = 0; | ||
886 | msg.Position = Vector3.Zero; | ||
887 | msg.RegionID = UUID.Zero.Guid; | ||
888 | msg.binaryBucket = data.BinaryBucket; | ||
889 | |||
890 | OutgoingInstantMessage(msg, remoteClient.AgentId); | ||
891 | } | ||
892 | |||
893 | } | ||
894 | |||
895 | public GridInstantMessage CreateGroupNoticeIM(UUID agentID, UUID groupNoticeID, byte dialog) | ||
896 | { | ||
897 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
898 | |||
899 | GridInstantMessage msg = new GridInstantMessage(); | ||
900 | msg.imSessionID = UUID.Zero.Guid; | ||
901 | msg.toAgentID = agentID.Guid; | ||
902 | msg.dialog = dialog; | ||
903 | // msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupNotice; | ||
904 | msg.fromGroup = true; | ||
905 | msg.offline = (byte)1; // Allow this message to be stored for offline use | ||
906 | msg.ParentEstateID = 0; | ||
907 | msg.Position = Vector3.Zero; | ||
908 | msg.RegionID = UUID.Zero.Guid; | ||
909 | |||
910 | GroupNoticeInfo info = m_groupData.GetGroupNotice(null, groupNoticeID); | ||
911 | if (info != null) | ||
912 | { | ||
913 | msg.fromAgentID = info.GroupID.Guid; | ||
914 | msg.timestamp = info.noticeData.Timestamp; | ||
915 | msg.fromAgentName = info.noticeData.FromName; | ||
916 | msg.message = info.noticeData.Subject + "|" + info.Message; | ||
917 | msg.binaryBucket = info.BinaryBucket; | ||
918 | } | ||
919 | else | ||
920 | { | ||
921 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Group Notice {0} not found, composing empty message.", groupNoticeID); | ||
922 | msg.fromAgentID = UUID.Zero.Guid; | ||
923 | msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); ; | ||
924 | msg.fromAgentName = string.Empty; | ||
925 | msg.message = string.Empty; | ||
926 | msg.binaryBucket = new byte[0]; | ||
927 | } | ||
928 | |||
929 | return msg; | ||
930 | } | ||
931 | |||
932 | public void SendAgentGroupDataUpdate(IClientAPI remoteClient) | ||
933 | { | ||
934 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
935 | |||
936 | // Send agent information about his groups | ||
937 | SendAgentGroupDataUpdate(remoteClient, remoteClient.AgentId); | ||
938 | } | ||
939 | |||
940 | public void JoinGroupRequest(IClientAPI remoteClient, UUID groupID) | ||
941 | { | ||
942 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
943 | |||
944 | // Should check to see if OpenEnrollment, or if there's an outstanding invitation | ||
945 | m_groupData.AddAgentToGroup(GetClientGroupRequestID(remoteClient), remoteClient.AgentId, groupID, UUID.Zero); | ||
946 | |||
947 | remoteClient.SendJoinGroupReply(groupID, true); | ||
948 | |||
949 | // Should this send updates to everyone in the group? | ||
950 | SendAgentGroupDataUpdate(remoteClient, remoteClient.AgentId); | ||
951 | } | ||
952 | |||
953 | public void LeaveGroupRequest(IClientAPI remoteClient, UUID groupID) | ||
954 | { | ||
955 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
956 | |||
957 | m_groupData.RemoveAgentFromGroup(GetClientGroupRequestID(remoteClient), remoteClient.AgentId, groupID); | ||
958 | |||
959 | remoteClient.SendLeaveGroupReply(groupID, true); | ||
960 | |||
961 | remoteClient.SendAgentDropGroup(groupID); | ||
962 | |||
963 | // SL sends out notifcations to the group messaging session that the person has left | ||
964 | // Should this also update everyone who is in the group? | ||
965 | SendAgentGroupDataUpdate(remoteClient, remoteClient.AgentId); | ||
966 | } | ||
967 | |||
968 | public void EjectGroupMemberRequest(IClientAPI remoteClient, UUID groupID, UUID ejecteeID) | ||
969 | { | ||
970 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
971 | |||
972 | GroupRequestID grID = GetClientGroupRequestID(remoteClient); | ||
973 | |||
974 | // Todo: Security check? | ||
975 | m_groupData.RemoveAgentFromGroup(grID, ejecteeID, groupID); | ||
976 | |||
977 | remoteClient.SendEjectGroupMemberReply(remoteClient.AgentId, groupID, true); | ||
978 | |||
979 | GroupRecord groupInfo = m_groupData.GetGroupRecord(grID, groupID, null); | ||
980 | UserProfileData userProfile = m_sceneList[0].CommsManager.UserService.GetUserProfile(ejecteeID); | ||
981 | |||
982 | if ((groupInfo == null) || (userProfile == null)) | ||
983 | { | ||
984 | return; | ||
985 | } | ||
986 | |||
987 | |||
988 | // Send Message to Ejectee | ||
989 | GridInstantMessage msg = new GridInstantMessage(); | ||
990 | |||
991 | msg.imSessionID = UUID.Zero.Guid; | ||
992 | msg.fromAgentID = remoteClient.AgentId.Guid; | ||
993 | // msg.fromAgentID = info.GroupID; | ||
994 | msg.toAgentID = ejecteeID.Guid; | ||
995 | //msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); | ||
996 | msg.timestamp = 0; | ||
997 | msg.fromAgentName = remoteClient.Name; | ||
998 | msg.message = string.Format("You have been ejected from '{1}' by {0}.", remoteClient.Name, groupInfo.GroupName); | ||
999 | msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageFromAgent; | ||
1000 | msg.fromGroup = false; | ||
1001 | msg.offline = (byte)0; | ||
1002 | msg.ParentEstateID = 0; | ||
1003 | msg.Position = Vector3.Zero; | ||
1004 | msg.RegionID = remoteClient.Scene.RegionInfo.RegionID.Guid; | ||
1005 | msg.binaryBucket = new byte[0]; | ||
1006 | OutgoingInstantMessage(msg, ejecteeID); | ||
1007 | |||
1008 | |||
1009 | // Message to ejector | ||
1010 | // Interop, received special 210 code for ejecting a group member | ||
1011 | // this only works within the comms servers domain, and won't work hypergrid | ||
1012 | // TODO:FIXME: Use a presense server of some kind to find out where the | ||
1013 | // client actually is, and try contacting that region directly to notify them, | ||
1014 | // or provide the notification via xmlrpc update queue | ||
1015 | |||
1016 | msg = new GridInstantMessage(); | ||
1017 | msg.imSessionID = UUID.Zero.Guid; | ||
1018 | msg.fromAgentID = remoteClient.AgentId.Guid; | ||
1019 | msg.toAgentID = remoteClient.AgentId.Guid; | ||
1020 | msg.timestamp = 0; | ||
1021 | msg.fromAgentName = remoteClient.Name; | ||
1022 | if (userProfile != null) | ||
1023 | { | ||
1024 | msg.message = string.Format("{2} has been ejected from '{1}' by {0}.", remoteClient.Name, groupInfo.GroupName, userProfile.Name); | ||
1025 | } | ||
1026 | else | ||
1027 | { | ||
1028 | msg.message = string.Format("{2} has been ejected from '{1}' by {0}.", remoteClient.Name, groupInfo.GroupName, "Unknown member"); | ||
1029 | } | ||
1030 | msg.dialog = (byte)210; //interop | ||
1031 | msg.fromGroup = false; | ||
1032 | msg.offline = (byte)0; | ||
1033 | msg.ParentEstateID = 0; | ||
1034 | msg.Position = Vector3.Zero; | ||
1035 | msg.RegionID = remoteClient.Scene.RegionInfo.RegionID.Guid; | ||
1036 | msg.binaryBucket = new byte[0]; | ||
1037 | OutgoingInstantMessage(msg, remoteClient.AgentId); | ||
1038 | |||
1039 | |||
1040 | // SL sends out messages to everyone in the group | ||
1041 | // Who all should receive updates and what should they be updated with? | ||
1042 | UpdateAllClientsWithGroupInfo(ejecteeID); | ||
1043 | } | ||
1044 | |||
1045 | public void InviteGroupRequest(IClientAPI remoteClient, UUID groupID, UUID invitedAgentID, UUID roleID) | ||
1046 | { | ||
1047 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
1048 | |||
1049 | // Todo: Security check, probably also want to send some kind of notification | ||
1050 | UUID InviteID = UUID.Random(); | ||
1051 | GroupRequestID grid = GetClientGroupRequestID(remoteClient); | ||
1052 | |||
1053 | m_groupData.AddAgentToGroupInvite(grid, InviteID, groupID, roleID, invitedAgentID); | ||
1054 | |||
1055 | // Check to see if the invite went through, if it did not then it's possible | ||
1056 | // the remoteClient did not validate or did not have permission to invite. | ||
1057 | GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(grid, InviteID); | ||
1058 | |||
1059 | if (inviteInfo != null) | ||
1060 | { | ||
1061 | if (m_msgTransferModule != null) | ||
1062 | { | ||
1063 | Guid inviteUUID = InviteID.Guid; | ||
1064 | |||
1065 | GridInstantMessage msg = new GridInstantMessage(); | ||
1066 | |||
1067 | msg.imSessionID = inviteUUID; | ||
1068 | |||
1069 | // msg.fromAgentID = remoteClient.AgentId.Guid; | ||
1070 | msg.fromAgentID = groupID.Guid; | ||
1071 | msg.toAgentID = invitedAgentID.Guid; | ||
1072 | //msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); | ||
1073 | msg.timestamp = 0; | ||
1074 | msg.fromAgentName = remoteClient.Name; | ||
1075 | msg.message = string.Format("{0} has invited you to join a group. There is no cost to join this group.", remoteClient.Name); | ||
1076 | msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupInvitation; | ||
1077 | msg.fromGroup = true; | ||
1078 | msg.offline = (byte)0; | ||
1079 | msg.ParentEstateID = 0; | ||
1080 | msg.Position = Vector3.Zero; | ||
1081 | msg.RegionID = remoteClient.Scene.RegionInfo.RegionID.Guid; | ||
1082 | msg.binaryBucket = new byte[20]; | ||
1083 | |||
1084 | OutgoingInstantMessage(msg, invitedAgentID); | ||
1085 | } | ||
1086 | } | ||
1087 | } | ||
1088 | |||
1089 | #endregion | ||
1090 | |||
1091 | #region Client/Update Tools | ||
1092 | |||
1093 | /// <summary> | ||
1094 | /// Try to find an active IClientAPI reference for agentID giving preference to root connections | ||
1095 | /// </summary> | ||
1096 | private IClientAPI GetActiveClient(UUID agentID) | ||
1097 | { | ||
1098 | IClientAPI child = null; | ||
1099 | |||
1100 | // Try root avatar first | ||
1101 | foreach (Scene scene in m_sceneList) | ||
1102 | { | ||
1103 | if (scene.Entities.ContainsKey(agentID) && | ||
1104 | scene.Entities[agentID] is ScenePresence) | ||
1105 | { | ||
1106 | ScenePresence user = (ScenePresence)scene.Entities[agentID]; | ||
1107 | if (!user.IsChildAgent) | ||
1108 | { | ||
1109 | return user.ControllingClient; | ||
1110 | } | ||
1111 | else | ||
1112 | { | ||
1113 | child = user.ControllingClient; | ||
1114 | } | ||
1115 | } | ||
1116 | } | ||
1117 | |||
1118 | // If we didn't find a root, then just return whichever child we found, or null if none | ||
1119 | return child; | ||
1120 | } | ||
1121 | |||
1122 | private GroupRequestID GetClientGroupRequestID(IClientAPI client) | ||
1123 | { | ||
1124 | if (client == null) | ||
1125 | { | ||
1126 | return new GroupRequestID(); | ||
1127 | } | ||
1128 | |||
1129 | lock (m_clientRequestIDInfo) | ||
1130 | { | ||
1131 | if (!m_clientRequestIDInfo.ContainsKey(client.AgentId)) | ||
1132 | { | ||
1133 | GroupRequestIDInfo info = new GroupRequestIDInfo(); | ||
1134 | info.RequestID.AgentID = client.AgentId; | ||
1135 | info.RequestID.SessionID = client.SessionId; | ||
1136 | |||
1137 | UserProfileData userProfile = m_sceneList[0].CommsManager.UserService.GetUserProfile(client.AgentId); | ||
1138 | if (userProfile == null) | ||
1139 | { | ||
1140 | // This should be impossible. If I've been passed a reference to a client | ||
1141 | // that client should be registered with the UserService. So something | ||
1142 | // is horribly wrong somewhere. | ||
1143 | |||
1144 | m_log.WarnFormat("[GROUPS]: Could not find a user profile for {0} / {1}", client.Name, client.AgentId); | ||
1145 | |||
1146 | // Default to local user service and hope for the best? | ||
1147 | info.RequestID.UserServiceURL = m_sceneList[0].CommsManager.NetworkServersInfo.UserURL; | ||
1148 | |||
1149 | } | ||
1150 | else if (userProfile is ForeignUserProfileData) | ||
1151 | { | ||
1152 | // They aren't from around here | ||
1153 | ForeignUserProfileData fupd = (ForeignUserProfileData)userProfile; | ||
1154 | info.RequestID.UserServiceURL = fupd.UserServerURI; | ||
1155 | } | ||
1156 | else | ||
1157 | { | ||
1158 | // They're a local user, use this: | ||
1159 | info.RequestID.UserServiceURL = m_sceneList[0].CommsManager.NetworkServersInfo.UserURL; | ||
1160 | } | ||
1161 | |||
1162 | m_clientRequestIDInfo.Add(client.AgentId, info); | ||
1163 | } | ||
1164 | |||
1165 | m_clientRequestIDInfo[client.AgentId].LastUsedTMStamp = DateTime.Now; | ||
1166 | } | ||
1167 | return m_clientRequestIDInfo[client.AgentId].RequestID; | ||
1168 | } | ||
1169 | |||
1170 | /// <summary> | ||
1171 | /// Send 'remoteClient' the group membership 'data' for agent 'dataForAgentID'. | ||
1172 | /// </summary> | ||
1173 | private void SendGroupMembershipInfoViaCaps(IClientAPI remoteClient, UUID dataForAgentID, GroupMembershipData[] data) | ||
1174 | { | ||
1175 | if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
1176 | |||
1177 | OSDArray AgentData = new OSDArray(1); | ||
1178 | OSDMap AgentDataMap = new OSDMap(1); | ||
1179 | AgentDataMap.Add("AgentID", OSD.FromUUID(dataForAgentID)); | ||
1180 | AgentData.Add(AgentDataMap); | ||
1181 | |||
1182 | |||
1183 | OSDArray GroupData = new OSDArray(data.Length); | ||
1184 | OSDArray NewGroupData = new OSDArray(data.Length); | ||
1185 | |||
1186 | foreach (GroupMembershipData membership in data) | ||
1187 | { | ||
1188 | OSDMap GroupDataMap = new OSDMap(6); | ||
1189 | OSDMap NewGroupDataMap = new OSDMap(1); | ||
1190 | |||
1191 | GroupDataMap.Add("GroupID", OSD.FromUUID(membership.GroupID)); | ||
1192 | GroupDataMap.Add("GroupPowers", OSD.FromBinary(membership.GroupPowers)); | ||
1193 | GroupDataMap.Add("AcceptNotices", OSD.FromBoolean(membership.AcceptNotices)); | ||
1194 | GroupDataMap.Add("GroupInsigniaID", OSD.FromUUID(membership.GroupPicture)); | ||
1195 | GroupDataMap.Add("Contribution", OSD.FromInteger(membership.Contribution)); | ||
1196 | GroupDataMap.Add("GroupName", OSD.FromString(membership.GroupName)); | ||
1197 | NewGroupDataMap.Add("ListInProfile", OSD.FromBoolean(membership.ListInProfile)); | ||
1198 | |||
1199 | GroupData.Add(GroupDataMap); | ||
1200 | NewGroupData.Add(NewGroupDataMap); | ||
1201 | } | ||
1202 | |||
1203 | OSDMap llDataStruct = new OSDMap(3); | ||
1204 | llDataStruct.Add("AgentData", AgentData); | ||
1205 | llDataStruct.Add("GroupData", GroupData); | ||
1206 | llDataStruct.Add("NewGroupData", NewGroupData); | ||
1207 | |||
1208 | IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>(); | ||
1209 | |||
1210 | if (queue != null) | ||
1211 | { | ||
1212 | queue.Enqueue(EventQueueHelper.buildEvent("AgentGroupDataUpdate", llDataStruct), remoteClient.AgentId); | ||
1213 | } | ||
1214 | |||
1215 | } | ||
1216 | |||
1217 | private void SendScenePresenceUpdate(UUID AgentID, string Title) | ||
1218 | { | ||
1219 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Updating scene title for {0} with title: {1}", AgentID, Title); | ||
1220 | |||
1221 | ScenePresence presence = null; | ||
1222 | lock (m_sceneList) | ||
1223 | { | ||
1224 | foreach (Scene scene in m_sceneList) | ||
1225 | { | ||
1226 | presence = scene.GetScenePresence(AgentID); | ||
1227 | if (presence != null) | ||
1228 | { | ||
1229 | presence.Grouptitle = Title; | ||
1230 | |||
1231 | // FixMe: Ter suggests a "Schedule" method that I can't find. | ||
1232 | presence.SendFullUpdateToAllClients(); | ||
1233 | } | ||
1234 | } | ||
1235 | } | ||
1236 | } | ||
1237 | |||
1238 | /// <summary> | ||
1239 | /// Send updates to all clients who might be interested in groups data for dataForClientID | ||
1240 | /// </summary> | ||
1241 | private void UpdateAllClientsWithGroupInfo(UUID dataForClientID) | ||
1242 | { | ||
1243 | if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
1244 | |||
1245 | // TODO: Probably isn't nessesary to update every client in every scene. | ||
1246 | // Need to examine client updates and do only what's nessesary. | ||
1247 | lock (m_sceneList) | ||
1248 | { | ||
1249 | foreach (Scene scene in m_sceneList) | ||
1250 | { | ||
1251 | scene.ForEachClient(delegate(IClientAPI client) { SendAgentGroupDataUpdate(client, dataForClientID); }); | ||
1252 | } | ||
1253 | } | ||
1254 | } | ||
1255 | |||
1256 | /// <summary> | ||
1257 | /// Update remoteClient with group information about dataForAgentID | ||
1258 | /// </summary> | ||
1259 | private void SendAgentGroupDataUpdate(IClientAPI remoteClient, UUID dataForAgentID) | ||
1260 | { | ||
1261 | if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called for {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, remoteClient.Name); | ||
1262 | |||
1263 | // TODO: All the client update functions need to be reexamined because most do too much and send too much stuff | ||
1264 | |||
1265 | OnAgentDataUpdateRequest(remoteClient, dataForAgentID, UUID.Zero); | ||
1266 | |||
1267 | |||
1268 | // Need to send a group membership update to the client | ||
1269 | // UDP version doesn't seem to behave nicely. But we're going to send it out here | ||
1270 | // with an empty group membership to hopefully remove groups being displayed due | ||
1271 | // to the core Groups Stub | ||
1272 | remoteClient.SendGroupMembership(new GroupMembershipData[0]); | ||
1273 | |||
1274 | GroupMembershipData[] membershipData = m_groupData.GetAgentGroupMemberships(GetClientGroupRequestID(remoteClient), dataForAgentID).ToArray(); | ||
1275 | |||
1276 | SendGroupMembershipInfoViaCaps(remoteClient, dataForAgentID, membershipData); | ||
1277 | remoteClient.SendAvatarGroupsReply(dataForAgentID, membershipData); | ||
1278 | |||
1279 | } | ||
1280 | |||
1281 | private void SendAgentDataUpdate(IClientAPI remoteClient, UUID dataForAgentID, UUID activeGroupID, string activeGroupName, ulong activeGroupPowers, string activeGroupTitle) | ||
1282 | { | ||
1283 | if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
1284 | |||
1285 | // TODO: All the client update functions need to be reexamined because most do too much and send too much stuff | ||
1286 | UserProfileData userProfile = m_sceneList[0].CommsManager.UserService.GetUserProfile(dataForAgentID); | ||
1287 | string firstname, lastname; | ||
1288 | if (userProfile != null) | ||
1289 | { | ||
1290 | firstname = userProfile.FirstName; | ||
1291 | lastname = userProfile.SurName; | ||
1292 | } | ||
1293 | else | ||
1294 | { | ||
1295 | firstname = "Unknown"; | ||
1296 | lastname = "Unknown"; | ||
1297 | } | ||
1298 | |||
1299 | remoteClient.SendAgentDataUpdate(dataForAgentID, activeGroupID, firstname, | ||
1300 | lastname, activeGroupPowers, activeGroupName, | ||
1301 | activeGroupTitle); | ||
1302 | } | ||
1303 | |||
1304 | #endregion | ||
1305 | |||
1306 | #region IM Backed Processes | ||
1307 | |||
1308 | private void OutgoingInstantMessage(GridInstantMessage msg, UUID msgTo) | ||
1309 | { | ||
1310 | if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); | ||
1311 | |||
1312 | IClientAPI localClient = GetActiveClient(msgTo); | ||
1313 | if (localClient != null) | ||
1314 | { | ||
1315 | if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: MsgTo ({0}) is local, delivering directly", localClient.Name); | ||
1316 | localClient.SendInstantMessage(msg); | ||
1317 | } | ||
1318 | else | ||
1319 | { | ||
1320 | if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: MsgTo ({0}) is not local, delivering via TransferModule", msgTo); | ||
1321 | m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Message Sent: {0}", success?"Succeeded":"Failed"); }); | ||
1322 | } | ||
1323 | } | ||
1324 | |||
1325 | public void NotifyChange(UUID groupID) | ||
1326 | { | ||
1327 | // Notify all group members of a chnge in group roles and/or | ||
1328 | // permissions | ||
1329 | // | ||
1330 | } | ||
1331 | |||
1332 | #endregion | ||
1333 | } | ||
1334 | |||
1335 | } | ||