OSDN Git Service

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