OSDN Git Service

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