// // 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 RadegastFormCreated; /// /// Triggers the RadegastFormCreated event. /// 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; } } /// /// When was Radegast started (UTC) /// public readonly DateTime StartupTimeUTC; /// /// Time zone of the current world (currently hard coded to US Pacific time) /// public TimeZoneInfo WordTimeZone; private string userDir; /// /// System (not grid!) user's dir /// public string UserDir { get { return userDir; } } /// /// Grid client's user dir for settings and logs /// 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 groups = new Dictionary(); public Dictionary Groups { get { return groups; } } private Settings globalSettings; /// /// Global settings for the entire application /// public Settings GlobalSettings { get { return globalSettings; } } private Settings clientSettings; /// /// Per client settings /// public Settings ClientSettings { get { return clientSettings; } } public Dictionary nameCache = new Dictionary(); public const string INCOMPLETE_NAME = "Loading..."; public readonly bool advancedDebugging = false; private PluginManager pluginManager; /// Handles loading plugins and scripts public PluginManager PluginManager { get { return pluginManager; } } private MediaManager mediaManager; /// /// Radegast media manager for playing streams and in world sounds /// public MediaManager MediaManager { get { return mediaManager; } } private CommandsManager commandsManager; /// /// Radegast command manager for executing textual console commands /// public CommandsManager CommandsManager { get { return commandsManager; } } /// /// Radegast ContextAction manager for context sensitive actions /// public ContextActionsManager ContextActionManager { get; private set; } private RadegastMovement movement; /// /// Allows key emulation for moving avatar around /// public RadegastMovement Movement { get { return movement; } } private InventoryClipboard inventoryClipboard; /// /// 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 /// public InventoryClipboard InventoryClipboard { get { return inventoryClipboard; } set { inventoryClipboard = value; OnInventoryClipboardUpdated(EventArgs.Empty); } } private RLVManager rlv; /// /// Manager for RLV functionality /// public RLVManager RLV { get { return rlv; } } private GridManager gridManager; /// Manages default params for different grids public GridManager GridManger { get { return gridManager; } } #region Events #region ClientChanged event /// The event subscribers, null of no subscribers private EventHandler m_ClientChanged; ///Raises the ClientChanged Event /// A ClientChangedEventArgs object containing /// the old and the new client protected virtual void OnClientChanged(ClientChangedEventArgs e) { EventHandler handler = m_ClientChanged; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ClientChangedLock = new object(); /// Raised when the GridClient object in the main Radegast instance is changed public event EventHandler ClientChanged { add { lock (m_ClientChangedLock) { m_ClientChanged += value; } } remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } } } #endregion ClientChanged event #region InventoryClipboardUpdated event /// The event subscribers, null of no subscribers private EventHandler m_InventoryClipboardUpdated; ///Raises the InventoryClipboardUpdated Event /// A EventArgs object containing /// the old and the new client protected virtual void OnInventoryClipboardUpdated(EventArgs e) { EventHandler handler = m_InventoryClipboardUpdated; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_InventoryClipboardUpdatedLock = new object(); /// Raised when the GridClient object in the main Radegast instance is changed public event EventHandler 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(Groups_CurrentGroups); client.Groups.GroupLeaveReply += new EventHandler(Groups_GroupsChanged); client.Groups.GroupDropped += new EventHandler(Groups_GroupsChanged); client.Groups.GroupJoinedReply += new EventHandler(Groups_GroupsChanged); client.Avatars.UUIDNameReply += new EventHandler(Avatars_UUIDNameReply); if (netcom != null) netcom.ClientConnected += new EventHandler(netcom_ClientConnected); } private void UnregisterClientEvents(GridClient client) { client.Groups.CurrentGroups -= new EventHandler(Groups_CurrentGroups); client.Groups.GroupLeaveReply -= new EventHandler(Groups_GroupsChanged); client.Groups.GroupDropped -= new EventHandler(Groups_GroupsChanged); client.Groups.GroupJoinedReply -= new EventHandler(Groups_GroupsChanged); client.Avatars.UUIDNameReply -= new EventHandler(Avatars_UUIDNameReply); if (netcom != null) netcom.ClientConnected -= new EventHandler(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 av in e.Names) { if (!nameCache.ContainsKey(av.Key)) { nameCache.Add(av.Key, av.Value); } } } } /// /// Fetches avatar name /// /// Avatar UUID /// Should we wait until the name is retrieved /// Avatar name public string getAvatarName(UUID key, bool blocking) { if (!blocking) return getAvatarName(key); string name = null; using (ManualResetEvent gotName = new ManualResetEvent(false)) { EventHandler 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; } /// /// Fetches avatar name from cache, if not in cache will requst name from the server /// /// Avatar UUID /// Avatar name 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 keys) { lock (nameCache) { List newNames = new List(); 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 }