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; } }
181 private GridManager gridManager;
182 /// <summary>Manages default params for different grids</summary>
183 public GridManager GridManger { get { return gridManager; } }
187 #region ClientChanged event
188 /// <summary>The event subscribers, null of no subscribers</summary>
189 private EventHandler<ClientChangedEventArgs> m_ClientChanged;
191 ///<summary>Raises the ClientChanged Event</summary>
192 /// <param name="e">A ClientChangedEventArgs object containing
193 /// the old and the new client</param>
194 protected virtual void OnClientChanged(ClientChangedEventArgs e)
196 EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;
201 /// <summary>Thread sync lock object</summary>
202 private readonly object m_ClientChangedLock = new object();
204 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
205 public event EventHandler<ClientChangedEventArgs> ClientChanged
207 add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }
208 remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }
210 #endregion ClientChanged event
212 #region InventoryClipboardUpdated event
213 /// <summary>The event subscribers, null of no subscribers</summary>
214 private EventHandler<EventArgs> m_InventoryClipboardUpdated;
216 ///<summary>Raises the InventoryClipboardUpdated Event</summary>
217 /// <param name="e">A EventArgs object containing
218 /// the old and the new client</param>
219 protected virtual void OnInventoryClipboardUpdated(EventArgs e)
221 EventHandler<EventArgs> handler = m_InventoryClipboardUpdated;
226 /// <summary>Thread sync lock object</summary>
227 private readonly object m_InventoryClipboardUpdatedLock = new object();
229 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
230 public event EventHandler<EventArgs> InventoryClipboardUpdated
232 add { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated += value; } }
233 remove { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated -= value; } }
235 #endregion InventoryClipboardUpdated event
240 public RadegastInstance(GridClient client0)
242 // incase something else calls GlobalInstance while we are loading
243 globalInstance = this;
246 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
247 Application.ThreadException += HandleThreadException;
252 // Are we running mono?
253 monoRuntime = Type.GetType("Mono.Runtime") != null;
255 netcom = new RadegastNetcom(this);
256 state = new StateManager(this);
257 mediaManager = new MediaManager(this);
258 commandsManager = new CommandsManager(this);
259 ContextActionManager = new ContextActionsManager(this);
260 movement = new RadegastMovement(this);
262 InitializeLoggingAndConfig();
263 InitializeClient(client);
265 rlv = new RLVManager(this);
266 gridManager = new GridManager(this);
267 gridManager.LoadGrids();
269 mainForm = new frmMain(this);
270 mainForm.InitializeControls();
272 mainForm.Load += new EventHandler(mainForm_Load);
273 pluginManager = new PluginManager(this);
274 pluginManager.ScanAndLoadPlugins();
277 private void InitializeClient(GridClient client)
279 client.Settings.MULTIPLE_SIMS = true;
281 client.Settings.USE_INTERPOLATION_TIMER = false;
282 client.Settings.ALWAYS_REQUEST_OBJECTS = true;
283 client.Settings.ALWAYS_DECODE_OBJECTS = true;
284 client.Settings.OBJECT_TRACKING = true;
285 client.Settings.ENABLE_SIMSTATS = true;
286 client.Settings.FETCH_MISSING_INVENTORY = true;
287 client.Settings.SEND_AGENT_THROTTLE = true;
288 client.Settings.SEND_AGENT_UPDATES = true;
290 client.Settings.USE_ASSET_CACHE = true;
291 client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");
292 client.Assets.Cache.AutoPruneEnabled = false;
294 client.Throttle.Total = 5000000f;
295 client.Settings.THROTTLE_OUTGOING_PACKETS = true;
296 client.Settings.LOGIN_TIMEOUT = 120 * 1000;
297 client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;
298 client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;
300 RegisterClientEvents(client);
303 private void RegisterClientEvents(GridClient client)
305 client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
306 client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
307 client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
308 client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
309 client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
311 netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
314 private void UnregisterClientEvents(GridClient client)
316 client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
317 client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
318 client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
319 client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
320 client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
322 netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
325 public void Reconnect()
327 TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);
328 Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);
329 GridClient oldClient = client;
330 client = new GridClient();
331 UnregisterClientEvents(oldClient);
332 InitializeClient(client);
333 OnClientChanged(new ClientChangedEventArgs(oldClient, client));
337 public void CleanUp()
339 if (gridManager != null)
341 gridManager.Dispose();
353 UnregisterClientEvents(client);
356 if (pluginManager != null)
358 pluginManager.Dispose();
359 pluginManager = null;
362 if (movement != null)
367 if (commandsManager != null)
369 commandsManager.Dispose();
370 commandsManager = null;
372 if (ContextActionManager != null)
374 ContextActionManager.Dispose();
375 ContextActionManager = null;
377 if (mediaManager != null)
379 mediaManager.Dispose();
392 if (mainForm != null)
394 mainForm.Load -= new EventHandler(mainForm_Load);
396 Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
399 void mainForm_Load(object sender, EventArgs e)
401 pluginManager.StartPlugins();
404 void netcom_ClientConnected(object sender, EventArgs e)
408 if (!Directory.Exists(ClientDir))
409 Directory.CreateDirectory(ClientDir);
413 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
416 clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
420 void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
424 foreach (KeyValuePair<UUID, string> av in e.Names)
426 if (!nameCache.ContainsKey(av.Key))
428 nameCache.Add(av.Key, av.Value);
435 /// Fetches avatar name
437 /// <param name="key">Avatar UUID</param>
438 /// <param name="blocking">Should we wait until the name is retrieved</param>
439 /// <returns>Avatar name</returns>
440 public string getAvatarName(UUID key, bool blocking)
443 return getAvatarName(key);
447 using (ManualResetEvent gotName = new ManualResetEvent(false))
450 EventHandler<UUIDNameReplyEventArgs> handler = (object sender, UUIDNameReplyEventArgs e) =>
452 if (e.Names.ContainsKey(key))
459 client.Avatars.UUIDNameReply += handler;
460 name = getAvatarName(key);
462 if (name == INCOMPLETE_NAME)
464 gotName.WaitOne(10 * 1000);
467 client.Avatars.UUIDNameReply -= handler;
474 /// Fetches avatar name from cache, if not in cache will requst name from the server
476 /// <param name="key">Avatar UUID</param>
477 /// <returns>Avatar name</returns>
478 public string getAvatarName(UUID key)
482 if (key == UUID.Zero)
484 return "(???) (???)";
486 if (nameCache.ContainsKey(key))
488 return nameCache[key];
492 client.Avatars.RequestAvatarName(key);
493 return INCOMPLETE_NAME;
498 public void getAvatarNames(List<UUID> keys)
502 List<UUID> newNames = new List<UUID>();
503 foreach (UUID key in keys)
505 if (!nameCache.ContainsKey(key))
510 if (newNames.Count > 0)
512 client.Avatars.RequestAvatarNames(newNames);
517 public bool haveAvatarName(UUID key)
521 if (nameCache.ContainsKey(key))
528 void Groups_GroupsChanged(object sender, EventArgs e)
530 client.Groups.RequestCurrentGroups();
533 public static string SafeFileName(string fileName)
535 foreach (char lDisallowed in Path.GetInvalidFileNameChars())
537 fileName = fileName.Replace(lDisallowed.ToString(), "_");
543 public void LogClientMessage(string fileName, string message)
549 foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
551 fileName = fileName.Replace(lDisallowed.ToString(), "_");
554 File.AppendAllText(Path.Combine(ClientDir, fileName),
555 DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
557 catch (Exception) { }
561 void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
563 this.groups = e.Groups;
566 private void InitializeLoggingAndConfig()
570 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
571 if (!Directory.Exists(userDir))
573 Directory.CreateDirectory(userDir);
578 userDir = System.Environment.CurrentDirectory;
581 globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
582 globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
585 public GridClient Client
587 get { return client; }
590 public RadegastNetcom Netcom
592 get { return netcom; }
595 public StateManager State
597 get { return state; }
600 public frmMain MainForm
602 get { return mainForm; }
605 public TabsConsole TabConsole
607 get { return mainForm.TabConsole; }
610 public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
612 Logger.Log("Unhandled Thread Exception: "
613 + e.Exception.Message + Environment.NewLine
614 + e.Exception.StackTrace + Environment.NewLine,
615 Helpers.LogLevel.Error,
623 #region Event classes
624 public class ClientChangedEventArgs : EventArgs
626 private GridClient m_OldClient;
627 private GridClient m_Client;
629 public GridClient OldClient { get { return m_OldClient; } }
630 public GridClient Client { get { return m_Client; } }
632 public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
634 m_OldClient = OldClient;
638 #endregion Event classes