OSDN Git Service

Merged Mojito's sounds branch
[radegast/radegast.git] / Radegast / Core / RadegastInstance.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009, Radegast Development Team
4 // All rights reserved.
5 // 
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 
9 //     * Redistributions of source code must retain the above copyright notice,
10 //       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 application "Radegast", nor the names of its
15 //       contributors may be used to endorse or promote products derived from
16 //       this software without specific prior written permission.
17 // 
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // $Id$
30 //
31 using System;
32 using System.Collections.Generic;
33 using System.IO;
34 using System.Reflection;
35 using System.Threading;
36 using System.Windows.Forms;
37 using Radegast.Commands;
38 using Radegast.Netcom;
39 using Radegast.Media;
40 using OpenMetaverse;
41
42 namespace Radegast
43 {
44     public class RadegastInstance
45     {
46         #region OnRadegastFormCreated
47         public event Action<RadegastForm> RadegastFormCreated;
48         /// <summary>
49         /// Triggers the RadegastFormCreated event.
50         /// </summary>
51         public virtual void OnRadegastFormCreated(RadegastForm radForm)
52         {
53             if (RadegastFormCreated != null) RadegastFormCreated(radForm);
54         }
55         #endregion
56         private GridClient client;
57         private RadegastNetcom netcom;
58
59         private StateManager state;
60
61         private frmMain mainForm;
62
63         // Singleton, there can be only one instance
64         private static RadegastInstance globalInstance = null;
65         public static RadegastInstance GlobalInstance
66         {
67             get
68             {
69                 if (globalInstance == null)
70                 {
71                     globalInstance = new RadegastInstance(new GridClient());
72                 }
73                 return globalInstance;
74             }
75         }
76
77         private string userDir;
78         /// <summary>
79         /// System (not grid!) user's dir
80         /// </summary>
81         public string UserDir { get { return userDir; } }
82
83         /// <summary>
84         /// Grid client's user dir for settings and logs
85         /// </summary>
86         public string ClientDir
87         {
88             get
89             {
90                 if (client != null && client.Self != null && !string.IsNullOrEmpty(client.Self.Name))
91                 {
92                     return Path.Combine(userDir, client.Self.Name);
93                 }
94                 else
95                 {
96                     return Environment.CurrentDirectory;
97                 }
98             }
99         }
100
101         public string InventoryCacheFileName { get { return Path.Combine(ClientDir, "inventory.cache"); } }
102
103         private string globalLogFile;
104         public string GlobalLogFile { get { return globalLogFile; } }
105
106         private bool monoRuntime;
107         public bool MonoRuntime { get { return monoRuntime; } }
108
109         private Dictionary<UUID, Group> groups = new Dictionary<UUID, Group>();
110         public Dictionary<UUID, Group> Groups { get { return groups; } }
111
112         private Settings globalSettings;
113         /// <summary>
114         /// Global settings for the entire application
115         /// </summary>
116         public Settings GlobalSettings { get { return globalSettings; } }
117
118         private Settings clientSettings;
119         /// <summary>
120         /// Per client settings
121         /// </summary>
122         public Settings ClientSettings { get { return clientSettings; } }
123
124         public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();
125
126         public const string INCOMPLETE_NAME = "Loading...";
127
128         public readonly bool advancedDebugging = false;
129
130         private PluginManager pluginManager;
131         /// <summary> Handles loading plugins and scripts</summary>
132         public PluginManager PluginManager { get { return pluginManager; } }
133
134         private MediaManager mediaManager;
135         /// <summary>
136         /// Radegast media manager for playing streams and in world sounds
137         /// </summary>
138         public MediaManager MediaManager { get { return mediaManager; } }
139
140
141         private CommandsManager commandsManager;
142         /// <summary>
143         /// Radegast command manager for executing textual console commands
144         /// </summary>
145         public CommandsManager CommandsManager { get { return commandsManager; } }
146
147         /// <summary>
148         /// Radegast ContextAction manager for context sensitive actions
149         /// </summary>
150         public ContextActionsManager ContextActionManager { get; private set; }
151
152         private RadegastMovement movement;
153         /// <summary>
154         /// Allows key emulation for moving avatar around
155         /// </summary>
156         public RadegastMovement Movement { get { return movement; } }
157
158         private InventoryClipboard inventoryClipboard;
159         /// <summary>
160         /// The last item that was cut or copied in the inventory, used for pasting
161         /// in a different place on the inventory, or other places like profile
162         /// that allow sending copied inventory items
163         /// </summary>
164         public InventoryClipboard InventoryClipboard
165         {
166             get { return inventoryClipboard; }
167             set
168             {
169                 inventoryClipboard = value;
170                 OnInventoryClipboardUpdated(EventArgs.Empty);
171             }
172         }
173
174         private RLVManager rlv;
175
176         /// <summary>
177         /// Manager for RLV functionality
178         /// </summary>
179         public RLVManager RLV { get { return rlv; } }
180
181         private GridManager gridManager;
182         /// <summary>Manages default params for different grids</summary>
183         public GridManager GridManger { get { return gridManager; } }
184
185         #region Events
186
187         #region ClientChanged event
188         /// <summary>The event subscribers, null of no subscribers</summary>
189         private EventHandler<ClientChangedEventArgs> m_ClientChanged;
190
191         ///<summary>Raises the ClientChanged Event</summary>
192         /// <param name="e">A ClientChangedEventArgs object containing
193         /// the old and the new client</param>
194         protected virtual void OnClientChanged(ClientChangedEventArgs e)
195         {
196             EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;
197             if (handler != null)
198                 handler(this, e);
199         }
200
201         /// <summary>Thread sync lock object</summary>
202         private readonly object m_ClientChangedLock = new object();
203
204         /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
205         public event EventHandler<ClientChangedEventArgs> ClientChanged
206         {
207             add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }
208             remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }
209         }
210         #endregion ClientChanged event
211
212         #region InventoryClipboardUpdated event
213         /// <summary>The event subscribers, null of no subscribers</summary>
214         private EventHandler<EventArgs> m_InventoryClipboardUpdated;
215
216         ///<summary>Raises the InventoryClipboardUpdated Event</summary>
217         /// <param name="e">A EventArgs object containing
218         /// the old and the new client</param>
219         protected virtual void OnInventoryClipboardUpdated(EventArgs e)
220         {
221             EventHandler<EventArgs> handler = m_InventoryClipboardUpdated;
222             if (handler != null)
223                 handler(this, e);
224         }
225
226         /// <summary>Thread sync lock object</summary>
227         private readonly object m_InventoryClipboardUpdatedLock = new object();
228
229         /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
230         public event EventHandler<EventArgs> InventoryClipboardUpdated
231         {
232             add { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated += value; } }
233             remove { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated -= value; } }
234         }
235         #endregion InventoryClipboardUpdated event
236
237
238         #endregion Events
239
240         public RadegastInstance(GridClient client0)
241         {
242             // incase something else calls GlobalInstance while we are loading
243             globalInstance = this;
244
245 #if !DEBUG
246             Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
247             Application.ThreadException += HandleThreadException;
248 #endif
249
250             client = client0;
251
252             // Are we running mono?
253             monoRuntime = Type.GetType("Mono.Runtime") != null;
254
255             netcom = new RadegastNetcom(this);
256             state = new StateManager(this);
257             mediaManager = new MediaManager(this);
258             commandsManager = new CommandsManager(this);
259             ContextActionManager = new ContextActionsManager(this);
260             movement = new RadegastMovement(this);
261
262             InitializeLoggingAndConfig();
263             InitializeClient(client);
264
265             rlv = new RLVManager(this);
266             gridManager = new GridManager(this);
267             gridManager.LoadGrids();
268
269             mainForm = new frmMain(this);
270             mainForm.InitializeControls();
271
272             mainForm.Load += new EventHandler(mainForm_Load);
273             pluginManager = new PluginManager(this);
274             pluginManager.ScanAndLoadPlugins();
275         }
276
277         private void InitializeClient(GridClient client)
278         {
279             client.Settings.MULTIPLE_SIMS = true;
280
281             client.Settings.USE_INTERPOLATION_TIMER = false;
282             client.Settings.ALWAYS_REQUEST_OBJECTS = true;
283             client.Settings.ALWAYS_DECODE_OBJECTS = true;
284             client.Settings.OBJECT_TRACKING = true;
285             client.Settings.ENABLE_SIMSTATS = true;
286             client.Settings.FETCH_MISSING_INVENTORY = true;
287             client.Settings.SEND_AGENT_THROTTLE = true;
288             client.Settings.SEND_AGENT_UPDATES = true;
289
290             client.Settings.USE_ASSET_CACHE = true;
291             client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");
292             client.Assets.Cache.AutoPruneEnabled = false;
293
294             client.Throttle.Total = 5000000f;
295             client.Settings.THROTTLE_OUTGOING_PACKETS = true;
296             client.Settings.LOGIN_TIMEOUT = 120 * 1000;
297             client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;
298             client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;
299
300             RegisterClientEvents(client);
301         }
302
303         private void RegisterClientEvents(GridClient client)
304         {
305             client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
306             client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
307             client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
308             client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
309             client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
310             if (netcom != null)
311                 netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
312         }
313
314         private void UnregisterClientEvents(GridClient client)
315         {
316             client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
317             client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
318             client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
319             client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
320             client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
321             if (netcom != null)
322                 netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
323         }
324
325         public void Reconnect()
326         {
327             TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);
328             Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);
329             GridClient oldClient = client;
330             client = new GridClient();
331             UnregisterClientEvents(oldClient);
332             InitializeClient(client);
333             OnClientChanged(new ClientChangedEventArgs(oldClient, client));
334             netcom.Login();
335         }
336
337         public void CleanUp()
338         {
339             if (gridManager != null)
340             {
341                 gridManager.Dispose();
342                 gridManager = null;
343             }
344
345             if (rlv != null)
346             {
347                 rlv.Dispose();
348                 rlv = null;
349             }
350
351             if (client != null)
352             {
353                 UnregisterClientEvents(client);
354             }
355
356             if (pluginManager != null)
357             {
358                 pluginManager.Dispose();
359                 pluginManager = null;
360             }
361
362             if (movement != null)
363             {
364                 movement.Dispose();
365                 movement = null;
366             }
367             if (commandsManager != null)
368             {
369                 commandsManager.Dispose();
370                 commandsManager = null;
371             }
372             if (ContextActionManager != null)
373             {
374                 ContextActionManager.Dispose();
375                 ContextActionManager = null;
376             }
377             if (mediaManager != null)
378             {
379                 mediaManager.Dispose();
380                 mediaManager = null;
381             }
382             if (state != null)
383             {
384                 state.Dispose();
385                 state = null;
386             }
387             if (netcom != null)
388             {
389                 netcom.Dispose();
390                 netcom = null;
391             }
392             if (mainForm != null)
393             {
394                 mainForm.Load -= new EventHandler(mainForm_Load);
395             }
396             Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
397         }
398
399         void mainForm_Load(object sender, EventArgs e)
400         {
401             pluginManager.StartPlugins();
402         }
403
404         void netcom_ClientConnected(object sender, EventArgs e)
405         {
406             try
407             {
408                 if (!Directory.Exists(ClientDir))
409                     Directory.CreateDirectory(ClientDir);
410             }
411             catch (Exception ex)
412             {
413                 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
414             }
415
416             clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
417         }
418
419
420         void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
421         {
422             lock (nameCache)
423             {
424                 foreach (KeyValuePair<UUID, string> av in e.Names)
425                 {
426                     if (!nameCache.ContainsKey(av.Key))
427                     {
428                         nameCache.Add(av.Key, av.Value);
429                     }
430                 }
431             }
432         }
433
434         /// <summary>
435         /// Fetches avatar name
436         /// </summary>
437         /// <param name="key">Avatar UUID</param>
438         /// <param name="blocking">Should we wait until the name is retrieved</param>
439         /// <returns>Avatar name</returns>
440         public string getAvatarName(UUID key, bool blocking)
441         {
442             if (!blocking)
443                 return getAvatarName(key);
444
445             string name = null;
446
447             using (ManualResetEvent gotName = new ManualResetEvent(false))
448             {
449
450                 EventHandler<UUIDNameReplyEventArgs> handler = (object sender, UUIDNameReplyEventArgs e) =>
451                     {
452                         if (e.Names.ContainsKey(key))
453                         {
454                             name = e.Names[key];
455                             gotName.Set();
456                         }
457                     };
458
459                 client.Avatars.UUIDNameReply += handler;
460                 name = getAvatarName(key);
461
462                 if (name == INCOMPLETE_NAME)
463                 {
464                     gotName.WaitOne(10 * 1000);
465                 }
466
467                 client.Avatars.UUIDNameReply -= handler;
468             }
469             return name;
470
471         }
472
473         /// <summary>
474         /// Fetches avatar name from cache, if not in cache will requst name from the server
475         /// </summary>
476         /// <param name="key">Avatar UUID</param>
477         /// <returns>Avatar name</returns>
478         public string getAvatarName(UUID key)
479         {
480             lock (nameCache)
481             {
482                 if (key == UUID.Zero)
483                 {
484                     return "(???) (???)";
485                 }
486                 if (nameCache.ContainsKey(key))
487                 {
488                     return nameCache[key];
489                 }
490                 else
491                 {
492                     client.Avatars.RequestAvatarName(key);
493                     return INCOMPLETE_NAME;
494                 }
495             }
496         }
497
498         public void getAvatarNames(List<UUID> keys)
499         {
500             lock (nameCache)
501             {
502                 List<UUID> newNames = new List<UUID>();
503                 foreach (UUID key in keys)
504                 {
505                     if (!nameCache.ContainsKey(key))
506                     {
507                         newNames.Add(key);
508                     }
509                 }
510                 if (newNames.Count > 0)
511                 {
512                     client.Avatars.RequestAvatarNames(newNames);
513                 }
514             }
515         }
516
517         public bool haveAvatarName(UUID key)
518         {
519             lock (nameCache)
520             {
521                 if (nameCache.ContainsKey(key))
522                     return true;
523                 else
524                     return false;
525             }
526         }
527
528         void Groups_GroupsChanged(object sender, EventArgs e)
529         {
530             client.Groups.RequestCurrentGroups();
531         }
532
533         public static string SafeFileName(string fileName)
534         {
535             foreach (char lDisallowed in Path.GetInvalidFileNameChars())
536             {
537                 fileName = fileName.Replace(lDisallowed.ToString(), "_");
538             }
539
540             return fileName;
541         }
542
543         public void LogClientMessage(string fileName, string message)
544         {
545             lock (this)
546             {
547                 try
548                 {
549                     foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
550                     {
551                         fileName = fileName.Replace(lDisallowed.ToString(), "_");
552                     }
553
554                     File.AppendAllText(Path.Combine(ClientDir, fileName),
555                         DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
556                 }
557                 catch (Exception) { }
558             }
559         }
560
561         void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
562         {
563             this.groups = e.Groups;
564         }
565
566         private void InitializeLoggingAndConfig()
567         {
568             try
569             {
570                 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
571                 if (!Directory.Exists(userDir))
572                 {
573                     Directory.CreateDirectory(userDir);
574                 }
575             }
576             catch (Exception)
577             {
578                 userDir = System.Environment.CurrentDirectory;
579             };
580
581             globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
582             globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
583         }
584
585         public GridClient Client
586         {
587             get { return client; }
588         }
589
590         public RadegastNetcom Netcom
591         {
592             get { return netcom; }
593         }
594
595         public StateManager State
596         {
597             get { return state; }
598         }
599
600         public frmMain MainForm
601         {
602             get { return mainForm; }
603         }
604
605         public TabsConsole TabConsole
606         {
607             get { return mainForm.TabConsole; }
608         }
609
610         public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
611         {
612             Logger.Log("Unhandled Thread Exception: "
613                 + e.Exception.Message + Environment.NewLine
614                 + e.Exception.StackTrace + Environment.NewLine,
615                 Helpers.LogLevel.Error,
616                 client);
617 #if DEBUG
618             Application.Exit();
619 #endif
620         }
621     }
622
623     #region Event classes
624     public class ClientChangedEventArgs : EventArgs
625     {
626         private GridClient m_OldClient;
627         private GridClient m_Client;
628
629         public GridClient OldClient { get { return m_OldClient; } }
630         public GridClient Client { get { return m_Client; } }
631
632         public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
633         {
634             m_OldClient = OldClient;
635             m_Client = Client;
636         }
637     }
638     #endregion Event classes
639 }