aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/OptionalModules/Avatar
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/OptionalModules/Avatar')
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Animations/AnimationsCommandModule.cs200
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs482
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs193
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Attachments/TempAttachmentsModule.cs190
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs635
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Chat/IRCBridgeModule.cs213
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs913
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Chat/RegionState.cs452
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs627
-rwxr-xr-xOpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeServer.py130
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Friends/FriendsCommandsModule.cs200
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/SitStand/SitStandCommandsModule.cs220
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs899
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs1328
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsMessagingModule.cs704
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsModule.cs1554
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/IGroupsServicesConnector.cs121
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/SimianGroupsServicesConnectorModule.cs1441
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs268
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/XmlRpcGroupsServicesConnectorModule.cs1174
20 files changed, 11944 insertions, 0 deletions
diff --git a/OpenSim/Region/OptionalModules/Avatar/Animations/AnimationsCommandModule.cs b/OpenSim/Region/OptionalModules/Avatar/Animations/AnimationsCommandModule.cs
new file mode 100644
index 0000000..84211a9
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Animations/AnimationsCommandModule.cs
@@ -0,0 +1,200 @@
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.Linq;
31using System.Reflection;
32using System.Text;
33using log4net;
34using Mono.Addins;
35using Nini.Config;
36using OpenMetaverse;
37using OpenSim.Framework;
38using OpenSim.Framework.Console;
39using OpenSim.Framework.Monitoring;
40using OpenSim.Region.ClientStack.LindenUDP;
41using OpenSim.Region.Framework.Interfaces;
42using OpenSim.Region.Framework.Scenes;
43using OpenSim.Region.Framework.Scenes.Animation;
44using OpenSim.Services.Interfaces;
45
46namespace OpenSim.Region.OptionalModules.Avatar.Animations
47{
48 /// <summary>
49 /// A module that just holds commands for inspecting avatar animations.
50 /// </summary>
51 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AnimationsCommandModule")]
52 public class AnimationsCommandModule : ISharedRegionModule
53 {
54// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
55
56 private List<Scene> m_scenes = new List<Scene>();
57
58 public string Name { get { return "Animations Command Module"; } }
59
60 public Type ReplaceableInterface { get { return null; } }
61
62 public void Initialise(IConfigSource source)
63 {
64// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: INITIALIZED MODULE");
65 }
66
67 public void PostInitialise()
68 {
69// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: POST INITIALIZED MODULE");
70 }
71
72 public void Close()
73 {
74// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: CLOSED MODULE");
75 }
76
77 public void AddRegion(Scene scene)
78 {
79// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: REGION {0} ADDED", scene.RegionInfo.RegionName);
80 }
81
82 public void RemoveRegion(Scene scene)
83 {
84// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName);
85
86 lock (m_scenes)
87 m_scenes.Remove(scene);
88 }
89
90 public void RegionLoaded(Scene scene)
91 {
92// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName);
93
94 lock (m_scenes)
95 m_scenes.Add(scene);
96
97 scene.AddCommand(
98 "Users", this, "show animations",
99 "show animations [<first-name> <last-name>]",
100 "Show animation information for avatars in this simulator.",
101 "If no name is supplied then information for all avatars is shown.\n"
102 + "Please note that for inventory animations, the animation name is the name under which the animation was originally uploaded\n"
103 + ", which is not necessarily the current inventory name.",
104 HandleShowAnimationsCommand);
105 }
106
107 protected void HandleShowAnimationsCommand(string module, string[] cmd)
108 {
109 if (cmd.Length != 2 && cmd.Length < 4)
110 {
111 MainConsole.Instance.OutputFormat("Usage: show animations [<first-name> <last-name>]");
112 return;
113 }
114
115 bool targetNameSupplied = false;
116 string optionalTargetFirstName = null;
117 string optionalTargetLastName = null;
118
119 if (cmd.Length >= 4)
120 {
121 targetNameSupplied = true;
122 optionalTargetFirstName = cmd[2];
123 optionalTargetLastName = cmd[3];
124 }
125
126 StringBuilder sb = new StringBuilder();
127
128 lock (m_scenes)
129 {
130 foreach (Scene scene in m_scenes)
131 {
132 if (targetNameSupplied)
133 {
134 ScenePresence sp = scene.GetScenePresence(optionalTargetFirstName, optionalTargetLastName);
135 if (sp != null && !sp.IsChildAgent)
136 GetAttachmentsReport(sp, sb);
137 }
138 else
139 {
140 scene.ForEachRootScenePresence(sp => GetAttachmentsReport(sp, sb));
141 }
142 }
143 }
144
145 MainConsole.Instance.Output(sb.ToString());
146 }
147
148 private void GetAttachmentsReport(ScenePresence sp, StringBuilder sb)
149 {
150 sb.AppendFormat("Animations for {0}\n", sp.Name);
151
152 ConsoleDisplayList cdl = new ConsoleDisplayList() { Indent = 2 };
153 ScenePresenceAnimator spa = sp.Animator;
154 AnimationSet anims = sp.Animator.Animations;
155
156 string cma = spa.CurrentMovementAnimation;
157 cdl.AddRow(
158 "Current movement anim",
159 string.Format("{0}, {1}", DefaultAvatarAnimations.GetDefaultAnimation(cma), cma));
160
161 UUID defaultAnimId = anims.DefaultAnimation.AnimID;
162 cdl.AddRow(
163 "Default anim",
164 string.Format("{0}, {1}", defaultAnimId, sp.Animator.GetAnimName(defaultAnimId)));
165
166 UUID implicitDefaultAnimId = anims.ImplicitDefaultAnimation.AnimID;
167 cdl.AddRow(
168 "Implicit default anim",
169 string.Format("{0}, {1}",
170 implicitDefaultAnimId, sp.Animator.GetAnimName(implicitDefaultAnimId)));
171
172 cdl.AddToStringBuilder(sb);
173
174 ConsoleDisplayTable cdt = new ConsoleDisplayTable() { Indent = 2 };
175 cdt.AddColumn("Animation ID", 36);
176 cdt.AddColumn("Name", 20);
177 cdt.AddColumn("Seq", 3);
178 cdt.AddColumn("Object ID", 36);
179
180 UUID[] animIds;
181 int[] sequenceNumbers;
182 UUID[] objectIds;
183
184 sp.Animator.Animations.GetArrays(out animIds, out sequenceNumbers, out objectIds);
185
186 for (int i = 0; i < animIds.Length; i++)
187 {
188 UUID animId = animIds[i];
189 string animName = sp.Animator.GetAnimName(animId);
190 int seq = sequenceNumbers[i];
191 UUID objectId = objectIds[i];
192
193 cdt.AddRow(animId, animName, seq, objectId);
194 }
195
196 cdt.AddToStringBuilder(sb);
197 sb.Append("\n");
198 }
199 }
200} \ No newline at end of file
diff --git a/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs b/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs
new file mode 100644
index 0000000..2f9bb1e
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Appearance/AppearanceInfoModule.cs
@@ -0,0 +1,482 @@
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.Linq;
31using System.Reflection;
32using System.Text;
33using log4net;
34using Mono.Addins;
35using Nini.Config;
36using OpenMetaverse;
37using OpenSim.Framework;
38using OpenSim.Framework.Console;
39using OpenSim.Framework.Monitoring;
40using OpenSim.Region.ClientStack.LindenUDP;
41using OpenSim.Region.Framework.Interfaces;
42using OpenSim.Region.Framework.Scenes;
43
44namespace OpenSim.Region.OptionalModules.Avatar.Appearance
45{
46 /// <summary>
47 /// A module that just holds commands for inspecting avatar appearance.
48 /// </summary>
49 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AppearanceInfoModule")]
50 public class AppearanceInfoModule : ISharedRegionModule
51 {
52// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
53
54 private List<Scene> m_scenes = new List<Scene>();
55
56// private IAvatarFactoryModule m_avatarFactory;
57
58 public string Name { get { return "Appearance Information Module"; } }
59
60 public Type ReplaceableInterface { get { return null; } }
61
62 public void Initialise(IConfigSource source)
63 {
64// m_log.DebugFormat("[APPEARANCE INFO MODULE]: INITIALIZED MODULE");
65 }
66
67 public void PostInitialise()
68 {
69// m_log.DebugFormat("[APPEARANCE INFO MODULE]: POST INITIALIZED MODULE");
70 }
71
72 public void Close()
73 {
74// m_log.DebugFormat("[APPEARANCE INFO MODULE]: CLOSED MODULE");
75 }
76
77 public void AddRegion(Scene scene)
78 {
79// m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} ADDED", scene.RegionInfo.RegionName);
80 }
81
82 public void RemoveRegion(Scene scene)
83 {
84// m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName);
85
86 lock (m_scenes)
87 m_scenes.Remove(scene);
88 }
89
90 public void RegionLoaded(Scene scene)
91 {
92// m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName);
93
94 lock (m_scenes)
95 m_scenes.Add(scene);
96
97 scene.AddCommand(
98 "Users", this, "show appearance",
99 "show appearance [<first-name> <last-name>]",
100 "Synonym for 'appearance show'",
101 HandleShowAppearanceCommand);
102
103 scene.AddCommand(
104 "Users", this, "appearance show",
105 "appearance show [<first-name> <last-name>]",
106 "Show appearance information for avatars.",
107 "This command checks whether the simulator has all the baked textures required to display an avatar to other viewers. "
108 + "\nIf not, then appearance is 'corrupt' and other avatars will continue to see it as a cloud."
109 + "\nOptionally, you can view just a particular avatar's appearance information."
110 + "\nIn this case, the texture UUID for each bake type is also shown and whether the simulator can find the referenced texture.",
111 HandleShowAppearanceCommand);
112
113 scene.AddCommand(
114 "Users", this, "appearance send",
115 "appearance send [<first-name> <last-name>]",
116 "Send appearance data for each avatar in the simulator to other viewers.",
117 "Optionally, you can specify that only a particular avatar's appearance data is sent.",
118 HandleSendAppearanceCommand);
119
120 scene.AddCommand(
121 "Users", this, "appearance rebake",
122 "appearance rebake <first-name> <last-name>",
123 "Send a request to the user's viewer for it to rebake and reupload its appearance textures.",
124 "This is currently done for all baked texture references previously received, whether the simulator can find the asset or not."
125 + "\nThis will only work for texture ids that the viewer has already uploaded."
126 + "\nIf the viewer has not yet sent the server any texture ids then nothing will happen"
127 + "\nsince requests can only be made for ids that the client has already sent us",
128 HandleRebakeAppearanceCommand);
129
130 scene.AddCommand(
131 "Users", this, "appearance find",
132 "appearance find <uuid-or-start-of-uuid>",
133 "Find out which avatar uses the given asset as a baked texture, if any.",
134 "You can specify just the beginning of the uuid, e.g. 2008a8d. A longer UUID must be in dashed format.",
135 HandleFindAppearanceCommand);
136
137 scene.AddCommand(
138 "Users", this, "wearables show",
139 "wearables show [<first-name> <last-name>]",
140 "Show information about wearables for avatars.",
141 "If no avatar name is given then a general summary for all avatars in the scene is shown.\n"
142 + "If an avatar name is given then specific information about current wearables is shown.",
143 HandleShowWearablesCommand);
144
145 scene.AddCommand(
146 "Users", this, "wearables check",
147 "wearables check <first-name> <last-name>",
148 "Check that the wearables of a given avatar in the scene are valid.",
149 "This currently checks that the wearable assets themselves and any assets referenced by them exist.",
150 HandleCheckWearablesCommand);
151 }
152
153 private void HandleSendAppearanceCommand(string module, string[] cmd)
154 {
155 if (cmd.Length != 2 && cmd.Length < 4)
156 {
157 MainConsole.Instance.OutputFormat("Usage: appearance send [<first-name> <last-name>]");
158 return;
159 }
160
161 bool targetNameSupplied = false;
162 string optionalTargetFirstName = null;
163 string optionalTargetLastName = null;
164
165 if (cmd.Length >= 4)
166 {
167 targetNameSupplied = true;
168 optionalTargetFirstName = cmd[2];
169 optionalTargetLastName = cmd[3];
170 }
171
172 lock (m_scenes)
173 {
174 foreach (Scene scene in m_scenes)
175 {
176 if (targetNameSupplied)
177 {
178 ScenePresence sp = scene.GetScenePresence(optionalTargetFirstName, optionalTargetLastName);
179 if (sp != null && !sp.IsChildAgent)
180 {
181 MainConsole.Instance.OutputFormat(
182 "Sending appearance information for {0} to all other avatars in {1}",
183 sp.Name, scene.RegionInfo.RegionName);
184
185 scene.AvatarFactory.SendAppearance(sp.UUID);
186 }
187 }
188 else
189 {
190 scene.ForEachRootScenePresence(
191 sp =>
192 {
193 MainConsole.Instance.OutputFormat(
194 "Sending appearance information for {0} to all other avatars in {1}",
195 sp.Name, scene.RegionInfo.RegionName);
196
197 scene.AvatarFactory.SendAppearance(sp.UUID);
198 }
199 );
200 }
201 }
202 }
203 }
204
205 private void HandleShowAppearanceCommand(string module, string[] cmd)
206 {
207 if (cmd.Length != 2 && cmd.Length < 4)
208 {
209 MainConsole.Instance.OutputFormat("Usage: appearance show [<first-name> <last-name>]");
210 return;
211 }
212
213 bool targetNameSupplied = false;
214 string optionalTargetFirstName = null;
215 string optionalTargetLastName = null;
216
217 if (cmd.Length >= 4)
218 {
219 targetNameSupplied = true;
220 optionalTargetFirstName = cmd[2];
221 optionalTargetLastName = cmd[3];
222 }
223
224 lock (m_scenes)
225 {
226 foreach (Scene scene in m_scenes)
227 {
228 if (targetNameSupplied)
229 {
230 ScenePresence sp = scene.GetScenePresence(optionalTargetFirstName, optionalTargetLastName);
231 if (sp != null && !sp.IsChildAgent)
232 scene.AvatarFactory.WriteBakedTexturesReport(sp, MainConsole.Instance.OutputFormat);
233 }
234 else
235 {
236 scene.ForEachRootScenePresence(
237 sp =>
238 {
239 bool bakedTextureValid = scene.AvatarFactory.ValidateBakedTextureCache(sp);
240 MainConsole.Instance.OutputFormat(
241 "{0} baked appearance texture is {1}", sp.Name, bakedTextureValid ? "OK" : "incomplete");
242 }
243 );
244 }
245 }
246 }
247 }
248
249 private void HandleRebakeAppearanceCommand(string module, string[] cmd)
250 {
251 if (cmd.Length != 4)
252 {
253 MainConsole.Instance.OutputFormat("Usage: appearance rebake <first-name> <last-name>");
254 return;
255 }
256
257 string firstname = cmd[2];
258 string lastname = cmd[3];
259
260 lock (m_scenes)
261 {
262 foreach (Scene scene in m_scenes)
263 {
264 ScenePresence sp = scene.GetScenePresence(firstname, lastname);
265 if (sp != null && !sp.IsChildAgent)
266 {
267 int rebakesRequested = scene.AvatarFactory.RequestRebake(sp, false);
268
269 if (rebakesRequested > 0)
270 MainConsole.Instance.OutputFormat(
271 "Requesting rebake of {0} uploaded textures for {1} in {2}",
272 rebakesRequested, sp.Name, scene.RegionInfo.RegionName);
273 else
274 MainConsole.Instance.OutputFormat(
275 "No texture IDs available for rebake request for {0} in {1}",
276 sp.Name, scene.RegionInfo.RegionName);
277 }
278 }
279 }
280 }
281
282 private void HandleFindAppearanceCommand(string module, string[] cmd)
283 {
284 if (cmd.Length != 3)
285 {
286 MainConsole.Instance.OutputFormat("Usage: appearance find <uuid-or-start-of-uuid>");
287 return;
288 }
289
290 string rawUuid = cmd[2];
291
292 HashSet<ScenePresence> matchedAvatars = new HashSet<ScenePresence>();
293
294 lock (m_scenes)
295 {
296 foreach (Scene scene in m_scenes)
297 {
298 scene.ForEachRootScenePresence(
299 sp =>
300 {
301 Dictionary<BakeType, Primitive.TextureEntryFace> bakedFaces = scene.AvatarFactory.GetBakedTextureFaces(sp.UUID);
302 foreach (Primitive.TextureEntryFace face in bakedFaces.Values)
303 {
304 if (face != null && face.TextureID.ToString().StartsWith(rawUuid))
305 matchedAvatars.Add(sp);
306 }
307 });
308 }
309 }
310
311 if (matchedAvatars.Count == 0)
312 {
313 MainConsole.Instance.OutputFormat("{0} did not match any baked avatar textures in use", rawUuid);
314 }
315 else
316 {
317 MainConsole.Instance.OutputFormat(
318 "{0} matched {1}",
319 rawUuid,
320 string.Join(", ", matchedAvatars.ToList().ConvertAll<string>(sp => sp.Name).ToArray()));
321 }
322 }
323
324 protected void HandleShowWearablesCommand(string module, string[] cmd)
325 {
326 if (cmd.Length != 2 && cmd.Length < 4)
327 {
328 MainConsole.Instance.OutputFormat("Usage: wearables show [<first-name> <last-name>]");
329 return;
330 }
331
332 bool targetNameSupplied = false;
333 string optionalTargetFirstName = null;
334 string optionalTargetLastName = null;
335
336 if (cmd.Length >= 4)
337 {
338 targetNameSupplied = true;
339 optionalTargetFirstName = cmd[2];
340 optionalTargetLastName = cmd[3];
341 }
342
343 StringBuilder sb = new StringBuilder();
344
345 if (targetNameSupplied)
346 {
347 lock (m_scenes)
348 {
349 foreach (Scene scene in m_scenes)
350 {
351 ScenePresence sp = scene.GetScenePresence(optionalTargetFirstName, optionalTargetLastName);
352 if (sp != null && !sp.IsChildAgent)
353 AppendWearablesDetailReport(sp, sb);
354 }
355 }
356 }
357 else
358 {
359 ConsoleDisplayTable cdt = new ConsoleDisplayTable();
360 cdt.AddColumn("Name", ConsoleDisplayUtil.UserNameSize);
361 cdt.AddColumn("Wearables", 2);
362
363 lock (m_scenes)
364 {
365 foreach (Scene scene in m_scenes)
366 {
367 scene.ForEachRootScenePresence(
368 sp =>
369 {
370 int count = 0;
371
372 for (int i = (int)WearableType.Shape; i < (int)WearableType.Physics; i++)
373 count += sp.Appearance.Wearables[i].Count;
374
375 cdt.AddRow(sp.Name, count);
376 }
377 );
378 }
379 }
380
381 sb.Append(cdt.ToString());
382 }
383
384 MainConsole.Instance.Output(sb.ToString());
385 }
386
387 private void HandleCheckWearablesCommand(string module, string[] cmd)
388 {
389 if (cmd.Length != 4)
390 {
391 MainConsole.Instance.OutputFormat("Usage: wearables check <first-name> <last-name>");
392 return;
393 }
394
395 string firstname = cmd[2];
396 string lastname = cmd[3];
397
398 StringBuilder sb = new StringBuilder();
399 UuidGatherer uuidGatherer = new UuidGatherer(m_scenes[0].AssetService);
400
401 lock (m_scenes)
402 {
403 foreach (Scene scene in m_scenes)
404 {
405 ScenePresence sp = scene.GetScenePresence(firstname, lastname);
406 if (sp != null && !sp.IsChildAgent)
407 {
408 sb.AppendFormat("Wearables checks for {0}\n\n", sp.Name);
409
410 for (int i = (int)WearableType.Shape; i < (int)WearableType.Physics; i++)
411 {
412 AvatarWearable aw = sp.Appearance.Wearables[i];
413
414 if (aw.Count > 0)
415 {
416 sb.Append(Enum.GetName(typeof(WearableType), i));
417 sb.Append("\n");
418
419 for (int j = 0; j < aw.Count; j++)
420 {
421 WearableItem wi = aw[j];
422
423 ConsoleDisplayList cdl = new ConsoleDisplayList();
424 cdl.Indent = 2;
425 cdl.AddRow("Item UUID", wi.ItemID);
426 cdl.AddRow("Assets", "");
427 sb.Append(cdl.ToString());
428
429 uuidGatherer.AddForInspection(wi.AssetID);
430 uuidGatherer.GatherAll();
431 string[] assetStrings
432 = Array.ConvertAll<UUID, string>(uuidGatherer.GatheredUuids.Keys.ToArray(), u => u.ToString());
433
434 bool[] existChecks = scene.AssetService.AssetsExist(assetStrings);
435
436 ConsoleDisplayTable cdt = new ConsoleDisplayTable();
437 cdt.Indent = 4;
438 cdt.AddColumn("Type", 10);
439 cdt.AddColumn("UUID", ConsoleDisplayUtil.UuidSize);
440 cdt.AddColumn("Found", 5);
441
442 for (int k = 0; k < existChecks.Length; k++)
443 cdt.AddRow(
444 (AssetType)uuidGatherer.GatheredUuids[new UUID(assetStrings[k])],
445 assetStrings[k], existChecks[k] ? "yes" : "no");
446
447 sb.Append(cdt.ToString());
448 sb.Append("\n");
449 }
450 }
451 }
452 }
453 }
454 }
455
456 MainConsole.Instance.Output(sb.ToString());
457 }
458
459 private void AppendWearablesDetailReport(ScenePresence sp, StringBuilder sb)
460 {
461 sb.AppendFormat("\nWearables for {0}\n", sp.Name);
462
463 ConsoleDisplayTable cdt = new ConsoleDisplayTable();
464 cdt.AddColumn("Type", 10);
465 cdt.AddColumn("Item UUID", ConsoleDisplayUtil.UuidSize);
466 cdt.AddColumn("Asset UUID", ConsoleDisplayUtil.UuidSize);
467
468 for (int i = (int)WearableType.Shape; i < (int)WearableType.Physics; i++)
469 {
470 AvatarWearable aw = sp.Appearance.Wearables[i];
471
472 for (int j = 0; j < aw.Count; j++)
473 {
474 WearableItem wi = aw[j];
475 cdt.AddRow(Enum.GetName(typeof(WearableType), i), wi.ItemID, wi.AssetID);
476 }
477 }
478
479 sb.Append(cdt.ToString());
480 }
481 }
482} \ No newline at end of file
diff --git a/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs b/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs
new file mode 100644
index 0000000..0333747
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs
@@ -0,0 +1,193 @@
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.Linq;
31using System.Reflection;
32using System.Text;
33using log4net;
34using Mono.Addins;
35using Nini.Config;
36using OpenMetaverse;
37using OpenSim.Framework;
38using OpenSim.Framework.Console;
39using OpenSim.Framework.Monitoring;
40using OpenSim.Region.ClientStack.LindenUDP;
41using OpenSim.Region.Framework.Interfaces;
42using OpenSim.Region.Framework.Scenes;
43
44namespace OpenSim.Region.OptionalModules.Avatar.Attachments
45{
46 /// <summary>
47 /// A module that just holds commands for inspecting avatar appearance.
48 /// </summary>
49 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AttachmentsCommandModule")]
50 public class AttachmentsCommandModule : ISharedRegionModule
51 {
52// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
53
54 private List<Scene> m_scenes = new List<Scene>();
55// private IAvatarFactoryModule m_avatarFactory;
56
57 public string Name { get { return "Attachments Command Module"; } }
58
59 public Type ReplaceableInterface { get { return null; } }
60
61 public void Initialise(IConfigSource source)
62 {
63// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: INITIALIZED MODULE");
64 }
65
66 public void PostInitialise()
67 {
68// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: POST INITIALIZED MODULE");
69 }
70
71 public void Close()
72 {
73// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: CLOSED MODULE");
74 }
75
76 public void AddRegion(Scene scene)
77 {
78// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: REGION {0} ADDED", scene.RegionInfo.RegionName);
79 }
80
81 public void RemoveRegion(Scene scene)
82 {
83// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName);
84
85 lock (m_scenes)
86 m_scenes.Remove(scene);
87 }
88
89 public void RegionLoaded(Scene scene)
90 {
91// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName);
92
93 lock (m_scenes)
94 m_scenes.Add(scene);
95
96 scene.AddCommand(
97 "Users", this, "attachments show",
98 "attachments show [<first-name> <last-name>]",
99 "Show attachment information for avatars in this simulator.",
100 "If no name is supplied then information for all avatars is shown.",
101 HandleShowAttachmentsCommand);
102 }
103
104 protected void HandleShowAttachmentsCommand(string module, string[] cmd)
105 {
106 if (cmd.Length != 2 && cmd.Length < 4)
107 {
108 MainConsole.Instance.OutputFormat("Usage: attachments show [<first-name> <last-name>]");
109 return;
110 }
111
112 bool targetNameSupplied = false;
113 string optionalTargetFirstName = null;
114 string optionalTargetLastName = null;
115
116 if (cmd.Length >= 4)
117 {
118 targetNameSupplied = true;
119 optionalTargetFirstName = cmd[2];
120 optionalTargetLastName = cmd[3];
121 }
122
123 StringBuilder sb = new StringBuilder();
124
125 lock (m_scenes)
126 {
127 foreach (Scene scene in m_scenes)
128 {
129 if (targetNameSupplied)
130 {
131 ScenePresence sp = scene.GetScenePresence(optionalTargetFirstName, optionalTargetLastName);
132 if (sp != null && !sp.IsChildAgent)
133 GetAttachmentsReport(sp, sb);
134 }
135 else
136 {
137 scene.ForEachRootScenePresence(sp => GetAttachmentsReport(sp, sb));
138 }
139 }
140 }
141
142 MainConsole.Instance.Output(sb.ToString());
143 }
144
145 private void GetAttachmentsReport(ScenePresence sp, StringBuilder sb)
146 {
147 sb.AppendFormat("Attachments for {0}\n", sp.Name);
148
149 ConsoleDisplayTable ct = new ConsoleDisplayTable() { Indent = 2 };
150 ct.Columns.Add(new ConsoleDisplayTableColumn("Attachment Name", 50));
151 ct.Columns.Add(new ConsoleDisplayTableColumn("Local ID", 10));
152 ct.Columns.Add(new ConsoleDisplayTableColumn("Item ID", 36));
153 ct.Columns.Add(new ConsoleDisplayTableColumn("Attach Point", 14));
154 ct.Columns.Add(new ConsoleDisplayTableColumn("Position", 15));
155
156// sb.AppendFormat(
157// " {0,-36} {1,-10} {2,-36} {3,-14} {4,-15}\n",
158// "Attachment Name", "Local ID", "Item ID", "Attach Point", "Position");
159
160 List<SceneObjectGroup> attachmentObjects = sp.GetAttachments();
161 foreach (SceneObjectGroup attachmentObject in attachmentObjects)
162 {
163// InventoryItemBase attachmentItem
164// = m_scenes[0].InventoryService.GetItem(new InventoryItemBase(attachmentObject.FromItemID));
165
166// if (attachmentItem == null)
167// {
168// sb.AppendFormat(
169// "WARNING: Couldn't find attachment for item {0} at point {1}\n",
170// attachmentData.ItemID, (AttachmentPoint)attachmentData.AttachPoint);
171// continue;
172// }
173// else
174// {
175// sb.AppendFormat(
176// " {0,-36} {1,-10} {2,-36} {3,-14} {4,-15}\n",
177// attachmentObject.Name, attachmentObject.LocalId, attachmentObject.FromItemID,
178// (AttachmentPoint)attachmentObject.AttachmentPoint, attachmentObject.RootPart.AttachedPos);
179
180 ct.AddRow(
181 attachmentObject.Name,
182 attachmentObject.LocalId,
183 attachmentObject.FromItemID,
184 ((AttachmentPoint)attachmentObject.AttachmentPoint),
185 attachmentObject.RootPart.AttachedPos);
186// }
187 }
188
189 ct.AddToStringBuilder(sb);
190 sb.Append("\n");
191 }
192 }
193} \ No newline at end of file
diff --git a/OpenSim/Region/OptionalModules/Avatar/Attachments/TempAttachmentsModule.cs b/OpenSim/Region/OptionalModules/Avatar/Attachments/TempAttachmentsModule.cs
new file mode 100644
index 0000000..535bf67
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Attachments/TempAttachmentsModule.cs
@@ -0,0 +1,190 @@
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.Linq;
31using System.Reflection;
32using System.Text;
33using log4net;
34using Mono.Addins;
35using Nini.Config;
36using OpenMetaverse;
37using OpenSim.Framework;
38using OpenSim.Framework.Console;
39using OpenSim.Framework.Monitoring;
40using OpenSim.Region.ClientStack.LindenUDP;
41using OpenSim.Region.Framework.Interfaces;
42using OpenSim.Region.Framework.Scenes;
43using PermissionMask = OpenSim.Framework.PermissionMask;
44
45namespace OpenSim.Region.OptionalModules.Avatar.Attachments
46{
47 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "TempAttachmentsModule")]
48 public class TempAttachmentsModule : INonSharedRegionModule
49 {
50 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
51
52 private Scene m_scene;
53 private IRegionConsole m_console;
54
55 public void Initialise(IConfigSource configSource)
56 {
57 }
58
59 public void AddRegion(Scene scene)
60 {
61 }
62
63 public void RemoveRegion(Scene scene)
64 {
65 }
66
67 public void RegionLoaded(Scene scene)
68 {
69 m_scene = scene;
70
71 IScriptModuleComms comms = scene.RequestModuleInterface<IScriptModuleComms>();
72 if (comms != null)
73 {
74 comms.RegisterScriptInvocation( this, "llAttachToAvatarTemp");
75 m_log.DebugFormat("[TEMP ATTACHS]: Registered script functions");
76 m_console = scene.RequestModuleInterface<IRegionConsole>();
77
78 if (m_console != null)
79 {
80 m_console.AddCommand("TempAttachModule", false, "set auto_grant_attach_perms", "set auto_grant_attach_perms true|false", "Allow objects owned by the region owner or estate managers to obtain attach permissions without asking the user", SetAutoGrantAttachPerms);
81 }
82 }
83 else
84 {
85 m_log.ErrorFormat("[TEMP ATTACHS]: Failed to register script functions");
86 }
87 }
88
89 public void Close()
90 {
91 }
92
93 public Type ReplaceableInterface
94 {
95 get { return null; }
96 }
97
98 public string Name
99 {
100 get { return "TempAttachmentsModule"; }
101 }
102
103 private void SendConsoleOutput(UUID agentID, string text)
104 {
105 if (m_console == null)
106 return;
107
108 m_console.SendConsoleOutput(agentID, text);
109 }
110
111 private void SetAutoGrantAttachPerms(string module, string[] parms)
112 {
113 UUID agentID = new UUID(parms[parms.Length - 1]);
114 Array.Resize(ref parms, parms.Length - 1);
115
116 if (parms.Length != 3)
117 {
118 SendConsoleOutput(agentID, "Command parameter error");
119 return;
120 }
121
122 string val = parms[2];
123 if (val != "true" && val != "false")
124 {
125 SendConsoleOutput(agentID, "Command parameter error");
126 return;
127 }
128
129 m_scene.StoreExtraSetting("auto_grant_attach_perms", val);
130
131 SendConsoleOutput(agentID, String.Format("auto_grant_attach_perms set to {0}", val));
132 }
133
134 private int llAttachToAvatarTemp(UUID host, UUID script, int attachmentPoint)
135 {
136 SceneObjectPart hostPart = m_scene.GetSceneObjectPart(host);
137
138 if (hostPart == null)
139 return 0;
140
141 if (hostPart.ParentGroup.IsAttachment)
142 return 0;
143
144 IAttachmentsModule attachmentsModule = m_scene.RequestModuleInterface<IAttachmentsModule>();
145 if (attachmentsModule == null)
146 return 0;
147
148 TaskInventoryItem item = hostPart.Inventory.GetInventoryItem(script);
149 if (item == null)
150 return 0;
151
152 if ((item.PermsMask & 32) == 0) // PERMISSION_ATTACH
153 return 0;
154
155 ScenePresence target;
156 if (!m_scene.TryGetScenePresence(item.PermsGranter, out target))
157 return 0;
158
159 if (target.UUID != hostPart.ParentGroup.OwnerID)
160 {
161 uint effectivePerms = hostPart.ParentGroup.GetEffectivePermissions();
162
163 if ((effectivePerms & (uint)PermissionMask.Transfer) == 0)
164 return 0;
165
166 hostPart.ParentGroup.SetOwnerId(target.UUID);
167 hostPart.ParentGroup.SetRootPartOwner(hostPart.ParentGroup.RootPart, target.UUID, target.ControllingClient.ActiveGroupId);
168
169 if (m_scene.Permissions.PropagatePermissions())
170 {
171 foreach (SceneObjectPart child in hostPart.ParentGroup.Parts)
172 {
173 child.Inventory.ChangeInventoryOwner(target.UUID);
174 child.TriggerScriptChangedEvent(Changed.OWNER);
175 child.ApplyNextOwnerPermissions();
176 }
177 }
178
179 hostPart.ParentGroup.RootPart.ObjectSaleType = 0;
180 hostPart.ParentGroup.RootPart.SalePrice = 10;
181
182 hostPart.ParentGroup.HasGroupChanged = true;
183 hostPart.ParentGroup.RootPart.SendPropertiesToClient(target.ControllingClient);
184 hostPart.ParentGroup.RootPart.ScheduleFullUpdate();
185 }
186
187 return attachmentsModule.AttachObject(target, hostPart.ParentGroup, (uint)attachmentPoint, false, false, true) ? 1 : 0;
188 }
189 }
190}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs b/OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs
new file mode 100644
index 0000000..b5d9fda
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs
@@ -0,0 +1,635 @@
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.Text.RegularExpressions;
32using log4net;
33using Nini.Config;
34using OpenSim.Framework;
35using OpenSim.Region.Framework.Interfaces;
36using OpenSim.Region.Framework.Scenes;
37
38namespace OpenSim.Region.OptionalModules.Avatar.Chat
39{
40
41 // An instance of this class exists for each unique combination of
42 // IRC chat interface characteristics, as determined by the supplied
43 // configuration file.
44
45 internal class ChannelState
46 {
47
48 private static readonly ILog m_log =
49 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
50
51 private static Regex arg = new Regex(@"\[[^\[\]]*\]");
52 private static int _idk_ = 0;
53 private static int DEBUG_CHANNEL = 2147483647;
54
55 // These are the IRC Connector configurable parameters with hard-wired
56 // default values (retained for compatability).
57
58 internal string Server = null;
59 internal string Password = null;
60 internal string IrcChannel = null;
61 internal string BaseNickname = "OSimBot";
62 internal uint Port = 6667;
63 internal string User = null;
64
65 internal bool ClientReporting = true;
66 internal bool RelayChat = true;
67 internal bool RelayPrivateChannels = false;
68 internal int RelayChannel = 1;
69 internal List<int> ValidInWorldChannels = new List<int>();
70
71 // Connector agnostic parameters. These values are NOT shared with the
72 // connector and do not differentiate at an IRC level
73
74 internal string PrivateMessageFormat = "PRIVMSG {0} :<{2}> {1} {3}";
75 internal string NoticeMessageFormat = "PRIVMSG {0} :<{2}> {3}";
76 internal int RelayChannelOut = -1;
77 internal bool RandomizeNickname = true;
78 internal bool CommandsEnabled = false;
79 internal int CommandChannel = -1;
80 internal int ConnectDelay = 10;
81 internal int PingDelay = 15;
82 internal string DefaultZone = "Sim";
83
84 internal string _accessPassword = String.Empty;
85 internal Regex AccessPasswordRegex = null;
86 internal List<string> ExcludeList = new List<string>();
87 internal string AccessPassword
88 {
89 get { return _accessPassword; }
90 set
91 {
92 _accessPassword = value;
93 AccessPasswordRegex = new Regex(String.Format(@"^{0},\s*(?<avatar>[^,]+),\s*(?<message>.+)$", _accessPassword),
94 RegexOptions.Compiled);
95 }
96 }
97
98
99
100 // IRC connector reference
101
102 internal IRCConnector irc = null;
103
104 internal int idn = _idk_++;
105
106 // List of regions dependent upon this connection
107
108 internal List<RegionState> clientregions = new List<RegionState>();
109
110 // Needed by OpenChannel
111
112 internal ChannelState()
113 {
114 }
115
116 // This constructor is used by the Update* methods. A copy of the
117 // existing channel state is created, and distinguishing characteristics
118 // are copied across.
119
120 internal ChannelState(ChannelState model)
121 {
122 Server = model.Server;
123 Password = model.Password;
124 IrcChannel = model.IrcChannel;
125 Port = model.Port;
126 BaseNickname = model.BaseNickname;
127 RandomizeNickname = model.RandomizeNickname;
128 User = model.User;
129 CommandsEnabled = model.CommandsEnabled;
130 CommandChannel = model.CommandChannel;
131 RelayChat = model.RelayChat;
132 RelayPrivateChannels = model.RelayPrivateChannels;
133 RelayChannelOut = model.RelayChannelOut;
134 RelayChannel = model.RelayChannel;
135 ValidInWorldChannels = model.ValidInWorldChannels;
136 PrivateMessageFormat = model.PrivateMessageFormat;
137 NoticeMessageFormat = model.NoticeMessageFormat;
138 ClientReporting = model.ClientReporting;
139 AccessPassword = model.AccessPassword;
140 DefaultZone = model.DefaultZone;
141 ConnectDelay = model.ConnectDelay;
142 PingDelay = model.PingDelay;
143 }
144
145 // Read the configuration file, performing variable substitution and any
146 // necessary aliasing. See accompanying documentation for how this works.
147 // If you don't need variables, then this works exactly as before.
148 // If either channel or server are not specified, the request fails.
149
150 internal static void OpenChannel(RegionState rs, IConfig config)
151 {
152
153 // Create a new instance of a channel. This may not actually
154 // get used if an equivalent channel already exists.
155
156 ChannelState cs = new ChannelState();
157
158 // Read in the configuration file and filter everything for variable
159 // subsititution.
160
161 m_log.DebugFormat("[IRC-Channel-{0}] Initial request by Region {1} to connect to IRC", cs.idn, rs.Region);
162
163 cs.Server = Substitute(rs, config.GetString("server", null));
164 m_log.DebugFormat("[IRC-Channel-{0}] Server : <{1}>", cs.idn, cs.Server);
165 cs.Password = Substitute(rs, config.GetString("password", null));
166 // probably not a good idea to put a password in the log file
167 cs.User = Substitute(rs, config.GetString("user", null));
168 cs.IrcChannel = Substitute(rs, config.GetString("channel", null));
169 m_log.DebugFormat("[IRC-Channel-{0}] IrcChannel : <{1}>", cs.idn, cs.IrcChannel);
170 cs.Port = Convert.ToUInt32(Substitute(rs, config.GetString("port", Convert.ToString(cs.Port))));
171 m_log.DebugFormat("[IRC-Channel-{0}] Port : <{1}>", cs.idn, cs.Port);
172 cs.BaseNickname = Substitute(rs, config.GetString("nick", cs.BaseNickname));
173 m_log.DebugFormat("[IRC-Channel-{0}] BaseNickname : <{1}>", cs.idn, cs.BaseNickname);
174 cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("randomize_nick", Convert.ToString(cs.RandomizeNickname))));
175 m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname);
176 cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("nicknum", Convert.ToString(cs.RandomizeNickname))));
177 m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname);
178 cs.User = Substitute(rs, config.GetString("username", cs.User));
179 m_log.DebugFormat("[IRC-Channel-{0}] User : <{1}>", cs.idn, cs.User);
180 cs.CommandsEnabled = Convert.ToBoolean(Substitute(rs, config.GetString("commands_enabled", Convert.ToString(cs.CommandsEnabled))));
181 m_log.DebugFormat("[IRC-Channel-{0}] CommandsEnabled : <{1}>", cs.idn, cs.CommandsEnabled);
182 cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("commandchannel", Convert.ToString(cs.CommandChannel))));
183 m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel);
184 cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("command_channel", Convert.ToString(cs.CommandChannel))));
185 m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel);
186 cs.RelayChat = Convert.ToBoolean(Substitute(rs, config.GetString("relay_chat", Convert.ToString(cs.RelayChat))));
187 m_log.DebugFormat("[IRC-Channel-{0}] RelayChat : <{1}>", cs.idn, cs.RelayChat);
188 cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("relay_private_channels", Convert.ToString(cs.RelayPrivateChannels))));
189 m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels);
190 cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("useworldcomm", Convert.ToString(cs.RelayPrivateChannels))));
191 m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels);
192 cs.RelayChannelOut = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_out", Convert.ToString(cs.RelayChannelOut))));
193 m_log.DebugFormat("[IRC-Channel-{0}] RelayChannelOut : <{1}>", cs.idn, cs.RelayChannelOut);
194 cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_in", Convert.ToString(cs.RelayChannel))));
195 m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel);
196 cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("inchannel", Convert.ToString(cs.RelayChannel))));
197 m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel);
198 cs.PrivateMessageFormat = Substitute(rs, config.GetString("msgformat", cs.PrivateMessageFormat));
199 m_log.DebugFormat("[IRC-Channel-{0}] PrivateMessageFormat : <{1}>", cs.idn, cs.PrivateMessageFormat);
200 cs.NoticeMessageFormat = Substitute(rs, config.GetString("noticeformat", cs.NoticeMessageFormat));
201 m_log.DebugFormat("[IRC-Channel-{0}] NoticeMessageFormat : <{1}>", cs.idn, cs.NoticeMessageFormat);
202 cs.ClientReporting = Convert.ToInt32(Substitute(rs, config.GetString("verbosity", cs.ClientReporting ? "1" : "0"))) > 0;
203 m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting);
204 cs.ClientReporting = Convert.ToBoolean(Substitute(rs, config.GetString("report_clients", Convert.ToString(cs.ClientReporting))));
205 m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting);
206 cs.DefaultZone = Substitute(rs, config.GetString("fallback_region", cs.DefaultZone));
207 m_log.DebugFormat("[IRC-Channel-{0}] DefaultZone : <{1}>", cs.idn, cs.DefaultZone);
208 cs.ConnectDelay = Convert.ToInt32(Substitute(rs, config.GetString("connect_delay", Convert.ToString(cs.ConnectDelay))));
209 m_log.DebugFormat("[IRC-Channel-{0}] ConnectDelay : <{1}>", cs.idn, cs.ConnectDelay);
210 cs.PingDelay = Convert.ToInt32(Substitute(rs, config.GetString("ping_delay", Convert.ToString(cs.PingDelay))));
211 m_log.DebugFormat("[IRC-Channel-{0}] PingDelay : <{1}>", cs.idn, cs.PingDelay);
212 cs.AccessPassword = Substitute(rs, config.GetString("access_password", cs.AccessPassword));
213 m_log.DebugFormat("[IRC-Channel-{0}] AccessPassword : <{1}>", cs.idn, cs.AccessPassword);
214 string[] excludes = config.GetString("exclude_list", "").Trim().Split(new Char[] { ',' });
215 cs.ExcludeList = new List<string>(excludes.Length);
216 foreach (string name in excludes)
217 {
218 cs.ExcludeList.Add(name.Trim().ToLower());
219 }
220
221 // Fail if fundamental information is still missing
222
223 if (cs.Server == null)
224 throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}: server missing", cs.idn, rs.Region));
225 else if (cs.IrcChannel == null)
226 throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}: channel missing", cs.idn, rs.Region));
227 else if (cs.BaseNickname == null)
228 throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}: nick missing", cs.idn, rs.Region));
229 else if (cs.User == null)
230 throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}: user missing", cs.idn, rs.Region));
231
232 m_log.InfoFormat("[IRC-Channel-{0}] Configuration for Region {1} is valid", cs.idn, rs.Region);
233 m_log.InfoFormat("[IRC-Channel-{0}] Server = {1}", cs.idn, cs.Server);
234 m_log.InfoFormat("[IRC-Channel-{0}] Channel = {1}", cs.idn, cs.IrcChannel);
235 m_log.InfoFormat("[IRC-Channel-{0}] Port = {1}", cs.idn, cs.Port);
236 m_log.InfoFormat("[IRC-Channel-{0}] Nickname = {1}", cs.idn, cs.BaseNickname);
237 m_log.InfoFormat("[IRC-Channel-{0}] User = {1}", cs.idn, cs.User);
238
239 // Set the channel state for this region
240
241 if (cs.RelayChat)
242 {
243 cs.ValidInWorldChannels.Add(0);
244 cs.ValidInWorldChannels.Add(DEBUG_CHANNEL);
245 }
246
247 if (cs.RelayPrivateChannels)
248 cs.ValidInWorldChannels.Add(cs.RelayChannelOut);
249
250 rs.cs = Integrate(rs, cs);
251
252 }
253
254 // An initialized channel state instance is passed in. If an identical
255 // channel state instance already exists, then the existing instance
256 // is used to replace the supplied value.
257 // If the instance matches with respect to IRC, then the underlying
258 // IRCConnector is assigned to the supplied channel state and the
259 // updated value is returned.
260 // If there is no match, then the supplied instance is completed by
261 // creating and assigning an instance of an IRC connector.
262
263 private static ChannelState Integrate(RegionState rs, ChannelState p_cs)
264 {
265
266 ChannelState cs = p_cs;
267
268 // Check to see if we have an existing server/channel setup that can be used
269 // In the absence of variable substitution this will always resolve to the
270 // same ChannelState instance, and the table will only contains a single
271 // entry, so the performance considerations for the existing behavior are
272 // zero. Only the IRC connector is shared, the ChannelState still contains
273 // values that, while independent of the IRC connetion, do still distinguish
274 // this region's behavior.
275
276 lock (IRCBridgeModule.m_channels)
277 {
278
279 foreach (ChannelState xcs in IRCBridgeModule.m_channels)
280 {
281 if (cs.IsAPerfectMatchFor(xcs))
282 {
283 m_log.DebugFormat("[IRC-Channel-{0}] Channel state matched", cs.idn);
284 cs = xcs;
285 break;
286 }
287 if (cs.IsAConnectionMatchFor(xcs))
288 {
289 m_log.DebugFormat("[IRC-Channel-{0}] Channel matched", cs.idn);
290 cs.irc = xcs.irc;
291 break;
292 }
293 }
294
295 }
296
297 // No entry was found, so this is going to be a new entry.
298
299 if (cs.irc == null)
300 {
301
302 m_log.DebugFormat("[IRC-Channel-{0}] New channel required", cs.idn);
303
304 if ((cs.irc = new IRCConnector(cs)) != null)
305 {
306
307 IRCBridgeModule.m_channels.Add(cs);
308
309 m_log.InfoFormat("[IRC-Channel-{0}] New channel initialized for {1}, nick: {2}, commands {3}, private channels {4}",
310 cs.idn, rs.Region, cs.DefaultZone,
311 cs.CommandsEnabled ? "enabled" : "not enabled",
312 cs.RelayPrivateChannels ? "relayed" : "not relayed");
313 }
314 else
315 {
316 string txt = String.Format("[IRC-Channel-{0}] Region {1} failed to connect to channel {2} on server {3}:{4}",
317 cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
318 m_log.Error(txt);
319 throw new Exception(txt);
320 }
321 }
322 else
323 {
324 m_log.InfoFormat("[IRC-Channel-{0}] Region {1} reusing existing connection to channel {2} on server {3}:{4}",
325 cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
326 }
327
328 m_log.InfoFormat("[IRC-Channel-{0}] Region {1} associated with channel {2} on server {3}:{4}",
329 cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
330
331 // We're finally ready to commit ourselves
332
333
334 return cs;
335
336 }
337
338 // These routines allow differentiating changes to
339 // the underlying channel state. If necessary, a
340 // new channel state will be created.
341
342 internal ChannelState UpdateServer(RegionState rs, string server)
343 {
344 RemoveRegion(rs);
345 ChannelState cs = new ChannelState(this);
346 cs.Server = server;
347 cs = Integrate(rs, cs);
348 cs.AddRegion(rs);
349 return cs;
350 }
351
352 internal ChannelState UpdatePort(RegionState rs, string port)
353 {
354 RemoveRegion(rs);
355 ChannelState cs = new ChannelState(this);
356 cs.Port = Convert.ToUInt32(port);
357 cs = Integrate(rs, cs);
358 cs.AddRegion(rs);
359 return cs;
360 }
361
362 internal ChannelState UpdateChannel(RegionState rs, string channel)
363 {
364 RemoveRegion(rs);
365 ChannelState cs = new ChannelState(this);
366 cs.IrcChannel = channel;
367 cs = Integrate(rs, cs);
368 cs.AddRegion(rs);
369 return cs;
370 }
371
372 internal ChannelState UpdateNickname(RegionState rs, string nickname)
373 {
374 RemoveRegion(rs);
375 ChannelState cs = new ChannelState(this);
376 cs.BaseNickname = nickname;
377 cs = Integrate(rs, cs);
378 cs.AddRegion(rs);
379 return cs;
380 }
381
382 internal ChannelState UpdateClientReporting(RegionState rs, string cr)
383 {
384 RemoveRegion(rs);
385 ChannelState cs = new ChannelState(this);
386 cs.ClientReporting = Convert.ToBoolean(cr);
387 cs = Integrate(rs, cs);
388 cs.AddRegion(rs);
389 return cs;
390 }
391
392 internal ChannelState UpdateRelayIn(RegionState rs, string channel)
393 {
394 RemoveRegion(rs);
395 ChannelState cs = new ChannelState(this);
396 cs.RelayChannel = Convert.ToInt32(channel);
397 cs = Integrate(rs, cs);
398 cs.AddRegion(rs);
399 return cs;
400 }
401
402 internal ChannelState UpdateRelayOut(RegionState rs, string channel)
403 {
404 RemoveRegion(rs);
405 ChannelState cs = new ChannelState(this);
406 cs.RelayChannelOut = Convert.ToInt32(channel);
407 cs = Integrate(rs, cs);
408 cs.AddRegion(rs);
409 return cs;
410 }
411
412 // Determine whether or not this is a 'new' channel. Only those
413 // attributes that uniquely distinguish an IRC connection should
414 // be included here (and only those attributes should really be
415 // in the ChannelState structure)
416
417 private bool IsAConnectionMatchFor(ChannelState cs)
418 {
419 return (
420 Server == cs.Server &&
421 IrcChannel == cs.IrcChannel &&
422 Port == cs.Port &&
423 BaseNickname == cs.BaseNickname &&
424 User == cs.User
425 );
426 }
427
428 // This level of obsessive matching allows us to produce
429 // a minimal overhead int he case of a server which does
430 // need to differentiate IRC at a region level.
431
432 private bool IsAPerfectMatchFor(ChannelState cs)
433 {
434 return (IsAConnectionMatchFor(cs) &&
435 RelayChannelOut == cs.RelayChannelOut &&
436 PrivateMessageFormat == cs.PrivateMessageFormat &&
437 NoticeMessageFormat == cs.NoticeMessageFormat &&
438 RandomizeNickname == cs.RandomizeNickname &&
439 AccessPassword == cs.AccessPassword &&
440 CommandsEnabled == cs.CommandsEnabled &&
441 CommandChannel == cs.CommandChannel &&
442 DefaultZone == cs.DefaultZone &&
443 RelayPrivateChannels == cs.RelayPrivateChannels &&
444 RelayChannel == cs.RelayChannel &&
445 RelayChat == cs.RelayChat &&
446 ClientReporting == cs.ClientReporting
447 );
448 }
449
450 // This function implements the variable substitution mechanism
451 // for the configuration values. Each string read from the
452 // configuration file is scanned for '[...]' enclosures. Each
453 // one that is found is replaced by either a runtime variable
454 // (%xxx) or an existing configuration key. When no further
455 // substitution is possible, the remaining string is returned
456 // to the caller. This allows for arbitrarily nested
457 // enclosures.
458
459 private static string Substitute(RegionState rs, string instr)
460 {
461
462 string result = instr;
463
464 if (string.IsNullOrEmpty(result))
465 return result;
466
467 // Repeatedly scan the string until all possible
468 // substitutions have been performed.
469
470 // m_log.DebugFormat("[IRC-Channel] Parse[1]: {0}", result);
471
472 while (arg.IsMatch(result))
473 {
474
475 string vvar = arg.Match(result).ToString();
476 string var = vvar.Substring(1, vvar.Length - 2).Trim();
477
478 switch (var.ToLower())
479 {
480 case "%region":
481 result = result.Replace(vvar, rs.Region);
482 break;
483 case "%host":
484 result = result.Replace(vvar, rs.Host);
485 break;
486 case "%locx":
487 result = result.Replace(vvar, rs.LocX);
488 break;
489 case "%locy":
490 result = result.Replace(vvar, rs.LocY);
491 break;
492 case "%k":
493 result = result.Replace(vvar, rs.IDK);
494 break;
495 default:
496 result = result.Replace(vvar, rs.config.GetString(var, var));
497 break;
498 }
499 // m_log.DebugFormat("[IRC-Channel] Parse[2]: {0}", result);
500 }
501
502 // m_log.DebugFormat("[IRC-Channel] Parse[3]: {0}", result);
503 return result;
504
505 }
506
507 public void Close()
508 {
509 m_log.InfoFormat("[IRC-Channel-{0}] Closing channel <{1}> to server <{2}:{3}>",
510 idn, IrcChannel, Server, Port);
511 m_log.InfoFormat("[IRC-Channel-{0}] There are {1} active clients",
512 idn, clientregions.Count);
513 irc.Close();
514 }
515
516 public void Open()
517 {
518 m_log.InfoFormat("[IRC-Channel-{0}] Opening channel <{1}> to server <{2}:{3}>",
519 idn, IrcChannel, Server, Port);
520
521 irc.Open();
522
523 }
524
525 // These are called by each region that attaches to this channel. The call affects
526 // only the relationship of the region with the channel. Not the channel to IRC
527 // relationship (unless it is closed and we want it open).
528
529 public void Open(RegionState rs)
530 {
531 AddRegion(rs);
532 Open();
533 }
534
535 // Close is called to ensure that the IRC session is terminated if this is the
536 // only client.
537
538 public void Close(RegionState rs)
539 {
540 RemoveRegion(rs);
541 lock (IRCBridgeModule.m_channels)
542 {
543 if (clientregions.Count == 0)
544 {
545 Close();
546 IRCBridgeModule.m_channels.Remove(this);
547 m_log.InfoFormat("[IRC-Channel-{0}] Region {1} is last user of channel <{2}> to server <{3}:{4}>",
548 idn, rs.Region, IrcChannel, Server, Port);
549 m_log.InfoFormat("[IRC-Channel-{0}] Removed", idn);
550 }
551 }
552 }
553
554 // Add a client region to this channel if it is not already known
555
556 public void AddRegion(RegionState rs)
557 {
558 m_log.InfoFormat("[IRC-Channel-{0}] Adding region {1} to channel <{2}> to server <{3}:{4}>",
559 idn, rs.Region, IrcChannel, Server, Port);
560 if (!clientregions.Contains(rs))
561 {
562 clientregions.Add(rs);
563 lock (irc) irc.depends++;
564 }
565 }
566
567 // Remove a client region from the channel. If this is the last
568 // region, then clean up the channel. The connector will clean itself
569 // up if it finds itself about to be GC'd.
570
571 public void RemoveRegion(RegionState rs)
572 {
573
574 m_log.InfoFormat("[IRC-Channel-{0}] Removing region {1} from channel <{2} to server <{3}:{4}>",
575 idn, rs.Region, IrcChannel, Server, Port);
576
577 if (clientregions.Contains(rs))
578 {
579 clientregions.Remove(rs);
580 lock (irc) irc.depends--;
581 }
582
583 }
584
585 // This function is lifted from the IRCConnector because it
586 // contains information that is not differentiating from an
587 // IRC point-of-view.
588
589 public static void OSChat(IRCConnector p_irc, OSChatMessage c, bool cmsg)
590 {
591
592 // m_log.DebugFormat("[IRC-OSCHAT] from {0}:{1}", p_irc.Server, p_irc.IrcChannel);
593
594 try
595 {
596
597 // Scan through the set of unique channel configuration for those
598 // that belong to this connector. And then forward the message to
599 // all regions known to those channels.
600 // Note that this code is responsible for completing some of the
601 // settings for the inbound OSChatMessage
602
603 lock (IRCBridgeModule.m_channels)
604 {
605 foreach (ChannelState cs in IRCBridgeModule.m_channels)
606 {
607 if (p_irc == cs.irc)
608 {
609
610 // This non-IRC differentiator moved to here
611
612 if (cmsg && !cs.ClientReporting)
613 continue;
614
615 // This non-IRC differentiator moved to here
616
617 c.Channel = (cs.RelayPrivateChannels ? cs.RelayChannel : 0);
618
619 foreach (RegionState region in cs.clientregions)
620 {
621 region.OSChat(cs.irc, c);
622 }
623
624 }
625 }
626 }
627 }
628 catch (Exception ex)
629 {
630 m_log.ErrorFormat("[IRC-OSCHAT]: BroadcastSim Exception: {0}", ex.Message);
631 m_log.Debug(ex);
632 }
633 }
634 }
635}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Chat/IRCBridgeModule.cs b/OpenSim/Region/OptionalModules/Avatar/Chat/IRCBridgeModule.cs
new file mode 100644
index 0000000..351dbfe
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Chat/IRCBridgeModule.cs
@@ -0,0 +1,213 @@
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;
30using System.Collections.Generic;
31using System.Net;
32using System.Reflection;
33using log4net;
34using Mono.Addins;
35using Nini.Config;
36using Nwc.XmlRpc;
37using OpenSim.Framework;
38using OpenSim.Framework.Servers;
39using OpenSim.Region.Framework.Interfaces;
40using OpenSim.Region.Framework.Scenes;
41
42namespace OpenSim.Region.OptionalModules.Avatar.Chat
43{
44 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "IRCBridgeModule")]
45 public class IRCBridgeModule : INonSharedRegionModule
46 {
47 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
48
49 internal static bool Enabled = false;
50 internal static IConfig m_config = null;
51
52 internal static List<ChannelState> m_channels = new List<ChannelState>();
53 internal static List<RegionState> m_regions = new List<RegionState>();
54
55 internal static string m_password = String.Empty;
56 internal RegionState m_region = null;
57
58 #region INonSharedRegionModule Members
59
60 public Type ReplaceableInterface
61 {
62 get { return null; }
63 }
64
65 public string Name
66 {
67 get { return "IRCBridgeModule"; }
68 }
69
70 public void Initialise(IConfigSource config)
71 {
72 m_config = config.Configs["IRC"];
73 if (m_config == null)
74 {
75 // m_log.InfoFormat("[IRC-Bridge] module not configured");
76 return;
77 }
78
79 if (!m_config.GetBoolean("enabled", false))
80 {
81 // m_log.InfoFormat("[IRC-Bridge] module disabled in configuration");
82 return;
83 }
84
85 if (config.Configs["RemoteAdmin"] != null)
86 {
87 m_password = config.Configs["RemoteAdmin"].GetString("access_password", m_password);
88 }
89
90 Enabled = true;
91
92 m_log.InfoFormat("[IRC-Bridge]: Module is enabled");
93 }
94
95 public void AddRegion(Scene scene)
96 {
97 if (Enabled)
98 {
99 try
100 {
101 m_log.InfoFormat("[IRC-Bridge] Connecting region {0}", scene.RegionInfo.RegionName);
102
103 if (!String.IsNullOrEmpty(m_password))
104 MainServer.Instance.AddXmlRPCHandler("irc_admin", XmlRpcAdminMethod, false);
105
106 m_region = new RegionState(scene, m_config);
107 lock (m_regions) m_regions.Add(m_region);
108 m_region.Open();
109 }
110 catch (Exception e)
111 {
112 m_log.WarnFormat("[IRC-Bridge] Region {0} not connected to IRC : {1}", scene.RegionInfo.RegionName, e.Message);
113 m_log.Debug(e);
114 }
115 }
116 else
117 {
118 //m_log.DebugFormat("[IRC-Bridge] Not enabled. Connect for region {0} ignored", scene.RegionInfo.RegionName);
119 }
120 }
121
122
123 public void RegionLoaded(Scene scene)
124 {
125 }
126
127 public void RemoveRegion(Scene scene)
128 {
129 if (!Enabled)
130 return;
131
132 if (m_region == null)
133 return;
134
135 if (!String.IsNullOrEmpty(m_password))
136 MainServer.Instance.RemoveXmlRPCHandler("irc_admin");
137
138 m_region.Close();
139
140 if (m_regions.Contains(m_region))
141 {
142 lock (m_regions) m_regions.Remove(m_region);
143 }
144 }
145
146 public void Close()
147 {
148 }
149 #endregion
150
151 public static XmlRpcResponse XmlRpcAdminMethod(XmlRpcRequest request, IPEndPoint remoteClient)
152 {
153 m_log.Debug("[IRC-Bridge]: XML RPC Admin Entry");
154
155 XmlRpcResponse response = new XmlRpcResponse();
156 Hashtable responseData = new Hashtable();
157
158 try
159 {
160 Hashtable requestData = (Hashtable)request.Params[0];
161 bool found = false;
162 string region = String.Empty;
163
164 if (m_password != String.Empty)
165 {
166 if (!requestData.ContainsKey("password"))
167 throw new Exception("Invalid request");
168 if ((string)requestData["password"] != m_password)
169 throw new Exception("Invalid request");
170 }
171
172 if (!requestData.ContainsKey("region"))
173 throw new Exception("No region name specified");
174 region = (string)requestData["region"];
175
176 foreach (RegionState rs in m_regions)
177 {
178 if (rs.Region == region)
179 {
180 responseData["server"] = rs.cs.Server;
181 responseData["port"] = (int)rs.cs.Port;
182 responseData["user"] = rs.cs.User;
183 responseData["channel"] = rs.cs.IrcChannel;
184 responseData["enabled"] = rs.cs.irc.Enabled;
185 responseData["connected"] = rs.cs.irc.Connected;
186 responseData["nickname"] = rs.cs.irc.Nick;
187 found = true;
188 break;
189 }
190 }
191
192 if (!found) throw new Exception(String.Format("Region <{0}> not found", region));
193
194 responseData["success"] = true;
195 }
196 catch (Exception e)
197 {
198 m_log.ErrorFormat("[IRC-Bridge] XML RPC Admin request failed : {0}", e.Message);
199
200 responseData["success"] = "false";
201 responseData["error"] = e.Message;
202 }
203 finally
204 {
205 response.Value = responseData;
206 }
207
208 m_log.Debug("[IRC-Bridge]: XML RPC Admin Exit");
209
210 return response;
211 }
212 }
213}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs b/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs
new file mode 100644
index 0000000..6985371
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs
@@ -0,0 +1,913 @@
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.Timers;
30using System.Collections.Generic;
31using System.IO;
32using System.Net.Sockets;
33using System.Reflection;
34using System.Text.RegularExpressions;
35using System.Threading;
36using OpenMetaverse;
37using log4net;
38using Nini.Config;
39using OpenSim.Framework;
40using OpenSim.Framework.Monitoring;
41using OpenSim.Region.Framework.Interfaces;
42using OpenSim.Region.Framework.Scenes;
43
44namespace OpenSim.Region.OptionalModules.Avatar.Chat
45{
46 public class IRCConnector
47 {
48
49 #region Global (static) state
50
51 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
52
53 // Local constants
54
55 // This computation is not the real region center if the region is larger than 256.
56 // This computation isn't fixed because there is not a handle back to the region.
57 private static readonly Vector3 CenterOfRegion = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20);
58 private static readonly char[] CS_SPACE = { ' ' };
59
60 private const int WD_INTERVAL = 1000; // base watchdog interval
61 private static int PING_PERIOD = 15; // WD intervals per PING
62 private static int ICCD_PERIOD = 10; // WD intervals between Connects
63 private static int L_TIMEOUT = 25; // Login time out interval
64
65 private static int _idk_ = 0; // core connector identifier
66 private static int _pdk_ = 0; // ping interval counter
67 private static int _icc_ = ICCD_PERIOD; // IRC connect counter
68
69 // List of configured connectors
70
71 private static List<IRCConnector> m_connectors = new List<IRCConnector>();
72
73 // Watchdog state
74
75 private static System.Timers.Timer m_watchdog = null;
76
77 // The watch-dog gets started as soon as the class is instantiated, and
78 // ticks once every second (WD_INTERVAL)
79
80 static IRCConnector()
81 {
82 m_log.DebugFormat("[IRC-Connector]: Static initialization started");
83 m_watchdog = new System.Timers.Timer(WD_INTERVAL);
84 m_watchdog.Elapsed += new ElapsedEventHandler(WatchdogHandler);
85 m_watchdog.AutoReset = true;
86 m_watchdog.Start();
87 m_log.DebugFormat("[IRC-Connector]: Static initialization complete");
88 }
89
90 #endregion
91
92 #region Instance state
93
94 // Connector identity
95
96 internal int idn = _idk_++;
97
98 // How many regions depend upon this connection
99 // This count is updated by the ChannelState object and reflects the sum
100 // of the region clients associated with the set of associated channel
101 // state instances. That's why it cannot be managed here.
102
103 internal int depends = 0;
104
105 // This variable counts the number of resets that have been performed
106 // on the connector. When a listener thread terminates, it checks to
107 // see of the reset count has changed before it schedules another
108 // reset.
109
110 internal int m_resetk = 0;
111
112 private Object msyncConnect = new Object();
113
114 internal bool m_randomizeNick = true; // add random suffix
115 internal string m_baseNick = null; // base name for randomizing
116 internal string m_nick = null; // effective nickname
117
118 public string Nick // Public property
119 {
120 get { return m_nick; }
121 set { m_nick = value; }
122 }
123
124 private bool m_enabled = false; // connector enablement
125 public bool Enabled
126 {
127 get { return m_enabled; }
128 }
129
130 private bool m_connected = false; // connection status
131 private bool m_pending = false; // login disposition
132 private int m_timeout = L_TIMEOUT; // login timeout counter
133 public bool Connected
134 {
135 get { return m_connected; }
136 }
137
138 private string m_ircChannel; // associated channel id
139 public string IrcChannel
140 {
141 get { return m_ircChannel; }
142 set { m_ircChannel = value; }
143 }
144
145 private uint m_port = 6667; // session port
146 public uint Port
147 {
148 get { return m_port; }
149 set { m_port = value; }
150 }
151
152 private string m_server = null; // IRC server name
153 public string Server
154 {
155 get { return m_server; }
156 set { m_server = value; }
157 }
158 private string m_password = null;
159 public string Password
160 {
161 get { return m_password; }
162 set { m_password = value; }
163 }
164
165 private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
166 public string User
167 {
168 get { return m_user; }
169 }
170
171 // Network interface
172
173 private TcpClient m_tcp;
174 private NetworkStream m_stream = null;
175 private StreamReader m_reader;
176 private StreamWriter m_writer;
177
178 // Channel characteristic info (if available)
179
180 internal string usermod = String.Empty;
181 internal string chanmod = String.Empty;
182 internal string version = String.Empty;
183 internal bool motd = false;
184
185 #endregion
186
187 #region connector instance management
188
189 internal IRCConnector(ChannelState cs)
190 {
191
192 // Prepare network interface
193
194 m_tcp = null;
195 m_writer = null;
196 m_reader = null;
197
198 // Setup IRC session parameters
199
200 m_server = cs.Server;
201 m_password = cs.Password;
202 m_baseNick = cs.BaseNickname;
203 m_randomizeNick = cs.RandomizeNickname;
204 m_ircChannel = cs.IrcChannel;
205 m_port = cs.Port;
206 m_user = cs.User;
207
208 if (m_watchdog == null)
209 {
210 // Non-differentiating
211
212 ICCD_PERIOD = cs.ConnectDelay;
213 PING_PERIOD = cs.PingDelay;
214
215 // Smaller values are not reasonable
216
217 if (ICCD_PERIOD < 5)
218 ICCD_PERIOD = 5;
219
220 if (PING_PERIOD < 5)
221 PING_PERIOD = 5;
222
223 _icc_ = ICCD_PERIOD; // get started right away!
224
225 }
226
227 // The last line of defense
228
229 if (m_server == null || m_baseNick == null || m_ircChannel == null || m_user == null)
230 throw new Exception("Invalid connector configuration");
231
232 // Generate an initial nickname
233
234 if (m_randomizeNick)
235 m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
236 else
237 m_nick = m_baseNick;
238
239 m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn);
240
241 }
242
243 ~IRCConnector()
244 {
245 m_watchdog.Stop();
246 Close();
247 }
248
249 // Mark the connector as connectable. Harmless if already enabled.
250
251 public void Open()
252 {
253 if (!m_enabled)
254 {
255
256 if (!Connected)
257 {
258 Connect();
259 }
260
261 lock (m_connectors)
262 m_connectors.Add(this);
263
264 m_enabled = true;
265
266 }
267 }
268
269 // Only close the connector if the dependency count is zero.
270
271 public void Close()
272 {
273
274 m_log.InfoFormat("[IRC-Connector-{0}] Closing", idn);
275
276 lock (msyncConnect)
277 {
278
279 if ((depends == 0) && Enabled)
280 {
281
282 m_enabled = false;
283
284 if (Connected)
285 {
286 m_log.DebugFormat("[IRC-Connector-{0}] Closing interface", idn);
287
288 // Cleanup the IRC session
289
290 try
291 {
292 m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing",
293 m_nick, m_ircChannel, m_server));
294 m_writer.Flush();
295 }
296 catch (Exception) { }
297
298
299 m_connected = false;
300
301 try { m_writer.Close(); }
302 catch (Exception) { }
303 try { m_reader.Close(); }
304 catch (Exception) { }
305 try { m_stream.Close(); }
306 catch (Exception) { }
307 try { m_tcp.Close(); }
308 catch (Exception) { }
309
310 }
311
312 lock (m_connectors)
313 m_connectors.Remove(this);
314
315 }
316 }
317
318 m_log.InfoFormat("[IRC-Connector-{0}] Closed", idn);
319
320 }
321
322 #endregion
323
324 #region session management
325
326 // Connect to the IRC server. A connector should always be connected, once enabled
327
328 public void Connect()
329 {
330
331 if (!m_enabled)
332 return;
333
334 // Delay until next WD cycle if this is too close to the last start attempt
335
336 while (_icc_ < ICCD_PERIOD)
337 return;
338
339 m_log.DebugFormat("[IRC-Connector-{0}]: Connection request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
340
341 lock (msyncConnect)
342 {
343
344 _icc_ = 0;
345
346 try
347 {
348
349 if (m_connected) return;
350
351 m_connected = true;
352 m_pending = true;
353 m_timeout = L_TIMEOUT;
354
355 m_tcp = new TcpClient(m_server, (int)m_port);
356 m_stream = m_tcp.GetStream();
357 m_reader = new StreamReader(m_stream);
358 m_writer = new StreamWriter(m_stream);
359
360 m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port);
361
362 WorkManager.StartThread(ListenerRun, "IRCConnectionListenerThread", ThreadPriority.Normal, true, false);
363
364 // This is the message order recommended by RFC 2812
365 if (m_password != null)
366 m_writer.WriteLine(String.Format("PASS {0}", m_password));
367 m_writer.WriteLine(String.Format("NICK {0}", m_nick));
368 m_writer.Flush();
369 m_writer.WriteLine(m_user);
370 m_writer.Flush();
371 m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
372 m_writer.Flush();
373
374 m_log.InfoFormat("[IRC-Connector-{0}]: {1} has asked to join {2}", idn, m_nick, m_ircChannel);
375
376 }
377 catch (Exception e)
378 {
379 m_log.ErrorFormat("[IRC-Connector-{0}] cannot connect {1} to {2}:{3}: {4}",
380 idn, m_nick, m_server, m_port, e.Message);
381 // It might seem reasonable to reset connected and pending status here
382 // Seeing as we know that the login has failed, but if we do that, then
383 // connection will be retried each time the interconnection interval
384 // expires. By leaving them as they are, the connection will be retried
385 // when the login timeout expires. Which is preferred.
386 }
387
388 }
389
390 return;
391
392 }
393
394 // Reconnect is used to force a re-cycle of the IRC connection. Should generally
395 // be a transparent event
396
397 public void Reconnect()
398 {
399 m_log.DebugFormat("[IRC-Connector-{0}]: Reconnect request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
400
401 // Don't do this if a Connect is in progress...
402
403 lock (msyncConnect)
404 {
405
406 if (m_connected)
407 {
408
409 m_log.InfoFormat("[IRC-Connector-{0}] Resetting connector", idn);
410
411 // Mark as disconnected. This will allow the listener thread
412 // to exit if still in-flight.
413
414
415 // The listener thread is not aborted - it *might* actually be
416 // the thread that is running the Reconnect! Instead just close
417 // the socket and it will disappear of its own accord, once this
418 // processing is completed.
419
420 try { m_writer.Close(); }
421 catch (Exception) { }
422 try { m_reader.Close(); }
423 catch (Exception) { }
424 try { m_tcp.Close(); }
425 catch (Exception) { }
426
427 m_connected = false;
428 m_pending = false;
429 m_resetk++;
430
431 }
432
433 }
434
435 Connect();
436
437 }
438
439 #endregion
440
441 #region Outbound (to-IRC) message handlers
442
443 public void PrivMsg(string pattern, string from, string region, string msg)
444 {
445
446 // m_log.DebugFormat("[IRC-Connector-{0}] PrivMsg to IRC from {1}: <{2}>", idn, from,
447 // String.Format(pattern, m_ircChannel, from, region, msg));
448
449 // One message to the IRC server
450
451 try
452 {
453 m_writer.WriteLine(pattern, m_ircChannel, from, region, msg);
454 m_writer.Flush();
455 // m_log.DebugFormat("[IRC-Connector-{0}]: PrivMsg from {1} in {2}: {3}", idn, from, region, msg);
456 }
457 catch (IOException)
458 {
459 m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg I/O Error: disconnected from IRC server", idn);
460 Reconnect();
461 }
462 catch (Exception ex)
463 {
464 m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg exception : {1}", idn, ex.Message);
465 m_log.Debug(ex);
466 }
467
468 }
469
470 public void Send(string msg)
471 {
472
473 // m_log.DebugFormat("[IRC-Connector-{0}] Send to IRC : <{1}>", idn, msg);
474
475 try
476 {
477 m_writer.WriteLine(msg);
478 m_writer.Flush();
479 // m_log.DebugFormat("[IRC-Connector-{0}] Sent command string: {1}", idn, msg);
480 }
481 catch (IOException)
482 {
483 m_log.ErrorFormat("[IRC-Connector-{0}] Disconnected from IRC server.(Send)", idn);
484 Reconnect();
485 }
486 catch (Exception ex)
487 {
488 m_log.ErrorFormat("[IRC-Connector-{0}] Send exception trap: {0}", idn, ex.Message);
489 m_log.Debug(ex);
490 }
491
492 }
493
494 #endregion
495
496 public void ListenerRun()
497 {
498
499 string inputLine;
500 int resetk = m_resetk;
501
502 try
503 {
504 while (m_enabled && m_connected)
505 {
506 if ((inputLine = m_reader.ReadLine()) == null)
507 throw new Exception("Listener input socket closed");
508
509 Watchdog.UpdateThread();
510
511 // m_log.Info("[IRCConnector]: " + inputLine);
512
513 if (inputLine.Contains("PRIVMSG"))
514 {
515 Dictionary<string, string> data = ExtractMsg(inputLine);
516
517 // Any chat ???
518 if (data != null)
519 {
520 OSChatMessage c = new OSChatMessage();
521 c.Message = data["msg"];
522 c.Type = ChatTypeEnum.Region;
523 c.Position = CenterOfRegion;
524 c.From = data["nick"];
525 c.Sender = null;
526 c.SenderUUID = UUID.Zero;
527
528 // Is message "\001ACTION foo bar\001"?
529 // Then change to: "/me foo bar"
530
531 if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION"))
532 c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9));
533
534 ChannelState.OSChat(this, c, false);
535 }
536 }
537 else
538 {
539 ProcessIRCCommand(inputLine);
540 }
541 }
542 }
543 catch (Exception /*e*/)
544 {
545 // m_log.ErrorFormat("[IRC-Connector-{0}]: ListenerRun exception trap: {1}", idn, e.Message);
546 // m_log.Debug(e);
547 }
548
549 // This is potentially circular, but harmless if so.
550 // The connection is marked as not connected the first time
551 // through reconnect.
552
553 if (m_enabled && (m_resetk == resetk))
554 Reconnect();
555
556 Watchdog.RemoveThread();
557 }
558
559 private Regex RE = new Regex(@":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)",
560 RegexOptions.Multiline);
561
562 private Dictionary<string, string> ExtractMsg(string input)
563 {
564 //examines IRC commands and extracts any private messages
565 // which will then be reboadcast in the Sim
566
567 // m_log.InfoFormat("[IRC-Connector-{0}]: ExtractMsg: {1}", idn, input);
568
569 Dictionary<string, string> result = null;
570 MatchCollection matches = RE.Matches(input);
571
572 // Get some direct matches $1 $4 is a
573 if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5))
574 {
575 // m_log.Info("[IRCConnector]: Number of matches: " + matches.Count);
576 // if (matches.Count > 0)
577 // {
578 // m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count);
579 // }
580 return null;
581 }
582
583 result = new Dictionary<string, string>();
584 result.Add("nick", matches[0].Groups[1].Value);
585 result.Add("user", matches[0].Groups[2].Value);
586 result.Add("channel", matches[0].Groups[3].Value);
587 result.Add("msg", matches[0].Groups[4].Value);
588
589 return result;
590 }
591
592 public void BroadcastSim(string sender, string format, params string[] args)
593 {
594 try
595 {
596 OSChatMessage c = new OSChatMessage();
597 c.From = sender;
598 c.Message = String.Format(format, args);
599 c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say;
600 c.Position = CenterOfRegion;
601 c.Sender = null;
602 c.SenderUUID = UUID.Zero;
603
604 ChannelState.OSChat(this, c, true);
605
606 }
607 catch (Exception ex) // IRC gate should not crash Sim
608 {
609 m_log.ErrorFormat("[IRC-Connector-{0}]: BroadcastSim Exception Trap: {1}\n{2}", idn, ex.Message, ex.StackTrace);
610 }
611 }
612
613 #region IRC Command Handlers
614
615 public void ProcessIRCCommand(string command)
616 {
617
618 string[] commArgs;
619 string c_server = m_server;
620
621 string pfx = String.Empty;
622 string cmd = String.Empty;
623 string parms = String.Empty;
624
625 // ":" indicates that a prefix is present
626 // There are NEVER more than 17 real
627 // fields. A parameter that starts with
628 // ":" indicates that the remainder of the
629 // line is a single parameter value.
630
631 commArgs = command.Split(CS_SPACE, 2);
632
633 if (commArgs[0].StartsWith(":"))
634 {
635 pfx = commArgs[0].Substring(1);
636 commArgs = commArgs[1].Split(CS_SPACE, 2);
637 }
638
639 cmd = commArgs[0];
640 parms = commArgs[1];
641
642 // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}>", idn, pfx, cmd);
643
644 switch (cmd)
645 {
646
647 // Messages 001-004 are always sent
648 // following signon.
649
650 case "001": // Welcome ...
651 case "002": // Server information
652 case "003": // Welcome ...
653 break;
654 case "004": // Server information
655 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
656 commArgs = parms.Split(CS_SPACE);
657 c_server = commArgs[1];
658 m_server = c_server;
659 version = commArgs[2];
660 usermod = commArgs[3];
661 chanmod = commArgs[4];
662 break;
663 case "005": // Server information
664 break;
665 case "042":
666 case "250":
667 case "251":
668 case "252":
669 case "254":
670 case "255":
671 case "265":
672 case "266":
673 case "332": // Subject
674 case "333": // Subject owner (?)
675 case "353": // Name list
676 case "366": // End-of-Name list marker
677 case "372": // MOTD body
678 case "375": // MOTD start
679 // m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
680 break;
681 case "376": // MOTD end
682 // m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
683 motd = true;
684 break;
685 case "451": // Not registered
686 break;
687 case "433": // Nickname in use
688 // Gen a new name
689 m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
690 m_log.ErrorFormat("[IRC-Connector-{0}]: [{1}] IRC SERVER reports NicknameInUse, trying {2}", idn, cmd, m_nick);
691 // Retry
692 m_writer.WriteLine(String.Format("NICK {0}", m_nick));
693 m_writer.Flush();
694 m_writer.WriteLine(m_user);
695 m_writer.Flush();
696 m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
697 m_writer.Flush();
698 break;
699 case "479": // Bad channel name, etc. This will never work, so disable the connection
700 m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
701 m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] Connector disabled", idn, cmd);
702 m_enabled = false;
703 m_connected = false;
704 m_pending = false;
705 break;
706 case "NOTICE":
707 // m_log.WarnFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
708 break;
709 case "ERROR":
710 m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
711 if (parms.Contains("reconnect too fast"))
712 ICCD_PERIOD++;
713 m_pending = false;
714 Reconnect();
715 break;
716 case "PING":
717 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
718 m_writer.WriteLine(String.Format("PONG {0}", parms));
719 m_writer.Flush();
720 break;
721 case "PONG":
722 break;
723 case "JOIN":
724 if (m_pending)
725 {
726 m_log.InfoFormat("[IRC-Connector-{0}] [{1}] Connected", idn, cmd);
727 m_pending = false;
728 }
729 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
730 eventIrcJoin(pfx, cmd, parms);
731 break;
732 case "PART":
733 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
734 eventIrcPart(pfx, cmd, parms);
735 break;
736 case "MODE":
737 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
738 eventIrcMode(pfx, cmd, parms);
739 break;
740 case "NICK":
741 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
742 eventIrcNickChange(pfx, cmd, parms);
743 break;
744 case "KICK":
745 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
746 eventIrcKick(pfx, cmd, parms);
747 break;
748 case "QUIT":
749 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
750 eventIrcQuit(pfx, cmd, parms);
751 break;
752 default:
753 m_log.DebugFormat("[IRC-Connector-{0}] Command '{1}' ignored, parms = {2}", idn, cmd, parms);
754 break;
755 }
756
757 // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}> complete", idn, pfx, cmd);
758
759 }
760
761 public void eventIrcJoin(string prefix, string command, string parms)
762 {
763 string[] args = parms.Split(CS_SPACE, 2);
764 string IrcUser = prefix.Split('!')[0];
765 string IrcChannel = args[0];
766
767 if (IrcChannel.StartsWith(":"))
768 IrcChannel = IrcChannel.Substring(1);
769
770 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCJoin {1}:{2}", idn, m_server, m_ircChannel);
771 BroadcastSim(IrcUser, "/me joins {0}", IrcChannel);
772 }
773
774 public void eventIrcPart(string prefix, string command, string parms)
775 {
776 string[] args = parms.Split(CS_SPACE, 2);
777 string IrcUser = prefix.Split('!')[0];
778 string IrcChannel = args[0];
779
780 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCPart {1}:{2}", idn, m_server, m_ircChannel);
781 BroadcastSim(IrcUser, "/me parts {0}", IrcChannel);
782 }
783
784 public void eventIrcMode(string prefix, string command, string parms)
785 {
786 string[] args = parms.Split(CS_SPACE, 2);
787 string UserMode = args[1];
788
789 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCMode {1}:{2}", idn, m_server, m_ircChannel);
790 if (UserMode.Substring(0, 1) == ":")
791 {
792 UserMode = UserMode.Remove(0, 1);
793 }
794 }
795
796 public void eventIrcNickChange(string prefix, string command, string parms)
797 {
798 string[] args = parms.Split(CS_SPACE, 2);
799 string UserOldNick = prefix.Split('!')[0];
800 string UserNewNick = args[0].Remove(0, 1);
801
802 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCNickChange {1}:{2}", idn, m_server, m_ircChannel);
803 BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick);
804 }
805
806 public void eventIrcKick(string prefix, string command, string parms)
807 {
808 string[] args = parms.Split(CS_SPACE, 3);
809 string UserKicker = prefix.Split('!')[0];
810 string IrcChannel = args[0];
811 string UserKicked = args[1];
812 string KickMessage = args[2];
813
814 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCKick {1}:{2}", idn, m_server, m_ircChannel);
815 BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage);
816
817 if (UserKicked == m_nick)
818 {
819 BroadcastSim(m_nick, "Hey, that was me!!!");
820 }
821
822 }
823
824 public void eventIrcQuit(string prefix, string command, string parms)
825 {
826 string IrcUser = prefix.Split('!')[0];
827 string QuitMessage = parms;
828
829 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCQuit {1}:{2}", idn, m_server, m_ircChannel);
830 BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage);
831 }
832
833 #endregion
834
835 #region Connector Watch Dog
836
837 // A single watch dog monitors extant connectors and makes sure that they
838 // are re-connected as necessary. If a connector IS connected, then it is
839 // pinged, but only if a PING period has elapsed.
840
841 protected static void WatchdogHandler(Object source, ElapsedEventArgs args)
842 {
843
844 // m_log.InfoFormat("[IRC-Watchdog] Status scan, pdk = {0}, icc = {1}", _pdk_, _icc_);
845
846 _pdk_ = (_pdk_ + 1) % PING_PERIOD; // cycle the ping trigger
847 _icc_++; // increment the inter-consecutive-connect-delay counter
848
849 lock (m_connectors)
850 foreach (IRCConnector connector in m_connectors)
851 {
852
853 // m_log.InfoFormat("[IRC-Watchdog] Scanning {0}", connector);
854
855 if (connector.Enabled)
856 {
857 if (!connector.Connected)
858 {
859 try
860 {
861 // m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel);
862 connector.Connect();
863 }
864 catch (Exception e)
865 {
866 m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message);
867 }
868 }
869 else
870 {
871
872 if (connector.m_pending)
873 {
874 if (connector.m_timeout == 0)
875 {
876 m_log.ErrorFormat("[IRC-Watchdog] Login timed-out for connector {0}, reconnecting", connector.idn);
877 connector.Reconnect();
878 }
879 else
880 connector.m_timeout--;
881 }
882
883 // Being marked connected is not enough to ping. Socket establishment can sometimes take a long
884 // time, in which case the watch dog might try to ping the server before the socket has been
885 // set up, with nasty side-effects.
886
887 else if (_pdk_ == 0)
888 {
889 try
890 {
891 connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server));
892 connector.m_writer.Flush();
893 }
894 catch (Exception e)
895 {
896 m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message);
897 m_log.Debug(e);
898 connector.Reconnect();
899 }
900 }
901
902 }
903 }
904 }
905
906 // m_log.InfoFormat("[IRC-Watchdog] Status scan completed");
907
908 }
909
910 #endregion
911
912 }
913}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Chat/RegionState.cs b/OpenSim/Region/OptionalModules/Avatar/Chat/RegionState.cs
new file mode 100644
index 0000000..5505001
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Chat/RegionState.cs
@@ -0,0 +1,452 @@
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.Text.RegularExpressions;
32using log4net;
33using Nini.Config;
34using OpenSim.Framework;
35using OpenSim.Region.Framework.Interfaces;
36using OpenSim.Region.Framework.Scenes;
37
38namespace OpenSim.Region.OptionalModules.Avatar.Chat
39{
40 // An instance of this class exists for every active region
41
42 internal class RegionState
43 {
44 private static readonly ILog m_log =
45 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
46
47 // This computation is not the real region center if the region is larger than 256.
48 // This computation isn't fixed because there is not a handle back to the region.
49 private static readonly OpenMetaverse.Vector3 CenterOfRegion = new OpenMetaverse.Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20);
50 private const int DEBUG_CHANNEL = 2147483647;
51
52 private static int _idk_ = 0;
53
54 // Runtime variables; these values are assigned when the
55 // IrcState is created and remain constant thereafter.
56
57 internal string Region = String.Empty;
58 internal string Host = String.Empty;
59 internal string LocX = String.Empty;
60 internal string LocY = String.Empty;
61 internal string IDK = String.Empty;
62
63 // System values - used only be the IRC classes themselves
64
65 internal ChannelState cs = null; // associated IRC configuration
66 internal Scene scene = null; // associated scene
67 internal IConfig config = null; // configuration file reference
68 internal bool enabled = true;
69
70 //AgentAlert
71 internal bool showAlert = false;
72 internal string alertMessage = String.Empty;
73 internal IDialogModule dialogModule = null;
74
75 // This list is used to keep track of who is here, and by
76 // implication, who is not.
77
78 internal List<IClientAPI> clients = new List<IClientAPI>();
79
80 // Setup runtime variable values
81
82 public RegionState(Scene p_scene, IConfig p_config)
83 {
84 scene = p_scene;
85 config = p_config;
86
87 Region = scene.RegionInfo.RegionName;
88 Host = scene.RegionInfo.ExternalHostName;
89 LocX = Convert.ToString(scene.RegionInfo.RegionLocX);
90 LocY = Convert.ToString(scene.RegionInfo.RegionLocY);
91 IDK = Convert.ToString(_idk_++);
92
93 showAlert = config.GetBoolean("alert_show", false);
94 string alertServerInfo = String.Empty;
95
96 if (showAlert)
97 {
98 bool showAlertServerInfo = config.GetBoolean("alert_show_serverinfo", true);
99
100 if (showAlertServerInfo)
101 alertServerInfo = String.Format("\nServer: {0}\nPort: {1}\nChannel: {2}\n\n",
102 config.GetString("server", ""), config.GetString("port", ""), config.GetString("channel", ""));
103
104 string alertPreMessage = config.GetString("alert_msg_pre", "This region is linked to Irc.");
105 string alertPostMessage = config.GetString("alert_msg_post", "Everything you say in public chat can be listened.");
106
107 alertMessage = String.Format("{0}\n{1}{2}", alertPreMessage, alertServerInfo, alertPostMessage);
108
109 dialogModule = scene.RequestModuleInterface<IDialogModule>();
110 }
111
112 // OpenChannel conditionally establishes a connection to the
113 // IRC server. The request will either succeed, or it will
114 // throw an exception.
115
116 ChannelState.OpenChannel(this, config);
117
118 // Connect channel to world events
119
120 scene.EventManager.OnChatFromWorld += OnSimChat;
121 scene.EventManager.OnChatFromClient += OnSimChat;
122 scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
123 scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
124
125 m_log.InfoFormat("[IRC-Region {0}] Initialization complete", Region);
126
127 }
128
129 // Auto cleanup when abandoned
130
131 ~RegionState()
132 {
133 if (cs != null)
134 cs.RemoveRegion(this);
135 }
136
137 // Called by PostInitialize after all regions have been created
138
139 public void Open()
140 {
141 cs.Open(this);
142 enabled = true;
143 }
144
145 // Called by IRCBridgeModule.Close immediately prior to unload
146 // of the module for this region. This happens when the region
147 // is being removed or the server is terminating. The IRC
148 // BridgeModule will remove the region from the region list
149 // when control returns.
150
151 public void Close()
152 {
153 enabled = false;
154 cs.Close(this);
155 }
156
157 // The agent has disconnected, cleanup associated resources
158
159 private void OnClientLoggedOut(IClientAPI client)
160 {
161 try
162 {
163 if (clients.Contains(client))
164 {
165 if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
166 {
167 m_log.InfoFormat("[IRC-Region {0}]: {1} has left", Region, client.Name);
168 //Check if this person is excluded from IRC
169 if (!cs.ExcludeList.Contains(client.Name.ToLower()))
170 {
171 cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", client.Name));
172 }
173 }
174 client.OnLogout -= OnClientLoggedOut;
175 client.OnConnectionClosed -= OnClientLoggedOut;
176 clients.Remove(client);
177 }
178 }
179 catch (Exception ex)
180 {
181 m_log.ErrorFormat("[IRC-Region {0}]: ClientLoggedOut exception: {1}", Region, ex.Message);
182 m_log.Debug(ex);
183 }
184 }
185
186 // This event indicates that the agent has left the building. We should treat that the same
187 // as if the agent has logged out (we don't want cross-region noise - or do we?)
188
189 private void OnMakeChildAgent(ScenePresence presence)
190 {
191
192 IClientAPI client = presence.ControllingClient;
193
194 try
195 {
196 if (clients.Contains(client))
197 {
198 if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
199 {
200 string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname);
201 m_log.DebugFormat("[IRC-Region {0}] {1} has left", Region, clientName);
202 cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", clientName));
203 }
204 client.OnLogout -= OnClientLoggedOut;
205 client.OnConnectionClosed -= OnClientLoggedOut;
206 clients.Remove(client);
207 }
208 }
209 catch (Exception ex)
210 {
211 m_log.ErrorFormat("[IRC-Region {0}]: MakeChildAgent exception: {1}", Region, ex.Message);
212 m_log.Debug(ex);
213 }
214
215 }
216
217 // An agent has entered the region (from another region). Add the client to the locally
218 // known clients list
219
220 private void OnMakeRootAgent(ScenePresence presence)
221 {
222 IClientAPI client = presence.ControllingClient;
223
224 try
225 {
226 if (!clients.Contains(client))
227 {
228 client.OnLogout += OnClientLoggedOut;
229 client.OnConnectionClosed += OnClientLoggedOut;
230 clients.Add(client);
231 if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
232 {
233 string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname);
234 m_log.DebugFormat("[IRC-Region {0}] {1} has arrived", Region, clientName);
235 //Check if this person is excluded from IRC
236 if (!cs.ExcludeList.Contains(clientName.ToLower()))
237 {
238 cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has arrived", clientName));
239 }
240 }
241 }
242
243 if (dialogModule != null && showAlert)
244 dialogModule.SendAlertToUser(client, alertMessage, true);
245 }
246 catch (Exception ex)
247 {
248 m_log.ErrorFormat("[IRC-Region {0}]: MakeRootAgent exception: {1}", Region, ex.Message);
249 m_log.Debug(ex);
250 }
251 }
252
253 // This handler detects chat events int he virtual world.
254 public void OnSimChat(Object sender, OSChatMessage msg)
255 {
256
257 // early return if this comes from the IRC forwarder
258
259 if (cs.irc.Equals(sender)) return;
260
261 // early return if nothing to forward
262
263 if (msg.Message.Length == 0) return;
264
265 // check for commands coming from avatars or in-world
266 // object (if commands are enabled)
267
268 if (cs.CommandsEnabled && msg.Channel == cs.CommandChannel)
269 {
270
271 m_log.DebugFormat("[IRC-Region {0}] command on channel {1}: {2}", Region, msg.Channel, msg.Message);
272
273 string[] messages = msg.Message.Split(' ');
274 string command = messages[0].ToLower();
275
276 try
277 {
278 switch (command)
279 {
280
281 // These commands potentially require a change in the
282 // underlying ChannelState.
283
284 case "server":
285 cs.Close(this);
286 cs = cs.UpdateServer(this, messages[1]);
287 cs.Open(this);
288 break;
289 case "port":
290 cs.Close(this);
291 cs = cs.UpdatePort(this, messages[1]);
292 cs.Open(this);
293 break;
294 case "channel":
295 cs.Close(this);
296 cs = cs.UpdateChannel(this, messages[1]);
297 cs.Open(this);
298 break;
299 case "nick":
300 cs.Close(this);
301 cs = cs.UpdateNickname(this, messages[1]);
302 cs.Open(this);
303 break;
304
305 // These may also (but are less likely) to require a
306 // change in ChannelState.
307
308 case "client-reporting":
309 cs = cs.UpdateClientReporting(this, messages[1]);
310 break;
311 case "in-channel":
312 cs = cs.UpdateRelayIn(this, messages[1]);
313 break;
314 case "out-channel":
315 cs = cs.UpdateRelayOut(this, messages[1]);
316 break;
317
318 // These are all taken to be temporary changes in state
319 // so the underlying connector remains intact. But note
320 // that with regions sharing a connector, there could
321 // be interference.
322
323 case "close":
324 enabled = false;
325 cs.Close(this);
326 break;
327
328 case "connect":
329 enabled = true;
330 cs.Open(this);
331 break;
332
333 case "reconnect":
334 enabled = true;
335 cs.Close(this);
336 cs.Open(this);
337 break;
338
339 // This one is harmless as far as we can judge from here.
340 // If it is not, then the complaints will eventually make
341 // that evident.
342
343 default:
344 m_log.DebugFormat("[IRC-Region {0}] Forwarding unrecognized command to IRC : {1}",
345 Region, msg.Message);
346 cs.irc.Send(msg.Message);
347 break;
348 }
349 }
350 catch (Exception ex)
351 {
352 m_log.WarnFormat("[IRC-Region {0}] error processing in-world command channel input: {1}",
353 Region, ex.Message);
354 m_log.Debug(ex);
355 }
356
357 return;
358
359 }
360
361 // The command channel remains enabled, even if we have otherwise disabled the IRC
362 // interface.
363
364 if (!enabled)
365 return;
366
367 // drop messages unless they are on a valid in-world
368 // channel as configured in the ChannelState
369
370 if (!cs.ValidInWorldChannels.Contains(msg.Channel))
371 {
372 m_log.DebugFormat("[IRC-Region {0}] dropping message {1} on channel {2}", Region, msg, msg.Channel);
373 return;
374 }
375
376 ScenePresence avatar = null;
377 string fromName = msg.From;
378
379 if (msg.Sender != null)
380 {
381 avatar = scene.GetScenePresence(msg.Sender.AgentId);
382 if (avatar != null) fromName = avatar.Name;
383 }
384
385 if (!cs.irc.Connected)
386 {
387 m_log.WarnFormat("[IRC-Region {0}] IRCConnector not connected: dropping message from {1}", Region, fromName);
388 return;
389 }
390
391 m_log.DebugFormat("[IRC-Region {0}] heard on channel {1} : {2}", Region, msg.Channel, msg.Message);
392
393 if (null != avatar && cs.RelayChat && (msg.Channel == 0 || msg.Channel == DEBUG_CHANNEL))
394 {
395 string txt = msg.Message;
396 if (txt.StartsWith("/me "))
397 txt = String.Format("{0} {1}", fromName, msg.Message.Substring(4));
398
399 cs.irc.PrivMsg(cs.PrivateMessageFormat, fromName, Region, txt);
400 return;
401 }
402
403 if (null == avatar && cs.RelayPrivateChannels && null != cs.AccessPassword &&
404 msg.Channel == cs.RelayChannelOut)
405 {
406 Match m = cs.AccessPasswordRegex.Match(msg.Message);
407 if (null != m)
408 {
409 m_log.DebugFormat("[IRC] relaying message from {0}: {1}", m.Groups["avatar"].ToString(),
410 m.Groups["message"].ToString());
411 cs.irc.PrivMsg(cs.PrivateMessageFormat, m.Groups["avatar"].ToString(),
412 scene.RegionInfo.RegionName, m.Groups["message"].ToString());
413 }
414 }
415 }
416
417 // This method gives the region an opportunity to interfere with
418 // message delivery. For now we just enforce the enable/disable
419 // flag.
420
421 internal void OSChat(Object irc, OSChatMessage msg)
422 {
423 if (enabled)
424 {
425 // m_log.DebugFormat("[IRC-OSCHAT] Region {0} being sent message", region.Region);
426 msg.Scene = scene;
427 scene.EventManager.TriggerOnChatBroadcast(irc, msg);
428 }
429 }
430
431 // This supports any local message traffic that might be needed in
432 // support of command processing. At present there is none.
433
434 internal void LocalChat(string msg)
435 {
436 if (enabled)
437 {
438 OSChatMessage osm = new OSChatMessage();
439 osm.From = "IRC Agent";
440 osm.Message = msg;
441 osm.Type = ChatTypeEnum.Region;
442 osm.Position = CenterOfRegion;
443 osm.Sender = null;
444 osm.SenderUUID = OpenMetaverse.UUID.Zero; // Hmph! Still?
445 osm.Channel = 0;
446 OSChat(this, osm);
447 }
448 }
449
450 }
451
452}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs
new file mode 100644
index 0000000..c48e585
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs
@@ -0,0 +1,627 @@
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;
30using System.Collections.Generic;
31using System.IO;
32using System.Net;
33using System.Net.Sockets;
34using System.Reflection;
35using System.Text;
36using System.Text.RegularExpressions;
37using System.Threading;
38using log4net;
39using Mono.Addins;
40using Nini.Config;
41using Nwc.XmlRpc;
42using OpenMetaverse;
43using OpenSim.Framework;
44using OpenSim.Framework.Servers;
45using OpenSim.Region.Framework.Interfaces;
46using OpenSim.Region.Framework.Scenes;
47using OpenSim.Region.CoreModules.Avatar.Chat;
48
49namespace OpenSim.Region.OptionalModules.Avatar.Concierge
50{
51 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "ConciergeModule")]
52 public class ConciergeModule : ChatModule, ISharedRegionModule
53 {
54 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
55
56 private const int DEBUG_CHANNEL = 2147483647;
57
58 private List<IScene> m_scenes = new List<IScene>();
59 private List<IScene> m_conciergedScenes = new List<IScene>();
60
61 private bool m_replacingChatModule = false;
62
63 private IConfig m_config;
64
65 private string m_whoami = "conferencier";
66 private Regex m_regions = null;
67 private string m_welcomes = null;
68 private int m_conciergeChannel = 42;
69 private string m_announceEntering = "{0} enters {1} (now {2} visitors in this region)";
70 private string m_announceLeaving = "{0} leaves {1} (back to {2} visitors in this region)";
71 private string m_xmlRpcPassword = String.Empty;
72 private string m_brokerURI = String.Empty;
73 private int m_brokerUpdateTimeout = 300;
74
75 internal object m_syncy = new object();
76
77 internal bool m_enabled = false;
78
79 #region ISharedRegionModule Members
80 public override void Initialise(IConfigSource config)
81 {
82 m_config = config.Configs["Concierge"];
83
84 if (null == m_config)
85 return;
86
87 if (!m_config.GetBoolean("enabled", false))
88 return;
89
90 m_enabled = true;
91
92
93 // check whether ChatModule has been disabled: if yes,
94 // then we'll "stand in"
95 try
96 {
97 if (config.Configs["Chat"] == null)
98 {
99 // if Chat module has not been configured it's
100 // enabled by default, so we are not going to
101 // replace it.
102 m_replacingChatModule = false;
103 }
104 else
105 {
106 m_replacingChatModule = !config.Configs["Chat"].GetBoolean("enabled", true);
107 }
108 }
109 catch (Exception)
110 {
111 m_replacingChatModule = false;
112 }
113
114 m_log.InfoFormat("[Concierge] {0} ChatModule", m_replacingChatModule ? "replacing" : "not replacing");
115
116 // take note of concierge channel and of identity
117 m_conciergeChannel = config.Configs["Concierge"].GetInt("concierge_channel", m_conciergeChannel);
118 m_whoami = m_config.GetString("whoami", "conferencier");
119 m_welcomes = m_config.GetString("welcomes", m_welcomes);
120 m_announceEntering = m_config.GetString("announce_entering", m_announceEntering);
121 m_announceLeaving = m_config.GetString("announce_leaving", m_announceLeaving);
122 m_xmlRpcPassword = m_config.GetString("password", m_xmlRpcPassword);
123 m_brokerURI = m_config.GetString("broker", m_brokerURI);
124 m_brokerUpdateTimeout = m_config.GetInt("broker_timeout", m_brokerUpdateTimeout);
125
126 m_log.InfoFormat("[Concierge] reporting as \"{0}\" to our users", m_whoami);
127
128 // calculate regions Regex
129 if (m_regions == null)
130 {
131 string regions = m_config.GetString("regions", String.Empty);
132 if (!String.IsNullOrEmpty(regions))
133 {
134 m_regions = new Regex(@regions, RegexOptions.Compiled | RegexOptions.IgnoreCase);
135 }
136 }
137 }
138
139
140 public override void AddRegion(Scene scene)
141 {
142 if (!m_enabled) return;
143
144 MainServer.Instance.AddXmlRPCHandler("concierge_update_welcome", XmlRpcUpdateWelcomeMethod, false);
145
146 lock (m_syncy)
147 {
148 if (!m_scenes.Contains(scene))
149 {
150 m_scenes.Add(scene);
151
152 if (m_regions == null || m_regions.IsMatch(scene.RegionInfo.RegionName))
153 m_conciergedScenes.Add(scene);
154
155 // subscribe to NewClient events
156 scene.EventManager.OnNewClient += OnNewClient;
157
158 // subscribe to *Chat events
159 scene.EventManager.OnChatFromWorld += OnChatFromWorld;
160 if (!m_replacingChatModule)
161 scene.EventManager.OnChatFromClient += OnChatFromClient;
162 scene.EventManager.OnChatBroadcast += OnChatBroadcast;
163
164 // subscribe to agent change events
165 scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
166 scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
167 }
168 }
169 m_log.InfoFormat("[Concierge]: initialized for {0}", scene.RegionInfo.RegionName);
170 }
171
172 public override void RemoveRegion(Scene scene)
173 {
174 if (!m_enabled) return;
175
176 MainServer.Instance.RemoveXmlRPCHandler("concierge_update_welcome");
177
178 lock (m_syncy)
179 {
180 // unsubscribe from NewClient events
181 scene.EventManager.OnNewClient -= OnNewClient;
182
183 // unsubscribe from *Chat events
184 scene.EventManager.OnChatFromWorld -= OnChatFromWorld;
185 if (!m_replacingChatModule)
186 scene.EventManager.OnChatFromClient -= OnChatFromClient;
187 scene.EventManager.OnChatBroadcast -= OnChatBroadcast;
188
189 // unsubscribe from agent change events
190 scene.EventManager.OnMakeRootAgent -= OnMakeRootAgent;
191 scene.EventManager.OnMakeChildAgent -= OnMakeChildAgent;
192
193 if (m_scenes.Contains(scene))
194 {
195 m_scenes.Remove(scene);
196 }
197
198 if (m_conciergedScenes.Contains(scene))
199 {
200 m_conciergedScenes.Remove(scene);
201 }
202 }
203 m_log.InfoFormat("[Concierge]: removed {0}", scene.RegionInfo.RegionName);
204 }
205
206 public override void PostInitialise()
207 {
208 }
209
210 public override void Close()
211 {
212 }
213
214 new public Type ReplaceableInterface
215 {
216 get { return null; }
217 }
218
219 public override string Name
220 {
221 get { return "ConciergeModule"; }
222 }
223 #endregion
224
225 #region ISimChat Members
226 public override void OnChatBroadcast(Object sender, OSChatMessage c)
227 {
228 if (m_replacingChatModule)
229 {
230 // distribute chat message to each and every avatar in
231 // the region
232 base.OnChatBroadcast(sender, c);
233 }
234
235 // TODO: capture logic
236 return;
237 }
238
239 public override void OnChatFromClient(Object sender, OSChatMessage c)
240 {
241 if (m_replacingChatModule)
242 {
243 // replacing ChatModule: need to redistribute
244 // ChatFromClient to interested subscribers
245 c = FixPositionOfChatMessage(c);
246
247 Scene scene = (Scene)c.Scene;
248 scene.EventManager.TriggerOnChatFromClient(sender, c);
249
250 if (m_conciergedScenes.Contains(c.Scene))
251 {
252 // when we are replacing ChatModule, we treat
253 // OnChatFromClient like OnChatBroadcast for
254 // concierged regions, effectively extending the
255 // range of chat to cover the whole
256 // region. however, we don't do this for whisper
257 // (got to have some privacy)
258 if (c.Type != ChatTypeEnum.Whisper)
259 {
260 base.OnChatBroadcast(sender, c);
261 return;
262 }
263 }
264
265 // redistribution will be done by base class
266 base.OnChatFromClient(sender, c);
267 }
268
269 // TODO: capture chat
270 return;
271 }
272
273 public override void OnChatFromWorld(Object sender, OSChatMessage c)
274 {
275 if (m_replacingChatModule)
276 {
277 if (m_conciergedScenes.Contains(c.Scene))
278 {
279 // when we are replacing ChatModule, we treat
280 // OnChatFromClient like OnChatBroadcast for
281 // concierged regions, effectively extending the
282 // range of chat to cover the whole
283 // region. however, we don't do this for whisper
284 // (got to have some privacy)
285 if (c.Type != ChatTypeEnum.Whisper)
286 {
287 base.OnChatBroadcast(sender, c);
288 return;
289 }
290 }
291
292 base.OnChatFromWorld(sender, c);
293 }
294 return;
295 }
296 #endregion
297
298
299 public override void OnNewClient(IClientAPI client)
300 {
301 client.OnLogout += OnClientLoggedOut;
302
303 if (m_replacingChatModule)
304 client.OnChatFromClient += OnChatFromClient;
305 }
306
307
308
309 public void OnClientLoggedOut(IClientAPI client)
310 {
311 client.OnLogout -= OnClientLoggedOut;
312 client.OnConnectionClosed -= OnClientLoggedOut;
313
314 if (m_conciergedScenes.Contains(client.Scene))
315 {
316 Scene scene = client.Scene as Scene;
317 m_log.DebugFormat("[Concierge]: {0} logs off from {1}", client.Name, scene.RegionInfo.RegionName);
318 AnnounceToAgentsRegion(scene, String.Format(m_announceLeaving, client.Name, scene.RegionInfo.RegionName, scene.GetRootAgentCount()));
319 UpdateBroker(scene);
320 }
321 }
322
323
324 public void OnMakeRootAgent(ScenePresence agent)
325 {
326 if (m_conciergedScenes.Contains(agent.Scene))
327 {
328 Scene scene = agent.Scene;
329 m_log.DebugFormat("[Concierge]: {0} enters {1}", agent.Name, scene.RegionInfo.RegionName);
330 WelcomeAvatar(agent, scene);
331 AnnounceToAgentsRegion(scene, String.Format(m_announceEntering, agent.Name,
332 scene.RegionInfo.RegionName, scene.GetRootAgentCount()));
333 UpdateBroker(scene);
334 }
335 }
336
337
338 public void OnMakeChildAgent(ScenePresence agent)
339 {
340 if (m_conciergedScenes.Contains(agent.Scene))
341 {
342 Scene scene = agent.Scene;
343 m_log.DebugFormat("[Concierge]: {0} leaves {1}", agent.Name, scene.RegionInfo.RegionName);
344 AnnounceToAgentsRegion(scene, String.Format(m_announceLeaving, agent.Name,
345 scene.RegionInfo.RegionName, scene.GetRootAgentCount()));
346 UpdateBroker(scene);
347 }
348 }
349
350 internal class BrokerState
351 {
352 public string Uri;
353 public string Payload;
354 public HttpWebRequest Poster;
355 public Timer Timer;
356
357 public BrokerState(string uri, string payload, HttpWebRequest poster)
358 {
359 Uri = uri;
360 Payload = payload;
361 Poster = poster;
362 }
363 }
364
365 protected void UpdateBroker(Scene scene)
366 {
367 if (String.IsNullOrEmpty(m_brokerURI))
368 return;
369
370 string uri = String.Format(m_brokerURI, scene.RegionInfo.RegionName, scene.RegionInfo.RegionID);
371
372 // create XML sniplet
373 StringBuilder list = new StringBuilder();
374 list.Append(String.Format("<avatars count=\"{0}\" region_name=\"{1}\" region_uuid=\"{2}\" timestamp=\"{3}\">\n",
375 scene.GetRootAgentCount(), scene.RegionInfo.RegionName,
376 scene.RegionInfo.RegionID,
377 DateTime.UtcNow.ToString("s")));
378
379 scene.ForEachRootScenePresence(delegate(ScenePresence sp)
380 {
381 list.Append(String.Format(" <avatar name=\"{0}\" uuid=\"{1}\" />\n", sp.Name, sp.UUID));
382 });
383
384 list.Append("</avatars>");
385 string payload = list.ToString();
386
387 // post via REST to broker
388 HttpWebRequest updatePost = WebRequest.Create(uri) as HttpWebRequest;
389 updatePost.Method = "POST";
390 updatePost.ContentType = "text/xml";
391 updatePost.ContentLength = payload.Length;
392 updatePost.UserAgent = "OpenSim.Concierge";
393
394
395 BrokerState bs = new BrokerState(uri, payload, updatePost);
396 bs.Timer = new Timer(delegate(object state)
397 {
398 BrokerState b = state as BrokerState;
399 b.Poster.Abort();
400 b.Timer.Dispose();
401 m_log.Debug("[Concierge]: async broker POST abort due to timeout");
402 }, bs, m_brokerUpdateTimeout * 1000, Timeout.Infinite);
403
404 try
405 {
406 updatePost.BeginGetRequestStream(UpdateBrokerSend, bs);
407 m_log.DebugFormat("[Concierge] async broker POST to {0} started", uri);
408 }
409 catch (WebException we)
410 {
411 m_log.ErrorFormat("[Concierge] async broker POST to {0} failed: {1}", uri, we.Status);
412 }
413 }
414
415 private void UpdateBrokerSend(IAsyncResult result)
416 {
417 BrokerState bs = null;
418 try
419 {
420 bs = result.AsyncState as BrokerState;
421 string payload = bs.Payload;
422 HttpWebRequest updatePost = bs.Poster;
423
424 using (StreamWriter payloadStream = new StreamWriter(updatePost.EndGetRequestStream(result)))
425 {
426 payloadStream.Write(payload);
427 payloadStream.Close();
428 }
429 updatePost.BeginGetResponse(UpdateBrokerDone, bs);
430 }
431 catch (WebException we)
432 {
433 m_log.DebugFormat("[Concierge]: async broker POST to {0} failed: {1}", bs.Uri, we.Status);
434 }
435 catch (Exception)
436 {
437 m_log.DebugFormat("[Concierge]: async broker POST to {0} failed", bs.Uri);
438 }
439 }
440
441 private void UpdateBrokerDone(IAsyncResult result)
442 {
443 BrokerState bs = null;
444 try
445 {
446 bs = result.AsyncState as BrokerState;
447 HttpWebRequest updatePost = bs.Poster;
448 using (HttpWebResponse response = updatePost.EndGetResponse(result) as HttpWebResponse)
449 {
450 m_log.DebugFormat("[Concierge] broker update: status {0}", response.StatusCode);
451 }
452 bs.Timer.Dispose();
453 }
454 catch (WebException we)
455 {
456 m_log.ErrorFormat("[Concierge] broker update to {0} failed with status {1}", bs.Uri, we.Status);
457 if (null != we.Response)
458 {
459 using (HttpWebResponse resp = we.Response as HttpWebResponse)
460 {
461 m_log.ErrorFormat("[Concierge] response from {0} status code: {1}", bs.Uri, resp.StatusCode);
462 m_log.ErrorFormat("[Concierge] response from {0} status desc: {1}", bs.Uri, resp.StatusDescription);
463 m_log.ErrorFormat("[Concierge] response from {0} server: {1}", bs.Uri, resp.Server);
464
465 if (resp.ContentLength > 0)
466 {
467 StreamReader content = new StreamReader(resp.GetResponseStream());
468 m_log.ErrorFormat("[Concierge] response from {0} content: {1}", bs.Uri, content.ReadToEnd());
469 content.Close();
470 }
471 }
472 }
473 }
474 }
475
476 protected void WelcomeAvatar(ScenePresence agent, Scene scene)
477 {
478 // welcome mechanics: check whether we have a welcomes
479 // directory set and wether there is a region specific
480 // welcome file there: if yes, send it to the agent
481 if (!String.IsNullOrEmpty(m_welcomes))
482 {
483 string[] welcomes = new string[] {
484 Path.Combine(m_welcomes, agent.Scene.RegionInfo.RegionName),
485 Path.Combine(m_welcomes, "DEFAULT")};
486 foreach (string welcome in welcomes)
487 {
488 if (File.Exists(welcome))
489 {
490 try
491 {
492 string[] welcomeLines = File.ReadAllLines(welcome);
493 foreach (string l in welcomeLines)
494 {
495 AnnounceToAgent(agent, String.Format(l, agent.Name, scene.RegionInfo.RegionName, m_whoami));
496 }
497 }
498 catch (IOException ioe)
499 {
500 m_log.ErrorFormat("[Concierge]: run into trouble reading welcome file {0} for region {1} for avatar {2}: {3}",
501 welcome, scene.RegionInfo.RegionName, agent.Name, ioe);
502 }
503 catch (FormatException fe)
504 {
505 m_log.ErrorFormat("[Concierge]: welcome file {0} is malformed: {1}", welcome, fe);
506 }
507 }
508 return;
509 }
510 m_log.DebugFormat("[Concierge]: no welcome message for region {0}", scene.RegionInfo.RegionName);
511 }
512 }
513
514 static private Vector3 PosOfGod = new Vector3(128, 128, 9999);
515
516 // protected void AnnounceToAgentsRegion(Scene scene, string msg)
517 // {
518 // ScenePresence agent = null;
519 // if ((client.Scene is Scene) && (client.Scene as Scene).TryGetScenePresence(client.AgentId, out agent))
520 // AnnounceToAgentsRegion(agent, msg);
521 // else
522 // m_log.DebugFormat("[Concierge]: could not find an agent for client {0}", client.Name);
523 // }
524
525 protected void AnnounceToAgentsRegion(IScene scene, string msg)
526 {
527 OSChatMessage c = new OSChatMessage();
528 c.Message = msg;
529 c.Type = ChatTypeEnum.Say;
530 c.Channel = 0;
531 c.Position = PosOfGod;
532 c.From = m_whoami;
533 c.Sender = null;
534 c.SenderUUID = UUID.Zero;
535 c.Scene = scene;
536
537 if (scene is Scene)
538 (scene as Scene).EventManager.TriggerOnChatBroadcast(this, c);
539 }
540
541 protected void AnnounceToAgent(ScenePresence agent, string msg)
542 {
543 OSChatMessage c = new OSChatMessage();
544 c.Message = msg;
545 c.Type = ChatTypeEnum.Say;
546 c.Channel = 0;
547 c.Position = PosOfGod;
548 c.From = m_whoami;
549 c.Sender = null;
550 c.SenderUUID = UUID.Zero;
551 c.Scene = agent.Scene;
552
553 agent.ControllingClient.SendChatMessage(
554 msg, (byte) ChatTypeEnum.Say, PosOfGod, m_whoami, UUID.Zero, UUID.Zero,
555 (byte)ChatSourceType.Object, (byte)ChatAudibleLevel.Fully);
556 }
557
558 private static void checkStringParameters(XmlRpcRequest request, string[] param)
559 {
560 Hashtable requestData = (Hashtable) request.Params[0];
561 foreach (string p in param)
562 {
563 if (!requestData.Contains(p))
564 throw new Exception(String.Format("missing string parameter {0}", p));
565 if (String.IsNullOrEmpty((string)requestData[p]))
566 throw new Exception(String.Format("parameter {0} is empty", p));
567 }
568 }
569
570 public XmlRpcResponse XmlRpcUpdateWelcomeMethod(XmlRpcRequest request, IPEndPoint remoteClient)
571 {
572 m_log.Info("[Concierge]: processing UpdateWelcome request");
573 XmlRpcResponse response = new XmlRpcResponse();
574 Hashtable responseData = new Hashtable();
575
576 try
577 {
578 Hashtable requestData = (Hashtable)request.Params[0];
579 checkStringParameters(request, new string[] { "password", "region", "welcome" });
580
581 // check password
582 if (!String.IsNullOrEmpty(m_xmlRpcPassword) &&
583 (string)requestData["password"] != m_xmlRpcPassword) throw new Exception("wrong password");
584
585 if (String.IsNullOrEmpty(m_welcomes))
586 throw new Exception("welcome templates are not enabled, ask your OpenSim operator to set the \"welcomes\" option in the [Concierge] section of OpenSim.ini");
587
588 string msg = (string)requestData["welcome"];
589 if (String.IsNullOrEmpty(msg))
590 throw new Exception("empty parameter \"welcome\"");
591
592 string regionName = (string)requestData["region"];
593 IScene scene = m_scenes.Find(delegate(IScene s) { return s.RegionInfo.RegionName == regionName; });
594 if (scene == null)
595 throw new Exception(String.Format("unknown region \"{0}\"", regionName));
596
597 if (!m_conciergedScenes.Contains(scene))
598 throw new Exception(String.Format("region \"{0}\" is not a concierged region.", regionName));
599
600 string welcome = Path.Combine(m_welcomes, regionName);
601 if (File.Exists(welcome))
602 {
603 m_log.InfoFormat("[Concierge]: UpdateWelcome: updating existing template \"{0}\"", welcome);
604 string welcomeBackup = String.Format("{0}~", welcome);
605 if (File.Exists(welcomeBackup))
606 File.Delete(welcomeBackup);
607 File.Move(welcome, welcomeBackup);
608 }
609 File.WriteAllText(welcome, msg);
610
611 responseData["success"] = "true";
612 response.Value = responseData;
613 }
614 catch (Exception e)
615 {
616 m_log.InfoFormat("[Concierge]: UpdateWelcome failed: {0}", e.Message);
617
618 responseData["success"] = "false";
619 responseData["error"] = e.Message;
620
621 response.Value = responseData;
622 }
623 m_log.Debug("[Concierge]: done processing UpdateWelcome request");
624 return response;
625 }
626 }
627}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeServer.py b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeServer.py
new file mode 100755
index 0000000..1c088fb
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeServer.py
@@ -0,0 +1,130 @@
1#!/usr/bin/env python
2# -*- encoding: utf-8 -*-
3#
4# Copyright (c) Contributors, http://opensimulator.org/
5# See CONTRIBUTORS.TXT for a full list of copyright holders.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12# notice, this list of conditions and the following disclaimer in the
13# documentation and/or other materials provided with the distribution.
14# * Neither the name of the OpenSim Project nor the
15# names of its contributors may be used to endorse or promote products
16# derived from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
19# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
22# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28#
29
30import logging
31import BaseHTTPServer
32import optparse
33import xml.etree.ElementTree as ET
34import xml.parsers.expat
35
36
37# enable debug level logging
38logging.basicConfig(level = logging.DEBUG,
39 format='%(asctime)s %(levelname)s %(message)s')
40
41options = None
42
43# subclassed HTTPRequestHandler
44class ConciergeHandler(BaseHTTPServer.BaseHTTPRequestHandler):
45 def logRequest(self):
46 logging.info('[ConciergeHandler] %(command)s request: %(host)s:%(port)d --- %(path)s',
47 dict(command = self.command,
48 host = self.client_address[0],
49 port = self.client_address[1],
50 path = self.path))
51
52 def logResponse(self, status):
53 logging.info('[ConciergeHandler] %(command)s returned %(status)d',
54 dict(command = self.command,
55 status = status))
56
57
58 def do_HEAD(self):
59 self.logRequest()
60
61 self.send_response(200)
62 self.send_header('Content-type', 'text/html')
63 self.end_headers()
64
65 self.logResponse(200)
66
67 def dumpXml(self, xml):
68 logging.debug('[ConciergeHandler] %s', xml.tag)
69 for attr in xml.attrib:
70 logging.debug('[ConciergeHandler] %s [%s] %s', xml.tag, attr, xml.attrib[attr])
71 for kid in xml.getchildren():
72 self.dumpXml(kid)
73
74 def do_POST(self):
75 self.logRequest()
76 hdrs = {}
77 for hdr in self.headers.headers:
78 logging.debug('[ConciergeHandler] POST: header: %s', hdr.rstrip())
79
80 length = int(self.headers.getheader('Content-Length'))
81 content = self.rfile.read(length)
82 self.rfile.close()
83
84 logging.debug('[ConciergeHandler] POST: content: %s', content)
85 try:
86 postXml = ET.fromstring(content)
87 self.dumpXml(postXml)
88 except xml.parsers.expat.ExpatError, xmlError:
89 logging.error('[ConciergeHandler] POST illformed:%s', xmlError)
90 self.send_response(500)
91 return
92
93 if not options.fail:
94 self.send_response(200)
95 self.send_header('Content-Type', 'text/html')
96 self.send_header('Content-Length', len('<success/>'))
97 self.end_headers()
98 self.logResponse(200)
99 self.wfile.write('<success/>')
100 self.wfile.close()
101 else:
102 self.send_response(500)
103 self.send_header('Content-Type', 'text/html')
104 self.send_header('Content-Length', len('<error>gotcha!</error>'))
105 self.end_headers()
106 self.wfile.write('<error>gotcha!</error>')
107 self.wfile.close()
108
109 self.logResponse(500)
110
111 def log_request(code, size):
112 pass
113
114if __name__ == '__main__':
115
116 logging.info('[ConciergeServer] Concierge Broker Test Server starting')
117
118 parser = optparse.OptionParser()
119 parser.add_option('-p', '--port', dest = 'port', help = 'port to listen on', metavar = 'PORT')
120 parser.add_option('-f', '--fail', dest = 'fail', action = 'store_true', help = 'always fail POST requests')
121
122 (options, args) = parser.parse_args()
123
124 httpServer = BaseHTTPServer.HTTPServer(('', 8080), ConciergeHandler)
125 try:
126 httpServer.serve_forever()
127 except KeyboardInterrupt:
128 logging.info('[ConciergeServer] terminating')
129
130 httpServer.server_close()
diff --git a/OpenSim/Region/OptionalModules/Avatar/Friends/FriendsCommandsModule.cs b/OpenSim/Region/OptionalModules/Avatar/Friends/FriendsCommandsModule.cs
new file mode 100644
index 0000000..4e84364
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Friends/FriendsCommandsModule.cs
@@ -0,0 +1,200 @@
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.Linq;
31using System.Reflection;
32using System.Text;
33using log4net;
34using Mono.Addins;
35using NDesk.Options;
36using Nini.Config;
37using OpenMetaverse;
38using OpenSim.Framework;
39using OpenSim.Framework.Console;
40using OpenSim.Framework.Monitoring;
41using OpenSim.Region.ClientStack.LindenUDP;
42using OpenSim.Region.CoreModules.Avatar.Friends;
43using OpenSim.Region.Framework.Interfaces;
44using OpenSim.Region.Framework.Scenes;
45using OpenSim.Services.Interfaces;
46using FriendInfo = OpenSim.Services.Interfaces.FriendInfo;
47
48namespace OpenSim.Region.OptionalModules.Avatar.Friends
49{
50 /// <summary>
51 /// A module that just holds commands for inspecting avatar appearance.
52 /// </summary>
53 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "FriendsCommandModule")]
54 public class FriendsCommandsModule : ISharedRegionModule
55 {
56// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
57
58 private Scene m_scene;
59 private IFriendsModule m_friendsModule;
60 private IUserManagement m_userManagementModule;
61 private IPresenceService m_presenceService;
62
63// private IAvatarFactoryModule m_avatarFactory;
64
65 public string Name { get { return "Appearance Information Module"; } }
66
67 public Type ReplaceableInterface { get { return null; } }
68
69 public void Initialise(IConfigSource source)
70 {
71// m_log.DebugFormat("[FRIENDS COMMAND MODULE]: INITIALIZED MODULE");
72 }
73
74 public void PostInitialise()
75 {
76// m_log.DebugFormat("[FRIENDS COMMAND MODULE]: POST INITIALIZED MODULE");
77 }
78
79 public void Close()
80 {
81// m_log.DebugFormat("[FRIENDS COMMAND MODULE]: CLOSED MODULE");
82 }
83
84 public void AddRegion(Scene scene)
85 {
86// m_log.DebugFormat("[FRIENDS COMMANDO MODULE]: REGION {0} ADDED", scene.RegionInfo.RegionName);
87 }
88
89 public void RemoveRegion(Scene scene)
90 {
91// m_log.DebugFormat("[FRIENDS COMMAND MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName);
92 }
93
94 public void RegionLoaded(Scene scene)
95 {
96// m_log.DebugFormat("[APPEARANCE INFO MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName);
97
98 if (m_scene == null)
99 m_scene = scene;
100
101 m_friendsModule = m_scene.RequestModuleInterface<IFriendsModule>();
102 m_userManagementModule = m_scene.RequestModuleInterface<IUserManagement>();
103 m_presenceService = m_scene.RequestModuleInterface<IPresenceService>();
104
105 if (m_friendsModule != null && m_userManagementModule != null && m_presenceService != null)
106 {
107 m_scene.AddCommand(
108 "Friends", this, "friends show",
109 "friends show [--cache] <first-name> <last-name>",
110 "Show the friends for the given user if they exist.\n",
111 "The --cache option will show locally cached information for that user.",
112 HandleFriendsShowCommand);
113 }
114 }
115
116 protected void HandleFriendsShowCommand(string module, string[] cmd)
117 {
118 Dictionary<string, object> options = new Dictionary<string, object>();
119 OptionSet optionSet = new OptionSet().Add("c|cache", delegate (string v) { options["cache"] = v != null; });
120
121 List<string> mainParams = optionSet.Parse(cmd);
122
123 if (mainParams.Count != 4)
124 {
125 MainConsole.Instance.OutputFormat("Usage: friends show [--cache] <first-name> <last-name>");
126 return;
127 }
128
129 string firstName = mainParams[2];
130 string lastName = mainParams[3];
131
132 UUID userId = m_userManagementModule.GetUserIdByName(firstName, lastName);
133
134// UserAccount ua
135// = m_Scenes[0].UserAccountService.GetUserAccount(m_Scenes[0].RegionInfo.ScopeID, firstName, lastName);
136
137 if (userId == UUID.Zero)
138 {
139 MainConsole.Instance.OutputFormat("No such user as {0} {1}", firstName, lastName);
140 return;
141 }
142
143 FriendInfo[] friends;
144
145 if (options.ContainsKey("cache"))
146 {
147 if (!m_friendsModule.AreFriendsCached(userId))
148 {
149 MainConsole.Instance.OutputFormat("No friends cached on this simulator for {0} {1}", firstName, lastName);
150 return;
151 }
152 else
153 {
154 friends = m_friendsModule.GetFriendsFromCache(userId);
155 }
156 }
157 else
158 {
159 // FIXME: We're forced to do this right now because IFriendsService has no region connectors. We can't
160 // just expose FriendsModule.GetFriendsFromService() because it forces an IClientAPI requirement that
161 // can't currently be changed because of HGFriendsModule code that takes the scene from the client.
162 friends = ((FriendsModule)m_friendsModule).FriendsService.GetFriends(userId);
163 }
164
165 MainConsole.Instance.OutputFormat("Friends for {0} {1} {2}:", firstName, lastName, userId);
166
167 MainConsole.Instance.OutputFormat(
168 "{0,-36} {1,-36} {2,-7} {3,7} {4,10}", "UUID", "Name", "Status", "MyFlags", "TheirFlags");
169
170 foreach (FriendInfo friend in friends)
171 {
172// MainConsole.Instance.OutputFormat(friend.PrincipalID.ToString());
173
174// string friendFirstName, friendLastName;
175//
176// UserAccount friendUa
177// = m_Scenes[0].UserAccountService.GetUserAccount(m_Scenes[0].RegionInfo.ScopeID, friend.PrincipalID);
178
179 UUID friendId;
180 string friendName;
181 string onlineText;
182
183 if (UUID.TryParse(friend.Friend, out friendId))
184 friendName = m_userManagementModule.GetUserName(friendId);
185 else
186 friendName = friend.Friend;
187
188 OpenSim.Services.Interfaces.PresenceInfo[] pi = m_presenceService.GetAgents(new string[] { friend.Friend });
189 if (pi.Length > 0)
190 onlineText = "online";
191 else
192 onlineText = "offline";
193
194 MainConsole.Instance.OutputFormat(
195 "{0,-36} {1,-36} {2,-7} {3,-7} {4,-10}",
196 friend.Friend, friendName, onlineText, friend.MyFlags, friend.TheirFlags);
197 }
198 }
199 }
200} \ No newline at end of file
diff --git a/OpenSim/Region/OptionalModules/Avatar/SitStand/SitStandCommandsModule.cs b/OpenSim/Region/OptionalModules/Avatar/SitStand/SitStandCommandsModule.cs
new file mode 100644
index 0000000..5a6b284
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/SitStand/SitStandCommandsModule.cs
@@ -0,0 +1,220 @@
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.Linq;
31using System.Reflection;
32using System.Text;
33using System.Text.RegularExpressions;
34using log4net;
35using Mono.Addins;
36using NDesk.Options;
37using Nini.Config;
38using OpenMetaverse;
39using OpenSim.Framework;
40using OpenSim.Framework.Console;
41using OpenSim.Region.Framework.Interfaces;
42using OpenSim.Region.Framework.Scenes;
43
44namespace OpenSim.Region.OptionalModules.Avatar.SitStand
45{
46 /// <summary>
47 /// A module that just holds commands for changing avatar sitting and standing states.
48 /// </summary>
49 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AnimationsCommandModule")]
50 public class SitStandCommandModule : INonSharedRegionModule
51 {
52// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
53
54 private Scene m_scene;
55
56 public string Name { get { return "SitStand Command Module"; } }
57
58 public Type ReplaceableInterface { get { return null; } }
59
60 public void Initialise(IConfigSource source)
61 {
62// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: INITIALIZED MODULE");
63 }
64
65 public void PostInitialise()
66 {
67// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: POST INITIALIZED MODULE");
68 }
69
70 public void Close()
71 {
72// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: CLOSED MODULE");
73 }
74
75 public void AddRegion(Scene scene)
76 {
77// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: REGION {0} ADDED", scene.RegionInfo.RegionName);
78 }
79
80 public void RemoveRegion(Scene scene)
81 {
82// m_log.DebugFormat("[ATTACHMENTS COMMAND MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName);
83 }
84
85 public void RegionLoaded(Scene scene)
86 {
87// m_log.DebugFormat("[ANIMATIONS COMMAND MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName);
88
89 m_scene = scene;
90
91 scene.AddCommand(
92 "Users", this, "sit user name",
93 "sit user name [--regex] <first-name> <last-name>",
94 "Sit the named user on an unoccupied object with a sit target.",
95 "If there are no such objects then nothing happens.\n"
96 + "If --regex is specified then the names are treated as regular expressions.",
97 HandleSitUserNameCommand);
98
99 scene.AddCommand(
100 "Users", this, "stand user name",
101 "stand user name [--regex] <first-name> <last-name>",
102 "Stand the named user.",
103 "If --regex is specified then the names are treated as regular expressions.",
104 HandleStandUserNameCommand);
105 }
106
107 private void HandleSitUserNameCommand(string module, string[] cmd)
108 {
109 if (MainConsole.Instance.ConsoleScene != m_scene && MainConsole.Instance.ConsoleScene != null)
110 return;
111
112 if (cmd.Length < 5)
113 {
114 MainConsole.Instance.Output("Usage: sit user name [--regex] <first-name> <last-name>");
115 return;
116 }
117
118 List<ScenePresence> scenePresences = GetScenePresences(cmd);
119
120 foreach (ScenePresence sp in scenePresences)
121 {
122 if (sp.SitGround || sp.IsSatOnObject)
123 continue;
124
125 SceneObjectPart sitPart = null;
126 List<SceneObjectGroup> sceneObjects = m_scene.GetSceneObjectGroups();
127
128 foreach (SceneObjectGroup sceneObject in sceneObjects)
129 {
130 if (sceneObject.IsAttachment)
131 continue;
132
133 foreach (SceneObjectPart part in sceneObject.Parts)
134 {
135 if (part.IsSitTargetSet && part.SitTargetAvatar == UUID.Zero)
136 {
137 sitPart = part;
138 break;
139 }
140 }
141 }
142
143 if (sitPart != null)
144 {
145 MainConsole.Instance.OutputFormat(
146 "Sitting {0} on {1} {2} in {3}",
147 sp.Name, sitPart.ParentGroup.Name, sitPart.ParentGroup.UUID, m_scene.Name);
148
149 sp.HandleAgentRequestSit(sp.ControllingClient, sp.UUID, sitPart.UUID, Vector3.Zero);
150 sp.HandleAgentSit(sp.ControllingClient, sp.UUID);
151 }
152 else
153 {
154 MainConsole.Instance.OutputFormat(
155 "Could not find any unoccupied set seat on which to sit {0} in {1}. Aborting",
156 sp.Name, m_scene.Name);
157
158 break;
159 }
160 }
161 }
162
163 private void HandleStandUserNameCommand(string module, string[] cmd)
164 {
165 if (MainConsole.Instance.ConsoleScene != m_scene && MainConsole.Instance.ConsoleScene != null)
166 return;
167
168 if (cmd.Length < 5)
169 {
170 MainConsole.Instance.Output("Usage: stand user name [--regex] <first-name> <last-name>");
171 return;
172 }
173
174 List<ScenePresence> scenePresences = GetScenePresences(cmd);
175
176 foreach (ScenePresence sp in scenePresences)
177 {
178 if (sp.SitGround || sp.IsSatOnObject)
179 {
180 MainConsole.Instance.OutputFormat("Standing {0} in {1}", sp.Name, m_scene.Name);
181 sp.StandUp();
182 }
183 }
184 }
185
186 private List<ScenePresence> GetScenePresences(string[] cmdParams)
187 {
188 bool useRegex = false;
189 OptionSet options = new OptionSet().Add("regex", v=> useRegex = v != null );
190
191 List<string> mainParams = options.Parse(cmdParams);
192
193 string firstName = mainParams[3];
194 string lastName = mainParams[4];
195
196 List<ScenePresence> scenePresencesMatched = new List<ScenePresence>();
197
198 if (useRegex)
199 {
200 Regex nameRegex = new Regex(string.Format("{0} {1}", firstName, lastName));
201 List<ScenePresence> scenePresences = m_scene.GetScenePresences();
202
203 foreach (ScenePresence sp in scenePresences)
204 {
205 if (!sp.IsChildAgent && nameRegex.IsMatch(sp.Name))
206 scenePresencesMatched.Add(sp);
207 }
208 }
209 else
210 {
211 ScenePresence sp = m_scene.GetScenePresence(firstName, lastName);
212
213 if (sp != null && !sp.IsChildAgent)
214 scenePresencesMatched.Add(sp);
215 }
216
217 return scenePresencesMatched;
218 }
219 }
220} \ No newline at end of file
diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs
new file mode 100644
index 0000000..45af212
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Voice/FreeSwitchVoice/FreeSwitchVoiceModule.cs
@@ -0,0 +1,899 @@
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.IO;
30using System.Net;
31using System.Net.Security;
32using System.Web;
33using System.Security.Cryptography.X509Certificates;
34using System.Text;
35using System.Xml;
36using System.Collections;
37using System.Collections.Generic;
38using System.Reflection;
39using OpenMetaverse;
40using OpenMetaverse.StructuredData;
41using log4net;
42using Nini.Config;
43using Nwc.XmlRpc;
44using OpenSim.Framework;
45using Mono.Addins;
46
47using OpenSim.Framework.Capabilities;
48using OpenSim.Framework.Servers;
49using OpenSim.Framework.Servers.HttpServer;
50using OpenSim.Region.Framework.Interfaces;
51using OpenSim.Region.Framework.Scenes;
52using Caps = OpenSim.Framework.Capabilities.Caps;
53using System.Text.RegularExpressions;
54using OpenSim.Server.Base;
55using OpenSim.Services.Interfaces;
56using OSDMap = OpenMetaverse.StructuredData.OSDMap;
57
58namespace OpenSim.Region.OptionalModules.Avatar.Voice.FreeSwitchVoice
59{
60 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "FreeSwitchVoiceModule")]
61 public class FreeSwitchVoiceModule : ISharedRegionModule, IVoiceModule
62 {
63 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
64
65 // Capability string prefixes
66 private static readonly string m_parcelVoiceInfoRequestPath = "0207/";
67 private static readonly string m_provisionVoiceAccountRequestPath = "0208/";
68 //private static readonly string m_chatSessionRequestPath = "0209/";
69
70 // Control info
71 private static bool m_Enabled = false;
72
73 // FreeSwitch server is going to contact us and ask us all
74 // sorts of things.
75
76 // SLVoice client will do a GET on this prefix
77 private static string m_freeSwitchAPIPrefix;
78
79 // We need to return some information to SLVoice
80 // figured those out via curl
81 // http://vd1.vivox.com/api2/viv_get_prelogin.php
82 //
83 // need to figure out whether we do need to return ALL of
84 // these...
85 private static string m_freeSwitchRealm;
86 private static string m_freeSwitchSIPProxy;
87 private static bool m_freeSwitchAttemptUseSTUN;
88 private static string m_freeSwitchEchoServer;
89 private static int m_freeSwitchEchoPort;
90 private static string m_freeSwitchDefaultWellKnownIP;
91 private static int m_freeSwitchDefaultTimeout;
92 private static string m_freeSwitchUrlResetPassword;
93 private uint m_freeSwitchServicePort;
94 private string m_openSimWellKnownHTTPAddress;
95// private string m_freeSwitchContext;
96
97 private readonly Dictionary<string, string> m_UUIDName = new Dictionary<string, string>();
98 private Dictionary<string, string> m_ParcelAddress = new Dictionary<string, string>();
99
100 private IConfig m_Config;
101
102 private IFreeswitchService m_FreeswitchService;
103
104 public void Initialise(IConfigSource config)
105 {
106 m_Config = config.Configs["FreeSwitchVoice"];
107
108 if (m_Config == null)
109 return;
110
111 if (!m_Config.GetBoolean("Enabled", false))
112 return;
113
114 try
115 {
116 string serviceDll = m_Config.GetString("LocalServiceModule",
117 String.Empty);
118
119 if (serviceDll == String.Empty)
120 {
121 m_log.Error("[FreeSwitchVoice]: No LocalServiceModule named in section FreeSwitchVoice. Not starting.");
122 return;
123 }
124
125 Object[] args = new Object[] { config };
126 m_FreeswitchService = ServerUtils.LoadPlugin<IFreeswitchService>(serviceDll, args);
127
128 string jsonConfig = m_FreeswitchService.GetJsonConfig();
129 //m_log.Debug("[FreeSwitchVoice]: Configuration string: " + jsonConfig);
130 OSDMap map = (OSDMap)OSDParser.DeserializeJson(jsonConfig);
131
132 m_freeSwitchAPIPrefix = map["APIPrefix"].AsString();
133 m_freeSwitchRealm = map["Realm"].AsString();
134 m_freeSwitchSIPProxy = map["SIPProxy"].AsString();
135 m_freeSwitchAttemptUseSTUN = map["AttemptUseSTUN"].AsBoolean();
136 m_freeSwitchEchoServer = map["EchoServer"].AsString();
137 m_freeSwitchEchoPort = map["EchoPort"].AsInteger();
138 m_freeSwitchDefaultWellKnownIP = map["DefaultWellKnownIP"].AsString();
139 m_freeSwitchDefaultTimeout = map["DefaultTimeout"].AsInteger();
140 m_freeSwitchUrlResetPassword = String.Empty;
141// m_freeSwitchContext = map["Context"].AsString();
142
143 if (String.IsNullOrEmpty(m_freeSwitchRealm) ||
144 String.IsNullOrEmpty(m_freeSwitchAPIPrefix))
145 {
146 m_log.Error("[FreeSwitchVoice]: Freeswitch service mis-configured. Not starting.");
147 return;
148 }
149
150 // set up http request handlers for
151 // - prelogin: viv_get_prelogin.php
152 // - signin: viv_signin.php
153 // - buddies: viv_buddy.php
154 // - ???: viv_watcher.php
155 // - signout: viv_signout.php
156 MainServer.Instance.AddHTTPHandler(String.Format("{0}/viv_get_prelogin.php", m_freeSwitchAPIPrefix),
157 FreeSwitchSLVoiceGetPreloginHTTPHandler);
158
159 MainServer.Instance.AddHTTPHandler(String.Format("{0}/freeswitch-config", m_freeSwitchAPIPrefix), FreeSwitchConfigHTTPHandler);
160
161 // RestStreamHandler h = new
162 // RestStreamHandler("GET",
163 // String.Format("{0}/viv_get_prelogin.php", m_freeSwitchAPIPrefix), FreeSwitchSLVoiceGetPreloginHTTPHandler);
164 // MainServer.Instance.AddStreamHandler(h);
165
166 MainServer.Instance.AddHTTPHandler(String.Format("{0}/viv_signin.php", m_freeSwitchAPIPrefix),
167 FreeSwitchSLVoiceSigninHTTPHandler);
168
169 MainServer.Instance.AddHTTPHandler(String.Format("{0}/viv_buddy.php", m_freeSwitchAPIPrefix),
170 FreeSwitchSLVoiceBuddyHTTPHandler);
171
172 MainServer.Instance.AddHTTPHandler(String.Format("{0}/viv_watcher.php", m_freeSwitchAPIPrefix),
173 FreeSwitchSLVoiceWatcherHTTPHandler);
174
175 m_log.InfoFormat("[FreeSwitchVoice]: using FreeSwitch server {0}", m_freeSwitchRealm);
176
177 m_Enabled = true;
178
179 m_log.Info("[FreeSwitchVoice]: plugin enabled");
180 }
181 catch (Exception e)
182 {
183 m_log.ErrorFormat("[FreeSwitchVoice]: plugin initialization failed: {0} {1}", e.Message, e.StackTrace);
184 return;
185 }
186
187 // This here is a region module trying to make a global setting.
188 // Not really a good idea but it's Windows only, so I can't test.
189 try
190 {
191 ServicePointManager.ServerCertificateValidationCallback += CustomCertificateValidation;
192 }
193 catch (NotImplementedException)
194 {
195 try
196 {
197#pragma warning disable 0612, 0618
198 // Mono does not implement the ServicePointManager.ServerCertificateValidationCallback yet! Don't remove this!
199 ServicePointManager.CertificatePolicy = new MonoCert();
200#pragma warning restore 0612, 0618
201 }
202 catch (Exception)
203 {
204 // COmmented multiline spam log message
205 //m_log.Error("[FreeSwitchVoice]: Certificate validation handler change not supported. You may get ssl certificate validation errors teleporting from your region to some SSL regions.");
206 }
207 }
208 }
209
210 public void PostInitialise()
211 {
212 }
213
214 public void AddRegion(Scene scene)
215 {
216 // We generate these like this: The region's external host name
217 // as defined in Regions.ini is a good address to use. It's a
218 // dotted quad (or should be!) and it can reach this host from
219 // a client. The port is grabbed from the region's HTTP server.
220 m_openSimWellKnownHTTPAddress = scene.RegionInfo.ExternalHostName;
221 m_freeSwitchServicePort = MainServer.Instance.Port;
222
223 if (m_Enabled)
224 {
225 // we need to capture scene in an anonymous method
226 // here as we need it later in the callbacks
227 scene.EventManager.OnRegisterCaps += delegate(UUID agentID, Caps caps)
228 {
229 OnRegisterCaps(scene, agentID, caps);
230 };
231 }
232 }
233
234 public void RemoveRegion(Scene scene)
235 {
236 }
237
238 public void RegionLoaded(Scene scene)
239 {
240 if (m_Enabled)
241 {
242 m_log.Info("[FreeSwitchVoice]: registering IVoiceModule with the scene");
243
244 // register the voice interface for this module, so the script engine can call us
245 scene.RegisterModuleInterface<IVoiceModule>(this);
246 }
247 }
248
249 public void Close()
250 {
251 }
252
253 public string Name
254 {
255 get { return "FreeSwitchVoiceModule"; }
256 }
257
258 public Type ReplaceableInterface
259 {
260 get { return null; }
261 }
262
263 // <summary>
264 // implementation of IVoiceModule, called by osSetParcelSIPAddress script function
265 // </summary>
266 public void setLandSIPAddress(string SIPAddress,UUID GlobalID)
267 {
268 m_log.DebugFormat("[FreeSwitchVoice]: setLandSIPAddress parcel id {0}: setting sip address {1}",
269 GlobalID, SIPAddress);
270
271 lock (m_ParcelAddress)
272 {
273 if (m_ParcelAddress.ContainsKey(GlobalID.ToString()))
274 {
275 m_ParcelAddress[GlobalID.ToString()] = SIPAddress;
276 }
277 else
278 {
279 m_ParcelAddress.Add(GlobalID.ToString(), SIPAddress);
280 }
281 }
282 }
283
284 // <summary>
285 // OnRegisterCaps is invoked via the scene.EventManager
286 // everytime OpenSim hands out capabilities to a client
287 // (login, region crossing). We contribute two capabilities to
288 // the set of capabilities handed back to the client:
289 // ProvisionVoiceAccountRequest and ParcelVoiceInfoRequest.
290 //
291 // ProvisionVoiceAccountRequest allows the client to obtain
292 // the voice account credentials for the avatar it is
293 // controlling (e.g., user name, password, etc).
294 //
295 // ParcelVoiceInfoRequest is invoked whenever the client
296 // changes from one region or parcel to another.
297 //
298 // Note that OnRegisterCaps is called here via a closure
299 // delegate containing the scene of the respective region (see
300 // Initialise()).
301 // </summary>
302 public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps)
303 {
304 m_log.DebugFormat(
305 "[FreeSwitchVoice]: OnRegisterCaps() called with agentID {0} caps {1} in scene {2}",
306 agentID, caps, scene.RegionInfo.RegionName);
307
308 string capsBase = "/CAPS/" + caps.CapsObjectPath;
309 caps.RegisterHandler(
310 "ProvisionVoiceAccountRequest",
311 new RestStreamHandler(
312 "POST",
313 capsBase + m_provisionVoiceAccountRequestPath,
314 (request, path, param, httpRequest, httpResponse)
315 => ProvisionVoiceAccountRequest(scene, request, path, param, agentID, caps),
316 "ProvisionVoiceAccountRequest",
317 agentID.ToString()));
318
319 caps.RegisterHandler(
320 "ParcelVoiceInfoRequest",
321 new RestStreamHandler(
322 "POST",
323 capsBase + m_parcelVoiceInfoRequestPath,
324 (request, path, param, httpRequest, httpResponse)
325 => ParcelVoiceInfoRequest(scene, request, path, param, agentID, caps),
326 "ParcelVoiceInfoRequest",
327 agentID.ToString()));
328
329 //caps.RegisterHandler(
330 // "ChatSessionRequest",
331 // new RestStreamHandler(
332 // "POST",
333 // capsBase + m_chatSessionRequestPath,
334 // (request, path, param, httpRequest, httpResponse)
335 // => ChatSessionRequest(scene, request, path, param, agentID, caps),
336 // "ChatSessionRequest",
337 // agentID.ToString()));
338 }
339
340 /// <summary>
341 /// Callback for a client request for Voice Account Details
342 /// </summary>
343 /// <param name="scene">current scene object of the client</param>
344 /// <param name="request"></param>
345 /// <param name="path"></param>
346 /// <param name="param"></param>
347 /// <param name="agentID"></param>
348 /// <param name="caps"></param>
349 /// <returns></returns>
350 public string ProvisionVoiceAccountRequest(Scene scene, string request, string path, string param,
351 UUID agentID, Caps caps)
352 {
353 m_log.DebugFormat(
354 "[FreeSwitchVoice][PROVISIONVOICE]: ProvisionVoiceAccountRequest() request: {0}, path: {1}, param: {2}", request, path, param);
355
356 ScenePresence avatar = scene.GetScenePresence(agentID);
357 if (avatar == null)
358 {
359 System.Threading.Thread.Sleep(2000);
360 avatar = scene.GetScenePresence(agentID);
361
362 if (avatar == null)
363 return "<llsd>undef</llsd>";
364 }
365 string avatarName = avatar.Name;
366
367 try
368 {
369 //XmlElement resp;
370 string agentname = "x" + Convert.ToBase64String(agentID.GetBytes());
371 string password = "1234";//temp hack//new UUID(Guid.NewGuid()).ToString().Replace('-','Z').Substring(0,16);
372
373 // XXX: we need to cache the voice credentials, as
374 // FreeSwitch is later going to come and ask us for
375 // those
376 agentname = agentname.Replace('+', '-').Replace('/', '_');
377
378 lock (m_UUIDName)
379 {
380 if (m_UUIDName.ContainsKey(agentname))
381 {
382 m_UUIDName[agentname] = avatarName;
383 }
384 else
385 {
386 m_UUIDName.Add(agentname, avatarName);
387 }
388 }
389
390 // LLSDVoiceAccountResponse voiceAccountResponse =
391 // new LLSDVoiceAccountResponse(agentname, password, m_freeSwitchRealm, "http://etsvc02.hursley.ibm.com/api");
392 LLSDVoiceAccountResponse voiceAccountResponse =
393 new LLSDVoiceAccountResponse(agentname, password, m_freeSwitchRealm,
394 String.Format("http://{0}:{1}{2}/", m_openSimWellKnownHTTPAddress,
395 m_freeSwitchServicePort, m_freeSwitchAPIPrefix));
396
397 string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse);
398
399// m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1}", avatarName, r);
400
401 return r;
402 }
403 catch (Exception e)
404 {
405 m_log.ErrorFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1}, retry later", avatarName, e.Message);
406 m_log.DebugFormat("[FreeSwitchVoice][PROVISIONVOICE]: avatar \"{0}\": {1} failed", avatarName, e.ToString());
407
408 return "<llsd>undef</llsd>";
409 }
410 }
411
412 /// <summary>
413 /// Callback for a client request for ParcelVoiceInfo
414 /// </summary>
415 /// <param name="scene">current scene object of the client</param>
416 /// <param name="request"></param>
417 /// <param name="path"></param>
418 /// <param name="param"></param>
419 /// <param name="agentID"></param>
420 /// <param name="caps"></param>
421 /// <returns></returns>
422 public string ParcelVoiceInfoRequest(Scene scene, string request, string path, string param,
423 UUID agentID, Caps caps)
424 {
425 m_log.DebugFormat(
426 "[FreeSwitchVoice][PARCELVOICE]: ParcelVoiceInfoRequest() on {0} for {1}",
427 scene.RegionInfo.RegionName, agentID);
428
429 ScenePresence avatar = scene.GetScenePresence(agentID);
430 string avatarName = avatar.Name;
431
432 // - check whether we have a region channel in our cache
433 // - if not:
434 // create it and cache it
435 // - send it to the client
436 // - send channel_uri: as "sip:regionID@m_sipDomain"
437 try
438 {
439 LLSDParcelVoiceInfoResponse parcelVoiceInfo;
440 string channelUri;
441
442 if (null == scene.LandChannel)
443 throw new Exception(String.Format("region \"{0}\": avatar \"{1}\": land data not yet available",
444 scene.RegionInfo.RegionName, avatarName));
445
446 // get channel_uri: check first whether estate
447 // settings allow voice, then whether parcel allows
448 // voice, if all do retrieve or obtain the parcel
449 // voice channel
450 LandData land = scene.GetLandData(avatar.AbsolutePosition);
451
452 //m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": request: {4}, path: {5}, param: {6}",
453 // scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, request, path, param);
454
455 // TODO: EstateSettings don't seem to get propagated...
456 // if (!scene.RegionInfo.EstateSettings.AllowVoice)
457 // {
458 // m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": voice not enabled in estate settings",
459 // scene.RegionInfo.RegionName);
460 // channel_uri = String.Empty;
461 // }
462 // else
463
464 if ((land.Flags & (uint)ParcelFlags.AllowVoiceChat) == 0)
465 {
466// m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": voice not enabled for parcel",
467// scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName);
468 channelUri = String.Empty;
469 }
470 else
471 {
472 channelUri = ChannelUri(scene, land);
473 }
474
475 // fill in our response to the client
476 Hashtable creds = new Hashtable();
477 creds["channel_uri"] = channelUri;
478
479 parcelVoiceInfo = new LLSDParcelVoiceInfoResponse(scene.RegionInfo.RegionName, land.LocalID, creds);
480 string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo);
481
482// m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": {4}",
483// scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, r);
484 return r;
485 }
486 catch (Exception e)
487 {
488 m_log.ErrorFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2}, retry later",
489 scene.RegionInfo.RegionName, avatarName, e.Message);
490 m_log.DebugFormat("[FreeSwitchVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2} failed",
491 scene.RegionInfo.RegionName, avatarName, e.ToString());
492
493 return "<llsd>undef</llsd>";
494 }
495 }
496
497 /// <summary>
498 /// Callback for a client request for ChatSessionRequest
499 /// </summary>
500 /// <param name="scene">current scene object of the client</param>
501 /// <param name="request"></param>
502 /// <param name="path"></param>
503 /// <param name="param"></param>
504 /// <param name="agentID"></param>
505 /// <param name="caps"></param>
506 /// <returns></returns>
507 public string ChatSessionRequest(Scene scene, string request, string path, string param,
508 UUID agentID, Caps caps)
509 {
510 ScenePresence avatar = scene.GetScenePresence(agentID);
511 string avatarName = avatar.Name;
512
513 m_log.DebugFormat("[FreeSwitchVoice][CHATSESSION]: avatar \"{0}\": request: {1}, path: {2}, param: {3}",
514 avatarName, request, path, param);
515
516 return "<llsd>true</llsd>";
517 }
518
519 public Hashtable ForwardProxyRequest(Hashtable request)
520 {
521 m_log.Debug("[PROXYING]: -------------------------------proxying request");
522 Hashtable response = new Hashtable();
523 response["content_type"] = "text/xml";
524 response["str_response_string"] = "";
525 response["int_response_code"] = 200;
526
527 string forwardaddress = "https://www.bhr.vivox.com/api2/";
528 string body = (string)request["body"];
529 string method = (string) request["http-method"];
530 string contenttype = (string) request["content-type"];
531 string uri = (string) request["uri"];
532 uri = uri.Replace("/api/", "");
533 forwardaddress += uri;
534
535
536 string fwdresponsestr = "";
537 int fwdresponsecode = 200;
538 string fwdresponsecontenttype = "text/xml";
539
540 HttpWebRequest forwardreq = (HttpWebRequest)WebRequest.Create(forwardaddress);
541 forwardreq.Method = method;
542 forwardreq.ContentType = contenttype;
543 forwardreq.KeepAlive = false;
544
545 if (method == "POST")
546 {
547 byte[] contentreq = Util.UTF8.GetBytes(body);
548 forwardreq.ContentLength = contentreq.Length;
549 Stream reqStream = forwardreq.GetRequestStream();
550 reqStream.Write(contentreq, 0, contentreq.Length);
551 reqStream.Close();
552 }
553
554 using (HttpWebResponse fwdrsp = (HttpWebResponse)forwardreq.GetResponse())
555 {
556 Encoding encoding = Util.UTF8;
557
558 using (Stream s = fwdrsp.GetResponseStream())
559 {
560 using (StreamReader fwdresponsestream = new StreamReader(s))
561 {
562 fwdresponsestr = fwdresponsestream.ReadToEnd();
563 fwdresponsecontenttype = fwdrsp.ContentType;
564 fwdresponsecode = (int)fwdrsp.StatusCode;
565 }
566 }
567 }
568
569 response["content_type"] = fwdresponsecontenttype;
570 response["str_response_string"] = fwdresponsestr;
571 response["int_response_code"] = fwdresponsecode;
572
573 return response;
574 }
575
576 public Hashtable FreeSwitchSLVoiceGetPreloginHTTPHandler(Hashtable request)
577 {
578 m_log.Debug("[FreeSwitchVoice]: FreeSwitchSLVoiceGetPreloginHTTPHandler called");
579
580 Hashtable response = new Hashtable();
581 response["content_type"] = "text/xml";
582 response["keepalive"] = false;
583
584 response["str_response_string"] = String.Format(
585 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
586 "<VCConfiguration>\r\n"+
587 "<DefaultRealm>{0}</DefaultRealm>\r\n" +
588 "<DefaultSIPProxy>{1}</DefaultSIPProxy>\r\n"+
589 "<DefaultAttemptUseSTUN>{2}</DefaultAttemptUseSTUN>\r\n"+
590 "<DefaultEchoServer>{3}</DefaultEchoServer>\r\n"+
591 "<DefaultEchoPort>{4}</DefaultEchoPort>\r\n"+
592 "<DefaultWellKnownIP>{5}</DefaultWellKnownIP>\r\n"+
593 "<DefaultTimeout>{6}</DefaultTimeout>\r\n"+
594 "<UrlResetPassword>{7}</UrlResetPassword>\r\n"+
595 "<UrlPrivacyNotice>{8}</UrlPrivacyNotice>\r\n"+
596 "<UrlEulaNotice/>\r\n"+
597 "<App.NoBottomLogo>false</App.NoBottomLogo>\r\n"+
598 "</VCConfiguration>",
599 m_freeSwitchRealm, m_freeSwitchSIPProxy, m_freeSwitchAttemptUseSTUN,
600 m_freeSwitchEchoServer, m_freeSwitchEchoPort,
601 m_freeSwitchDefaultWellKnownIP, m_freeSwitchDefaultTimeout,
602 m_freeSwitchUrlResetPassword, "");
603
604 response["int_response_code"] = 200;
605
606 //m_log.DebugFormat("[FreeSwitchVoice] FreeSwitchSLVoiceGetPreloginHTTPHandler return {0}",response["str_response_string"]);
607 return response;
608 }
609
610 public Hashtable FreeSwitchSLVoiceBuddyHTTPHandler(Hashtable request)
611 {
612 m_log.Debug("[FreeSwitchVoice]: FreeSwitchSLVoiceBuddyHTTPHandler called");
613
614 Hashtable response = new Hashtable();
615 response["int_response_code"] = 200;
616 response["str_response_string"] = string.Empty;
617 response["content-type"] = "text/xml";
618
619 Hashtable requestBody = ParseRequestBody((string)request["body"]);
620
621 if (!requestBody.ContainsKey("auth_token"))
622 return response;
623
624 string auth_token = (string)requestBody["auth_token"];
625 //string[] auth_tokenvals = auth_token.Split(':');
626 //string username = auth_tokenvals[0];
627 int strcount = 0;
628
629 string[] ids = new string[strcount];
630
631 int iter = -1;
632 lock (m_UUIDName)
633 {
634 strcount = m_UUIDName.Count;
635 ids = new string[strcount];
636 foreach (string s in m_UUIDName.Keys)
637 {
638 iter++;
639 ids[iter] = s;
640 }
641 }
642 StringBuilder resp = new StringBuilder();
643 resp.Append("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?><response xmlns=\"http://www.vivox.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"/xsd/buddy_list.xsd\">");
644
645 resp.Append(string.Format(@"<level0>
646 <status>OK</status>
647 <cookie_name>lib_session</cookie_name>
648 <cookie>{0}</cookie>
649 <auth_token>{0}</auth_token>
650 <body>
651 <buddies>",auth_token));
652 /*
653 <cookie_name>lib_session</cookie_name>
654 <cookie>{0}:{1}:9303959503950::</cookie>
655 <auth_token>{0}:{1}:9303959503950::</auth_token>
656 */
657 for (int i=0;i<ids.Length;i++)
658 {
659 DateTime currenttime = DateTime.Now;
660 string dt = currenttime.ToString("yyyy-MM-dd HH:mm:ss.0zz");
661 resp.Append(
662 string.Format(@"<level3>
663 <bdy_id>{1}</bdy_id>
664 <bdy_data></bdy_data>
665 <bdy_uri>sip:{0}@{2}</bdy_uri>
666 <bdy_nickname>{0}</bdy_nickname>
667 <bdy_username>{0}</bdy_username>
668 <bdy_domain>{2}</bdy_domain>
669 <bdy_status>A</bdy_status>
670 <modified_ts>{3}</modified_ts>
671 <b2g_group_id></b2g_group_id>
672 </level3>", ids[i], i ,m_freeSwitchRealm, dt));
673 }
674
675 resp.Append("</buddies><groups></groups></body></level0></response>");
676
677 response["str_response_string"] = resp.ToString();
678// Regex normalizeEndLines = new Regex(@"(\r\n|\n)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.Multiline);
679//
680// m_log.DebugFormat(
681// "[FREESWITCH]: FreeSwitchSLVoiceBuddyHTTPHandler() response {0}",
682// normalizeEndLines.Replace((string)response["str_response_string"],""));
683
684 return response;
685 }
686
687 public Hashtable FreeSwitchSLVoiceWatcherHTTPHandler(Hashtable request)
688 {
689 m_log.Debug("[FreeSwitchVoice]: FreeSwitchSLVoiceWatcherHTTPHandler called");
690
691 Hashtable response = new Hashtable();
692 response["int_response_code"] = 200;
693 response["content-type"] = "text/xml";
694
695 Hashtable requestBody = ParseRequestBody((string)request["body"]);
696
697 string auth_token = (string)requestBody["auth_token"];
698 //string[] auth_tokenvals = auth_token.Split(':');
699 //string username = auth_tokenvals[0];
700
701 StringBuilder resp = new StringBuilder();
702 resp.Append("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?><response xmlns=\"http://www.vivox.com\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation= \"/xsd/buddy_list.xsd\">");
703
704 // FIXME: This is enough of a response to stop viewer 2 complaining about a login failure and get voice to work. If we don't
705 // give an OK response, then viewer 2 engages in an continuous viv_signin.php, viv_buddy.php, viv_watcher.php loop
706 // Viewer 1 appeared happy to ignore the lack of reply and still works with this reply.
707 //
708 // However, really we need to fill in whatever watcher data should be here (whatever that is).
709 resp.Append(string.Format(@"<level0>
710 <status>OK</status>
711 <cookie_name>lib_session</cookie_name>
712 <cookie>{0}</cookie>
713 <auth_token>{0}</auth_token>
714 <body/></level0></response>", auth_token));
715
716 response["str_response_string"] = resp.ToString();
717
718// Regex normalizeEndLines = new Regex(@"(\r\n|\n)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.Multiline);
719//
720// m_log.DebugFormat(
721// "[FREESWITCH]: FreeSwitchSLVoiceWatcherHTTPHandler() response {0}",
722// normalizeEndLines.Replace((string)response["str_response_string"],""));
723
724 return response;
725 }
726
727 public Hashtable FreeSwitchSLVoiceSigninHTTPHandler(Hashtable request)
728 {
729 m_log.Debug("[FreeSwitchVoice]: FreeSwitchSLVoiceSigninHTTPHandler called");
730// string requestbody = (string)request["body"];
731// string uri = (string)request["uri"];
732// string contenttype = (string)request["content-type"];
733
734 Hashtable requestBody = ParseRequestBody((string)request["body"]);
735
736 //string pwd = (string) requestBody["pwd"];
737 string userid = (string) requestBody["userid"];
738
739 string avatarName = string.Empty;
740 int pos = -1;
741 lock (m_UUIDName)
742 {
743 if (m_UUIDName.ContainsKey(userid))
744 {
745 avatarName = m_UUIDName[userid];
746 foreach (string s in m_UUIDName.Keys)
747 {
748 pos++;
749 if (s == userid)
750 break;
751 }
752 }
753 }
754
755 //m_log.DebugFormat("[FreeSwitchVoice]: AUTH, URI: {0}, Content-Type:{1}, Body{2}", uri, contenttype,
756 // requestbody);
757 Hashtable response = new Hashtable();
758 response["str_response_string"] = string.Format(@"<response xsi:schemaLocation=""/xsd/signin.xsd"">
759 <level0>
760 <status>OK</status>
761 <body>
762 <code>200</code>
763 <cookie_name>lib_session</cookie_name>
764 <cookie>{0}:{1}:9303959503950::</cookie>
765 <auth_token>{0}:{1}:9303959503950::</auth_token>
766 <primary>1</primary>
767 <account_id>{1}</account_id>
768 <displayname>{2}</displayname>
769 <msg>auth successful</msg>
770 </body>
771 </level0>
772 </response>", userid, pos, avatarName);
773
774 response["int_response_code"] = 200;
775
776// m_log.DebugFormat("[FreeSwitchVoice]: Sending FreeSwitchSLVoiceSigninHTTPHandler response");
777
778 return response;
779 }
780
781 public Hashtable ParseRequestBody(string body)
782 {
783 Hashtable bodyParams = new Hashtable();
784 // split string
785 string [] nvps = body.Split(new Char [] {'&'});
786
787 foreach (string s in nvps)
788 {
789 if (s.Trim() != "")
790 {
791 string [] nvp = s.Split(new Char [] {'='});
792 bodyParams.Add(HttpUtility.UrlDecode(nvp[0]), HttpUtility.UrlDecode(nvp[1]));
793 }
794 }
795
796 return bodyParams;
797 }
798
799 private string ChannelUri(Scene scene, LandData land)
800 {
801 string channelUri = null;
802
803 string landUUID;
804 string landName;
805
806 // Create parcel voice channel. If no parcel exists, then the voice channel ID is the same
807 // as the directory ID. Otherwise, it reflects the parcel's ID.
808
809 lock (m_ParcelAddress)
810 {
811 if (m_ParcelAddress.ContainsKey(land.GlobalID.ToString()))
812 {
813 m_log.DebugFormat("[FreeSwitchVoice]: parcel id {0}: using sip address {1}",
814 land.GlobalID, m_ParcelAddress[land.GlobalID.ToString()]);
815 return m_ParcelAddress[land.GlobalID.ToString()];
816 }
817 }
818
819 if (land.LocalID != 1 && (land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) == 0)
820 {
821 landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, land.Name);
822 landUUID = land.GlobalID.ToString();
823 m_log.DebugFormat("[FreeSwitchVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
824 landName, land.LocalID, landUUID);
825 }
826 else
827 {
828 landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, scene.RegionInfo.RegionName);
829 landUUID = scene.RegionInfo.RegionID.ToString();
830 m_log.DebugFormat("[FreeSwitchVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
831 landName, land.LocalID, landUUID);
832 }
833
834 // slvoice handles the sip address differently if it begins with confctl, hiding it from the user in the friends list. however it also disables
835 // the personal speech indicators as well unless some siren14-3d codec magic happens. we dont have siren143d so we'll settle for the personal speech indicator.
836 channelUri = String.Format("sip:conf-{0}@{1}", "x" + Convert.ToBase64String(Encoding.ASCII.GetBytes(landUUID)), m_freeSwitchRealm);
837
838 lock (m_ParcelAddress)
839 {
840 if (!m_ParcelAddress.ContainsKey(land.GlobalID.ToString()))
841 {
842 m_ParcelAddress.Add(land.GlobalID.ToString(),channelUri);
843 }
844 }
845
846 return channelUri;
847 }
848
849 private static bool CustomCertificateValidation(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error)
850 {
851 return true;
852 }
853
854 public Hashtable FreeSwitchConfigHTTPHandler(Hashtable request)
855 {
856 Hashtable response = new Hashtable();
857 response["str_response_string"] = string.Empty;
858 response["content_type"] = "text/plain";
859 response["keepalive"] = false;
860 response["int_response_code"] = 500;
861
862 Hashtable requestBody = ParseRequestBody((string)request["body"]);
863
864 string section = (string) requestBody["section"];
865
866 if (section == "directory")
867 {
868 string eventCallingFunction = (string)requestBody["Event-Calling-Function"];
869 m_log.DebugFormat(
870 "[FreeSwitchVoice]: Received request for config section directory, event calling function '{0}'",
871 eventCallingFunction);
872
873 response = m_FreeswitchService.HandleDirectoryRequest(requestBody);
874 }
875 else if (section == "dialplan")
876 {
877 m_log.DebugFormat("[FreeSwitchVoice]: Received request for config section dialplan");
878
879 response = m_FreeswitchService.HandleDialplanRequest(requestBody);
880 }
881 else
882 m_log.WarnFormat("[FreeSwitchVoice]: Unknown section {0} was requested from config.", section);
883
884 return response;
885 }
886 }
887
888 public class MonoCert : ICertificatePolicy
889 {
890 #region ICertificatePolicy Members
891
892 public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem)
893 {
894 return true;
895 }
896
897 #endregion
898 }
899}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs
new file mode 100644
index 0000000..dd44564
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Voice/VivoxVoice/VivoxVoiceModule.cs
@@ -0,0 +1,1328 @@
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 OpenSim 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.IO;
30using System.Net;
31using System.Text;
32using System.Xml;
33using System.Collections;
34using System.Collections.Generic;
35using System.Reflection;
36using System.Threading;
37using OpenMetaverse;
38using log4net;
39using Mono.Addins;
40using Nini.Config;
41using Nwc.XmlRpc;
42using OpenSim.Framework;
43
44using OpenSim.Framework.Capabilities;
45using OpenSim.Framework.Servers;
46using OpenSim.Framework.Servers.HttpServer;
47using OpenSim.Region.Framework.Interfaces;
48using OpenSim.Region.Framework.Scenes;
49using Caps = OpenSim.Framework.Capabilities.Caps;
50
51namespace OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice
52{
53 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "VivoxVoiceModule")]
54 public class VivoxVoiceModule : ISharedRegionModule
55 {
56
57 // channel distance model values
58 public const int CHAN_DIST_NONE = 0; // no attenuation
59 public const int CHAN_DIST_INVERSE = 1; // inverse distance attenuation
60 public const int CHAN_DIST_LINEAR = 2; // linear attenuation
61 public const int CHAN_DIST_EXPONENT = 3; // exponential attenuation
62 public const int CHAN_DIST_DEFAULT = CHAN_DIST_LINEAR;
63
64 // channel type values
65 public static readonly string CHAN_TYPE_POSITIONAL = "positional";
66 public static readonly string CHAN_TYPE_CHANNEL = "channel";
67 public static readonly string CHAN_TYPE_DEFAULT = CHAN_TYPE_POSITIONAL;
68
69 // channel mode values
70 public static readonly string CHAN_MODE_OPEN = "open";
71 public static readonly string CHAN_MODE_LECTURE = "lecture";
72 public static readonly string CHAN_MODE_PRESENTATION = "presentation";
73 public static readonly string CHAN_MODE_AUDITORIUM = "auditorium";
74 public static readonly string CHAN_MODE_DEFAULT = CHAN_MODE_OPEN;
75
76 // unconstrained default values
77 public const double CHAN_ROLL_OFF_DEFAULT = 2.0; // rate of attenuation
78 public const double CHAN_ROLL_OFF_MIN = 1.0;
79 public const double CHAN_ROLL_OFF_MAX = 4.0;
80 public const int CHAN_MAX_RANGE_DEFAULT = 80; // distance at which channel is silent
81 public const int CHAN_MAX_RANGE_MIN = 0;
82 public const int CHAN_MAX_RANGE_MAX = 160;
83 public const int CHAN_CLAMPING_DISTANCE_DEFAULT = 10; // distance before attenuation applies
84 public const int CHAN_CLAMPING_DISTANCE_MIN = 0;
85 public const int CHAN_CLAMPING_DISTANCE_MAX = 160;
86
87 // Infrastructure
88 private static readonly ILog m_log =
89 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
90 private static readonly Object vlock = new Object();
91
92 // Capability strings
93 private static readonly string m_parcelVoiceInfoRequestPath = "0107/";
94 private static readonly string m_provisionVoiceAccountRequestPath = "0108/";
95 //private static readonly string m_chatSessionRequestPath = "0109/";
96
97 // Control info, e.g. vivox server, admin user, admin password
98 private static bool m_pluginEnabled = false;
99 private static bool m_adminConnected = false;
100
101 private static string m_vivoxServer;
102 private static string m_vivoxSipUri;
103 private static string m_vivoxVoiceAccountApi;
104 private static string m_vivoxAdminUser;
105 private static string m_vivoxAdminPassword;
106 private static string m_authToken = String.Empty;
107
108 private static int m_vivoxChannelDistanceModel;
109 private static double m_vivoxChannelRollOff;
110 private static int m_vivoxChannelMaximumRange;
111 private static string m_vivoxChannelMode;
112 private static string m_vivoxChannelType;
113 private static int m_vivoxChannelClampingDistance;
114
115 private static Dictionary<string,string> m_parents = new Dictionary<string,string>();
116 private static bool m_dumpXml;
117
118 private IConfig m_config;
119
120 private object m_Lock;
121
122 public void Initialise(IConfigSource config)
123 {
124
125 m_config = config.Configs["VivoxVoice"];
126
127 if (null == m_config)
128 return;
129
130 if (!m_config.GetBoolean("enabled", false))
131 return;
132
133 m_Lock = new object();
134
135 try
136 {
137 // retrieve configuration variables
138 m_vivoxServer = m_config.GetString("vivox_server", String.Empty);
139 m_vivoxSipUri = m_config.GetString("vivox_sip_uri", String.Empty);
140 m_vivoxAdminUser = m_config.GetString("vivox_admin_user", String.Empty);
141 m_vivoxAdminPassword = m_config.GetString("vivox_admin_password", String.Empty);
142
143 m_vivoxChannelDistanceModel = m_config.GetInt("vivox_channel_distance_model", CHAN_DIST_DEFAULT);
144 m_vivoxChannelRollOff = m_config.GetDouble("vivox_channel_roll_off", CHAN_ROLL_OFF_DEFAULT);
145 m_vivoxChannelMaximumRange = m_config.GetInt("vivox_channel_max_range", CHAN_MAX_RANGE_DEFAULT);
146 m_vivoxChannelMode = m_config.GetString("vivox_channel_mode", CHAN_MODE_DEFAULT).ToLower();
147 m_vivoxChannelType = m_config.GetString("vivox_channel_type", CHAN_TYPE_DEFAULT).ToLower();
148 m_vivoxChannelClampingDistance = m_config.GetInt("vivox_channel_clamping_distance",
149 CHAN_CLAMPING_DISTANCE_DEFAULT);
150 m_dumpXml = m_config.GetBoolean("dump_xml", false);
151
152 // Validate against constraints and default if necessary
153 if (m_vivoxChannelRollOff < CHAN_ROLL_OFF_MIN || m_vivoxChannelRollOff > CHAN_ROLL_OFF_MAX)
154 {
155 m_log.WarnFormat("[VivoxVoice] Invalid value for roll off ({0}), reset to {1}.",
156 m_vivoxChannelRollOff, CHAN_ROLL_OFF_DEFAULT);
157 m_vivoxChannelRollOff = CHAN_ROLL_OFF_DEFAULT;
158 }
159
160 if (m_vivoxChannelMaximumRange < CHAN_MAX_RANGE_MIN || m_vivoxChannelMaximumRange > CHAN_MAX_RANGE_MAX)
161 {
162 m_log.WarnFormat("[VivoxVoice] Invalid value for maximum range ({0}), reset to {1}.",
163 m_vivoxChannelMaximumRange, CHAN_MAX_RANGE_DEFAULT);
164 m_vivoxChannelMaximumRange = CHAN_MAX_RANGE_DEFAULT;
165 }
166
167 if (m_vivoxChannelClampingDistance < CHAN_CLAMPING_DISTANCE_MIN ||
168 m_vivoxChannelClampingDistance > CHAN_CLAMPING_DISTANCE_MAX)
169 {
170 m_log.WarnFormat("[VivoxVoice] Invalid value for clamping distance ({0}), reset to {1}.",
171 m_vivoxChannelClampingDistance, CHAN_CLAMPING_DISTANCE_DEFAULT);
172 m_vivoxChannelClampingDistance = CHAN_CLAMPING_DISTANCE_DEFAULT;
173 }
174
175 switch (m_vivoxChannelMode)
176 {
177 case "open" : break;
178 case "lecture" : break;
179 case "presentation" : break;
180 case "auditorium" : break;
181 default :
182 m_log.WarnFormat("[VivoxVoice] Invalid value for channel mode ({0}), reset to {1}.",
183 m_vivoxChannelMode, CHAN_MODE_DEFAULT);
184 m_vivoxChannelMode = CHAN_MODE_DEFAULT;
185 break;
186 }
187
188 switch (m_vivoxChannelType)
189 {
190 case "positional" : break;
191 case "channel" : break;
192 default :
193 m_log.WarnFormat("[VivoxVoice] Invalid value for channel type ({0}), reset to {1}.",
194 m_vivoxChannelType, CHAN_TYPE_DEFAULT);
195 m_vivoxChannelType = CHAN_TYPE_DEFAULT;
196 break;
197 }
198
199 m_vivoxVoiceAccountApi = String.Format("http://{0}/api2", m_vivoxServer);
200
201 // Admin interface required values
202 if (String.IsNullOrEmpty(m_vivoxServer) ||
203 String.IsNullOrEmpty(m_vivoxSipUri) ||
204 String.IsNullOrEmpty(m_vivoxAdminUser) ||
205 String.IsNullOrEmpty(m_vivoxAdminPassword))
206 {
207 m_log.Error("[VivoxVoice] plugin mis-configured");
208 m_log.Info("[VivoxVoice] plugin disabled: incomplete configuration");
209 return;
210 }
211
212 m_log.InfoFormat("[VivoxVoice] using vivox server {0}", m_vivoxServer);
213
214 // Get admin rights and cleanup any residual channel definition
215
216 DoAdminLogin();
217
218 m_pluginEnabled = true;
219
220 m_log.Info("[VivoxVoice] plugin enabled");
221 }
222 catch (Exception e)
223 {
224 m_log.ErrorFormat("[VivoxVoice] plugin initialization failed: {0}", e.Message);
225 m_log.DebugFormat("[VivoxVoice] plugin initialization failed: {0}", e.ToString());
226 return;
227 }
228 }
229
230 public void AddRegion(Scene scene)
231 {
232 if (m_pluginEnabled)
233 {
234 lock (vlock)
235 {
236 string channelId;
237
238 string sceneUUID = scene.RegionInfo.RegionID.ToString();
239 string sceneName = scene.RegionInfo.RegionName;
240
241 // Make sure that all local channels are deleted.
242 // So we have to search for the children, and then do an
243 // iteration over the set of chidren identified.
244 // This assumes that there is just one directory per
245 // region.
246
247 if (VivoxTryGetDirectory(sceneUUID + "D", out channelId))
248 {
249 m_log.DebugFormat("[VivoxVoice]: region {0}: uuid {1}: located directory id {2}",
250 sceneName, sceneUUID, channelId);
251
252 XmlElement children = VivoxListChildren(channelId);
253 string count;
254
255 if (XmlFind(children, "response.level0.channel-search.count", out count))
256 {
257 int cnum = Convert.ToInt32(count);
258 for (int i = 0; i < cnum; i++)
259 {
260 string id;
261 if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
262 {
263 if (!IsOK(VivoxDeleteChannel(channelId, id)))
264 m_log.WarnFormat("[VivoxVoice] Channel delete failed {0}:{1}:{2}", i, channelId, id);
265 }
266 }
267 }
268 }
269 else
270 {
271 if (!VivoxTryCreateDirectory(sceneUUID + "D", sceneName, out channelId))
272 {
273 m_log.WarnFormat("[VivoxVoice] Create failed <{0}:{1}:{2}>",
274 "*", sceneUUID, sceneName);
275 channelId = String.Empty;
276 }
277 }
278
279 // Create a dictionary entry unconditionally. This eliminates the
280 // need to check for a parent in the core code. The end result is
281 // the same, if the parent table entry is an empty string, then
282 // region channels will be created as first-level channels.
283 lock (m_parents)
284 {
285 if (m_parents.ContainsKey(sceneUUID))
286 {
287 RemoveRegion(scene);
288 m_parents.Add(sceneUUID, channelId);
289 }
290 else
291 {
292 m_parents.Add(sceneUUID, channelId);
293 }
294 }
295 }
296
297 // we need to capture scene in an anonymous method
298 // here as we need it later in the callbacks
299 scene.EventManager.OnRegisterCaps += delegate(UUID agentID, Caps caps)
300 {
301 OnRegisterCaps(scene, agentID, caps);
302 };
303 }
304 }
305
306 public void RegionLoaded(Scene scene)
307 {
308 // Do nothing.
309 }
310
311 public void RemoveRegion(Scene scene)
312 {
313 if (m_pluginEnabled)
314 {
315 lock (vlock)
316 {
317 string channelId;
318
319 string sceneUUID = scene.RegionInfo.RegionID.ToString();
320 string sceneName = scene.RegionInfo.RegionName;
321
322 // Make sure that all local channels are deleted.
323 // So we have to search for the children, and then do an
324 // iteration over the set of chidren identified.
325 // This assumes that there is just one directory per
326 // region.
327 if (VivoxTryGetDirectory(sceneUUID + "D", out channelId))
328 {
329 m_log.DebugFormat("[VivoxVoice]: region {0}: uuid {1}: located directory id {2}",
330 sceneName, sceneUUID, channelId);
331
332 XmlElement children = VivoxListChildren(channelId);
333 string count;
334
335 if (XmlFind(children, "response.level0.channel-search.count", out count))
336 {
337 int cnum = Convert.ToInt32(count);
338 for (int i = 0; i < cnum; i++)
339 {
340 string id;
341 if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
342 {
343 if (!IsOK(VivoxDeleteChannel(channelId, id)))
344 m_log.WarnFormat("[VivoxVoice] Channel delete failed {0}:{1}:{2}", i, channelId, id);
345 }
346 }
347 }
348 }
349
350 if (!IsOK(VivoxDeleteChannel(null, channelId)))
351 m_log.WarnFormat("[VivoxVoice] Parent channel delete failed {0}:{1}:{2}", sceneName, sceneUUID, channelId);
352
353 // Remove the channel umbrella entry
354
355 lock (m_parents)
356 {
357 if (m_parents.ContainsKey(sceneUUID))
358 {
359 m_parents.Remove(sceneUUID);
360 }
361 }
362 }
363 }
364 }
365
366 public void PostInitialise()
367 {
368 // Do nothing.
369 }
370
371 public void Close()
372 {
373 if (m_pluginEnabled)
374 VivoxLogout();
375 }
376
377 public Type ReplaceableInterface
378 {
379 get { return null; }
380 }
381
382 public string Name
383 {
384 get { return "VivoxVoiceModule"; }
385 }
386
387 public bool IsSharedModule
388 {
389 get { return true; }
390 }
391
392 // <summary>
393 // OnRegisterCaps is invoked via the scene.EventManager
394 // everytime OpenSim hands out capabilities to a client
395 // (login, region crossing). We contribute two capabilities to
396 // the set of capabilities handed back to the client:
397 // ProvisionVoiceAccountRequest and ParcelVoiceInfoRequest.
398 //
399 // ProvisionVoiceAccountRequest allows the client to obtain
400 // the voice account credentials for the avatar it is
401 // controlling (e.g., user name, password, etc).
402 //
403 // ParcelVoiceInfoRequest is invoked whenever the client
404 // changes from one region or parcel to another.
405 //
406 // Note that OnRegisterCaps is called here via a closure
407 // delegate containing the scene of the respective region (see
408 // Initialise()).
409 // </summary>
410 public void OnRegisterCaps(Scene scene, UUID agentID, Caps caps)
411 {
412 m_log.DebugFormat("[VivoxVoice] OnRegisterCaps: agentID {0} caps {1}", agentID, caps);
413
414 string capsBase = "/CAPS/" + caps.CapsObjectPath;
415
416 caps.RegisterHandler(
417 "ProvisionVoiceAccountRequest",
418 new RestStreamHandler(
419 "POST",
420 capsBase + m_provisionVoiceAccountRequestPath,
421 (request, path, param, httpRequest, httpResponse)
422 => ProvisionVoiceAccountRequest(scene, request, path, param, agentID, caps),
423 "ProvisionVoiceAccountRequest",
424 agentID.ToString()));
425
426 caps.RegisterHandler(
427 "ParcelVoiceInfoRequest",
428 new RestStreamHandler(
429 "POST",
430 capsBase + m_parcelVoiceInfoRequestPath,
431 (request, path, param, httpRequest, httpResponse)
432 => ParcelVoiceInfoRequest(scene, request, path, param, agentID, caps),
433 "ParcelVoiceInfoRequest",
434 agentID.ToString()));
435
436 //caps.RegisterHandler(
437 // "ChatSessionRequest",
438 // new RestStreamHandler(
439 // "POST",
440 // capsBase + m_chatSessionRequestPath,
441 // (request, path, param, httpRequest, httpResponse)
442 // => ChatSessionRequest(scene, request, path, param, agentID, caps),
443 // "ChatSessionRequest",
444 // agentID.ToString()));
445 }
446
447 /// <summary>
448 /// Callback for a client request for Voice Account Details
449 /// </summary>
450 /// <param name="scene">current scene object of the client</param>
451 /// <param name="request"></param>
452 /// <param name="path"></param>
453 /// <param name="param"></param>
454 /// <param name="agentID"></param>
455 /// <param name="caps"></param>
456 /// <returns></returns>
457 public string ProvisionVoiceAccountRequest(Scene scene, string request, string path, string param,
458 UUID agentID, Caps caps)
459 {
460 try
461 {
462 ScenePresence avatar = null;
463 string avatarName = null;
464
465 if (scene == null)
466 throw new Exception("[VivoxVoice][PROVISIONVOICE]: Invalid scene");
467
468 avatar = scene.GetScenePresence(agentID);
469 while (avatar == null)
470 {
471 Thread.Sleep(100);
472 avatar = scene.GetScenePresence(agentID);
473 }
474
475 avatarName = avatar.Name;
476
477 m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: scene = {0}, agentID = {1}", scene, agentID);
478 m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}",
479 request, path, param);
480
481 XmlElement resp;
482 bool retry = false;
483 string agentname = "x" + Convert.ToBase64String(agentID.GetBytes());
484 string password = new UUID(Guid.NewGuid()).ToString().Replace('-','Z').Substring(0,16);
485 string code = String.Empty;
486
487 agentname = agentname.Replace('+', '-').Replace('/', '_');
488
489 do
490 {
491 resp = VivoxGetAccountInfo(agentname);
492
493 if (XmlFind(resp, "response.level0.status", out code))
494 {
495 if (code != "OK")
496 {
497 if (XmlFind(resp, "response.level0.body.code", out code))
498 {
499 // If the request was recognized, then this should be set to something
500 switch (code)
501 {
502 case "201" : // Account expired
503 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : expired credentials",
504 avatarName);
505 m_adminConnected = false;
506 retry = DoAdminLogin();
507 break;
508
509 case "202" : // Missing credentials
510 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : missing credentials",
511 avatarName);
512 break;
513
514 case "212" : // Not authorized
515 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : not authorized",
516 avatarName);
517 break;
518
519 case "300" : // Required parameter missing
520 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : parameter missing",
521 avatarName);
522 break;
523
524 case "403" : // Account does not exist
525 resp = VivoxCreateAccount(agentname,password);
526 // Note: This REALLY MUST BE status. Create Account does not return code.
527 if (XmlFind(resp, "response.level0.status", out code))
528 {
529 switch (code)
530 {
531 case "201" : // Account expired
532 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : expired credentials",
533 avatarName);
534 m_adminConnected = false;
535 retry = DoAdminLogin();
536 break;
537
538 case "202" : // Missing credentials
539 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : missing credentials",
540 avatarName);
541 break;
542
543 case "212" : // Not authorized
544 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : not authorized",
545 avatarName);
546 break;
547
548 case "300" : // Required parameter missing
549 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : parameter missing",
550 avatarName);
551 break;
552
553 case "400" : // Create failed
554 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Create account information failed : create failed",
555 avatarName);
556 break;
557 }
558 }
559 break;
560
561 case "404" : // Failed to retrieve account
562 m_log.ErrorFormat("[VivoxVoice]: avatar \"{0}\": Get account information failed : retrieve failed");
563 // [AMW] Sleep and retry for a fixed period? Or just abandon?
564 break;
565 }
566 }
567 }
568 }
569 }
570 while (retry);
571
572 if (code != "OK")
573 {
574 m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: Get Account Request failed for \"{0}\"", avatarName);
575 throw new Exception("Unable to execute request");
576 }
577
578 // Unconditionally change the password on each request
579 VivoxPassword(agentname, password);
580
581 LLSDVoiceAccountResponse voiceAccountResponse =
582 new LLSDVoiceAccountResponse(agentname, password, m_vivoxSipUri, m_vivoxVoiceAccountApi);
583
584 string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse);
585
586 m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: avatar \"{0}\": {1}", avatarName, r);
587
588 return r;
589 }
590 catch (Exception e)
591 {
592 m_log.ErrorFormat("[VivoxVoice][PROVISIONVOICE]: : {0}, retry later", e.Message);
593 m_log.DebugFormat("[VivoxVoice][PROVISIONVOICE]: : {0} failed", e.ToString());
594 return "<llsd><undef /></llsd>";
595 }
596 }
597
598 /// <summary>
599 /// Callback for a client request for ParcelVoiceInfo
600 /// </summary>
601 /// <param name="scene">current scene object of the client</param>
602 /// <param name="request"></param>
603 /// <param name="path"></param>
604 /// <param name="param"></param>
605 /// <param name="agentID"></param>
606 /// <param name="caps"></param>
607 /// <returns></returns>
608 public string ParcelVoiceInfoRequest(Scene scene, string request, string path, string param,
609 UUID agentID, Caps caps)
610 {
611 ScenePresence avatar = scene.GetScenePresence(agentID);
612 string avatarName = avatar.Name;
613
614 // - check whether we have a region channel in our cache
615 // - if not:
616 // create it and cache it
617 // - send it to the client
618 // - send channel_uri: as "sip:regionID@m_sipDomain"
619 try
620 {
621 LLSDParcelVoiceInfoResponse parcelVoiceInfo;
622 string channel_uri;
623
624 if (null == scene.LandChannel)
625 throw new Exception(String.Format("region \"{0}\": avatar \"{1}\": land data not yet available",
626 scene.RegionInfo.RegionName, avatarName));
627
628 // get channel_uri: check first whether estate
629 // settings allow voice, then whether parcel allows
630 // voice, if all do retrieve or obtain the parcel
631 // voice channel
632 LandData land = scene.GetLandData(avatar.AbsolutePosition);
633
634 m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": request: {4}, path: {5}, param: {6}",
635 scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, request, path, param);
636 // m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: avatar \"{0}\": location: {1} {2} {3}",
637 // avatarName, avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y, avatar.AbsolutePosition.Z);
638
639 // TODO: EstateSettings don't seem to get propagated...
640 if (!scene.RegionInfo.EstateSettings.AllowVoice)
641 {
642 m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": voice not enabled in estate settings",
643 scene.RegionInfo.RegionName);
644 channel_uri = String.Empty;
645 }
646
647 if ((land.Flags & (uint)ParcelFlags.AllowVoiceChat) == 0)
648 {
649 m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": voice not enabled for parcel",
650 scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName);
651 channel_uri = String.Empty;
652 }
653 else
654 {
655 channel_uri = RegionGetOrCreateChannel(scene, land);
656 }
657
658 // fill in our response to the client
659 Hashtable creds = new Hashtable();
660 creds["channel_uri"] = channel_uri;
661
662 parcelVoiceInfo = new LLSDParcelVoiceInfoResponse(scene.RegionInfo.RegionName, land.LocalID, creds);
663 string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo);
664
665 m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": Parcel \"{1}\" ({2}): avatar \"{3}\": {4}",
666 scene.RegionInfo.RegionName, land.Name, land.LocalID, avatarName, r);
667 return r;
668 }
669 catch (Exception e)
670 {
671 m_log.ErrorFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2}, retry later",
672 scene.RegionInfo.RegionName, avatarName, e.Message);
673 m_log.DebugFormat("[VivoxVoice][PARCELVOICE]: region \"{0}\": avatar \"{1}\": {2} failed",
674 scene.RegionInfo.RegionName, avatarName, e.ToString());
675
676 return "<llsd><undef /></llsd>";
677 }
678 }
679
680 /// <summary>
681 /// Callback for a client request for a private chat channel
682 /// </summary>
683 /// <param name="scene">current scene object of the client</param>
684 /// <param name="request"></param>
685 /// <param name="path"></param>
686 /// <param name="param"></param>
687 /// <param name="agentID"></param>
688 /// <param name="caps"></param>
689 /// <returns></returns>
690 public string ChatSessionRequest(Scene scene, string request, string path, string param,
691 UUID agentID, Caps caps)
692 {
693 ScenePresence avatar = scene.GetScenePresence(agentID);
694 string avatarName = avatar.Name;
695
696 m_log.DebugFormat("[VivoxVoice][CHATSESSION]: avatar \"{0}\": request: {1}, path: {2}, param: {3}",
697 avatarName, request, path, param);
698 return "<llsd>true</llsd>";
699 }
700
701 private string RegionGetOrCreateChannel(Scene scene, LandData land)
702 {
703 string channelUri = null;
704 string channelId = null;
705
706 string landUUID;
707 string landName;
708 string parentId;
709
710 lock (m_parents)
711 parentId = m_parents[scene.RegionInfo.RegionID.ToString()];
712
713 // Create parcel voice channel. If no parcel exists, then the voice channel ID is the same
714 // as the directory ID. Otherwise, it reflects the parcel's ID.
715 if (land.LocalID != 1 && (land.Flags & (uint)ParcelFlags.UseEstateVoiceChan) == 0)
716 {
717 landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, land.Name);
718 landUUID = land.GlobalID.ToString();
719 m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
720 landName, land.LocalID, landUUID);
721 }
722 else
723 {
724 landName = String.Format("{0}:{1}", scene.RegionInfo.RegionName, scene.RegionInfo.RegionName);
725 landUUID = scene.RegionInfo.RegionID.ToString();
726 m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parcel id {1}: using channel name {2}",
727 landName, land.LocalID, landUUID);
728 }
729
730 lock (vlock)
731 {
732 // Added by Adam to help debug channel not availible errors.
733 if (VivoxTryGetChannel(parentId, landUUID, out channelId, out channelUri))
734 m_log.DebugFormat("[VivoxVoice] Found existing channel at " + channelUri);
735 else if (VivoxTryCreateChannel(parentId, landUUID, landName, out channelUri))
736 m_log.DebugFormat("[VivoxVoice] Created new channel at " + channelUri);
737 else
738 throw new Exception("vivox channel uri not available");
739
740 m_log.DebugFormat("[VivoxVoice]: Region:Parcel \"{0}\": parent channel id {1}: retrieved parcel channel_uri {2} ",
741 landName, parentId, channelUri);
742 }
743
744 return channelUri;
745 }
746
747 private static readonly string m_vivoxLoginPath = "http://{0}/api2/viv_signin.php?userid={1}&pwd={2}";
748
749 /// <summary>
750 /// Perform administrative login for Vivox.
751 /// Returns a hash table containing values returned from the request.
752 /// </summary>
753 private XmlElement VivoxLogin(string name, string password)
754 {
755 string requrl = String.Format(m_vivoxLoginPath, m_vivoxServer, name, password);
756 return VivoxCall(requrl, false);
757 }
758
759 private static readonly string m_vivoxLogoutPath = "http://{0}/api2/viv_signout.php?auth_token={1}";
760
761 /// <summary>
762 /// Perform administrative logout for Vivox.
763 /// </summary>
764 private XmlElement VivoxLogout()
765 {
766 string requrl = String.Format(m_vivoxLogoutPath, m_vivoxServer, m_authToken);
767 return VivoxCall(requrl, false);
768 }
769
770 private static readonly string m_vivoxGetAccountPath = "http://{0}/api2/viv_get_acct.php?auth_token={1}&user_name={2}";
771
772 /// <summary>
773 /// Retrieve account information for the specified user.
774 /// Returns a hash table containing values returned from the request.
775 /// </summary>
776 private XmlElement VivoxGetAccountInfo(string user)
777 {
778 string requrl = String.Format(m_vivoxGetAccountPath, m_vivoxServer, m_authToken, user);
779 return VivoxCall(requrl, true);
780 }
781
782 private static readonly string m_vivoxNewAccountPath = "http://{0}/api2/viv_adm_acct_new.php?username={1}&pwd={2}&auth_token={3}";
783
784 /// <summary>
785 /// Creates a new account.
786 /// For now we supply the minimum set of values, which
787 /// is user name and password. We *can* supply a lot more
788 /// demographic data.
789 /// </summary>
790 private XmlElement VivoxCreateAccount(string user, string password)
791 {
792 string requrl = String.Format(m_vivoxNewAccountPath, m_vivoxServer, user, password, m_authToken);
793 return VivoxCall(requrl, true);
794 }
795
796 private static readonly string m_vivoxPasswordPath = "http://{0}/api2/viv_adm_password.php?user_name={1}&new_pwd={2}&auth_token={3}";
797
798 /// <summary>
799 /// Change the user's password.
800 /// </summary>
801 private XmlElement VivoxPassword(string user, string password)
802 {
803 string requrl = String.Format(m_vivoxPasswordPath, m_vivoxServer, user, password, m_authToken);
804 return VivoxCall(requrl, true);
805 }
806
807 private static readonly string m_vivoxChannelPath = "http://{0}/api2/viv_chan_mod.php?mode={1}&chan_name={2}&auth_token={3}";
808
809 /// <summary>
810 /// Create a channel.
811 /// Once again, there a multitude of options possible. In the simplest case
812 /// we specify only the name and get a non-persistent cannel in return. Non
813 /// persistent means that the channel gets deleted if no-one uses it for
814 /// 5 hours. To accomodate future requirements, it may be a good idea to
815 /// initially create channels under the umbrella of a parent ID based upon
816 /// the region name. That way we have a context for side channels, if those
817 /// are required in a later phase.
818 ///
819 /// In this case the call handles parent and description as optional values.
820 /// </summary>
821 private bool VivoxTryCreateChannel(string parent, string channelId, string description, out string channelUri)
822 {
823 string requrl = String.Format(m_vivoxChannelPath, m_vivoxServer, "create", channelId, m_authToken);
824
825 if (!string.IsNullOrEmpty(parent))
826 {
827 requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
828 }
829 if (!string.IsNullOrEmpty(description))
830 {
831 requrl = String.Format("{0}&chan_desc={1}", requrl, description);
832 }
833
834 requrl = String.Format("{0}&chan_type={1}", requrl, m_vivoxChannelType);
835 requrl = String.Format("{0}&chan_mode={1}", requrl, m_vivoxChannelMode);
836 requrl = String.Format("{0}&chan_roll_off={1}", requrl, m_vivoxChannelRollOff);
837 requrl = String.Format("{0}&chan_dist_model={1}", requrl, m_vivoxChannelDistanceModel);
838 requrl = String.Format("{0}&chan_max_range={1}", requrl, m_vivoxChannelMaximumRange);
839 requrl = String.Format("{0}&chan_clamping_distance={1}", requrl, m_vivoxChannelClampingDistance);
840
841 XmlElement resp = VivoxCall(requrl, true);
842 if (XmlFind(resp, "response.level0.body.chan_uri", out channelUri))
843 return true;
844
845 channelUri = String.Empty;
846 return false;
847 }
848
849 /// <summary>
850 /// Create a directory.
851 /// Create a channel with an unconditional type of "dir" (indicating directory).
852 /// This is used to create an arbitrary name tree for partitioning of the
853 /// channel name space.
854 /// The parent and description are optional values.
855 /// </summary>
856 private bool VivoxTryCreateDirectory(string dirId, string description, out string channelId)
857 {
858 string requrl = String.Format(m_vivoxChannelPath, m_vivoxServer, "create", dirId, m_authToken);
859
860 // if (parent != null && parent != String.Empty)
861 // {
862 // requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
863 // }
864
865 if (!string.IsNullOrEmpty(description))
866 {
867 requrl = String.Format("{0}&chan_desc={1}", requrl, description);
868 }
869 requrl = String.Format("{0}&chan_type={1}", requrl, "dir");
870
871 XmlElement resp = VivoxCall(requrl, true);
872 if (IsOK(resp) && XmlFind(resp, "response.level0.body.chan_id", out channelId))
873 return true;
874
875 channelId = String.Empty;
876 return false;
877 }
878
879 private static readonly string m_vivoxChannelSearchPath = "http://{0}/api2/viv_chan_search.php?cond_channame={1}&auth_token={2}";
880
881 /// <summary>
882 /// Retrieve a channel.
883 /// Once again, there a multitude of options possible. In the simplest case
884 /// we specify only the name and get a non-persistent cannel in return. Non
885 /// persistent means that the channel gets deleted if no-one uses it for
886 /// 5 hours. To accomodate future requirements, it may be a good idea to
887 /// initially create channels under the umbrella of a parent ID based upon
888 /// the region name. That way we have a context for side channels, if those
889 /// are required in a later phase.
890 /// In this case the call handles parent and description as optional values.
891 /// </summary>
892 private bool VivoxTryGetChannel(string channelParent, string channelName,
893 out string channelId, out string channelUri)
894 {
895 string count;
896
897 string requrl = String.Format(m_vivoxChannelSearchPath, m_vivoxServer, channelName, m_authToken);
898 XmlElement resp = VivoxCall(requrl, true);
899
900 if (XmlFind(resp, "response.level0.channel-search.count", out count))
901 {
902 int channels = Convert.ToInt32(count);
903
904 // Bug in Vivox Server r2978 where count returns 0
905 // Found by Adam
906 if (channels == 0)
907 {
908 for (int j=0;j<100;j++)
909 {
910 string tmpId;
911 if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", j, out tmpId))
912 break;
913
914 channels = j + 1;
915 }
916 }
917
918 for (int i = 0; i < channels; i++)
919 {
920 string name;
921 string id;
922 string type;
923 string uri;
924 string parent;
925
926 // skip if not a channel
927 if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.type", i, out type) ||
928 (type != "channel" && type != "positional_M"))
929 {
930 m_log.Debug("[VivoxVoice] Skipping Channel " + i + " as it's not a channel.");
931 continue;
932 }
933
934 // skip if not the name we are looking for
935 if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.name", i, out name) ||
936 name != channelName)
937 {
938 m_log.Debug("[VivoxVoice] Skipping Channel " + i + " as it has no name.");
939 continue;
940 }
941
942 // skip if parent does not match
943 if (channelParent != null && !XmlFind(resp, "response.level0.channel-search.channels.channels.level4.parent", i, out parent))
944 {
945 m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it's parent doesnt match");
946 continue;
947 }
948
949 // skip if no channel id available
950 if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", i, out id))
951 {
952 m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it has no channel ID");
953 continue;
954 }
955
956 // skip if no channel uri available
957 if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.uri", i, out uri))
958 {
959 m_log.Debug("[VivoxVoice] Skipping Channel " + i + "/" + name + " as it has no channel URI");
960 continue;
961 }
962
963 channelId = id;
964 channelUri = uri;
965
966 return true;
967 }
968 }
969 else
970 {
971 m_log.Debug("[VivoxVoice] No count element?");
972 }
973
974 channelId = String.Empty;
975 channelUri = String.Empty;
976
977 // Useful incase something goes wrong.
978 //m_log.Debug("[VivoxVoice] Could not find channel in XMLRESP: " + resp.InnerXml);
979
980 return false;
981 }
982
983 private bool VivoxTryGetDirectory(string directoryName, out string directoryId)
984 {
985 string count;
986
987 string requrl = String.Format(m_vivoxChannelSearchPath, m_vivoxServer, directoryName, m_authToken);
988 XmlElement resp = VivoxCall(requrl, true);
989
990 if (XmlFind(resp, "response.level0.channel-search.count", out count))
991 {
992 int channels = Convert.ToInt32(count);
993 for (int i = 0; i < channels; i++)
994 {
995 string name;
996 string id;
997 string type;
998
999 // skip if not a directory
1000 if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.type", i, out type) ||
1001 type != "dir")
1002 continue;
1003
1004 // skip if not the name we are looking for
1005 if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.name", i, out name) ||
1006 name != directoryName)
1007 continue;
1008
1009 // skip if no channel id available
1010 if (!XmlFind(resp, "response.level0.channel-search.channels.channels.level4.id", i, out id))
1011 continue;
1012
1013 directoryId = id;
1014 return true;
1015 }
1016 }
1017
1018 directoryId = String.Empty;
1019 return false;
1020 }
1021
1022 // private static readonly string m_vivoxChannelById = "http://{0}/api2/viv_chan_mod.php?mode={1}&chan_id={2}&auth_token={3}";
1023
1024 // private XmlElement VivoxGetChannelById(string parent, string channelid)
1025 // {
1026 // string requrl = String.Format(m_vivoxChannelById, m_vivoxServer, "get", channelid, m_authToken);
1027
1028 // if (parent != null && parent != String.Empty)
1029 // return VivoxGetChild(parent, channelid);
1030 // else
1031 // return VivoxCall(requrl, true);
1032 // }
1033
1034 private static readonly string m_vivoxChannelDel = "http://{0}/api2/viv_chan_mod.php?mode={1}&chan_id={2}&auth_token={3}";
1035
1036 /// <summary>
1037 /// Delete a channel.
1038 /// Once again, there a multitude of options possible. In the simplest case
1039 /// we specify only the name and get a non-persistent cannel in return. Non
1040 /// persistent means that the channel gets deleted if no-one uses it for
1041 /// 5 hours. To accomodate future requirements, it may be a good idea to
1042 /// initially create channels under the umbrella of a parent ID based upon
1043 /// the region name. That way we have a context for side channels, if those
1044 /// are required in a later phase.
1045 /// In this case the call handles parent and description as optional values.
1046 /// </summary>
1047 private XmlElement VivoxDeleteChannel(string parent, string channelid)
1048 {
1049 string requrl = String.Format(m_vivoxChannelDel, m_vivoxServer, "delete", channelid, m_authToken);
1050 if (!string.IsNullOrEmpty(parent))
1051 {
1052 requrl = String.Format("{0}&chan_parent={1}", requrl, parent);
1053 }
1054 return VivoxCall(requrl, true);
1055 }
1056
1057 private static readonly string m_vivoxChannelSearch = "http://{0}/api2/viv_chan_search.php?&cond_chanparent={1}&auth_token={2}";
1058
1059 /// <summary>
1060 /// Return information on channels in the given directory
1061 /// </summary>
1062 private XmlElement VivoxListChildren(string channelid)
1063 {
1064 string requrl = String.Format(m_vivoxChannelSearch, m_vivoxServer, channelid, m_authToken);
1065 return VivoxCall(requrl, true);
1066 }
1067
1068 // private XmlElement VivoxGetChild(string parent, string child)
1069 // {
1070
1071 // XmlElement children = VivoxListChildren(parent);
1072 // string count;
1073
1074 // if (XmlFind(children, "response.level0.channel-search.count", out count))
1075 // {
1076 // int cnum = Convert.ToInt32(count);
1077 // for (int i = 0; i < cnum; i++)
1078 // {
1079 // string name;
1080 // string id;
1081 // if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.name", i, out name))
1082 // {
1083 // if (name == child)
1084 // {
1085 // if (XmlFind(children, "response.level0.channel-search.channels.channels.level4.id", i, out id))
1086 // {
1087 // return VivoxGetChannelById(null, id);
1088 // }
1089 // }
1090 // }
1091 // }
1092 // }
1093
1094 // // One we *know* does not exist.
1095 // return VivoxGetChannel(null, Guid.NewGuid().ToString());
1096
1097 // }
1098
1099 /// <summary>
1100 /// This method handles the WEB side of making a request over the
1101 /// Vivox interface. The returned values are tansferred to a has
1102 /// table which is returned as the result.
1103 /// The outcome of the call can be determined by examining the
1104 /// status value in the hash table.
1105 /// </summary>
1106 private XmlElement VivoxCall(string requrl, bool admin)
1107 {
1108
1109 XmlDocument doc = null;
1110
1111 // If this is an admin call, and admin is not connected,
1112 // and the admin id cannot be connected, then fail.
1113 if (admin && !m_adminConnected && !DoAdminLogin())
1114 return null;
1115
1116 doc = new XmlDocument();
1117
1118 // Let's serialize all calls to Vivox. Most of these are driven by
1119 // the clients (CAPs), when the user arrives at the region. We don't
1120 // want to issue many simultaneous http requests to Vivox, because mono
1121 // doesn't like that
1122 lock (m_Lock)
1123 {
1124 try
1125 {
1126 // Otherwise prepare the request
1127 m_log.DebugFormat("[VivoxVoice] Sending request <{0}>", requrl);
1128
1129 HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requrl);
1130
1131 // We are sending just parameters, no content
1132 req.ContentLength = 0;
1133
1134 // Send request and retrieve the response
1135 using (HttpWebResponse rsp = (HttpWebResponse)req.GetResponse())
1136 using (Stream s = rsp.GetResponseStream())
1137 using (XmlTextReader rdr = new XmlTextReader(s))
1138 doc.Load(rdr);
1139 }
1140 catch (Exception e)
1141 {
1142 m_log.ErrorFormat("[VivoxVoice] Error in admin call : {0}", e.Message);
1143 }
1144 }
1145
1146 // If we're debugging server responses, dump the whole
1147 // load now
1148 if (m_dumpXml) XmlScanl(doc.DocumentElement,0);
1149
1150 return doc.DocumentElement;
1151 }
1152
1153 /// <summary>
1154 /// Just say if it worked.
1155 /// </summary>
1156 private bool IsOK(XmlElement resp)
1157 {
1158 string status;
1159 XmlFind(resp, "response.level0.status", out status);
1160 return (status == "OK");
1161 }
1162
1163 /// <summary>
1164 /// Login has been factored in this way because it gets called
1165 /// from several places in the module, and we want it to work
1166 /// the same way each time.
1167 /// </summary>
1168 private bool DoAdminLogin()
1169 {
1170 m_log.Debug("[VivoxVoice] Establishing admin connection");
1171
1172 lock (vlock)
1173 {
1174 if (!m_adminConnected)
1175 {
1176 string status = "Unknown";
1177 XmlElement resp = null;
1178
1179 resp = VivoxLogin(m_vivoxAdminUser, m_vivoxAdminPassword);
1180
1181 if (XmlFind(resp, "response.level0.body.status", out status))
1182 {
1183 if (status == "Ok")
1184 {
1185 m_log.Info("[VivoxVoice] Admin connection established");
1186 if (XmlFind(resp, "response.level0.body.auth_token", out m_authToken))
1187 {
1188 if (m_dumpXml) m_log.DebugFormat("[VivoxVoice] Auth Token <{0}>",
1189 m_authToken);
1190 m_adminConnected = true;
1191 }
1192 }
1193 else
1194 {
1195 m_log.WarnFormat("[VivoxVoice] Admin connection failed, status = {0}",
1196 status);
1197 }
1198 }
1199 }
1200 }
1201
1202 return m_adminConnected;
1203 }
1204
1205 /// <summary>
1206 /// The XmlScan routine is provided to aid in the
1207 /// reverse engineering of incompletely
1208 /// documented packets returned by the Vivox
1209 /// voice server. It is only called if the
1210 /// m_dumpXml switch is set.
1211 /// </summary>
1212 private void XmlScanl(XmlElement e, int index)
1213 {
1214 if (e.HasChildNodes)
1215 {
1216 m_log.DebugFormat("<{0}>".PadLeft(index+5), e.Name);
1217 XmlNodeList children = e.ChildNodes;
1218 foreach (XmlNode node in children)
1219 switch (node.NodeType)
1220 {
1221 case XmlNodeType.Element :
1222 XmlScanl((XmlElement)node, index+1);
1223 break;
1224 case XmlNodeType.Text :
1225 m_log.DebugFormat("\"{0}\"".PadLeft(index+5), node.Value);
1226 break;
1227 default :
1228 break;
1229 }
1230 m_log.DebugFormat("</{0}>".PadLeft(index+6), e.Name);
1231 }
1232 else
1233 {
1234 m_log.DebugFormat("<{0}/>".PadLeft(index+6), e.Name);
1235 }
1236 }
1237
1238 private static readonly char[] C_POINT = {'.'};
1239
1240 /// <summary>
1241 /// The Find method is passed an element whose
1242 /// inner text is scanned in an attempt to match
1243 /// the name hierarchy passed in the 'tag' parameter.
1244 /// If the whole hierarchy is resolved, the InnerText
1245 /// value at that point is returned. Note that this
1246 /// may itself be a subhierarchy of the entire
1247 /// document. The function returns a boolean indicator
1248 /// of the search's success. The search is performed
1249 /// by the recursive Search method.
1250 /// </summary>
1251 private bool XmlFind(XmlElement root, string tag, int nth, out string result)
1252 {
1253 if (root == null || tag == null || tag == String.Empty)
1254 {
1255 result = String.Empty;
1256 return false;
1257 }
1258 return XmlSearch(root,tag.Split(C_POINT),0, ref nth, out result);
1259 }
1260
1261 private bool XmlFind(XmlElement root, string tag, out string result)
1262 {
1263 int nth = 0;
1264 if (root == null || tag == null || tag == String.Empty)
1265 {
1266 result = String.Empty;
1267 return false;
1268 }
1269 return XmlSearch(root,tag.Split(C_POINT),0, ref nth, out result);
1270 }
1271
1272 /// <summary>
1273 /// XmlSearch is initially called by XmlFind, and then
1274 /// recursively called by itself until the document
1275 /// supplied to XmlFind is either exhausted or the name hierarchy
1276 /// is matched.
1277 ///
1278 /// If the hierarchy is matched, the value is returned in
1279 /// result, and true returned as the function's
1280 /// value. Otherwise the result is set to the empty string and
1281 /// false is returned.
1282 /// </summary>
1283 private bool XmlSearch(XmlElement e, string[] tags, int index, ref int nth, out string result)
1284 {
1285 if (index == tags.Length || e.Name != tags[index])
1286 {
1287 result = String.Empty;
1288 return false;
1289 }
1290
1291 if (tags.Length-index == 1)
1292 {
1293 if (nth == 0)
1294 {
1295 result = e.InnerText;
1296 return true;
1297 }
1298 else
1299 {
1300 nth--;
1301 result = String.Empty;
1302 return false;
1303 }
1304 }
1305
1306 if (e.HasChildNodes)
1307 {
1308 XmlNodeList children = e.ChildNodes;
1309 foreach (XmlNode node in children)
1310 {
1311 switch (node.NodeType)
1312 {
1313 case XmlNodeType.Element :
1314 if (XmlSearch((XmlElement)node, tags, index+1, ref nth, out result))
1315 return true;
1316 break;
1317
1318 default :
1319 break;
1320 }
1321 }
1322 }
1323
1324 result = String.Empty;
1325 return false;
1326 }
1327 }
1328}
diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsMessagingModule.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsMessagingModule.cs
new file mode 100644
index 0000000..e1b6abb
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/GroupsMessagingModule.cs
@@ -0,0 +1,704 @@
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.Linq;
31using System.Reflection;
32using log4net;
33using Mono.Addins;
34using Nini.Config;
35using OpenMetaverse;
36using OpenMetaverse.StructuredData;
37using OpenSim.Framework;
38using OpenSim.Region.Framework.Interfaces;
39using OpenSim.Region.Framework.Scenes;
40using OpenSim.Services.Interfaces;
41using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo;
42
43namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
44{
45 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GroupsMessagingModule")]
46 public class GroupsMessagingModule : ISharedRegionModule, IGroupsMessagingModule
47 {
48 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
49
50 private List<Scene> m_sceneList = new List<Scene>();
51 private IPresenceService m_presenceService;
52
53 private IMessageTransferModule m_msgTransferModule = null;
54
55 private IGroupsServicesConnector m_groupData = null;
56
57 // Config Options
58 private bool m_groupMessagingEnabled;
59 private bool m_debugEnabled;
60
61 /// <summary>
62 /// If enabled, module only tries to send group IMs to online users by querying cached presence information.
63 /// </summary>
64 private bool m_messageOnlineAgentsOnly;
65
66 /// <summary>
67 /// Cache for online users.
68 /// </summary>
69 /// <remarks>
70 /// Group ID is key, presence information for online members is value.
71 /// Will only be non-null if m_messageOnlineAgentsOnly = true
72 /// We cache here so that group messages don't constantly have to re-request the online user list to avoid
73 /// attempted expensive sending of messages to offline users.
74 /// The tradeoff is that a user that comes online will not receive messages consistently from all other users
75 /// until caches have updated.
76 /// Therefore, we set the cache expiry to just 20 seconds.
77 /// </remarks>
78 private ExpiringCache<UUID, PresenceInfo[]> m_usersOnlineCache;
79
80 private int m_usersOnlineCacheExpirySeconds = 20;
81
82 #region Region Module interfaceBase Members
83
84 public void Initialise(IConfigSource config)
85 {
86 IConfig groupsConfig = config.Configs["Groups"];
87
88 if (groupsConfig == null)
89 {
90 // Do not run this module by default.
91 return;
92 }
93 else
94 {
95 // if groups aren't enabled, we're not needed.
96 // if we're not specified as the connector to use, then we're not wanted
97 if ((groupsConfig.GetBoolean("Enabled", false) == false)
98 || (groupsConfig.GetString("MessagingModule", "") != Name))
99 {
100 m_groupMessagingEnabled = false;
101 return;
102 }
103
104 m_groupMessagingEnabled = groupsConfig.GetBoolean("MessagingEnabled", true);
105
106 if (!m_groupMessagingEnabled)
107 {
108 return;
109 }
110
111 m_messageOnlineAgentsOnly = groupsConfig.GetBoolean("MessageOnlineUsersOnly", false);
112
113 if (m_messageOnlineAgentsOnly)
114 m_usersOnlineCache = new ExpiringCache<UUID, PresenceInfo[]>();
115
116 m_debugEnabled = groupsConfig.GetBoolean("MessagingDebugEnabled", m_debugEnabled);
117 }
118
119 m_log.InfoFormat(
120 "[GROUPS-MESSAGING]: GroupsMessagingModule enabled with MessageOnlineOnly = {0}, DebugEnabled = {1}",
121 m_messageOnlineAgentsOnly, m_debugEnabled);
122 }
123
124 public void AddRegion(Scene scene)
125 {
126 if (!m_groupMessagingEnabled)
127 return;
128
129 scene.RegisterModuleInterface<IGroupsMessagingModule>(this);
130
131 scene.AddCommand(
132 "Debug",
133 this,
134 "debug groups messaging verbose",
135 "debug groups messaging verbose <true|false>",
136 "This setting turns on very verbose groups messaging debugging",
137 HandleDebugGroupsMessagingVerbose);
138 }
139
140 public void RegionLoaded(Scene scene)
141 {
142 if (!m_groupMessagingEnabled)
143 return;
144
145 if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
146
147 m_groupData = scene.RequestModuleInterface<IGroupsServicesConnector>();
148
149 // No groups module, no groups messaging
150 if (m_groupData == null)
151 {
152 m_log.Error("[GROUPS-MESSAGING]: Could not get IGroupsServicesConnector, GroupsMessagingModule is now disabled.");
153 Close();
154 m_groupMessagingEnabled = false;
155 return;
156 }
157
158 m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
159
160 // No message transfer module, no groups messaging
161 if (m_msgTransferModule == null)
162 {
163 m_log.Error("[GROUPS-MESSAGING]: Could not get MessageTransferModule");
164 Close();
165 m_groupMessagingEnabled = false;
166 return;
167 }
168
169 if (m_presenceService == null)
170 m_presenceService = scene.PresenceService;
171
172 m_sceneList.Add(scene);
173
174 scene.EventManager.OnNewClient += OnNewClient;
175 scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
176 scene.EventManager.OnClientLogin += OnClientLogin;
177 }
178
179 public void RemoveRegion(Scene scene)
180 {
181 if (!m_groupMessagingEnabled)
182 return;
183
184 if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
185
186 m_sceneList.Remove(scene);
187 }
188
189 public void Close()
190 {
191 if (!m_groupMessagingEnabled)
192 return;
193
194 if (m_debugEnabled) m_log.Debug("[GROUPS-MESSAGING]: Shutting down GroupsMessagingModule module.");
195
196 foreach (Scene scene in m_sceneList)
197 {
198 scene.EventManager.OnNewClient -= OnNewClient;
199 scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage;
200 }
201
202 m_sceneList.Clear();
203
204 m_groupData = null;
205 m_msgTransferModule = null;
206 }
207
208 public Type ReplaceableInterface
209 {
210 get { return null; }
211 }
212
213 public string Name
214 {
215 get { return "GroupsMessagingModule"; }
216 }
217
218 #endregion
219
220 #region ISharedRegionModule Members
221
222 public void PostInitialise()
223 {
224 // NoOp
225 }
226
227 #endregion
228
229 private void HandleDebugGroupsMessagingVerbose(object modules, string[] args)
230 {
231 if (args.Length < 5)
232 {
233 MainConsole.Instance.Output("Usage: debug groups messaging verbose <true|false>");
234 return;
235 }
236
237 bool verbose = false;
238 if (!bool.TryParse(args[4], out verbose))
239 {
240 MainConsole.Instance.Output("Usage: debug groups messaging verbose <true|false>");
241 return;
242 }
243
244 m_debugEnabled = verbose;
245
246 MainConsole.Instance.OutputFormat("{0} verbose logging set to {1}", Name, m_debugEnabled);
247 }
248
249 /// <summary>
250 /// Not really needed, but does confirm that the group exists.
251 /// </summary>
252 public bool StartGroupChatSession(UUID agentID, UUID groupID)
253 {
254 if (m_debugEnabled)
255 m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
256
257 GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID, groupID, null);
258
259 if (groupInfo != null)
260 {
261 return true;
262 }
263 else
264 {
265 return false;
266 }
267 }
268
269 public void SendMessageToGroup(GridInstantMessage im, UUID groupID)
270 {
271 SendMessageToGroup(im, groupID, new UUID(im.fromAgentID), null);
272 }
273
274 public void SendMessageToGroup(
275 GridInstantMessage im, UUID groupID, UUID sendingAgentForGroupCalls, Func<GroupMembersData, bool> sendCondition)
276 {
277 int requestStartTick = Environment.TickCount;
278
279 List<GroupMembersData> groupMembers = m_groupData.GetGroupMembers(sendingAgentForGroupCalls, groupID);
280 int groupMembersCount = groupMembers.Count;
281 HashSet<string> attemptDeliveryUuidSet = null;
282
283 if (m_messageOnlineAgentsOnly)
284 {
285 string[] t1 = groupMembers.ConvertAll<string>(gmd => gmd.AgentID.ToString()).ToArray();
286
287 // We cache in order not to overwhlem the presence service on large grids with many groups. This does
288 // mean that members coming online will not see all group members until after m_usersOnlineCacheExpirySeconds has elapsed.
289 // (assuming this is the same across all grid simulators).
290 PresenceInfo[] onlineAgents;
291 if (!m_usersOnlineCache.TryGetValue(groupID, out onlineAgents))
292 {
293 onlineAgents = m_presenceService.GetAgents(t1);
294 m_usersOnlineCache.Add(groupID, onlineAgents, m_usersOnlineCacheExpirySeconds);
295 }
296
297 attemptDeliveryUuidSet
298 = new HashSet<string>(Array.ConvertAll<PresenceInfo, string>(onlineAgents, pi => pi.UserID));
299
300 //Array.ForEach<PresenceInfo>(onlineAgents, pi => attemptDeliveryUuidSet.Add(pi.UserID));
301
302 //groupMembers = groupMembers.Where(gmd => onlineAgentsUuidSet.Contains(gmd.AgentID.ToString())).ToList();
303
304 // if (m_debugEnabled)
305// m_log.DebugFormat(
306// "[GROUPS-MESSAGING]: SendMessageToGroup called for group {0} with {1} visible members, {2} online",
307// groupID, groupMembersCount, groupMembers.Count());
308 }
309 else
310 {
311 attemptDeliveryUuidSet
312 = new HashSet<string>(groupMembers.ConvertAll<string>(gmd => gmd.AgentID.ToString()));
313
314 if (m_debugEnabled)
315 m_log.DebugFormat(
316 "[GROUPS-MESSAGING]: SendMessageToGroup called for group {0} with {1} visible members",
317 groupID, groupMembers.Count);
318 }
319
320 foreach (GroupMembersData member in groupMembers)
321 {
322 if (sendCondition != null)
323 {
324 if (!sendCondition(member))
325 {
326 if (m_debugEnabled)
327 m_log.DebugFormat(
328 "[GROUPS-MESSAGING]: Not sending to {0} as they do not fulfill send condition",
329 member.AgentID);
330
331 continue;
332 }
333 }
334 else if (m_groupData.hasAgentDroppedGroupChatSession(member.AgentID, groupID))
335 {
336 // Don't deliver messages to people who have dropped this session
337 if (m_debugEnabled)
338 m_log.DebugFormat(
339 "[GROUPS-MESSAGING]: {0} has dropped session, not delivering to them", member.AgentID);
340
341 continue;
342 }
343
344 // Copy Message
345 GridInstantMessage msg = new GridInstantMessage();
346 msg.imSessionID = im.imSessionID;
347 msg.fromAgentName = im.fromAgentName;
348 msg.message = im.message;
349 msg.dialog = im.dialog;
350 msg.offline = im.offline;
351 msg.ParentEstateID = im.ParentEstateID;
352 msg.Position = im.Position;
353 msg.RegionID = im.RegionID;
354 msg.binaryBucket = im.binaryBucket;
355 msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
356
357 msg.fromAgentID = im.fromAgentID;
358 msg.fromGroup = true;
359
360 msg.toAgentID = member.AgentID.Guid;
361
362 if (attemptDeliveryUuidSet.Contains(member.AgentID.ToString()))
363 {
364 IClientAPI client = GetActiveClient(member.AgentID);
365 if (client == null)
366 {
367 int startTick = Environment.TickCount;
368
369 // If they're not local, forward across the grid
370 m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { });
371
372 if (m_debugEnabled)
373 m_log.DebugFormat(
374 "[GROUPS-MESSAGING]: Delivering to {0} via grid took {1} ms",
375 member.AgentID, Environment.TickCount - startTick);
376 }
377 else
378 {
379 int startTick = Environment.TickCount;
380
381 ProcessMessageFromGroupSession(msg, client);
382
383 // Deliver locally, directly
384 if (m_debugEnabled)
385 m_log.DebugFormat(
386 "[GROUPS-MESSAGING]: Delivering to {0} locally took {1} ms",
387 member.AgentID, Environment.TickCount - startTick);
388 }
389 }
390 else
391 {
392 int startTick = Environment.TickCount;
393
394 m_msgTransferModule.HandleUndeliverableMessage(msg, delegate(bool success) { });
395
396 if (m_debugEnabled)
397 m_log.DebugFormat(
398 "[GROUPS-MESSAGING]: Handling undeliverable message for {0} took {1} ms",
399 member.AgentID, Environment.TickCount - startTick);
400 }
401 }
402
403 if (m_debugEnabled)
404 m_log.DebugFormat(
405 "[GROUPS-MESSAGING]: Total SendMessageToGroup for group {0} with {1} members, {2} candidates for delivery took {3} ms",
406 groupID, groupMembersCount, attemptDeliveryUuidSet.Count(), Environment.TickCount - requestStartTick);
407 }
408
409 #region SimGridEventHandlers
410
411 void OnClientLogin(IClientAPI client)
412 {
413 if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: OnInstantMessage registered for {0}", client.Name);
414 }
415
416 private void OnNewClient(IClientAPI client)
417 {
418 if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: OnInstantMessage registered for {0}", client.Name);
419
420 client.OnInstantMessage += OnInstantMessage;
421 }
422
423 private void OnGridInstantMessage(GridInstantMessage msg)
424 {
425 // The instant message module will only deliver messages of dialog types:
426 // MessageFromAgent, StartTyping, StopTyping, MessageFromObject
427 //
428 // Any other message type will not be delivered to a client by the
429 // Instant Message Module
430
431 if (m_debugEnabled)
432 {
433 m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
434
435 DebugGridInstantMessage(msg);
436 }
437
438 // Incoming message from a group
439 if ((msg.fromGroup == true) &&
440 ((msg.dialog == (byte)InstantMessageDialog.SessionSend)
441 || (msg.dialog == (byte)InstantMessageDialog.SessionAdd)
442 || (msg.dialog == (byte)InstantMessageDialog.SessionDrop)))
443 {
444 IClientAPI client = null;
445
446 if (msg.dialog == (byte)InstantMessageDialog.SessionSend)
447 {
448 client = GetActiveClient(new UUID(msg.toAgentID));
449
450 if (client != null)
451 {
452 if (m_debugEnabled)
453 m_log.DebugFormat("[GROUPS-MESSAGING]: Delivering to {0} locally", client.Name);
454 }
455 else
456 {
457 m_log.WarnFormat("[GROUPS-MESSAGING]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID);
458
459 return;
460 }
461 }
462
463 ProcessMessageFromGroupSession(msg, client);
464 }
465 }
466
467 private void ProcessMessageFromGroupSession(GridInstantMessage msg, IClientAPI client)
468 {
469 if (m_debugEnabled)
470 m_log.DebugFormat(
471 "[GROUPS-MESSAGING]: Session message from {0} going to agent {1}, sessionID {2}, type {3}",
472 msg.fromAgentName, msg.toAgentID, msg.imSessionID, (InstantMessageDialog)msg.dialog);
473
474 UUID AgentID = new UUID(msg.fromAgentID);
475 UUID GroupID = new UUID(msg.imSessionID);
476
477 switch (msg.dialog)
478 {
479 case (byte)InstantMessageDialog.SessionAdd:
480 m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID);
481 break;
482
483 case (byte)InstantMessageDialog.SessionDrop:
484 m_groupData.AgentDroppedFromGroupChatSession(AgentID, GroupID);
485 break;
486
487 case (byte)InstantMessageDialog.SessionSend:
488 if (!m_groupData.hasAgentDroppedGroupChatSession(AgentID, GroupID)
489 && !m_groupData.hasAgentBeenInvitedToGroupChatSession(AgentID, GroupID)
490 )
491 {
492 // Agent not in session and hasn't dropped from session
493 // Add them to the session for now, and Invite them
494 m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID);
495
496 GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero, GroupID, null);
497 if (groupInfo != null)
498 {
499 if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Sending chatterbox invite instant message");
500
501 // Force? open the group session dialog???
502 // and simultanously deliver the message, so we don't need to do a seperate client.SendInstantMessage(msg);
503 IEventQueue eq = client.Scene.RequestModuleInterface<IEventQueue>();
504 eq.ChatterboxInvitation(
505 GroupID
506 , groupInfo.GroupName
507 , new UUID(msg.fromAgentID)
508 , msg.message
509 , new UUID(msg.toAgentID)
510 , msg.fromAgentName
511 , msg.dialog
512 , msg.timestamp
513 , msg.offline == 1
514 , (int)msg.ParentEstateID
515 , msg.Position
516 , 1
517 , new UUID(msg.imSessionID)
518 , msg.fromGroup
519 , Utils.StringToBytes(groupInfo.GroupName)
520 );
521
522 eq.ChatterBoxSessionAgentListUpdates(
523 new UUID(GroupID)
524 , new UUID(msg.fromAgentID)
525 , new UUID(msg.toAgentID)
526 , false //canVoiceChat
527 , false //isModerator
528 , false //text mute
529 );
530 }
531
532 break;
533 }
534 else if (!m_groupData.hasAgentDroppedGroupChatSession(AgentID, GroupID))
535 {
536 // User hasn't dropped, so they're in the session,
537 // maybe we should deliver it.
538 client.SendInstantMessage(msg);
539 }
540
541 break;
542
543 default:
544 client.SendInstantMessage(msg);
545
546 break;;
547 }
548 }
549
550 #endregion
551
552 #region ClientEvents
553 private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im)
554 {
555 if (m_debugEnabled)
556 {
557 m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
558
559 DebugGridInstantMessage(im);
560 }
561
562 // Start group IM session
563 if ((im.dialog == (byte)InstantMessageDialog.SessionGroupStart))
564 {
565 if (m_debugEnabled) m_log.InfoFormat("[GROUPS-MESSAGING]: imSessionID({0}) toAgentID({1})", im.imSessionID, im.toAgentID);
566
567 UUID GroupID = new UUID(im.imSessionID);
568 UUID AgentID = new UUID(im.fromAgentID);
569
570 GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero, GroupID, null);
571
572 if (groupInfo != null)
573 {
574 m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID);
575
576 ChatterBoxSessionStartReplyViaCaps(remoteClient, groupInfo.GroupName, GroupID);
577
578 IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
579 queue.ChatterBoxSessionAgentListUpdates(
580 GroupID
581 , AgentID
582 , new UUID(im.toAgentID)
583 , false //canVoiceChat
584 , false //isModerator
585 , false //text mute
586 );
587 }
588 }
589
590 // Send a message from locally connected client to a group
591 if ((im.dialog == (byte)InstantMessageDialog.SessionSend))
592 {
593 UUID GroupID = new UUID(im.imSessionID);
594 UUID AgentID = new UUID(im.fromAgentID);
595
596 if (m_debugEnabled)
597 m_log.DebugFormat("[GROUPS-MESSAGING]: Send message to session for group {0} with session ID {1}", GroupID, im.imSessionID.ToString());
598
599 //If this agent is sending a message, then they want to be in the session
600 m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID);
601
602 SendMessageToGroup(im, GroupID);
603 }
604 }
605
606 #endregion
607
608 void ChatterBoxSessionStartReplyViaCaps(IClientAPI remoteClient, string groupName, UUID groupID)
609 {
610 if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
611
612 OSDMap moderatedMap = new OSDMap(4);
613 moderatedMap.Add("voice", OSD.FromBoolean(false));
614
615 OSDMap sessionMap = new OSDMap(4);
616 sessionMap.Add("moderated_mode", moderatedMap);
617 sessionMap.Add("session_name", OSD.FromString(groupName));
618 sessionMap.Add("type", OSD.FromInteger(0));
619 sessionMap.Add("voice_enabled", OSD.FromBoolean(false));
620
621 OSDMap bodyMap = new OSDMap(4);
622 bodyMap.Add("session_id", OSD.FromUUID(groupID));
623 bodyMap.Add("temp_session_id", OSD.FromUUID(groupID));
624 bodyMap.Add("success", OSD.FromBoolean(true));
625 bodyMap.Add("session_info", sessionMap);
626
627 IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
628
629 if (queue != null)
630 {
631 queue.Enqueue(queue.BuildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId);
632 }
633 }
634
635 private void DebugGridInstantMessage(GridInstantMessage im)
636 {
637 // Don't log any normal IMs (privacy!)
638 if (m_debugEnabled && im.dialog != (byte)InstantMessageDialog.MessageFromAgent)
639 {
640 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: fromGroup({0})", im.fromGroup ? "True" : "False");
641 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: Dialog({0})", (InstantMessageDialog)im.dialog);
642 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: fromAgentID({0})", im.fromAgentID);
643 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: fromAgentName({0})", im.fromAgentName);
644 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: imSessionID({0})", im.imSessionID);
645 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: message({0})", im.message);
646 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: offline({0})", im.offline);
647 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: toAgentID({0})", im.toAgentID);
648 m_log.DebugFormat("[GROUPS-MESSAGING]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket"));
649 }
650 }
651
652 #region Client Tools
653
654 /// <summary>
655 /// Try to find an active IClientAPI reference for agentID giving preference to root connections
656 /// </summary>
657 private IClientAPI GetActiveClient(UUID agentID)
658 {
659 if (m_debugEnabled)
660 m_log.DebugFormat("[GROUPS-MESSAGING]: Looking for local client {0}", agentID);
661
662 IClientAPI child = null;
663
664 // Try root avatar first
665 foreach (Scene scene in m_sceneList)
666 {
667 ScenePresence sp = scene.GetScenePresence(agentID);
668 if (sp != null)
669 {
670 if (!sp.IsChildAgent)
671 {
672 if (m_debugEnabled)
673 m_log.DebugFormat("[GROUPS-MESSAGING]: Found root agent for client : {0}", sp.ControllingClient.Name);
674
675 return sp.ControllingClient;
676 }
677 else
678 {
679 if (m_debugEnabled)
680 m_log.DebugFormat("[GROUPS-MESSAGING]: Found child agent for client : {0}", sp.ControllingClient.Name);
681
682 child = sp.ControllingClient;
683 }
684 }
685 }
686
687 // If we didn't find a root, then just return whichever child we found, or null if none
688 if (child == null)
689 {
690 if (m_debugEnabled)
691 m_log.DebugFormat("[GROUPS-MESSAGING]: Could not find local client for agent : {0}", agentID);
692 }
693 else
694 {
695 if (m_debugEnabled)
696 m_log.DebugFormat("[GROUPS-MESSAGING]: Returning child agent for client : {0}", child.Name);
697 }
698
699 return child;
700 }
701
702 #endregion
703 }
704}
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}
diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/IGroupsServicesConnector.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/IGroupsServicesConnector.cs
new file mode 100644
index 0000000..6b5b40a
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/IGroupsServicesConnector.cs
@@ -0,0 +1,121 @@
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 OpenMetaverse;
31using OpenSim.Framework;
32
33namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
34{
35 public interface IGroupsServicesConnector
36 {
37 UUID CreateGroup(UUID RequestingAgentID, string name, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish, UUID founderID);
38 void UpdateGroup(UUID RequestingAgentID, UUID groupID, string charter, bool showInList, UUID insigniaID, int membershipFee, bool openEnrollment, bool allowPublish, bool maturePublish);
39
40 /// <summary>
41 /// Get the group record.
42 /// </summary>
43 /// <returns></returns>
44 /// <param name='RequestingAgentID'>The UUID of the user making the request.</param>
45 /// <param name='GroupID'>
46 /// The ID of the record to retrieve.
47 /// GroupName may be specified instead, in which case this parameter will be UUID.Zero
48 /// </param>
49 /// <param name='GroupName'>
50 /// The name of the group to retrieve.
51 /// GroupID may be specified instead, in which case this parmeter will be null.
52 /// </param>
53 GroupRecord GetGroupRecord(UUID RequestingAgentID, UUID GroupID, string GroupName);
54
55 List<DirGroupsReplyData> FindGroups(UUID RequestingAgentID, string search);
56 List<GroupMembersData> GetGroupMembers(UUID RequestingAgentID, UUID GroupID);
57
58 void AddGroupRole(UUID RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers);
59 void UpdateGroupRole(UUID RequestingAgentID, UUID groupID, UUID roleID, string name, string description, string title, ulong powers);
60 void RemoveGroupRole(UUID RequestingAgentID, UUID groupID, UUID roleID);
61 List<GroupRolesData> GetGroupRoles(UUID RequestingAgentID, UUID GroupID);
62 List<GroupRoleMembersData> GetGroupRoleMembers(UUID RequestingAgentID, UUID GroupID);
63
64 void AddAgentToGroup(UUID RequestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID);
65 void RemoveAgentFromGroup(UUID RequestingAgentID, UUID AgentID, UUID GroupID);
66
67 void AddAgentToGroupInvite(UUID RequestingAgentID, UUID inviteID, UUID groupID, UUID roleID, UUID agentID);
68 GroupInviteInfo GetAgentToGroupInvite(UUID RequestingAgentID, UUID inviteID);
69 void RemoveAgentToGroupInvite(UUID RequestingAgentID, UUID inviteID);
70
71 void AddAgentToGroupRole(UUID RequestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID);
72 void RemoveAgentFromGroupRole(UUID RequestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID);
73 List<GroupRolesData> GetAgentGroupRoles(UUID RequestingAgentID, UUID AgentID, UUID GroupID);
74
75 void SetAgentActiveGroup(UUID RequestingAgentID, UUID AgentID, UUID GroupID);
76 GroupMembershipData GetAgentActiveMembership(UUID RequestingAgentID, UUID AgentID);
77
78 void SetAgentActiveGroupRole(UUID RequestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID);
79 void SetAgentGroupInfo(UUID RequestingAgentID, UUID AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile);
80
81 /// <summary>
82 /// Get information about a specific group to which the user belongs.
83 /// </summary>
84 /// <param name="RequestingAgentID">The agent requesting the information.</param>
85 /// <param name="AgentID">The agent requested.</param>
86 /// <param name="GroupID">The group requested.</param>
87 /// <returns>
88 /// If the user is a member of the group then the data structure is returned. If not, then null is returned.
89 /// </returns>
90 GroupMembershipData GetAgentGroupMembership(UUID RequestingAgentID, UUID AgentID, UUID GroupID);
91
92 /// <summary>
93 /// Get information about the groups to which a user belongs.
94 /// </summary>
95 /// <param name="RequestingAgentID">The agent requesting the information.</param>
96 /// <param name="AgentID">The agent requested.</param>
97 /// <returns>
98 /// Information about the groups to which the user belongs. If the user belongs to no groups then an empty
99 /// list is returned.
100 /// </returns>
101 List<GroupMembershipData> GetAgentGroupMemberships(UUID RequestingAgentID, UUID AgentID);
102
103 void AddGroupNotice(UUID RequestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, byte[] binaryBucket);
104 GroupNoticeInfo GetGroupNotice(UUID RequestingAgentID, UUID noticeID);
105 List<GroupNoticeData> GetGroupNotices(UUID RequestingAgentID, UUID GroupID);
106
107 void ResetAgentGroupChatSessions(UUID agentID);
108 bool hasAgentBeenInvitedToGroupChatSession(UUID agentID, UUID groupID);
109 bool hasAgentDroppedGroupChatSession(UUID agentID, UUID groupID);
110 void AgentDroppedFromGroupChatSession(UUID agentID, UUID groupID);
111 void AgentInvitedToGroupChatSession(UUID agentID, UUID groupID);
112 }
113
114 public class GroupInviteInfo
115 {
116 public UUID GroupID = UUID.Zero;
117 public UUID RoleID = UUID.Zero;
118 public UUID AgentID = UUID.Zero;
119 public UUID InviteID = UUID.Zero;
120 }
121}
diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/SimianGroupsServicesConnectorModule.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/SimianGroupsServicesConnectorModule.cs
new file mode 100644
index 0000000..8095b28
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/SimianGroupsServicesConnectorModule.cs
@@ -0,0 +1,1441 @@
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;
30using System.Collections.Generic;
31using System.Collections.Specialized;
32using System.Reflection;
33using System.Threading;
34
35using Nwc.XmlRpc;
36
37using log4net;
38using Mono.Addins;
39using Nini.Config;
40
41using OpenMetaverse;
42using OpenMetaverse.StructuredData;
43
44using OpenSim.Framework;
45using OpenSim.Framework.Communications;
46using OpenSim.Region.Framework.Interfaces;
47using OpenSim.Services.Interfaces;
48
49/***************************************************************************
50 * Simian Data Map
51 * ===============
52 *
53 * OwnerID -> Type -> Key
54 * -----------------------
55 *
56 * UserID -> Group -> ActiveGroup
57 * + GroupID
58 *
59 * UserID -> GroupSessionDropped -> GroupID
60 * UserID -> GroupSessionInvited -> GroupID
61 *
62 * UserID -> GroupMember -> GroupID
63 * + SelectedRoleID [UUID]
64 * + AcceptNotices [bool]
65 * + ListInProfile [bool]
66 * + Contribution [int]
67 *
68 * UserID -> GroupRole[GroupID] -> RoleID
69 *
70 *
71 * GroupID -> Group -> GroupName
72 * + Charter
73 * + ShowInList
74 * + InsigniaID
75 * + MembershipFee
76 * + OpenEnrollment
77 * + AllowPublish
78 * + MaturePublish
79 * + FounderID
80 * + EveryonePowers
81 * + OwnerRoleID
82 * + OwnersPowers
83 *
84 * GroupID -> GroupRole -> RoleID
85 * + Name
86 * + Description
87 * + Title
88 * + Powers
89 *
90 * GroupID -> GroupMemberInvite -> InviteID
91 * + AgentID
92 * + RoleID
93 *
94 * GroupID -> GroupNotice -> NoticeID
95 * + TimeStamp [uint]
96 * + FromName [string]
97 * + Subject [string]
98 * + Message [string]
99 * + BinaryBucket [byte[]]
100 *
101 * */
102
103namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
104{
105 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "SimianGroupsServicesConnectorModule")]
106 public class SimianGroupsServicesConnectorModule : ISharedRegionModule, IGroupsServicesConnector
107 {
108 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
109
110 public const GroupPowers m_DefaultEveryonePowers = GroupPowers.AllowSetHome |
111 GroupPowers.Accountable |
112 GroupPowers.JoinChat |
113 GroupPowers.AllowVoiceChat |
114 GroupPowers.ReceiveNotices |
115 GroupPowers.StartProposal |
116 GroupPowers.VoteOnProposal;
117
118 // Would this be cleaner as (GroupPowers)ulong.MaxValue;
119 public const GroupPowers m_DefaultOwnerPowers = GroupPowers.Accountable
120 | GroupPowers.AllowEditLand
121 | GroupPowers.AllowFly
122 | GroupPowers.AllowLandmark
123 | GroupPowers.AllowRez
124 | GroupPowers.AllowSetHome
125 | GroupPowers.AllowVoiceChat
126 | GroupPowers.AssignMember
127 | GroupPowers.AssignMemberLimited
128 | GroupPowers.ChangeActions
129 | GroupPowers.ChangeIdentity
130 | GroupPowers.ChangeMedia
131 | GroupPowers.ChangeOptions
132 | GroupPowers.CreateRole
133 | GroupPowers.DeedObject
134 | GroupPowers.DeleteRole
135 | GroupPowers.Eject
136 | GroupPowers.FindPlaces
137 | GroupPowers.Invite
138 | GroupPowers.JoinChat
139 | GroupPowers.LandChangeIdentity
140 | GroupPowers.LandDeed
141 | GroupPowers.LandDivideJoin
142 | GroupPowers.LandEdit
143 | GroupPowers.LandEjectAndFreeze
144 | GroupPowers.LandGardening
145 | GroupPowers.LandManageAllowed
146 | GroupPowers.LandManageBanned
147 | GroupPowers.LandManagePasses
148 | GroupPowers.LandOptions
149 | GroupPowers.LandRelease
150 | GroupPowers.LandSetSale
151 | GroupPowers.ModerateChat
152 | GroupPowers.ObjectManipulate
153 | GroupPowers.ObjectSetForSale
154 | GroupPowers.ReceiveNotices
155 | GroupPowers.RemoveMember
156 | GroupPowers.ReturnGroupOwned
157 | GroupPowers.ReturnGroupSet
158 | GroupPowers.ReturnNonGroup
159 | GroupPowers.RoleProperties
160 | GroupPowers.SendNotices
161 | GroupPowers.SetLandingPoint
162 | GroupPowers.StartProposal
163 | GroupPowers.VoteOnProposal;
164
165 private bool m_connectorEnabled = false;
166
167 private string m_groupsServerURI = string.Empty;
168
169 private bool m_debugEnabled = false;
170
171 private Dictionary<string, bool> m_pendingRequests = new Dictionary<string,bool>();
172
173 private ExpiringCache<string, OSDMap> m_memoryCache;
174 private int m_cacheTimeout = 30;
175
176 // private IUserAccountService m_accountService = null;
177
178
179 #region Region Module interfaceBase Members
180
181 public string Name
182 {
183 get { return "SimianGroupsServicesConnector"; }
184 }
185
186 // this module is not intended to be replaced, but there should only be 1 of them.
187 public Type ReplaceableInterface
188 {
189 get { return null; }
190 }
191
192 public void Initialise(IConfigSource config)
193 {
194 IConfig groupsConfig = config.Configs["Groups"];
195
196 if (groupsConfig == null)
197 {
198 // Do not run this module by default.
199 return;
200 }
201 else
202 {
203 // if groups aren't enabled, we're not needed.
204 // if we're not specified as the connector to use, then we're not wanted
205 if ((groupsConfig.GetBoolean("Enabled", false) == false)
206 || (groupsConfig.GetString("ServicesConnectorModule", "XmlRpcGroupsServicesConnector") != Name))
207 {
208 m_connectorEnabled = false;
209 return;
210 }
211
212 m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR]: Initializing {0}", this.Name);
213
214 m_groupsServerURI = groupsConfig.GetString("GroupsServerURI", string.Empty);
215 if (string.IsNullOrEmpty(m_groupsServerURI))
216 {
217 m_log.ErrorFormat("Please specify a valid Simian Server for GroupsServerURI in OpenSim.ini, [Groups]");
218 m_connectorEnabled = false;
219 return;
220 }
221
222
223 m_cacheTimeout = groupsConfig.GetInt("GroupsCacheTimeout", 30);
224 if (m_cacheTimeout == 0)
225 {
226 m_log.WarnFormat("[SIMIAN-GROUPS-CONNECTOR] Groups Cache Disabled.");
227 }
228 else
229 {
230 m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] Groups Cache Timeout set to {0}.", m_cacheTimeout);
231 }
232
233
234
235 m_memoryCache = new ExpiringCache<string,OSDMap>();
236
237
238 // If we got all the config options we need, lets start'er'up
239 m_connectorEnabled = true;
240
241 m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", true);
242
243 }
244 }
245
246 public void Close()
247 {
248 m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR]: Closing {0}", this.Name);
249 }
250
251 public void AddRegion(OpenSim.Region.Framework.Scenes.Scene scene)
252 {
253 if (m_connectorEnabled)
254 {
255 scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
256 }
257 }
258
259 public void RemoveRegion(OpenSim.Region.Framework.Scenes.Scene scene)
260 {
261 if (scene.RequestModuleInterface<IGroupsServicesConnector>() == this)
262 {
263 scene.UnregisterModuleInterface<IGroupsServicesConnector>(this);
264 }
265 }
266
267 public void RegionLoaded(OpenSim.Region.Framework.Scenes.Scene scene)
268 {
269 // TODO: May want to consider listenning for Agent Connections so we can pre-cache group info
270 // scene.EventManager.OnNewClient += OnNewClient;
271 }
272
273 #endregion
274
275 #region ISharedRegionModule Members
276
277 public void PostInitialise()
278 {
279 // NoOp
280 }
281
282 #endregion
283
284
285
286
287 #region IGroupsServicesConnector Members
288
289 /// <summary>
290 /// Create a Group, including Everyone and Owners Role, place FounderID in both groups, select Owner as selected role, and newly created group as agent's active role.
291 /// </summary>
292 public UUID CreateGroup(UUID requestingAgentID, string name, string charter, bool showInList, UUID insigniaID,
293 int membershipFee, bool openEnrollment, bool allowPublish,
294 bool maturePublish, UUID founderID)
295 {
296 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
297
298 UUID GroupID = UUID.Random();
299 UUID OwnerRoleID = UUID.Random();
300
301 OSDMap GroupInfoMap = new OSDMap();
302 GroupInfoMap["Charter"] = OSD.FromString(charter);
303 GroupInfoMap["ShowInList"] = OSD.FromBoolean(showInList);
304 GroupInfoMap["InsigniaID"] = OSD.FromUUID(insigniaID);
305 GroupInfoMap["MembershipFee"] = OSD.FromInteger(0);
306 GroupInfoMap["OpenEnrollment"] = OSD.FromBoolean(openEnrollment);
307 GroupInfoMap["AllowPublish"] = OSD.FromBoolean(allowPublish);
308 GroupInfoMap["MaturePublish"] = OSD.FromBoolean(maturePublish);
309 GroupInfoMap["FounderID"] = OSD.FromUUID(founderID);
310 GroupInfoMap["EveryonePowers"] = OSD.FromULong((ulong)m_DefaultEveryonePowers);
311 GroupInfoMap["OwnerRoleID"] = OSD.FromUUID(OwnerRoleID);
312 GroupInfoMap["OwnersPowers"] = OSD.FromULong((ulong)m_DefaultOwnerPowers);
313
314 if (SimianAddGeneric(GroupID, "Group", name, GroupInfoMap))
315 {
316 AddGroupRole(requestingAgentID, GroupID, UUID.Zero, "Everyone", "Members of " + name, "Member of " + name, (ulong)m_DefaultEveryonePowers);
317 AddGroupRole(requestingAgentID, GroupID, OwnerRoleID, "Owners", "Owners of " + name, "Owner of " + name, (ulong)m_DefaultOwnerPowers);
318
319 AddAgentToGroup(requestingAgentID, requestingAgentID, GroupID, OwnerRoleID);
320
321 return GroupID;
322 }
323 else
324 {
325 return UUID.Zero;
326 }
327 }
328
329
330 public void UpdateGroup(UUID requestingAgentID, UUID groupID, string charter, bool showInList,
331 UUID insigniaID, int membershipFee, bool openEnrollment,
332 bool allowPublish, bool maturePublish)
333 {
334 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
335 // TODO: Check to make sure requestingAgentID has permission to update group
336
337 string GroupName;
338 OSDMap GroupInfoMap;
339 if (SimianGetFirstGenericEntry(groupID, "GroupInfo", out GroupName, out GroupInfoMap))
340 {
341 GroupInfoMap["Charter"] = OSD.FromString(charter);
342 GroupInfoMap["ShowInList"] = OSD.FromBoolean(showInList);
343 GroupInfoMap["InsigniaID"] = OSD.FromUUID(insigniaID);
344 GroupInfoMap["MembershipFee"] = OSD.FromInteger(0);
345 GroupInfoMap["OpenEnrollment"] = OSD.FromBoolean(openEnrollment);
346 GroupInfoMap["AllowPublish"] = OSD.FromBoolean(allowPublish);
347 GroupInfoMap["MaturePublish"] = OSD.FromBoolean(maturePublish);
348
349 SimianAddGeneric(groupID, "Group", GroupName, GroupInfoMap);
350 }
351
352 }
353
354
355 public void AddGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID, string name, string description,
356 string title, ulong powers)
357 {
358 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
359
360 OSDMap GroupRoleInfo = new OSDMap();
361 GroupRoleInfo["Name"] = OSD.FromString(name);
362 GroupRoleInfo["Description"] = OSD.FromString(description);
363 GroupRoleInfo["Title"] = OSD.FromString(title);
364 GroupRoleInfo["Powers"] = OSD.FromULong((ulong)powers);
365
366 // TODO: Add security, make sure that requestingAgentID has permision to add roles
367 SimianAddGeneric(groupID, "GroupRole", roleID.ToString(), GroupRoleInfo);
368 }
369
370 public void RemoveGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID)
371 {
372 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
373
374 // TODO: Add security
375
376 // Can't delete the Everyone Role
377 if (roleID != UUID.Zero)
378 {
379 // Remove all GroupRole Members from Role
380 Dictionary<UUID, OSDMap> GroupRoleMembers;
381 string GroupRoleMemberType = "GroupRole" + groupID.ToString();
382 if (SimianGetGenericEntries(GroupRoleMemberType, roleID.ToString(), out GroupRoleMembers))
383 {
384 foreach (UUID UserID in GroupRoleMembers.Keys)
385 {
386 EnsureRoleNotSelectedByMember(groupID, roleID, UserID);
387
388 SimianRemoveGenericEntry(UserID, GroupRoleMemberType, roleID.ToString());
389 }
390 }
391
392 // Remove role
393 SimianRemoveGenericEntry(groupID, "GroupRole", roleID.ToString());
394 }
395 }
396
397
398 public void UpdateGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID, string name, string description,
399 string title, ulong powers)
400 {
401 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
402
403 // TODO: Security, check that requestingAgentID is allowed to update group roles
404
405 OSDMap GroupRoleInfo;
406 if (SimianGetGenericEntry(groupID, "GroupRole", roleID.ToString(), out GroupRoleInfo))
407 {
408 if (name != null)
409 {
410 GroupRoleInfo["Name"] = OSD.FromString(name);
411 }
412 if (description != null)
413 {
414 GroupRoleInfo["Description"] = OSD.FromString(description);
415 }
416 if (title != null)
417 {
418 GroupRoleInfo["Title"] = OSD.FromString(title);
419 }
420 GroupRoleInfo["Powers"] = OSD.FromULong((ulong)powers);
421
422 }
423
424
425 SimianAddGeneric(groupID, "GroupRole", roleID.ToString(), GroupRoleInfo);
426 }
427
428 public GroupRecord GetGroupRecord(UUID requestingAgentID, UUID groupID, string groupName)
429 {
430 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
431
432 OSDMap GroupInfoMap = null;
433 if (groupID != UUID.Zero)
434 {
435 if (!SimianGetFirstGenericEntry(groupID, "Group", out groupName, out GroupInfoMap))
436 {
437 return null;
438 }
439 }
440 else if (!string.IsNullOrEmpty(groupName))
441 {
442 if (!SimianGetFirstGenericEntry("Group", groupName, out groupID, out GroupInfoMap))
443 {
444 return null;
445 }
446 }
447
448 GroupRecord GroupInfo = new GroupRecord();
449
450 GroupInfo.GroupID = groupID;
451 GroupInfo.GroupName = groupName;
452 GroupInfo.Charter = GroupInfoMap["Charter"].AsString();
453 GroupInfo.ShowInList = GroupInfoMap["ShowInList"].AsBoolean();
454 GroupInfo.GroupPicture = GroupInfoMap["InsigniaID"].AsUUID();
455 GroupInfo.MembershipFee = GroupInfoMap["MembershipFee"].AsInteger();
456 GroupInfo.OpenEnrollment = GroupInfoMap["OpenEnrollment"].AsBoolean();
457 GroupInfo.AllowPublish = GroupInfoMap["AllowPublish"].AsBoolean();
458 GroupInfo.MaturePublish = GroupInfoMap["MaturePublish"].AsBoolean();
459 GroupInfo.FounderID = GroupInfoMap["FounderID"].AsUUID();
460 GroupInfo.OwnerRoleID = GroupInfoMap["OwnerRoleID"].AsUUID();
461
462 return GroupInfo;
463
464 }
465
466 public GroupProfileData GetMemberGroupProfile(UUID requestingAgentID, UUID groupID, UUID memberID)
467 {
468 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
469
470 OSDMap groupProfile;
471 string groupName;
472 if (!SimianGetFirstGenericEntry(groupID, "Group", out groupName, out groupProfile))
473 {
474 // GroupProfileData is not nullable
475 return new GroupProfileData();
476 }
477
478 GroupProfileData MemberGroupProfile = new GroupProfileData();
479 MemberGroupProfile.GroupID = groupID;
480 MemberGroupProfile.Name = groupName;
481
482 if (groupProfile["Charter"] != null)
483 {
484 MemberGroupProfile.Charter = groupProfile["Charter"].AsString();
485 }
486
487 MemberGroupProfile.ShowInList = groupProfile["ShowInList"].AsString() == "1";
488 MemberGroupProfile.InsigniaID = groupProfile["InsigniaID"].AsUUID();
489 MemberGroupProfile.MembershipFee = groupProfile["MembershipFee"].AsInteger();
490 MemberGroupProfile.OpenEnrollment = groupProfile["OpenEnrollment"].AsBoolean();
491 MemberGroupProfile.AllowPublish = groupProfile["AllowPublish"].AsBoolean();
492 MemberGroupProfile.MaturePublish = groupProfile["MaturePublish"].AsBoolean();
493 MemberGroupProfile.FounderID = groupProfile["FounderID"].AsUUID();;
494 MemberGroupProfile.OwnerRole = groupProfile["OwnerRoleID"].AsUUID();
495
496 Dictionary<UUID, OSDMap> Members;
497 if (SimianGetGenericEntries("GroupMember",groupID.ToString(), out Members))
498 {
499 MemberGroupProfile.GroupMembershipCount = Members.Count;
500 }
501
502 Dictionary<string, OSDMap> Roles;
503 if (SimianGetGenericEntries(groupID, "GroupRole", out Roles))
504 {
505 MemberGroupProfile.GroupRolesCount = Roles.Count;
506 }
507
508 // TODO: Get Group Money balance from somewhere
509 // group.Money = 0;
510
511 GroupMembershipData MemberInfo = GetAgentGroupMembership(requestingAgentID, memberID, groupID);
512
513 MemberGroupProfile.MemberTitle = MemberInfo.GroupTitle;
514 MemberGroupProfile.PowersMask = MemberInfo.GroupPowers;
515
516 return MemberGroupProfile;
517 }
518
519 public void SetAgentActiveGroup(UUID requestingAgentID, UUID agentID, UUID groupID)
520 {
521 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
522
523 OSDMap ActiveGroup = new OSDMap();
524 ActiveGroup.Add("GroupID", OSD.FromUUID(groupID));
525 SimianAddGeneric(agentID, "Group", "ActiveGroup", ActiveGroup);
526 }
527
528 public void SetAgentActiveGroupRole(UUID requestingAgentID, UUID agentID, UUID groupID, UUID roleID)
529 {
530 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
531
532 OSDMap GroupMemberInfo;
533 if (!SimianGetGenericEntry(agentID, "GroupMember", groupID.ToString(), out GroupMemberInfo))
534 {
535 GroupMemberInfo = new OSDMap();
536 }
537
538 GroupMemberInfo["SelectedRoleID"] = OSD.FromUUID(roleID);
539 SimianAddGeneric(agentID, "GroupMember", groupID.ToString(), GroupMemberInfo);
540 }
541
542 public void SetAgentGroupInfo(UUID requestingAgentID, UUID agentID, UUID groupID, bool acceptNotices, bool listInProfile)
543 {
544 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
545
546 OSDMap GroupMemberInfo;
547 if (!SimianGetGenericEntry(agentID, "GroupMember", groupID.ToString(), out GroupMemberInfo))
548 {
549 GroupMemberInfo = new OSDMap();
550 }
551
552 GroupMemberInfo["AcceptNotices"] = OSD.FromBoolean(acceptNotices);
553 GroupMemberInfo["ListInProfile"] = OSD.FromBoolean(listInProfile);
554 GroupMemberInfo["Contribution"] = OSD.FromInteger(0);
555 GroupMemberInfo["SelectedRole"] = OSD.FromUUID(UUID.Zero);
556 SimianAddGeneric(agentID, "GroupMember", groupID.ToString(), GroupMemberInfo);
557 }
558
559 public void AddAgentToGroupInvite(UUID requestingAgentID, UUID inviteID, UUID groupID, UUID roleID, UUID agentID)
560 {
561 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
562
563 OSDMap Invite = new OSDMap();
564 Invite["AgentID"] = OSD.FromUUID(agentID);
565 Invite["RoleID"] = OSD.FromUUID(roleID);
566
567 SimianAddGeneric(groupID, "GroupMemberInvite", inviteID.ToString(), Invite);
568 }
569
570 public GroupInviteInfo GetAgentToGroupInvite(UUID requestingAgentID, UUID inviteID)
571 {
572 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
573
574 OSDMap GroupMemberInvite;
575 UUID GroupID;
576 if (!SimianGetFirstGenericEntry("GroupMemberInvite", inviteID.ToString(), out GroupID, out GroupMemberInvite))
577 {
578 return null;
579 }
580
581 GroupInviteInfo inviteInfo = new GroupInviteInfo();
582 inviteInfo.InviteID = inviteID;
583 inviteInfo.GroupID = GroupID;
584 inviteInfo.AgentID = GroupMemberInvite["AgentID"].AsUUID();
585 inviteInfo.RoleID = GroupMemberInvite["RoleID"].AsUUID();
586
587 return inviteInfo;
588 }
589
590 public void RemoveAgentToGroupInvite(UUID requestingAgentID, UUID inviteID)
591 {
592 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
593
594 GroupInviteInfo invite = GetAgentToGroupInvite(requestingAgentID, inviteID);
595 SimianRemoveGenericEntry(invite.GroupID, "GroupMemberInvite", inviteID.ToString());
596 }
597
598 public void AddAgentToGroup(UUID requestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID)
599 {
600 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
601
602 // Setup Agent/Group information
603 SetAgentGroupInfo(requestingAgentID, AgentID, GroupID, true, true);
604
605 // Add agent to Everyone Group
606 AddAgentToGroupRole(requestingAgentID, AgentID, GroupID, UUID.Zero);
607
608 // Add agent to Specified Role
609 AddAgentToGroupRole(requestingAgentID, AgentID, GroupID, RoleID);
610
611 // Set selected role in this group to specified role
612 SetAgentActiveGroupRole(requestingAgentID, AgentID, GroupID, RoleID);
613 }
614
615 public void RemoveAgentFromGroup(UUID requestingAgentID, UUID agentID, UUID groupID)
616 {
617 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
618
619 // If current active group is the group the agent is being removed from, change their group to UUID.Zero
620 GroupMembershipData memberActiveMembership = GetAgentActiveMembership(requestingAgentID, agentID);
621 if (memberActiveMembership.GroupID == groupID)
622 {
623 SetAgentActiveGroup(agentID, agentID, UUID.Zero);
624 }
625
626 // Remove Group Member information for this group
627 SimianRemoveGenericEntry(agentID, "GroupMember", groupID.ToString());
628
629 // By using a Simian Generics Type consisting of a prefix and a groupID,
630 // combined with RoleID as key allows us to get a list of roles a particular member
631 // of a group is assigned to.
632 string GroupRoleMemberType = "GroupRole" + groupID.ToString();
633
634 // Take Agent out of all other group roles
635 Dictionary<string, OSDMap> GroupRoles;
636 if (SimianGetGenericEntries(agentID, GroupRoleMemberType, out GroupRoles))
637 {
638 foreach (string roleID in GroupRoles.Keys)
639 {
640 SimianRemoveGenericEntry(agentID, GroupRoleMemberType, roleID);
641 }
642 }
643 }
644
645 public void AddAgentToGroupRole(UUID requestingAgentID, UUID agentID, UUID groupID, UUID roleID)
646 {
647 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
648
649 SimianAddGeneric(agentID, "GroupRole" + groupID.ToString(), roleID.ToString(), new OSDMap());
650 }
651
652 public void RemoveAgentFromGroupRole(UUID requestingAgentID, UUID agentID, UUID groupID, UUID roleID)
653 {
654 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
655
656 // Cannot remove members from the Everyone Role
657 if (roleID != UUID.Zero)
658 {
659 EnsureRoleNotSelectedByMember(groupID, roleID, agentID);
660
661 string GroupRoleMemberType = "GroupRole" + groupID.ToString();
662 SimianRemoveGenericEntry(agentID, GroupRoleMemberType, roleID.ToString());
663 }
664 }
665
666 public List<DirGroupsReplyData> FindGroups(UUID requestingAgentID, string search)
667 {
668 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
669
670 List<DirGroupsReplyData> findings = new List<DirGroupsReplyData>();
671
672 NameValueCollection requestArgs = new NameValueCollection
673 {
674 { "RequestMethod", "GetGenerics" },
675 { "Type", "Group" },
676 { "Key", search },
677 { "Fuzzy", "1" }
678 };
679
680
681 OSDMap response = CachedPostRequest(requestArgs);
682 if (response["Success"].AsBoolean() && response["Entries"] is OSDArray)
683 {
684 OSDArray entryArray = (OSDArray)response["Entries"];
685 foreach (OSDMap entryMap in entryArray)
686 {
687 DirGroupsReplyData data = new DirGroupsReplyData();
688 data.groupID = entryMap["OwnerID"].AsUUID();
689 data.groupName = entryMap["Key"].AsString();
690
691 // TODO: is there a better way to do this?
692 Dictionary<UUID, OSDMap> Members;
693 if (SimianGetGenericEntries("GroupMember", data.groupID.ToString(), out Members))
694 {
695 data.members = Members.Count;
696 }
697 else
698 {
699 data.members = 0;
700 }
701
702 // TODO: sort results?
703 // data.searchOrder = order;
704
705 findings.Add(data);
706 }
707 }
708
709 return findings;
710 }
711
712 public GroupMembershipData GetAgentGroupMembership(UUID requestingAgentID, UUID agentID, UUID groupID)
713 {
714 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
715
716 GroupMembershipData data = null;
717// bool foundData = false;
718
719 OSDMap UserGroupMemberInfo;
720 if (SimianGetGenericEntry(agentID, "GroupMember", groupID.ToString(), out UserGroupMemberInfo))
721 {
722 data = new GroupMembershipData();
723 data.AcceptNotices = UserGroupMemberInfo["AcceptNotices"].AsBoolean();
724 data.Contribution = UserGroupMemberInfo["Contribution"].AsInteger();
725 data.ListInProfile = UserGroupMemberInfo["ListInProfile"].AsBoolean();
726 data.ActiveRole = UserGroupMemberInfo["SelectedRoleID"].AsUUID();
727
728 ///////////////////////////////
729 // Agent Specific Information:
730 //
731 OSDMap UserActiveGroup;
732 if (SimianGetGenericEntry(agentID, "Group", "ActiveGroup", out UserActiveGroup))
733 {
734 data.Active = UserActiveGroup["GroupID"].AsUUID().Equals(groupID);
735 }
736
737 ///////////////////////////////
738 // Role Specific Information:
739 //
740 OSDMap GroupRoleInfo;
741 if (SimianGetGenericEntry(groupID, "GroupRole", data.ActiveRole.ToString(), out GroupRoleInfo))
742 {
743 data.GroupTitle = GroupRoleInfo["Title"].AsString();
744 data.GroupPowers = GroupRoleInfo["Powers"].AsULong();
745 }
746
747 ///////////////////////////////
748 // Group Specific Information:
749 //
750 OSDMap GroupInfo;
751 string GroupName;
752 if (SimianGetFirstGenericEntry(groupID, "Group", out GroupName, out GroupInfo))
753 {
754 data.GroupID = groupID;
755 data.AllowPublish = GroupInfo["AllowPublish"].AsBoolean();
756 data.Charter = GroupInfo["Charter"].AsString();
757 data.FounderID = GroupInfo["FounderID"].AsUUID();
758 data.GroupName = GroupName;
759 data.GroupPicture = GroupInfo["InsigniaID"].AsUUID();
760 data.MaturePublish = GroupInfo["MaturePublish"].AsBoolean();
761 data.MembershipFee = GroupInfo["MembershipFee"].AsInteger();
762 data.OpenEnrollment = GroupInfo["OpenEnrollment"].AsBoolean();
763 data.ShowInList = GroupInfo["ShowInList"].AsBoolean();
764 }
765 }
766
767 return data;
768 }
769
770 public GroupMembershipData GetAgentActiveMembership(UUID requestingAgentID, UUID agentID)
771 {
772 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
773
774 UUID GroupID = UUID.Zero;
775 OSDMap UserActiveGroup;
776 if (SimianGetGenericEntry(agentID, "Group", "ActiveGroup", out UserActiveGroup))
777 {
778 GroupID = UserActiveGroup["GroupID"].AsUUID();
779 }
780
781 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] Active GroupID : {0}", GroupID.ToString());
782 return GetAgentGroupMembership(requestingAgentID, agentID, GroupID);
783 }
784
785 public List<GroupMembershipData> GetAgentGroupMemberships(UUID requestingAgentID, UUID agentID)
786 {
787 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
788
789 List<GroupMembershipData> memberships = new List<GroupMembershipData>();
790
791 Dictionary<string,OSDMap> GroupMemberShips;
792 if (SimianGetGenericEntries(agentID, "GroupMember", out GroupMemberShips))
793 {
794 foreach (string key in GroupMemberShips.Keys)
795 {
796 memberships.Add(GetAgentGroupMembership(requestingAgentID, agentID, UUID.Parse(key)));
797 }
798 }
799
800 return memberships;
801 }
802
803 public List<GroupRolesData> GetAgentGroupRoles(UUID requestingAgentID, UUID agentID, UUID groupID)
804 {
805 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
806
807 List<GroupRolesData> Roles = new List<GroupRolesData>();
808
809 Dictionary<string, OSDMap> GroupRoles;
810 if (SimianGetGenericEntries(groupID, "GroupRole", out GroupRoles))
811 {
812 Dictionary<string, OSDMap> MemberRoles;
813 if (SimianGetGenericEntries(agentID, "GroupRole" + groupID.ToString(), out MemberRoles))
814 {
815 foreach (KeyValuePair<string, OSDMap> kvp in MemberRoles)
816 {
817 GroupRolesData data = new GroupRolesData();
818 data.RoleID = UUID.Parse(kvp.Key);
819 data.Name = GroupRoles[kvp.Key]["Name"].AsString();
820 data.Description = GroupRoles[kvp.Key]["Description"].AsString();
821 data.Title = GroupRoles[kvp.Key]["Title"].AsString();
822 data.Powers = GroupRoles[kvp.Key]["Powers"].AsULong();
823
824 Roles.Add(data);
825 }
826 }
827 }
828 return Roles;
829 }
830
831 public List<GroupRolesData> GetGroupRoles(UUID requestingAgentID, UUID groupID)
832 {
833 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
834
835 List<GroupRolesData> Roles = new List<GroupRolesData>();
836
837 Dictionary<string, OSDMap> GroupRoles;
838 if (SimianGetGenericEntries(groupID, "GroupRole", out GroupRoles))
839 {
840 foreach (KeyValuePair<string, OSDMap> role in GroupRoles)
841 {
842 GroupRolesData data = new GroupRolesData();
843
844 data.RoleID = UUID.Parse(role.Key);
845
846 data.Name = role.Value["Name"].AsString();
847 data.Description = role.Value["Description"].AsString();
848 data.Title = role.Value["Title"].AsString();
849 data.Powers = role.Value["Powers"].AsULong();
850
851 Dictionary<UUID, OSDMap> GroupRoleMembers;
852 if (SimianGetGenericEntries("GroupRole" + groupID.ToString(), role.Key, out GroupRoleMembers))
853 {
854 data.Members = GroupRoleMembers.Count;
855 }
856 else
857 {
858 data.Members = 0;
859 }
860
861 Roles.Add(data);
862 }
863 }
864
865 return Roles;
866
867 }
868
869
870
871 public List<GroupMembersData> GetGroupMembers(UUID requestingAgentID, UUID GroupID)
872 {
873 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
874
875 List<GroupMembersData> members = new List<GroupMembersData>();
876
877 OSDMap GroupInfo;
878 string GroupName;
879 UUID GroupOwnerRoleID = UUID.Zero;
880 if (!SimianGetFirstGenericEntry(GroupID, "Group", out GroupName, out GroupInfo))
881 {
882 return members;
883 }
884 GroupOwnerRoleID = GroupInfo["OwnerRoleID"].AsUUID();
885
886 // Locally cache group roles, since we'll be needing this data for each member
887 Dictionary<string,OSDMap> GroupRoles;
888 SimianGetGenericEntries(GroupID, "GroupRole", out GroupRoles);
889
890 // Locally cache list of group owners
891 Dictionary<UUID, OSDMap> GroupOwners;
892 SimianGetGenericEntries("GroupRole" + GroupID.ToString(), GroupOwnerRoleID.ToString(), out GroupOwners);
893
894
895 Dictionary<UUID, OSDMap> GroupMembers;
896 if (SimianGetGenericEntries("GroupMember", GroupID.ToString(), out GroupMembers))
897 {
898 foreach (KeyValuePair<UUID, OSDMap> member in GroupMembers)
899 {
900 GroupMembersData data = new GroupMembersData();
901
902 data.AgentID = member.Key;
903
904 UUID SelectedRoleID = member.Value["SelectedRoleID"].AsUUID();
905
906 data.AcceptNotices = member.Value["AcceptNotices"].AsBoolean();
907 data.ListInProfile = member.Value["ListInProfile"].AsBoolean();
908 data.Contribution = member.Value["Contribution"].AsInteger();
909
910 data.IsOwner = GroupOwners.ContainsKey(member.Key);
911
912 OSDMap GroupRoleInfo = GroupRoles[SelectedRoleID.ToString()];
913 data.Title = GroupRoleInfo["Title"].AsString();
914 data.AgentPowers = GroupRoleInfo["Powers"].AsULong();
915
916 members.Add(data);
917 }
918 }
919
920 return members;
921
922 }
923
924 public List<GroupRoleMembersData> GetGroupRoleMembers(UUID requestingAgentID, UUID groupID)
925 {
926 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
927
928 List<GroupRoleMembersData> members = new List<GroupRoleMembersData>();
929
930 Dictionary<string, OSDMap> GroupRoles;
931 if (SimianGetGenericEntries(groupID, "GroupRole", out GroupRoles))
932 {
933 foreach (KeyValuePair<string, OSDMap> Role in GroupRoles)
934 {
935 Dictionary<UUID, OSDMap> GroupRoleMembers;
936 if (SimianGetGenericEntries("GroupRole"+groupID.ToString(), Role.Key, out GroupRoleMembers))
937 {
938 foreach (KeyValuePair<UUID, OSDMap> GroupRoleMember in GroupRoleMembers)
939 {
940 GroupRoleMembersData data = new GroupRoleMembersData();
941
942 data.MemberID = GroupRoleMember.Key;
943 data.RoleID = UUID.Parse(Role.Key);
944
945 members.Add(data);
946 }
947 }
948 }
949 }
950
951 return members;
952 }
953
954 public List<GroupNoticeData> GetGroupNotices(UUID requestingAgentID, UUID GroupID)
955 {
956 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
957
958 List<GroupNoticeData> values = new List<GroupNoticeData>();
959
960 Dictionary<string, OSDMap> Notices;
961 if (SimianGetGenericEntries(GroupID, "GroupNotice", out Notices))
962 {
963 foreach (KeyValuePair<string, OSDMap> Notice in Notices)
964 {
965 GroupNoticeData data = new GroupNoticeData();
966 data.NoticeID = UUID.Parse(Notice.Key);
967 data.Timestamp = Notice.Value["TimeStamp"].AsUInteger();
968 data.FromName = Notice.Value["FromName"].AsString();
969 data.Subject = Notice.Value["Subject"].AsString();
970 data.HasAttachment = Notice.Value["BinaryBucket"].AsBinary().Length > 0;
971
972 //TODO: Figure out how to get this
973 data.AssetType = 0;
974
975 values.Add(data);
976 }
977 }
978
979 return values;
980
981 }
982 public GroupNoticeInfo GetGroupNotice(UUID requestingAgentID, UUID noticeID)
983 {
984 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
985
986 OSDMap GroupNotice;
987 UUID GroupID;
988 if (SimianGetFirstGenericEntry("GroupNotice", noticeID.ToString(), out GroupID, out GroupNotice))
989 {
990 GroupNoticeInfo data = new GroupNoticeInfo();
991 data.GroupID = GroupID;
992 data.Message = GroupNotice["Message"].AsString();
993 data.BinaryBucket = GroupNotice["BinaryBucket"].AsBinary();
994 data.noticeData.NoticeID = noticeID;
995 data.noticeData.Timestamp = GroupNotice["TimeStamp"].AsUInteger();
996 data.noticeData.FromName = GroupNotice["FromName"].AsString();
997 data.noticeData.Subject = GroupNotice["Subject"].AsString();
998 data.noticeData.HasAttachment = data.BinaryBucket.Length > 0;
999 data.noticeData.AssetType = 0;
1000
1001 if (data.Message == null)
1002 {
1003 data.Message = string.Empty;
1004 }
1005
1006 return data;
1007 }
1008 return null;
1009 }
1010 public void AddGroupNotice(UUID requestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, byte[] binaryBucket)
1011 {
1012 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1013
1014 OSDMap Notice = new OSDMap();
1015 Notice["TimeStamp"] = OSD.FromUInteger((uint)Util.UnixTimeSinceEpoch());
1016 Notice["FromName"] = OSD.FromString(fromName);
1017 Notice["Subject"] = OSD.FromString(subject);
1018 Notice["Message"] = OSD.FromString(message);
1019 Notice["BinaryBucket"] = OSD.FromBinary(binaryBucket);
1020
1021 SimianAddGeneric(groupID, "GroupNotice", noticeID.ToString(), Notice);
1022
1023 }
1024 #endregion
1025
1026 #region GroupSessionTracking
1027
1028 public void ResetAgentGroupChatSessions(UUID agentID)
1029 {
1030 Dictionary<string, OSDMap> agentSessions;
1031
1032 if (SimianGetGenericEntries(agentID, "GroupSessionDropped", out agentSessions))
1033 {
1034 foreach (string GroupID in agentSessions.Keys)
1035 {
1036 SimianRemoveGenericEntry(agentID, "GroupSessionDropped", GroupID);
1037 }
1038 }
1039
1040 if (SimianGetGenericEntries(agentID, "GroupSessionInvited", out agentSessions))
1041 {
1042 foreach (string GroupID in agentSessions.Keys)
1043 {
1044 SimianRemoveGenericEntry(agentID, "GroupSessionInvited", GroupID);
1045 }
1046 }
1047 }
1048
1049 public bool hasAgentDroppedGroupChatSession(UUID agentID, UUID groupID)
1050 {
1051 OSDMap session;
1052 return SimianGetGenericEntry(agentID, "GroupSessionDropped", groupID.ToString(), out session);
1053 }
1054
1055 public void AgentDroppedFromGroupChatSession(UUID agentID, UUID groupID)
1056 {
1057 SimianAddGeneric(agentID, "GroupSessionDropped", groupID.ToString(), new OSDMap());
1058 }
1059
1060 public void AgentInvitedToGroupChatSession(UUID agentID, UUID groupID)
1061 {
1062 SimianAddGeneric(agentID, "GroupSessionInvited", groupID.ToString(), new OSDMap());
1063 }
1064
1065 public bool hasAgentBeenInvitedToGroupChatSession(UUID agentID, UUID groupID)
1066 {
1067 OSDMap session;
1068 return SimianGetGenericEntry(agentID, "GroupSessionDropped", groupID.ToString(), out session);
1069 }
1070
1071 #endregion
1072
1073 private void EnsureRoleNotSelectedByMember(UUID groupID, UUID roleID, UUID userID)
1074 {
1075 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
1076
1077 // If member's SelectedRole is roleID, change their selected role to Everyone
1078 // before removing them from the role
1079 OSDMap UserGroupInfo;
1080 if (SimianGetGenericEntry(userID, "GroupMember", groupID.ToString(), out UserGroupInfo))
1081 {
1082 if (UserGroupInfo["SelectedRoleID"].AsUUID() == roleID)
1083 {
1084 UserGroupInfo["SelectedRoleID"] = OSD.FromUUID(UUID.Zero);
1085 }
1086 SimianAddGeneric(userID, "GroupMember", groupID.ToString(), UserGroupInfo);
1087 }
1088 }
1089
1090
1091 #region Simian Util Methods
1092 private bool SimianAddGeneric(UUID ownerID, string type, string key, OSDMap map)
1093 {
1094 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called ({1},{2},{3})", System.Reflection.MethodBase.GetCurrentMethod().Name, ownerID, type, key);
1095
1096 string value = OSDParser.SerializeJsonString(map);
1097
1098 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] value: {0}", value);
1099
1100 NameValueCollection RequestArgs = new NameValueCollection
1101 {
1102 { "RequestMethod", "AddGeneric" },
1103 { "OwnerID", ownerID.ToString() },
1104 { "Type", type },
1105 { "Key", key },
1106 { "Value", value}
1107 };
1108
1109
1110 OSDMap Response = CachedPostRequest(RequestArgs);
1111 if (Response["Success"].AsBoolean())
1112 {
1113 return true;
1114 }
1115 else
1116 {
1117 m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: Error {0}, {1}, {2}, {3}", ownerID, type, key, Response["Message"]);
1118 return false;
1119 }
1120 }
1121
1122 /// <summary>
1123 /// Returns the first of possibly many entries for Owner/Type pair
1124 /// </summary>
1125 private bool SimianGetFirstGenericEntry(UUID ownerID, string type, out string key, out OSDMap map)
1126 {
1127 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called ({1},{2})", System.Reflection.MethodBase.GetCurrentMethod().Name, ownerID, type);
1128
1129 NameValueCollection RequestArgs = new NameValueCollection
1130 {
1131 { "RequestMethod", "GetGenerics" },
1132 { "OwnerID", ownerID.ToString() },
1133 { "Type", type }
1134 };
1135
1136
1137 OSDMap Response = CachedPostRequest(RequestArgs);
1138 if (Response["Success"].AsBoolean() && Response["Entries"] is OSDArray)
1139 {
1140 OSDArray entryArray = (OSDArray)Response["Entries"];
1141 if (entryArray.Count >= 1)
1142 {
1143 OSDMap entryMap = entryArray[0] as OSDMap;
1144 key = entryMap["Key"].AsString();
1145 map = (OSDMap)OSDParser.DeserializeJson(entryMap["Value"].AsString());
1146
1147 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] Generics Result {0}", entryMap["Value"].AsString());
1148
1149 return true;
1150 }
1151 else
1152 {
1153 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] No Generics Results");
1154 }
1155 }
1156 else
1157 {
1158 m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: Error retrieving group info ({0})", Response["Message"]);
1159 }
1160 key = null;
1161 map = null;
1162 return false;
1163 }
1164 private bool SimianGetFirstGenericEntry(string type, string key, out UUID ownerID, out OSDMap map)
1165 {
1166 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called ({1},{2})", System.Reflection.MethodBase.GetCurrentMethod().Name, type, key);
1167
1168
1169 NameValueCollection RequestArgs = new NameValueCollection
1170 {
1171 { "RequestMethod", "GetGenerics" },
1172 { "Type", type },
1173 { "Key", key}
1174 };
1175
1176
1177 OSDMap Response = CachedPostRequest(RequestArgs);
1178 if (Response["Success"].AsBoolean() && Response["Entries"] is OSDArray)
1179 {
1180 OSDArray entryArray = (OSDArray)Response["Entries"];
1181 if (entryArray.Count >= 1)
1182 {
1183 OSDMap entryMap = entryArray[0] as OSDMap;
1184 ownerID = entryMap["OwnerID"].AsUUID();
1185 map = (OSDMap)OSDParser.DeserializeJson(entryMap["Value"].AsString());
1186
1187 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] Generics Result {0}", entryMap["Value"].AsString());
1188
1189 return true;
1190 }
1191 else
1192 {
1193 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] No Generics Results");
1194 }
1195 }
1196 else
1197 {
1198 m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: Error retrieving group info ({0})", Response["Message"]);
1199 }
1200 ownerID = UUID.Zero;
1201 map = null;
1202 return false;
1203 }
1204
1205 private bool SimianGetGenericEntry(UUID ownerID, string type, string key, out OSDMap map)
1206 {
1207 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called ({1},{2},{3})", System.Reflection.MethodBase.GetCurrentMethod().Name, ownerID, type, key);
1208
1209 NameValueCollection RequestArgs = new NameValueCollection
1210 {
1211 { "RequestMethod", "GetGenerics" },
1212 { "OwnerID", ownerID.ToString() },
1213 { "Type", type },
1214 { "Key", key}
1215 };
1216
1217
1218 OSDMap Response = CachedPostRequest(RequestArgs);
1219 if (Response["Success"].AsBoolean() && Response["Entries"] is OSDArray)
1220 {
1221 OSDArray entryArray = (OSDArray)Response["Entries"];
1222 if (entryArray.Count == 1)
1223 {
1224 OSDMap entryMap = entryArray[0] as OSDMap;
1225 key = entryMap["Key"].AsString();
1226 map = (OSDMap)OSDParser.DeserializeJson(entryMap["Value"].AsString());
1227
1228 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] Generics Result {0}", entryMap["Value"].AsString());
1229
1230 return true;
1231 }
1232 else
1233 {
1234 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] No Generics Results");
1235 }
1236 }
1237 else
1238 {
1239 m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: Error retrieving group info ({0})", Response["Message"]);
1240 }
1241 map = null;
1242 return false;
1243 }
1244
1245 private bool SimianGetGenericEntries(UUID ownerID, string type, out Dictionary<string, OSDMap> maps)
1246 {
1247 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called ({1},{2})", System.Reflection.MethodBase.GetCurrentMethod().Name,ownerID, type);
1248
1249 NameValueCollection requestArgs = new NameValueCollection
1250 {
1251 { "RequestMethod", "GetGenerics" },
1252 { "OwnerID", ownerID.ToString() },
1253 { "Type", type }
1254 };
1255
1256
1257
1258 OSDMap response = CachedPostRequest(requestArgs);
1259 if (response["Success"].AsBoolean() && response["Entries"] is OSDArray)
1260 {
1261 maps = new Dictionary<string, OSDMap>();
1262
1263 OSDArray entryArray = (OSDArray)response["Entries"];
1264 foreach (OSDMap entryMap in entryArray)
1265 {
1266 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] Generics Result {0}", entryMap["Value"].AsString());
1267 maps.Add(entryMap["Key"].AsString(), (OSDMap)OSDParser.DeserializeJson(entryMap["Value"].AsString()));
1268 }
1269 if (maps.Count == 0)
1270 {
1271 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] No Generics Results");
1272 }
1273
1274 return true;
1275 }
1276 else
1277 {
1278 maps = null;
1279 m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: Error retrieving group info ({0})", response["Message"]);
1280 }
1281 return false;
1282 }
1283 private bool SimianGetGenericEntries(string type, string key, out Dictionary<UUID, OSDMap> maps)
1284 {
1285 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called ({1},{2})", System.Reflection.MethodBase.GetCurrentMethod().Name, type, key);
1286
1287 NameValueCollection requestArgs = new NameValueCollection
1288 {
1289 { "RequestMethod", "GetGenerics" },
1290 { "Type", type },
1291 { "Key", key }
1292 };
1293
1294
1295
1296 OSDMap response = CachedPostRequest(requestArgs);
1297 if (response["Success"].AsBoolean() && response["Entries"] is OSDArray)
1298 {
1299 maps = new Dictionary<UUID, OSDMap>();
1300
1301 OSDArray entryArray = (OSDArray)response["Entries"];
1302 foreach (OSDMap entryMap in entryArray)
1303 {
1304 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] Generics Result {0}", entryMap["Value"].AsString());
1305 maps.Add(entryMap["OwnerID"].AsUUID(), (OSDMap)OSDParser.DeserializeJson(entryMap["Value"].AsString()));
1306 }
1307 if (maps.Count == 0)
1308 {
1309 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] No Generics Results");
1310 }
1311 return true;
1312 }
1313 else
1314 {
1315 maps = null;
1316 m_log.WarnFormat("[SIMIAN-GROUPS-CONNECTOR]: Error retrieving group info ({0})", response["Message"]);
1317 }
1318 return false;
1319 }
1320
1321 private bool SimianRemoveGenericEntry(UUID ownerID, string type, string key)
1322 {
1323 if (m_debugEnabled) m_log.InfoFormat("[SIMIAN-GROUPS-CONNECTOR] {0} called ({1},{2},{3})", System.Reflection.MethodBase.GetCurrentMethod().Name, ownerID, type, key);
1324
1325 NameValueCollection requestArgs = new NameValueCollection
1326 {
1327 { "RequestMethod", "RemoveGeneric" },
1328 { "OwnerID", ownerID.ToString() },
1329 { "Type", type },
1330 { "Key", key }
1331 };
1332
1333
1334 OSDMap response = CachedPostRequest(requestArgs);
1335 if (response["Success"].AsBoolean())
1336 {
1337 return true;
1338 }
1339 else
1340 {
1341 m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: Error {0}, {1}, {2}, {3}", ownerID, type, key, response["Message"]);
1342 return false;
1343 }
1344 }
1345 #endregion
1346
1347 #region CheesyCache
1348 OSDMap CachedPostRequest(NameValueCollection requestArgs)
1349 {
1350 // Immediately forward the request if the cache is disabled.
1351 if (m_cacheTimeout == 0)
1352 {
1353 m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: cache is disabled");
1354 return WebUtil.PostToService(m_groupsServerURI, requestArgs);
1355 }
1356
1357 // Check if this is an update or a request
1358 if (requestArgs["RequestMethod"] == "RemoveGeneric"
1359 || requestArgs["RequestMethod"] == "AddGeneric")
1360 {
1361 m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: clearing generics cache");
1362
1363 // Any and all updates cause the cache to clear
1364 m_memoryCache.Clear();
1365
1366 // Send update to server, return the response without caching it
1367 return WebUtil.PostToService(m_groupsServerURI, requestArgs);
1368 }
1369
1370 // If we're not doing an update, we must be requesting data
1371
1372 // Create the cache key for the request and see if we have it cached
1373 string CacheKey = WebUtil.BuildQueryString(requestArgs);
1374
1375 // This code uses a leader/follower pattern. On a cache miss, the request is added
1376 // to a queue; the first thread to add it to the queue completes the request while
1377 // follow on threads busy wait for the results, this situation seems to happen
1378 // often when checking permissions
1379 while (true)
1380 {
1381 OSDMap response = null;
1382 bool firstRequest = false;
1383
1384 lock (m_memoryCache)
1385 {
1386 if (m_memoryCache.TryGetValue(CacheKey, out response))
1387 return response;
1388
1389 if (! m_pendingRequests.ContainsKey(CacheKey))
1390 {
1391 m_pendingRequests.Add(CacheKey,true);
1392 firstRequest = true;
1393 }
1394 }
1395
1396 if (firstRequest)
1397 {
1398 // if it wasn't in the cache, pass the request to the Simian Grid Services
1399 try
1400 {
1401 response = WebUtil.PostToService(m_groupsServerURI, requestArgs);
1402 }
1403 catch (Exception)
1404 {
1405 m_log.ErrorFormat("[SIMIAN GROUPS CONNECTOR]: request failed {0}", CacheKey);
1406 }
1407
1408 // and cache the response
1409 lock (m_memoryCache)
1410 {
1411 m_memoryCache.AddOrUpdate(CacheKey, response, TimeSpan.FromSeconds(m_cacheTimeout));
1412 m_pendingRequests.Remove(CacheKey);
1413 }
1414
1415 return response;
1416 }
1417
1418 Thread.Sleep(50); // waiting for a web request to complete, 50msecs is reasonable
1419 }
1420
1421 // if (!m_memoryCache.TryGetValue(CacheKey, out response))
1422 // {
1423 // m_log.WarnFormat("[SIMIAN GROUPS CONNECTOR]: query not in the cache");
1424 // Util.PrintCallStack();
1425
1426 // // if it wasn't in the cache, pass the request to the Simian Grid Services
1427 // response = WebUtil.PostToService(m_groupsServerURI, requestArgs);
1428
1429 // // and cache the response
1430 // m_memoryCache.AddOrUpdate(CacheKey, response, TimeSpan.FromSeconds(m_cacheTimeout));
1431 // }
1432
1433 // // return cached response
1434 // return response;
1435 }
1436 #endregion
1437
1438 }
1439
1440}
1441
diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs
new file mode 100644
index 0000000..e03e71d
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs
@@ -0,0 +1,268 @@
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;
30using System.Collections.Generic;
31using System.Net;
32using System.Reflection;
33using Nini.Config;
34using NUnit.Framework;
35using OpenMetaverse;
36using OpenMetaverse.Messages.Linden;
37using OpenMetaverse.Packets;
38using OpenMetaverse.StructuredData;
39using OpenSim.Framework;
40using OpenSim.Framework.Communications;
41using OpenSim.Framework.Servers;
42using OpenSim.Framework.Servers.HttpServer;
43using OpenSim.Region.ClientStack.Linden;
44using OpenSim.Region.CoreModules.Avatar.InstantMessage;
45using OpenSim.Region.CoreModules.Framework;
46using OpenSim.Region.Framework.Scenes;
47using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups;
48using OpenSim.Tests.Common;
49
50namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups.Tests
51{
52 /// <summary>
53 /// Basic groups module tests
54 /// </summary>
55 [TestFixture]
56 public class GroupsModuleTests : OpenSimTestCase
57 {
58 [SetUp]
59 public override void SetUp()
60 {
61 base.SetUp();
62
63 uint port = 9999;
64 uint sslPort = 9998;
65
66 // This is an unfortunate bit of clean up we have to do because MainServer manages things through static
67 // variables and the VM is not restarted between tests.
68 MainServer.RemoveHttpServer(port);
69
70 BaseHttpServer server = new BaseHttpServer(port, false, sslPort, "");
71 MainServer.AddHttpServer(server);
72 MainServer.Instance = server;
73 }
74
75 [Test]
76 public void TestSendAgentGroupDataUpdate()
77 {
78 TestHelpers.InMethod();
79// TestHelpers.EnableLogging();
80
81 TestScene scene = new SceneHelpers().SetupScene();
82 IConfigSource configSource = new IniConfigSource();
83 IConfig config = configSource.AddConfig("Groups");
84 config.Set("Enabled", true);
85 config.Set("Module", "GroupsModule");
86 config.Set("DebugEnabled", true);
87
88 GroupsModule gm = new GroupsModule();
89 EventQueueGetModule eqgm = new EventQueueGetModule();
90
91 // We need a capabilities module active so that adding the scene presence creates an event queue in the
92 // EventQueueGetModule
93 SceneHelpers.SetupSceneModules(
94 scene, configSource, gm, new MockGroupsServicesConnector(), new CapabilitiesModule(), eqgm);
95
96 ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseStem("1"));
97
98 gm.SendAgentGroupDataUpdate(sp.ControllingClient);
99
100 Hashtable eventsResponse = eqgm.GetEvents(UUID.Zero, sp.UUID);
101
102 Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.OK));
103
104// Console.WriteLine("Response [{0}]", (string)eventsResponse["str_response_string"]);
105
106 OSDMap rawOsd = (OSDMap)OSDParser.DeserializeLLSDXml((string)eventsResponse["str_response_string"]);
107 OSDArray eventsOsd = (OSDArray)rawOsd["events"];
108
109 bool foundUpdate = false;
110 foreach (OSD osd in eventsOsd)
111 {
112 OSDMap eventOsd = (OSDMap)osd;
113
114 if (eventOsd["message"] == "AgentGroupDataUpdate")
115 foundUpdate = true;
116 }
117
118 Assert.That(foundUpdate, Is.True, "Did not find AgentGroupDataUpdate in response");
119
120 // TODO: More checking of more actual event data.
121 }
122
123 [Test]
124 public void TestSendGroupNotice()
125 {
126 TestHelpers.InMethod();
127// TestHelpers.EnableLogging();
128
129 TestScene scene = new SceneHelpers().SetupScene();
130
131 MessageTransferModule mtm = new MessageTransferModule();
132 GroupsModule gm = new GroupsModule();
133 GroupsMessagingModule gmm = new GroupsMessagingModule();
134 MockGroupsServicesConnector mgsc = new MockGroupsServicesConnector();
135
136 IConfigSource configSource = new IniConfigSource();
137
138 {
139 IConfig config = configSource.AddConfig("Messaging");
140 config.Set("MessageTransferModule", mtm.Name);
141 }
142
143 {
144 IConfig config = configSource.AddConfig("Groups");
145 config.Set("Enabled", true);
146 config.Set("Module", gm.Name);
147 config.Set("DebugEnabled", true);
148 config.Set("MessagingModule", gmm.Name);
149 config.Set("MessagingEnabled", true);
150 }
151
152 SceneHelpers.SetupSceneModules(scene, configSource, mgsc, mtm, gm, gmm);
153
154 UUID userId = TestHelpers.ParseTail(0x1);
155 string subjectText = "newman";
156 string messageText = "Hello";
157 string combinedSubjectMessage = string.Format("{0}|{1}", subjectText, messageText);
158
159 ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1));
160 TestClient tc = (TestClient)sp.ControllingClient;
161
162 UUID groupID = gm.CreateGroup(tc, "group1", null, true, UUID.Zero, 0, true, true, true);
163 gm.JoinGroupRequest(tc, groupID);
164
165 // Create a second user who doesn't want to receive notices
166 ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x2));
167 TestClient tc2 = (TestClient)sp2.ControllingClient;
168 gm.JoinGroupRequest(tc2, groupID);
169 gm.SetGroupAcceptNotices(tc2, groupID, false, true);
170
171 List<GridInstantMessage> spReceivedMessages = new List<GridInstantMessage>();
172 tc.OnReceivedInstantMessage += im => spReceivedMessages.Add(im);
173
174 List<GridInstantMessage> sp2ReceivedMessages = new List<GridInstantMessage>();
175 tc2.OnReceivedInstantMessage += im => sp2ReceivedMessages.Add(im);
176
177 GridInstantMessage noticeIm = new GridInstantMessage();
178 noticeIm.fromAgentID = userId.Guid;
179 noticeIm.toAgentID = groupID.Guid;
180 noticeIm.message = combinedSubjectMessage;
181 noticeIm.dialog = (byte)InstantMessageDialog.GroupNotice;
182
183 tc.HandleImprovedInstantMessage(noticeIm);
184
185 Assert.That(spReceivedMessages.Count, Is.EqualTo(1));
186 Assert.That(spReceivedMessages[0].message, Is.EqualTo(combinedSubjectMessage));
187
188 List<GroupNoticeData> notices = mgsc.GetGroupNotices(UUID.Zero, groupID);
189 Assert.AreEqual(1, notices.Count);
190
191 // OpenSimulator (possibly also SL) transport the notice ID as the session ID!
192 Assert.AreEqual(notices[0].NoticeID.Guid, spReceivedMessages[0].imSessionID);
193
194 Assert.That(sp2ReceivedMessages.Count, Is.EqualTo(0));
195 }
196
197 /// <summary>
198 /// Run test with the MessageOnlineUsersOnly flag set.
199 /// </summary>
200 [Test]
201 public void TestSendGroupNoticeOnlineOnly()
202 {
203 TestHelpers.InMethod();
204 // TestHelpers.EnableLogging();
205
206 TestScene scene = new SceneHelpers().SetupScene();
207
208 MessageTransferModule mtm = new MessageTransferModule();
209 GroupsModule gm = new GroupsModule();
210 GroupsMessagingModule gmm = new GroupsMessagingModule();
211
212 IConfigSource configSource = new IniConfigSource();
213
214 {
215 IConfig config = configSource.AddConfig("Messaging");
216 config.Set("MessageTransferModule", mtm.Name);
217 }
218
219 {
220 IConfig config = configSource.AddConfig("Groups");
221 config.Set("Enabled", true);
222 config.Set("Module", gm.Name);
223 config.Set("DebugEnabled", true);
224 config.Set("MessagingModule", gmm.Name);
225 config.Set("MessagingEnabled", true);
226 config.Set("MessageOnlineUsersOnly", true);
227 }
228
229 SceneHelpers.SetupSceneModules(scene, configSource, new MockGroupsServicesConnector(), mtm, gm, gmm);
230
231 UUID userId = TestHelpers.ParseTail(0x1);
232 string subjectText = "newman";
233 string messageText = "Hello";
234 string combinedSubjectMessage = string.Format("{0}|{1}", subjectText, messageText);
235
236 ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1));
237 TestClient tc = (TestClient)sp.ControllingClient;
238
239 UUID groupID = gm.CreateGroup(tc, "group1", null, true, UUID.Zero, 0, true, true, true);
240 gm.JoinGroupRequest(tc, groupID);
241
242 // Create a second user who doesn't want to receive notices
243 ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x2));
244 TestClient tc2 = (TestClient)sp2.ControllingClient;
245 gm.JoinGroupRequest(tc2, groupID);
246 gm.SetGroupAcceptNotices(tc2, groupID, false, true);
247
248 List<GridInstantMessage> spReceivedMessages = new List<GridInstantMessage>();
249 tc.OnReceivedInstantMessage += im => spReceivedMessages.Add(im);
250
251 List<GridInstantMessage> sp2ReceivedMessages = new List<GridInstantMessage>();
252 tc2.OnReceivedInstantMessage += im => sp2ReceivedMessages.Add(im);
253
254 GridInstantMessage noticeIm = new GridInstantMessage();
255 noticeIm.fromAgentID = userId.Guid;
256 noticeIm.toAgentID = groupID.Guid;
257 noticeIm.message = combinedSubjectMessage;
258 noticeIm.dialog = (byte)InstantMessageDialog.GroupNotice;
259
260 tc.HandleImprovedInstantMessage(noticeIm);
261
262 Assert.That(spReceivedMessages.Count, Is.EqualTo(1));
263 Assert.That(spReceivedMessages[0].message, Is.EqualTo(combinedSubjectMessage));
264
265 Assert.That(sp2ReceivedMessages.Count, Is.EqualTo(0));
266 }
267 }
268} \ No newline at end of file
diff --git a/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/XmlRpcGroupsServicesConnectorModule.cs b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/XmlRpcGroupsServicesConnectorModule.cs
new file mode 100644
index 0000000..a040f43
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/XmlRpcGroups/XmlRpcGroupsServicesConnectorModule.cs
@@ -0,0 +1,1174 @@
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;
30using System.Collections.Generic;
31using System.Reflection;
32using System.Text;
33
34using Nwc.XmlRpc;
35
36using log4net;
37using Mono.Addins;
38using Nini.Config;
39
40using OpenMetaverse;
41using OpenMetaverse.StructuredData;
42
43using OpenSim.Framework;
44using OpenSim.Framework.Communications;
45using OpenSim.Region.Framework.Interfaces;
46using OpenSim.Services.Interfaces;
47
48namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
49{
50 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "XmlRpcGroupsServicesConnectorModule")]
51 public class XmlRpcGroupsServicesConnectorModule : ISharedRegionModule, IGroupsServicesConnector
52 {
53 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
54
55 private bool m_debugEnabled = false;
56
57 public const GroupPowers DefaultEveryonePowers
58 = GroupPowers.AllowSetHome
59 | GroupPowers.Accountable
60 | GroupPowers.JoinChat
61 | GroupPowers.AllowVoiceChat
62 | GroupPowers.ReceiveNotices
63 | GroupPowers.StartProposal
64 | GroupPowers.VoteOnProposal;
65
66 // Would this be cleaner as (GroupPowers)ulong.MaxValue?
67 public const GroupPowers DefaultOwnerPowers
68 = GroupPowers.Accountable
69 | GroupPowers.AllowEditLand
70 | GroupPowers.AllowFly
71 | GroupPowers.AllowLandmark
72 | GroupPowers.AllowRez
73 | GroupPowers.AllowSetHome
74 | GroupPowers.AllowVoiceChat
75 | GroupPowers.AssignMember
76 | GroupPowers.AssignMemberLimited
77 | GroupPowers.ChangeActions
78 | GroupPowers.ChangeIdentity
79 | GroupPowers.ChangeMedia
80 | GroupPowers.ChangeOptions
81 | GroupPowers.CreateRole
82 | GroupPowers.DeedObject
83 | GroupPowers.DeleteRole
84 | GroupPowers.Eject
85 | GroupPowers.FindPlaces
86 | GroupPowers.Invite
87 | GroupPowers.JoinChat
88 | GroupPowers.LandChangeIdentity
89 | GroupPowers.LandDeed
90 | GroupPowers.LandDivideJoin
91 | GroupPowers.LandEdit
92 | GroupPowers.LandEjectAndFreeze
93 | GroupPowers.LandGardening
94 | GroupPowers.LandManageAllowed
95 | GroupPowers.LandManageBanned
96 | GroupPowers.LandManagePasses
97 | GroupPowers.LandOptions
98 | GroupPowers.LandRelease
99 | GroupPowers.LandSetSale
100 | GroupPowers.ModerateChat
101 | GroupPowers.ObjectManipulate
102 | GroupPowers.ObjectSetForSale
103 | GroupPowers.ReceiveNotices
104 | GroupPowers.RemoveMember
105 | GroupPowers.ReturnGroupOwned
106 | GroupPowers.ReturnGroupSet
107 | GroupPowers.ReturnNonGroup
108 | GroupPowers.RoleProperties
109 | GroupPowers.SendNotices
110 | GroupPowers.SetLandingPoint
111 | GroupPowers.StartProposal
112 | GroupPowers.VoteOnProposal;
113
114 private bool m_connectorEnabled = false;
115
116 private string m_groupsServerURI = string.Empty;
117
118 private bool m_disableKeepAlive = false;
119
120 private string m_groupReadKey = string.Empty;
121 private string m_groupWriteKey = string.Empty;
122
123 private IUserAccountService m_accountService = null;
124
125 private ExpiringCache<string, XmlRpcResponse> m_memoryCache;
126 private int m_cacheTimeout = 30;
127
128 // Used to track which agents are have dropped from a group chat session
129 // Should be reset per agent, on logon
130 // TODO: move this to Flotsam XmlRpc Service
131 // SessionID, List<AgentID>
132 private Dictionary<UUID, List<UUID>> m_groupsAgentsDroppedFromChatSession = new Dictionary<UUID, List<UUID>>();
133 private Dictionary<UUID, List<UUID>> m_groupsAgentsInvitedToChatSession = new Dictionary<UUID, List<UUID>>();
134
135 #region Region Module interfaceBase Members
136
137 public string Name
138 {
139 get { return "XmlRpcGroupsServicesConnector"; }
140 }
141
142 // this module is not intended to be replaced, but there should only be 1 of them.
143 public Type ReplaceableInterface
144 {
145 get { return null; }
146 }
147
148 public void Initialise(IConfigSource config)
149 {
150 IConfig groupsConfig = config.Configs["Groups"];
151
152 if (groupsConfig == null)
153 {
154 // Do not run this module by default.
155 return;
156 }
157 else
158 {
159 // if groups aren't enabled, we're not needed.
160 // if we're not specified as the connector to use, then we're not wanted
161 if ((groupsConfig.GetBoolean("Enabled", false) == false)
162 || (groupsConfig.GetString("ServicesConnectorModule", "XmlRpcGroupsServicesConnector") != Name))
163 {
164 m_connectorEnabled = false;
165 return;
166 }
167
168 m_log.DebugFormat("[XMLRPC-GROUPS-CONNECTOR]: Initializing {0}", this.Name);
169
170 m_groupsServerURI = groupsConfig.GetString("GroupsServerURI", string.Empty);
171 if (string.IsNullOrEmpty(m_groupsServerURI))
172 {
173 m_log.ErrorFormat("Please specify a valid URL for GroupsServerURI in OpenSim.ini, [Groups]");
174 m_connectorEnabled = false;
175 return;
176 }
177
178 m_disableKeepAlive = groupsConfig.GetBoolean("XmlRpcDisableKeepAlive", false);
179
180 m_groupReadKey = groupsConfig.GetString("XmlRpcServiceReadKey", string.Empty);
181 m_groupWriteKey = groupsConfig.GetString("XmlRpcServiceWriteKey", string.Empty);
182
183
184 m_cacheTimeout = groupsConfig.GetInt("GroupsCacheTimeout", 30);
185 if (m_cacheTimeout == 0)
186 {
187 m_log.WarnFormat("[XMLRPC-GROUPS-CONNECTOR]: Groups Cache Disabled.");
188 }
189 else
190 {
191 m_log.InfoFormat("[XMLRPC-GROUPS-CONNECTOR]: Groups Cache Timeout set to {0}.", m_cacheTimeout);
192 }
193
194 m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", false);
195
196 // If we got all the config options we need, lets start'er'up
197 m_memoryCache = new ExpiringCache<string, XmlRpcResponse>();
198 m_connectorEnabled = true;
199 }
200 }
201
202 public void Close()
203 {
204 m_log.DebugFormat("[XMLRPC-GROUPS-CONNECTOR]: Closing {0}", this.Name);
205 }
206
207 public void AddRegion(OpenSim.Region.Framework.Scenes.Scene scene)
208 {
209 if (m_connectorEnabled)
210 {
211
212 if (m_accountService == null)
213 {
214 m_accountService = scene.UserAccountService;
215 }
216
217
218 scene.RegisterModuleInterface<IGroupsServicesConnector>(this);
219 }
220 }
221
222 public void RemoveRegion(OpenSim.Region.Framework.Scenes.Scene scene)
223 {
224 if (scene.RequestModuleInterface<IGroupsServicesConnector>() == this)
225 {
226 scene.UnregisterModuleInterface<IGroupsServicesConnector>(this);
227 }
228 }
229
230 public void RegionLoaded(OpenSim.Region.Framework.Scenes.Scene scene)
231 {
232 // TODO: May want to consider listenning for Agent Connections so we can pre-cache group info
233 // scene.EventManager.OnNewClient += OnNewClient;
234 }
235
236 #endregion
237
238 #region ISharedRegionModule Members
239
240 public void PostInitialise()
241 {
242 // NoOp
243 }
244
245 #endregion
246
247 #region IGroupsServicesConnector Members
248
249 /// <summary>
250 /// Create a Group, including Everyone and Owners Role, place FounderID in both groups, select Owner as selected role, and newly created group as agent's active role.
251 /// </summary>
252 public UUID CreateGroup(UUID requestingAgentID, string name, string charter, bool showInList, UUID insigniaID,
253 int membershipFee, bool openEnrollment, bool allowPublish,
254 bool maturePublish, UUID founderID)
255 {
256 UUID GroupID = UUID.Random();
257 UUID OwnerRoleID = UUID.Random();
258
259 Hashtable param = new Hashtable();
260 param["GroupID"] = GroupID.ToString();
261 param["Name"] = name;
262 param["Charter"] = charter;
263 param["ShowInList"] = showInList == true ? 1 : 0;
264 param["InsigniaID"] = insigniaID.ToString();
265 param["MembershipFee"] = membershipFee;
266 param["OpenEnrollment"] = openEnrollment == true ? 1 : 0;
267 param["AllowPublish"] = allowPublish == true ? 1 : 0;
268 param["MaturePublish"] = maturePublish == true ? 1 : 0;
269 param["FounderID"] = founderID.ToString();
270 param["EveryonePowers"] = ((ulong)DefaultEveryonePowers).ToString();
271 param["OwnerRoleID"] = OwnerRoleID.ToString();
272 param["OwnersPowers"] = ((ulong)DefaultOwnerPowers).ToString();
273
274 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.createGroup", param);
275
276 if (respData.Contains("error"))
277 {
278 // UUID is not nullable
279
280 return UUID.Zero;
281 }
282
283 return UUID.Parse((string)respData["GroupID"]);
284 }
285
286 public void UpdateGroup(UUID requestingAgentID, UUID groupID, string charter, bool showInList,
287 UUID insigniaID, int membershipFee, bool openEnrollment,
288 bool allowPublish, bool maturePublish)
289 {
290 Hashtable param = new Hashtable();
291 param["GroupID"] = groupID.ToString();
292 param["Charter"] = charter;
293 param["ShowInList"] = showInList == true ? 1 : 0;
294 param["InsigniaID"] = insigniaID.ToString();
295 param["MembershipFee"] = membershipFee;
296 param["OpenEnrollment"] = openEnrollment == true ? 1 : 0;
297 param["AllowPublish"] = allowPublish == true ? 1 : 0;
298 param["MaturePublish"] = maturePublish == true ? 1 : 0;
299
300 XmlRpcCall(requestingAgentID, "groups.updateGroup", param);
301 }
302
303 public void AddGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID, string name, string description,
304 string title, ulong powers)
305 {
306 Hashtable param = new Hashtable();
307 param["GroupID"] = groupID.ToString();
308 param["RoleID"] = roleID.ToString();
309 param["Name"] = name;
310 param["Description"] = description;
311 param["Title"] = title;
312 param["Powers"] = powers.ToString();
313
314 XmlRpcCall(requestingAgentID, "groups.addRoleToGroup", param);
315 }
316
317 public void RemoveGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID)
318 {
319 Hashtable param = new Hashtable();
320 param["GroupID"] = groupID.ToString();
321 param["RoleID"] = roleID.ToString();
322
323 XmlRpcCall(requestingAgentID, "groups.removeRoleFromGroup", param);
324 }
325
326 public void UpdateGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID, string name, string description,
327 string title, ulong powers)
328 {
329 Hashtable param = new Hashtable();
330 param["GroupID"] = groupID.ToString();
331 param["RoleID"] = roleID.ToString();
332 if (name != null)
333 {
334 param["Name"] = name;
335 }
336 if (description != null)
337 {
338 param["Description"] = description;
339 }
340 if (title != null)
341 {
342 param["Title"] = title;
343 }
344 param["Powers"] = powers.ToString();
345
346 XmlRpcCall(requestingAgentID, "groups.updateGroupRole", param);
347 }
348
349 public GroupRecord GetGroupRecord(UUID requestingAgentID, UUID GroupID, string GroupName)
350 {
351 Hashtable param = new Hashtable();
352 if (GroupID != UUID.Zero)
353 {
354 param["GroupID"] = GroupID.ToString();
355 }
356 if (!string.IsNullOrEmpty(GroupName))
357 {
358 param["Name"] = GroupName.ToString();
359 }
360
361 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getGroup", param);
362
363 if (respData.Contains("error"))
364 {
365 return null;
366 }
367
368 return GroupProfileHashtableToGroupRecord(respData);
369
370 }
371
372 public GroupProfileData GetMemberGroupProfile(UUID requestingAgentID, UUID GroupID, UUID AgentID)
373 {
374 Hashtable param = new Hashtable();
375 param["GroupID"] = GroupID.ToString();
376
377 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getGroup", param);
378
379 if (respData.Contains("error"))
380 {
381 // GroupProfileData is not nullable
382 return new GroupProfileData();
383 }
384
385 GroupMembershipData MemberInfo = GetAgentGroupMembership(requestingAgentID, AgentID, GroupID);
386 GroupProfileData MemberGroupProfile = GroupProfileHashtableToGroupProfileData(respData);
387
388 MemberGroupProfile.MemberTitle = MemberInfo.GroupTitle;
389 MemberGroupProfile.PowersMask = MemberInfo.GroupPowers;
390
391 return MemberGroupProfile;
392 }
393
394 public void SetAgentActiveGroup(UUID requestingAgentID, UUID AgentID, UUID GroupID)
395 {
396 Hashtable param = new Hashtable();
397 param["AgentID"] = AgentID.ToString();
398 param["GroupID"] = GroupID.ToString();
399
400 XmlRpcCall(requestingAgentID, "groups.setAgentActiveGroup", param);
401 }
402
403 public void SetAgentActiveGroupRole(UUID requestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID)
404 {
405 Hashtable param = new Hashtable();
406 param["AgentID"] = AgentID.ToString();
407 param["GroupID"] = GroupID.ToString();
408 param["SelectedRoleID"] = RoleID.ToString();
409
410 XmlRpcCall(requestingAgentID, "groups.setAgentGroupInfo", param);
411 }
412
413 public void SetAgentGroupInfo(UUID requestingAgentID, UUID AgentID, UUID GroupID, bool AcceptNotices, bool ListInProfile)
414 {
415 Hashtable param = new Hashtable();
416 param["AgentID"] = AgentID.ToString();
417 param["GroupID"] = GroupID.ToString();
418 param["AcceptNotices"] = AcceptNotices ? "1" : "0";
419 param["ListInProfile"] = ListInProfile ? "1" : "0";
420
421 XmlRpcCall(requestingAgentID, "groups.setAgentGroupInfo", param);
422
423 }
424
425 public void AddAgentToGroupInvite(UUID requestingAgentID, UUID inviteID, UUID groupID, UUID roleID, UUID agentID)
426 {
427 Hashtable param = new Hashtable();
428 param["InviteID"] = inviteID.ToString();
429 param["AgentID"] = agentID.ToString();
430 param["RoleID"] = roleID.ToString();
431 param["GroupID"] = groupID.ToString();
432
433 XmlRpcCall(requestingAgentID, "groups.addAgentToGroupInvite", param);
434
435 }
436
437 public GroupInviteInfo GetAgentToGroupInvite(UUID requestingAgentID, UUID inviteID)
438 {
439 Hashtable param = new Hashtable();
440 param["InviteID"] = inviteID.ToString();
441
442 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getAgentToGroupInvite", param);
443
444 if (respData.Contains("error"))
445 {
446 return null;
447 }
448
449 GroupInviteInfo inviteInfo = new GroupInviteInfo();
450 inviteInfo.InviteID = inviteID;
451 inviteInfo.GroupID = UUID.Parse((string)respData["GroupID"]);
452 inviteInfo.RoleID = UUID.Parse((string)respData["RoleID"]);
453 inviteInfo.AgentID = UUID.Parse((string)respData["AgentID"]);
454
455 return inviteInfo;
456 }
457
458 public void RemoveAgentToGroupInvite(UUID requestingAgentID, UUID inviteID)
459 {
460 Hashtable param = new Hashtable();
461 param["InviteID"] = inviteID.ToString();
462
463 XmlRpcCall(requestingAgentID, "groups.removeAgentToGroupInvite", param);
464 }
465
466 public void AddAgentToGroup(UUID requestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID)
467 {
468 Hashtable param = new Hashtable();
469 param["AgentID"] = AgentID.ToString();
470 param["GroupID"] = GroupID.ToString();
471 param["RoleID"] = RoleID.ToString();
472
473 XmlRpcCall(requestingAgentID, "groups.addAgentToGroup", param);
474 }
475
476 public void RemoveAgentFromGroup(UUID requestingAgentID, UUID AgentID, UUID GroupID)
477 {
478 Hashtable param = new Hashtable();
479 param["AgentID"] = AgentID.ToString();
480 param["GroupID"] = GroupID.ToString();
481
482 XmlRpcCall(requestingAgentID, "groups.removeAgentFromGroup", param);
483 }
484
485 public void AddAgentToGroupRole(UUID requestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID)
486 {
487 Hashtable param = new Hashtable();
488 param["AgentID"] = AgentID.ToString();
489 param["GroupID"] = GroupID.ToString();
490 param["RoleID"] = RoleID.ToString();
491
492 XmlRpcCall(requestingAgentID, "groups.addAgentToGroupRole", param);
493 }
494
495 public void RemoveAgentFromGroupRole(UUID requestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID)
496 {
497 Hashtable param = new Hashtable();
498 param["AgentID"] = AgentID.ToString();
499 param["GroupID"] = GroupID.ToString();
500 param["RoleID"] = RoleID.ToString();
501
502 XmlRpcCall(requestingAgentID, "groups.removeAgentFromGroupRole", param);
503 }
504
505 public List<DirGroupsReplyData> FindGroups(UUID requestingAgentID, string search)
506 {
507 Hashtable param = new Hashtable();
508 param["Search"] = search;
509
510 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.findGroups", param);
511
512 List<DirGroupsReplyData> findings = new List<DirGroupsReplyData>();
513
514 if (!respData.Contains("error"))
515 {
516 Hashtable results = (Hashtable)respData["results"];
517 foreach (Hashtable groupFind in results.Values)
518 {
519 DirGroupsReplyData data = new DirGroupsReplyData();
520 data.groupID = new UUID((string)groupFind["GroupID"]); ;
521 data.groupName = (string)groupFind["Name"];
522 data.members = int.Parse((string)groupFind["Members"]);
523 // data.searchOrder = order;
524
525 findings.Add(data);
526 }
527 }
528
529 return findings;
530 }
531
532 public GroupMembershipData GetAgentGroupMembership(UUID requestingAgentID, UUID AgentID, UUID GroupID)
533 {
534 Hashtable param = new Hashtable();
535 param["AgentID"] = AgentID.ToString();
536 param["GroupID"] = GroupID.ToString();
537
538 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getAgentGroupMembership", param);
539
540 if (respData.Contains("error"))
541 {
542 return null;
543 }
544
545 GroupMembershipData data = HashTableToGroupMembershipData(respData);
546
547 return data;
548 }
549
550 public GroupMembershipData GetAgentActiveMembership(UUID requestingAgentID, UUID AgentID)
551 {
552 Hashtable param = new Hashtable();
553 param["AgentID"] = AgentID.ToString();
554
555 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getAgentActiveMembership", param);
556
557 if (respData.Contains("error"))
558 {
559 return null;
560 }
561
562 return HashTableToGroupMembershipData(respData);
563 }
564
565 public List<GroupMembershipData> GetAgentGroupMemberships(UUID requestingAgentID, UUID AgentID)
566 {
567 Hashtable param = new Hashtable();
568 param["AgentID"] = AgentID.ToString();
569
570 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getAgentGroupMemberships", param);
571
572 List<GroupMembershipData> memberships = new List<GroupMembershipData>();
573
574 if (!respData.Contains("error"))
575 {
576 foreach (object membership in respData.Values)
577 {
578 memberships.Add(HashTableToGroupMembershipData((Hashtable)membership));
579 }
580 }
581
582 return memberships;
583 }
584
585 public List<GroupRolesData> GetAgentGroupRoles(UUID requestingAgentID, UUID AgentID, UUID GroupID)
586 {
587 Hashtable param = new Hashtable();
588 param["AgentID"] = AgentID.ToString();
589 param["GroupID"] = GroupID.ToString();
590
591 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getAgentRoles", param);
592
593 List<GroupRolesData> Roles = new List<GroupRolesData>();
594
595 if (respData.Contains("error"))
596 {
597 return Roles;
598 }
599
600 foreach (Hashtable role in respData.Values)
601 {
602 GroupRolesData data = new GroupRolesData();
603 data.RoleID = new UUID((string)role["RoleID"]);
604 data.Name = (string)role["Name"];
605 data.Description = (string)role["Description"];
606 data.Powers = ulong.Parse((string)role["Powers"]);
607 data.Title = (string)role["Title"];
608
609 Roles.Add(data);
610 }
611
612 return Roles;
613 }
614
615 public List<GroupRolesData> GetGroupRoles(UUID requestingAgentID, UUID GroupID)
616 {
617 Hashtable param = new Hashtable();
618 param["GroupID"] = GroupID.ToString();
619
620 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getGroupRoles", param);
621
622 List<GroupRolesData> Roles = new List<GroupRolesData>();
623
624 if (respData.Contains("error"))
625 {
626 return Roles;
627 }
628
629 foreach (Hashtable role in respData.Values)
630 {
631 GroupRolesData data = new GroupRolesData();
632 data.Description = (string)role["Description"];
633 data.Members = int.Parse((string)role["Members"]);
634 data.Name = (string)role["Name"];
635 data.Powers = ulong.Parse((string)role["Powers"]);
636 data.RoleID = new UUID((string)role["RoleID"]);
637 data.Title = (string)role["Title"];
638
639 Roles.Add(data);
640 }
641
642 return Roles;
643 }
644
645 public List<GroupMembersData> GetGroupMembers(UUID requestingAgentID, UUID GroupID)
646 {
647 Hashtable param = new Hashtable();
648 param["GroupID"] = GroupID.ToString();
649
650 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getGroupMembers", param);
651
652 List<GroupMembersData> members = new List<GroupMembersData>();
653
654 if (respData.Contains("error"))
655 {
656 return members;
657 }
658
659 foreach (Hashtable membership in respData.Values)
660 {
661 GroupMembersData data = new GroupMembersData();
662
663 data.AcceptNotices = ((string)membership["AcceptNotices"]) == "1";
664 data.AgentID = new UUID((string)membership["AgentID"]);
665 data.Contribution = int.Parse((string)membership["Contribution"]);
666 data.IsOwner = ((string)membership["IsOwner"]) == "1";
667 data.ListInProfile = ((string)membership["ListInProfile"]) == "1";
668 data.AgentPowers = ulong.Parse((string)membership["AgentPowers"]);
669 data.Title = (string)membership["Title"];
670
671 members.Add(data);
672 }
673
674 return members;
675 }
676
677 public List<GroupRoleMembersData> GetGroupRoleMembers(UUID requestingAgentID, UUID GroupID)
678 {
679 Hashtable param = new Hashtable();
680 param["GroupID"] = GroupID.ToString();
681
682 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getGroupRoleMembers", param);
683
684 List<GroupRoleMembersData> members = new List<GroupRoleMembersData>();
685
686 if (!respData.Contains("error"))
687 {
688 foreach (Hashtable membership in respData.Values)
689 {
690 GroupRoleMembersData data = new GroupRoleMembersData();
691
692 data.MemberID = new UUID((string)membership["AgentID"]);
693 data.RoleID = new UUID((string)membership["RoleID"]);
694
695 members.Add(data);
696 }
697 }
698 return members;
699 }
700
701 public List<GroupNoticeData> GetGroupNotices(UUID requestingAgentID, UUID GroupID)
702 {
703 Hashtable param = new Hashtable();
704 param["GroupID"] = GroupID.ToString();
705
706 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getGroupNotices", param);
707
708 List<GroupNoticeData> values = new List<GroupNoticeData>();
709
710 if (!respData.Contains("error"))
711 {
712 foreach (Hashtable value in respData.Values)
713 {
714 GroupNoticeData data = new GroupNoticeData();
715 data.NoticeID = UUID.Parse((string)value["NoticeID"]);
716 data.Timestamp = uint.Parse((string)value["Timestamp"]);
717 data.FromName = (string)value["FromName"];
718 data.Subject = (string)value["Subject"];
719 data.HasAttachment = false;
720 data.AssetType = 0;
721
722 values.Add(data);
723 }
724 }
725
726 return values;
727 }
728
729 public GroupNoticeInfo GetGroupNotice(UUID requestingAgentID, UUID noticeID)
730 {
731 Hashtable param = new Hashtable();
732 param["NoticeID"] = noticeID.ToString();
733
734 Hashtable respData = XmlRpcCall(requestingAgentID, "groups.getGroupNotice", param);
735
736 if (respData.Contains("error"))
737 {
738 return null;
739 }
740
741 GroupNoticeInfo data = new GroupNoticeInfo();
742 data.GroupID = UUID.Parse((string)respData["GroupID"]);
743 data.Message = (string)respData["Message"];
744 data.BinaryBucket = Utils.HexStringToBytes((string)respData["BinaryBucket"], true);
745 data.noticeData.NoticeID = UUID.Parse((string)respData["NoticeID"]);
746 data.noticeData.Timestamp = uint.Parse((string)respData["Timestamp"]);
747 data.noticeData.FromName = (string)respData["FromName"];
748 data.noticeData.Subject = (string)respData["Subject"];
749 data.noticeData.HasAttachment = false;
750 data.noticeData.AssetType = 0;
751
752 if (data.Message == null)
753 {
754 data.Message = string.Empty;
755 }
756
757 return data;
758 }
759
760 public void AddGroupNotice(UUID requestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, byte[] binaryBucket)
761 {
762 string binBucket = OpenMetaverse.Utils.BytesToHexString(binaryBucket, "");
763
764 Hashtable param = new Hashtable();
765 param["GroupID"] = groupID.ToString();
766 param["NoticeID"] = noticeID.ToString();
767 param["FromName"] = fromName;
768 param["Subject"] = subject;
769 param["Message"] = message;
770 param["BinaryBucket"] = binBucket;
771 param["TimeStamp"] = ((uint)Util.UnixTimeSinceEpoch()).ToString();
772
773 XmlRpcCall(requestingAgentID, "groups.addGroupNotice", param);
774 }
775
776 #endregion
777
778 #region GroupSessionTracking
779
780 public void ResetAgentGroupChatSessions(UUID agentID)
781 {
782 foreach (List<UUID> agentList in m_groupsAgentsDroppedFromChatSession.Values)
783 {
784 agentList.Remove(agentID);
785 }
786 }
787
788 public bool hasAgentBeenInvitedToGroupChatSession(UUID agentID, UUID groupID)
789 {
790 // If we're tracking this group, and we can find them in the tracking, then they've been invited
791 return m_groupsAgentsInvitedToChatSession.ContainsKey(groupID)
792 && m_groupsAgentsInvitedToChatSession[groupID].Contains(agentID);
793 }
794
795 public bool hasAgentDroppedGroupChatSession(UUID agentID, UUID groupID)
796 {
797 // If we're tracking drops for this group,
798 // and we find them, well... then they've dropped
799 return m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID)
800 && m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID);
801 }
802
803 public void AgentDroppedFromGroupChatSession(UUID agentID, UUID groupID)
804 {
805 if (m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID))
806 {
807 // If not in dropped list, add
808 if (!m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID))
809 {
810 m_groupsAgentsDroppedFromChatSession[groupID].Add(agentID);
811 }
812 }
813 }
814
815 public void AgentInvitedToGroupChatSession(UUID agentID, UUID groupID)
816 {
817 // Add Session Status if it doesn't exist for this session
818 CreateGroupChatSessionTracking(groupID);
819
820 // If nessesary, remove from dropped list
821 if (m_groupsAgentsDroppedFromChatSession[groupID].Contains(agentID))
822 {
823 m_groupsAgentsDroppedFromChatSession[groupID].Remove(agentID);
824 }
825 }
826
827 private void CreateGroupChatSessionTracking(UUID groupID)
828 {
829 if (!m_groupsAgentsDroppedFromChatSession.ContainsKey(groupID))
830 {
831 m_groupsAgentsDroppedFromChatSession.Add(groupID, new List<UUID>());
832 m_groupsAgentsInvitedToChatSession.Add(groupID, new List<UUID>());
833 }
834
835 }
836 #endregion
837
838 #region XmlRpcHashtableMarshalling
839 private GroupProfileData GroupProfileHashtableToGroupProfileData(Hashtable groupProfile)
840 {
841 GroupProfileData group = new GroupProfileData();
842 group.GroupID = UUID.Parse((string)groupProfile["GroupID"]);
843 group.Name = (string)groupProfile["Name"];
844
845 if (groupProfile["Charter"] != null)
846 {
847 group.Charter = (string)groupProfile["Charter"];
848 }
849
850 group.ShowInList = ((string)groupProfile["ShowInList"]) == "1";
851 group.InsigniaID = UUID.Parse((string)groupProfile["InsigniaID"]);
852 group.MembershipFee = int.Parse((string)groupProfile["MembershipFee"]);
853 group.OpenEnrollment = ((string)groupProfile["OpenEnrollment"]) == "1";
854 group.AllowPublish = ((string)groupProfile["AllowPublish"]) == "1";
855 group.MaturePublish = ((string)groupProfile["MaturePublish"]) == "1";
856 group.FounderID = UUID.Parse((string)groupProfile["FounderID"]);
857 group.OwnerRole = UUID.Parse((string)groupProfile["OwnerRoleID"]);
858
859 group.GroupMembershipCount = int.Parse((string)groupProfile["GroupMembershipCount"]);
860 group.GroupRolesCount = int.Parse((string)groupProfile["GroupRolesCount"]);
861
862 return group;
863 }
864
865 private GroupRecord GroupProfileHashtableToGroupRecord(Hashtable groupProfile)
866 {
867 GroupRecord group = new GroupRecord();
868 group.GroupID = UUID.Parse((string)groupProfile["GroupID"]);
869 group.GroupName = groupProfile["Name"].ToString();
870 if (groupProfile["Charter"] != null)
871 {
872 group.Charter = (string)groupProfile["Charter"];
873 }
874 group.ShowInList = ((string)groupProfile["ShowInList"]) == "1";
875 group.GroupPicture = UUID.Parse((string)groupProfile["InsigniaID"]);
876 group.MembershipFee = int.Parse((string)groupProfile["MembershipFee"]);
877 group.OpenEnrollment = ((string)groupProfile["OpenEnrollment"]) == "1";
878 group.AllowPublish = ((string)groupProfile["AllowPublish"]) == "1";
879 group.MaturePublish = ((string)groupProfile["MaturePublish"]) == "1";
880 group.FounderID = UUID.Parse((string)groupProfile["FounderID"]);
881 group.OwnerRoleID = UUID.Parse((string)groupProfile["OwnerRoleID"]);
882
883 return group;
884 }
885
886 private static GroupMembershipData HashTableToGroupMembershipData(Hashtable respData)
887 {
888 GroupMembershipData data = new GroupMembershipData();
889 data.AcceptNotices = ((string)respData["AcceptNotices"] == "1");
890 data.Contribution = int.Parse((string)respData["Contribution"]);
891 data.ListInProfile = ((string)respData["ListInProfile"] == "1");
892
893 data.ActiveRole = new UUID((string)respData["SelectedRoleID"]);
894 data.GroupTitle = (string)respData["Title"];
895
896 data.GroupPowers = ulong.Parse((string)respData["GroupPowers"]);
897
898 // Is this group the agent's active group
899
900 data.GroupID = new UUID((string)respData["GroupID"]);
901
902 UUID ActiveGroup = new UUID((string)respData["ActiveGroupID"]);
903 data.Active = data.GroupID.Equals(ActiveGroup);
904
905 data.AllowPublish = ((string)respData["AllowPublish"] == "1");
906 if (respData["Charter"] != null)
907 {
908 data.Charter = (string)respData["Charter"];
909 }
910 data.FounderID = new UUID((string)respData["FounderID"]);
911 data.GroupID = new UUID((string)respData["GroupID"]);
912 data.GroupName = (string)respData["GroupName"];
913 data.GroupPicture = new UUID((string)respData["InsigniaID"]);
914 data.MaturePublish = ((string)respData["MaturePublish"] == "1");
915 data.MembershipFee = int.Parse((string)respData["MembershipFee"]);
916 data.OpenEnrollment = ((string)respData["OpenEnrollment"] == "1");
917 data.ShowInList = ((string)respData["ShowInList"] == "1");
918
919 return data;
920 }
921
922 #endregion
923
924 /// <summary>
925 /// Encapsulate the XmlRpc call to standardize security and error handling.
926 /// </summary>
927 private Hashtable XmlRpcCall(UUID requestingAgentID, string function, Hashtable param)
928 {
929 XmlRpcResponse resp = null;
930 string CacheKey = null;
931
932 // Only bother with the cache if it isn't disabled.
933 if (m_cacheTimeout > 0)
934 {
935 if (!function.StartsWith("groups.get"))
936 {
937 // Any and all updates cause the cache to clear
938 m_memoryCache.Clear();
939 }
940 else
941 {
942 StringBuilder sb = new StringBuilder(requestingAgentID + function);
943 foreach (object key in param.Keys)
944 {
945 if (param[key] != null)
946 {
947 sb.AppendFormat(",{0}:{1}", key.ToString(), param[key].ToString());
948 }
949 }
950
951 CacheKey = sb.ToString();
952 m_memoryCache.TryGetValue(CacheKey, out resp);
953 }
954 }
955
956 if (resp == null)
957 {
958 if (m_debugEnabled)
959 m_log.DebugFormat("[XMLRPC-GROUPS-CONNECTOR]: Cache miss for key {0}", CacheKey);
960
961 string UserService;
962 UUID SessionID;
963 GetClientGroupRequestID(requestingAgentID, out UserService, out SessionID);
964
965 param.Add("RequestingAgentID", requestingAgentID.ToString());
966 param.Add("RequestingAgentUserService", UserService);
967 param.Add("RequestingSessionID", SessionID.ToString());
968 param.Add("ReadKey", m_groupReadKey);
969 param.Add("WriteKey", m_groupWriteKey);
970
971 IList parameters = new ArrayList();
972 parameters.Add(param);
973
974 ConfigurableKeepAliveXmlRpcRequest req;
975 req = new ConfigurableKeepAliveXmlRpcRequest(function, parameters, m_disableKeepAlive);
976
977 try
978 {
979 resp = req.Send(m_groupsServerURI, 10000);
980
981 if ((m_cacheTimeout > 0) && (CacheKey != null))
982 {
983 m_memoryCache.AddOrUpdate(CacheKey, resp, TimeSpan.FromSeconds(m_cacheTimeout));
984 }
985 }
986 catch (Exception e)
987 {
988 m_log.ErrorFormat(
989 "[XMLRPC-GROUPS-CONNECTOR]: An error has occured while attempting to access the XmlRpcGroups server method {0} at {1}",
990 function, m_groupsServerURI);
991
992 m_log.ErrorFormat("[XMLRPC-GROUPS-CONNECTOR]: {0}{1}", e.Message, e.StackTrace);
993
994 foreach (string ResponseLine in req.RequestResponse.Split(new string[] { Environment.NewLine }, StringSplitOptions.None))
995 {
996 m_log.ErrorFormat("[XMLRPC-GROUPS-CONNECTOR]: {0} ", ResponseLine);
997 }
998
999 foreach (string key in param.Keys)
1000 {
1001 m_log.WarnFormat("[XMLRPC-GROUPS-CONNECTOR]: {0} :: {1}", key, param[key].ToString());
1002 }
1003
1004 Hashtable respData = new Hashtable();
1005 respData.Add("error", e.ToString());
1006 return respData;
1007 }
1008 }
1009
1010 if (resp.Value is Hashtable)
1011 {
1012 Hashtable respData = (Hashtable)resp.Value;
1013 if (respData.Contains("error") && !respData.Contains("succeed"))
1014 {
1015 LogRespDataToConsoleError(requestingAgentID, function, param, respData);
1016 }
1017
1018 return respData;
1019 }
1020
1021 m_log.ErrorFormat("[XMLRPC-GROUPS-CONNECTOR]: The XmlRpc server returned a {1} instead of a hashtable for {0}", function, resp.Value.GetType().ToString());
1022
1023 if (resp.Value is ArrayList)
1024 {
1025 ArrayList al = (ArrayList)resp.Value;
1026 m_log.ErrorFormat("[XMLRPC-GROUPS-CONNECTOR]: Contains {0} elements", al.Count);
1027
1028 foreach (object o in al)
1029 {
1030 m_log.ErrorFormat("[XMLRPC-GROUPS-CONNECTOR]: {0} :: {1}", o.GetType().ToString(), o.ToString());
1031 }
1032 }
1033 else
1034 {
1035 m_log.ErrorFormat("[XMLRPC-GROUPS-CONNECTOR]: Function returned: {0}", resp.Value.ToString());
1036 }
1037
1038 Hashtable error = new Hashtable();
1039 error.Add("error", "invalid return value");
1040 return error;
1041 }
1042
1043 private void LogRespDataToConsoleError(UUID requestingAgentID, string function, Hashtable param, Hashtable respData)
1044 {
1045 m_log.ErrorFormat(
1046 "[XMLRPC-GROUPS-CONNECTOR]: Error when calling {0} for {1} with params {2}. Response params are {3}",
1047 function, requestingAgentID, Util.PrettyFormatToSingleLine(param), Util.PrettyFormatToSingleLine(respData));
1048 }
1049
1050 /// <summary>
1051 /// Group Request Tokens are an attempt to allow the groups service to authenticate
1052 /// requests.
1053 /// TODO: This broke after the big grid refactor, either find a better way, or discard this
1054 /// </summary>
1055 /// <param name="client"></param>
1056 /// <returns></returns>
1057 private void GetClientGroupRequestID(UUID AgentID, out string UserServiceURL, out UUID SessionID)
1058 {
1059 UserServiceURL = "";
1060 SessionID = UUID.Zero;
1061
1062
1063 // Need to rework this based on changes to User Services
1064 /*
1065 UserAccount userAccount = m_accountService.GetUserAccount(UUID.Zero,AgentID);
1066 if (userAccount == null)
1067 {
1068 // This should be impossible. If I've been passed a reference to a client
1069 // that client should be registered with the UserService. So something
1070 // is horribly wrong somewhere.
1071
1072 m_log.WarnFormat("[GROUPS]: Could not find a UserServiceURL for {0}", AgentID);
1073
1074 }
1075 else if (userProfile is ForeignUserProfileData)
1076 {
1077 // They aren't from around here
1078 ForeignUserProfileData fupd = (ForeignUserProfileData)userProfile;
1079 UserServiceURL = fupd.UserServerURI;
1080 SessionID = fupd.CurrentAgent.SessionID;
1081
1082 }
1083 else
1084 {
1085 // They're a local user, use this:
1086 UserServiceURL = m_commManager.NetworkServersInfo.UserURL;
1087 SessionID = userProfile.CurrentAgent.SessionID;
1088 }
1089 */
1090 }
1091
1092 }
1093}
1094
1095namespace Nwc.XmlRpc
1096{
1097 using System;
1098 using System.Collections;
1099 using System.IO;
1100 using System.Xml;
1101 using System.Net;
1102 using System.Text;
1103 using System.Reflection;
1104
1105 /// <summary>Class supporting the request side of an XML-RPC transaction.</summary>
1106 public class ConfigurableKeepAliveXmlRpcRequest : XmlRpcRequest
1107 {
1108 private XmlRpcRequestSerializer _serializer = new XmlRpcRequestSerializer();
1109 private XmlRpcResponseDeserializer _deserializer = new XmlRpcResponseDeserializer();
1110 private bool _disableKeepAlive = true;
1111
1112 public string RequestResponse = String.Empty;
1113
1114 /// <summary>Instantiate an <c>XmlRpcRequest</c> for a specified method and parameters.</summary>
1115 /// <param name="methodName"><c>String</c> designating the <i>object.method</i> on the server the request
1116 /// should be directed to.</param>
1117 /// <param name="parameters"><c>ArrayList</c> of XML-RPC type parameters to invoke the request with.</param>
1118 public ConfigurableKeepAliveXmlRpcRequest(String methodName, IList parameters, bool disableKeepAlive)
1119 {
1120 MethodName = methodName;
1121 _params = parameters;
1122 _disableKeepAlive = disableKeepAlive;
1123 }
1124
1125 /// <summary>Send the request to the server.</summary>
1126 /// <param name="url"><c>String</c> The url of the XML-RPC server.</param>
1127 /// <returns><c>XmlRpcResponse</c> The response generated.</returns>
1128 public XmlRpcResponse Send(String url)
1129 {
1130 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
1131 if (request == null)
1132 throw new XmlRpcException(XmlRpcErrorCodes.TRANSPORT_ERROR,
1133 XmlRpcErrorCodes.TRANSPORT_ERROR_MSG + ": Could not create request with " + url);
1134 request.Method = "POST";
1135 request.ContentType = "text/xml";
1136 request.AllowWriteStreamBuffering = true;
1137 request.KeepAlive = !_disableKeepAlive;
1138
1139 using (Stream stream = request.GetRequestStream())
1140 {
1141 using (XmlTextWriter xml = new XmlTextWriter(stream, Encoding.ASCII))
1142 {
1143 _serializer.Serialize(xml, this);
1144 xml.Flush();
1145 }
1146 }
1147
1148 XmlRpcResponse resp;
1149
1150 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
1151 {
1152 using (Stream s = response.GetResponseStream())
1153 {
1154 using (StreamReader input = new StreamReader(s))
1155 {
1156 string inputXml = input.ReadToEnd();
1157
1158 try
1159 {
1160 resp = (XmlRpcResponse)_deserializer.Deserialize(inputXml);
1161 }
1162 catch (Exception e)
1163 {
1164 RequestResponse = inputXml;
1165 throw e;
1166 }
1167 }
1168 }
1169 }
1170
1171 return resp;
1172 }
1173 }
1174}