clockwerk-opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ |
||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
||
4 | * |
||
5 | * Redistribution and use in source and binary forms, with or without |
||
6 | * modification, are permitted provided that the following conditions are met: |
||
7 | * * Redistributions of source code must retain the above copyright |
||
8 | * notice, this list of conditions and the following disclaimer. |
||
9 | * * Redistributions in binary form must reproduce the above copyright |
||
10 | * notice, this list of conditions and the following disclaimer in the |
||
11 | * documentation and/or other materials provided with the distribution. |
||
12 | * * Neither the name of the 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 | |||
28 | using System; |
||
29 | using System.IO; |
||
30 | using System.Net; |
||
31 | using System.Text; |
||
32 | using System.Xml; |
||
33 | using System.Collections; |
||
34 | using System.Collections.Generic; |
||
35 | using System.Reflection; |
||
36 | using System.Threading; |
||
37 | using OpenMetaverse; |
||
38 | using log4net; |
||
39 | using Mono.Addins; |
||
40 | using Nini.Config; |
||
41 | using Nwc.XmlRpc; |
||
42 | using OpenSim.Framework; |
||
43 | |||
44 | using OpenSim.Framework.Capabilities; |
||
45 | using OpenSim.Framework.Servers; |
||
46 | using OpenSim.Framework.Servers.HttpServer; |
||
47 | using OpenSim.Region.Framework.Interfaces; |
||
48 | using OpenSim.Region.Framework.Scenes; |
||
49 | using Caps = OpenSim.Framework.Capabilities.Caps; |
||
50 | |||
51 | namespace 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 | } |