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);
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;
79 /// Processed command line options
81 public CommandLine CommandLine { get; set; }
83 private string userDir;
85 /// System (not grid!) user's dir
87 public string UserDir { get { return userDir; } }
90 /// Grid client's user dir for settings and logs
92 public string ClientDir
96 if (client != null && client.Self != null && !string.IsNullOrEmpty(client.Self.Name))
98 return Path.Combine(userDir, client.Self.Name);
102 return Environment.CurrentDirectory;
107 public string InventoryCacheFileName { get { return Path.Combine(ClientDir, "inventory.cache"); } }
109 private string globalLogFile;
110 public string GlobalLogFile { get { return globalLogFile; } }
112 private bool monoRuntime;
113 public bool MonoRuntime { get { return monoRuntime; } }
115 private Dictionary<UUID, Group> groups = new Dictionary<UUID, Group>();
116 public Dictionary<UUID, Group> Groups { get { return groups; } }
118 private Settings globalSettings;
120 /// Global settings for the entire application
122 public Settings GlobalSettings { get { return globalSettings; } }
124 private Settings clientSettings;
126 /// Per client settings
128 public Settings ClientSettings { get { return clientSettings; } }
130 public Dictionary<UUID, string> nameCache = new Dictionary<UUID, string>();
132 public const string INCOMPLETE_NAME = "Loading...";
134 public readonly bool advancedDebugging = false;
136 private PluginManager pluginManager;
137 /// <summary> Handles loading plugins and scripts</summary>
138 public PluginManager PluginManager { get { return pluginManager; } }
140 private MediaManager mediaManager;
142 /// Radegast media manager for playing streams and in world sounds
144 public MediaManager MediaManager { get { return mediaManager; } }
147 private CommandsManager commandsManager;
149 /// Radegast command manager for executing textual console commands
151 public CommandsManager CommandsManager { get { return commandsManager; } }
154 /// Radegast ContextAction manager for context sensitive actions
156 public ContextActionsManager ContextActionManager { get; private set; }
158 private RadegastMovement movement;
160 /// Allows key emulation for moving avatar around
162 public RadegastMovement Movement { get { return movement; } }
164 private InventoryClipboard inventoryClipboard;
166 /// The last item that was cut or copied in the inventory, used for pasting
167 /// in a different place on the inventory, or other places like profile
168 /// that allow sending copied inventory items
170 public InventoryClipboard InventoryClipboard
172 get { return inventoryClipboard; }
175 inventoryClipboard = value;
176 OnInventoryClipboardUpdated(EventArgs.Empty);
180 private RLVManager rlv;
183 /// Manager for RLV functionality
185 public RLVManager RLV { get { return rlv; } }
187 private GridManager gridManager;
188 /// <summary>Manages default params for different grids</summary>
189 public GridManager GridManger { get { return gridManager; } }
193 #region ClientChanged event
194 /// <summary>The event subscribers, null of no subscribers</summary>
195 private EventHandler<ClientChangedEventArgs> m_ClientChanged;
197 ///<summary>Raises the ClientChanged Event</summary>
198 /// <param name="e">A ClientChangedEventArgs object containing
199 /// the old and the new client</param>
200 protected virtual void OnClientChanged(ClientChangedEventArgs e)
202 EventHandler<ClientChangedEventArgs> handler = m_ClientChanged;
207 /// <summary>Thread sync lock object</summary>
208 private readonly object m_ClientChangedLock = new object();
210 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
211 public event EventHandler<ClientChangedEventArgs> ClientChanged
213 add { lock (m_ClientChangedLock) { m_ClientChanged += value; } }
214 remove { lock (m_ClientChangedLock) { m_ClientChanged -= value; } }
216 #endregion ClientChanged event
218 #region InventoryClipboardUpdated event
219 /// <summary>The event subscribers, null of no subscribers</summary>
220 private EventHandler<EventArgs> m_InventoryClipboardUpdated;
222 ///<summary>Raises the InventoryClipboardUpdated Event</summary>
223 /// <param name="e">A EventArgs object containing
224 /// the old and the new client</param>
225 protected virtual void OnInventoryClipboardUpdated(EventArgs e)
227 EventHandler<EventArgs> handler = m_InventoryClipboardUpdated;
232 /// <summary>Thread sync lock object</summary>
233 private readonly object m_InventoryClipboardUpdatedLock = new object();
235 /// <summary>Raised when the GridClient object in the main Radegast instance is changed</summary>
236 public event EventHandler<EventArgs> InventoryClipboardUpdated
238 add { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated += value; } }
239 remove { lock (m_InventoryClipboardUpdatedLock) { m_InventoryClipboardUpdated -= value; } }
241 #endregion InventoryClipboardUpdated event
246 public RadegastInstance(GridClient client0)
248 // incase something else calls GlobalInstance while we are loading
249 globalInstance = this;
252 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
253 Application.ThreadException += HandleThreadException;
258 // Are we running mono?
259 monoRuntime = Type.GetType("Mono.Runtime") != null;
261 netcom = new RadegastNetcom(this);
262 state = new StateManager(this);
263 mediaManager = new MediaManager(this);
264 commandsManager = new CommandsManager(this);
265 ContextActionManager = new ContextActionsManager(this);
266 movement = new RadegastMovement(this);
268 InitializeLoggingAndConfig();
269 InitializeClient(client);
271 rlv = new RLVManager(this);
272 gridManager = new GridManager(this);
273 gridManager.LoadGrids();
275 mainForm = new frmMain(this);
276 mainForm.InitializeControls();
278 mainForm.Load += new EventHandler(mainForm_Load);
279 pluginManager = new PluginManager(this);
280 pluginManager.ScanAndLoadPlugins();
283 private void InitializeClient(GridClient client)
285 client.Settings.MULTIPLE_SIMS = true;
287 client.Settings.USE_INTERPOLATION_TIMER = false;
288 client.Settings.ALWAYS_REQUEST_OBJECTS = true;
289 client.Settings.ALWAYS_DECODE_OBJECTS = true;
290 client.Settings.OBJECT_TRACKING = true;
291 client.Settings.ENABLE_SIMSTATS = true;
292 client.Settings.FETCH_MISSING_INVENTORY = true;
293 client.Settings.SEND_AGENT_THROTTLE = true;
294 client.Settings.SEND_AGENT_UPDATES = true;
296 client.Settings.USE_ASSET_CACHE = true;
297 client.Settings.ASSET_CACHE_DIR = Path.Combine(userDir, "cache");
298 client.Assets.Cache.AutoPruneEnabled = false;
300 client.Throttle.Total = 5000000f;
301 client.Settings.THROTTLE_OUTGOING_PACKETS = true;
302 client.Settings.LOGIN_TIMEOUT = 120 * 1000;
303 client.Settings.SIMULATOR_TIMEOUT = 120 * 1000;
304 client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS = 20;
306 RegisterClientEvents(client);
309 private void RegisterClientEvents(GridClient client)
311 client.Groups.CurrentGroups += new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
312 client.Groups.GroupLeaveReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
313 client.Groups.GroupDropped += new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
314 client.Groups.GroupJoinedReply += new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
315 client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
317 netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
320 private void UnregisterClientEvents(GridClient client)
322 client.Groups.CurrentGroups -= new EventHandler<CurrentGroupsEventArgs>(Groups_CurrentGroups);
323 client.Groups.GroupLeaveReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
324 client.Groups.GroupDropped -= new EventHandler<GroupDroppedEventArgs>(Groups_GroupsChanged);
325 client.Groups.GroupJoinedReply -= new EventHandler<GroupOperationEventArgs>(Groups_GroupsChanged);
326 client.Avatars.UUIDNameReply -= new EventHandler<UUIDNameReplyEventArgs>(Avatars_UUIDNameReply);
328 netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
331 public void Reconnect()
333 TabConsole.DisplayNotificationInChat("Attempting to reconnect...", ChatBufferTextStyle.StatusDarkBlue);
334 Logger.Log("Attemting to reconnect", Helpers.LogLevel.Info, client);
335 GridClient oldClient = client;
336 client = new GridClient();
337 UnregisterClientEvents(oldClient);
338 InitializeClient(client);
339 OnClientChanged(new ClientChangedEventArgs(oldClient, client));
343 public void CleanUp()
345 if (gridManager != null)
347 gridManager.Dispose();
359 UnregisterClientEvents(client);
362 if (pluginManager != null)
364 pluginManager.Dispose();
365 pluginManager = null;
368 if (movement != null)
373 if (commandsManager != null)
375 commandsManager.Dispose();
376 commandsManager = null;
378 if (ContextActionManager != null)
380 ContextActionManager.Dispose();
381 ContextActionManager = null;
383 if (mediaManager != null)
385 mediaManager.Dispose();
398 if (mainForm != null)
400 mainForm.Load -= new EventHandler(mainForm_Load);
402 Logger.Log("RadegastInstance finished cleaning up.", Helpers.LogLevel.Debug);
405 void mainForm_Load(object sender, EventArgs e)
407 pluginManager.StartPlugins();
410 void netcom_ClientConnected(object sender, EventArgs e)
414 if (!Directory.Exists(ClientDir))
415 Directory.CreateDirectory(ClientDir);
419 Logger.Log("Failed to create client directory", Helpers.LogLevel.Warning, ex);
422 clientSettings = new Settings(Path.Combine(ClientDir, "client_settings.xml"));
426 void Avatars_UUIDNameReply(object sender, UUIDNameReplyEventArgs e)
430 foreach (KeyValuePair<UUID, string> av in e.Names)
432 if (!nameCache.ContainsKey(av.Key))
434 nameCache.Add(av.Key, av.Value);
441 /// Fetches avatar name
443 /// <param name="key">Avatar UUID</param>
444 /// <param name="blocking">Should we wait until the name is retrieved</param>
445 /// <returns>Avatar name</returns>
446 public string getAvatarName(UUID key, bool blocking)
449 return getAvatarName(key);
453 using (ManualResetEvent gotName = new ManualResetEvent(false))
456 EventHandler<UUIDNameReplyEventArgs> handler = (object sender, UUIDNameReplyEventArgs e) =>
458 if (e.Names.ContainsKey(key))
465 client.Avatars.UUIDNameReply += handler;
466 name = getAvatarName(key);
468 if (name == INCOMPLETE_NAME)
470 gotName.WaitOne(10 * 1000);
473 client.Avatars.UUIDNameReply -= handler;
480 /// Fetches avatar name from cache, if not in cache will requst name from the server
482 /// <param name="key">Avatar UUID</param>
483 /// <returns>Avatar name</returns>
484 public string getAvatarName(UUID key)
488 if (key == UUID.Zero)
490 return "(???) (???)";
492 if (nameCache.ContainsKey(key))
494 return nameCache[key];
498 client.Avatars.RequestAvatarName(key);
499 return INCOMPLETE_NAME;
504 public void getAvatarNames(List<UUID> keys)
508 List<UUID> newNames = new List<UUID>();
509 foreach (UUID key in keys)
511 if (!nameCache.ContainsKey(key))
516 if (newNames.Count > 0)
518 client.Avatars.RequestAvatarNames(newNames);
523 public bool haveAvatarName(UUID key)
527 if (nameCache.ContainsKey(key))
534 void Groups_GroupsChanged(object sender, EventArgs e)
536 client.Groups.RequestCurrentGroups();
539 public static string SafeFileName(string fileName)
541 foreach (char lDisallowed in Path.GetInvalidFileNameChars())
543 fileName = fileName.Replace(lDisallowed.ToString(), "_");
549 public void LogClientMessage(string fileName, string message)
555 foreach (char lDisallowed in System.IO.Path.GetInvalidFileNameChars())
557 fileName = fileName.Replace(lDisallowed.ToString(), "_");
560 File.AppendAllText(Path.Combine(ClientDir, fileName),
561 DateTime.Now.ToString("yyyy-MM-dd [HH:mm:ss] ") + message + Environment.NewLine);
563 catch (Exception) { }
567 void Groups_CurrentGroups(object sender, CurrentGroupsEventArgs e)
569 this.groups = e.Groups;
572 private void InitializeLoggingAndConfig()
576 userDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), Properties.Resources.ProgramName);
577 if (!Directory.Exists(userDir))
579 Directory.CreateDirectory(userDir);
584 userDir = System.Environment.CurrentDirectory;
587 globalLogFile = Path.Combine(userDir, Properties.Resources.ProgramName + ".log");
588 globalSettings = new Settings(Path.Combine(userDir, "settings.xml"));
591 public GridClient Client
593 get { return client; }
596 public RadegastNetcom Netcom
598 get { return netcom; }
601 public StateManager State
603 get { return state; }
606 public frmMain MainForm
608 get { return mainForm; }
611 public TabsConsole TabConsole
613 get { return mainForm.TabConsole; }
616 public void HandleThreadException(object sender, ThreadExceptionEventArgs e)
618 Logger.Log("Unhandled Thread Exception: "
619 + e.Exception.Message + Environment.NewLine
620 + e.Exception.StackTrace + Environment.NewLine,
621 Helpers.LogLevel.Error,
629 #region Event classes
630 public class ClientChangedEventArgs : EventArgs
632 private GridClient m_OldClient;
633 private GridClient m_Client;
635 public GridClient OldClient { get { return m_OldClient; } }
636 public GridClient Client { get { return m_Client; } }
638 public ClientChangedEventArgs(GridClient OldClient, GridClient Client)
640 m_OldClient = OldClient;
644 #endregion Event classes