aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs')
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs1554
1 files changed, 1554 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..dff3f78
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs
@@ -0,0 +1,1554 @@
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
28using System;
29using System.Collections.Generic;
30using System.Reflection;
31using System.Timers;
32using log4net;
33using Mono.Addins;
34using Nini.Config;
35using OpenMetaverse;
36using OpenMetaverse.StructuredData;
37using OpenSim.Framework;
38using OpenSim.Framework.Communications;
39using OpenSim.Region.Framework.Interfaces;
40using OpenSim.Region.Framework.Scenes;
41using OpenSim.Services.Interfaces;
42using System.Text;
43using DirFindFlags = OpenMetaverse.DirectoryManager.DirFindFlags;
44
45namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
46{
47 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsModule")]
48 public class GroupsModule : ISharedRegionModule, IGroupsModule
49 {
50 /// <summary>
51 /// ; To use this module, you must specify the following in your OpenSim.ini
52 /// [GROUPS]
53 /// Enabled = true
54 ///
55 /// Module = GroupsModule
56 /// NoticesEnabled = true
57 /// DebugEnabled = true
58 ///
59 /// GroupsServicesConnectorModule = XmlRpcGroupsServicesConnector
60 /// XmlRpcServiceURL = http://osflotsam.org/xmlrpc.php
61 /// XmlRpcServiceReadKey = 1234
62 /// XmlRpcServiceWriteKey = 1234
63 ///
64 /// MessagingModule = GroupsMessagingModule
65 /// MessagingEnabled = true
66 ///
67 /// ; Disables HTTP Keep-Alive for Groups Module HTTP Requests, work around for
68 /// ; a problem discovered on some Windows based region servers. Only disable
69 /// ; if you see a large number (dozens) of the following Exceptions:
70 /// ; System.Net.WebException: The request was aborted: The request was canceled.
71 ///
72 /// XmlRpcDisableKeepAlive = false
73 /// </summary>
74
75 private static readonly ILog m_log =
76 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
77
78 private List<Scene> m_sceneList = new List<Scene>();
79
80 private IMessageTransferModule m_msgTransferModule;
81
82 private IGroupsMessagingModule m_groupsMessagingModule;
83
84 private IGroupsServicesConnector m_groupData;
85
86 // Configuration settings
87 private bool m_groupsEnabled = false;
88 private bool m_groupNoticesEnabled = true;
89 private bool m_debugEnabled = false;
90 private int m_levelGroupCreate = 0;
91
92 #region Region Module interfaceBase Members
93
94 public void Initialise(IConfigSource config)
95 {
96 IConfig groupsConfig = config.Configs["Groups"];
97
98 if (groupsConfig == null)
99 {
100 // Do not run this module by default.
101 return;
102 }
103 else
104 {
105 m_groupsEnabled = groupsConfig.GetBoolean("Enabled", false);
106 if (!m_groupsEnabled)
107 {
108 return;
109 }
110
111 if (groupsConfig.GetString("Module", "Default") != Name)
112 {
113 m_groupsEnabled = false;
114
115 return;
116 }
117
118 m_log.InfoFormat("[GROUPS]: Initializing {0}", this.Name);
119
120 m_groupNoticesEnabled = groupsConfig.GetBoolean("NoticesEnabled", true);
121 m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", false);
122 m_levelGroupCreate = groupsConfig.GetInt("LevelGroupCreate", 0);
123 }
124 }
125
126 public void AddRegion(Scene scene)
127 {
128 if (m_groupsEnabled)
129 {
130 scene.RegisterModuleInterface<IGroupsModule>(this);
131 scene.AddCommand(
132 "Debug",
133 this,
134 "debug groups verbose",
135 "debug groups verbose <true|false>",
136 "This setting turns on very verbose groups debugging",
137 HandleDebugGroupsVerbose);
138 }
139 }
140
141 private void HandleDebugGroupsVerbose(object modules, string[] args)
142 {
143 if (args.Length < 4)
144 {
145 MainConsole.Instance.Output("Usage: debug groups verbose <true|false>");
146 return;
147 }
148
149 bool verbose = false;
150 if (!bool.TryParse(args[3], out verbose))
151 {
152 MainConsole.Instance.Output("Usage: debug groups verbose <true|false>");
153 return;
154 }
155
156 m_debugEnabled = verbose;
157
158 MainConsole.Instance.OutputFormat("{0} verbose logging set to {1}", Name, m_debugEnabled);
159 }
160
161 public void RegionLoaded(Scene scene)
162 {
163 if (!m_groupsEnabled)
164 return;
165
166 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
167
168 if (m_groupData == null)
169 {
170 m_groupData = scene.RequestModuleInterface<IGroupsServicesConnector>();
171
172 // No Groups Service Connector, then nothing works...
173 if (m_groupData == null)
174 {
175 m_groupsEnabled = false;
176 m_log.Error("[GROUPS]: Could not get IGroupsServicesConnector");
177 Close();
178 return;
179 }
180 }
181
182 if (m_msgTransferModule == null)
183 {
184 m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
185
186 // No message transfer module, no notices, group invites, rejects, ejects, etc
187 if (m_msgTransferModule == null)
188 {
189 m_groupsEnabled = false;
190 m_log.Warn("[GROUPS]: Could not get IMessageTransferModule");
191 }
192 }
193
194 if (m_groupsMessagingModule == null)
195 {
196 m_groupsMessagingModule = scene.RequestModuleInterface<IGroupsMessagingModule>();
197
198 // No message transfer module, no notices, group invites, rejects, ejects, etc
199 if (m_groupsMessagingModule == null)
200 m_log.Warn("[GROUPS]: Could not get IGroupsMessagingModule");
201 }
202
203 lock (m_sceneList)
204 {
205 m_sceneList.Add(scene);
206 }
207
208 scene.EventManager.OnNewClient += OnNewClient;
209 scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
210 // The InstantMessageModule itself doesn't do this,
211 // so lets see if things explode if we don't do it
212 // scene.EventManager.OnClientClosed += OnClientClosed;
213
214 }
215
216 public void RemoveRegion(Scene scene)
217 {
218 if (!m_groupsEnabled)
219 return;
220
221 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
222
223 lock (m_sceneList)
224 {
225 m_sceneList.Remove(scene);
226 }
227 }
228
229 public void Close()
230 {
231 if (!m_groupsEnabled)
232 return;
233
234 if (m_debugEnabled) m_log.Debug("[GROUPS]: Shutting down Groups module.");
235 }
236
237 public Type ReplaceableInterface
238 {
239 get { return null; }
240 }
241
242 public string Name
243 {
244 get { return "GroupsModule"; }
245 }
246
247 #endregion
248
249 #region ISharedRegionModule Members
250
251 public void PostInitialise()
252 {
253 // NoOp
254 }
255
256 #endregion
257
258 #region EventHandlers
259 private void OnNewClient(IClientAPI client)
260 {
261 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
262
263 client.OnUUIDGroupNameRequest += HandleUUIDGroupNameRequest;
264 client.OnAgentDataUpdateRequest += OnAgentDataUpdateRequest;
265 client.OnRequestAvatarProperties += OnRequestAvatarProperties;
266
267 // Used for Notices and Group Invites/Accept/Reject
268 client.OnInstantMessage += OnInstantMessage;
269
270 // Send client their groups information.
271 SendAgentGroupDataUpdate(client, client.AgentId);
272 }
273
274 private void OnRequestAvatarProperties(IClientAPI remoteClient, UUID avatarID)
275 {
276 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
277
278 //GroupMembershipData[] avatarGroups = m_groupData.GetAgentGroupMemberships(GetRequestingAgentID(remoteClient), avatarID).ToArray();
279 GroupMembershipData[] avatarGroups = GetProfileListedGroupMemberships(remoteClient, avatarID);
280 remoteClient.SendAvatarGroupsReply(avatarID, avatarGroups);
281 }
282
283 /*
284 * This becomes very problematic in a shared module. In a shared module you may have more then one
285 * reference to IClientAPI's, one for 0 or 1 root connections, and 0 or more child connections.
286 * The OnClientClosed event does not provide anything to indicate which one of those should be closed
287 * nor does it provide what scene it was from so that the specific reference can be looked up.
288 * The InstantMessageModule.cs does not currently worry about unregistering the handles,
289 * and it should be an issue, since it's the client that references us not the other way around
290 * , so as long as we don't keep a reference to the client laying around, the client can still be GC'ed
291 private void OnClientClosed(UUID AgentId)
292 {
293 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
294
295 lock (m_ActiveClients)
296 {
297 if (m_ActiveClients.ContainsKey(AgentId))
298 {
299 IClientAPI client = m_ActiveClients[AgentId];
300 client.OnUUIDGroupNameRequest -= HandleUUIDGroupNameRequest;
301 client.OnAgentDataUpdateRequest -= OnAgentDataUpdateRequest;
302 client.OnDirFindQuery -= OnDirFindQuery;
303 client.OnInstantMessage -= OnInstantMessage;
304
305 m_ActiveClients.Remove(AgentId);
306 }
307 else
308 {
309 if (m_debugEnabled) m_log.WarnFormat("[GROUPS]: Client closed that wasn't registered here.");
310 }
311
312
313 }
314 }
315 */
316
317 private void OnAgentDataUpdateRequest(IClientAPI remoteClient, UUID dataForAgentID, UUID sessionID)
318 {
319 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
320
321 UUID activeGroupID = UUID.Zero;
322 string activeGroupTitle = string.Empty;
323 string activeGroupName = string.Empty;
324 ulong activeGroupPowers = (ulong)GroupPowers.None;
325
326 GroupMembershipData membership = m_groupData.GetAgentActiveMembership(GetRequestingAgentID(remoteClient), dataForAgentID);
327 if (membership != null)
328 {
329 activeGroupID = membership.GroupID;
330 activeGroupTitle = membership.GroupTitle;
331 activeGroupPowers = membership.GroupPowers;
332 }
333
334 SendAgentDataUpdate(remoteClient, dataForAgentID, activeGroupID, activeGroupName, activeGroupPowers, activeGroupTitle);
335
336 SendScenePresenceUpdate(dataForAgentID, activeGroupTitle);
337 }
338
339 private void HandleUUIDGroupNameRequest(UUID GroupID, IClientAPI remoteClient)
340 {
341 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
342
343 string GroupName;
344
345 GroupRecord group = m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), GroupID, null);
346 if (group != null)
347 {
348 GroupName = group.GroupName;
349 }
350 else
351 {
352 GroupName = "Unknown";
353 }
354
355 remoteClient.SendGroupNameReply(GroupID, GroupName);
356 }
357
358 private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im)
359 {
360 if (m_debugEnabled)
361 m_log.DebugFormat(
362 "[GROUPS]: {0} called for {1}, message type {2}",
363 System.Reflection.MethodBase.GetCurrentMethod().Name, remoteClient.Name, (InstantMessageDialog)im.dialog);
364
365 // Group invitations
366 if ((im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) || (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline))
367 {
368 UUID inviteID = new UUID(im.imSessionID);
369 GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID);
370
371 if (inviteInfo == null)
372 {
373 if (m_debugEnabled) m_log.WarnFormat("[GROUPS]: Received an Invite IM for an invite that does not exist {0}.", inviteID);
374 return;
375 }
376
377 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Invite is for Agent {0} to Group {1}.", inviteInfo.AgentID, inviteInfo.GroupID);
378
379 UUID fromAgentID = new UUID(im.fromAgentID);
380 if ((inviteInfo != null) && (fromAgentID == inviteInfo.AgentID))
381 {
382 // Accept
383 if (im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept)
384 {
385 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received an accept invite notice.");
386
387 // and the sessionid is the role
388 m_groupData.AddAgentToGroup(GetRequestingAgentID(remoteClient), inviteInfo.AgentID, inviteInfo.GroupID, inviteInfo.RoleID);
389
390 GridInstantMessage msg = new GridInstantMessage();
391 msg.imSessionID = UUID.Zero.Guid;
392 msg.fromAgentID = UUID.Zero.Guid;
393 msg.toAgentID = inviteInfo.AgentID.Guid;
394 msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
395 msg.fromAgentName = "Groups";
396 msg.message = string.Format("You have been added to the group.");
397 msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageBox;
398 msg.fromGroup = false;
399 msg.offline = (byte)0;
400 msg.ParentEstateID = 0;
401 msg.Position = Vector3.Zero;
402 msg.RegionID = UUID.Zero.Guid;
403 msg.binaryBucket = new byte[0];
404
405 OutgoingInstantMessage(msg, inviteInfo.AgentID);
406
407 UpdateAllClientsWithGroupInfo(inviteInfo.AgentID);
408
409 // TODO: If the inviter is still online, they need an agent dataupdate
410 // and maybe group membership updates for the invitee
411
412 m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID);
413 }
414
415 // Reject
416 if (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline)
417 {
418 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received a reject invite notice.");
419 m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID);
420 }
421 }
422 }
423
424 // Group notices
425 if ((im.dialog == (byte)InstantMessageDialog.GroupNotice))
426 {
427 if (!m_groupNoticesEnabled)
428 {
429 return;
430 }
431
432 UUID GroupID = new UUID(im.toAgentID);
433 if (m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), GroupID, null) != null)
434 {
435 UUID NoticeID = UUID.Random();
436 string Subject = im.message.Substring(0, im.message.IndexOf('|'));
437 string Message = im.message.Substring(Subject.Length + 1);
438
439 InventoryItemBase item = null;
440 bool hasAttachment = false;
441 UUID itemID = UUID.Zero; //Assignment to quiet compiler
442 UUID ownerID = UUID.Zero; //Assignment to quiet compiler
443 byte[] bucket;
444
445 if (im.binaryBucket.Length >= 1 && im.binaryBucket[0] > 0)
446 {
447 string binBucket = OpenMetaverse.Utils.BytesToString(im.binaryBucket);
448 binBucket = binBucket.Remove(0, 14).Trim();
449
450 OSDMap binBucketOSD = (OSDMap)OSDParser.DeserializeLLSDXml(binBucket);
451 if (binBucketOSD is OSD)
452 {
453 OSDMap binBucketMap = (OSDMap)binBucketOSD;
454
455 itemID = binBucketMap["item_id"].AsUUID();
456 ownerID = binBucketMap["owner_id"].AsUUID();
457
458 //Attempt to get the details of the attached item.
459 //If sender doesn't own the attachment, the item
460 //variable will be set to null and attachment will
461 //not be included with the group notice.
462 Scene scene = (Scene)remoteClient.Scene;
463 item = new InventoryItemBase(itemID, ownerID);
464 item = scene.InventoryService.GetItem(item);
465
466 if (item != null)
467 {
468 //Got item details so include the attachment.
469 hasAttachment = true;
470 }
471 }
472 else
473 {
474 m_log.DebugFormat("[Groups]: Received OSD with unexpected type: {0}", binBucketOSD.GetType());
475 }
476 }
477
478 if (hasAttachment)
479 {
480 //Bucket contains information about attachment.
481 //
482 //Byte offset and description of bucket data:
483 //0: 1 byte indicating if attachment is present
484 //1: 1 byte indicating the type of attachment
485 //2: 16 bytes - Group UUID
486 //18: 16 bytes - UUID of the attachment owner
487 //34: 16 bytes - UUID of the attachment
488 //50: variable - Name of the attachment
489 //??: NUL byte to terminate the attachment name
490 byte[] name = Encoding.UTF8.GetBytes(item.Name);
491 bucket = new byte[51 + name.Length];//3 bytes, 3 UUIDs, and name
492 bucket[0] = 1; //Has attachment flag
493 bucket[1] = (byte)item.InvType; //Type of Attachment
494 GroupID.ToBytes(bucket, 2);
495 ownerID.ToBytes(bucket, 18);
496 itemID.ToBytes(bucket, 34);
497 name.CopyTo(bucket, 50);
498 }
499 else
500 {
501 bucket = new byte[19];
502 bucket[0] = 0; //Has attachment flag
503 bucket[1] = 0; //Type of attachment
504 GroupID.ToBytes(bucket, 2);
505 bucket[18] = 0; //NUL terminate name of attachment
506 }
507
508 m_groupData.AddGroupNotice(GetRequestingAgentID(remoteClient), GroupID, NoticeID, im.fromAgentName, Subject, Message, bucket);
509 if (OnNewGroupNotice != null)
510 {
511 OnNewGroupNotice(GroupID, NoticeID);
512 }
513
514 if (m_debugEnabled)
515 {
516 foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetRequestingAgentID(remoteClient), GroupID))
517 {
518 if (m_debugEnabled)
519 {
520 UserAccount targetUser
521 = m_sceneList[0].UserAccountService.GetUserAccount(
522 remoteClient.Scene.RegionInfo.ScopeID, member.AgentID);
523
524 if (targetUser != null)
525 {
526 m_log.DebugFormat(
527 "[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})",
528 NoticeID, targetUser.FirstName + " " + targetUser.LastName, member.AcceptNotices);
529 }
530 else
531 {
532 m_log.DebugFormat(
533 "[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})",
534 NoticeID, member.AgentID, member.AcceptNotices);
535 }
536 }
537 }
538 }
539
540 GridInstantMessage msg
541 = CreateGroupNoticeIM(UUID.Zero, NoticeID, (byte)OpenMetaverse.InstantMessageDialog.GroupNotice);
542
543 if (m_groupsMessagingModule != null)
544 m_groupsMessagingModule.SendMessageToGroup(
545 msg, GroupID, remoteClient.AgentId, gmd => gmd.AcceptNotices);
546 }
547 }
548
549 if (im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryAccepted)
550 {
551 //Is bucket large enough to hold UUID of the attachment?
552 if (im.binaryBucket.Length < 16)
553 return;
554
555 UUID noticeID = new UUID(im.imSessionID);
556
557 if (m_debugEnabled)
558 m_log.DebugFormat("[GROUPS]: Requesting notice {0} for {1}", noticeID, remoteClient.AgentId);
559
560 GroupNoticeInfo notice = m_groupData.GetGroupNotice(GetRequestingAgentID(remoteClient), noticeID);
561 if (notice != null)
562 {
563 UUID giver = new UUID(notice.BinaryBucket, 18);
564 UUID attachmentUUID = new UUID(notice.BinaryBucket, 34);
565
566 if (m_debugEnabled)
567 m_log.DebugFormat("[Groups]: Giving inventory from {0} to {1}", giver, remoteClient.AgentId);
568
569 string message;
570 InventoryItemBase itemCopy = ((Scene)(remoteClient.Scene)).GiveInventoryItem(remoteClient.AgentId,
571 giver, attachmentUUID, out message);
572
573 if (itemCopy == null)
574 {
575 remoteClient.SendAgentAlertMessage(message, false);
576 return;
577 }
578
579 remoteClient.SendInventoryItemCreateUpdate(itemCopy, 0);
580 }
581 else
582 {
583 if (m_debugEnabled)
584 m_log.DebugFormat(
585 "[GROUPS]: Could not find notice {0} for {1} on GroupNoticeInventoryAccepted.",
586 noticeID, remoteClient.AgentId);
587 }
588 }
589
590 // Interop, received special 210 code for ejecting a group member
591 // this only works within the comms servers domain, and won't work hypergrid
592 // TODO:FIXME: Use a presence server of some kind to find out where the
593 // client actually is, and try contacting that region directly to notify them,
594 // or provide the notification via xmlrpc update queue
595 if ((im.dialog == 210))
596 {
597 // This is sent from the region that the ejectee was ejected from
598 // if it's being delivered here, then the ejectee is here
599 // so we need to send local updates to the agent.
600
601 UUID ejecteeID = new UUID(im.toAgentID);
602
603 im.dialog = (byte)InstantMessageDialog.MessageFromAgent;
604 OutgoingInstantMessage(im, ejecteeID);
605
606 IClientAPI ejectee = GetActiveClient(ejecteeID);
607 if (ejectee != null)
608 {
609 UUID groupID = new UUID(im.imSessionID);
610 ejectee.SendAgentDropGroup(groupID);
611 }
612 }
613 }
614
615 private void OnGridInstantMessage(GridInstantMessage msg)
616 {
617 if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
618
619 // Trigger the above event handler
620 OnInstantMessage(null, msg);
621
622 // If a message from a group arrives here, it may need to be forwarded to a local client
623 if (msg.fromGroup == true)
624 {
625 switch (msg.dialog)
626 {
627 case (byte)InstantMessageDialog.GroupInvitation:
628 case (byte)InstantMessageDialog.GroupNotice:
629 UUID toAgentID = new UUID(msg.toAgentID);
630 IClientAPI localClient = GetActiveClient(toAgentID);
631 if (localClient != null)
632 {
633 localClient.SendInstantMessage(msg);
634 }
635 break;
636 }
637 }
638 }
639
640 #endregion
641
642 #region IGroupsModule Members
643
644 public event NewGroupNotice OnNewGroupNotice;
645
646 public GroupRecord GetGroupRecord(UUID GroupID)
647 {
648 return m_groupData.GetGroupRecord(UUID.Zero, GroupID, null);
649 }
650
651 public GroupRecord GetGroupRecord(string name)
652 {
653 return m_groupData.GetGroupRecord(UUID.Zero, UUID.Zero, name);
654 }
655
656 public void ActivateGroup(IClientAPI remoteClient, UUID groupID)
657 {
658 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
659
660 m_groupData.SetAgentActiveGroup(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID);
661
662 // Changing active group changes title, active powers, all kinds of things
663 // anyone who is in any region that can see this client, should probably be
664 // updated with new group info. At a minimum, they should get ScenePresence
665 // updated with new title.
666 UpdateAllClientsWithGroupInfo(GetRequestingAgentID(remoteClient));
667 }
668
669 /// <summary>
670 /// Get the Role Titles for an Agent, for a specific group
671 /// </summary>
672 public List<GroupTitlesData> GroupTitlesRequest(IClientAPI remoteClient, UUID groupID)
673 {
674 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
675
676
677 List<GroupRolesData> agentRoles = m_groupData.GetAgentGroupRoles(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID);
678 GroupMembershipData agentMembership = m_groupData.GetAgentGroupMembership(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID);
679
680 List<GroupTitlesData> titles = new List<GroupTitlesData>();
681 foreach (GroupRolesData role in agentRoles)
682 {
683 GroupTitlesData title = new GroupTitlesData();
684 title.Name = role.Name;
685 if (agentMembership != null)
686 {
687 title.Selected = agentMembership.ActiveRole == role.RoleID;
688 }
689 title.UUID = role.RoleID;
690
691 titles.Add(title);
692 }
693
694 return titles;
695 }
696
697 public List<GroupMembersData> GroupMembersRequest(IClientAPI remoteClient, UUID groupID)
698 {
699 if (m_debugEnabled)
700 m_log.DebugFormat(
701 "[GROUPS]: GroupMembersRequest called for {0} from client {1}", groupID, remoteClient.Name);
702
703 List<GroupMembersData> data = m_groupData.GetGroupMembers(GetRequestingAgentID(remoteClient), groupID);
704
705 if (m_debugEnabled)
706 {
707 foreach (GroupMembersData member in data)
708 {
709 m_log.DebugFormat("[GROUPS]: Member({0}) - IsOwner({1})", member.AgentID, member.IsOwner);
710 }
711 }
712
713 return data;
714
715 }
716
717 public List<GroupRolesData> GroupRoleDataRequest(IClientAPI remoteClient, UUID groupID)
718 {
719 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
720
721 List<GroupRolesData> data = m_groupData.GetGroupRoles(GetRequestingAgentID(remoteClient), groupID);
722
723 return data;
724 }
725
726 public List<GroupRoleMembersData> GroupRoleMembersRequest(IClientAPI remoteClient, UUID groupID)
727 {
728 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
729
730 List<GroupRoleMembersData> data = m_groupData.GetGroupRoleMembers(GetRequestingAgentID(remoteClient), groupID);
731
732 if (m_debugEnabled)
733 {
734 foreach (GroupRoleMembersData member in data)
735 {
736 m_log.DebugFormat("[GROUPS]: Member({0}) - Role({1})", member.MemberID, member.RoleID);
737 }
738 }
739 return data;
740 }
741
742 public GroupProfileData GroupProfileRequest(IClientAPI remoteClient, UUID groupID)
743 {
744 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
745
746 GroupProfileData profile = new GroupProfileData();
747
748
749 GroupRecord groupInfo = m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), groupID, null);
750 if (groupInfo != null)
751 {
752 profile.AllowPublish = groupInfo.AllowPublish;
753 profile.Charter = groupInfo.Charter;
754 profile.FounderID = groupInfo.FounderID;
755 profile.GroupID = groupID;
756 profile.GroupMembershipCount = m_groupData.GetGroupMembers(GetRequestingAgentID(remoteClient), groupID).Count;
757 profile.GroupRolesCount = m_groupData.GetGroupRoles(GetRequestingAgentID(remoteClient), groupID).Count;
758 profile.InsigniaID = groupInfo.GroupPicture;
759 profile.MaturePublish = groupInfo.MaturePublish;
760 profile.MembershipFee = groupInfo.MembershipFee;
761 profile.Money = 0; // TODO: Get this from the currency server?
762 profile.Name = groupInfo.GroupName;
763 profile.OpenEnrollment = groupInfo.OpenEnrollment;
764 profile.OwnerRole = groupInfo.OwnerRoleID;
765 profile.ShowInList = groupInfo.ShowInList;
766 }
767
768 GroupMembershipData memberInfo = m_groupData.GetAgentGroupMembership(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID);
769 if (memberInfo != null)
770 {
771 profile.MemberTitle = memberInfo.GroupTitle;
772 profile.PowersMask = memberInfo.GroupPowers;
773 }
774
775 return profile;
776 }
777
778 public GroupMembershipData[] GetMembershipData(UUID agentID)
779 {
780 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
781
782 return m_groupData.GetAgentGroupMemberships(UUID.Zero, agentID).ToArray();
783 }
784
785 public GroupMembershipData GetMembershipData(UUID groupID, UUID agentID)
786 {
787 if (m_debugEnabled)
788 m_log.DebugFormat(
789 "[GROUPS]: {0} called with groupID={1}, agentID={2}",
790 System.Reflection.MethodBase.GetCurrentMethod().Name, groupID, agentID);
791
792 return m_groupData.GetAgentGroupMembership(UUID.Zero, agentID, groupID);
793 }
794
795 public void UpdateGroupInfo(IClientAPI remoteClient, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish)
796 {
797 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
798
799 // Note: Permissions checking for modification rights is handled by the Groups Server/Service
800 m_groupData.UpdateGroup(GetRequestingAgentID(remoteClient), groupID, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish);
801 }
802
803 public void SetGroupAcceptNotices(IClientAPI remoteClient, UUID groupID, bool acceptNotices, bool listInProfile)
804 {
805 // Note: Permissions checking for modification rights is handled by the Groups Server/Service
806 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
807
808 m_groupData.SetAgentGroupInfo(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID, acceptNotices, listInProfile);
809 }
810
811 public UUID CreateGroup(IClientAPI remoteClient, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish)
812 {
813 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
814
815 if (m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), UUID.Zero, name) != null)
816 {
817 remoteClient.SendCreateGroupReply(UUID.Zero, false, "A group with the same name already exists.");
818 return UUID.Zero;
819 }
820
821 // check user level
822 ScenePresence avatar = null;
823 Scene scene = (Scene)remoteClient.Scene;
824 scene.TryGetScenePresence(remoteClient.AgentId, out avatar);
825
826 if (avatar != null)
827 {
828 if (avatar.UserLevel < m_levelGroupCreate)
829 {
830 remoteClient.SendCreateGroupReply(UUID.Zero, false, "You have got insufficient permissions to create a group.");
831 return UUID.Zero;
832 }
833 }
834
835 // check funds
836 // is there is a money module present ?
837 IMoneyModule money = scene.RequestModuleInterface<IMoneyModule>();
838 if (money != null)
839 {
840 // do the transaction, that is if the agent has got sufficient funds
841 if (!money.AmountCovered(remoteClient.AgentId, money.GroupCreationCharge)) {
842 remoteClient.SendCreateGroupReply(UUID.Zero, false, "You have got insufficient funds to create a group.");
843 return UUID.Zero;
844 }
845 money.ApplyCharge(GetRequestingAgentID(remoteClient), money.GroupCreationCharge, MoneyTransactionType.GroupCreate);
846 }
847 UUID groupID = m_groupData.CreateGroup(GetRequestingAgentID(remoteClient), name, charter, showInList, insigniaID, membershipFee, openEnrollment, allowPublish, maturePublish, GetRequestingAgentID(remoteClient));
848
849 remoteClient.SendCreateGroupReply(groupID, true, "Group created successfullly");
850
851 // Update the founder with new group information.
852 SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient));
853
854 return groupID;
855 }
856
857 public GroupNoticeData[] GroupNoticesListRequest(IClientAPI remoteClient, UUID groupID)
858 {
859 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
860
861 // ToDo: check if agent is a member of group and is allowed to see notices?
862
863 return m_groupData.GetGroupNotices(GetRequestingAgentID(remoteClient), groupID).ToArray();
864 }
865
866 /// <summary>
867 /// Get the title of the agent's current role.
868 /// </summary>
869 public string GetGroupTitle(UUID avatarID)
870 {
871 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
872
873 GroupMembershipData membership = m_groupData.GetAgentActiveMembership(UUID.Zero, avatarID);
874 if (membership != null)
875 {
876 return membership.GroupTitle;
877 }
878 return string.Empty;
879 }
880
881 /// <summary>
882 /// Change the current Active Group Role for Agent
883 /// </summary>
884 public void GroupTitleUpdate(IClientAPI remoteClient, UUID groupID, UUID titleRoleID)
885 {
886 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
887
888 m_groupData.SetAgentActiveGroupRole(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID, titleRoleID);
889
890 // TODO: Not sure what all is needed here, but if the active group role change is for the group
891 // the client currently has set active, then we need to do a scene presence update too
892 // if (m_groupData.GetAgentActiveMembership(GetRequestingAgentID(remoteClient)).GroupID == GroupID)
893
894 UpdateAllClientsWithGroupInfo(GetRequestingAgentID(remoteClient));
895 }
896
897
898 public void GroupRoleUpdate(IClientAPI remoteClient, UUID groupID, UUID roleID, string name, string description, string title, ulong powers, byte updateType)
899 {
900 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
901
902 // Security Checks are handled in the Groups Service.
903
904 switch ((OpenMetaverse.GroupRoleUpdate)updateType)
905 {
906 case OpenMetaverse.GroupRoleUpdate.Create:
907 m_groupData.AddGroupRole(GetRequestingAgentID(remoteClient), groupID, UUID.Random(), name, description, title, powers);
908 break;
909
910 case OpenMetaverse.GroupRoleUpdate.Delete:
911 m_groupData.RemoveGroupRole(GetRequestingAgentID(remoteClient), groupID, roleID);
912 break;
913
914 case OpenMetaverse.GroupRoleUpdate.UpdateAll:
915 case OpenMetaverse.GroupRoleUpdate.UpdateData:
916 case OpenMetaverse.GroupRoleUpdate.UpdatePowers:
917 if (m_debugEnabled)
918 {
919 GroupPowers gp = (GroupPowers)powers;
920 m_log.DebugFormat("[GROUPS]: Role ({0}) updated with Powers ({1}) ({2})", name, powers.ToString(), gp.ToString());
921 }
922 m_groupData.UpdateGroupRole(GetRequestingAgentID(remoteClient), groupID, roleID, name, description, title, powers);
923 break;
924
925 case OpenMetaverse.GroupRoleUpdate.NoUpdate:
926 default:
927 // No Op
928 break;
929
930 }
931
932 // TODO: This update really should send out updates for everyone in the role that just got changed.
933 SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient));
934 }
935
936 public void GroupRoleChanges(IClientAPI remoteClient, UUID groupID, UUID roleID, UUID memberID, uint changes)
937 {
938 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
939 // Todo: Security check
940
941 switch (changes)
942 {
943 case 0:
944 // Add
945 m_groupData.AddAgentToGroupRole(GetRequestingAgentID(remoteClient), memberID, groupID, roleID);
946
947 break;
948 case 1:
949 // Remove
950 m_groupData.RemoveAgentFromGroupRole(GetRequestingAgentID(remoteClient), memberID, groupID, roleID);
951
952 break;
953 default:
954 m_log.ErrorFormat("[GROUPS]: {0} does not understand changes == {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, changes);
955 break;
956 }
957
958 // TODO: This update really should send out updates for everyone in the role that just got changed.
959 SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient));
960 }
961
962 public void GroupNoticeRequest(IClientAPI remoteClient, UUID groupNoticeID)
963 {
964 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
965
966 GroupNoticeInfo data = m_groupData.GetGroupNotice(GetRequestingAgentID(remoteClient), groupNoticeID);
967
968 if (data != null)
969 {
970 GridInstantMessage msg = CreateGroupNoticeIM(remoteClient.AgentId, groupNoticeID, (byte)InstantMessageDialog.GroupNoticeRequested);
971
972 OutgoingInstantMessage(msg, GetRequestingAgentID(remoteClient));
973 }
974 }
975
976 public GridInstantMessage CreateGroupNoticeIM(UUID agentID, UUID groupNoticeID, byte dialog)
977 {
978 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
979
980 GridInstantMessage msg = new GridInstantMessage();
981 byte[] bucket;
982
983 msg.imSessionID = groupNoticeID.Guid;
984 msg.toAgentID = agentID.Guid;
985 msg.dialog = dialog;
986 msg.fromGroup = true;
987 msg.offline = (byte)0;
988 msg.ParentEstateID = 0;
989 msg.Position = Vector3.Zero;
990 msg.RegionID = UUID.Zero.Guid;
991
992 GroupNoticeInfo info = m_groupData.GetGroupNotice(agentID, groupNoticeID);
993 if (info != null)
994 {
995 msg.fromAgentID = info.GroupID.Guid;
996 msg.timestamp = info.noticeData.Timestamp;
997 msg.fromAgentName = info.noticeData.FromName;
998 msg.message = info.noticeData.Subject + "|" + info.Message;
999
1000 if (info.BinaryBucket[0] > 0)
1001 {
1002 //32 is due to not needing space for two of the UUIDs.
1003 //(Don't need UUID of attachment or its owner in IM)
1004 //50 offset gets us to start of attachment name.
1005 //We are skipping the attachment flag, type, and
1006 //the three UUID fields at the start of the bucket.
1007 bucket = new byte[info.BinaryBucket.Length-32];
1008 bucket[0] = 1; //Has attachment
1009 bucket[1] = info.BinaryBucket[1];
1010 Array.Copy(info.BinaryBucket, 50,
1011 bucket, 18, info.BinaryBucket.Length-50);
1012 }
1013 else
1014 {
1015 bucket = new byte[19];
1016 bucket[0] = 0; //No attachment
1017 bucket[1] = 0; //Attachment type
1018 bucket[18] = 0; //NUL terminate name
1019 }
1020
1021 info.GroupID.ToBytes(bucket, 2);
1022 msg.binaryBucket = bucket;
1023 }
1024 else
1025 {
1026 if (m_debugEnabled)
1027 m_log.DebugFormat("[GROUPS]: Group Notice {0} not found, composing empty message.", groupNoticeID);
1028
1029 msg.fromAgentID = UUID.Zero.Guid;
1030 msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
1031 msg.fromAgentName = string.Empty;
1032 msg.message = string.Empty;
1033 msg.binaryBucket = new byte[0];
1034 }
1035
1036 return msg;
1037 }
1038
1039 public void SendAgentGroupDataUpdate(IClientAPI remoteClient)
1040 {
1041 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1042
1043 // Send agent information about his groups
1044 SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient));
1045 }
1046
1047 public void JoinGroupRequest(IClientAPI remoteClient, UUID groupID)
1048 {
1049 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1050
1051 // Should check to see if OpenEnrollment, or if there's an outstanding invitation
1052 m_groupData.AddAgentToGroup(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID, UUID.Zero);
1053
1054 remoteClient.SendJoinGroupReply(groupID, true);
1055
1056 // Should this send updates to everyone in the group?
1057 SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient));
1058 }
1059
1060 public void LeaveGroupRequest(IClientAPI remoteClient, UUID groupID)
1061 {
1062 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1063
1064 m_groupData.RemoveAgentFromGroup(GetRequestingAgentID(remoteClient), GetRequestingAgentID(remoteClient), groupID);
1065
1066 remoteClient.SendLeaveGroupReply(groupID, true);
1067
1068 remoteClient.SendAgentDropGroup(groupID);
1069
1070 // SL sends out notifcations to the group messaging session that the person has left
1071 // Should this also update everyone who is in the group?
1072 SendAgentGroupDataUpdate(remoteClient, GetRequestingAgentID(remoteClient));
1073 }
1074
1075 public void EjectGroupMemberRequest(IClientAPI remoteClient, UUID groupID, UUID ejecteeID)
1076 {
1077 EjectGroupMember(remoteClient, GetRequestingAgentID(remoteClient), groupID, ejecteeID);
1078 }
1079
1080 public void EjectGroupMember(IClientAPI remoteClient, UUID agentID, UUID groupID, UUID ejecteeID)
1081 {
1082 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1083
1084 // Todo: Security check?
1085 m_groupData.RemoveAgentFromGroup(agentID, ejecteeID, groupID);
1086
1087 string agentName;
1088 RegionInfo regionInfo;
1089
1090 // remoteClient provided or just agentID?
1091 if (remoteClient != null)
1092 {
1093 agentName = remoteClient.Name;
1094 regionInfo = remoteClient.Scene.RegionInfo;
1095 remoteClient.SendEjectGroupMemberReply(agentID, groupID, true);
1096 }
1097 else
1098 {
1099 IClientAPI client = GetActiveClient(agentID);
1100
1101 if (client != null)
1102 {
1103 agentName = client.Name;
1104 regionInfo = client.Scene.RegionInfo;
1105 client.SendEjectGroupMemberReply(agentID, groupID, true);
1106 }
1107 else
1108 {
1109 regionInfo = m_sceneList[0].RegionInfo;
1110 UserAccount acc = m_sceneList[0].UserAccountService.GetUserAccount(regionInfo.ScopeID, agentID);
1111
1112 if (acc != null)
1113 {
1114 agentName = acc.FirstName + " " + acc.LastName;
1115 }
1116 else
1117 {
1118 agentName = "Unknown member";
1119 }
1120 }
1121 }
1122
1123 GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID, groupID, null);
1124
1125 UserAccount account = m_sceneList[0].UserAccountService.GetUserAccount(regionInfo.ScopeID, ejecteeID);
1126 if ((groupInfo == null) || (account == null))
1127 {
1128 return;
1129 }
1130
1131 // Send Message to Ejectee
1132 GridInstantMessage msg = new GridInstantMessage();
1133
1134 msg.imSessionID = UUID.Zero.Guid;
1135 msg.fromAgentID = agentID.Guid;
1136 // msg.fromAgentID = info.GroupID;
1137 msg.toAgentID = ejecteeID.Guid;
1138 //msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
1139 msg.timestamp = 0;
1140 msg.fromAgentName = agentName;
1141 msg.message = string.Format("You have been ejected from '{1}' by {0}.", agentName, groupInfo.GroupName);
1142 msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageFromAgent;
1143 msg.fromGroup = false;
1144 msg.offline = (byte)0;
1145 msg.ParentEstateID = 0;
1146 msg.Position = Vector3.Zero;
1147 msg.RegionID = regionInfo.RegionID.Guid;
1148 msg.binaryBucket = new byte[0];
1149 OutgoingInstantMessage(msg, ejecteeID);
1150
1151 // Message to ejector
1152 // Interop, received special 210 code for ejecting a group member
1153 // this only works within the comms servers domain, and won't work hypergrid
1154 // TODO:FIXME: Use a presence server of some kind to find out where the
1155 // client actually is, and try contacting that region directly to notify them,
1156 // or provide the notification via xmlrpc update queue
1157
1158 msg = new GridInstantMessage();
1159 msg.imSessionID = UUID.Zero.Guid;
1160 msg.fromAgentID = agentID.Guid;
1161 msg.toAgentID = agentID.Guid;
1162 msg.timestamp = 0;
1163 msg.fromAgentName = agentName;
1164 if (account != null)
1165 {
1166 msg.message = string.Format("{2} has been ejected from '{1}' by {0}.", agentName, groupInfo.GroupName, account.FirstName + " " + account.LastName);
1167 }
1168 else
1169 {
1170 msg.message = string.Format("{2} has been ejected from '{1}' by {0}.", agentName, groupInfo.GroupName, "Unknown member");
1171 }
1172 msg.dialog = (byte)210; //interop
1173 msg.fromGroup = false;
1174 msg.offline = (byte)0;
1175 msg.ParentEstateID = 0;
1176 msg.Position = Vector3.Zero;
1177 msg.RegionID = regionInfo.RegionID.Guid;
1178 msg.binaryBucket = new byte[0];
1179 OutgoingInstantMessage(msg, agentID);
1180
1181
1182 // SL sends out messages to everyone in the group
1183 // Who all should receive updates and what should they be updated with?
1184 UpdateAllClientsWithGroupInfo(ejecteeID);
1185 }
1186
1187 public void InviteGroupRequest(IClientAPI remoteClient, UUID groupID, UUID invitedAgentID, UUID roleID)
1188 {
1189 InviteGroup(remoteClient, GetRequestingAgentID(remoteClient), groupID, invitedAgentID, roleID);
1190 }
1191
1192 public void InviteGroup(IClientAPI remoteClient, UUID agentID, UUID groupID, UUID invitedAgentID, UUID roleID)
1193 {
1194 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1195
1196 string agentName;
1197 RegionInfo regionInfo;
1198
1199 // remoteClient provided or just agentID?
1200 if (remoteClient != null)
1201 {
1202 agentName = remoteClient.Name;
1203 regionInfo = remoteClient.Scene.RegionInfo;
1204 }
1205 else
1206 {
1207 IClientAPI client = GetActiveClient(agentID);
1208
1209 if (client != null)
1210 {
1211 agentName = client.Name;
1212 regionInfo = client.Scene.RegionInfo;
1213 }
1214 else
1215 {
1216 regionInfo = m_sceneList[0].RegionInfo;
1217 UserAccount account = m_sceneList[0].UserAccountService.GetUserAccount(regionInfo.ScopeID, agentID);
1218
1219 if (account != null)
1220 {
1221 agentName = account.FirstName + " " + account.LastName;
1222 }
1223 else
1224 {
1225 agentName = "Unknown member";
1226 }
1227 }
1228 }
1229
1230 // Todo: Security check, probably also want to send some kind of notification
1231 UUID InviteID = UUID.Random();
1232
1233 m_groupData.AddAgentToGroupInvite(agentID, InviteID, groupID, roleID, invitedAgentID);
1234
1235 // Check to see if the invite went through, if it did not then it's possible
1236 // the remoteClient did not validate or did not have permission to invite.
1237 GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(agentID, InviteID);
1238
1239 if (inviteInfo != null)
1240 {
1241 if (m_msgTransferModule != null)
1242 {
1243 Guid inviteUUID = InviteID.Guid;
1244
1245 GridInstantMessage msg = new GridInstantMessage();
1246
1247 msg.imSessionID = inviteUUID;
1248
1249 // msg.fromAgentID = agentID.Guid;
1250 msg.fromAgentID = groupID.Guid;
1251 msg.toAgentID = invitedAgentID.Guid;
1252 //msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
1253 msg.timestamp = 0;
1254 msg.fromAgentName = agentName;
1255 msg.message = string.Format("{0} has invited you to join a group. There is no cost to join this group.", agentName);
1256 msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.GroupInvitation;
1257 msg.fromGroup = true;
1258 msg.offline = (byte)0;
1259 msg.ParentEstateID = 0;
1260 msg.Position = Vector3.Zero;
1261 msg.RegionID = regionInfo.RegionID.Guid;
1262 msg.binaryBucket = new byte[20];
1263
1264 OutgoingInstantMessage(msg, invitedAgentID);
1265 }
1266 }
1267 }
1268
1269 public List<DirGroupsReplyData> FindGroups(IClientAPI remoteClient, string query)
1270 {
1271 return m_groupData.FindGroups(GetRequestingAgentID(remoteClient), query);
1272 }
1273
1274
1275 #endregion
1276
1277 #region Client/Update Tools
1278
1279 /// <summary>
1280 /// Try to find an active IClientAPI reference for agentID giving preference to root connections
1281 /// </summary>
1282 private IClientAPI GetActiveClient(UUID agentID)
1283 {
1284 IClientAPI child = null;
1285
1286 // Try root avatar first
1287 foreach (Scene scene in m_sceneList)
1288 {
1289 ScenePresence sp = scene.GetScenePresence(agentID);
1290 if (sp != null)
1291 {
1292 if (!sp.IsChildAgent)
1293 {
1294 return sp.ControllingClient;
1295 }
1296 else
1297 {
1298 child = sp.ControllingClient;
1299 }
1300 }
1301 }
1302
1303 // If we didn't find a root, then just return whichever child we found, or null if none
1304 return child;
1305 }
1306
1307 /// <summary>
1308 /// Send 'remoteClient' the group membership 'data' for agent 'dataForAgentID'.
1309 /// </summary>
1310 private void SendGroupMembershipInfoViaCaps(IClientAPI remoteClient, UUID dataForAgentID, GroupMembershipData[] data)
1311 {
1312 if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1313
1314 OSDArray AgentData = new OSDArray(1);
1315 OSDMap AgentDataMap = new OSDMap(1);
1316 AgentDataMap.Add("AgentID", OSD.FromUUID(dataForAgentID));
1317 AgentData.Add(AgentDataMap);
1318
1319 OSDArray GroupData = new OSDArray(data.Length);
1320 OSDArray NewGroupData = new OSDArray(data.Length);
1321
1322 foreach (GroupMembershipData membership in data)
1323 {
1324 if (GetRequestingAgentID(remoteClient) != dataForAgentID)
1325 {
1326 if (!membership.ListInProfile)
1327 {
1328 // If we're sending group info to remoteclient about another agent,
1329 // filter out groups the other agent doesn't want to share.
1330 continue;
1331 }
1332 }
1333
1334 OSDMap GroupDataMap = new OSDMap(6);
1335 OSDMap NewGroupDataMap = new OSDMap(1);
1336
1337 GroupDataMap.Add("GroupID", OSD.FromUUID(membership.GroupID));
1338 GroupDataMap.Add("GroupPowers", OSD.FromULong(membership.GroupPowers));
1339 GroupDataMap.Add("AcceptNotices", OSD.FromBoolean(membership.AcceptNotices));
1340 GroupDataMap.Add("GroupInsigniaID", OSD.FromUUID(membership.GroupPicture));
1341 GroupDataMap.Add("Contribution", OSD.FromInteger(membership.Contribution));
1342 GroupDataMap.Add("GroupName", OSD.FromString(membership.GroupName));
1343 NewGroupDataMap.Add("ListInProfile", OSD.FromBoolean(membership.ListInProfile));
1344
1345 GroupData.Add(GroupDataMap);
1346 NewGroupData.Add(NewGroupDataMap);
1347 }
1348
1349 OSDMap llDataStruct = new OSDMap(3);
1350 llDataStruct.Add("AgentData", AgentData);
1351 llDataStruct.Add("GroupData", GroupData);
1352 llDataStruct.Add("NewGroupData", NewGroupData);
1353
1354 if (m_debugEnabled)
1355 {
1356 m_log.InfoFormat("[GROUPS]: {0}", OSDParser.SerializeJsonString(llDataStruct));
1357 }
1358
1359 IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
1360
1361 if (queue != null)
1362 {
1363 queue.Enqueue(queue.BuildEvent("AgentGroupDataUpdate", llDataStruct), GetRequestingAgentID(remoteClient));
1364 }
1365
1366 }
1367
1368 private void SendScenePresenceUpdate(UUID AgentID, string Title)
1369 {
1370 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Updating scene title for {0} with title: {1}", AgentID, Title);
1371
1372 ScenePresence presence = null;
1373
1374 foreach (Scene scene in m_sceneList)
1375 {
1376 presence = scene.GetScenePresence(AgentID);
1377 if (presence != null)
1378 {
1379 if (presence.Grouptitle != Title)
1380 {
1381 presence.Grouptitle = Title;
1382
1383 if (! presence.IsChildAgent)
1384 presence.SendAvatarDataToAllClients();
1385 }
1386 }
1387 }
1388 }
1389
1390 /// <summary>
1391 /// Send updates to all clients who might be interested in groups data for dataForClientID
1392 /// </summary>
1393 private void UpdateAllClientsWithGroupInfo(UUID dataForClientID)
1394 {
1395 if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1396
1397 // TODO: Probably isn't nessesary to update every client in every scene.
1398 // Need to examine client updates and do only what's nessesary.
1399 lock (m_sceneList)
1400 {
1401 foreach (Scene scene in m_sceneList)
1402 {
1403 scene.ForEachClient(delegate(IClientAPI client) { SendAgentGroupDataUpdate(client, dataForClientID); });
1404 }
1405 }
1406 }
1407
1408 /// <summary>
1409 /// Update remoteClient with group information about dataForAgentID
1410 /// </summary>
1411 private void SendAgentGroupDataUpdate(IClientAPI remoteClient, UUID dataForAgentID)
1412 {
1413 if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called for {1}", System.Reflection.MethodBase.GetCurrentMethod().Name, remoteClient.Name);
1414
1415 // TODO: All the client update functions need to be reexamined because most do too much and send too much stuff
1416
1417 OnAgentDataUpdateRequest(remoteClient, dataForAgentID, UUID.Zero);
1418
1419 // Need to send a group membership update to the client
1420 // UDP version doesn't seem to behave nicely. But we're going to send it out here
1421 // with an empty group membership to hopefully remove groups being displayed due
1422 // to the core Groups Stub
1423 remoteClient.SendGroupMembership(new GroupMembershipData[0]);
1424
1425 GroupMembershipData[] membershipArray = GetProfileListedGroupMemberships(remoteClient, dataForAgentID);
1426 SendGroupMembershipInfoViaCaps(remoteClient, dataForAgentID, membershipArray);
1427 remoteClient.SendAvatarGroupsReply(dataForAgentID, membershipArray);
1428
1429 if (remoteClient.AgentId == dataForAgentID)
1430 remoteClient.RefreshGroupMembership();
1431 }
1432
1433 /// <summary>
1434 /// Get a list of groups memberships for the agent that are marked "ListInProfile"
1435 /// (unless that agent has a godLike aspect, in which case get all groups)
1436 /// </summary>
1437 /// <param name="dataForAgentID"></param>
1438 /// <returns></returns>
1439 private GroupMembershipData[] GetProfileListedGroupMemberships(IClientAPI requestingClient, UUID dataForAgentID)
1440 {
1441 List<GroupMembershipData> membershipData = m_groupData.GetAgentGroupMemberships(requestingClient.AgentId, dataForAgentID);
1442 GroupMembershipData[] membershipArray;
1443
1444 // cScene and property accessor 'isGod' are in support of the opertions to bypass 'hidden' group attributes for
1445 // those with a GodLike aspect.
1446 Scene cScene = (Scene)requestingClient.Scene;
1447 bool isGod = cScene.Permissions.IsGod(requestingClient.AgentId);
1448
1449 if (isGod)
1450 {
1451 membershipArray = membershipData.ToArray();
1452 }
1453 else
1454 {
1455 if (requestingClient.AgentId != dataForAgentID)
1456 {
1457 Predicate<GroupMembershipData> showInProfile = delegate(GroupMembershipData membership)
1458 {
1459 return membership.ListInProfile;
1460 };
1461
1462 membershipArray = membershipData.FindAll(showInProfile).ToArray();
1463 }
1464 else
1465 {
1466 membershipArray = membershipData.ToArray();
1467 }
1468 }
1469
1470 if (m_debugEnabled)
1471 {
1472 m_log.InfoFormat("[GROUPS]: Get group membership information for {0} requested by {1}", dataForAgentID, requestingClient.AgentId);
1473 foreach (GroupMembershipData membership in membershipArray)
1474 {
1475 m_log.InfoFormat("[GROUPS]: {0} :: {1} - {2} - {3}", dataForAgentID, membership.GroupName, membership.GroupTitle, membership.GroupPowers);
1476 }
1477 }
1478
1479 return membershipArray;
1480 }
1481
1482
1483 private void SendAgentDataUpdate(IClientAPI remoteClient, UUID dataForAgentID, UUID activeGroupID, string activeGroupName, ulong activeGroupPowers, string activeGroupTitle)
1484 {
1485 if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1486
1487 // TODO: All the client update functions need to be reexamined because most do too much and send too much stuff
1488 UserAccount account = m_sceneList[0].UserAccountService.GetUserAccount(remoteClient.Scene.RegionInfo.ScopeID, dataForAgentID);
1489 string firstname, lastname;
1490 if (account != null)
1491 {
1492 firstname = account.FirstName;
1493 lastname = account.LastName;
1494 }
1495 else
1496 {
1497 firstname = "Unknown";
1498 lastname = "Unknown";
1499 }
1500
1501 remoteClient.SendAgentDataUpdate(dataForAgentID, activeGroupID, firstname,
1502 lastname, activeGroupPowers, activeGroupName,
1503 activeGroupTitle);
1504 }
1505
1506 #endregion
1507
1508 #region IM Backed Processes
1509
1510 private void OutgoingInstantMessage(GridInstantMessage msg, UUID msgTo)
1511 {
1512 if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1513
1514 IClientAPI localClient = GetActiveClient(msgTo);
1515 if (localClient != null)
1516 {
1517 if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: MsgTo ({0}) is local, delivering directly", localClient.Name);
1518 localClient.SendInstantMessage(msg);
1519 }
1520 else if (m_msgTransferModule != null)
1521 {
1522 if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: MsgTo ({0}) is not local, delivering via TransferModule", msgTo);
1523 m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Message Sent: {0}", success?"Succeeded":"Failed"); });
1524 }
1525 }
1526
1527 public void NotifyChange(UUID groupID)
1528 {
1529 // Notify all group members of a chnge in group roles and/or
1530 // permissions
1531 //
1532 }
1533
1534 #endregion
1535
1536 private UUID GetRequestingAgentID(IClientAPI client)
1537 {
1538 UUID requestingAgentID = UUID.Zero;
1539 if (client != null)
1540 {
1541 requestingAgentID = client.AgentId;
1542 }
1543 return requestingAgentID;
1544 }
1545 }
1546
1547 public class GroupNoticeInfo
1548 {
1549 public GroupNoticeData noticeData = new GroupNoticeData();
1550 public UUID GroupID = UUID.Zero;
1551 public string Message = string.Empty;
1552 public byte[] BinaryBucket = new byte[0];
1553 }
1554}