-// \r
-// Radegast Metaverse Client\r
-// Copyright (c) 2009, Radegast Development Team\r
-// All rights reserved.\r
-// \r
-// Redistribution and use in source and binary forms, with or without\r
-// modification, are permitted provided that the following conditions are met:\r
-// \r
-// * Redistributions of source code must retain the above copyright notice,\r
-// this list of conditions and the following disclaimer.\r
-// * Redistributions in binary form must reproduce the above copyright\r
-// notice, this list of conditions and the following disclaimer in the\r
-// documentation and/or other materials provided with the distribution.\r
-// * Neither the name of the application "Radegast", nor the names of its\r
-// contributors may be used to endorse or promote products derived from\r
-// this software without specific prior written permission.\r
-// \r
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\r
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\r
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\r
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\r
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
-//\r
-// $Id$\r
-//\r
-using System;\r
-using System.Collections.Generic;\r
-using System.IO;\r
-using System.Reflection;\r
-using System.Threading;\r
-using System.Windows.Forms;\r
-using Radegast.Commands;\r
-using Radegast.Netcom;\r
-using Radegast.Media;\r
-using OpenMetaverse;\r
-\r
-namespace Radegast\r
-{\r
- public class RadegastInstance\r
- {\r
- private GridClient client;\r
- private RadegastNetcom netcom;\r
-\r
- private StateManager state;\r
-\r
- private frmMain mainForm;\r
-\r
- // Singleton, there can be only one instance\r
- private static RadegastInstance globalInstance = null;\r
- public static RadegastInstance GlobalInstance\r
- {\r
- get\r
- {\r
- if (globalInstance == null)\r
- {\r
- globalInstance = new RadegastInstance(new GridClient());\r
- }\r
- return globalInstance;\r
- }\r
- }\r
-\r
- private string userDir;\r
- /// <summary>\r
- /// System (not grid!) user's dir\r
- /// </summary>\r
- public string UserDir { get { return userDir; } }\r
-\r
- /// <summary>\r
- /// Grid client's user dir for settings and logs\r
- /// </summary>\r
- public string ClientDir\r
- {\r
- get\r
- {\r
- if (client != null && client.Self != null && !string.IsNullOrEmpty(client.Self.Name))\r
- {\r
- return Path.Combine(userDir, client.Self.Name);\r
- }\r
- else\r
- {\r
- return Environment.CurrentDirectory;\r
- }\r
- }\r
- }\r
-\r
- public string InventoryCacheFileName { get { return Path.Combine(ClientDir, "inventory.cache"); } }\r
-\r
- private string animCacheDir;\r
- public string AnimCacheDir { get { return animCacheDir; } }\r
-\r
- private string globalLogFile;\r
- public string GlobalLogFile { get { return globalLogFile; } }\r
-\r
- private bool monoRuntime;\r
- public bool MonoRuntime { get { return monoRuntime; } }\r
-\r
- private Dictionary<UUID, Group> groups = new Dictionary<UUID, Group>();\r
- public Dictionary<UUID, Group> Groups { get { return groups; } }\r
-\r
- private Settings globalSettings;\r
- /// <summary>\r
- /// Global settings for the entire application\r
- /// </summary>\r
- public Settings GlobalSettings { get { return globalSettings; } }\r
-\r
- private Settings clientSettings;\r
- /// <summary>\r
- /// Per client settings\r
- /// </summary>\r
- public Settings ClientSettings { get { return clientSettings; } }\r
-\r
- public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();\r
-\r
- public const string INCOMPLETE_NAME = "Loading...";\r
-\r
- public readonly bool advancedDebugging = false;\r
-\r
- public readonly List<IRadegastPlugin> PluginsLoaded = new List<IRadegastPlugin>();\r
-\r
- private MediaManager mediaManager;\r
- /// <summary>\r
- /// Radegast media manager for playing streams and in world sounds\r
- /// </summary>\r
- public MediaManager MediaManager { get { return mediaManager; } }\r
-\r
-\r
- private CommandsManager commandsManager;\r
- /// <summary>\r
- /// Radegast command manager for executing textual console commands\r
- /// </summary>\r
- public CommandsManager CommandsManager { get { return commandsManager; } }\r
-\r
- /// <summary>\r
- /// Radegast ContextAction manager for context sensitive actions\r
- /// </summary>\r
- public ContextActionsManager ContextActionManager { get; private set; }\r
-\r
- private RadegastMovement movement;\r
- /// <summary>\r
- /// Allows key emulation for moving avatar around\r
- /// </summary>\r
- public RadegastMovement Movement { get { return movement; } }\r
-\r
- #region Events\r
- /// <summary>The event subscribers, null of no subscribers</summary>\r
- private EventHandler<ClientChangedEventArgs> m_ClientChanged;\r
-\r
- ///<summary>Raises the ClientChanged Event</summary>\r
- /// <param name="e">A ClientChangedEventArgs object containing\r
- /// the old and the new client</param>\r
- protected virtual void OnClientChanged(ClientChangedEventArgs e)\r
- {\r
- EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;\r
- if (handler != null)\r
- handler(this, e);\r
- }\r
-\r
- /// <summary>Thread sync lock object</summary>\r
- private readonly object m_ClientChangedLock = new object();\r
-\r
- /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>\r
- public event EventHandler<ClientChangedEventArgs> ClientChanged\r
- {\r
- add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }\r
- remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }\r
- }\r
- #endregion Events\r
-\r
- public RadegastInstance(GridClient client0)\r
- {\r
- // incase something else calls GlobalInstance while we are loading\r
- globalInstance = this;\r
-\r
-#if HANDLE_THREAD_EXCEPTIONS\r
- Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);\r
- Application.ThreadException += HandleThreadException;\r
-#endif\r
-\r
- client = client0;\r
-\r
- // Are we running mono?\r
- monoRuntime = Type.GetType("Mono.Runtime") != null;\r
-\r
- netcom = new RadegastNetcom(this);\r
- state = new StateManager(this);\r
- mediaManager = new MediaManager(this);\r
- commandsManager = new CommandsManager(this);\r
- ContextActionManager = new ContextActionsManager(this);\r
- movement = new RadegastMovement(this);\r
-\r
- InitializeLoggingAndConfig();\r
- InitializeClient(client);\r
-\r
- mainForm = new frmMain(this);\r
- mainForm.InitializeControls();\r
-\r
- mainForm.Load += new EventHandler(mainForm_Load);\r
- ScanAndLoadPlugins();\r
- }\r
-\r
- private void InitializeClient(GridClient client)\r
- {\r
- client.Settings.MULTIPLE_SIMS = true;\r
-\r
- client.Settings.USE_INTERPOLATION_TIMER = false;\r
- client.Settings.ALWAYS_REQUEST_OBJECTS = true;\r
- client.Settings.ALWAYS_DECODE_OBJECTS = true;\r
- client.Settings.OBJECT_TRACKING = true;\r
- client.Settings.ENABLE_SIMSTATS = true;\r
- client.Settings.FETCH_MISSING_INVENTORY = true;\r
- client.Settings.SEND_AGENT_THROTTLE = true;\r
- client.Settings.SEND_AGENT_UPDATES = true;\r
-\r
- client.Settings.USE_ASSET_CACHE = true;\r
- client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");\r
- client.Assets.Cache.AutoPruneEnabled = false;\r
-\r
- client.Throttle.Total = 5000000f;\r
- client.Settings.THROTTLE_OUTGOING_PACKETS = true;\r
- client.Settings.LOGIN_TIMEOUT = 120 * 1000;\r
- client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;\r
- client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;\r
-\r
- RegisterClientEvents(client);\r
- }\r
-\r
- private void RegisterClientEvents(GridClient client)\r
- {\r
- client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);\r
- client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);\r
- client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);\r
- client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);\r
- client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);\r
- netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);\r
- }\r
-\r
- private void UnregisterClientEvents(GridClient client)\r
- {\r
- client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);\r
- client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);\r
- client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);\r
- client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);\r
- client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);\r
- netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);\r
- }\r
-\r
- public void Reconnect()\r
- {\r
- TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);\r
- Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);\r
- GridClient oldClient = client;\r
- client = new GridClient();\r
- UnregisterClientEvents(oldClient);\r
- InitializeClient(client);\r
- OnClientChanged(new ClientChangedEventArgs(oldClient, client));\r
- netcom.Login();\r
- }\r
-\r
- public void CleanUp()\r
- {\r
- if (client != null)\r
- {\r
- UnregisterClientEvents(client);\r
- }\r
-\r
- lock (PluginsLoaded)\r
- {\r
- List<IRadegastPlugin> unload = new List<IRadegastPlugin>(PluginsLoaded);\r
- unload.ForEach(plug =>\r
- {\r
- PluginsLoaded.Remove(plug);\r
- try\r
- {\r
- plug.StopPlugin(this);\r
- }\r
- catch (Exception ex)\r
- {\r
- Logger.Log("ERROR in Shutdown Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug, ex);\r
- }\r
- });\r
- }\r
-\r
- if (movement != null)\r
- {\r
- movement.Dispose();\r
- movement = null;\r
- }\r
- if (commandsManager != null)\r
- {\r
- commandsManager.Dispose();\r
- commandsManager = null;\r
- }\r
- if (ContextActionManager != null)\r
- {\r
- ContextActionManager.Dispose();\r
- ContextActionManager = null;\r
- }\r
- if (mediaManager != null)\r
- {\r
- mediaManager.Dispose();\r
- mediaManager = null;\r
- }\r
- if (state != null)\r
- {\r
- state.Dispose();\r
- state = null;\r
- }\r
- if (netcom != null)\r
- {\r
- netcom.Dispose();\r
- netcom = null;\r
- }\r
- if (mainForm != null)\r
- {\r
- mainForm.Load -= new EventHandler(mainForm_Load);\r
- }\r
- Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);\r
- }\r
-\r
- void mainForm_Load(object sender, EventArgs e)\r
- {\r
- StartPlugins();\r
- }\r
-\r
- private void StartPlugins()\r
- {\r
- lock (PluginsLoaded)\r
- {\r
- foreach (IRadegastPlugin plug in PluginsLoaded)\r
- {\r
- try\r
- {\r
- plug.StartPlugin(this);\r
- }\r
- catch (Exception ex)\r
- {\r
- Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);\r
- }\r
- }\r
- }\r
- }\r
-\r
- private void ScanAndLoadPlugins()\r
- {\r
- string dirName = Application.StartupPath;\r
-\r
- if (!Directory.Exists(dirName)) return;\r
-\r
- foreach (string loadfilename in Directory.GetFiles(dirName))\r
- {\r
- if (loadfilename.ToLower().EndsWith(".dll") || loadfilename.ToLower().EndsWith(".exe"))\r
- {\r
- try\r
- {\r
- Assembly assembly = Assembly.LoadFile(loadfilename);\r
- LoadAssembly(loadfilename, assembly);\r
- }\r
- catch (BadImageFormatException)\r
- {\r
- // non .NET .dlls\r
- }\r
- catch (ReflectionTypeLoadException)\r
- {\r
- // Out of date or dlls missing sub dependencies\r
- }\r
- catch (TypeLoadException)\r
- {\r
- // Another version of: Out of date or dlls missing sub dependencies\r
- }\r
- catch (Exception ex)\r
- {\r
- Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " because " + ex, Helpers.LogLevel.Debug);\r
- }\r
- }\r
- }\r
- }\r
-\r
- public void LoadAssembly(string loadfilename, Assembly assembly)\r
- {\r
- foreach (Type type in assembly.GetTypes())\r
- {\r
- if (typeof(IRadegastPlugin).IsAssignableFrom(type))\r
- {\r
- if (type.IsInterface) continue;\r
- try\r
- {\r
- IRadegastPlugin plug;\r
- ConstructorInfo constructorInfo = type.GetConstructor(new Type[] {typeof (RadegastInstance)});\r
- if (constructorInfo != null)\r
- plug = (IRadegastPlugin) constructorInfo.Invoke(new[] {this});\r
- else\r
- {\r
- constructorInfo = type.GetConstructor(new Type[] {});\r
- if (constructorInfo != null)\r
- plug = (IRadegastPlugin) constructorInfo.Invoke(new object[0]);\r
- else\r
- {\r
- Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because "+type+ " has no usable constructor.",Helpers.LogLevel.Debug);\r
- continue;\r
- }\r
- }\r
- lock (PluginsLoaded) PluginsLoaded.Add(plug);\r
- }\r
- catch (Exception ex)\r
- {\r
- Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex,\r
- Helpers.LogLevel.Debug);\r
- }\r
- }\r
- else\r
- {\r
- try\r
- {\r
- commandsManager.LoadType(type);\r
- }\r
- catch (Exception ex)\r
- {\r
- Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type +\r
- " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug);\r
- }\r
- }\r
- }\r
- }\r
-\r
- void netcom_ClientConnected(object sender, EventArgs e)\r
- {\r
- try\r
- {\r
- if (!Directory.Exists(ClientDir))\r
- Directory.CreateDirectory(ClientDir);\r
- }\r
- catch (Exception ex)\r
- {\r
- Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);\r
- }\r
-\r
- clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));\r
- }\r
-\r
-\r
- void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)\r
- {\r
- lock (nameCache)\r
- {\r
- foreach (KeyValuePair<UUID, string> av in e.Names)\r
- {\r
- if (!nameCache.ContainsKey(av.Key))\r
- {\r
- nameCache.Add(av.Key, av.Value);\r
- }\r
- }\r
- }\r
- }\r
-\r
- public string getAvatarName(UUID key)\r
- {\r
- lock (nameCache)\r
- {\r
- if (key == UUID.Zero)\r
- {\r
- return "(???) (???)";\r
- }\r
- if (nameCache.ContainsKey(key))\r
- {\r
- return nameCache[key];\r
- }\r
- else\r
- {\r
- client.Avatars.RequestAvatarName(key);\r
- return INCOMPLETE_NAME;\r
- }\r
- }\r
- }\r
-\r
- public void getAvatarNames(List<UUID> keys)\r
- {\r
- lock (nameCache)\r
- {\r
- List<UUID> newNames = new List<UUID>();\r
- foreach (UUID key in keys)\r
- {\r
- if (!nameCache.ContainsKey(key))\r
- {\r
- newNames.Add(key);\r
- }\r
- }\r
- if (newNames.Count > 0)\r
- {\r
- client.Avatars.RequestAvatarNames(newNames);\r
- }\r
- }\r
- }\r
-\r
- public bool haveAvatarName(UUID key)\r
- {\r
- lock (nameCache)\r
- {\r
- if (nameCache.ContainsKey(key))\r
- return true;\r
- else\r
- return false;\r
- }\r
- }\r
-\r
- void Groups_GroupsChanged(object sender, EventArgs e)\r
- {\r
- client.Groups.RequestCurrentGroups();\r
- }\r
-\r
- public static string SafeFileName(string fileName)\r
- {\r
- foreach (char lDisallowed in Path.GetInvalidFileNameChars())\r
- {\r
- fileName = fileName.Replace(lDisallowed.ToString(), "_");\r
- }\r
-\r
- return fileName;\r
- }\r
-\r
- public void LogClientMessage(string fileName, string message)\r
- {\r
- lock (this)\r
- {\r
- try\r
- {\r
- foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())\r
- {\r
- fileName = fileName.Replace(lDisallowed.ToString(), "_");\r
- }\r
-\r
- File.AppendAllText(Path.Combine(ClientDir, fileName),\r
- DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);\r
- }\r
- catch (Exception) { }\r
- }\r
- }\r
-\r
- void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)\r
- {\r
- this.groups = e.Groups;\r
- }\r
-\r
- private void InitializeLoggingAndConfig()\r
- {\r
- try\r
- {\r
- userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);\r
- if (!Directory.Exists(userDir))\r
- {\r
- Directory.CreateDirectory(userDir);\r
- }\r
- }\r
- catch (Exception)\r
- {\r
- userDir = System.Environment.CurrentDirectory;\r
- };\r
-\r
- animCacheDir = Path.Combine(userDir, @"anim_cache");\r
- globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");\r
- globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));\r
- }\r
-\r
- public GridClient Client\r
- {\r
- get { return client; }\r
- }\r
-\r
- public RadegastNetcom Netcom\r
- {\r
- get { return netcom; }\r
- }\r
-\r
- public StateManager State\r
- {\r
- get { return state; }\r
- }\r
-\r
- public frmMain MainForm\r
- {\r
- get { return mainForm; }\r
- }\r
-\r
- public TabsConsole TabConsole\r
- {\r
- get { return mainForm.TabConsole; }\r
- }\r
-\r
- public void HandleThreadException(object sender, ThreadExceptionEventArgs e)\r
- {\r
- Logger.Log("Unhandled Thread Exception: " \r
- + e.Exception.Message + Environment.NewLine\r
- + e.Exception.StackTrace + Environment.NewLine,\r
- Helpers.LogLevel.Error,\r
- client);\r
-\r
- Application.Exit();\r
- }\r
- }\r
-\r
- #region Event classes\r
- public class ClientChangedEventArgs : EventArgs\r
- {\r
- private GridClient m_OldClient;\r
- private GridClient m_Client;\r
-\r
- public GridClient OldClient { get { return m_OldClient; } }\r
- public GridClient Client { get { return m_Client; } }\r
-\r
- public ClientChangedEventArgs(GridClient OldClient, GridClient Client)\r
- {\r
- m_OldClient = OldClient;\r
- m_Client = Client;\r
- }\r
- }\r
- #endregion Event classes\r
-}\r
+//
+// Radegast Metaverse Client
+// Copyright (c) 2009-2010, Radegast Development Team
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of the application "Radegast", nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// $Id$
+//
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Windows.Forms;
+using Radegast.Commands;
+using Radegast.Netcom;
+using Radegast.Media;
+using OpenMetaverse;
+
+namespace Radegast
+{
+ public class RadegastInstance
+ {
+ #region OnRadegastFormCreated
+ public event Action<RadegastForm> RadegastFormCreated;
+ /// <summary>
+ /// Triggers the RadegastFormCreated event.
+ /// </summary>
+ public virtual void OnRadegastFormCreated(RadegastForm radForm)
+ {
+ if (RadegastFormCreated != null) RadegastFormCreated(radForm);
+ }
+ #endregion
+
+ private GridClient client;
+ private RadegastNetcom netcom;
+
+ private StateManager state;
+
+ private frmMain mainForm;
+
+ // Singleton, there can be only one instance
+ private static RadegastInstance globalInstance = null;
+ public static RadegastInstance GlobalInstance
+ {
+ get
+ {
+ if (globalInstance == null)
+ {
+ globalInstance = new RadegastInstance(new GridClient());
+ }
+ return globalInstance;
+ }
+ }
+
+ /// <summary>
+ /// When was Radegast started (UTC)
+ /// </summary>
+ public readonly DateTime StartupTimeUTC;
+
+ /// <summary>
+ /// Time zone of the current world (currently hard coded to US Pacific time)
+ /// </summary>
+ public TimeZoneInfo WordTimeZone;
+
+ private string userDir;
+ /// <summary>
+ /// System (not grid!) user's dir
+ /// </summary>
+ public string UserDir { get { return userDir; } }
+
+ /// <summary>
+ /// Grid client's user dir for settings and logs
+ /// </summary>
+ public string ClientDir
+ {
+ get
+ {
+ if (client != null && client.Self != null && !string.IsNullOrEmpty(client.Self.Name))
+ {
+ return Path.Combine(userDir, client.Self.Name);
+ }
+ else
+ {
+ return Environment.CurrentDirectory;
+ }
+ }
+ }
+
+ public string InventoryCacheFileName { get { return Path.Combine(ClientDir, "inventory.cache"); } }
+
+ private string globalLogFile;
+ public string GlobalLogFile { get { return globalLogFile; } }
+
+ private bool monoRuntime;
+ public bool MonoRuntime { get { return monoRuntime; } }
+
+ private Dictionary<UUID, Group> groups = new Dictionary<UUID, Group>();
+ public Dictionary<UUID, Group> Groups { get { return groups; } }
+
+ private Settings globalSettings;
+ /// <summary>
+ /// Global settings for the entire application
+ /// </summary>
+ public Settings GlobalSettings { get { return globalSettings; } }
+
+ private Settings clientSettings;
+ /// <summary>
+ /// Per client settings
+ /// </summary>
+ public Settings ClientSettings { get { return clientSettings; } }
+
+ public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();
+
+ public const string INCOMPLETE_NAME = "Loading...";
+
+ public readonly bool advancedDebugging = false;
+
+ private PluginManager pluginManager;
+ /// <summary> Handles loading plugins and scripts</summary>
+ public PluginManager PluginManager { get { return pluginManager; } }
+
+ private MediaManager mediaManager;
+ /// <summary>
+ /// Radegast media manager for playing streams and in world sounds
+ /// </summary>
+ public MediaManager MediaManager { get { return mediaManager; } }
+
+
+ private CommandsManager commandsManager;
+ /// <summary>
+ /// Radegast command manager for executing textual console commands
+ /// </summary>
+ public CommandsManager CommandsManager { get { return commandsManager; } }
+
+ /// <summary>
+ /// Radegast ContextAction manager for context sensitive actions
+ /// </summary>
+ public ContextActionsManager ContextActionManager { get; private set; }
+
+ private RadegastMovement movement;
+ /// <summary>
+ /// Allows key emulation for moving avatar around
+ /// </summary>
+ public RadegastMovement Movement { get { return movement; } }
+
+ private InventoryClipboard inventoryClipboard;
+ /// <summary>
+ /// The last item that was cut or copied in the inventory, used for pasting
+ /// in a different place on the inventory, or other places like profile
+ /// that allow sending copied inventory items
+ /// </summary>
+ public InventoryClipboard InventoryClipboard
+ {
+ get { return inventoryClipboard; }
+ set
+ {
+ inventoryClipboard = value;
+ OnInventoryClipboardUpdated(EventArgs.Empty);
+ }
+ }
+
+ private RLVManager rlv;
+
+ /// <summary>
+ /// Manager for RLV functionality
+ /// </summary>
+ public RLVManager RLV { get { return rlv; } }
+
+ private GridManager gridManager;
+ /// <summary>Manages default params for different grids</summary>
+ public GridManager GridManger { get { return gridManager; } }
+
+ #region Events
+
+ #region ClientChanged event
+ /// <summary>The event subscribers, null of no subscribers</summary>
+ private EventHandler<ClientChangedEventArgs> m_ClientChanged;
+
+ ///<summary>Raises the ClientChanged Event</summary>
+ /// <param name="e">A ClientChangedEventArgs object containing
+ /// the old and the new client</param>
+ protected virtual void OnClientChanged(ClientChangedEventArgs e)
+ {
+ EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;
+ if (handler != null)
+ handler(this, e);
+ }
+
+ /// <summary>Thread sync lock object</summary>
+ private readonly object m_ClientChangedLock = new object();
+
+ /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
+ public event EventHandler<ClientChangedEventArgs> ClientChanged
+ {
+ add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }
+ remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }
+ }
+ #endregion ClientChanged event
+
+ #region InventoryClipboardUpdated event
+ /// <summary>The event subscribers, null of no subscribers</summary>
+ private EventHandler<EventArgs> m_InventoryClipboardUpdated;
+
+ ///<summary>Raises the InventoryClipboardUpdated Event</summary>
+ /// <param name="e">A EventArgs object containing
+ /// the old and the new client</param>
+ protected virtual void OnInventoryClipboardUpdated(EventArgs e)
+ {
+ EventHandler<EventArgs> handler = m_InventoryClipboardUpdated;
+ if (handler != null)
+ handler(this, e);
+ }
+
+ /// <summary>Thread sync lock object</summary>
+ private readonly object m_InventoryClipboardUpdatedLock = new object();
+
+ /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
+ public event EventHandler<EventArgs> InventoryClipboardUpdated
+ {
+ add { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated += value; } }
+ remove { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated -= value; } }
+ }
+ #endregion InventoryClipboardUpdated event
+
+
+ #endregion Events
+
+ public RadegastInstance(GridClient client0)
+ {
+ // incase something else calls GlobalInstance while we are loading
+ globalInstance = this;
+
+#if !DEBUG
+ Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
+ Application.ThreadException += HandleThreadException;
+#endif
+
+ client = client0;
+
+ // Initialize current time zone, and mark when we started
+ GetWorldTimeZone();
+ StartupTimeUTC = DateTime.UtcNow;
+
+ // Are we running mono?
+ monoRuntime = Type.GetType("Mono.Runtime") != null;
+
+ netcom = new RadegastNetcom(this);
+ state = new StateManager(this);
+ mediaManager = new MediaManager(this);
+ commandsManager = new CommandsManager(this);
+ ContextActionManager = new ContextActionsManager(this);
+ movement = new RadegastMovement(this);
+
+ InitializeLoggingAndConfig();
+ InitializeClient(client);
+
+ rlv = new RLVManager(this);
+ gridManager = new GridManager(this);
+ gridManager.LoadGrids();
+
+ mainForm = new frmMain(this);
+ mainForm.InitializeControls();
+
+ mainForm.Load += new EventHandler(mainForm_Load);
+ pluginManager = new PluginManager(this);
+ pluginManager.ScanAndLoadPlugins();
+ }
+
+ private void InitializeClient(GridClient client)
+ {
+ client.Settings.MULTIPLE_SIMS = true;
+
+ client.Settings.USE_INTERPOLATION_TIMER = false;
+ client.Settings.ALWAYS_REQUEST_OBJECTS = true;
+ client.Settings.ALWAYS_DECODE_OBJECTS = true;
+ client.Settings.OBJECT_TRACKING = true;
+ client.Settings.ENABLE_SIMSTATS = true;
+ client.Settings.FETCH_MISSING_INVENTORY = true;
+ client.Settings.SEND_AGENT_THROTTLE = true;
+ client.Settings.SEND_AGENT_UPDATES = true;
+
+ client.Settings.USE_ASSET_CACHE = true;
+ client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");
+ client.Assets.Cache.AutoPruneEnabled = false;
+
+ client.Throttle.Total = 5000000f;
+ client.Settings.THROTTLE_OUTGOING_PACKETS = false;
+ client.Settings.LOGIN_TIMEOUT = 120 * 1000;
+ client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;
+ client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;
+
+ RegisterClientEvents(client);
+ }
+
+ private void RegisterClientEvents(GridClient client)
+ {
+ client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
+ client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
+ client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
+ client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
+ client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
+ if (netcom != null)
+ netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
+ }
+
+ private void UnregisterClientEvents(GridClient client)
+ {
+ client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
+ client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
+ client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
+ client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
+ client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
+ if (netcom != null)
+ netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
+ }
+
+ private void GetWorldTimeZone()
+ {
+ try
+ {
+ foreach (TimeZoneInfo tz in TimeZoneInfo.GetSystemTimeZones())
+ {
+ if (tz.Id == "Pacific Standard Time" || tz.Id == "America/Los_Angeles")
+ {
+ WordTimeZone = tz;
+ break;
+ }
+ }
+ }
+ catch (Exception) { }
+ }
+
+ public DateTime GetWorldTime()
+ {
+ DateTime now;
+
+ try
+ {
+ if (WordTimeZone != null)
+ now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, WordTimeZone);
+ else
+ now = DateTime.UtcNow.AddHours(-7);
+ }
+ catch (Exception)
+ {
+ now = DateTime.UtcNow.AddHours(-7);
+ }
+
+ return now;
+ }
+
+
+ public void Reconnect()
+ {
+ TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);
+ Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);
+ GridClient oldClient = client;
+ client = new GridClient();
+ UnregisterClientEvents(oldClient);
+ InitializeClient(client);
+ OnClientChanged(new ClientChangedEventArgs(oldClient, client));
+ netcom.Login();
+ }
+
+ public void CleanUp()
+ {
+ if (gridManager != null)
+ {
+ gridManager.Dispose();
+ gridManager = null;
+ }
+
+ if (rlv != null)
+ {
+ rlv.Dispose();
+ rlv = null;
+ }
+
+ if (client != null)
+ {
+ UnregisterClientEvents(client);
+ }
+
+ if (pluginManager != null)
+ {
+ pluginManager.Dispose();
+ pluginManager = null;
+ }
+
+ if (movement != null)
+ {
+ movement.Dispose();
+ movement = null;
+ }
+ if (commandsManager != null)
+ {
+ commandsManager.Dispose();
+ commandsManager = null;
+ }
+ if (ContextActionManager != null)
+ {
+ ContextActionManager.Dispose();
+ ContextActionManager = null;
+ }
+ if (mediaManager != null)
+ {
+ mediaManager.Dispose();
+ mediaManager = null;
+ }
+ if (state != null)
+ {
+ state.Dispose();
+ state = null;
+ }
+ if (netcom != null)
+ {
+ netcom.Dispose();
+ netcom = null;
+ }
+ if (mainForm != null)
+ {
+ mainForm.Load -= new EventHandler(mainForm_Load);
+ }
+ Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
+ }
+
+ void mainForm_Load(object sender, EventArgs e)
+ {
+ pluginManager.StartPlugins();
+ }
+
+ void netcom_ClientConnected(object sender, EventArgs e)
+ {
+ try
+ {
+ if (!Directory.Exists(ClientDir))
+ Directory.CreateDirectory(ClientDir);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
+ }
+
+ clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
+ }
+
+
+ void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
+ {
+ lock (nameCache)
+ {
+ foreach (KeyValuePair<UUID, string> av in e.Names)
+ {
+ if (!nameCache.ContainsKey(av.Key))
+ {
+ nameCache.Add(av.Key, av.Value);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Fetches avatar name
+ /// </summary>
+ /// <param name="key">Avatar UUID</param>
+ /// <param name="blocking">Should we wait until the name is retrieved</param>
+ /// <returns>Avatar name</returns>
+ public string getAvatarName(UUID key, bool blocking)
+ {
+ if (!blocking)
+ return getAvatarName(key);
+
+ string name = null;
+
+ using (ManualResetEvent gotName = new ManualResetEvent(false))
+ {
+
+ EventHandler<UUIDNameReplyEventArgs> handler = (object sender, UUIDNameReplyEventArgs e) =>
+ {
+ if (e.Names.ContainsKey(key))
+ {
+ name = e.Names[key];
+ gotName.Set();
+ }
+ };
+
+ client.Avatars.UUIDNameReply += handler;
+ name = getAvatarName(key);
+
+ if (name == INCOMPLETE_NAME)
+ {
+ gotName.WaitOne(10 * 1000, false);
+ }
+
+ client.Avatars.UUIDNameReply -= handler;
+ }
+ return name;
+
+ }
+
+ /// <summary>
+ /// Fetches avatar name from cache, if not in cache will requst name from the server
+ /// </summary>
+ /// <param name="key">Avatar UUID</param>
+ /// <returns>Avatar name</returns>
+ public string getAvatarName(UUID key)
+ {
+ lock (nameCache)
+ {
+ if (key == UUID.Zero)
+ {
+ return "(???) (???)";
+ }
+ if (nameCache.ContainsKey(key))
+ {
+ return nameCache[key];
+ }
+ else
+ {
+ client.Avatars.RequestAvatarName(key);
+ return INCOMPLETE_NAME;
+ }
+ }
+ }
+
+ public void getAvatarNames(List<UUID> keys)
+ {
+ lock (nameCache)
+ {
+ List<UUID> newNames = new List<UUID>();
+ foreach (UUID key in keys)
+ {
+ if (!nameCache.ContainsKey(key))
+ {
+ newNames.Add(key);
+ }
+ }
+ if (newNames.Count > 0)
+ {
+ client.Avatars.RequestAvatarNames(newNames);
+ }
+ }
+ }
+
+ public bool haveAvatarName(UUID key)
+ {
+ lock (nameCache)
+ {
+ if (nameCache.ContainsKey(key))
+ return true;
+ else
+ return false;
+ }
+ }
+
+ void Groups_GroupsChanged(object sender, EventArgs e)
+ {
+ client.Groups.RequestCurrentGroups();
+ }
+
+ public static string SafeFileName(string fileName)
+ {
+ foreach (char lDisallowed in Path.GetInvalidFileNameChars())
+ {
+ fileName = fileName.Replace(lDisallowed.ToString(), "_");
+ }
+
+ return fileName;
+ }
+
+ public void LogClientMessage(string fileName, string message)
+ {
+ lock (this)
+ {
+ try
+ {
+ foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
+ {
+ fileName = fileName.Replace(lDisallowed.ToString(), "_");
+ }
+
+ File.AppendAllText(Path.Combine(ClientDir, fileName),
+ DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
+ }
+ catch (Exception) { }
+ }
+ }
+
+ void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
+ {
+ this.groups = e.Groups;
+ }
+
+ private void InitializeLoggingAndConfig()
+ {
+ try
+ {
+ userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
+ if (!Directory.Exists(userDir))
+ {
+ Directory.CreateDirectory(userDir);
+ }
+ }
+ catch (Exception)
+ {
+ userDir = System.Environment.CurrentDirectory;
+ };
+
+ globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
+ globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
+ }
+
+ public GridClient Client
+ {
+ get { return client; }
+ }
+
+ public RadegastNetcom Netcom
+ {
+ get { return netcom; }
+ }
+
+ public StateManager State
+ {
+ get { return state; }
+ }
+
+ public frmMain MainForm
+ {
+ get { return mainForm; }
+ }
+
+ public TabsConsole TabConsole
+ {
+ get { return mainForm.TabConsole; }
+ }
+
+ public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
+ {
+ Logger.Log("Unhandled Thread Exception: "
+ + e.Exception.Message + Environment.NewLine
+ + e.Exception.StackTrace + Environment.NewLine,
+ Helpers.LogLevel.Error,
+ client);
+#if DEBUG
+ Application.Exit();
+#endif
+ }
+ }
+
+ #region Event classes
+ public class ClientChangedEventArgs : EventArgs
+ {
+ private GridClient m_OldClient;
+ private GridClient m_Client;
+
+ public GridClient OldClient { get { return m_OldClient; } }
+ public GridClient Client { get { return m_Client; } }
+
+ public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
+ {
+ m_OldClient = OldClient;
+ m_Client = Client;
+ }
+ }
+ #endregion Event classes
+}