OSDN Git Service

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