OSDN Git Service

Shutdown sequence cleanup.
[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 globalLogFile;\r
94         public string GlobalLogFile { get { return globalLogFile; } }\r
95 \r
96         private bool monoRuntime;\r
97         public bool MonoRuntime { get { return monoRuntime; } }\r
98 \r
99         private Dictionary<UUID, Group> groups = new Dictionary<UUID, Group>();\r
100         public Dictionary<UUID, Group> Groups { get { return groups; } }\r
101 \r
102         private Settings globalSettings;\r
103         /// <summary>\r
104         /// Global settings for the entire application\r
105         /// </summary>\r
106         public Settings GlobalSettings { get { return globalSettings; } }\r
107 \r
108         private Settings clientSettings;\r
109         /// <summary>\r
110         /// Per client settings\r
111         /// </summary>\r
112         public Settings ClientSettings { get { return clientSettings; } }\r
113 \r
114         public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();\r
115 \r
116         public const string INCOMPLETE_NAME = "Loading...";\r
117 \r
118         public readonly bool advancedDebugging = false;\r
119 \r
120         public readonly List<IRadegastPlugin> PluginsLoaded = new List<IRadegastPlugin>();\r
121 \r
122         private MediaManager mediaManager;\r
123         /// <summary>\r
124         /// Radegast media manager for playing streams and in world sounds\r
125         /// </summary>\r
126         public MediaManager MediaManager { get { return mediaManager; } }\r
127 \r
128 \r
129         private CommandsManager commandsManager;\r
130         /// <summary>\r
131         /// Radegast command manager for executing textual console commands\r
132         /// </summary>\r
133         public CommandsManager CommandsManager { get { return commandsManager; } }\r
134 \r
135         /// <summary>\r
136         /// Radegast ContextAction manager for context sensitive actions\r
137         /// </summary>\r
138         public ContextActionsManager ContextActionManager { get; private set; }\r
139 \r
140         private RadegastMovement movement;\r
141         /// <summary>\r
142         /// Allows key emulation for moving avatar around\r
143         /// </summary>\r
144         public RadegastMovement Movement { get { return movement; } }\r
145 \r
146         #region Events\r
147         /// <summary>The event subscribers, null of no subscribers</summary>\r
148         private EventHandler<ClientChangedEventArgs> m_ClientChanged;\r
149 \r
150         ///<summary>Raises the ClientChanged Event</summary>\r
151         /// <param name="e">A ClientChangedEventArgs object containing\r
152         /// the old and the new client</param>\r
153         protected virtual void OnClientChanged(ClientChangedEventArgs e)\r
154         {\r
155             EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;\r
156             if (handler != null)\r
157                 handler(this, e);\r
158         }\r
159 \r
160         /// <summary>Thread sync lock object</summary>\r
161         private readonly object m_ClientChangedLock = new object();\r
162 \r
163         /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>\r
164         public event EventHandler<ClientChangedEventArgs> ClientChanged\r
165         {\r
166             add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }\r
167             remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }\r
168         }\r
169         #endregion Events\r
170 \r
171         public RadegastInstance(GridClient client0)\r
172         {\r
173             // incase something else calls GlobalInstance while we are loading\r
174             globalInstance = this;\r
175 \r
176 #if HANDLE_THREAD_EXCEPTIONS\r
177             Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);\r
178             Application.ThreadException += HandleThreadException;\r
179 #endif\r
180 \r
181             client = client0;\r
182 \r
183             // Are we running mono?\r
184             monoRuntime = Type.GetType("Mono.Runtime") != null;\r
185 \r
186             netcom = new RadegastNetcom(this);\r
187             state = new StateManager(this);\r
188             mediaManager = new MediaManager(this);\r
189             commandsManager = new CommandsManager(this);\r
190             ContextActionManager = new ContextActionsManager(this);\r
191             movement = new RadegastMovement(this);\r
192 \r
193             InitializeLoggingAndConfig();\r
194             InitializeClient(client);\r
195 \r
196             mainForm = new frmMain(this);\r
197             mainForm.InitializeControls();\r
198 \r
199             mainForm.Load += new EventHandler(mainForm_Load);\r
200             ScanAndLoadPlugins();\r
201         }\r
202 \r
203         private void InitializeClient(GridClient client)\r
204         {\r
205             client.Settings.MULTIPLE_SIMS = true;\r
206 \r
207             client.Settings.USE_INTERPOLATION_TIMER = false;\r
208             client.Settings.ALWAYS_REQUEST_OBJECTS = true;\r
209             client.Settings.ALWAYS_DECODE_OBJECTS = true;\r
210             client.Settings.OBJECT_TRACKING = true;\r
211             client.Settings.ENABLE_SIMSTATS = true;\r
212             client.Settings.FETCH_MISSING_INVENTORY = true;\r
213             client.Settings.SEND_AGENT_THROTTLE = true;\r
214             client.Settings.SEND_AGENT_UPDATES = true;\r
215 \r
216             client.Settings.USE_ASSET_CACHE = true;\r
217             client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");\r
218             client.Assets.Cache.AutoPruneEnabled = false;\r
219 \r
220             client.Throttle.Total = 5000000f;\r
221             client.Settings.THROTTLE_OUTGOING_PACKETS = true;\r
222             client.Settings.LOGIN_TIMEOUT = 120 * 1000;\r
223             client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;\r
224             client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;\r
225 \r
226             RegisterClientEvents(client);\r
227         }\r
228 \r
229         private void RegisterClientEvents(GridClient client)\r
230         {\r
231             client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);\r
232             client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);\r
233             client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);\r
234             client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);\r
235             client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);\r
236             netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);\r
237        }\r
238 \r
239         private void UnregisterClientEvents(GridClient client)\r
240         {\r
241             client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);\r
242             client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);\r
243             client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);\r
244             client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);\r
245             client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);\r
246             netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);\r
247         }\r
248 \r
249         public void Reconnect()\r
250         {\r
251             TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);\r
252             Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);\r
253             GridClient oldClient = client;\r
254             client = new GridClient();\r
255             UnregisterClientEvents(oldClient);\r
256             InitializeClient(client);\r
257             OnClientChanged(new ClientChangedEventArgs(oldClient, client));\r
258             netcom.Login();\r
259         }\r
260 \r
261         public void CleanUp()\r
262         {\r
263             if (client != null)\r
264             {\r
265                 UnregisterClientEvents(client);\r
266             }\r
267 \r
268             lock (PluginsLoaded)\r
269             {\r
270                 List<IRadegastPlugin> unload = new List<IRadegastPlugin>(PluginsLoaded);\r
271                 unload.ForEach(plug =>\r
272                {\r
273                    PluginsLoaded.Remove(plug);\r
274                    try\r
275                    {\r
276                        plug.StopPlugin(this);\r
277                    }\r
278                    catch (Exception ex)\r
279                    {\r
280                        Logger.Log("ERROR in Shutdown Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug, ex);\r
281                    }\r
282                });\r
283             }\r
284 \r
285             if (movement != null)\r
286             {\r
287                 movement.Dispose();\r
288                 movement = null;\r
289             }\r
290             if (commandsManager != null)\r
291             {\r
292                 commandsManager.Dispose();\r
293                 commandsManager = null;\r
294             }\r
295             if (ContextActionManager != null)\r
296             {\r
297                 ContextActionManager.Dispose();\r
298                 ContextActionManager = null;\r
299             }\r
300             if (mediaManager != null)\r
301             {\r
302                 mediaManager.Dispose();\r
303                 mediaManager = null;\r
304             }\r
305             if (state != null)\r
306             {\r
307                 state.Dispose();\r
308                 state = null;\r
309             }\r
310             if (netcom != null)\r
311             {\r
312                 netcom.Dispose();\r
313                 netcom = null;\r
314             }\r
315             if (mainForm != null)\r
316             {\r
317                 mainForm.Load -= new EventHandler(mainForm_Load);\r
318             }\r
319             Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);\r
320         }\r
321 \r
322         void mainForm_Load(object sender, EventArgs e)\r
323         {\r
324             StartPlugins();\r
325         }\r
326 \r
327         private void StartPlugins()\r
328         {\r
329             lock (PluginsLoaded)\r
330             {\r
331                 foreach (IRadegastPlugin plug in PluginsLoaded)\r
332                 {\r
333                     try\r
334                     {\r
335                         plug.StartPlugin(this);\r
336                     }\r
337                     catch (Exception ex)\r
338                     {\r
339                         Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);\r
340                     }\r
341                 }\r
342             }\r
343         }\r
344 \r
345         private void ScanAndLoadPlugins()\r
346         {\r
347             string dirName = Application.StartupPath;\r
348 \r
349             if (!Directory.Exists(dirName)) return;\r
350 \r
351             foreach (string loadfilename in Directory.GetFiles(dirName))\r
352             {\r
353                 if (loadfilename.ToLower().EndsWith(".dll") || loadfilename.ToLower().EndsWith(".exe"))\r
354                 {\r
355                     try\r
356                     {\r
357                         Assembly assembly = Assembly.LoadFile(loadfilename);\r
358                         LoadAssembly(loadfilename, assembly);\r
359                     }\r
360                     catch (BadImageFormatException)\r
361                     {\r
362                         // non .NET .dlls\r
363                     }\r
364                     catch (ReflectionTypeLoadException)\r
365                     {\r
366                         // Out of date or dlls missing sub dependencies\r
367                     }\r
368                     catch (TypeLoadException)\r
369                     {\r
370                         // Another version of: Out of date or dlls missing sub dependencies\r
371                     }\r
372                     catch (Exception ex)\r
373                     {\r
374                         Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " because " + ex, Helpers.LogLevel.Debug);\r
375                     }\r
376                 }\r
377             }\r
378         }\r
379 \r
380         public void LoadAssembly(string loadfilename, Assembly assembly)\r
381         {\r
382             foreach (Type type in assembly.GetTypes())\r
383             {\r
384                 if (typeof(IRadegastPlugin).IsAssignableFrom(type))\r
385                 {\r
386                     if  (type.IsInterface) continue;\r
387                     try\r
388                     {\r
389                         IRadegastPlugin plug;\r
390                         ConstructorInfo constructorInfo = type.GetConstructor(new Type[] {typeof (RadegastInstance)});\r
391                         if (constructorInfo != null)\r
392                             plug = (IRadegastPlugin) constructorInfo.Invoke(new[] {this});\r
393                         else\r
394                         {\r
395                             constructorInfo = type.GetConstructor(new Type[] {});\r
396                             if (constructorInfo != null)\r
397                                 plug = (IRadegastPlugin) constructorInfo.Invoke(new object[0]);\r
398                             else\r
399                             {\r
400                                 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because "+type+ " has no usable constructor.",Helpers.LogLevel.Debug);\r
401                                 continue;\r
402                             }\r
403                         }\r
404                         lock (PluginsLoaded) PluginsLoaded.Add(plug);\r
405                     }\r
406                     catch (Exception ex)\r
407                     {\r
408                         Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex,\r
409                                    Helpers.LogLevel.Debug);\r
410                     }\r
411                 }\r
412                 else\r
413                 {\r
414                     try\r
415                     {\r
416                         commandsManager.LoadType(type);\r
417                     }\r
418                     catch (Exception ex)\r
419                     {\r
420                         Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type +\r
421                                    " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug);\r
422                     }\r
423                 }\r
424             }\r
425         }\r
426 \r
427         void netcom_ClientConnected(object sender, EventArgs e)\r
428         {\r
429             try\r
430             {\r
431                 if (!Directory.Exists(ClientDir))\r
432                     Directory.CreateDirectory(ClientDir);\r
433             }\r
434             catch (Exception ex)\r
435             {\r
436                 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);\r
437             }\r
438 \r
439             clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));\r
440         }\r
441 \r
442 \r
443         void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)\r
444         {\r
445             lock (nameCache)\r
446             {\r
447                 foreach (KeyValuePair<UUID, string> av in e.Names)\r
448                 {\r
449                     if (!nameCache.ContainsKey(av.Key))\r
450                     {\r
451                         nameCache.Add(av.Key, av.Value);\r
452                     }\r
453                 }\r
454             }\r
455         }\r
456 \r
457         public string getAvatarName(UUID key)\r
458         {\r
459             lock (nameCache)\r
460             {\r
461                 if (key == UUID.Zero)\r
462                 {\r
463                     return "(???) (???)";\r
464                 }\r
465                 if (nameCache.ContainsKey(key))\r
466                 {\r
467                     return nameCache[key];\r
468                 }\r
469                 else\r
470                 {\r
471                     client.Avatars.RequestAvatarName(key);\r
472                     return INCOMPLETE_NAME;\r
473                 }\r
474             }\r
475         }\r
476 \r
477         public void getAvatarNames(List<UUID> keys)\r
478         {\r
479             lock (nameCache)\r
480             {\r
481                 List<UUID> newNames = new List<UUID>();\r
482                 foreach (UUID key in keys)\r
483                 {\r
484                     if (!nameCache.ContainsKey(key))\r
485                     {\r
486                         newNames.Add(key);\r
487                     }\r
488                 }\r
489                 if (newNames.Count > 0)\r
490                 {\r
491                     client.Avatars.RequestAvatarNames(newNames);\r
492                 }\r
493             }\r
494         }\r
495 \r
496         public bool haveAvatarName(UUID key)\r
497         {\r
498             lock (nameCache)\r
499             {\r
500                 if (nameCache.ContainsKey(key))\r
501                     return true;\r
502                 else\r
503                     return false;\r
504             }\r
505         }\r
506 \r
507         void Groups_GroupsChanged(object sender, EventArgs e)\r
508         {\r
509             client.Groups.RequestCurrentGroups();\r
510         }\r
511 \r
512         public static string SafeFileName(string fileName)\r
513         {\r
514             foreach (char lDisallowed in Path.GetInvalidFileNameChars())\r
515             {\r
516                 fileName = fileName.Replace(lDisallowed.ToString(), "_");\r
517             }\r
518 \r
519             return fileName;\r
520         }\r
521 \r
522         public void LogClientMessage(string fileName, string message)\r
523         {\r
524             lock (this)\r
525             {\r
526                 try\r
527                 {\r
528                     foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())\r
529                     {\r
530                         fileName = fileName.Replace(lDisallowed.ToString(), "_");\r
531                     }\r
532 \r
533                     File.AppendAllText(Path.Combine(ClientDir, fileName),\r
534                         DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);\r
535                 }\r
536                 catch (Exception) { }\r
537             }\r
538         }\r
539 \r
540         void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)\r
541         {\r
542             this.groups = e.Groups;\r
543         }\r
544 \r
545         private void InitializeLoggingAndConfig()\r
546         {\r
547             try\r
548             {\r
549                 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);\r
550                 if (!Directory.Exists(userDir))\r
551                 {\r
552                     Directory.CreateDirectory(userDir);\r
553                 }\r
554             }\r
555             catch (Exception)\r
556             {\r
557                 userDir = System.Environment.CurrentDirectory;\r
558             };\r
559 \r
560             globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");\r
561             globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));\r
562         }\r
563 \r
564         public GridClient Client\r
565         {\r
566             get { return client; }\r
567         }\r
568 \r
569         public RadegastNetcom Netcom\r
570         {\r
571             get { return netcom; }\r
572         }\r
573 \r
574         public StateManager State\r
575         {\r
576             get { return state; }\r
577         }\r
578 \r
579         public frmMain MainForm\r
580         {\r
581             get { return mainForm; }\r
582         }\r
583 \r
584         public TabsConsole TabConsole\r
585         {\r
586             get { return mainForm.TabConsole; }\r
587         }\r
588 \r
589         public void HandleThreadException(object sender, ThreadExceptionEventArgs e)\r
590         {\r
591             Logger.Log("Unhandled Thread Exception: " \r
592                 + e.Exception.Message + Environment.NewLine\r
593                 + e.Exception.StackTrace + Environment.NewLine,\r
594                 Helpers.LogLevel.Error,\r
595                 client);\r
596 \r
597             Application.Exit();\r
598         }\r
599     }\r
600 \r
601     #region Event classes\r
602     public class ClientChangedEventArgs : EventArgs\r
603     {\r
604         private GridClient m_OldClient;\r
605         private GridClient m_Client;\r
606 \r
607         public GridClient OldClient { get { return m_OldClient; } }\r
608         public GridClient Client { get { return m_Client; } }\r
609 \r
610         public ClientChangedEventArgs(GridClient OldClient, GridClient Client)\r
611         {\r
612             m_OldClient = OldClient;\r
613             m_Client = Client;\r
614         }\r
615     }\r
616     #endregion Event classes\r
617 }\r