corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
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 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26  
27 using System;
28 using System.Collections.Generic;
29 using System.IO;
30 using System.Net.Sockets;
31 using System.Diagnostics;
32 using System.Threading;
33 using System.Text;
34  
35 namespace OpenMetaverse.Voice
36 {
37 public partial class VoiceGateway
38 {
39 public delegate void DaemonRunningCallback();
40 public delegate void DaemonExitedCallback();
41 public delegate void DaemonCouldntRunCallback();
42 public delegate void DaemonConnectedCallback();
43 public delegate void DaemonDisconnectedCallback();
44 public delegate void DaemonCouldntConnectCallback();
45  
46 public event DaemonRunningCallback OnDaemonRunning;
47 public event DaemonExitedCallback OnDaemonExited;
48 public event DaemonCouldntRunCallback OnDaemonCouldntRun;
49 public event DaemonConnectedCallback OnDaemonConnected;
50 public event DaemonDisconnectedCallback OnDaemonDisconnected;
51 public event DaemonCouldntConnectCallback OnDaemonCouldntConnect;
52  
53 public bool DaemonIsRunning { get { return daemonIsRunning; } }
54 public bool DaemonIsConnected { get { return daemonIsConnected; } }
55 public int RequestId { get { return requestId; } }
56  
57 protected Process daemonProcess;
58 protected ManualResetEvent daemonLoopSignal = new ManualResetEvent(false);
59 protected TCPPipe daemonPipe;
60 protected bool daemonIsRunning = false;
61 protected bool daemonIsConnected = false;
62 protected int requestId = 0;
63  
64 #region Daemon Management
65  
66 /// <summary>
67 /// Starts a thread that keeps the daemon running
68 /// </summary>
69 /// <param name="path"></param>
70 /// <param name="args"></param>
71 public void StartDaemon(string path, string args)
72 {
73 StopDaemon();
74 daemonLoopSignal.Set();
75  
76 Thread thread = new Thread(new ThreadStart(delegate()
77 {
78 while (daemonLoopSignal.WaitOne(500, false))
79 {
80 daemonProcess = new Process();
81 daemonProcess.StartInfo.FileName = path;
82 daemonProcess.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
83 daemonProcess.StartInfo.Arguments = args;
84 daemonProcess.StartInfo.UseShellExecute = false;
85  
86 if (Environment.OSVersion.Platform == PlatformID.Unix)
87 {
88 string ldPath = string.Empty;
89 try
90 {
91 ldPath = Environment.GetEnvironmentVariable("LD_LIBRARY_PATH");
92 }
93 catch { }
94 string newLdPath = daemonProcess.StartInfo.WorkingDirectory;
95 if (!string.IsNullOrEmpty(ldPath))
96 newLdPath += ":" + ldPath;
97 daemonProcess.StartInfo.EnvironmentVariables.Add("LD_LIBRARY_PATH", newLdPath);
98 }
99  
100 Logger.DebugLog("Voice folder: " + daemonProcess.StartInfo.WorkingDirectory);
101 Logger.DebugLog(path + " " + args);
102 bool ok = true;
103  
104 if (!File.Exists(path))
105 ok = false;
106  
107 if (ok)
108 {
109 // Attempt to start the process
110 if (!daemonProcess.Start())
111 ok = false;
112 }
113  
114 if (!ok)
115 {
116 daemonIsRunning = false;
117 daemonLoopSignal.Reset();
118  
119 if (OnDaemonCouldntRun != null)
120 {
121 try { OnDaemonCouldntRun(); }
122 catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, null, e); }
123 }
124  
125 return;
126 }
127 else
128 {
129 Thread.Sleep(2000);
130 daemonIsRunning = true;
131 if (OnDaemonRunning != null)
132 {
133 try { OnDaemonRunning(); }
134 catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, null, e); }
135 }
136  
137 Logger.DebugLog("Started voice daemon, waiting for exit...");
138 daemonProcess.WaitForExit();
139 Logger.DebugLog("Voice daemon exited");
140 daemonIsRunning = false;
141  
142 if (OnDaemonExited != null)
143 {
144 try { OnDaemonExited(); }
145 catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, null, e); }
146 }
147 }
148 }
149 }));
150  
151 thread.Name = "VoiceDaemonController";
152 thread.IsBackground = true;
153 thread.Start();
154 }
155  
156 /// <summary>
157 /// Stops the daemon and the thread keeping it running
158 /// </summary>
159 public void StopDaemon()
160 {
161 daemonLoopSignal.Reset();
162 if (daemonProcess != null)
163 {
164 try
165 {
166 daemonProcess.Kill();
167 }
168 catch (InvalidOperationException ex)
169 {
170 Logger.Log("Failed to stop the voice daemon", Helpers.LogLevel.Error, ex);
171 }
172 }
173 }
174  
175 /// <summary>
176 ///
177 /// </summary>
178 /// <param name="address"></param>
179 /// <param name="port"></param>
180 /// <returns></returns>
181 public bool ConnectToDaemon(string address, int port)
182 {
183 daemonIsConnected = false;
184  
185 daemonPipe = new TCPPipe();
186 daemonPipe.OnDisconnected +=
187 delegate(SocketException e)
188 {
189 if (OnDaemonDisconnected != null)
190 {
191 try { OnDaemonDisconnected(); }
192 catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, null, ex); }
193 }
194 };
195 daemonPipe.OnReceiveLine += new TCPPipe.OnReceiveLineCallback(daemonPipe_OnReceiveLine);
196  
197 SocketException se = daemonPipe.Connect(address, port);
198 if (se == null)
199 {
200 daemonIsConnected = true;
201  
202 if (OnDaemonConnected != null)
203 {
204 try { OnDaemonConnected(); }
205 catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, null, e); }
206 }
207  
208 return true;
209 }
210 else
211 {
212 daemonIsConnected = false;
213  
214 if (OnDaemonCouldntConnect != null)
215 {
216 try { OnDaemonCouldntConnect(); }
217 catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, null, e); }
218 }
219  
220 Logger.Log("Voice daemon connection failed: " + se.Message, Helpers.LogLevel.Error);
221 return false;
222 }
223 }
224  
225 #endregion Daemon Management
226  
227 public int Request(string action)
228 {
229 return Request(action, null);
230 }
231  
232 public int Request(string action, string requestXML)
233 {
234 int returnId = requestId;
235 if (daemonIsConnected)
236 {
237 StringBuilder sb = new StringBuilder();
238 sb.Append(String.Format("<Request requestId=\"{0}\" action=\"{1}\"", requestId++, action));
239 if (string.IsNullOrEmpty(requestXML))
240 {
241 sb.Append(" />");
242 }
243 else
244 {
245 sb.Append(">");
246 sb.Append(requestXML);
247 sb.Append("</Request>");
248 }
249 sb.Append("\n\n\n");
250  
251 #if DEBUG
252 Logger.Log("Request: " + sb.ToString(), Helpers.LogLevel.Debug);
253 #endif
254 try
255 {
256 daemonPipe.SendData(Encoding.ASCII.GetBytes(sb.ToString()));
257 }
258 catch
259 {
260 returnId = -1;
261 }
262  
263 return returnId;
264 }
265 else
266 {
267 return -1;
268 }
269 }
270  
271 public static string MakeXML(string name, string text)
272 {
273 if (string.IsNullOrEmpty(text))
274 return string.Format("<{0} />", name);
275 else
276 return string.Format("<{0}>{1}</{0}>", name, text);
277 }
278  
279 private void daemonPipe_OnReceiveLine(string line)
280 {
281 #if DEBUG
282 Logger.Log(line, Helpers.LogLevel.Debug);
283 #endif
284  
285 if (line.Substring(0, 10) == "<Response ")
286 {
287 VoiceResponse rsp = null;
288 try
289 {
290 rsp = (VoiceResponse)ResponseSerializer.Deserialize(new StringReader(line));
291 }
292 catch (Exception e)
293 {
294 Logger.Log("Failed to deserialize voice daemon response", Helpers.LogLevel.Error, e);
295 return;
296 }
297  
298 ResponseType genericResponse = ResponseType.None;
299  
300 switch (rsp.Action)
301 {
302 // These first responses carry useful information beyond simple status,
303 // so they each have a specific Event.
304 case "Connector.Create.1":
305 if (OnConnectorCreateResponse != null)
306 {
307 OnConnectorCreateResponse(
308 rsp.InputXml.Request,
309 new VoiceConnectorEventArgs(
310 int.Parse(rsp.ReturnCode),
311 int.Parse(rsp.Results.StatusCode),
312 rsp.Results.StatusString,
313 rsp.Results.VersionID,
314 rsp.Results.ConnectorHandle));
315 }
316 break;
317 case "Aux.GetCaptureDevices.1":
318 inputDevices = new List<string>();
319  
320 if (rsp.Results.CaptureDevices.Count == 0 || rsp.Results.CurrentCaptureDevice == null)
321 break;
322  
323 foreach (CaptureDevice device in rsp.Results.CaptureDevices)
324 inputDevices.Add(device.Device);
325 currentCaptureDevice = rsp.Results.CurrentCaptureDevice.Device;
326  
327 if (OnAuxGetCaptureDevicesResponse != null && rsp.Results.CaptureDevices.Count > 0)
328 {
329 OnAuxGetCaptureDevicesResponse(
330 rsp.InputXml.Request,
331 new VoiceDevicesEventArgs(
332 ResponseType.GetCaptureDevices,
333 int.Parse(rsp.ReturnCode),
334 int.Parse(rsp.Results.StatusCode),
335 rsp.Results.StatusString,
336 rsp.Results.CurrentCaptureDevice.Device,
337 inputDevices));
338 }
339 break;
340 case "Aux.GetRenderDevices.1":
341 outputDevices = new List<string>();
342  
343 if (rsp.Results.RenderDevices.Count == 0 || rsp.Results.CurrentRenderDevice == null)
344 break;
345  
346 foreach (RenderDevice device in rsp.Results.RenderDevices)
347 outputDevices.Add(device.Device);
348  
349  
350 currentPlaybackDevice = rsp.Results.CurrentRenderDevice.Device;
351  
352 if (OnAuxGetRenderDevicesResponse != null)
353 {
354 OnAuxGetRenderDevicesResponse(
355 rsp.InputXml.Request,
356 new VoiceDevicesEventArgs(
357 ResponseType.GetCaptureDevices,
358 int.Parse(rsp.ReturnCode),
359 int.Parse(rsp.Results.StatusCode),
360 rsp.Results.StatusString,
361 rsp.Results.CurrentRenderDevice.Device,
362 outputDevices));
363 }
364 break;
365  
366 case "Account.Login.1":
367 if (OnAccountLoginResponse != null)
368 {
369 OnAccountLoginResponse(rsp.InputXml.Request,
370 new VoiceAccountEventArgs(
371 int.Parse(rsp.ReturnCode),
372 int.Parse(rsp.Results.StatusCode),
373 rsp.Results.StatusString,
374 rsp.Results.AccountHandle));
375 }
376 break;
377  
378 case "Session.Create.1":
379 if (OnSessionCreateResponse != null)
380 {
381 OnSessionCreateResponse(
382 rsp.InputXml.Request,
383 new VoiceSessionEventArgs(
384 int.Parse(rsp.ReturnCode),
385 int.Parse(rsp.Results.StatusCode),
386 rsp.Results.StatusString,
387 rsp.Results.SessionHandle));
388 }
389 break;
390  
391 // All the remaining responses below this point just report status,
392 // so they all share the same Event. Most are useful only for
393 // detecting coding errors.
394 case "Connector.InitiateShutdown.1":
395 genericResponse = ResponseType.ConnectorInitiateShutdown;
396 break;
397 case "Aux.SetRenderDevice.1":
398 genericResponse = ResponseType.SetRenderDevice;
399 break;
400 case "Connector.MuteLocalMic.1":
401 genericResponse = ResponseType.MuteLocalMic;
402 break;
403 case "Connector.MuteLocalSpeaker.1":
404 genericResponse = ResponseType.MuteLocalSpeaker;
405 break;
406 case "Connector.SetLocalMicVolume.1":
407 genericResponse = ResponseType.SetLocalMicVolume;
408 break;
409 case "Connector.SetLocalSpeakerVolume.1":
410 genericResponse = ResponseType.SetLocalSpeakerVolume;
411 break;
412 case "Aux.SetCaptureDevice.1":
413 genericResponse = ResponseType.SetCaptureDevice;
414 break;
415 case "Session.RenderAudioStart.1":
416 genericResponse = ResponseType.RenderAudioStart;
417 break;
418 case "Session.RenderAudioStop.1":
419 genericResponse = ResponseType.RenderAudioStop;
420 break;
421 case "Aux.CaptureAudioStart.1":
422 genericResponse = ResponseType.CaptureAudioStart;
423 break;
424 case "Aux.CaptureAudioStop.1":
425 genericResponse = ResponseType.CaptureAudioStop;
426 break;
427 case "Aux.SetMicLevel.1":
428 genericResponse = ResponseType.SetMicLevel;
429 break;
430 case "Aux.SetSpeakerLevel.1":
431 genericResponse = ResponseType.SetSpeakerLevel;
432 break;
433 case "Account.Logout.1":
434 genericResponse = ResponseType.AccountLogout;
435 break;
436 case "Session.Connect.1":
437 genericResponse = ResponseType.SessionConnect;
438 break;
439 case "Session.Terminate.1":
440 genericResponse = ResponseType.SessionTerminate;
441 break;
442 case "Session.SetParticipantVolumeForMe.1":
443 genericResponse = ResponseType.SetParticipantVolumeForMe;
444 break;
445 case "Session.SetParticipantMuteForMe.1":
446 genericResponse = ResponseType.SetParticipantMuteForMe;
447 break;
448 case "Session.Set3DPosition.1":
449 genericResponse = ResponseType.Set3DPosition;
450 break;
451 default:
452 Logger.Log("Unimplemented response from the voice daemon: " + line, Helpers.LogLevel.Error);
453 break;
454 }
455  
456 // Send the Response Event for all the simple cases.
457 if (genericResponse != ResponseType.None && OnVoiceResponse != null)
458 {
459 OnVoiceResponse(rsp.InputXml.Request,
460 new VoiceResponseEventArgs(
461 genericResponse,
462 int.Parse(rsp.ReturnCode),
463 int.Parse(rsp.Results.StatusCode),
464 rsp.Results.StatusString));
465 }
466 }
467 else if (line.Substring(0, 7) == "<Event ")
468 {
469 VoiceEvent evt = null;
470 try
471 {
472 evt = (VoiceEvent)EventSerializer.Deserialize(new StringReader(line));
473 }
474 catch (Exception e)
475 {
476 Logger.Log("Failed to deserialize voice daemon event", Helpers.LogLevel.Error, e);
477 return;
478 }
479  
480 switch (evt.Type)
481 {
482 case "LoginStateChangeEvent":
483 case "AccountLoginStateChangeEvent":
484 if (OnAccountLoginStateChangeEvent != null)
485 {
486 OnAccountLoginStateChangeEvent(this,
487 new AccountLoginStateChangeEventArgs(
488 evt.AccountHandle,
489 int.Parse(evt.StatusCode),
490 evt.StatusString,
491 (LoginState)int.Parse(evt.State)));
492 }
493 break;
494  
495 case "SessionNewEvent":
496 if (OnSessionNewEvent != null)
497 {
498 OnSessionNewEvent(this,
499 new NewSessionEventArgs(
500 evt.AccountHandle,
501 evt.SessionHandle,
502 evt.URI,
503 bool.Parse(evt.IsChannel),
504 evt.Name,
505 evt.AudioMedia));
506 }
507 break;
508  
509 case "SessionStateChangeEvent":
510 if (OnSessionStateChangeEvent != null)
511 {
512 OnSessionStateChangeEvent(this,
513 new SessionStateChangeEventArgs(
514 evt.SessionHandle,
515 int.Parse(evt.StatusCode),
516 evt.StatusString,
517 (SessionState)int.Parse(evt.State),
518 evt.URI,
519 bool.Parse(evt.IsChannel),
520 evt.ChannelName));
521 }
522 break;
523  
524 case "ParticipantAddedEvent":
525 Logger.Log("Add participant " + evt.ParticipantUri, Helpers.LogLevel.Debug);
526 if (OnSessionParticipantAddedEvent != null)
527 {
528 OnSessionParticipantAddedEvent(this,
529 new ParticipantAddedEventArgs(
530 evt.SessionGroupHandle,
531 evt.SessionHandle,
532 evt.ParticipantUri,
533 evt.AccountName,
534 evt.DisplayName,
535 (ParticipantType)int.Parse(evt.ParticipantType),
536 evt.Application));
537 }
538 break;
539  
540 case "ParticipantRemovedEvent":
541 if (OnSessionParticipantRemovedEvent != null)
542 {
543 OnSessionParticipantRemovedEvent(this,
544 new ParticipantRemovedEventArgs(
545 evt.SessionGroupHandle,
546 evt.SessionHandle,
547 evt.ParticipantUri,
548 evt.AccountName,
549 evt.Reason));
550 }
551 break;
552  
553 case "ParticipantStateChangeEvent":
554 // Useful in person-to-person calls
555 if (OnSessionParticipantStateChangeEvent != null)
556 {
557 OnSessionParticipantStateChangeEvent(this,
558 new ParticipantStateChangeEventArgs(
559 evt.SessionHandle,
560 int.Parse(evt.StatusCode),
561 evt.StatusString,
562 (ParticipantState)int.Parse(evt.State), // Ringing, Connected, etc
563 evt.ParticipantUri,
564 evt.AccountName,
565 evt.DisplayName,
566 (ParticipantType)int.Parse(evt.ParticipantType)));
567 }
568 break;
569  
570 case "ParticipantPropertiesEvent":
571 if (OnSessionParticipantPropertiesEvent != null)
572 {
573 OnSessionParticipantPropertiesEvent(this,
574 new ParticipantPropertiesEventArgs(
575 evt.SessionHandle,
576 evt.ParticipantUri,
577 bool.Parse(evt.IsLocallyMuted),
578 bool.Parse(evt.IsModeratorMuted),
579 bool.Parse(evt.IsSpeaking),
580 int.Parse(evt.Volume),
581 float.Parse(evt.Energy)));
582 }
583 break;
584  
585 case "ParticipantUpdatedEvent":
586 if (OnSessionParticipantUpdatedEvent != null)
587 {
588 OnSessionParticipantUpdatedEvent(this,
589 new ParticipantUpdatedEventArgs(
590 evt.SessionHandle,
591 evt.ParticipantUri,
592 bool.Parse(evt.IsModeratorMuted),
593 bool.Parse(evt.IsSpeaking),
594 int.Parse(evt.Volume),
595 float.Parse(evt.Energy)));
596 }
597 break;
598  
599 case "SessionGroupAddedEvent":
600 if (OnSessionGroupAddedEvent != null)
601 {
602 OnSessionGroupAddedEvent(this,
603 new SessionGroupAddedEventArgs(
604 evt.AccountHandle,
605 evt.SessionGroupHandle,
606 evt.Type));
607 }
608 break;
609  
610 case "SessionAddedEvent":
611 if (OnSessionAddedEvent != null)
612 {
613 OnSessionAddedEvent(this,
614 new SessionAddedEventArgs(
615 evt.SessionGroupHandle,
616 evt.SessionHandle,
617 evt.Uri,
618 bool.Parse(evt.IsChannel),
619 bool.Parse(evt.Incoming)));
620 }
621 break;
622  
623 case "SessionRemovedEvent":
624 if (OnSessionRemovedEvent != null)
625 {
626 OnSessionRemovedEvent(this,
627 new SessionRemovedEventArgs(
628 evt.SessionGroupHandle,
629 evt.SessionHandle,
630 evt.Uri));
631 }
632 break;
633  
634 case "SessionUpdatedEvent":
635 if (OnSessionRemovedEvent != null)
636 {
637 OnSessionUpdatedEvent(this,
638 new SessionUpdatedEventArgs(
639 evt.SessionGroupHandle,
640 evt.SessionHandle,
641 evt.Uri,
642 int.Parse(evt.IsMuted) != 0,
643 int.Parse(evt.Volume),
644 int.Parse(evt.TransmitEnabled) != 0,
645 + int.Parse(evt.IsFocused) != 0));
646 }
647 break;
648  
649 case "AuxAudioPropertiesEvent":
650 if (OnAuxAudioPropertiesEvent != null)
651 {
652 OnAuxAudioPropertiesEvent(this,
653 new AudioPropertiesEventArgs(
654 bool.Parse(evt.MicIsActive),
655 float.Parse(evt.MicEnergy),
656 int.Parse(evt.MicVolume),
657 int.Parse(evt.SpeakerVolume)));
658 }
659 break;
660  
661 case "SessionMediaEvent":
662 if (OnSessionMediaEvent != null)
663 {
664 OnSessionMediaEvent(this,
665 new SessionMediaEventArgs(
666 evt.SessionHandle,
667 bool.Parse(evt.HasText),
668 bool.Parse(evt.HasAudio),
669 bool.Parse(evt.HasVideo),
670 bool.Parse(evt.Terminated)));
671 }
672 break;
673  
674 case "BuddyAndGroupListChangedEvent":
675 // TODO * <AccountHandle>c1_m1000xrjiQgi95QhCzH_D6ZJ8c5A==</AccountHandle><Buddies /><Groups />
676 break;
677  
678 case "MediaStreamUpdatedEvent":
679 // TODO <SessionGroupHandle>c1_m1000xrjiQgi95QhCzH_D6ZJ8c5A==_sg0</SessionGroupHandle>
680 // <SessionHandle>c1_m1000xrjiQgi95QhCzH_D6ZJ8c5A==0</SessionHandle>
681 //<StatusCode>0</StatusCode><StatusString /><State>1</State><Incoming>false</Incoming>
682  
683 break;
684  
685 default:
686 Logger.Log("Unimplemented event from the voice daemon: " + line, Helpers.LogLevel.Error);
687 break;
688 }
689 }
690 else
691 {
692 Logger.Log("Unrecognized data from the voice daemon: " + line, Helpers.LogLevel.Error);
693 }
694 }
695 }
696 }