OSDN Git Service

Disable handling of thread exceptions for now unless HANDLE_THREAD_EXCEPTIONS is...
[radegast/radegast.git] / Radegast / Core / RadegastInstance.cs
1 // \r
2 // Radegast Metaverse Client\r
3 // Copyright (c) 2009, Radegast Development Team\r
4 // All rights reserved.\r
5 // \r
6 // Redistribution and use in source and binary forms, with or without\r
7 // modification, are permitted provided that the following conditions are met:\r
8 // \r
9 //     * Redistributions of source code must retain the above copyright notice,\r
10 //       this list of conditions and the following disclaimer.\r
11 //     * Redistributions in binary form must reproduce the above copyright\r
12 //       notice, this list of conditions and the following disclaimer in the\r
13 //       documentation and/or other materials provided with the distribution.\r
14 //     * Neither the name of the application "Radegast", nor the names of its\r
15 //       contributors may be used to endorse or promote products derived from\r
16 //       this software without specific prior written permission.\r
17 // \r
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\r
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\r
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\r
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\r
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
28 //\r
29 // $Id$\r
30 //\r
31 using System;\r
32 using System.Collections.Generic;\r
33 using System.IO;\r
34 using System.Reflection;\r
35 using System.Threading;\r
36 using System.Windows.Forms;\r
37 using Radegast.Commands;\r
38 using Radegast.Netcom;\r
39 using Radegast.Media;\r
40 using OpenMetaverse;\r
41 \r
42 namespace Radegast\r
43 {\r
44     public class RadegastInstance\r
45     {\r
46         private GridClient client;\r
47         private RadegastNetcom netcom;\r
48 \r
49         private StateManager state;\r
50 \r
51         private frmMain mainForm;\r
52 \r
53         // Singleton, there can be only one instance\r
54         private static RadegastInstance globalInstance = null;\r
55         public static RadegastInstance GlobalInstance\r
56         {\r
57             get\r
58             {\r
59                 if (globalInstance == null)\r
60                 {\r
61                     globalInstance = new RadegastInstance(new GridClient());\r
62                 }\r
63                 return globalInstance;\r
64             }\r
65         }\r
66 \r
67         private string userDir;\r
68         /// <summary>\r
69         /// System (not grid!) user's dir\r
70         /// </summary>\r
71         public string UserDir { get { return userDir; } }\r
72 \r
73         /// <summary>\r
74         /// Grid client's user dir for settings and logs\r
75         /// </summary>\r
76         public string ClientDir\r
77         {\r
78             get\r
79             {\r
80                 if (client != null && client.Self != null && !string.IsNullOrEmpty(client.Self.Name))\r
81                 {\r
82                     return Path.Combine(userDir, client.Self.Name);\r
83                 }\r
84                 else\r
85                 {\r
86                     return Environment.CurrentDirectory;\r
87                 }\r
88             }\r
89         }\r
90 \r
91         public string InventoryCacheFileName { get { return Path.Combine(ClientDir, "inventory.cache"); } }\r
92 \r
93         private string animCacheDir;\r
94         public string AnimCacheDir { get { return animCacheDir; } }\r
95 \r
96         private string globalLogFile;\r
97         public string GlobalLogFile { get { return globalLogFile; } }\r
98 \r
99         private bool monoRuntime;\r
100         public bool MonoRuntime { get { return monoRuntime; } }\r
101 \r
102         private Dictionary<UUID, Group> groups;\r
103         public Dictionary<UUID, Group> Groups { get { return groups; } }\r
104 \r
105         private Settings globalSettings;\r
106         /// <summary>\r
107         /// Global settings for the entire application\r
108         /// </summary>\r
109         public Settings GlobalSettings { get { return globalSettings; } }\r
110 \r
111         private Settings clientSettings;\r
112         /// <summary>\r
113         /// Per client settings\r
114         /// </summary>\r
115         public Settings ClientSettings { get { return clientSettings; } }\r
116 \r
117         public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();\r
118 \r
119         public const string INCOMPLETE_NAME = "Loading...";\r
120 \r
121         public readonly bool advancedDebugging = false;\r
122 \r
123         public readonly List<IRadegastPlugin> PluginsLoaded = new List<IRadegastPlugin>();\r
124 \r
125         private MediaManager mediaManager;\r
126         /// <summary>\r
127         /// Radegast media manager for playing streams and in world sounds\r
128         /// </summary>\r
129         public MediaManager MediaManager { get { return mediaManager; } }\r
130 \r
131 \r
132         private CommandsManager commandsManager;\r
133         /// <summary>\r
134         /// Radegast command manager for executing textual console commands\r
135         /// </summary>\r
136         public CommandsManager CommandsManager { get { return commandsManager; } }\r
137 \r
138         /// <summary>\r
139         /// Radegast ContextAction manager for context sensitive actions\r
140         /// </summary>\r
141         public ContextActionsManager ContextActionManager { get; private set; }\r
142 \r
143         private RadegastMovement movement;\r
144         /// <summary>\r
145         /// Allows key emulation for moving avatar around\r
146         /// </summary>\r
147         public RadegastMovement Movement { get { return movement; } }\r
148 \r
149         public RadegastInstance(GridClient client0)\r
150         {\r
151             // incase something else calls GlobalInstance while we are loading\r
152             globalInstance = this;\r
153 \r
154 #if HANDLE_THREAD_EXCEPTIONS\r
155             Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);\r
156             Application.ThreadException += HandleThreadException;\r
157 #endif\r
158 \r
159             client = client0;\r
160 \r
161             // Are we running mono?\r
162             monoRuntime = Type.GetType("Mono.Runtime") != null;\r
163 \r
164             netcom = new RadegastNetcom(client);\r
165             state = new StateManager(this);\r
166             mediaManager = new MediaManager(this);\r
167             commandsManager = new CommandsManager(this);\r
168             ContextActionManager = new ContextActionsManager(this);\r
169             movement = new RadegastMovement(client);\r
170 \r
171             InitializeLoggingAndConfig();\r
172 \r
173             client.Settings.MULTIPLE_SIMS = true;\r
174 \r
175             client.Settings.USE_INTERPOLATION_TIMER = false;\r
176             client.Settings.ALWAYS_REQUEST_OBJECTS = true;\r
177             client.Settings.ALWAYS_DECODE_OBJECTS = true;\r
178             client.Settings.OBJECT_TRACKING = true;\r
179             client.Settings.ENABLE_SIMSTATS = true;\r
180             client.Settings.FETCH_MISSING_INVENTORY = true;\r
181             client.Settings.SEND_AGENT_THROTTLE = true;\r
182             client.Settings.SEND_AGENT_UPDATES = true;\r
183 \r
184             client.Settings.USE_ASSET_CACHE = true;\r
185             client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");\r
186             client.Assets.Cache.AutoPruneEnabled = false;\r
187 \r
188             client.Throttle.Total = 5000000f;\r
189             client.Settings.THROTTLE_OUTGOING_PACKETS = true;\r
190             client.Settings.LOGIN_TIMEOUT = 120 * 1000;\r
191             client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;\r
192             client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;\r
193 \r
194             mainForm = new frmMain(this);\r
195             mainForm.InitializeControls();\r
196 \r
197             groups = new Dictionary<UUID, Group>();\r
198 \r
199             client.Groups.OnCurrentGroups += new GroupManager.CurrentGroupsCallback(Groups_OnCurrentGroups);\r
200             client.Groups.OnGroupLeft += new GroupManager.GroupLeftCallback(Groups_OnGroupLeft);\r
201             client.Groups.OnGroupDropped += new GroupManager.GroupDroppedCallback(Groups_OnGroupDropped);\r
202             client.Groups.OnGroupJoined += new GroupManager.GroupJoinedCallback(Groups_OnGroupJoined);\r
203             client.Avatars.OnAvatarNames += new AvatarManager.AvatarNamesCallback(Avatars_OnAvatarNames);\r
204             client.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnected);\r
205             mainForm.Load += new EventHandler(mainForm_Load);\r
206             ScanAndLoadPlugins();\r
207         }\r
208 \r
209         public void CleanUp()\r
210         {\r
211             if (client != null)\r
212             {\r
213                 client.Groups.OnCurrentGroups -= new GroupManager.CurrentGroupsCallback(Groups_OnCurrentGroups);\r
214                 client.Groups.OnGroupLeft -= new GroupManager.GroupLeftCallback(Groups_OnGroupLeft);\r
215                 client.Groups.OnGroupDropped -= new GroupManager.GroupDroppedCallback(Groups_OnGroupDropped);\r
216                 client.Groups.OnGroupJoined -= new GroupManager.GroupJoinedCallback(Groups_OnGroupJoined);\r
217                 client.Avatars.OnAvatarNames -= new AvatarManager.AvatarNamesCallback(Avatars_OnAvatarNames);\r
218                 client.Network.OnConnected -= new NetworkManager.ConnectedCallback(Network_OnConnected);\r
219             }\r
220 \r
221             lock (PluginsLoaded)\r
222             {\r
223                 List<IRadegastPlugin> unload = new List<IRadegastPlugin>(PluginsLoaded);\r
224                 unload.ForEach(plug =>\r
225                {\r
226                    PluginsLoaded.Remove(plug);\r
227                    try\r
228                    {\r
229                        plug.StopPlugin(this);\r
230                    }\r
231                    catch (Exception ex)\r
232                    {\r
233                        Logger.Log("ERROR in Shutdown Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug, ex);\r
234                    }\r
235                });\r
236             }\r
237 \r
238             if (movement != null)\r
239             {\r
240                 movement.Dispose();\r
241                 movement = null;\r
242             }\r
243             if (commandsManager != null)\r
244             {\r
245                 commandsManager.Dispose();\r
246                 commandsManager = null;\r
247             }\r
248             if (ContextActionManager != null)\r
249             {\r
250                 ContextActionManager.Dispose();\r
251                 ContextActionManager = null;\r
252             }\r
253             if (mediaManager != null)\r
254             {\r
255                 mediaManager.Dispose();\r
256                 mediaManager = null;\r
257             }\r
258             if (state != null)\r
259             {\r
260                 state.Dispose();\r
261                 state = null;\r
262             }\r
263             if (netcom != null)\r
264             {\r
265                 netcom.Dispose();\r
266                 netcom = null;\r
267             }\r
268             if (mainForm != null)\r
269             {\r
270                 mainForm.Load -= new EventHandler(mainForm_Load);\r
271             }\r
272             Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);\r
273         }\r
274 \r
275         void mainForm_Load(object sender, EventArgs e)\r
276         {\r
277             StartPlugins();\r
278         }\r
279 \r
280         private void StartPlugins()\r
281         {\r
282             lock (PluginsLoaded)\r
283             {\r
284                 foreach (IRadegastPlugin plug in PluginsLoaded)\r
285                 {\r
286                     try\r
287                     {\r
288                         plug.StartPlugin(this);\r
289                     }\r
290                     catch (Exception ex)\r
291                     {\r
292                         Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);\r
293                     }\r
294                 }\r
295             }\r
296         }\r
297 \r
298         private void ScanAndLoadPlugins()\r
299         {\r
300             string dirName = Application.StartupPath;\r
301 \r
302             if (!Directory.Exists(dirName)) return;\r
303 \r
304             foreach (string loadfilename in Directory.GetFiles(dirName))\r
305             {\r
306                 if (loadfilename.ToLower().EndsWith(".dll") || loadfilename.ToLower().EndsWith(".exe"))\r
307                 {\r
308                     try\r
309                     {\r
310                         Assembly assembly = Assembly.LoadFile(loadfilename);\r
311                         LoadAssembly(loadfilename, assembly);\r
312                     }\r
313                     catch (BadImageFormatException)\r
314                     {\r
315                         // non .NET .dlls\r
316                     }\r
317                     catch (ReflectionTypeLoadException)\r
318                     {\r
319                         // Out of date or dlls missing sub dependencies\r
320                     }\r
321                     catch (TypeLoadException)\r
322                     {\r
323                         // Another version of: Out of date or dlls missing sub dependencies\r
324                     }\r
325                     catch (Exception ex)\r
326                     {\r
327                         Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " because " + ex, Helpers.LogLevel.Debug);\r
328                     }\r
329                 }\r
330             }\r
331         }\r
332 \r
333         public void LoadAssembly(string loadfilename, Assembly assembly)\r
334         {\r
335             foreach (Type type in assembly.GetTypes())\r
336             {\r
337                 if (typeof(IRadegastPlugin).IsAssignableFrom(type))\r
338                 {\r
339                     if  (type.IsInterface) continue;\r
340                     try\r
341                     {\r
342                         IRadegastPlugin plug;\r
343                         ConstructorInfo constructorInfo = type.GetConstructor(new Type[] {typeof (RadegastInstance)});\r
344                         if (constructorInfo != null)\r
345                             plug = (IRadegastPlugin) constructorInfo.Invoke(new[] {this});\r
346                         else\r
347                         {\r
348                             constructorInfo = type.GetConstructor(new Type[] {});\r
349                             if (constructorInfo != null)\r
350                                 plug = (IRadegastPlugin) constructorInfo.Invoke(new object[0]);\r
351                             else\r
352                             {\r
353                                 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because "+type+ " has no usable constructor.",Helpers.LogLevel.Debug);\r
354                                 continue;\r
355                             }\r
356                         }\r
357                         lock (PluginsLoaded) PluginsLoaded.Add(plug);\r
358                     }\r
359                     catch (Exception ex)\r
360                     {\r
361                         Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex,\r
362                                    Helpers.LogLevel.Debug);\r
363                     }\r
364                 }\r
365                 else\r
366                 {\r
367                     try\r
368                     {\r
369                         commandsManager.LoadType(type);\r
370                         ContextActionManager.LoadType(type);\r
371                     }\r
372                     catch (Exception ex)\r
373                     {\r
374                         Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type +\r
375                                    " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug);\r
376                     }\r
377                 }\r
378             }\r
379         }\r
380 \r
381         void Network_OnConnected(object sender)\r
382         {\r
383             try\r
384             {\r
385                 if (!Directory.Exists(ClientDir))\r
386                     Directory.CreateDirectory(ClientDir);\r
387             }\r
388             catch (Exception ex)\r
389             {\r
390                 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);\r
391             }\r
392 \r
393             clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));\r
394         }\r
395 \r
396         void Avatars_OnAvatarNames(Dictionary<UUID, string> names)\r
397         {\r
398             lock (nameCache)\r
399             {\r
400                 foreach (KeyValuePair<UUID, string> av in names)\r
401                 {\r
402                     if (!nameCache.ContainsKey(av.Key))\r
403                     {\r
404                         nameCache.Add(av.Key, av.Value);\r
405                     }\r
406                 }\r
407             }\r
408         }\r
409 \r
410         public string getAvatarName(UUID key)\r
411         {\r
412             lock (nameCache)\r
413             {\r
414                 if (key == UUID.Zero)\r
415                 {\r
416                     return "(???) (???)";\r
417                 }\r
418                 if (nameCache.ContainsKey(key))\r
419                 {\r
420                     return nameCache[key];\r
421                 }\r
422                 else\r
423                 {\r
424                     client.Avatars.RequestAvatarName(key);\r
425                     return INCOMPLETE_NAME;\r
426                 }\r
427             }\r
428         }\r
429 \r
430         public void getAvatarNames(List<UUID> keys)\r
431         {\r
432             lock (nameCache)\r
433             {\r
434                 List<UUID> newNames = new List<UUID>();\r
435                 foreach (UUID key in keys)\r
436                 {\r
437                     if (!nameCache.ContainsKey(key))\r
438                     {\r
439                         newNames.Add(key);\r
440                     }\r
441                 }\r
442                 if (newNames.Count > 0)\r
443                 {\r
444                     client.Avatars.RequestAvatarNames(newNames);\r
445                 }\r
446             }\r
447         }\r
448 \r
449         public bool haveAvatarName(UUID key)\r
450         {\r
451             lock (nameCache)\r
452             {\r
453                 if (nameCache.ContainsKey(key))\r
454                     return true;\r
455                 else\r
456                     return false;\r
457             }\r
458         }\r
459 \r
460         void Groups_OnGroupJoined(UUID groupID, bool success)\r
461         {\r
462             client.Groups.RequestCurrentGroups();\r
463         }\r
464 \r
465         void Groups_OnGroupLeft(UUID groupID, bool success)\r
466         {\r
467             client.Groups.RequestCurrentGroups();\r
468         }\r
469 \r
470         void Groups_OnGroupDropped(UUID groupID)\r
471         {\r
472             client.Groups.RequestCurrentGroups();\r
473         }\r
474 \r
475         public static string SafeFileName(string fileName)\r
476         {\r
477             foreach (char lDisallowed in Path.GetInvalidFileNameChars())\r
478             {\r
479                 fileName = fileName.Replace(lDisallowed.ToString(), "_");\r
480             }\r
481 \r
482             return fileName;\r
483         }\r
484 \r
485         public void LogClientMessage(string fileName, string message)\r
486         {\r
487             lock (this)\r
488             {\r
489                 try\r
490                 {\r
491                     foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())\r
492                     {\r
493                         fileName = fileName.Replace(lDisallowed.ToString(), "_");\r
494                     }\r
495 \r
496                     File.AppendAllText(Path.Combine(ClientDir, fileName),\r
497                         DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);\r
498                 }\r
499                 catch (Exception) { }\r
500             }\r
501         }\r
502 \r
503         void Groups_OnCurrentGroups(Dictionary<UUID, Group> gr)\r
504         {\r
505             this.groups = gr;\r
506         }\r
507 \r
508         private void InitializeLoggingAndConfig()\r
509         {\r
510             try\r
511             {\r
512                 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);\r
513                 if (!Directory.Exists(userDir))\r
514                 {\r
515                     Directory.CreateDirectory(userDir);\r
516                 }\r
517             }\r
518             catch (Exception)\r
519             {\r
520                 userDir = System.Environment.CurrentDirectory;\r
521             };\r
522 \r
523             animCacheDir = Path.Combine(userDir, @"anim_cache");\r
524             globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");\r
525             globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));\r
526         }\r
527 \r
528         public GridClient Client\r
529         {\r
530             get { return client; }\r
531         }\r
532 \r
533         public RadegastNetcom Netcom\r
534         {\r
535             get { return netcom; }\r
536         }\r
537 \r
538         public StateManager State\r
539         {\r
540             get { return state; }\r
541         }\r
542 \r
543         public frmMain MainForm\r
544         {\r
545             get { return mainForm; }\r
546         }\r
547 \r
548         public TabsConsole TabConsole\r
549         {\r
550             get { return mainForm.TabConsole; }\r
551         }\r
552 \r
553         public void HandleThreadException(object sender, ThreadExceptionEventArgs e)\r
554         {\r
555             Logger.Log("Unhandled Thread Exception: " \r
556                 + e.Exception.Message + Environment.NewLine\r
557                 + e.Exception.StackTrace + Environment.NewLine,\r
558                 Helpers.LogLevel.Error,\r
559                 client);\r
560 \r
561             Application.Exit();\r
562         }\r
563     }\r
564 }\r