2 // Radegast Metaverse Client
3 // Copyright (c) 2009, Radegast Development Team
4 // All rights reserved.
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
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.
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.
32 using System.Collections.Generic;
34 using System.Reflection;
35 using System.Threading;
36 using System.Windows.Forms;
37 using Radegast.Commands;
38 using Radegast.Netcom;
44 public class RadegastInstance
46 #region OnRadegastFormCreated
47 public event Action<RadegastForm> RadegastFormCreated;
49 /// Triggers the RadegastFormCreated event.
51 public virtual void OnRadegastFormCreated(RadegastForm radForm)
53 if (RadegastFormCreated != null) RadegastFormCreated(radForm);
56 private GridClient client;
57 private RadegastNetcom netcom;
59 private StateManager state;
61 private frmMain mainForm;
63 // Singleton, there can be only one instance
64 private static RadegastInstance globalInstance = null;
65 public static RadegastInstance GlobalInstance
69 if (globalInstance == null)
71 globalInstance = new RadegastInstance(new GridClient());
73 return globalInstance;
77 private string userDir;
79 /// System (not grid!) user's dir
81 public string UserDir { get { return userDir; } }
84 /// Grid client's user dir for settings and logs
86 public string ClientDir
90 if (client != null && client.Self != null && !string.IsNullOrEmpty(client.Self.Name))
92 return Path.Combine(userDir, client.Self.Name);
96 return Environment.CurrentDirectory;
101 public string InventoryCacheFileName { get { return Path.Combine(ClientDir, "inventory.cache"); } }
103 private string globalLogFile;
104 public string GlobalLogFile { get { return globalLogFile; } }
106 private bool monoRuntime;
107 public bool MonoRuntime { get { return monoRuntime; } }
109 private Dictionary<UUID, Group> groups = new Dictionary<UUID, Group>();
110 public Dictionary<UUID, Group> Groups { get { return groups; } }
112 private Settings globalSettings;
114 /// Global settings for the entire application
116 public Settings GlobalSettings { get { return globalSettings; } }
118 private Settings clientSettings;
120 /// Per client settings
122 public Settings ClientSettings { get { return clientSettings; } }
124 public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();
126 public const string INCOMPLETE_NAME = "Loading...";
128 public readonly bool advancedDebugging = false;
130 public readonly List<IRadegastPlugin> PluginsLoaded = new List<IRadegastPlugin>();
132 private MediaManager mediaManager;
134 /// Radegast media manager for playing streams and in world sounds
136 public MediaManager MediaManager { get { return mediaManager; } }
139 private CommandsManager commandsManager;
141 /// Radegast command manager for executing textual console commands
143 public CommandsManager CommandsManager { get { return commandsManager; } }
146 /// Radegast ContextAction manager for context sensitive actions
148 public ContextActionsManager ContextActionManager { get; private set; }
150 private RadegastMovement movement;
152 /// Allows key emulation for moving avatar around
154 public RadegastMovement Movement { get { return movement; } }
156 private InventoryClipboard inventoryClipboard;
158 /// The last item that was cut or copied in the inventory, used for pasting
159 /// in a different place on the inventory, or other places like profile
160 /// that allow sending copied inventory items
162 public InventoryClipboard InventoryClipboard
164 get { return inventoryClipboard; }
167 inventoryClipboard = value;
168 OnInventoryClipboardUpdated(EventArgs.Empty);
172 private RLVManager rlv;
175 /// Manager for RLV functionality
177 public RLVManager RLV { get { return rlv; } }
181 #region ClientChanged event
182 /// <summary>The event subscribers, null of no subscribers</summary>
183 private EventHandler<ClientChangedEventArgs> m_ClientChanged;
185 ///<summary>Raises the ClientChanged Event</summary>
186 /// <param name="e">A ClientChangedEventArgs object containing
187 /// the old and the new client</param>
188 protected virtual void OnClientChanged(ClientChangedEventArgs e)
190 EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;
195 /// <summary>Thread sync lock object</summary>
196 private readonly object m_ClientChangedLock = new object();
198 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
199 public event EventHandler<ClientChangedEventArgs> ClientChanged
201 add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }
202 remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }
204 #endregion ClientChanged event
206 #region InventoryClipboardUpdated event
207 /// <summary>The event subscribers, null of no subscribers</summary>
208 private EventHandler<EventArgs> m_InventoryClipboardUpdated;
210 ///<summary>Raises the InventoryClipboardUpdated Event</summary>
211 /// <param name="e">A EventArgs object containing
212 /// the old and the new client</param>
213 protected virtual void OnInventoryClipboardUpdated(EventArgs e)
215 EventHandler<EventArgs> handler = m_InventoryClipboardUpdated;
220 /// <summary>Thread sync lock object</summary>
221 private readonly object m_InventoryClipboardUpdatedLock = new object();
223 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
224 public event EventHandler<EventArgs> InventoryClipboardUpdated
226 add { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated += value; } }
227 remove { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated -= value; } }
229 #endregion InventoryClipboardUpdated event
234 public RadegastInstance(GridClient client0)
236 // incase something else calls GlobalInstance while we are loading
237 globalInstance = this;
240 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
241 Application.ThreadException += HandleThreadException;
246 // Are we running mono?
247 monoRuntime = Type.GetType("Mono.Runtime") != null;
249 netcom = new RadegastNetcom(this);
250 state = new StateManager(this);
251 mediaManager = new MediaManager(this);
252 commandsManager = new CommandsManager(this);
253 ContextActionManager = new ContextActionsManager(this);
254 movement = new RadegastMovement(this);
256 InitializeLoggingAndConfig();
257 InitializeClient(client);
259 rlv = new RLVManager(this);
261 mainForm = new frmMain(this);
262 mainForm.InitializeControls();
264 mainForm.Load += new EventHandler(mainForm_Load);
265 ScanAndLoadPlugins();
268 private void InitializeClient(GridClient client)
270 client.Settings.MULTIPLE_SIMS = true;
272 client.Settings.USE_INTERPOLATION_TIMER = false;
273 client.Settings.ALWAYS_REQUEST_OBJECTS = true;
274 client.Settings.ALWAYS_DECODE_OBJECTS = true;
275 client.Settings.OBJECT_TRACKING = true;
276 client.Settings.ENABLE_SIMSTATS = true;
277 client.Settings.FETCH_MISSING_INVENTORY = true;
278 client.Settings.SEND_AGENT_THROTTLE = true;
279 client.Settings.SEND_AGENT_UPDATES = true;
281 client.Settings.USE_ASSET_CACHE = true;
282 client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");
283 client.Assets.Cache.AutoPruneEnabled = false;
285 client.Throttle.Total = 5000000f;
286 client.Settings.THROTTLE_OUTGOING_PACKETS = true;
287 client.Settings.LOGIN_TIMEOUT = 120 * 1000;
288 client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;
289 client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;
291 RegisterClientEvents(client);
294 private void RegisterClientEvents(GridClient client)
296 client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
297 client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
298 client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
299 client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
300 client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
301 netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
304 private void UnregisterClientEvents(GridClient client)
306 client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
307 client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
308 client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
309 client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
310 client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
311 netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
314 public void Reconnect()
316 TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);
317 Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);
318 GridClient oldClient = client;
319 client = new GridClient();
320 UnregisterClientEvents(oldClient);
321 InitializeClient(client);
322 OnClientChanged(new ClientChangedEventArgs(oldClient, client));
326 public void CleanUp()
336 UnregisterClientEvents(client);
341 List<IRadegastPlugin> unload = new List<IRadegastPlugin>(PluginsLoaded);
342 unload.ForEach(plug =>
344 PluginsLoaded.Remove(plug);
347 plug.StopPlugin(this);
351 Logger.Log("ERROR in Shutdown Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug, ex);
356 if (movement != null)
361 if (commandsManager != null)
363 commandsManager.Dispose();
364 commandsManager = null;
366 if (ContextActionManager != null)
368 ContextActionManager.Dispose();
369 ContextActionManager = null;
371 if (mediaManager != null)
373 mediaManager.Dispose();
386 if (mainForm != null)
388 mainForm.Load -= new EventHandler(mainForm_Load);
390 Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
393 void mainForm_Load(object sender, EventArgs e)
398 private void StartPlugins()
402 foreach (IRadegastPlugin plug in PluginsLoaded)
406 plug.StartPlugin(this);
410 Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);
416 private void ScanAndLoadPlugins()
418 string dirName = Application.StartupPath;
420 if (!Directory.Exists(dirName)) return;
422 foreach (string loadfilename in Directory.GetFiles(dirName))
424 if (loadfilename.ToLower().EndsWith(".dll") || loadfilename.ToLower().EndsWith(".exe"))
428 Assembly assembly = Assembly.LoadFile(loadfilename);
429 LoadAssembly(loadfilename, assembly);
431 catch (BadImageFormatException)
435 catch (ReflectionTypeLoadException)
437 // Out of date or dlls missing sub dependencies
439 catch (TypeLoadException)
441 // Another version of: Out of date or dlls missing sub dependencies
445 Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " because " + ex, Helpers.LogLevel.Debug);
451 public void LoadAssembly(string loadfilename, Assembly assembly)
453 foreach (Type type in assembly.GetTypes())
455 if (typeof(IRadegastPlugin).IsAssignableFrom(type))
457 if (type.IsInterface) continue;
460 IRadegastPlugin plug;
461 ConstructorInfo constructorInfo = type.GetConstructor(new Type[] {typeof (RadegastInstance)});
462 if (constructorInfo != null)
463 plug = (IRadegastPlugin) constructorInfo.Invoke(new[] {this});
466 constructorInfo = type.GetConstructor(new Type[] {});
467 if (constructorInfo != null)
468 plug = (IRadegastPlugin) constructorInfo.Invoke(new object[0]);
471 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because "+type+ " has no usable constructor.",Helpers.LogLevel.Debug);
475 lock (PluginsLoaded) PluginsLoaded.Add(plug);
479 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex,
480 Helpers.LogLevel.Debug);
487 commandsManager.LoadType(type);
491 Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type +
492 " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug);
498 void netcom_ClientConnected(object sender, EventArgs e)
502 if (!Directory.Exists(ClientDir))
503 Directory.CreateDirectory(ClientDir);
507 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
510 clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
514 void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
518 foreach (KeyValuePair<UUID, string> av in e.Names)
520 if (!nameCache.ContainsKey(av.Key))
522 nameCache.Add(av.Key, av.Value);
528 public string getAvatarName(UUID key)
532 if (key == UUID.Zero)
534 return "(???) (???)";
536 if (nameCache.ContainsKey(key))
538 return nameCache[key];
542 client.Avatars.RequestAvatarName(key);
543 return INCOMPLETE_NAME;
548 public void getAvatarNames(List<UUID> keys)
552 List<UUID> newNames = new List<UUID>();
553 foreach (UUID key in keys)
555 if (!nameCache.ContainsKey(key))
560 if (newNames.Count > 0)
562 client.Avatars.RequestAvatarNames(newNames);
567 public bool haveAvatarName(UUID key)
571 if (nameCache.ContainsKey(key))
578 void Groups_GroupsChanged(object sender, EventArgs e)
580 client.Groups.RequestCurrentGroups();
583 public static string SafeFileName(string fileName)
585 foreach (char lDisallowed in Path.GetInvalidFileNameChars())
587 fileName = fileName.Replace(lDisallowed.ToString(), "_");
593 public void LogClientMessage(string fileName, string message)
599 foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
601 fileName = fileName.Replace(lDisallowed.ToString(), "_");
604 File.AppendAllText(Path.Combine(ClientDir, fileName),
605 DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
607 catch (Exception) { }
611 void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
613 this.groups = e.Groups;
616 private void InitializeLoggingAndConfig()
620 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
621 if (!Directory.Exists(userDir))
623 Directory.CreateDirectory(userDir);
628 userDir = System.Environment.CurrentDirectory;
631 globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
632 globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
635 public GridClient Client
637 get { return client; }
640 public RadegastNetcom Netcom
642 get { return netcom; }
645 public StateManager State
647 get { return state; }
650 public frmMain MainForm
652 get { return mainForm; }
655 public TabsConsole TabConsole
657 get { return mainForm.TabConsole; }
660 public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
662 Logger.Log("Unhandled Thread Exception: "
663 + e.Exception.Message + Environment.NewLine
664 + e.Exception.StackTrace + Environment.NewLine,
665 Helpers.LogLevel.Error,
673 #region Event classes
674 public class ClientChangedEventArgs : EventArgs
676 private GridClient m_OldClient;
677 private GridClient m_Client;
679 public GridClient OldClient { get { return m_OldClient; } }
680 public GridClient Client { get { return m_Client; } }
682 public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
684 m_OldClient = OldClient;
688 #endregion Event classes