OSDN Git Service

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