OSDN Git Service

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