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 private PluginManager pluginManager;
131 /// <summary> Handles loading plugins and scripts</summary>
132 public PluginManager PluginManager { get { return pluginManager; } }
134 private MediaManager mediaManager;
136 /// Radegast media manager for playing streams and in world sounds
138 public MediaManager MediaManager { get { return mediaManager; } }
141 private CommandsManager commandsManager;
143 /// Radegast command manager for executing textual console commands
145 public CommandsManager CommandsManager { get { return commandsManager; } }
148 /// Radegast ContextAction manager for context sensitive actions
150 public ContextActionsManager ContextActionManager { get; private set; }
152 private RadegastMovement movement;
154 /// Allows key emulation for moving avatar around
156 public RadegastMovement Movement { get { return movement; } }
158 private InventoryClipboard inventoryClipboard;
160 /// The last item that was cut or copied in the inventory, used for pasting
161 /// in a different place on the inventory, or other places like profile
162 /// that allow sending copied inventory items
164 public InventoryClipboard InventoryClipboard
166 get { return inventoryClipboard; }
169 inventoryClipboard = value;
170 OnInventoryClipboardUpdated(EventArgs.Empty);
174 private RLVManager rlv;
177 /// Manager for RLV functionality
179 public RLVManager RLV { get { return rlv; } }
183 #region ClientChanged event
184 /// <summary>The event subscribers, null of no subscribers</summary>
185 private EventHandler<ClientChangedEventArgs> m_ClientChanged;
187 ///<summary>Raises the ClientChanged Event</summary>
188 /// <param name="e">A ClientChangedEventArgs object containing
189 /// the old and the new client</param>
190 protected virtual void OnClientChanged(ClientChangedEventArgs e)
192 EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;
197 /// <summary>Thread sync lock object</summary>
198 private readonly object m_ClientChangedLock = new object();
200 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
201 public event EventHandler<ClientChangedEventArgs> ClientChanged
203 add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }
204 remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }
206 #endregion ClientChanged event
208 #region InventoryClipboardUpdated event
209 /// <summary>The event subscribers, null of no subscribers</summary>
210 private EventHandler<EventArgs> m_InventoryClipboardUpdated;
212 ///<summary>Raises the InventoryClipboardUpdated Event</summary>
213 /// <param name="e">A EventArgs object containing
214 /// the old and the new client</param>
215 protected virtual void OnInventoryClipboardUpdated(EventArgs e)
217 EventHandler<EventArgs> handler = m_InventoryClipboardUpdated;
222 /// <summary>Thread sync lock object</summary>
223 private readonly object m_InventoryClipboardUpdatedLock = new object();
225 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
226 public event EventHandler<EventArgs> InventoryClipboardUpdated
228 add { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated += value; } }
229 remove { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated -= value; } }
231 #endregion InventoryClipboardUpdated event
236 public RadegastInstance(GridClient client0)
238 // incase something else calls GlobalInstance while we are loading
239 globalInstance = this;
242 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
243 Application.ThreadException += HandleThreadException;
248 // Are we running mono?
249 monoRuntime = Type.GetType("Mono.Runtime") != null;
251 netcom = new RadegastNetcom(this);
252 state = new StateManager(this);
253 mediaManager = new MediaManager(this);
254 commandsManager = new CommandsManager(this);
255 ContextActionManager = new ContextActionsManager(this);
256 movement = new RadegastMovement(this);
258 InitializeLoggingAndConfig();
259 InitializeClient(client);
261 rlv = new RLVManager(this);
263 mainForm = new frmMain(this);
264 mainForm.InitializeControls();
266 mainForm.Load += new EventHandler(mainForm_Load);
267 pluginManager = new PluginManager(this);
268 pluginManager.ScanAndLoadPlugins();
271 private void InitializeClient(GridClient client)
273 client.Settings.MULTIPLE_SIMS = true;
275 client.Settings.USE_INTERPOLATION_TIMER = false;
276 client.Settings.ALWAYS_REQUEST_OBJECTS = true;
277 client.Settings.ALWAYS_DECODE_OBJECTS = true;
278 client.Settings.OBJECT_TRACKING = true;
279 client.Settings.ENABLE_SIMSTATS = true;
280 client.Settings.FETCH_MISSING_INVENTORY = true;
281 client.Settings.SEND_AGENT_THROTTLE = true;
282 client.Settings.SEND_AGENT_UPDATES = true;
284 client.Settings.USE_ASSET_CACHE = true;
285 client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");
286 client.Assets.Cache.AutoPruneEnabled = false;
288 client.Throttle.Total = 5000000f;
289 client.Settings.THROTTLE_OUTGOING_PACKETS = true;
290 client.Settings.LOGIN_TIMEOUT = 120 * 1000;
291 client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;
292 client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;
294 RegisterClientEvents(client);
297 private void RegisterClientEvents(GridClient client)
299 client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
300 client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
301 client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
302 client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
303 client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
305 netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
308 private void UnregisterClientEvents(GridClient client)
310 client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
311 client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
312 client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
313 client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
314 client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
316 netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
319 public void Reconnect()
321 TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);
322 Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);
323 GridClient oldClient = client;
324 client = new GridClient();
325 UnregisterClientEvents(oldClient);
326 InitializeClient(client);
327 OnClientChanged(new ClientChangedEventArgs(oldClient, client));
331 public void CleanUp()
341 UnregisterClientEvents(client);
344 if (pluginManager != null)
346 pluginManager.Dispose();
347 pluginManager = null;
350 if (movement != null)
355 if (commandsManager != null)
357 commandsManager.Dispose();
358 commandsManager = null;
360 if (ContextActionManager != null)
362 ContextActionManager.Dispose();
363 ContextActionManager = null;
365 if (mediaManager != null)
367 mediaManager.Dispose();
380 if (mainForm != null)
382 mainForm.Load -= new EventHandler(mainForm_Load);
384 Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
387 void mainForm_Load(object sender, EventArgs e)
389 pluginManager.StartPlugins();
392 void netcom_ClientConnected(object sender, EventArgs e)
396 if (!Directory.Exists(ClientDir))
397 Directory.CreateDirectory(ClientDir);
401 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
404 clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
408 void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
412 foreach (KeyValuePair<UUID, string> av in e.Names)
414 if (!nameCache.ContainsKey(av.Key))
416 nameCache.Add(av.Key, av.Value);
422 public string getAvatarName(UUID key)
426 if (key == UUID.Zero)
428 return "(???) (???)";
430 if (nameCache.ContainsKey(key))
432 return nameCache[key];
436 client.Avatars.RequestAvatarName(key);
437 return INCOMPLETE_NAME;
442 public void getAvatarNames(List<UUID> keys)
446 List<UUID> newNames = new List<UUID>();
447 foreach (UUID key in keys)
449 if (!nameCache.ContainsKey(key))
454 if (newNames.Count > 0)
456 client.Avatars.RequestAvatarNames(newNames);
461 public bool haveAvatarName(UUID key)
465 if (nameCache.ContainsKey(key))
472 void Groups_GroupsChanged(object sender, EventArgs e)
474 client.Groups.RequestCurrentGroups();
477 public static string SafeFileName(string fileName)
479 foreach (char lDisallowed in Path.GetInvalidFileNameChars())
481 fileName = fileName.Replace(lDisallowed.ToString(), "_");
487 public void LogClientMessage(string fileName, string message)
493 foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
495 fileName = fileName.Replace(lDisallowed.ToString(), "_");
498 File.AppendAllText(Path.Combine(ClientDir, fileName),
499 DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
501 catch (Exception) { }
505 void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
507 this.groups = e.Groups;
510 private void InitializeLoggingAndConfig()
514 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
515 if (!Directory.Exists(userDir))
517 Directory.CreateDirectory(userDir);
522 userDir = System.Environment.CurrentDirectory;
525 globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
526 globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
529 public GridClient Client
531 get { return client; }
534 public RadegastNetcom Netcom
536 get { return netcom; }
539 public StateManager State
541 get { return state; }
544 public frmMain MainForm
546 get { return mainForm; }
549 public TabsConsole TabConsole
551 get { return mainForm.TabConsole; }
554 public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
556 Logger.Log("Unhandled Thread Exception: "
557 + e.Exception.Message + Environment.NewLine
558 + e.Exception.StackTrace + Environment.NewLine,
559 Helpers.LogLevel.Error,
567 #region Event classes
568 public class ClientChangedEventArgs : EventArgs
570 private GridClient m_OldClient;
571 private GridClient m_Client;
573 public GridClient OldClient { get { return m_OldClient; } }
574 public GridClient Client { get { return m_Client; } }
576 public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
578 m_OldClient = OldClient;
582 #endregion Event classes