2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2010, 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);
57 private GridClient client;
58 private RadegastNetcom netcom;
60 private StateManager state;
62 private frmMain mainForm;
64 // Singleton, there can be only one instance
65 private static RadegastInstance globalInstance = null;
66 public static RadegastInstance GlobalInstance
70 if (globalInstance == null)
72 globalInstance = new RadegastInstance(new GridClient());
74 return globalInstance;
78 private string userDir;
80 /// System (not grid!) user's dir
82 public string UserDir { get { return userDir; } }
85 /// Grid client's user dir for settings and logs
87 public string ClientDir
91 if (client != null && client.Self != null && !string.IsNullOrEmpty(client.Self.Name))
93 return Path.Combine(userDir, client.Self.Name);
97 return Environment.CurrentDirectory;
102 public string InventoryCacheFileName { get { return Path.Combine(ClientDir, "inventory.cache"); } }
104 private string globalLogFile;
105 public string GlobalLogFile { get { return globalLogFile; } }
107 private bool monoRuntime;
108 public bool MonoRuntime { get { return monoRuntime; } }
110 private Dictionary<UUID, Group> groups = new Dictionary<UUID, Group>();
111 public Dictionary<UUID, Group> Groups { get { return groups; } }
113 private Settings globalSettings;
115 /// Global settings for the entire application
117 public Settings GlobalSettings { get { return globalSettings; } }
119 private Settings clientSettings;
121 /// Per client settings
123 public Settings ClientSettings { get { return clientSettings; } }
125 public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();
127 public const string INCOMPLETE_NAME = "Loading...";
129 public readonly bool advancedDebugging = false;
131 private PluginManager pluginManager;
132 /// <summary> Handles loading plugins and scripts</summary>
133 public PluginManager PluginManager { get { return pluginManager; } }
135 private MediaManager mediaManager;
137 /// Radegast media manager for playing streams and in world sounds
139 public MediaManager MediaManager { get { return mediaManager; } }
142 private CommandsManager commandsManager;
144 /// Radegast command manager for executing textual console commands
146 public CommandsManager CommandsManager { get { return commandsManager; } }
149 /// Radegast ContextAction manager for context sensitive actions
151 public ContextActionsManager ContextActionManager { get; private set; }
153 private RadegastMovement movement;
155 /// Allows key emulation for moving avatar around
157 public RadegastMovement Movement { get { return movement; } }
159 private InventoryClipboard inventoryClipboard;
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
165 public InventoryClipboard InventoryClipboard
167 get { return inventoryClipboard; }
170 inventoryClipboard = value;
171 OnInventoryClipboardUpdated(EventArgs.Empty);
175 private RLVManager rlv;
178 /// Manager for RLV functionality
180 public RLVManager RLV { get { return rlv; } }
182 private GridManager gridManager;
183 /// <summary>Manages default params for different grids</summary>
184 public GridManager GridManger { get { return gridManager; } }
188 #region ClientChanged event
189 /// <summary>The event subscribers, null of no subscribers</summary>
190 private EventHandler<ClientChangedEventArgs> m_ClientChanged;
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)
197 EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;
202 /// <summary>Thread sync lock object</summary>
203 private readonly object m_ClientChangedLock = new object();
205 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
206 public event EventHandler<ClientChangedEventArgs> ClientChanged
208 add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }
209 remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }
211 #endregion ClientChanged event
213 #region InventoryClipboardUpdated event
214 /// <summary>The event subscribers, null of no subscribers</summary>
215 private EventHandler<EventArgs> m_InventoryClipboardUpdated;
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)
222 EventHandler<EventArgs> handler = m_InventoryClipboardUpdated;
227 /// <summary>Thread sync lock object</summary>
228 private readonly object m_InventoryClipboardUpdatedLock = new object();
230 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
231 public event EventHandler<EventArgs> InventoryClipboardUpdated
233 add { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated += value; } }
234 remove { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated -= value; } }
236 #endregion InventoryClipboardUpdated event
241 public RadegastInstance(GridClient client0)
243 // incase something else calls GlobalInstance while we are loading
244 globalInstance = this;
247 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
248 Application.ThreadException += HandleThreadException;
253 // Are we running mono?
254 monoRuntime = Type.GetType("Mono.Runtime") != null;
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);
263 InitializeLoggingAndConfig();
264 InitializeClient(client);
266 rlv = new RLVManager(this);
267 gridManager = new GridManager(this);
268 gridManager.LoadGrids();
270 mainForm = new frmMain(this);
271 mainForm.InitializeControls();
273 mainForm.Load += new EventHandler(mainForm_Load);
274 pluginManager = new PluginManager(this);
275 pluginManager.ScanAndLoadPlugins();
278 private void InitializeClient(GridClient client)
280 client.Settings.MULTIPLE_SIMS = true;
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;
291 client.Settings.USE_ASSET_CACHE = true;
292 client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");
293 client.Assets.Cache.AutoPruneEnabled = false;
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;
301 RegisterClientEvents(client);
304 private void RegisterClientEvents(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);
312 netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
315 private void UnregisterClientEvents(GridClient client)
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);
323 netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
326 public void Reconnect()
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));
338 public void CleanUp()
340 if (gridManager != null)
342 gridManager.Dispose();
354 UnregisterClientEvents(client);
357 if (pluginManager != null)
359 pluginManager.Dispose();
360 pluginManager = null;
363 if (movement != null)
368 if (commandsManager != null)
370 commandsManager.Dispose();
371 commandsManager = null;
373 if (ContextActionManager != null)
375 ContextActionManager.Dispose();
376 ContextActionManager = null;
378 if (mediaManager != null)
380 mediaManager.Dispose();
393 if (mainForm != null)
395 mainForm.Load -= new EventHandler(mainForm_Load);
397 Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
400 void mainForm_Load(object sender, EventArgs e)
402 pluginManager.StartPlugins();
405 void netcom_ClientConnected(object sender, EventArgs e)
409 if (!Directory.Exists(ClientDir))
410 Directory.CreateDirectory(ClientDir);
414 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
417 clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
421 void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
425 foreach (KeyValuePair<UUID, string> av in e.Names)
427 if (!nameCache.ContainsKey(av.Key))
429 nameCache.Add(av.Key, av.Value);
436 /// Fetches avatar name
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)
444 return getAvatarName(key);
448 using (ManualResetEvent gotName = new ManualResetEvent(false))
451 EventHandler<UUIDNameReplyEventArgs> handler = (object sender, UUIDNameReplyEventArgs e) =>
453 if (e.Names.ContainsKey(key))
460 client.Avatars.UUIDNameReply += handler;
461 name = getAvatarName(key);
463 if (name == INCOMPLETE_NAME)
465 gotName.WaitOne(10 * 1000, false);
468 client.Avatars.UUIDNameReply -= handler;
475 /// Fetches avatar name from cache, if not in cache will requst name from the server
477 /// <param name="key">Avatar UUID</param>
478 /// <returns>Avatar name</returns>
479 public string getAvatarName(UUID key)
483 if (key == UUID.Zero)
485 return "(???) (???)";
487 if (nameCache.ContainsKey(key))
489 return nameCache[key];
493 client.Avatars.RequestAvatarName(key);
494 return INCOMPLETE_NAME;
499 public void getAvatarNames(List<UUID> keys)
503 List<UUID> newNames = new List<UUID>();
504 foreach (UUID key in keys)
506 if (!nameCache.ContainsKey(key))
511 if (newNames.Count > 0)
513 client.Avatars.RequestAvatarNames(newNames);
518 public bool haveAvatarName(UUID key)
522 if (nameCache.ContainsKey(key))
529 void Groups_GroupsChanged(object sender, EventArgs e)
531 client.Groups.RequestCurrentGroups();
534 public static string SafeFileName(string fileName)
536 foreach (char lDisallowed in Path.GetInvalidFileNameChars())
538 fileName = fileName.Replace(lDisallowed.ToString(), "_");
544 public void LogClientMessage(string fileName, string message)
550 foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
552 fileName = fileName.Replace(lDisallowed.ToString(), "_");
555 File.AppendAllText(Path.Combine(ClientDir, fileName),
556 DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
558 catch (Exception) { }
562 void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
564 this.groups = e.Groups;
567 private void InitializeLoggingAndConfig()
571 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
572 if (!Directory.Exists(userDir))
574 Directory.CreateDirectory(userDir);
579 userDir = System.Environment.CurrentDirectory;
582 globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
583 globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
586 public GridClient Client
588 get { return client; }
591 public RadegastNetcom Netcom
593 get { return netcom; }
596 public StateManager State
598 get { return state; }
601 public frmMain MainForm
603 get { return mainForm; }
606 public TabsConsole TabConsole
608 get { return mainForm.TabConsole; }
611 public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
613 Logger.Log("Unhandled Thread Exception: "
614 + e.Exception.Message + Environment.NewLine
615 + e.Exception.StackTrace + Environment.NewLine,
616 Helpers.LogLevel.Error,
624 #region Event classes
625 public class ClientChangedEventArgs : EventArgs
627 private GridClient m_OldClient;
628 private GridClient m_Client;
630 public GridClient OldClient { get { return m_OldClient; } }
631 public GridClient Client { get { return m_Client; } }
633 public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
635 m_OldClient = OldClient;
639 #endregion Event classes