2 // Radegast Metaverse Client
\r
3 // Copyright (c) 2009, Radegast Development Team
\r
4 // All rights reserved.
\r
6 // Redistribution and use in source and binary forms, with or without
\r
7 // modification, are permitted provided that the following conditions are met:
\r
9 // * Redistributions of source code must retain the above copyright notice,
\r
10 // this list of conditions and the following disclaimer.
\r
11 // * Redistributions in binary form must reproduce the above copyright
\r
12 // notice, this list of conditions and the following disclaimer in the
\r
13 // documentation and/or other materials provided with the distribution.
\r
14 // * Neither the name of the application "Radegast", nor the names of its
\r
15 // contributors may be used to endorse or promote products derived from
\r
16 // this software without specific prior written permission.
\r
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
\r
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
\r
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
\r
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
\r
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
\r
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
\r
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
\r
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
\r
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
\r
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\r
32 using System.Collections.Generic;
\r
34 using System.Reflection;
\r
35 using System.Threading;
\r
36 using System.Windows.Forms;
\r
37 using Radegast.Commands;
\r
38 using Radegast.Netcom;
\r
39 using Radegast.Media;
\r
40 using OpenMetaverse;
\r
44 public class RadegastInstance
\r
46 #region OnRadegastFormCreated
\r
47 public event Action<RadegastForm> RadegastFormCreated;
\r
49 /// Triggers the RadegastFormCreated event.
\r
51 public virtual void OnRadegastFormCreated(RadegastForm radForm)
\r
53 if (RadegastFormCreated != null) RadegastFormCreated(radForm);
\r
56 private GridClient client;
\r
57 private RadegastNetcom netcom;
\r
59 private StateManager state;
\r
61 private frmMain mainForm;
\r
63 // Singleton, there can be only one instance
\r
64 private static RadegastInstance globalInstance = null;
\r
65 public static RadegastInstance GlobalInstance
\r
69 if (globalInstance == null)
\r
71 globalInstance = new RadegastInstance(new GridClient());
\r
73 return globalInstance;
\r
77 private string userDir;
\r
79 /// System (not grid!) user's dir
\r
81 public string UserDir { get { return userDir; } }
\r
84 /// Grid client's user dir for settings and logs
\r
86 public string ClientDir
\r
90 if (client != null && client.Self != null && !string.IsNullOrEmpty(client.Self.Name))
\r
92 return Path.Combine(userDir, client.Self.Name);
\r
96 return Environment.CurrentDirectory;
\r
101 public string InventoryCacheFileName { get { return Path.Combine(ClientDir, "inventory.cache"); } }
\r
103 private string globalLogFile;
\r
104 public string GlobalLogFile { get { return globalLogFile; } }
\r
106 private bool monoRuntime;
\r
107 public bool MonoRuntime { get { return monoRuntime; } }
\r
109 private Dictionary<UUID, Group> groups = new Dictionary<UUID, Group>();
\r
110 public Dictionary<UUID, Group> Groups { get { return groups; } }
\r
112 private Settings globalSettings;
\r
114 /// Global settings for the entire application
\r
116 public Settings GlobalSettings { get { return globalSettings; } }
\r
118 private Settings clientSettings;
\r
120 /// Per client settings
\r
122 public Settings ClientSettings { get { return clientSettings; } }
\r
124 public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();
\r
126 public const string INCOMPLETE_NAME = "Loading...";
\r
128 public readonly bool advancedDebugging = false;
\r
130 public readonly List<IRadegastPlugin> PluginsLoaded = new List<IRadegastPlugin>();
\r
132 private MediaManager mediaManager;
\r
134 /// Radegast media manager for playing streams and in world sounds
\r
136 public MediaManager MediaManager { get { return mediaManager; } }
\r
139 private CommandsManager commandsManager;
\r
141 /// Radegast command manager for executing textual console commands
\r
143 public CommandsManager CommandsManager { get { return commandsManager; } }
\r
146 /// Radegast ContextAction manager for context sensitive actions
\r
148 public ContextActionsManager ContextActionManager { get; private set; }
\r
150 private RadegastMovement movement;
\r
152 /// Allows key emulation for moving avatar around
\r
154 public RadegastMovement Movement { get { return movement; } }
\r
157 /// <summary>The event subscribers, null of no subscribers</summary>
\r
158 private EventHandler<ClientChangedEventArgs> m_ClientChanged;
\r
160 ///<summary>Raises the ClientChanged Event</summary>
\r
161 /// <param name="e">A ClientChangedEventArgs object containing
\r
162 /// the old and the new client</param>
\r
163 protected virtual void OnClientChanged(ClientChangedEventArgs e)
\r
165 EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;
\r
166 if (handler != null)
\r
170 /// <summary>Thread sync lock object</summary>
\r
171 private readonly object m_ClientChangedLock = new object();
\r
173 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
\r
174 public event EventHandler<ClientChangedEventArgs> ClientChanged
\r
176 add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }
\r
177 remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }
\r
181 public RadegastInstance(GridClient client0)
\r
183 // incase something else calls GlobalInstance while we are loading
\r
184 globalInstance = this;
\r
186 #if HANDLE_THREAD_EXCEPTIONS
\r
187 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
\r
188 Application.ThreadException += HandleThreadException;
\r
193 // Are we running mono?
\r
194 monoRuntime = Type.GetType("Mono.Runtime") != null;
\r
196 netcom = new RadegastNetcom(this);
\r
197 state = new StateManager(this);
\r
198 mediaManager = new MediaManager(this);
\r
199 commandsManager = new CommandsManager(this);
\r
200 ContextActionManager = new ContextActionsManager(this);
\r
201 movement = new RadegastMovement(this);
\r
203 InitializeLoggingAndConfig();
\r
204 InitializeClient(client);
\r
206 mainForm = new frmMain(this);
\r
207 mainForm.InitializeControls();
\r
209 mainForm.Load += new EventHandler(mainForm_Load);
\r
210 ScanAndLoadPlugins();
\r
213 private void InitializeClient(GridClient client)
\r
215 client.Settings.MULTIPLE_SIMS = true;
\r
217 client.Settings.USE_INTERPOLATION_TIMER = false;
\r
218 client.Settings.ALWAYS_REQUEST_OBJECTS = true;
\r
219 client.Settings.ALWAYS_DECODE_OBJECTS = true;
\r
220 client.Settings.OBJECT_TRACKING = true;
\r
221 client.Settings.ENABLE_SIMSTATS = true;
\r
222 client.Settings.FETCH_MISSING_INVENTORY = true;
\r
223 client.Settings.SEND_AGENT_THROTTLE = true;
\r
224 client.Settings.SEND_AGENT_UPDATES = true;
\r
226 client.Settings.USE_ASSET_CACHE = true;
\r
227 client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");
\r
228 client.Assets.Cache.AutoPruneEnabled = false;
\r
230 client.Throttle.Total = 5000000f;
\r
231 client.Settings.THROTTLE_OUTGOING_PACKETS = true;
\r
232 client.Settings.LOGIN_TIMEOUT = 120 * 1000;
\r
233 client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;
\r
234 client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;
\r
236 RegisterClientEvents(client);
\r
239 private void RegisterClientEvents(GridClient client)
\r
241 client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
\r
242 client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
\r
243 client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
\r
244 client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
\r
245 client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
\r
246 netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
\r
249 private void UnregisterClientEvents(GridClient client)
\r
251 client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
\r
252 client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
\r
253 client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
\r
254 client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
\r
255 client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
\r
256 netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
\r
259 public void Reconnect()
\r
261 TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);
\r
262 Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);
\r
263 GridClient oldClient = client;
\r
264 client = new GridClient();
\r
265 UnregisterClientEvents(oldClient);
\r
266 InitializeClient(client);
\r
267 OnClientChanged(new ClientChangedEventArgs(oldClient, client));
\r
271 public void CleanUp()
\r
273 if (client != null)
\r
275 UnregisterClientEvents(client);
\r
278 lock (PluginsLoaded)
\r
280 List<IRadegastPlugin> unload = new List<IRadegastPlugin>(PluginsLoaded);
\r
281 unload.ForEach(plug =>
\r
283 PluginsLoaded.Remove(plug);
\r
286 plug.StopPlugin(this);
\r
288 catch (Exception ex)
\r
290 Logger.Log("ERROR in Shutdown Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug, ex);
\r
295 if (movement != null)
\r
297 movement.Dispose();
\r
300 if (commandsManager != null)
\r
302 commandsManager.Dispose();
\r
303 commandsManager = null;
\r
305 if (ContextActionManager != null)
\r
307 ContextActionManager.Dispose();
\r
308 ContextActionManager = null;
\r
310 if (mediaManager != null)
\r
312 mediaManager.Dispose();
\r
313 mediaManager = null;
\r
320 if (netcom != null)
\r
325 if (mainForm != null)
\r
327 mainForm.Load -= new EventHandler(mainForm_Load);
\r
329 Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
\r
332 void mainForm_Load(object sender, EventArgs e)
\r
337 private void StartPlugins()
\r
339 lock (PluginsLoaded)
\r
341 foreach (IRadegastPlugin plug in PluginsLoaded)
\r
345 plug.StartPlugin(this);
\r
347 catch (Exception ex)
\r
349 Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);
\r
355 private void ScanAndLoadPlugins()
\r
357 string dirName = Application.StartupPath;
\r
359 if (!Directory.Exists(dirName)) return;
\r
361 foreach (string loadfilename in Directory.GetFiles(dirName))
\r
363 if (loadfilename.ToLower().EndsWith(".dll") || loadfilename.ToLower().EndsWith(".exe"))
\r
367 Assembly assembly = Assembly.LoadFile(loadfilename);
\r
368 LoadAssembly(loadfilename, assembly);
\r
370 catch (BadImageFormatException)
\r
374 catch (ReflectionTypeLoadException)
\r
376 // Out of date or dlls missing sub dependencies
\r
378 catch (TypeLoadException)
\r
380 // Another version of: Out of date or dlls missing sub dependencies
\r
382 catch (Exception ex)
\r
384 Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " because " + ex, Helpers.LogLevel.Debug);
\r
390 public void LoadAssembly(string loadfilename, Assembly assembly)
\r
392 foreach (Type type in assembly.GetTypes())
\r
394 if (typeof(IRadegastPlugin).IsAssignableFrom(type))
\r
396 if (type.IsInterface) continue;
\r
399 IRadegastPlugin plug;
\r
400 ConstructorInfo constructorInfo = type.GetConstructor(new Type[] {typeof (RadegastInstance)});
\r
401 if (constructorInfo != null)
\r
402 plug = (IRadegastPlugin) constructorInfo.Invoke(new[] {this});
\r
405 constructorInfo = type.GetConstructor(new Type[] {});
\r
406 if (constructorInfo != null)
\r
407 plug = (IRadegastPlugin) constructorInfo.Invoke(new object[0]);
\r
410 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because "+type+ " has no usable constructor.",Helpers.LogLevel.Debug);
\r
414 lock (PluginsLoaded) PluginsLoaded.Add(plug);
\r
416 catch (Exception ex)
\r
418 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex,
\r
419 Helpers.LogLevel.Debug);
\r
426 commandsManager.LoadType(type);
\r
428 catch (Exception ex)
\r
430 Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type +
\r
431 " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug);
\r
437 void netcom_ClientConnected(object sender, EventArgs e)
\r
441 if (!Directory.Exists(ClientDir))
\r
442 Directory.CreateDirectory(ClientDir);
\r
444 catch (Exception ex)
\r
446 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
\r
449 clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
\r
453 void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
\r
457 foreach (KeyValuePair<UUID, string> av in e.Names)
\r
459 if (!nameCache.ContainsKey(av.Key))
\r
461 nameCache.Add(av.Key, av.Value);
\r
467 public string getAvatarName(UUID key)
\r
471 if (key == UUID.Zero)
\r
473 return "(???) (???)";
\r
475 if (nameCache.ContainsKey(key))
\r
477 return nameCache[key];
\r
481 client.Avatars.RequestAvatarName(key);
\r
482 return INCOMPLETE_NAME;
\r
487 public void getAvatarNames(List<UUID> keys)
\r
491 List<UUID> newNames = new List<UUID>();
\r
492 foreach (UUID key in keys)
\r
494 if (!nameCache.ContainsKey(key))
\r
499 if (newNames.Count > 0)
\r
501 client.Avatars.RequestAvatarNames(newNames);
\r
506 public bool haveAvatarName(UUID key)
\r
510 if (nameCache.ContainsKey(key))
\r
517 void Groups_GroupsChanged(object sender, EventArgs e)
\r
519 client.Groups.RequestCurrentGroups();
\r
522 public static string SafeFileName(string fileName)
\r
524 foreach (char lDisallowed in Path.GetInvalidFileNameChars())
\r
526 fileName = fileName.Replace(lDisallowed.ToString(), "_");
\r
532 public void LogClientMessage(string fileName, string message)
\r
538 foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
\r
540 fileName = fileName.Replace(lDisallowed.ToString(), "_");
\r
543 File.AppendAllText(Path.Combine(ClientDir, fileName),
\r
544 DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
\r
546 catch (Exception) { }
\r
550 void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
\r
552 this.groups = e.Groups;
\r
555 private void InitializeLoggingAndConfig()
\r
559 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
\r
560 if (!Directory.Exists(userDir))
\r
562 Directory.CreateDirectory(userDir);
\r
567 userDir = System.Environment.CurrentDirectory;
\r
570 globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
\r
571 globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
\r
574 public GridClient Client
\r
576 get { return client; }
\r
579 public RadegastNetcom Netcom
\r
581 get { return netcom; }
\r
584 public StateManager State
\r
586 get { return state; }
\r
589 public frmMain MainForm
\r
591 get { return mainForm; }
\r
594 public TabsConsole TabConsole
\r
596 get { return mainForm.TabConsole; }
\r
599 public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
\r
601 Logger.Log("Unhandled Thread Exception: "
\r
602 + e.Exception.Message + Environment.NewLine
\r
603 + e.Exception.StackTrace + Environment.NewLine,
\r
604 Helpers.LogLevel.Error,
\r
607 Application.Exit();
\r
611 #region Event classes
\r
612 public class ClientChangedEventArgs : EventArgs
\r
614 private GridClient m_OldClient;
\r
615 private GridClient m_Client;
\r
617 public GridClient OldClient { get { return m_OldClient; } }
\r
618 public GridClient Client { get { return m_Client; } }
\r
620 public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
\r
622 m_OldClient = OldClient;
\r
626 #endregion Event classes
\r