OSDN Git Service

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