OSDN Git Service

Added ability to run C# scripts, load command definitions, and command interpreters...
[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             if (netcom != null)
305                 netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
306         }
307
308         private void UnregisterClientEvents(GridClient client)
309         {
310             client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
311             client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
312             client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
313             client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
314             client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
315             if (netcom != null)
316                 netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
317         }
318
319         public void Reconnect()
320         {
321             TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);
322             Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);
323             GridClient oldClient = client;
324             client = new GridClient();
325             UnregisterClientEvents(oldClient);
326             InitializeClient(client);
327             OnClientChanged(new ClientChangedEventArgs(oldClient, client));
328             netcom.Login();
329         }
330
331         public void CleanUp()
332         {
333             if (rlv != null)
334             {
335                 rlv.Dispose();
336                 rlv = null;
337             }
338
339             if (client != null)
340             {
341                 UnregisterClientEvents(client);
342             }
343
344             if (pluginManager != null)
345             {
346                 pluginManager.Dispose();
347                 pluginManager = null;
348             }
349
350             if (movement != null)
351             {
352                 movement.Dispose();
353                 movement = null;
354             }
355             if (commandsManager != null)
356             {
357                 commandsManager.Dispose();
358                 commandsManager = null;
359             }
360             if (ContextActionManager != null)
361             {
362                 ContextActionManager.Dispose();
363                 ContextActionManager = null;
364             }
365             if (mediaManager != null)
366             {
367                 mediaManager.Dispose();
368                 mediaManager = null;
369             }
370             if (state != null)
371             {
372                 state.Dispose();
373                 state = null;
374             }
375             if (netcom != null)
376             {
377                 netcom.Dispose();
378                 netcom = null;
379             }
380             if (mainForm != null)
381             {
382                 mainForm.Load -= new EventHandler(mainForm_Load);
383             }
384             Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
385         }
386
387         void mainForm_Load(object sender, EventArgs e)
388         {
389             pluginManager.StartPlugins();
390         }
391
392         void netcom_ClientConnected(object sender, EventArgs e)
393         {
394             try
395             {
396                 if (!Directory.Exists(ClientDir))
397                     Directory.CreateDirectory(ClientDir);
398             }
399             catch (Exception ex)
400             {
401                 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
402             }
403
404             clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
405         }
406
407
408         void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
409         {
410             lock (nameCache)
411             {
412                 foreach (KeyValuePair<UUID, string> av in e.Names)
413                 {
414                     if (!nameCache.ContainsKey(av.Key))
415                     {
416                         nameCache.Add(av.Key, av.Value);
417                     }
418                 }
419             }
420         }
421
422         public string getAvatarName(UUID key)
423         {
424             lock (nameCache)
425             {
426                 if (key == UUID.Zero)
427                 {
428                     return "(???) (???)";
429                 }
430                 if (nameCache.ContainsKey(key))
431                 {
432                     return nameCache[key];
433                 }
434                 else
435                 {
436                     client.Avatars.RequestAvatarName(key);
437                     return INCOMPLETE_NAME;
438                 }
439             }
440         }
441
442         public void getAvatarNames(List<UUID> keys)
443         {
444             lock (nameCache)
445             {
446                 List<UUID> newNames = new List<UUID>();
447                 foreach (UUID key in keys)
448                 {
449                     if (!nameCache.ContainsKey(key))
450                     {
451                         newNames.Add(key);
452                     }
453                 }
454                 if (newNames.Count > 0)
455                 {
456                     client.Avatars.RequestAvatarNames(newNames);
457                 }
458             }
459         }
460
461         public bool haveAvatarName(UUID key)
462         {
463             lock (nameCache)
464             {
465                 if (nameCache.ContainsKey(key))
466                     return true;
467                 else
468                     return false;
469             }
470         }
471
472         void Groups_GroupsChanged(object sender, EventArgs e)
473         {
474             client.Groups.RequestCurrentGroups();
475         }
476
477         public static string SafeFileName(string fileName)
478         {
479             foreach (char lDisallowed in Path.GetInvalidFileNameChars())
480             {
481                 fileName = fileName.Replace(lDisallowed.ToString(), "_");
482             }
483
484             return fileName;
485         }
486
487         public void LogClientMessage(string fileName, string message)
488         {
489             lock (this)
490             {
491                 try
492                 {
493                     foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
494                     {
495                         fileName = fileName.Replace(lDisallowed.ToString(), "_");
496                     }
497
498                     File.AppendAllText(Path.Combine(ClientDir, fileName),
499                         DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
500                 }
501                 catch (Exception) { }
502             }
503         }
504
505         void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
506         {
507             this.groups = e.Groups;
508         }
509
510         private void InitializeLoggingAndConfig()
511         {
512             try
513             {
514                 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
515                 if (!Directory.Exists(userDir))
516                 {
517                     Directory.CreateDirectory(userDir);
518                 }
519             }
520             catch (Exception)
521             {
522                 userDir = System.Environment.CurrentDirectory;
523             };
524
525             globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
526             globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
527         }
528
529         public GridClient Client
530         {
531             get { return client; }
532         }
533
534         public RadegastNetcom Netcom
535         {
536             get { return netcom; }
537         }
538
539         public StateManager State
540         {
541             get { return state; }
542         }
543
544         public frmMain MainForm
545         {
546             get { return mainForm; }
547         }
548
549         public TabsConsole TabConsole
550         {
551             get { return mainForm.TabConsole; }
552         }
553
554         public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
555         {
556             Logger.Log("Unhandled Thread Exception: "
557                 + e.Exception.Message + Environment.NewLine
558                 + e.Exception.StackTrace + Environment.NewLine,
559                 Helpers.LogLevel.Error,
560                 client);
561 #if DEBUG
562             Application.Exit();
563 #endif
564         }
565     }
566
567     #region Event classes
568     public class ClientChangedEventArgs : EventArgs
569     {
570         private GridClient m_OldClient;
571         private GridClient m_Client;
572
573         public GridClient OldClient { get { return m_OldClient; } }
574         public GridClient Client { get { return m_Client; } }
575
576         public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
577         {
578             m_OldClient = OldClient;
579             m_Client = Client;
580         }
581     }
582     #endregion Event classes
583 }